【C++笔记】STL详解:Vector类的使用
前言:
本文在介绍STL框架基础上,进一步讲解了迭代器、auto关键字和范围for循环的使用方法,接下来我们将重点探讨vector类的常用接口及其应用。
一、vector容器的简介
C++ 的 vector 是标准模板库(STL)中最核心且实用的容器之一,其与固定大小的传统数组(如 int arr[10])不同,vector 克服了数组的局限性,它不需要预先确定大小,并且可以动态调整容量。
简单理解为:vector是可变的、经过封装函数功能的数组。
核心优势:
①动态扩容:您不需要一开始就告诉它要存多少数据。当空间不够时,它会在底层自动帮您寻找一块更大的内存,把数据搬过去。
②内存安全:它负责自己内存的分配和释放,大大减少了手动 new 和 delete 带来的内存泄漏风险。
③功能丰富:它自带了大量现成的工具函数,比如:获取大小、清空数据、在尾部添加数据等。
二、vector容器的常见构造
2.1 vector无参构造函数
无参构造函数:vector()
函数功能:构造一个空容器,不包含任何元素,其中存储的元素可以是任意类型。
代码演示:
#include<iostream> #include<vector> using namespace std; class Student { public: void func(); private: string _name; int _age; }; int main() { vector<int> v1; //构造int类型的空容器 vector<double> v2; //构造double类型的空容器 vector<char> v3; //构造char类型的空容器 vector<string> v4; //构造string类型的空容器 vector<Student> v5; //构造自定义类型Student的空容器 return 0; }
2.2 vector填充构造函数
template<class T>
填充构造函数: vector(int n, const T& value = T() )
函数功能:构造一个包含 n 个元素的容器,每个元素都是 value 的副本。
关键参数介绍:
①模板参数T:接受任意类型的value变量,例如:char、string、int、double...
②给定缺省值为T() :如果您只告诉系统要创建 n 个元素,却没有告诉它里面装什么,系统就会调用 T()(该类型的默认构造函数)来填充。
注意:int类型、char类型、double类型...这样的内置类型在c++中也存在无参构造函数。
代码示例:
#include<iostream> #include<vector> using namespace std; class Student { public: //无参构造 Student() {} void func(); private: string _name; int _age; }; int main() { //填充构造:5个整形元素,且其值都为1 vector<int> v1(5, 1); //填充构造:10个字符类型元素,且其值都为'a' vector<char> v2(10, 'a'); //填充构造:15个浮点型元素,且其值都为3.14 vector<double> v3(15, 3.14); //填充构造:20个字符串类型元素,且其值都为"C++" vector<string> v4(20, "C++"); //填充构造:5个自定义类型Student元素,通过缺省值调用无参构造 vector<Student> v5(5); return 0; }
2.3 vector拷贝构造函数
template<class T>
拷贝构造函数: vector(const vector<T>& v)
函数功能:用已存在的 vector 构造新的 vector(即构造一新个容器),按照顺序拷贝已存在的vector容器 v 中每个元素。
代码示例:
#include<iostream> #include<vector> using namespace std; int main() { vector<int> v1(10, 1); vector<int> v2 = v1; //调用拷贝构造函数,容器v2为v1的拷贝(副本) }
2.4 vector范围构造函数
template<class InputIterator>
范围构造函数:vector(InputIterator first, InputIterator last)
函数功能:“左闭右开”区间 [first, last),它会拷贝从 first 开始,直到 last 之前的所有元素,但不包含 last 本身。
参数介绍:
①形参first 和 last: 叫做迭代器 (Iterator),可以暂时把它们理解为“高级指针”,用来指向数据的位置。
②模板参数 InputIterator:它意味着起点和终点并非一定是vector 的迭代器, 只要是合法的数据范围(比如普通数组的指针、std::list 的迭代器、std::set 的迭代器),它都能照单全收。
注意事项:
①起点必须在终点之前:first必须能够通过不断地向后移动(递增)到达last,如果first在last后面,程序会在运行时崩溃。
②它们必须来自同一个“源头”:不能把vector A的起点和vector B的终点硬凑在一起传进去,这会导致内存越界。
场景 A:从“普通 C 语言数组”无缝转换为 vector
这在混合使用 C 和 C++ 代码时非常常见,普通数组的指针就是一种天然的迭代器。
#include <iostream> #include <vector> int main() { int arr[] = {10, 20, 30, 40, 50}; // 起点:数组头部 (arr) // 终点:数组尾部的下一个位置 (arr + 5) std::vector<int> v(arr, arr + 5); // v 的内容变成了: [10, 20, 30, 40, 50] return 0; }
场景 B:截取(拷贝)另一个 vector 的一部分
如果您只想拷贝别人的中间一段数据,这个构造函数是最佳选择。
#include <iostream> #include <vector> int main() { std::vector<int> source = {1, 2, 3, 4, 5, 6, 7}; // 从 source 的第 2 个元素(索引1)开始,到第 5 个元素(索引4)结束 // source.begin() 指向 1 std::vector<int> sub_v(source.begin() + 1, source.begin() + 5); // sub_v 的内容是: [2, 3, 4, 5] return 0; }
场景 C:跨容器转换(例如从 set 转为 vector)
std::set 会自动对数据进行排序和去重。我们可以利用这一点,先用 set 处理数据,再用这个构造函数无缝转回 vector。
#include <iostream> #include <vector> #include <set> int main() { // set 自动把数据变成了有序且无重复的: {1, 3, 5, 9} std::set<int> my_set = {5, 1, 9, 3, 5, 1}; // 把 set 的起点和终点传给 vector std::vector<int> v(my_set.begin(), my_set.end()); // v 的内容是: [1, 3, 5, 9] return 0; }
三、vector迭代器
vector 的迭代器底层实现通常就是一个普通指针。
由于 vector 的元素像静态数组一样存储在连续内存中,使用指针就能实现迭代器的全部功能,所以说在 vector 中,迭代器本质上与指针是等价的。
3.1vector迭代器简介
vector 的底层数据结构是一段连续的线性内存空间,所以原生指针天生就具备了作为 vector 迭代器的所有能力。
模板参数: template<class T>
普通迭代器(可读可改):typedef T* iterator;
常量迭代器(只读不改):typedef const T* const_iterator;
参数分析:
①模板参数为T,可接收任意类型
②T* 和 const T * 作为原生指针,指向数据的位置。
3.2 begin
函数原型:
① iterator begin();
② const_iterator begin() const
函数功能:返回一个指向 vector 中第一个元素的迭代器。
如下图所示:

