文章目录
-
- [C++ STL Vector 深度剖析:从内存管理到性能优化](#C++ STL Vector 深度剖析:从内存管理到性能优化)
- 正文
-
- [一. vector的构造方式](#一. vector的构造方式)
- [二. vector与容量相关的接口](#二. vector与容量相关的接口)
-
- [2.1 vector的size()与capacity()接口](#2.1 vector的size()与capacity()接口)
- [2.2 vector的resize()接口](#2.2 vector的resize()接口)
- [2.3 vector的empty()接口](#2.3 vector的empty()接口)
- [2.4 vector的reserve()接口](#2.4 vector的reserve()接口)
- 2.5vector的shrink_to_fit()接口
- [三 vector访问数据的相关接口](#三 vector访问数据的相关接口)
-
- [3.1 vector重载的operator\[\]运算符与at()接口](#3.1 vector重载的operator[]运算符与at()接口)
- [3.2 vector的front()与back()接口](#3.2 vector的front()与back()接口)
- [3.3 vector的data()接口](#3.3 vector的data()接口)
- [四 vector中关于增删查改的相关接口](#四 vector中关于增删查改的相关接口)
-
- [4.1 vector的push_back()于pop_back()接口](#4.1 vector的push_back()于pop_back()接口)
- [4.2 vector的assign()接口](#4.2 vector的assign()接口)
- [4.3 vector的insert()相关接口](#4.3 vector的insert()相关接口)
- [4.4 vector的erase()与clear()接口](#4.4 vector的erase()与clear()接口)
- [4.5 vector中迭代器失效的原因](#4.5 vector中迭代器失效的原因)
C++ STL Vector 深度剖析:从内存管理到性能优化
| 本篇文章主要讲解的是vector中比较常用的接口以及其中迭代器失效的问题 |
|---|
正文
vector文档链接:https://legacy.cplusplus.com/reference/vector/vector/?kw=vector
一. vector的构造方式
vector有很多的构造方式,我们可以根据自己的需求进行选择,用以满足我们的需求:
cpp
在这里插入代码片
vector<int> a;//空容器
vector<int> b(3, 10);//填充容器(10,10,10);
vector<int> c{1, 2, 3, 4};//直接初始化容器
vector<int> d(c);//拷贝构造
//vector<int> d = c这个也是拷贝构造
int op[10] = { 0 };
vector<int> e(op, op + 3);//迭代器区间初始化容器(0,0,0);
这五个是比较常用的vector的构造方式
二. vector与容量相关的接口
2.1 vector的size()与capacity()接口
size跟之前的string一样是获取当前元素的个数,我们可以直接进行使用。
capacity是获取当前的空间容量。
还有一个max_size()的接口,它是用来获取当前类型理论上的最大容量
2.2 vector的resize()接口
resize的用法跟string中的resize的用法很相似,都是用来控制元素的个数的。
cpp
在这里插入代码片
std::vector<int> myvector;
// set some initial content:
for (int i=1;i<10;i++) myvector.push_back(i);
myvector.resize(5);
myvector.resize(8,100);
myvector.resize(12);
std::cout << "myvector contains:";
for (int i=0;i<myvector.size();i++)
std::cout << ' ' << myvector[i];
std::cout << '\n';
输出的结果:
cpp
在这里插入代码片
myvector contains: 1 2 3 4 5 100 100 100 0 0 0 0
| 场景 resize(n) resize(n, 10) | |
| n > 当前 size 扩充,新增元素填充 0(默认值) 扩充,新增元素填充 10 | |
| n < 当前 size 切割,删除多余元素,不可恢复 切割,删除多余元素,不可恢复 | |
| n == 当前 size 什么都不做 什么都不做 |
上面就是resize的工作原理。
2.3 vector的empty()接口
这个接口一看名字我们就知道它是用来判断当前的容器是否为空的 ,是一个比较常用的接口.
2.4 vector的reserve()接口
reserve他的中文翻译由保留的意思,他的作用就是预分配空间.,如果reserve分配的空间大于等于capacity就会扩容到与reserve一致的空间,如果小于capacity就什么也不做。需要注意的是capacity是不会缩容的。
cpp
在这里插入代码片
vector<int> a(10, 1);
a.reserve(20);
cout << a.size() << endl;
cout << a.capacity() << endl;
a.reserve(15);
cout << a.size() << endl;
cout << a.capacity() << endl;
a.reserve(5);
cout << a.size() << endl;
cout << a.capacity() << endl;
从上面的代码,我们可以验证reserve是坚决不缩容的在vscode上。
2.5vector的shrink_to_fit()接口
shrink_to_fit()这个接口的意思是释放多余的空间,但是这个并不是强制的,编译器可能会听可能不会听,像嵌入式某些地方可能为了性能,并不会缩容。
cpp
在这里插入代码片
vector<int> a(1000, 1);
a.resize(10);
cout << a.capacity() << endl;
a.shrink_to_fit();
cout << a.capacity() << endl;
但是需要注意的是,如果频繁的调用这个接口进行缩减空间,会造成性能的下降。如果需要快速的进行空间进行缩减,可以使用下面的方法:
cpp
在这里插入代码片
vector<int> a(1000, 1);
a.resize(10);
cout << a.capacity() << endl;
vector<int> (a).swap(a);//这个是a的副本等价于temp(a).swap;
cout << a.capacity() << endl;
这个代码是通过拷贝构造之后,再通过交换两者的指针状态达到缩减空间的效果。
三 vector访问数据的相关接口
3.1 vector重载的operator\[\]运算符与at()接口
重载的operator\[\]使得vector可以像数组一样,通过下标准确访问数据。
这里是与string的接口是一致的,重载的\[\]是不会进行边界检查的,但是at()是会进行边界检查的。
3.2 vector的front()与back()接口
这里的front与back与string中的接口是保持一致的,获取第一个数据与最后一个数据。
3.3 vector的data()接口
cpp
在这里插入代码片
std::vector<int> myvector (5);
int* p = myvector.data();
*p = 10;
++p;
*p = 20;
p[2] = 100;
std::cout << "myvector contains:";
for (unsigned i=0; i<myvector.size(); ++i)
std::cout << ' ' << myvector[i];
std::cout << '\n';
data() 返回的是这个容器的指针(底层数组的指针),我们对这个指针进行的操作都同步于容器中的数据。这个接口并不常用,了解即可。
四 vector中关于增删查改的相关接口
4.1 vector的push_back()于pop_back()接口
这两个接口是与string的相关接口是保持一致的,就是尾插与尾删。
4.2 vector的assign()接口
这个接口是不太常用的,我们仅需了解即可。他的一种用法就是对数据进行赋值,但是他的赋值会直接将原来容器中的数据全部清除,然后使用自己的数据替代,还有一种用法是通过迭代器进行更改数据的,也会清除原来容器的数据。
无论哪种用法,assign() 都会先清空容器原有数据,再用新数据填充。
cpp
在这里插入代码片
vector<int> v{1, 2, 3, 4, 5};
cout << "原始: ";
for (int x : v) cout << x << " ";
cout << endl;
// 2. assign 填充版本:3 个 100
v.assign(3, 100);
cout << "assign(3, 100): ";
for (int x : v) cout << x << " ";
cout << endl;
// 3. assign 初始化列表版本
v.assign({10, 20, 30, 40});
cout << "assign({10,20,30,40}): ";
for (int x : v) cout << x << " ";
cout << endl;
// 4. assign 迭代器版本:从数组复制
int arr[] = {88, 99, 77};
v.assign(arr, arr + 3);
cout << "assign(arr, arr+3): ";
for (int x : v) cout << x << " ";
cout << endl;
4.3 vector的insert()相关接口
vector的insert相关用法与string中的使用很多的相似性的,他也具有指定位置的插入与指定位置插入多个数据以及迭代器区间插入数据
cpp
在这里插入代码片
vector<int> v{1, 2, 3};
// 1. 插入单个元素
v.insert(v.begin() + 1, 99); // {1, 99, 2, 3}
// 2. 插入 n 个相同元素
v.insert(v.end(), 3, 100); // {1, 99, 2, 3, 100, 100, 100}
// 3. 插入初始化列表
v.insert(v.begin(), {88, 89}); // {88, 89, 1, 99, 2, 3, 100, 100, 100}
// 4. 插入迭代器区间(从数组复制)
int arr[] = {50, 60};
v.insert(v.begin() + 2, arr, arr + 2); // {88, 89, 50, 60, 1, ...}
// 输出
for (int x : v) cout << x << " ";
cout << endl;
但是我们需要注意的是,使用迭代器区间进行插入的时候,他是有可能失效的,这个在文章的后面会进行讲解。
4.4 vector的erase()与clear()接口
erase我们可以从他的名字就可以知道,是用来删除数据的,他有两种方式进行删除数据,一种是指定位置进行删除数据,一种是进行区间删除数据。
下面是测试用例:
cpp
在这里插入代码片
vector<int> v{10, 20, 30, 40, 50};
// 1. 删除单个:删除索引 2(30)
v.erase(v.begin() + 2);
cout << "删除单个: ";
for (int x : v) cout << x << " "; // 10 20 40 50
cout << endl;
// 2. 删除区间:删除索引 1~2(20,40)
v = {10, 20, 30, 40, 50};
v.erase(v.begin() + 1, v.begin() + 3);
cout << "删除区间: ";
for (int x : v) cout << x << " "; // 10 40 50
cout << endl;
而clear比erase更简单粗暴,就是将容器内的所有元素进行清除。
4.5 vector中迭代器失效的原因
我们想要知道迭代器为什么失效,我们首先要了解,迭代器的是类似于指针的,我们知道指针失效的原因是指向的空间发生改变对不对,所以指针失效的原因简单理解就是指向的空间发生了改变。
当容器进行扩容的时候,此时的迭代器还指向原来旧的空间,此时与野指针一样,他是无法进行工作的即迭代器失效。
例子:
cpp
在这里插入代码片
vector<int> v{1, 2, 3, 4, 5}; // capacity = 5,已满
auto it = v.begin() + 2; // 指向 3
v.insert(v.begin(), 99); // 扩容!it 失效
v.reserve(10); // 预留空间
v = {1, 2, 3, 4, 5};
auto it2 = v.begin() + 2; // 指向 3
v.insert(v.begin(), 99); // 不扩容,但元素后移
cout << *it2; // 现在指向 2(不是原来的 3)
上面这个代码会直接崩溃,就是因为it指向了原来的就空间导致其成为了野指针。
**总结:**vector 是动态数组:构造灵活、容量可调、访问像数组、修改需注意性能,迭代器失效是核心坑点