【C++笔记】STL详解:vector容器的使用

【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,如果 firstlast 后面,程序会在运行时崩溃。

        

②它们必须来自同一个“源头”:不能把 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; } 

        

打印结果为:

        

        

四、vector的容量操作

        

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 循环。

既然看到这里了,不妨关注+点赞+收藏,感谢大家,若有问题请指正。

Read more

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

【前端】Vue 组件开发中的枚举值验证:从一个Type属性错误说起

🌹欢迎来到《小5讲堂》🌹 🌹这是《小程序》系列文章,每篇文章将以博主理解的角度展开讲解。🌹 🌹温馨提示:博主能力有限,理解水平有限,若有不对之处望指正!🌹 👨💻 作者简介 🏆 荣誉头衔:2024博客之星Top14 | ZEEKLOG博客专家 | 阿里云专家博主 🎤 经历:曾多次进行线下演讲,亦是 ZEEKLOG内容合伙人 以及 新星优秀导师 💡 信念:“帮助别人,成长自己!” 🚀 技术领域:深耕全栈,精通 .NET Core (C#)、Python、Java,熟悉主流数据库 🤝 欢迎交流:无论是基础概念还是进阶实战,都欢迎与我探讨! 目录 * 前言 * 解决过程 * 一、错误场景还原 * 1.1 错误发生的位置 * 1.2 常见的触发场景 * 二、深入理解 Vue

By Ne0inhk

Android WebRTC VAD 实战指南:从原理到避坑

快速体验 在开始今天关于 Android WebRTC VAD 实战指南:从原理到避坑 的探讨之前,我想先分享一个最近让我觉得很有意思的全栈技术挑战。 我们常说 AI 是未来,但作为开发者,如何将大模型(LLM)真正落地为一个低延迟、可交互的实时系统,而不仅仅是调个 API? 这里有一个非常硬核的动手实验:基于火山引擎豆包大模型,从零搭建一个实时语音通话应用。它不是简单的问答,而是需要你亲手打通 ASR(语音识别)→ LLM(大脑思考)→ TTS(语音合成)的完整 WebSocket 链路。对于想要掌握 AI 原生应用架构的同学来说,这是个绝佳的练手项目。 从0到1构建生产级别应用,脱离Demo,点击打开 从0打造个人豆包实时通话AI动手实验 Android WebRTC VAD 实战指南:从原理到避坑 在语音通话或语音识别应用中,如何让设备"聪明&

By Ne0inhk
【踩坑记录】使用 Layui 框架时解决 Unity WebGL 渲染在 Tab 切换时黑屏问题

【踩坑记录】使用 Layui 框架时解决 Unity WebGL 渲染在 Tab 切换时黑屏问题

【踩坑记录】使用 Layui 框架时解决 Unity WebGL 渲染在 Tab 切换时黑屏问题 在开发 Web 应用时,尤其是集成了 Unity WebGL 内容的页面,遇到一个问题:当 Unity WebGL 渲染内容嵌入到一个 Tab 中时,切换 Tab 后画面会变黑,直到用户点击黑屏区域,才会恢复显示。 这个问题通常是因为 Unity 渲染在 Tab 切换时被暂停或未能获得焦点所致。 在本文中,我们将介绍如何在使用 Layui 框架时,通过监听 Tab 切换事件并强制 Unity WebGL 渲染恢复,来解决这一问题。 1. 问题描述 当 Unity WebGL 内容嵌入到页面中的多个

By Ne0inhk
Flutter for OpenHarmony:web 拥抱 Web 标准的桥梁(Wasm GC 与 DOM 互操作) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:web 拥抱 Web 标准的桥梁(Wasm GC 与 DOM 互操作) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 随着 Flutter 3.x 全面拥抱 Wasm(WebAssembly),Dart 团队推出了全新的 package:web 来取代老旧的 dart:html。 package:web 是基于最新的 JS Interop 机制构建的,它不仅性能更好,而且兼容 Wasm GC 标准。 虽然这个库通过名字看是为 “Web” 平台的,但对于 OpenHarmony 开发者来说,了解它有着特殊的意义: 1. 混合开发:鸿蒙原生支持 ArkWeb (WebView),在 Flutter 中通过 JS互操作与 Web 页面交互是常见需求。 2.

By Ne0inhk