3.3 end
函数原型:
① iterator end();
② const_iterator end() const
函数功能:返回一个指向 vector 容器 中最后一个元素之后的元素。
如图所示:最后一个元素为整形8,end指向下一个位置

实战演示:vector容器中 begin() 和 end() 的使用
int main() { vector<int> v(10, 6); vector<int>::iterator it = v.begin(); while (it != v.end()) { cout << *it << " " ; ++it; } cout << endl; return 0; }
打印结果为:

四、vecto的容量操作
4.1 size
函数原型:size_t size()
函数功能:返回vector容器中的元素数量。
代码示例:
#include<iostream> #include<vector> using namespace std; int main() { // 1. 创建一个空的 vector vector<int> myVec; cout << "初始状态的 size: " << myVec.size() << endl; // 输出: 0 // 2. 插入三个元素 myVec.push_back(10); myVec.push_back(20); myVec.push_back(30); cout << "插入3个元素后的 size: " << myVec.size() << endl; // 输出: 3 return 0; }
打印结果如下所示:

4.2 capacity
函数原型: size_t capacity()
函数功能:返回已分配存储容量的大小
代码示例:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> myVec; // 初始状态 cout << "1. 初始状态:" << endl; cout << "Size: " << myVec.size() << ", Capacity: " << myVec.capacity() << "\n" << endl; // 连续插入元素,观察容量的增长策略 cout << "2. 观察动态扩容:" << endl; for (int i = 1; i <= 10; ++i) { myVec.push_back(i); cout << "插入第 " << i << " 个元素后 -> Size: " << myVec.size() << ", Capacity: " << myVec.capacity() << endl; } return 0; }
打印结果如下:

