【C++ 入门】Cyber动态义体——【vector容器】vector底层原理是什么?该怎么使用他?一文带你搞定所有问题!!!

⚡ CYBER_PROFILE ⚡
/// SYSTEM READY ///


WARNING \]: DETECTING HIGH ENERGY **🌊 🌉 🌊 心手合一 · 水到渠成** ![分隔符](https://i-blog.csdnimg.cn/direct/60a3de2294e9439abad47378e657b337.gif) |------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| | **\>\>\> ACCESS TERMINAL \<\<\<** || | [**\[ 🦾 作者主页 \]**](https://blog.csdn.net/fengtinghuqu520?spm=1000.2115.3001.5343) | [**\[ 🔥 C语言核心 \]**](https://blog.csdn.net/fengtinghuqu520/category_12955956.html) | | [**\[ 💾 编程百度 \]**](https://blog.csdn.net/fengtinghuqu520/category_13083835.html) | [**\[ 📡 代码仓库 \]**](https://blog.csdn.net/fengtinghuqu520/article/details/147275999?spm=1001.2014.3001.5502) | --------------------------------------- Running Process: 100% \| Latency: 0ms *** ** * ** *** #### 索引与导读 * [1. 什么是 vector?](#1. 什么是 vector?) * [2. 基本语法 与 核心特性](#2. 基本语法 与 核心特性) * [3. vector 常用的构造声明定义](#3. vector 常用的构造声明定义) * * [3)示例代码与详细讲解](#3)示例代码与详细讲解) * [4. vector iterator的使用](#4. vector iterator的使用) * * [4)示例代码与详细讲解](#4)示例代码与详细讲解) * [5. vector的容量增长问题](#5. vector的容量增长问题) * * [🚩注意事项](#🚩注意事项) * * [1)增容倍数:VS (1.5倍) vs g++ (2倍)](#1)增容倍数:VS (1.5倍) vs g++ (2倍)) * [2)reserve:只开辟空间,不影响 size](#2)reserve:只开辟空间,不影响 size) * [3)resize:开辟空间 + 初始化 + 影响 size](#3)resize:开辟空间 + 初始化 + 影响 size) * [总结建议](#总结建议) * [6. vector 增删查改](#6. vector 增删查改) * * [6)示例代码与详细讲解](#6)示例代码与详细讲解) * [7. vector 在线OJ算法题](#7. vector 在线OJ算法题) * * [7.1)只出现一次的数字](#7.1)只出现一次的数字) * [7.2)杨辉三角](#7.2)杨辉三角) * [7.3)删除有序数组中的重复项](#7.3)删除有序数组中的重复项) * [8. vector 底层原理与模拟实现](#8. vector 底层原理与模拟实现) * * [8.1)底层原理](#8.1)底层原理) * * [动态扩容机制:破茧成蝶的代价](#动态扩容机制:破茧成蝶的代价) * [8.2)vector 模拟实现](#8.2)vector 模拟实现) * [9. 关于 vector 动态二维数组](#9. 关于 vector 动态二维数组) * * [9.1)核心结构与内存布局](#9.1)核心结构与内存布局) * [9.2)创建与初始化方式](#9.2)创建与初始化方式) * * [① 创建空的二维数组](#① 创建空的二维数组) * [② 指定行数和列数(最常用)](#② 指定行数和列数(最常用)) * [③ 使用初始化列表(硬编码)](#③ 使用初始化列表(硬编码)) * [9.3)动态调整大小与插入数据](#9.3)动态调整大小与插入数据) * * [① 添加新的一行](#① 添加新的一行) * [② 在已有行中添加新元素](#② 在已有行中添加新元素) * [③ 动态调整矩阵尺寸](#③ 动态调整矩阵尺寸) * [9.4)元素的访问与修改](#9.4)元素的访问与修改) * * [① 使用 operator\[\] (无边界检查,速度快)](#① 使用 operator[] (无边界检查,速度快)) * [② 使用 at() (有边界检查,更安全)](#② 使用 at() (有边界检查,更安全)) * [9.5)遍历二维数组的高效方法](#9.5)遍历二维数组的高效方法) * * [① C++11 范围 for 循环(最推荐,代码最简洁)](#① C++11 范围 for 循环(最推荐,代码最简洁)) * [② 传统的索引遍历(适用于需要知道具体坐标 i, j 的场景)](#② 传统的索引遍历(适用于需要知道具体坐标 i, j 的场景)) * [9.6)函数参数传递](#9.6)函数参数传递) * [10. 关于 vector 的迭代器失效问题](#10. 关于 vector 的迭代器失效问题) * * [10)迭代器失效解决办法:在使用前,对迭代器重新赋值即可](#10)迭代器失效解决办法:在使用前,对迭代器重新赋值即可) * [11. memcpy 引起的浅拷贝问题](#11. memcpy 引起的浅拷贝问题) * * [11)深度原因分析](#11)深度原因分析) * [💻结尾--- 核心连接协议](#💻结尾— 核心连接协议) > [🔗Lucy的空间骇客裂缝:vector文档介绍](https://cplusplus.com/reference/vector/vector/) ## 1. 什么是 vector? `std::vector` 是 C++ 标准模板库(STL)中的**动态数组**。与普通数组不同,它可以在运行时自动调整大小,并提供了一系列安全的接口来管理元素 **使用前需要声明头文件``** ## 2. 基本语法 与 核心特性 ```cpp vector v1; //存储整数 vector v2; //存储浮点数 vector v3; //存储字符 vector v4; //存储布尔值 vector v5; //存储动态字符串列表 vector v6; //存储你自定义的类实例 ...... ``` * **连续存储** :元素在内存中是连续存放的,支持 O ( 1 ) O(1) O(1) 时间复杂度的随机访问。 * **动态扩容**:当空间不足时,自动申请更大的内存并迁移数据。 * **尾部高效** :在末尾插入或删除元素通常是 O ( 1 ) O(1) O(1),但在中间插入为 O ( n ) O(n) O(n) *** ** * ** *** ## 3. vector 常用的构造声明定义 | 构造函数声明 | 接口说明 | |-------------------------------------------------------------|--------------------| | `vector()`(重点) | 无参构造 | | `vector(size_type n, const value_type& val = value_type())` | 构造并初始化 `n` 个 `val` | | `vector(const vector& x)`(重点) | 拷贝构造 | | `vector(InputIterator first, InputIterator last)` | 使用迭代器进行初始化构造 | ### 3)示例代码与详细讲解 ```cpp #include #include using namespace std; // 引入命名空间,后续无需加 std:: 前缀 int main() { // --- 1. 无参构造 vector() --- // 创建一个存储整数的空容器,此时 size 为 0 vector v1; cout << "v1 size: " << v1.size() << " (无参构造)" << endl; // --- 2. 指定数量及初始值构造 --- // 创建一个包含 5 个元素的容器,每个元素都初始化为 100 // 如果省略第二个参数 100,即 vector v(5),则默认初始化为 0 vector v2(5, 100); cout << "v2 内容: "; for (int x : v2) cout << x << " "; // 输出: 100 100 100 100 100 cout << endl; // --- 3. 拷贝构造 vector(const vector& x) --- // 使用已有的 v2 来创建一个完全相同的新容器 v3 // 这是一个深拷贝过程,修改 v3 不会影响 v2 的原数据 vector v3(v2); cout << "v3 (由 v2 拷贝而来) 内容: "; for (int x : v3) cout << x << " "; cout << endl; // --- 4. 迭代器区间构造 --- // 使用数组的指针区间来初始化 vector int arr[] = {1, 2, 3, 4, 5}; // 指针在数组中扮演了迭代器的角色 // arr 是起始地址(指向 1),arr + 5 是结束地址(指向 5 之后的位置) vector v4(arr, arr + 5); cout << "v4 (由数组指针初始化) 内容: "; for (int x : v4) cout << x << " "; cout << endl; // 也可以使用另一个 vector 的部分区间来初始化 // 例如:取 v4 的前 3 个元素 vector v5(v4.begin(), v4.begin() + 3); cout << "v5 (由 v4 的前三个元素初始化) 内容: "; for (int x : v5) cout << x << " "; cout << endl; return 0; } ``` **注意:** `string`类拷贝函数执行的也是深拷贝 *** ** * ** *** ## 4. vector iterator的使用 | 迭代器使用 | 接口说明 | |---------------------------|----------------------------------------------------------------------------------------| | `begin()` / `end()`(重点) | 获取第一个数据位置的 `iterator` / `const_iterator`,获取最后一个数据的下一个位置的 `iterator` / `const_iterator` | | `rbegin()` / `rend()`(重点) | 获取最后一个数据位置的 `reverse_iterator`,获取第一个数据前一个位置的 `reverse_iterator` | ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3f6047e10af547caae8ca238e3a24ee6.png) ### 4)示例代码与详细讲解 ```cpp #include #include using namespace std; // 引入 std 命名空间 int main() { vector v = {10, 20, 30, 40}; // --- 反向遍历详解 --- // v.rbegin() -> 指向最后一个元素 40 // v.rend() -> 指向第一个元素 10 之前的一个"虚空"位置 cout << "反向输出结果: "; for (auto rit = v.rbegin(); rit != v.rend(); ++rit) { // 这里的 ++rit 实际上是让迭代器在内存中"向左"移动 cout << *rit << " "; } cout << endl; // --- 实用技巧:获取最后一个元素 --- // 除了 v.back(),你也可以用 *v.rbegin() cout << "最后一个元素是: " << *v.rbegin() << endl; // 40 return 0; } ``` *** ** * ** *** ## 5. vector的容量增长问题 | 容量空间 | 接口说明 | |-----------------|--------------------------| | `size()` | 获取数据个数 | | `capacity()` | 获取容量大小 | | `empty()` | 判断是否为空 | | `resize()`(重点) | 改变 `vector` 的 `size` | | `reserve()`(重点) | 改变 `vector` 的 `capacity` | | `distance()` | 计算两个迭代器之间的距离 | ### 🚩注意事项 #### 1)增容倍数:VS (1.5倍) vs g++ (2倍) 当 `vector` 的 `size == capacity` 时再插入元素,就会触发扩容 #### 2)reserve:只开辟空间,不影响 size `reserve` 就像是预订餐厅位子,位子给你留好了,但还没人坐。 **意义:** 如果你知道要插入 `1000` 个元素,提前 `reserve(1000) `可以避免在插入过程中频繁触发耗时的 **"重新分配+数据搬移"** 操作 #### 3)resize:开辟空间 + 初始化 + 影响 size `resize` 不仅订了位子,还直接安排了"假人"坐进去 #### 总结建议 * 如果你只是想优化性能,避免多次插入导致的频繁搬家,用 `reserve` * 如果你想立刻操作这些元素(比如用下标 `[]` 赋值),或者想改变容器长度,用 `resize` *** ** * ** *** ## 6. vector 增删查改 | `vector` 增删查改 | 接口说明 | |------------------|------------------------------------| | `push_back`(重点) | 尾插 | | `pop_back`(重点) | 尾删 | | `find` | 查找。(注意这个是算法模块实现,不是 `vector` 的成员接口) | | `insert` | 在 `position` 之前插入 `val` | | `erase` | 删除 `position` 位置的数据 | | `swap` | 交换两个 `vector` 的数据空间 | | `operator[]`(重点) | 像数组一样访问 | ### 6)示例代码与详细讲解 ```cpp #include #include #include using namespace std; class VectorDemo { private: vector _v; //私有成员变量 public: // 1. 无参构造函数:创建一个空的 vector VectorDemo() {} // 2. 提供一个手动初始化的方法:模拟批量添加 void initData() { _v.push_back(10); _v.push_back(20); _v.push_back(30); cout << "[初始化] 已手动添加 10, 20, 30" << endl; } // --- 增 --- void addElements() { _v.push_back(40); _v.insert(_v.begin() + 1, 15); cout << "[增] 执行完毕" << endl; } // --- 删 --- void removeElements() { if (!_v.empty()) { _v.pop_back(); _v.erase(_v.begin() + 1); cout << "[删] 执行完毕" << endl; } } // --- 查 --- void findAndAccess() { cout << "[查] 索引1的元素为: " << _v.[1] << endl; auto it = find(_v.begin(), _v.end(), 20); if (it != _v.end()) { cout << "[查] 找到20,索引为: " << distance(_v.begin(), it) << endl; } } // --- 改 --- void modifyElement(int index, int newVal) { if (index < _v.size()) { _v[index] = newVal; cout << "[改] 将索引" << index << "修改为" << newVal << endl; } } // --- 换 --- void swapData(vector& other) { _v.swap(other); cout << "[换] 数据空间已交换" << endl; } // 辅助函数:打印当前内容 void display() const { cout << "当前容器内容: "; if (_v.empty()) cout << "(空)"; for (int x : _v) { cout << x << " "; } cout << "\n" << endl; } }; int main() { // 实例化对象(现在不需要传参数了) VectorDemo demo; // 手动初始化 demo.initData(); demo.display(); // 后续操作保持不变 demo.addElements(); demo.display(); demo.findAndAccess(); demo.modifyElement(0, 100); demo.display(); demo.removeElements(); demo.display(); vector target = { 1, 2, 3 }; // 注意:这里的 target 还是用了 {},如果你想彻底不用,可以一个个 push_back demo.swapData(target); demo.display(); return 0; } ``` *** ** * ** *** ## 7. vector 在线OJ算法题 ### 7.1)只出现一次的数字 > [🔗Lucy的空间骇客裂缝:只出现一次的数字](https://leetcode.cn/problems/single-number-ii/description/) > [🔗Lucy的空间骇客裂缝:只出现一次的数字\|\|](https://leetcode.cn/problems/single-number-iii/) ### 7.2)杨辉三角 > [🔗Lucy的空间骇客裂缝:杨辉三角](https://leetcode.cn/problems/pascals-triangle/) > [🔗Lucy的空间骇客裂缝:杨辉三角\|\|](https://leetcode.cn/problems/pascals-triangle-ii/) ### 7.3)删除有序数组中的重复项 > [🔗Lucy的空间骇客裂缝:删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/description/) *** ** * ** *** ## 8. vector 底层原理与模拟实现 ### 8.1)底层原理 很多人以为 `vector` 底层是一个复杂的结构,但实际上,在主流的 `STL` 实现(如 `GCC` 的 `libstdc++`)中,`vector` 的核心状态仅仅由 **三个指针** 来维护: ```cpp template class vector { protected: iterator start; // 指向目前使用空间的头部 iterator finish; // 指向目前使用空间的尾部(最后一个元素的下一个位置) iterator end_of_storage; // 指向目前可用容量的尾部 // ... 其他成员函数 }; ``` 借助这三个核心指针,`vector` 能够轻松计算出当前的状态: * **当前元素个数(`size`):** `finish - start` * **当前最大容量(`capacity`):** `end_of_storage - start` * **是否为空(`empty`):** `start == finish` > 💡 **核心要点:** `vector` 的底层就是一段连续的线性内存空间。`start` 和 `finish` 之间是已经被初始化的有效数据,而 `finish` 到 `end_of_storage` 之间则是备用的未初始化内存。 #### 动态扩容机制:破茧成蝶的代价 既然底层是连续内存,如果备用空间(`finish == end_of_storage`)用完了,我们还能继续塞数据吗? 答案是能,这就引出了 `vector` 最核心的机制:**动态扩容**。 * **扩容的四个步骤** 当空间不足以容纳新元素时,`vector` 并不能直接在原内存后面"强行"拓展(因为后面的内存可能已经被其他程序占用了)。它必须经历一次痛苦的"搬家"过程: 1. **开辟新空间**:在内存中寻找一块更大的连续空间。 2. **数据迁移** :将旧空间里的数据 **拷贝(或移动)** 到新空间中。 3. **释放旧空间**:销毁旧空间上的对象,并把旧内存归还给操作系统。 4. **更新指针** :将 `start`、`finish`、`end_of_storage` 指向新空间。 * **扩容因子:1.5 倍 还是 2 倍?** 新开辟的空间到底该有多大?这就是著名的"扩容因子"问题。不同编译器的实现有所不同: * **GCC (Linux) 体系** :通常采用 **2 倍** 扩容。 * **MSVC (Windows) 体系** :通常采用 **1.5 倍** 扩容。 * **为什么是 1.5 倍或 2 倍,而不是每次增加固定大小?** 这里涉及到一个经典的均摊复杂度分析。如果是每次增加固定大小,每次插入的均摊时间复杂度会退化为 O ( n ) O(n) O(n)。而采用按比例(几何级数)增长,插入操作的均摊时间复杂度可以被平摊为 O ( 1 ) O(1) O(1)。 *** ** * ** *** ### 8.2)vector 模拟实现 ```cpp #include #include #include using namespace std; template class my_vector { public: typedef T* iterator; // 构造与析构 my_vector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {} ~my_vector() { delete[] _start; _start = _finish = _end_of_storage = nullptr; } // 核心基础接口 size_t size() const { return _finish - _start; } size_t capacity() const { return _end_of_storage - _start; } iterator begin() { return _start; } iterator end() { return _finish; } T& operator[](size_t i) { return _start[i]; } // 扩容逻辑 void reserve(size_t n) { if (n > capacity()) { size_t oldSize = size(); T* tmp = new T[n]; if (_start) { for (size_t i = 0; i < oldSize; ++i) tmp[i] = _start[i]; delete[] _start; } _start = tmp; _finish = _start + oldSize; _end_of_storage = _start + n; } } // 修改器 void push_back(const T& x) { if (_finish == _end_of_storage) { reserve(capacity() == 0 ? 4 : capacity() * 2); } *_finish++ = x; } iterator insert(iterator pos, const T& x) { if (_finish == _end_of_storage) { size_t len = pos - _start; reserve(capacity() == 0 ? 4 : capacity() * 2); pos = _start + len; // 解决迭代器失效 } // 从后往前挪动数据 for (iterator it = _finish; it > pos; --it) { *it = *(it - 1); } *pos = x; ++_finish; return pos; } iterator erase(iterator pos) { for (iterator it = pos + 1; it < _finish; ++it) { *(it - 1) = *it; } --_finish; return pos; } }; int main() { my_vector v; v.push_back(10); v.push_back(20); v.push_back(30); v.insert(v.begin() + 1, 15); // 在20前面插入15 v.erase(v.begin() + 2); // 删除20 cout << "Vector contents: "; for (auto e : v) cout << e << " "; // 10 15 30 cout << "\nSize: " << v.size() << ", Capacity: " << v.capacity() << endl; return 0; } ``` > [🔗Lucy的空间骇客裂缝:函数模板与类模板](https://blog.csdn.net/fengtinghuqu520/article/details/158240822?spm=1011.2415.3001.10575&sharefrom=mp_manage_link) *** ** * ** *** ## 9. 关于 vector 动态二维数组 在 `C++` 中,使用 `std::vector` 来构建动态二维数组是非常主流且安全的做法。相比于传统的 `C` 语言风格指针(`int** arr`),它能够自动管理内存,避免了内存泄漏和繁琐的 `new`/`delete` 操作 以下是关于 `vector` 动态二维数组(即 `vector>`)的详细深度拆解: *** ** * ** *** ### 9.1)核心结构与内存布局 `vector>` 的本质是\*\*"容器的容器"\*\*。 它在内存中并不是一块完整连续的矩阵。 * 外层 `vector`:是一个一维数组,里面存储的是内层 `vector` 的控制结构(包含指针、大小、容量等信息)。 * 内层 `vector`:代表每一行。每一行各自在堆区开辟一块连续的内存空间来存储实际的元素。 **核心推论:** 因为每一行是独立分配的,**所以二维 `vector` 完全支持锯齿数组(`Jagged Array`),即每一行的列数可以完全不同** *** ** * ** *** ### 9.2)创建与初始化方式 根据不同的场景,我们有多种初始化二维 `vector` 的方法: #### ① 创建空的二维数组 常用于一开始不知道行数和列数,需要后续通过 `push_back` 动态添加的场景。 ```cpp #include using namespace std; vector> matrix; ``` #### ② 指定行数和列数(最常用) 如果已经知道具体的行数 `M` 和列数 `N`,可以在创建时直接分配好空间,并赋予初始值。 ```cpp int M = 3; // 行数 int N = 4; // 列数 // 创建一个 3行4列 的二维数组,所有元素默认初始化为 0 vector> matrix(M, vector(N, 0)); ``` **语法解析:** 外层 `vector` 的大小为 M,它的每一个元素被初始化为一个大小为 `N`、元素值为 `0` 的一维 `vector` #### ③ 使用初始化列表(硬编码) 如果在定义时就已经确定了具体数据: ```cpp vector> matrix = { {1, 2, 3}, {4, 5}, // 注意:列数可以不同(锯齿数组) {6, 7, 8, 9} }; ``` *** ** * ** *** ### 9.3)动态调整大小与插入数据 `vector` 最强大的地方在于它的动态扩容能力。 #### ① 添加新的一行 ```cpp #include using namespace std; `vector`<`vector`<`int`>> `matrix`; // 准备一个新行 `vector`<`int`> `newRow` = {`10`, `20`, `30`}; // 将新行添加到二维数组的末尾 `matrix`.`push_back`(`newRow`); ``` #### ② 在已有行中添加新元素 ```cpp // 假设 matrix 至少有 1 行 // 在第 0 行的末尾追加一个元素 40 matrix[0].push_back(40); ``` #### ③ 动态调整矩阵尺寸 ```cpp // 将二维数组的行数调整为 5 行 matrix.resize(5); // 将第 0 行的列数调整为 10 列 matrix[0].resize(10); ``` *** ** * ** *** ### 9.4)元素的访问与修改 #### ① 使用 operator\[\] (无边界检查,速度快) ```cpp int val = matrix[1][2]; // 获取第 1 行 第 2 列的元素 matrix[1][2] = 99; // 修改该元素 ``` #### ② 使用 at() (有边界检查,更安全) 如果索引越界,`at()` 会抛出 `std::out_of_range` 异常,非常适合调试代码 ```cpp try { int val = matrix.at(1).at(2); } catch (const out_of_range& e) { // 处理越界异常 } ``` *** ** * ** *** ### 9.5)遍历二维数组的高效方法 #### ① C++11 范围 for 循环(最推荐,代码最简洁) 如果不需要知道元素的行号和列号,强烈建议使用此方法。 ```cpp // 注意:外层循环使用 const auto& 可以避免拷贝一整行数据,提升性能 for (const auto& row : matrix) { for (int val : row) { cout << val << " "; } cout << endl; } ``` #### ② 传统的索引遍历(适用于需要知道具体坐标 i, j 的场景) ```cpp for (size_t i = 0; i < matrix.size(); ++i) { for (size_t j = 0; j < matrix[i].size(); ++j) { cout << matrix[i][j] << " "; } cout << endl; } ``` *** ** * ** *** ### 9.6)函数参数传递 将二维 `vector` 传递给函数时,切记要使用引用传递,否则会触发整个二维数组的深度拷贝,极其消耗性能 ```cpp // 推荐做法:使用 const 引用传递(只读场景) void printMatrix(const vector>& mat) { // ... } // 推荐做法:使用普通引用传递(需要修改数据的场景) void modifyMatrix(vector>& mat) { // ... } ``` *** ** * ** *** ## 10. 关于 vector 的迭代器失效问题 迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个`指针`,或者是对`指针`进行了封装,比如:`vector`的迭代器就是原生态指针`T*`。**因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)** * 对于`vector`可能会导致其迭代器失效的操作有: 1. 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:`resize、reserve、insert、 assign、push_back`等 ```cpp #include using namespace std; #include int main() { vector v{1,2,3,4,5,6}; auto it = v.begin(); // 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容 // v.resize(100, 8); // reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变 // v.reserve(100); // 插入元素期间,可能会引起扩容,而导致原空间被释放 // v.insert(v.begin(), 0); // v.push_back(8); // 给vector重新赋值,可能会引起底层容量改变 v.assign(100, 8); /* 出错原因:以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉, 而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块 已经被释放的空间,而引起代码运行时崩溃。 解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给 it重新赋值即可。 */ while(it != v.end()) { cout<< *it << " " ; ++it; } cout< using namespace std; #include #include // 必须包含此头文件才能使用 find 算法 int main() { int a[] = { 1, 2, 3, 4 }; vector v(a, a + sizeof(a) / sizeof(int)); // 使用 find 查找 3 所在位置的 iterator vector::iterator pos = find(v.begin(), v.end(), 3); // 删除 pos 位置的数据,导致 pos 迭代器失效 v.erase(pos); // 此处会导致非法访问 cout << *pos << endl; return 0; } ``` `erase`删除`pos`位置元素后,`pos`位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果`pos`刚好是最后一个元素,删完之后`pos`刚好是`end`的位置,而`end`位置是没有元素的,那么`pos`就失效了。因此删除`vector`中任意位置上元素时,`vs`就认为该位置迭代器失效了。 以下代码的功能是删除`vector`中所有的偶数,请问哪个代码是正确的,为什么? ```cpp #include using namespace std; #include int main() { vector v{ 1, 2, 3, 4 }; auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) v.erase(it); ++it; } return 0; } int main() { vector v{ 1, 2, 3, 4 }; auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) it = v.erase(it); else ++it; } return 0; } ``` **第二个代码块采用了标准且安全的写法:** ```cpp if (*it % 2 == 0) it = v.erase(it); // 1. 关键:获取删除后的有效迭代器 else ++it; // 2. 只有没删除时才移动 ``` * 利用返回值更新:`vector::erase` 会返回一个指向被删除元素之后那个元素的新迭代器。通过 `it = v.erase(it)`,我们让 `it` 重新获得了合法的身份,指向了新搬移过来的元素(比如上例中的 `3`)。 * 分情况处理: * 如果删除了:`it` 已经指向了下一个待检查的元素,所以不需要再 `++it`。 * 如果没有删除:说明当前元素是奇数,我们手动执行 `++it` 去看下一个。 * 边界安全:即使删除了最后一个元素,`erase` 也会返回 `v.end()`,循环条件 `it != v.end()` 依然能正确识别并退出。 ### 10)迭代器失效解决办法:在使用前,对迭代器重新赋值即可 核心场景:遍历时删除元素 (erase) 这是引发迭代器失效最常见的灾难现场。当我们删除一个元素时,该位置之后的所有元素都会向前移动填补空缺,导致当前迭代器变成野指针。 ❌ 错误示范(经典的崩溃代码) ```cpp #include #include using namespace std; int main() { vector vec = {1, 2, 3, 3, 4, 5}; // 试图删除所有的 3 for (auto it = vec.begin(); it != vec.end(); ++it) { if (*it == 3) { vec.erase(it); // 🚨 灾难发生! // erase 之后,it 已经失效。 // 循环末尾还要执行 ++it,对失效的迭代器进行 ++ 操作会导致程序直接崩溃(Undefined Behavior)。 } } return 0; } ``` ✅ 正确示范:使用返回值"重新赋值" `erase()` 函数的设计非常巧妙,它在删除元素后,会返回一个指向被删除元素下一个位置的全新且有效的迭代器。这就是你说的"重新赋值"。 ```cpp #include #include using namespace std; int main() { vector vec = {1, 2, 3, 3, 4, 5}; // 注意:这里的 for 循环去掉了第三个部分的 ++it for (auto it = vec.begin(); it != vec.end(); ) { if (*it == 3) { // ✅ 核心操作:重新赋值! // erase 删除了当前的 3,并返回指向下一个元素的有效迭代器,覆盖掉失效的 it。 it = vec.erase(it); } else { // 只有在没有删除元素时,才手动移动迭代器 ++it; } } // 打印结果验证:输出 1 2 4 5 for (int val : vec) cout << val << " "; return 0; } ``` *** ** * ** *** ## 11. memcpy 引起的浅拷贝问题 使用 `memcpy` 进行的拷贝,以下代码会发生什么问题? ```cpp int main() { vector v; v.push_back("1111"); v.push_back("2222"); v.push_back("3333"); return 0; } ``` ### 11)深度原因分析 `memcpy` 的功能是按字节拷贝。 1. **扩容前(旧空间)**: * `vector` 内部存储了 `3` 个 `string` 对象。**每个 `string` 对象内部都有一个指针(比如 `char* _str`)指向堆上的字符数据(如 `"1111"`)** 2. **执行 `memcpy` 拷贝**: * `memcpy` 把旧空间里 `string` 对象的字节直接复制到新空间。这意味着:**新空间里 `string` 对象的 `_str` 指针指向的地址,和旧空间里一模一样。** 3. **释放旧空间(核心死穴)**: * 扩容逻辑中会调用 `delete[] _start`。这会触发旧空间里每个 `string` 对象的析构函数。 * 旧空间的 `string` 析构了,执行了 `delete[] _str`。 * 结果:**新空间里的 `string` 对象现在全是指向已释放内存的野指针!** 4. **再次访问或析构**: * 当 `main` 函数结束,新空间的 `vector` 析构时,会再次对那些已经被释放过的地址调用 `delete[]`。 * **结论** :触发 `Double Free`(两次释放同一块内存),程序直接挂掉。 *** ** * ** *** ## 💻结尾--- 核心连接协议 **警告:** 🌠🌠正在接入底层技术矩阵。如果你已成功破解学习中的逻辑断层,请执行以下指令序列以同步数据:🌠🌠 *** ** * ** *** **【📡】 建立深度链接:** **关注**本终端。在赛博丛林中深耕底层架构,从原始代码到进阶协议,同步见证每一次系统升级。 **【⚡】 能量过载分发:** 执行**点赞**操作。通过高带宽分发,让优质模组在信息流中高亮显示,赋予知识跨维度的传播力。 **【💾】 离线缓存核心:** 将本页加入**收藏**。把这些高频实战逻辑存入你的离线存储器,在遭遇系统崩溃或需要离线检索时,实现瞬时读取。 **【💬】 协议加密解密:** 在**评论区**留下你的散列码。分享你曾遭遇的代码冲突或系统漏洞(那些年踩过的坑),通过交互式编译共同绕过技术陷阱。 **【🛰️】 信号频率投票:** 通过**投票**发射你的选择。你的每一次点击都在重新定义矩阵的进化方向,决定下一个被全量拆解的技术节点。 *** ** * ** *** ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/57b03915c54b43a7a03fa92dbbfe57c3.gif) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/0905dc972de8414bb602715de3f866ee.gif)

相关推荐
学嵌入式的小杨同学2 小时前
STM32 进阶封神之路(二十三):低功耗深度解析 —— 从睡眠模式到停机模式(底层原理 + 寄存器配置)
c++·stm32·单片机·嵌入式硬件·mcu·架构·硬件架构
AC赳赳老秦2 小时前
OpenClaw 系统监控实战指南:构建高效的电脑/服务器状态监控与自动告警系统
服务器·开发语言·人工智能·php·ai-native·deepseek·openclaw
宝耶2 小时前
Java面试题5:List、Set、Map 的区别?各自有哪些实现类?
java·开发语言·list
Cosmoshhhyyy2 小时前
《Effective Java》解读第44条:坚持使用标准的函数接口
java·开发语言
yunyun321232 小时前
动态库热加载技术
开发语言·c++·算法
88号技师2 小时前
2026年3月一区SCI-B样条曲线优化算法B-spline curves optimizer-附Matlab免费代码
开发语言·算法·数学建模·matlab·优化算法
dapeng28702 小时前
C++中的享元模式实战
开发语言·c++·算法
程序员爱酸奶2 小时前
Java后端工程师成长指南
java·开发语言
me8322 小时前
【Java】关于控制台 SQL 日志显示查询有值但Swagger不显示字段问题
java·开发语言·sql