当前位置: 首页 > news >正文

C++ std::vector 超详细指南:基础实践(手搓vector)

目录

一.基本概念

二.类的常用接口说明

1.类对象的常见构造

2. vector类空间变化

1).size()(获取数据个数)

2).capacity()(获取容量大小)

3).empty()(判断是否为空)

4).resize()(改变vector的size) 

5).reserve()(改变vector的capacity)

6).注意

 3.vector iterator (迭代器)的使用

1).begin()+end()(获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator)

2).rbegin()+rend()(获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置)

 4.vector 增删查改

 1).push_back()(尾插)

 2).pop_back()(尾删) 

 3).insert()(在position之前插入val)

 4).erase()(删除position位置的数据)

 5).swap()(交换两个vector的数据空间)

 6).operator[ ]()(像数组一样访问)

三.vector 迭代器失效问题

1.导致迭代器失效的常见操作

1).插入元素(push_back/insert/emplace_back) 

2).删除元素(erase/pop_back)

3).容量调整(resize/reserve/shrink_to_fit)

2.典型错误场景 

1).循环中删除元素 

2).插入后使用旧迭代器

四.vector类的模拟实现 

1.vector.h 

2.vector.cpp(内容实现在.h里。我就不分离了,如需分离可自行实现)


一.基本概念


  • vector是STL提供的动态数组容器

  • 使用vector要包含头文件:#include <vector>

  • 特性:

    • 支持随机访问(O(1)时间复杂度)

    • 动态扩容(自动管理内存)

    • 尾部插入/删除高效(O(1)时间复杂度)

    • 连续内存空间存储元素

二.类的常用接口说明


1.类对象的常见构造

 我们可以通过监视窗口查看构造完初始化的值

#include <vector>
#include <iostream>
using namespace std;

int main() {
    // 空vector
    vector<int> v1;
    cout << "v1 size: " << v1.size() << endl;  // 0

    // 包含5个默认初始化的int(0)
    vector<int> v2(5);
    cout << "v2: ";
    for(int n : v2) cout << n << " ";  // 0 0 0 0 0 

    // 包含3个值为10的元素
    vector<int> v3(3, 10);
    cout << "\nv3: ";
    for(int n : v3) cout << n << " ";  // 10 10 10

    // 拷贝构造
    vector<int> v4(v3);
    cout << "\nv4: ";
    for(int n : v4) cout << n << " ";  // 10 10 10

    // 用数组初始化
    int arr[] = {1,3,5,7};
    vector<int> v5(arr, arr + sizeof(arr)/sizeof(int));
    cout << "\nv5: ";
    for(int n : v5) cout << n << " ";  // 1 3 5 7

    // C++11初始化列表
    vector<int> v6{2,4,6,8};
    cout << "\nv6: ";
    for(int n : v6) cout << n << " ";  // 2 4 6 8
}
构造函数声明接口说明
vector()无参构造
vector(size_type n, const value_type& val =value_type())构造并初始化n个val
vector (const vector& x)拷贝构造
vector (InputIterator first, InputIterator last);

使用迭代器进行初始化构

2. vector类空间变化

1).size()(获取数据个数)


vector<int> v(10, 1);  
cout << v.size() << endl;     //10

 2).capacity()(获取容量大小)


	vector<int> v(10, 1);
	cout << v.capacity() << endl;      //10

3).empty()(判断是否为空)


	vector<int> v;
	cout << v.empty()<<endl;      //1

4).resize()(改变vector的size) 



#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v(10, 1);

	v.resize(15, 2);
	cout << v.size() << endl;    //15
	cout << v.capacity() << endl;     //15

	v.resize(25, 3);
	cout << v.size() << endl;     //25
	cout << v.capacity() << endl;     //25

	v.resize(5);
	cout << v.size() << endl;     //5
	cout << v.capacity() << endl;     //25

}

5).reserve()(改变vector的capacity)


	vector<int> v(10, 1);
	v.reserve(20);
	cout << v.size() << endl;     //10 
	cout << v.capacity() << endl;     //20

6).注意

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为
  •  reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题
  • resize在开空间的同时还会进行初始化,影响size 
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{
	vector<int> v;
	size_t sz = v.capacity();
	v.reserve(100); // 提前将容量设置好,可以避免一遍插入一遍扩容
	cout << "making bar grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

 3.vector iterator (迭代器)的使用

1).begin()+end()(获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator)