4.3 reserve
函数原型:void reserve(size_t n)
函数功能:
① 当所给值n > 当前 capacity 时,扩容到该值。
② 当所给值n ≤ 当前 capacity 时,不做任何操作。
代码示例:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> myVec; // 1. 观察 reserve 的基本行为 cout << "--- 1. 调用 reserve 之前 ---" << endl; cout << "Size: " << myVec.size() << ", Capacity: " << myVec.capacity() << endl; myVec.reserve(100); // 告诉底层:给我直接去申请 100 个元素的连续内存! cout << "\n--- 2. 调用 reserve(100) 之后 ---" << endl; // 注意:size 依然是 0!因为你只租了空间,还没往里面放数据。 cout << "Size: " << myVec.size() << ", Capacity: " << myVec.capacity() << endl; // 2. 正确的做法:搭配 push_back 使用 for (int i = 0; i < 5; ++i) { myVec.push_back(i); } cout << "\n--- 3. push_back 5个元素后 ---" << endl; // 此时不需要发生任何底层的“搬家”,因为容量绝对够用,效率极高。 cout << "Size: " << myVec.size() << ", Capacity: " << myVec.capacity() << endl; // --------------------------------------------------------- // 3. 致命错误演示(初学者常犯的误区) // --------------------------------------------------------- vector<int> badVec; badVec.reserve(10); // 错误!虽然底层给你留了 10 个位置的内存,但这些内存是“未初始化”的。 // badVec[0] = 100; // 危险!越界访问!因为此时 size() 仍然是 0。 // 这会导致未定义行为,程序可能会崩溃。 return 0; }
打印结果如下所示:

4.4 resize
函数原型:void resize(size_t n, const T& value = T() );
函数功能: 通常用于初始化
① 当所给值n > 当前 size 时,扩展 size 到该值,新增元素为指定值(默认 0)。
② 当所给值n < 当前 size 时,缩小 size 到该值,超出部分元素被移除。
参数解析:
①n:你期望 vector 最终拥有的实际元素个数(size)。
②const T& value = T():这是一个默认参数。
A.如果需要新增元素,新增的元素会全部用这个 value 来初始化(拷贝构造)。
B.如果你不传这个参数,它就会调用泛型的默认构造函数 T()。 对于 int,默认就是 0;对于 string,默认就是空字符串 ""。
4.5 empty
函数原型:bool empty()
函数功能:判断当前vector容器是否为空
代码示例:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> myVec; // 1. 刚创建时,它是空的 cout << "1. 初始状态:" << endl; if (myVec.empty()) { cout << " myVec 是空的! (size = " << myVec.size() << ")" << endl; } // 2. 放入元素后,它不再为空 myVec.push_back(100); cout << "\n2. push_back 之后:" << endl; if (!myVec.empty()) { cout << " myVec 里面有数据了! (size = " << myVec.size() << ")" << endl; } // 3. 清空元素后,它再次变为空 myVec.pop_back(); // 或者使用 myVec.clear() cout << "\n3. 删掉所有元素后:" << endl; if (myVec.empty()) { cout << " myVec 又变成空的了!" << endl; } return 0; }
五、vector的修改操作
5.1 push_back
函数原型: void push_back(const T& x);
函数功能:在vector容器的末尾添加元素
代码示例:
#include <iostream> #include <vector> using namespace std; int main() { // 1. 创建一个空的整型 vector vector<int> numbers; // 2. 使用 push_back 逐个追加元素 numbers.push_back(10); numbers.push_back(20); numbers.push_back(30); cout << "手动追加后的 vector: "; for (int i=0;i< numbers.size();i++) { cout << numbers[i] << " "; } cout << endl; // 输出: 10 20 30 // 3. 常见的实战场景:在循环中使用 push_back // 假设我们要生成 1 到 5 的平方数并存入 vector vector<int> squares; for (int i = 1; i <= 5; ++i) { // 计算平方并直接追加到末尾 squares.push_back(i * i); } cout << "循环追加平方数后的 vector: "; for (int i = 0; i < squares.size(); i++) { cout << squares[i] << " "; } cout << endl; // 输出: 1 4 9 16 25 return 0; }
代码示例:

