【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

C++入门看这一篇就够了——超详细讲解(120000多字详细讲解,涵盖C++大量知识)

C++入门看这一篇就够了——超详细讲解(120000多字详细讲解,涵盖C++大量知识)

目录 一、面向对象的思想 二、类的使用 1.类的构成 2.类的设计 三、对象的基本使用 四、类的构造函数 1.构造函数的作用 2.构造函数的特点 3.默认构造函数 3.1.合成的默认构造函数 3.2.手动定义的默认构造函数 四、自定义的重载构造函数 五、拷贝构造函数 1.手动定义的拷贝构造函数 2.合成的拷贝构造函数 3.什么时候调用拷贝构造函数 六、赋值构造函数 七、析构函数 八、this指针 九、类文件的分离 十、静态数据 1.静态数据成员 2.静态成员函数 十一、

By Ne0inhk
C++ 虚函数与纯虚函数:多态的核心实现基石

C++ 虚函数与纯虚函数:多态的核心实现基石

C++ 虚函数与纯虚函数:多态的核心实现基石 💡 学习目标:深度理解虚函数与纯虚函数的本质区别,掌握虚函数表的底层原理,能够灵活运用二者设计具备多态特性的类结构。 💡 学习重点:虚函数的声明与重写规则、纯虚函数与抽象类的使用场景、虚函数表的工作机制、虚函数的常见陷阱与解决方案。 一、虚函数的本质与定义 ✅ 结论:虚函数是 C++ 实现动态多态的核心,通过在基类成员函数前添加 virtual 关键字,允许派生类重写该函数,并在运行时根据对象的实际类型调用对应版本。 1.1 虚函数的声明语法 虚函数的声明必须在基类中进行,语法格式如下: class 基类名 {public:virtual 返回值类型 函数名(参数列表){// 函数体}}; 1.2 虚函数的核心特性 1. 运行时绑定:函数调用关系在程序运行时确定,而非编译时。 2. 重写规则:派生类重写的函数必须与基类虚函数的函数名、参数列表、返回值类型完全一致(协变类型除外)。 3.

By Ne0inhk
C++新手入门学习教程(完整版)

C++新手入门学习教程(完整版)

以下教程覆盖了 C++ 学习的各个方面,适合初学者循序渐进地学习。学习过程中,建议初学者多做练习和项目,以加深对理论知识的理解。希望这个教程能为你提供一个清晰的学习路径。 目录 第一章:C++ 简介 1.1 C++ 的历史与演变 1.2 C++ 的特点和优势 1.3 C++ 的应用领域 1.4 C++ 的未来展望 第二章:环境搭建 2.1 安装 C++ 编译器与 IDE Windows Linux Mac 2.2 配置开发环境 2.3 编译与运行示例程序 第三章:基本语法 3.1 C+

By Ne0inhk
嵌入式知识点学习篇五(C\C++)

嵌入式知识点学习篇五(C\C++)

变量/函数 * 全局变量和静态变量的区别是什么? * 全局变量可不可以定义在可被多个.c文件包含的头文件中?为什么? * 局部变量能否和全局变量重名? * 为什么析构函数必须是虚函数? * 为什么C++默认的析构函数不是虚函数? * C++中析构函数的作用? * 静态函数和虚函数的区别? * 重载和覆盖有什么区别? * 虚函数表具体是怎样实现运行时多态的? * C语言是怎么进行函数调用? * 请你说一说select * 请你说说fork,wait,exec函数 全局变量和静态变量的区别是什么? 1. 全局变量的作用域为程序块,而局部变量的作用域为当前函数。 2. 内存存储方式不同,全局变量(静态全局变量,静态局部变量)分配在全局数据区(静态存储空间),后者分配在栈区。 3. 生命周期不同。全局变量随主程序创建而创建,随主程序销毁而销毁,局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在了。 4. 使用方式不同。通过声明为全局变量,程序的各个部分都可以用到,而局部变量只能在局部使用。 全局变量可不可以定义在可被多个.c文件包含的

By Ne0inhk