vector<int> v(10, 1);
v.resize(15, 2);
vector<int>::iterator beg =v.begin();
vector<int>::iterator end =v.end()-1;  //end是 \0 无法访问
cout << *beg<<" "<<*end;     //1   2

 2).rbegin()+rend()(获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置)


	vector<int> v(10, 1);
	v.resize(15, 2);
	vector<int>::reverse_iterator rbeg =v.rbegin();
	vector<int>::reverse_iterator rend =v.rend()-1;
	cout << *rbeg<<" "<<*rend;     //2   1

 4.vector 增删查改

 1).push_back()(尾插)


	vector<string> v1;
	string s1("xxxx");
	v1.push_back(s1);
	v1.push_back("yyyyy");
	for (const auto& e : v1)
	{
		cout << e << " ";      //xxxx yyyyy
	}
	cout << endl;

 2).pop_back()(尾删) 


	vector<string> v1;
	string s1("xxxx");
	v1.push_back(s1);
	v1.push_back("yyyyy");
	v1.pop_back();
	for (const auto& e : v1)
	{
		cout << e << " ";      //xxxx
	}
	cout << endl;

 3).insert()(在position之前插入val)


	vector<int> v(10, 1);
	v.insert(v.begin(), 0);
	v.insert(v.begin() + 3, 10);
	for (auto e : v)
	{
		cout << e << " ";     //0 1 1 10 1 1 1 1 1 1 1 1
	}
	cout << endl;

  3).erase()(删除position位置的数据)


	vector<int> v(10, 1);
	v.insert(v.begin(), 0);
	v.insert(v.begin() + 3, 10);
	for (auto e : v)
	{
		cout << e << " ";     //0 1 1 10 1 1 1 1 1 1 1 1
	}
	cout << endl;
	v.erase(v.begin());  //参数必须是迭代器
	v.erase(v.begin(), v.begin()+2);
	for (auto e : v)
	{
		cout << e << " ";     //10 1 1 1 1 1 1 1 1
	}

   4).swap()(交换两个vector的数据空间)


vector<int> v(10, 1);
vector<int> c(10, 2);
v.swap(c);

for (auto e : v)
{
	cout << e << " ";     //2 2 2 2 2 2 2 2 2 2
}
cout << endl;
for (auto e : c)
{
	cout << e << " ";    //1 1 1 1 1 1 1 1 1 1
}

 5).operator[ ]()(像数组一样访问)


	vector<int> v(10, 1);
	for (size_t i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";    //1 1 1 1 1 1 1 1 1 1
	}

三.vector 迭代器失效问题


迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃) 。

1.导致迭代器失效的常见操作

1). 插入元素(push_back/insert/emplace_back) 

  • 扩容触发:当插入元素导致容量不足时,vector会重新分配内存,原有所有迭代器、指针和引用均失效。

  • 中间插入:使用insert在中间插入元素,插入位置之后的迭代器失效(元素后移)

vector<int> v = {1, 2, 3};
auto it = v.begin() + 1; // 指向2

// 插入导致扩容
v.push_back(4); // 可能触发扩容,所有迭代器失效
// cout << *it; // 未定义行为(可能崩溃或输出垃圾值)

// 中间插入
auto new_it = v.begin() + 1;
v.insert(new_it, 5); // 插入后元素变为[1,5,2,3,4]
// 原new_it之后的迭代器(指向2的位置)失效

2). 删除元素(erase/pop_back

  • 尾部删除pop_back仅使最后一个元素的迭代器失效。

  • 中间删除erase会使被删除元素及其之后的所有迭代器失效(元素前移)

vector<int> v = {1, 2, 3, 4, 5};
auto it1 = v.begin() + 2; // 指向3
auto it2 = v.begin() + 3; // 指向4

v.erase(it1); // 删除3,元素变为[1,2,4,5]
// it1和it2均失效(原位置后的元素前移)

3).容量调整(resize/reserve/shrink_to_fit

  • 扩容resizereserve导致容量增加时,若触发内存重分配,所有迭代器失效。

  • 缩容shrink_to_fit可能缩小容量,导致迭代器失效。

vector<int> v = {1, 2, 3};
auto it = v.begin();

v.reserve(100); // 若当前capacity < 100,触发重分配
// it失效

v.resize(2);    // 不改变容量,仅修改size
// it可能仍有效(取决于是否重分配)

v.shrink_to_fit(); // 请求缩容到size=2
// 可能触发重分配,it失效

2.典型错误场景 

1).循环中删除元素 

vector<int> v = {1, 2, 3, 4, 5};

// 错误写法:erase后继续使用失效的迭代器
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it % 2 == 0) {
        v.erase(it); // erase后it失效,后续++it导致未定义行为
    }
}

// 正确写法:利用erase返回值更新迭代器
for (auto it = v.begin(); it != v.end(); ) {
    if (*it % 2 == 0) {
        it = v.erase(it); // erase返回下一个有效迭代器
    } else {
        ++it;
    }
}

2).插入后使用旧迭代器

vector<int> v = {1, 2, 3};
auto it = v.begin() + 1; // 指向2