5.2 pop_back
函数原型:void pop_back();
函数功能:删除vector容器的最后一个元素
代码示例:
#include <iostream> #include <vector> using namespace std; int main() { // 1. 创建一个空的整型 vector vector<int> numbers; // 2. 使用 push_back 逐个追加元素 numbers.push_back(10); numbers.push_back(20); numbers.push_back(30); cout << "手动追加后的 vector: "; for (int i=0;i< numbers.size();i++) { cout << numbers[i] << " "; } cout << endl; // 输出: 10 20 30 //3.使用 pop_back 逐个删除元素 numbers.pop_back(); numbers.pop_back(); numbers.pop_back(); cout << "手动删除后的 vector: "; for (int i = 0; i < numbers.size(); i++) { cout << numbers[i] << " "; } cout << endl; // 输出: 10 20 30 return 0; }
打印结果如下:

5.3 insert
函数原型:iterator insert(iterator pos, const T& x);
函数功能: 在所给迭代器pos位置插入一个元素
代码示例:
#include <iostream> #include <vector> using namespace std; void printVector(const vector<int>& v) { for (int num : v) cout << num << " "; cout << endl; } int main() { // 初始化一个 vector vector<int> vec = { 10, 20, 30 }; cout << "1. 初始状态: "; printVector(vec); // 输出: 10 20 30 // --- 场景 A:在头部插入 --- // vec.begin() 指向 10,在 10 之前插入 5 vec.insert(vec.begin(), 5); cout << "2. 头部插入 5: "; printVector(vec); // 输出: 5 10 20 30 // --- 场景 B:在中间插入 --- // vec.begin() + 2 指向当前的第三个元素 (也就是 20) // 在 20 之前插入 15 auto it = vec.insert(vec.begin() + 2, 15); cout << "3. 中间插入 15: "; printVector(vec); // 输出: 5 10 15 20 30 // 验证返回值:it 应该指向刚刚插入的 15 cout << " 刚刚插入的元素是: " << *it << endl; // --- 场景 C:在尾部插入 (等同于 push_back) --- // vec.end() 指向最后一个元素的下一个位置,在它之前插入,就是插在最后 vec.insert(vec.end(), 100); cout << "4. 尾部插入 100: "; printVector(vec); // 输出: 5 10 15 20 30 100 return 0; }
打印结果如下所示:

5.4 erase
函数原型: iterator erase(iterator pos);
函数功能:删除所给迭代器在pos位置的元素
代码示例:
#include <iostream> #include <vector> using namespace std; void printVector(const vector<int>& v) { for (int num : v) cout << num << " "; cout << endl; } int main() { vector<int> vec = { 10, 20, 30, 40, 50 }; cout << "1. 初始状态: "; printVector(vec); // 输出: 10 20 30 40 50 // --- 场景 A:删除中间的某个元素 --- // 删除第三个元素 (也就是 30) // vec.begin() 指向 10,+ 2 后指向 30 auto it = vec.erase(vec.begin() + 2); cout << "2. 删除 30 后: "; printVector(vec); // 输出: 10 20 40 50 // 验证返回值:it 应该指向被删除元素 (30) 的下一个元素 (40) cout << " 此时 it 指向的元素是: " << *it << endl; // --- 场景 B:删除头部的元素 --- vec.erase(vec.begin()); cout << "3. 删除头部元素 (10) 后: "; printVector(vec); // 输出: 20 40 50 // --- 场景 C:在循环中安全地删除特定条件的元素 (极其重要!) --- // 假设我们要删除所有大于 30 的偶数 cout << "\n4. 循环删除演示:" << endl; vector<int> nums = { 15, 42, 28, 50, 33, 60 }; cout << " 原始数据: "; printVector(nums); // 正确的遍历删除姿势 for (auto iter = nums.begin(); iter != nums.end(); ) { if (*iter > 30 && *iter % 2 == 0) { // 找到 42, 50, 60 // erase 返回的是下一个元素的有效迭代器,必须用 iter 接收它 iter = nums.erase(iter); } else { // 只有当没有发生删除时,才手动将迭代器前进 ++iter; } } cout << " 删除大于30的偶数后: "; printVector(nums); // 输出: 15 28 33 return 0; }
打印结果如下:

