一、什么是 vector?为什么要用它?
vector 是 C++ 标准库中提供的一个 动态数组容器,属于 <vector> 头文件。它的本质是:
一个可以自动扩容的数组,支持高效的随机访问和灵活的元素插入与删除。
相比 C 风格的数组,vector 有以下额外特点:
- 自动管理内存
- 内置边界检查
- 丰富的成员函数
- 与 STL 算法无缝配合
因此,在现代 C++ 开发中,vector 是最推荐的数组替代品之一。
二、vector 的核心特点
| 特性 | 描述 |
|---|---|
| 动态扩容 | 不需要提前知道数组大小,push_back() 会自动增加容量 |
| 支持随机访问 | 可以像数组一样通过 v[i] 或 at(i) 访问元素 |
| 支持迭代器 | 可与 begin()、end() 配合使用 STL 算法 |
| 数据连续存储 | 元素在内存中连续排列,可与 C 数组互通(如传递给 C 风格函数) |
| 类型安全 | 支持泛型类型 vector<T>,保证类型一致性 |
三、vector 的基本使用方法
1. 引入头文件
#include <vector>
2. 创建与初始化
std::vector<int> v1; // 空 vector
std::vector<int> v2(5); // 长度为5,默认值为0
std::vector<int> v3(5, 10); // 长度为5,值为10
std::vector<int> v4 = {1, 2, 3, 4}; // 列表初始化(C++11)
3. 常用操作
v.push_back(10); // 末尾添加元素
v.pop_back(); // 删除末尾元素
v.size(); // 当前元素数量
v.empty(); // 是否为空
v.clear(); // 清空所有元素
v.front(); // 第一个元素
v.back(); // 最后一个元素
v[i]; // 随机访问(不检查越界)
v.at(i); // 随机访问(检查越界)
四、内存管理与扩容机制(理解 vector 的关键)
vector 在内部维护三个指针:
begin:指向数据起始位置end:指向数据末尾capacity_end:指向当前分配内存的末尾
当你调用 push_back() 添加元素时:
- 如果容量足够,仅构造新元素
- 如果容量不足,会自动重新分配更大的内存空间,并复制旧数据
通常扩容是按 1.5~2 倍增长,因此在大量插入前可使用 reserve(n) 提前分配空间,避免多次扩容带来的性能损耗。
鉴于vector是动态数组,那么动态分配内存就难免会产生内存碎片,这也是无法避免的问题,那么我们如果要尽量减少内存碎片,就应该提前预算好自己需要的空间。
五、迭代器的使用
vector 支持标准 STL 迭代器:
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
std::cout << *it << std::endl;
}
也可以使用 C++11 范围 for:
for (int x : v) {
std::cout << x << std::endl;
}
甚至可以配合算法库:
std::sort(v.begin(), v.end());
当然,使用迭代器要注意避免迭代器失效问题。
什么是迭代器失效?
迭代器失效指的是:原来有效的迭代器,在容器结构或数据发生变化后,变得无效,不能再安全地使用,否则会导致未定义行为。
vector它维护了几个内部成员:
T* data:数据指针,指向第一个元素size_t size:当前元素个数size_t capacity:当前分配的容量
所以,如果底层的 data 指针发生了变化(比如扩容重新分配内存),那么旧的迭代器(指针)就不再指向有效内存,也就失效了。
有效的情况(不会失效):
- 对 vector 的元素进行修改:
vec[i] = 5;不会使迭代器失效。 - 不涉及插入或删除操作。
会导致失效的操作:
| 操作 | 是否会导致失效 | 说明 |
|---|---|---|
push_back | 可能 | 如果导致扩容,所有迭代器失效 |
insert | 是 | 插入点之后的所有迭代器失效,若扩容则全部失效 |
erase | 是 | 被删除元素之后的所有迭代器失效 |
clear | 是 | 所有迭代器失效 |
resize | 是 | 会影响所有元素和迭代器,尤其是缩小时 |
reserve | 可能 | 如果容量变化,会导致所有迭代器失效 |
示例代码
#include <iostream>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3};
auto it = v.begin(); // 指向1
v.push_back(4); // 如果导致扩容,it 将失效
std::cout << *it << std::endl; // 未定义行为,可能崩溃
}
如何避免迭代器失效?
- 在容器修改后重新获取迭代器。
- 使用 返回值 重新赋值:
auto it = v.erase(v.begin()); // 返回值是新位置的迭代器
- 使用
std::list或std::deque,它们在某些操作中不会使迭代器失效。 - 使用
reserve()提前分配足够空间,减少扩容次数。
六、vector 与指针 / 数组兼容性
由于 vector 内存是连续的,你可以将其数据传递给接受指针的函数:
void process(int* data, size_t len);
std::vector<int> v = {1, 2, 3, 4};
process(v.data(), v.size());
这让 vector 成为既现代又兼容的容器选择。
七、拷贝、赋值、引用行为
vector 支持拷贝构造、赋值运算,底层会进行深拷贝:
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = v1; // 拷贝数据,不共享内存
传参时建议使用 引用或 const 引用,避免不必要的复制:
void print(const std::vector<int>& v);
八、常见用法
| 技巧 | 说明 |
|---|---|
reserve(n) | 提前分配内存,避免多次扩容 |
shrink_to_fit() | 将 capacity 减少到 size(不一定成功) |
emplace_back() | 直接原地构造对象,性能优于 push_back() |
| 与结构体结合使用 | vector<Person> 可以存储自定义类型 |
std::vector<std::vector<T>> | 可构建二维数组(如矩阵、图等结构) |
九、vector 的使用场景
适合:
- 元素数量不确定的线性结构
- 需要快速随机访问的场景
- 需要与 C 接口互通的数组型数据
不适合:
- 频繁在中间插入/删除(建议使用
list或deque) - 数据量特别大但很稀疏(建议使用
map或unordered_map) - 固定长度、追求极致性能的低层嵌入式场景(建议使用原始数组)
十、总结
std::vector 是 C++ 中最常用、最稳定、最强大的容器之一。它不仅是数组的现代替代品,更是构建所有线性结构的基础。
大家快用起来吧~