【C++】STL详解(三)—vector使用手册:不看你会后悔
坚持用 清晰易懂的图解 + 代码语言,让每个知识点变得简单!
🚀呆头个人主页详情
🌱 呆头个人Gitee代码仓库
📌 呆头详细专栏系列
座右铭: “不患无位,患所以立。”
【C++】STL详解(三)—vector使用手册:不看你会后悔
- 摘要
- 目录
- 一、vector的介绍
- 二、vector的使用
vector文档参考----------请点击
摘要
🚀 欢迎来到《C++修炼之路》!
这里是C++程序员的成长乐园,带你领略从面向对象到现代C++的精彩世界。我们将>用简洁的代码和生动的案例,助你掌握C++核心精髓。
🔍 专栏亮点:
- 现代C++特性解析(C++11/14/17)
- STL源码剖析与实战应用
- 内存管理与性能优化技巧
💡 收获预期:
✔️ 写出更健壮的C++代码
✔️ 深入理解面向对象设计
✔️ 掌握模板编程基础📌 编程箴言:
“好的C++代码就像好酒,需要时间沉淀。”
(正文开始👇)—— + 本篇讲解vector的使用
目录
一、vector的介绍
-
定义:
vector
是一个表示 可变大小数组 的序列容器。 -
存储方式: 与数组一样,
vector
使用 连续内存空间 存储元素,因此可以通过下标随机访问,时间复杂度为O(1)
。 -
动态扩容: 与普通数组不同,
vector
的大小可以动态改变。当空间不足时,会分配新的更大内存,把原有元素拷贝过去,再释放旧空间。 -
空间策略:
vector
会 预留额外空间 来减少频繁扩容。不同实现的扩容策略不同,但通常是以倍数方式增长,从而保证 均摊插入复杂度为O(1)
。 -
性能特点:
- 访问元素效率高(支持随机访问)。
- 在末尾插入/删除效率高。
- 在中间或开头插入/删除效率低(需要移动大量元素)。
👉 一句话总结:
vector
是 C++ 中最常用的容器,本质是一个能自动扩容的动态数组,既有数组的高效访问,又比数组更灵活。
二、vector的使用
1.vector的定义方式
构造一个空的 vector(任意类型)
注意:这只是一个示例,注意是任意类型,不止如下的类型
vector<int> v1; //构造int类型的空容器vector<double> v2; //构造double类型的空容器vector<char> v3; //构造char类型的空容器vector<string> v4; //构造string类型的空容器
构造一个含有 n 个元素的 vector,每个元素的值都是 val
vector v1(n个元素,值val);
vector<int> v1(10,2);vector<double> v2(10,0.0);vector<char> v3(10,\'d\');
拷贝构造函数,用已有的 vector 构造新的 vector
vector<int> v1(v2); //拷贝构造int类型的v2容器的复制品
用区间 [first, last) 中的元素构造 vector
vector<int> v2(v1.begin(), v1.end()); //使用迭代器拷贝构造v2容器的某一段内容
2.迭代器的使用
在
vector
中,迭代器的底层实现通常就是一个普通指针,因为vector
的元素存储在连续的内存空间里,用指针就能完成迭代器的所有功能。因此在vector
阶段,迭代器本质上等同于指针。但在其他容器(如
list
、map
)中,元素存储方式不同,不一定是连续内存,这时迭代器并不是单纯的指针,而是一个 封装了指针行为的类对象,通过运算符重载来模拟“像指针一样使用”。
begin和end
begin函数可以得到容器中第一个元素的正向迭代器,通过end函数可以得到容器中最后一个元素的下一个位置的正向迭代器。
int main(){vector<int> v(6, 6);vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << \" \" ;++it;}cout << endl;return 0;}
此处仅展示普通vector对象的迭代器使用,因为容器的迭代器的使用都是相通的
rbegin和rend
rbegin函数可以得到容器中最后一个元素的反向迭代器,通过rend函数可以得到容器中第一个元素的前一个位置的反向迭代器
int main(){vector<int> v1;v1.push_back(5);v1.push_back(2);v1.push_back(0);vector<int>::reverse_iterator rit = v1.rbegin();while (rit != v1.rend()){cout << *rit << \" \";++rit;}return 0;}
运行结果如下:
3.空间的增长问题
size和capacity
通过size函数获取当前容器中的有效元素个数,通过capacity函数获取当前容器的最大容量。
#include#includeusing namespace std;void test01(){vector<int> v1(6, 6);cout << \"size:\" << v1.size() << endl;//获取当前容器有效数据个数cout << \"capacity:\" << v1.capacity() << endl;//获取当前容器最大容量}int main(){test01();return 0;}
运行结果如下:
reserve和reszie
2. 当所给值 ≤ 当前 capacity 时,不做任何操作。
2. 当所给值 < 当前 size 时,缩小 size 到该值,超出部分元素被移除。
#include#includeusing namespace std;void test01(){vector<int> v1(6, 6);cout << \"size: \" << v1.size() << endl;//获取当前容器有效数据个数 6cout << \"capacity: \" << v1.capacity() << endl;//获取当前容器最大容量 6v1.reserve(20);//修改容器最大容量为20cout << \"size: \" << v1.size() << endl;//6cout << \"capacity: \" << v1.capacity() << endl;//20v1.resize(10);cout << \"size: \" << v1.size() << endl;//10,剩余空间默认为0cout << \"capacity: \" << v1.capacity() << endl;//10v1.resize(15,8);cout << \"size: \" << v1.size() << endl;//15,剩余空间补8cout << \"capacity: \" << v1.capacity() << endl;//15}int main(){test01();return 0;}
运行结果如下:
empty
通过empty函数判断当前容器是否为空。
#include#includeusing namespace std;void test02(){vector<int> v2(6, 6);vector<int> v3;cout << \"v2: \" << v2.empty() << endl;cout << \"v3: \" << v3.empty() << endl;}int main(){test02();return 0;}
运行结果如下:
4.vector增删查改
push_back和pop_back
通过push_back函数对容器进行尾插,pop_back函数对容器进行尾删。
#include#includeusing namespace std;void test03(){vector<int> v1;v1.push_back(5);v1.push_back(2);v1.push_back(0);v1.push_back(1);v1.push_back(3);v1.push_back(1);v1.push_back(4);v1.pop_back();v1.pop_back();}int main(){//test01();//test02();test03();return 0;}
代码运行如下:
insert和erase
通过insert函数可以在所给迭代器pos位置插入一个或多个元素,通过erase函数可以删除所给迭代器pos位置的元素,或删除所给迭代器区间内的所有元素(左闭右开)。
#include#includeusing namespace std;void test04(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.insert(v.begin(), 0); //在容器开头插入0v.insert(v.begin(), 5, -1); //在容器开头插入5个-1v.erase(v.begin()); //删除容器中的第一个元素v.erase(v.begin(), v.begin() + 5); //删除在该迭代器区间内的元素(左闭右开)return 0;}int main(){//test01();//test02();//test03();test04();return 0;}
运行结果如下:
以上是按位置进行插入或删除元素的方式,若要按值进行插入或删除(在某一特定值位置进行插入或删除),则需要用到find函数。
find函数:
find函数共三个参数,前两个参数确定一个迭代器区间(左闭右开),第三个参数确定所要寻找的值。
find函数在所给迭代器区间寻找第一个匹配的元素,并返回它的迭代器,若未找到,则返回所给的第二个参数。注意: find函数是在算法模块(algorithm)当中实现的,不是vector的成员函数。
#include#include#include using namespace std;void test04(){vector<int> v;v.push_back(5);v.push_back(2);v.push_back(0);v.insert(v.begin(), 1); //在容器开头插入1vector<int>::iterator pos = find(v.begin(), v.end(), 2); //获取值为2的元素的迭代器v.insert(pos, 10); //在2的位置插入10pos = find(v.begin(), v.end(), 0); //获取值为0的元素的迭代器v.erase(pos); //删除0}int main(){//test01();//test02();//test03();test04();return 0;}
运行结果如下:
swap
通过swap函数可以交换两个容器的数据空间,实现两个容器的交换。
#include#includeusing namespace std;void test05(){vector<int> v1(6, 6);vector<int> v2(6, 8);cout << \"v1的数据为:\"; for (auto e : v1){cout << e << \' \';}cout << endl;cout << \"v2的数据为:\";for (auto e : v2){cout << e << \' \';}cout << endl << endl;v1.swap(v2);cout << \"v1的数据为:\";for (auto e : v1){cout << e << \' \';}cout << endl;cout << \"v2的数据为:\";for (auto e : v2){cout << e << \' \';}cout << endl;}int main(){//test01();//test02();//test03();//test04();test05();return 0;}
运行结果如下:
元素访问operator[ ]
vector当中实现了 [ ] 操作符的重载,因此我们也可以通过“下标+[ ]”的方式对容器当中的元素进行访问。
#include#includeusing namespace std;#include void test06(){vector<int> v(10, 1);//使用“下标+[]”的方式遍历容器for (size_t i = 0; i < v.size(); i++){cout << v[i] << \" \";}cout << endl;}int main(){//test01();//test02();//test03();//test04();//test05();test06();return 0;}
运行结果如下:
5.迭代器失效问题
迭代器的主要作用是屏蔽底层实现细节,使我们在操作不同容器时无需关心其内部数据结构。例如,在
vector
中,迭代器的底层本质上就是一个普通指针。所谓迭代器失效,是指迭代器底层所依赖的指针指向的内存空间已经被释放或移动,导致该迭代器无法再正确访问数据。如果在失效后继续使用该迭代器,就会访问到一块已经无效的内存区域,从而可能引发程序异常甚至崩溃。
示例1
#include #include #include using namespace std;int main(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//v: 1 2 3 4 5vector<int>::iterator pos = find(v.begin(), v.end(), 2); //获取值为2的元素的迭代器v.insert(pos, 10); //在值为2的元素的位置插入10//v: 1 10 2 3 4 5v.erase(pos); //删除元素2 ???error(迭代器失效)//v: 1 2 3 4 5return 0;}
这里 pos
指向容器中值为 2
的元素。此时容器内容为:
v: 1 2 3 4 5 ↑ pos
在 pos
所指元素(值为 2
)之前插入一个 10
,容器变为:
v: 1 10 2 3 4 5 ↑ pos
关键点:
vector::insert
可能导致 扩容,扩容会使所有迭代器失效。- 即使没有扩容,插入操作也会导致插入位置及其之后的所有迭代器失效。
- 因为
vector
是连续存储的,插入元素需要搬移后续元素到新位置。- 所以,
pos
在insert
之后已经不再是合法迭代器。
这里继续使用pos
,但pos
已经失效。- 它指向的内存位置已经被移动,不再代表原来的元素。
- 访问或传入 STL 算法时会导致 未定义行为,可能崩溃,也可能表现异常。
正确写法
要避免失效,可以在 insert
后重新获取迭代器:
pos = find(v.begin(), v.end(), 2); // 重新定位v.erase(pos);
示例2
#include #include using namespace std;int main(){vector<int> v;for (size_t i = 1; i <= 6; i++){v.push_back(i);}vector<int>::iterator it = v.begin();while (it != v.end()){if (*it % 2 == 0) //删除容器当中的全部偶数{v.erase(it);}it++;}return 0;}
- 在
vector
中,erase(it)
会删除it
所指元素,并返回一个指向被删元素后一个位置的新迭代器。 - 由于
vector
的底层是连续存储的,删除操作会导致当前位置及其后的所有迭代器失效。
例如:
v: 1 2 3 4 5 6 ↑ it (指向2)erase(it) 后:v: 1 3 4 5 6 ↑ it (原来的指针已经失效!)
此时 it
不再合法,继续 it++
就是 未定义行为,可能跳过元素、也可能崩溃。
✅ 正确写法
用 erase
的返回值更新迭代器
while (it != v.end()){ if (*it % 2 == 0) { it = v.erase(it); // 返回值是“下一个有效迭代器” } else { ++it; // 只有没删的时候才自增 }}
📢 如果你也喜欢这种“不呆头”的技术风格:
👁️ 【关注】 看一个非典型程序员如何用野路子解决正经问题
👍 【点赞】 给“不写八股文”的技术分享一点鼓励
🔖 【收藏】 把这些“奇怪但有用”的代码技巧打包带走
💬 【评论】 来聊聊——你遇到过最“呆头”的 Bug 是啥?
🗳️ 【投票】 您的投票是支持我前行的动力
技术没有标准答案,让我们一起用最有趣的方式,写出最靠谱的代码! 🎮💻