六、vector的访问
6.1 operator[]
函数原型:
①T& operator[](size_t pos)
②const T& operator[](size_t pos)const
函数功能:返回向量容器中位置为 n 的元素的引用。
代码演示:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> scores = { 85, 92, 78, 90 }; // --- 1. 读取元素 --- cout << "1. 初始状态:" << endl; cout << " 第 0 个元素: " << scores[0] << endl; // 输出: 85 cout << " 第 2 个元素: " << scores[2] << endl; // 输出: 78 // --- 2. 修改元素 --- // 因为 operator[] 返回的是 T& (引用),所以我们可以直接修改底层的数据 scores[1] = 100; // 将 92 修改为 100 scores[3] += 5; // 将 90 增加 5,变成 95 cout << "\n2. 修改后的状态:" << endl; for (size_t i = 0; i < scores.size(); ++i) { cout << scores[i] << " "; } cout << endl; // 输出: 85 100 78 95 // --------------------------------------------------------- // 3. 致命陷阱:越界访问 // --------------------------------------------------------- // 当前 size 是 4,有效索引是 0, 1, 2, 3。 // 如果你尝试访问 scores[10] // scores[10] = 999; // 危险!取消注释会导致未定义行为 // 编译器绝不会报错,程序运行时可能会崩溃(段错误),也可能会默默篡改其他内存数据! return 0; }
打印结果如下所示:

