C++ Vector深度易错点指南(临时抱佛脚)(基础用法;进阶;高级;实战)
Vector
- 1. Vector 基础概念
- 1.1 内存结构
- 1.2 初始化方法
- 2.1 访问方式对比
- 2.2 遍历流程
- 3.1 扩容流程
- 3.2 迭代器失效
- 3.2.1 插入操作导致迭代器失效
- 3.2.2 删除操作导致迭代器失效
- 3.2.3 避免迭代器失效的建议
- 4.1 排序与去重
- 排序
- 去重
- 性能分析
- 4.2 查找与条件删除
- 查找
- 条件删除
- 性能分析
- 5.1 内存模型
- 5.2 正确初始化
- 5.2.1 使用默认构造函数初始化
- 5.2.2 使用初始化列表初始化
- 5.2.3 使用构造函数初始化
- 5.2.4 动态初始化
- 错误示例 ❌
- 修正方法 ✅
- 6.1 预分配内存
- 6.1.1 为什么需要预分配内存
- 6.1.2 如何预分配内存
- 6.1.3 性能对比
- 6.1.4 注意事项
- 6.2 避免不必要的拷贝
- 6.2.1 使用引用传递
- 6.2.2 使用 `std::move`
- 6.2.3 使用 `emplace_back`
- 6.2.4 避免临时对象的生成
- 7.1 基础概念与初始化
- 7.2 元素访问与遍历
- 7.3 动态扩容与陷阱
- 7.4 STL 算法与 Vector 结合
- 7.5 二维 Vector
- 7.6 进阶优化技巧
1. Vector 基础概念
1.1 内存结构
std::vector
是 C++ 标准模板库(STL)中的一个动态数组容器,其内存结构由三部分组成:
- 动态数组指针
data
:指向实际存储元素的连续内存区域,这是std::vector
的核心,使得其能够通过下标快速访问元素。 - 当前元素数量
size
:记录当前存储在std::vector
中的元素个数,反映了std::vector
中实际使用的内存大小。 - 预分配内存容量
capacity
:表示std::vector
已经分配的内存大小,通常大于或等于size
。当向std::vector
中添加元素时,如果size
超过了capacity
,std::vector
会自动扩容,重新分配更大的内存空间,并将原有元素复制到新内存中,这一过程会带来额外的性能开销。
这种内存结构使得 std::vector
兼具动态数组的灵活性和静态数组的高效性,能够根据需要自动调整大小,同时保证了对元素的快速随机访问。
1.2 初始化方法
std::vector
提供了多种初始化方法,以满足不同的使用场景:
-
默认初始化:创建一个空的
std::vector
,不包含任何元素,size
和capacity
均为 0。std::vector<int> v1;
-
指定大小初始化:创建一个包含指定数量元素的
std::vector
,所有元素均初始化为默认值(对于基本数据类型为 0,对于自定义类型则调用默认构造函数)。std::vector<int> v2(5); // 创建一个包含 5 个 0 的 vector
-
指定大小和初始值初始化:创建一个包含指定数量元素的
std::vector
,并将所有元素初始化为指定的值。std::vector<int> v3(3, 100); // 创建一个包含 3 个 100 的 vector
-
列表初始化(C++11 及以上版本支持):使用花括号
{}
将一组元素初始化到std::vector
中,这种方式直观且方便。std::vector<int> v4 = {1, 2, 3}; // 使用列表初始化
-
拷贝初始化:通过已有的
std::vector
创建一个新的std::vector
,新std::vector
中的元素是原std::vector
中元素的副本。std::vector<int> v5(v4); // 拷贝初始化
这些初始化方法为用户提供了灵活的选择,能够根据具体需求快速创建并初始化 std::vector
,从而提高编程效率。# 2. 元素访问与遍历
2.1 访问方式对比
std::vector
提供了多种访问元素的方式,每种方式都有其特点和适用场景。以下是几种常见的访问方式对比:
访问方式 | 代码示例 | 越界检查 | 性能 | 适用场景 |
---|---|---|---|---|
下标操作符 [] | v[0] | 无 | 快速访问 | 已知索引且确保安全时 |
at() 方法 | v.at(0) | 有 | 较慢 | 需要安全访问时 |
迭代器 | *it | 无 | 快速访问 | 遍历或算法操作 |
-
下标操作符
[]
:通过索引直接访问元素,性能高,但不进行越界检查。如果索引超出范围,程序可能会崩溃或出现未定义行为。std::vector<int> v = {1, 2, 3}; int first = v[0]; // 访问第一个元素
-
at()
方法:提供范围检查,如果索引超出范围,会抛出std::out_of_range
异常。虽然安全性更高,但性能略低于下标操作符。std::vector<int> v = {1, 2, 3}; int first = v.at(0); // 安全访问第一个元素
-
迭代器:通过迭代器访问元素,适用于遍历整个容器或在算法中使用。迭代器提供了指针类似的语法,但不进行越界检查。
std::vector<int> v = {1, 2, 3}; for (auto it = v.begin(); it != v.end(); ++it) {std::cout << *it << " "; }
2.2 遍历流程
遍历 std::vector
的过程可以通过迭代器或范围 for
循环实现。以下是遍历的流程图和代码实现: