首页 / 第十一章 · STL 标准模板库 / 11.2 vector

11.2 vector 动态数组

STL 中最常用的容器——一个能自动伸缩的数组,支持下标快速访问,也能随时增删元素。

本页目录

vector 是 STL 中最常用的容器,可以把它理解成一个能自动伸缩的数组。当你需要一个可以随时增减元素、且能用下标快速访问任意位置的序列时,vector 是第一选择。使用前需引入 <vector> 头文件。

11.2.1 定义与初始化
C++ · vector 的几种初始化方式
1#include <iostream>
2#include <vector>
3using namespace std;
4
5int main()
6{
7 vector<int> v1; // 空数组,里面什么都没有
8 vector<int> v2(5); // 5 个元素,全部自动初始化为 0
9 vector<int> v3(5, 7); // 5 个元素,全部初始化为 7
10 vector<int> v4 = {1, 2, 3, 4, 5}; // 直接用花括号列出元素(最直观的写法)
11 vector<int> v5(v4); // 拷贝构造——复制一个一模一样的数组
12
13 // 二维数组:3 行 4 列,全部初始化为 0
14 vector<vector<int>> matrix(3, vector<int>(4, 0));
15 return 0;
16}
⚠️
注意事项:
· vector<int> v(n) 创建的 n 个元素会被自动初始化为 0,不会像局部数组那样留下"脏数据"。
· vector<int> v(n, val) 创建的 n 个元素会被初始化为指定的 val
· 初始化列表 {...} 是 C++11 引入的语法,简洁直观,优先使用。
11.2.2 常用操作

📏 容量管理

size() 与 capacity() 的区别
已用(size = 3)
预留空间(未用)
实际存储的元素 — size() 已分配但还没用 — capacity() 多出的部分
C++ · 容量相关操作
1vector<int> v = {1, 2, 3};
2v.size(); // 3——当前有多少个元素
3v.capacity(); // 已分配的内存最多能装多少个(通常 ≥ size)
4v.empty(); // false——判断是否为空
5v.clear(); // 清空所有元素,size 变成 0
6v.resize(5); // 把大小调整为 5,多出来的位置填 0
7v.resize(10, -1); // 把大小调整为 10,多出来的位置填 -1
8v.reserve(1000); // 提前预留 1000 个位置的空间
9v.shrink_to_fit(); // 把多余的内存释放掉
💡
reserve() 有什么用?
vector 扩容时,需要重新找一块更大的内存,然后把旧数据全部搬过去——这很花时间。如果你预先知道大概要存 1000 个元素,提前调用 reserve(1000) 就可以避免反复搬家,显著提升性能。这是竞赛中常见的优化手段。
⚠️
clear() 的陷阱:clear() 只把元素清掉,让 size() 变成 0,但已经占用的内存不会释放。如果想真正释放内存,可以用 vector<int>().swap(v) 这个小技巧。

🔍 元素访问

C++ · 元素访问
1vector<int> v = {10, 20, 30};
2int x = v[0]; // 10——用下标访问,不检查越界
3int y = v.at(0); // 10——用 at 访问,越界时会报错
4int first = v.front(); // 10——第一个元素
5int last = v.back(); // 30——最后一个元素
6int* p = v.data(); // 获取底层数组的原始指针(需要调用 C 函数时有用)
🔌
data() 的妙用:有些老旧的 C 语言函数需要传入数组指针(比如 memcpyscanf),这时可以用 v.data() 把 vector 当成普通数组来用。
🎯
效率须知:vector尾部增删元素是 O(1),非常快。但在头部或中间插入/删除是 O(n),因为需要把后面的元素全部挪位置。如果需要频繁在中间操作,应该考虑用 list

✏️ 增删修改

C++ · push_back / insert / erase
1vector<int> v = {1, 2, 3};
2v.push_back(4); // 在末尾追加:{1, 2, 3, 4}
3v.pop_back(); // 删除最后一个:{1, 2, 3}
4v.insert(v.begin() + 1, 99); // 在下标 1 处插入 99:{1, 99, 2, 3}
5v.erase(v.begin() + 1); // 删除下标 1 的元素:{1, 2, 3}
6v1.swap(v2); // 交换两个 vector 的全部内容(O(1),极快)
7v.emplace_back(10, 'a'); // 在末尾原地构造元素(比 push_back 更高效)
💡
emplace_back vs push_back:push_back 是先造好一个临时对象再拷贝进去,emplace_back 是直接在末尾原地构造,省去了拷贝这一步。对于简单的 int 差别不大,但对于复杂的类型(比如 pair、结构体),emplace_back 更高效。
🔥 经典陷阱:删除时迭代器失效
C++ · erase 与迭代器失效
1// ❌ 错误写法——erase 之后迭代器已经"过期"了
2for (auto it = v.begin(); it != v.end(); ++it)
3{
4 if (*it == target) v.erase(it); // 危险!it 已失效,++it 会出错
5}
6
7// ✅ 正确写法——利用 erase 返回的新迭代器
8for (auto it = v.begin(); it != v.end(); )
9{
10 if (*it == target)
11 it = v.erase(it); // erase 返回被删除元素的下一个位置
12 else
13 ++it;
14}
🔥
这是竞赛和工程中最经典的 STL 陷阱之一:删除元素后,原来的迭代器立刻失效,再对它做 ++ 是未定义行为,轻则结果错误,重则程序崩溃。记住口诀:erase 返回新位置,用它接着往下走,不要再手动 ++it
11.2.3 完整使用示例
C++ · vector 综合示例
1#include <iostream>
2#include <vector>
3using namespace std;
4
5int main()
6{
7 vector<int> v = {1, 2, 3, 4, 5};
8
9 // 遍历方式一:下标
10 for (size_t i = 0; i < v.size(); i++) {
11 cout << v[i] << " ";
12 }
13 cout << endl;
14
15 // 遍历方式二:范围 for(最简洁)
16 for (int x : v) {
17 cout << x << " ";
18 }
19 cout << endl;
20
21 // 删除偶数
22 for (auto it = v.begin(); it != v.end(); ) {
23 if (*it % 2 == 0)
24 it = v.erase(it);
25 else
26 ++it;
27 }
28 // v 变成 {1, 3, 5}
29
30 cout << "size: " << v.size() << endl; // 输出:3
31
32 return 0;
33}
📋 vector 常用操作速查表

把本节出现过的方法按用途归类汇总,写代码时可以直接当参考卡用。

方法分类作用
v.size() / v.empty()容量元素个数 / 判断是否为空
v.capacity()容量已分配的内存最多能装多少个
v.clear()容量清空元素(size 变 0,内存不释放)
v.resize(n) / v.resize(n, val)容量调整大小,多出的位置填 0 或 val
v.reserve(n)容量提前预留 n 个位置,避免反复扩容
v.shrink_to_fit()容量释放多余内存
v[i]访问下标访问,不检查越界,速度快
v.at(i)访问下标访问,越界抛异常,更安全
v.front() / v.back()访问第一个 / 最后一个元素
v.data()访问获取底层数组的原始指针
v.push_back(x) / v.pop_back()修改末尾追加 / 删除元素,O(1)
v.insert(it, x)修改在 it 位置插入元素,O(n)
v.erase(it)修改删除 it 位置的元素,返回下一个有效迭代器
v.emplace_back(args...)修改末尾原地构造元素,省去拷贝
v1.swap(v2)修改交换两个 vector 的全部内容,O(1)
v.begin() / v.end()遍历正向迭代器范围