v.push_back(4); // 可能触发扩容,it失效
// cout << *it; // 危险!

// 正确做法:插入后重新获取迭代器
it = v.begin() + 1; // 重新定位

四.vector类的模拟实现 

1.vector.h 

#pragma once
#include <iostream>
#include <vector>
using namespace std;

namespace bit
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef T* const_iterator;
		// C++11 强制生成默认构造
		vector() = default;
		vector(const vector<T>& v)
		{
			reserve(v.size());
			for (auto& e : v)
			{
				push_back(e);
			}
		}
		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

		vector(int n, const T& val = T())
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		void clear()
		{
			_finish = _start;
		}
		// v1 = v3
		/*vector<T>& operator=(const vector<T>& v) //传统写法
		{
			if (this != &v)
			{
				clear();
				reserve(v.size());
				for (auto& e : v)
				{
					push_back(e);
				}
			}
			return *this;
		}*/
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}
		//现代写法
		vector<T>& operator=(vector<T> v)  //注意要使用传值,不能使用引用
		{
			swap(v);
			return *this;
		}
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t old_size = size();
				T* tmp = new T[n];
				for (size_t i = 0; i < old_size; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;
				_start = tmp;
				_finish = tmp + old_size;
				_end_of_storage = tmp + n;
			}
		}

		void resize(size_t n, T val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish < _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}
		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		bool empty() const
		{
			return _start == _finish;
		}
		void push_back(const T& x)
		{
			// 扩容
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x;
			++_finish;
		}
		void pop_back()
		{
			assert(!empty());
			--_finish;
		}
		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			// 扩容
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
			return pos;
		}
		void erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

			iterator it = pos + 1;
			while (it != end())
			{
				*(it - 1) = *it;
				++it;
			}
			--_finish;
		}
		T& operator[](size_t i)
		{
			assert(i < size());
			return _start[i];
		}

		const T& operator[](size_t i) const
		{
			assert(i < size());
			return _start[i];
		}

	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
}

template<class Container>
void print_container(const Container& v)
{
	for (auto e : v)
	{
		std::cout << e << " ";
	}
	std::cout << std::endl;
}

 2.vector.cpp(内容实现在.h里。我就不分离了,如需分离可自行实现)

#include"vector.h"

namespace bit
{
	template<class T>
	void vector<T>::reserve(size_t n)
	{
		if (n > capacity())
		{
			size_t old_size = size();
			T* tmp = new T[n];
			for (size_t i = 0; i < old_size; i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
			_start = tmp;
			_finish = tmp + old_size;
			_end_of_storage = tmp + n;
		}
	}
	template<class T>
	void vector<T>::resize(size_t n, T val)
	{
		if (n < size())
		{
			_finish = _start + n;
		}
		else
		{
			reserve(n);
			while (_finish < _start + n)
			{
				*_finish = val;
				++_finish;
			}
		}
	}
}

相关文章:

  • FFMPEG利用H264+AAC合成TS文件
  • 关于tresos Studio(EB)的MCAL配置之GPT
  • Netty笔记6:Netty组件
  • 剑指 Offer II 060. 出现频率最高的 k 个数字
  • [Redis] 终极缓存四连杀:缓存预热、缓存击穿、缓存穿透、缓存雪崩,真的懂了吗?
  • XHR请求解密:抓取动态生成数据的方法
  • 【django初学者项目】
  • Unity3D 布料模拟(Cloth Simulation)详解
  • 计算机网络(1) 网络通信基础,协议介绍,通信框架
  • 【杂谈】信创电脑华为w515(统信系统)登录锁定及忘记密码处理
  • JVM简单了解
  • Avalonia 打包成deb
  • Stream流的核心思想
  • 为AI聊天工具添加一个知识系统 之130 详细设计之71 通用编程语言 之1
  • MAUI(C#)安卓开发起步
  • #define GBB_DEPRECATED_MSG(msg) __declspec(deprecated(msg))
  • VIA的寄生电感和Stub对高速信号的影响
  • 【大模型安全】大模型的技术风险
  • 【虚拟仿真】Unity3D中实现激光/射线的发射/折射/反射的效果(3D版)
  • 嵌入式L6计算机网络
  • 武汉警方通报一起故意伤害案件:1人死亡,嫌疑人已被抓获
  • 第十届青春文学奖揭晓,梁晓声获特别奖
  • 复旦建校120周年大型义诊举行,百余名专家服务市民超三千人次
  • 蒲慕明院士:未来数十年不是AI取代人,而是会用AI的人取代不会用的
  • 日本一季度实际GDP环比下降0.2%
  • 北京警方:海淀发生小客车刮碰行人事故4人受伤,肇事司机已被查获