6.2 范围for
它本质上是一种“语法糖”,能让你从繁琐的索引 i 和迭代器中彻底解放出来,代码变得极其极其干净。
1.范围for的使用场景:三段代码示例展示
代码示例1:值拷贝 (按值遍历)
#include <iostream> #include <vector> #include <string> using namespace std; int main() { vector<int> numbers = { 10, 20, 30 }; // 适用场景:只是读取数据,且数据类型很小(如 int, char 等基础类型)。 cout << "1. 按值遍历 (只读,复制了一份): "; for (int num : numbers) { cout << num << " "; num = 99; // 注意:这里的修改毫无意义,因为它只是副本!原 vector 不受影响。 } cout << endl; // 原 vector 依然是 10 20 30 return 0; }
代码示例2:引用遍历 (按引用遍历)
#include <iostream> #include <vector> #include <string> using namespace std; int main() { vector<int> numbers = { 10, 20, 30 }; // 适用场景:你想要直接修改 vector 里面真正的数据。 cout << "2. 按引用遍历 (可读可写,直接操作本体): "; for (int& num : numbers) { num *= 2; // 直接把本体的数值翻倍! } // 打印看看修改结果 for (int num : numbers) { cout << num << " "; // 输出: 20 40 60 } cout << endl; return 0; }
代码示例3:常量引用遍历
#include <iostream> #include <vector> #include <string> using namespace std; int main() { vector<string> words = { "Hello", "C++", "World" }; // 适用场景:只读取数据,不修改。且数据类型比较大(比如长字符串、复杂的类对象)。 // 因为它既省去了拷贝庞大数据的性能开销,又用 const 保证了绝对的安全。 cout << "3. 按常量引用遍历 (只读,且没有拷贝开销): "; for (const string& word : words) { cout << word << " "; // word = "Hi"; // 编译报错!const 保护了原数据不被意外篡改。 } cout << endl; return 0; }
2. 底层逻辑:编译器施展的“语法糖”魔法
范围 for 循环看起来神奇,不需要知道边界,也不需要维护索引,但实际上,编译器在背后默默帮你做完了所有的脏活累活。
当你写下这段非常干净的代码:
vector<int> vec = {1, 2, 3}; for (int val : vec) { cout << val; }
编译器在编译时,会把它直接翻译(展开)成我们最开始讲过的、基于 begin() 和 end() 的原生迭代器循环:
// 编译器眼中的真实代码: for (auto __begin = vec.begin(), __end = vec.end(); __begin != __end; ++__begin) { int val = *__begin; // 解引用迭代器,赋值给 val cout << val; }
3. 终极建议:无脑配合 auto 关键字
在现代 C++ 中,我们通常连类型都懒得写了,直接用 auto 让编译器去推导。
配合auto进行使用:
①适用于只读小对象: for (auto x : vec)
②适用于修改对象: for (auto& x : vec)
③适用于只读大对象 / 复杂对象: for (const auto& x : vec)
七、迭代器失效
迭代器的本质:
vector 的迭代器,本质上就是指向连续内存的原生指针。
迭代器失效的原因:
既然是原生指针,一旦底层的那块连续内存发生了 “搬家” 或者 内部元素发生了 “挪动” ,你手里捏着的那个旧指针,就会瞬间变成指向未知区域的野指针,一旦继续使用它,就是未定义行为(通常是程序直接崩溃)。
场景一:扩容导致的“全军覆没”(全局失效)
这是最致命的一种失效,当你在执行 push_back、insert 等操作时,如果新加入元素导致 (size > capacity),vector 就会触发底层扩容。
①底层发生了什么: 申请两倍的新地皮 -> 把老房子搬过去 -> 把老地皮直接炸毁退还给操作系统。
②失效范围: 所有指向这个 vector 的迭代器、指针和引用,会在一瞬间全部失效。
代码示例:
vector<int> vec = {1, 2, 3}; vec.reserve(3); // 当前 size=3, capacity=3 auto it = vec.begin(); // it 指向元素 1所在的旧内存地址 vec.push_back(4); // 此时容量不够,触发大搬家!旧内存被释放! // cout << *it << endl; // 致命错误!it 已经成了野指针,解引用直接崩溃!
场景二:元素挪动导致的“半壁江山沦陷”(局部失效)
如果你调用了 insert 或 erase,并且没有触发底层扩容(备用空间足够),情况会稍微好一点,但依然危机四伏。
①执行 insert(pos, x) 时: pos 到末尾的元素集体向后挪动一位腾出空间。
失效范围: 指向 pos,以及 pos 之后所有元素的迭代器全部失效,此时pos 之前的依然安全。
代码示例:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> vec; // 极其关键的一步:预留足够空间,保证接下来的 insert 不会触发“大搬家” vec.reserve(10); vec.push_back(10); vec.push_back(20); vec.push_back(30); vec.push_back(40); vec.push_back(50); // 此时内存布局: [10] [20] [30] [40] [50] // 索引: 0 1 2 3 4 // 1. 布下三个“眼线” auto it_before = vec.begin() + 1; // 指向 20 (在插入点之前) auto it_target = vec.begin() + 2; // 指向 30 (目标插入点) auto it_after = vec.begin() + 3; // 指向 40 (在插入点之后) cout << "--- 执行 insert 之前 ---" << endl; cout << "it_before 指向: " << *it_before << endl; // 20 cout << "it_target 指向: " << *it_target << endl; // 30 cout << "it_after 指向: " << *it_after << endl; // 40 // 2. 强行插队:在 30 之前插入一个 99 vec.insert(it_target, 99); // 此时内存布局变成了: [10] [20] [99] [30] [40] [50] // 发生的事情:从 30 开始,后面的所有人全都被迫向后挪了一个座位。 cout << "\n--- 执行 insert 之后 ---" << endl; // 安全区:目标之前的迭代器完全不受影响 cout << "it_before 依然指向: " << *it_before << " (绝对安全!)" << endl; // 沦陷区:物理地址没变,但坐在那个位置上的人变了!(逻辑失效) // 注意:在实际工程中,绝不能再使用这两个迭代器,此处仅为原理解析。 cout << "it_target 现在指向: " << *it_target << " (它本想跟踪 30,结果变成了插队的 99)" << endl; cout << "it_after 现在指向: " << *it_after << " (它本想跟踪 40,结果变成了被挤退后的 30)" << endl; return 0; }
②执行 erase(pos) 时: pos 位置的元素被销毁,后面的元素集体向前挪动一位填补空缺。
失效范围: 指向被删元素 pos,以及 pos 之后所有元素的迭代器全部失效,此时pos 之前的迭代器依然安全。
代码示例:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> vec = { 10, 20, 30, 40, 50 }; // 此时内存布局: [10] [20] [30] [40] [50] // 索引: 0 1 2 3 4 // 1. 提前布下三个“眼线” (迭代器) auto it_before = vec.begin() + 1; // 指向 20 (在目标之前) auto it_target = vec.begin() + 2; // 指向 30 (准备被干掉的目标) auto it_after = vec.begin() + 3; // 指向 40 (在目标之后) cout << "--- 执行 erase 之前 ---" << endl; cout << "it_before 指向: " << *it_before << endl; // 20 cout << "it_target 指向: " << *it_target << endl; // 30 cout << "it_after 指向: " << *it_after << endl; // 40 // 2. 执行暗杀:干掉 30 vec.erase(it_target); // 此时内存布局变成了: [10] [20] [40] [50] // 发生的事情:40 向前覆盖了 30,50 向前覆盖了 40,最后销毁了原来 50 的位置。 cout << "\n--- 执行 erase 之后 ---" << endl; // 安全区:目标之前的迭代器完全不受影响 cout << "it_before 依然指向: " << *it_before << " (绝对安全!)" << endl; // 沦陷区:虽然物理内存还在,但逻辑已经彻底乱了(严格来说属于未定义行为) // 下面两行代码在工程中是绝对禁止的,这里仅作底层现象的原理解示: cout << "it_target 现在指向: " << *it_target << " (逻辑已变,它变成了替补上来的 40)" << endl; cout << "it_after 现在指向: " << *it_after << " (逻辑已变,它变成了替补上来的 50)" << endl; // 更可怕的情况:如果 it_after 原本指向的是最后一个元素 50 // erase 之后,它就会变成越界的野指针 ! return 0; }
场景三:在“范围 for”里做增删会极其恐怖
代码示例:
vector<int> vec = {1, 2, 3}; // 你写的看起来很安全的代码: for (auto x : vec) { if (x == 2) { vec.push_back(4); // 灾难降临! } } // 编译器实际执行的代码: for (auto __begin = vec.begin(), __end = vec.end(); __begin != __end; ++__begin) { int x = *__begin; if (x == 2) { vec.push_back(4); // 扩容发生!底层的 __begin 和 __end 瞬间全部变成野指针! // 下一次循环执行 ++__begin 或 __begin != __end 时,程序直接暴毙! } }
迭代器失效的注意事项:
①对于扩容失效: 永远养成使用 reserve() 预先分配足够内存的习惯。只要不扩容,就不会“全军覆没”。
②对于 erase 失效: 必须使用 erase 的返回值!它会返回挪动后顶替上来的那个全新的、合法的迭代器(我们之前在 erase 那一节的循环删除代码中演示过)。
③对于 insert 失效: 同样,接收 insert 返回的指向新元素的迭代器,并根据逻辑重新定位。
④黄金法则: 绝对不要在范围 for 循环内部改变容器的 size,如果必须要增删元素,请退回到传统的、手动控制迭代器的 for 循环。
既然看到这里了,不妨关注+点赞+收藏,感谢大家,若有问题请指正。
