C++ vector 是什么?
vector 本质是动态大小的数组,底层通过连续内存存储元素,支持:
- 随机访问(像数组一样用 [] 访问,时间复杂度为 O(1))。
- 自动扩容(元素超过容量时,自动分配更大内存并转移数据)。
- 灵活增删(尾插、尾删高效,中间插入/删除需移动元素)。
在 C++ STL 容器中,vector 是最常用、最灵活的动态数组容器,既能像普通数组一样高效访问元素,又能自动扩容适配数据量变化。无论是日常开发还是算法刷题,vector 都是使用非常高频的工具。本文将从'能用'到'会用',系统讲解 vector 的核心接口、使用技巧与避坑要点。
二、vector 核心接口:必学的几个高频操作
学习 vector 前掌握 string 相关功能会有帮助。重点掌握'定义、迭代器、空间管理、增删查改'四大类核心接口,即可覆盖 90% 以上场景。
1、定义和初始化(构造函数)
vector 提供了 4 种常用构造方式,可根据不同场景选择:
| 构造函数声明 | 接口说明 | 代码示例 |
|---|
| vector()(重点) | 无参构造,创建空 vector | vector<int> v;(空容器,size=0,capacity=0) |
| vector(size_type n, val) | 构造并初始化 n 个 val | vector<int> v(5, 3);(含 5 个 3,size=5,capacity=5) |
| vector(const vector& x)(重点) | 拷贝构造,复制另一个 vector | vector<int> v2(v1);(v2 是 v1 的副本) |
| vector(iterator first, iterator last) | 迭代器初始化,复制区间元素 | vector<int> v(a, a+5);(复制数组 a[0]~a[4]) |
#include<iostream>
#include<vector>
using namespace std;
void test_vector1() {
vector<int> v1;
vector<int> v2(5, 1);
vector<int> v3(v2);
vector<int> v4(++v2.begin(), --v2.end());
}
int main() {
test_vector1();
return 0;
}
2、遍历 vector 的三大'万能工具'
在 vector 中同样适用以下三种遍历方法:
2.1 operator[] 下标访问
void test_vector1() {
vector<int> v2(5, 1);
for (size_t i = 0; i < v2.size(); i++) {
cout << v2[i] << " ";
}
cout << endl;
}
int main() {
test_vector1();
return 0;
}
2.2 迭代器 iterator 遍历
迭代器是访问容器元素的通用接口,vector 的迭代器本质是'封装的指针',支持遍历、取值、移动等操作。
核心迭代器接口如下:
| 迭代器接口 | 接口说明 | 代码示例(遍历 vector) |
|---|
| begin() | 获取第一个元素的迭代器 | auto it = v.begin();(指向 v[0]) |
| end() | 获取最后一个元素下一位的迭代器 | while (it != v.end()) { ... } |
| rbegin() | 获取最后一个元素的反向迭代器 | auto rit = v.rbegin();(指向 v[size-1]) |
| rend() | 获取第一个元素前一位的反向迭代器 | while (rit != v.rend()) { ... } |
void test_vector1() {
vector<int> v2(5, 1);
vector<int> v3(v2);
vector<int>::iterator it = v3.begin();
while (it != v3.end()) {
cout << *it << " ";
it++;
}
cout << endl;
}
int main() {
test_vector1();
return 0;
}
2.3 范围 for 遍历
void test_vector1() {
vector<int> v2(5, 1);
vector<int> v4(++v2.begin(), --v2.end());
for (auto e : v4) {
cout << e << " ";
}
cout << endl;
}
int main() {
test_vector1();
return 0;
}
3、空间管理:size、capacity 与扩容策略
vector 的'空间'分为 size(有效元素个数)和 capacity(最大可容纳元素个数),理解两者区别是避免扩容开销的关键。
核心空间接口:
| 空间接口 | 接口说明 | 代码示例 |
|---|
| size() | 获取有效元素个数 | cout << v.size(); |
| capacity() | 获取容量大小 | cout << v.capacity(); |
| empty() | 判断是否为空(size==0) | if (v.empty()) { ... } |
| reserve(n)(重点) | 扩容到 n(仅开辟空间,不初始化) | v.reserve(100); |
| resize(n, val)(重点) | 调整 size 到 n(缺省用 0 填充) | v.resize(5, 2); |
关键:vector 扩容策略
vector 扩容时会分配新内存、迁移旧元素、释放旧内存,这个过程耗时较高。不同编译器扩容倍数不同:
- VS:1.5 倍扩容(如 capacity 从 4→6→9→13...)
- G++:2 倍扩容(如 capacity 从 4→8→16→32...)
reserve 只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代价缺陷问题。
resize 在开空间的同时还会进行初始化,影响 size。
string 和 vector 有一个非常大的区别就在于 reserve:vector 不会缩容,而 string 在 g++ 环境下可能会缩容到不小于 size 的空间大小。
避坑建议:若已知元素个数,提前用 reserve(n) 扩容,避免边插入边扩容的效率损耗。
void test_vector2() {
vector<int> v1(10, 1);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
v1.reserve(20);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
v1.reserve(15);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
v1.reserve(5);
cout << v1.size() << endl;
cout << v1.capacity() << endl;
}
int main() {
test_vector2();
return 0;
}
void test_vector2() {
vector<int> v2(10, 1);
for (auto e : v2) {
cout << e << " ";
}
cout << endl;
v2.resize(15, 2);
for (auto e : v2) {
cout << e << " ";
}
cout << endl;
v2.resize(5);
for (auto e : v2) {
cout << e << " ";
}
cout << endl;
}
int main() {
test_vector2();
return 0;
}
4、增删查改:日常开发高频操作
vector 的增删查改接口设计简洁,重点关注'尾插/尾删'的高效性和'中间插入/删除'的注意事项。
| 增删查改接口 | 接口说明 | 代码示例 | 时间复杂度 |
|---|
| push_back(val)(重点) | 尾插元素 val | v.push_back(5); | O(1)(无扩容时)/ O(n)(需扩容时) |
| pop_back()(重点) | 尾删最后一个元素 | v.pop_back(); | O(1)(仅修改 size,不释放空间) |
| insert(pos, val) | 在 pos 迭代器前插入 val | v.insert(v.begin()+2, 6); | O(n)(需移动 pos 后元素) |
| erase(pos) | 删除 pos 迭代器指向的元素 | v.erase(v.begin()+1); | O(n)(需移动 pos 后元素) |
4.1 内置类型的增删查改
void test_vector3() {
vector<int> v1(5, 1);
v1.push_back(2);
v1.insert(v1.begin(), 3);
v1.insert(v1.begin() + 3, 4);
cout << endl;
v1.erase(v1.begin() + 5);
}
int main() {
test_vector3();
return 0;
}
4.2 自定义类型的增删查改
class AA {
public:
AA(int a1 = 0, int a2 = 0) :_a1(a1), _a2(a2) {}
int _a1 = 1;
int _a2 = 1;
};
void test_vector3() {
AA aa1;
vector<AA> vAA1 = { aa1, {0, 0}, {1, 1}, {2, 2} };
vector<AA>::iterator it1 = vAA1.begin();
while (it1 != vAA1.end()) {
cout << it1->_a1 << "-" << it1->_a2 << " ";
it1++;
}
cout << endl;
vAA1.push_back({ 3, 3 });
AA aa2(4, 4);
vAA1.push_back(aa2);
vAA1.insert(vAA1.begin() + 1, aa2);
vector<AA>::iterator it2 = vAA1.begin();
while (it2 != vAA1.end()) {
cout << it2->_a1 << "-" << it2->_a2 << " ";
it2++;
}
}
int main {
();
;
}
4.3 vector 里面存 string 类型数据
#include<string>
void test_vector3() {
vector<string> vs1;
string s1("xxxxx");
vs1.push_back(s1);
vs1.push_back("yyyyy");
for (const auto& e : vs1) {
cout << e << " ";
}
cout << endl;
}
int main() {
test_vector3();
return 0;
}
4.4 vector 里面存 vector 类型数据(二维数组)
#include<string>
void test_vector3() {
vector<int> v(5, 1);
vector<vector<int>> vv(10, v);
vv[2][1] = 2;
for (size_t i = 0; i < vv.size(); i++) {
for (size_t j = 0; j < v.size(); j++) {
cout << vv[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
int main() {
test_vector3();
return 0;
}
第一次 [ ] 访问到的结果是 vector,第二次 [ ] 访问到的结果是 int。两个 [ ] 调用是两个不同的类,之所以上面代码没有写是因为编译器自动将 vector 的模板实例化出了 T 为 int 和 vector 两个不同的类。
三、vector 实战:OJ 算法题中的高频用法
1、只出现一次的数字
题目链接:
136. 只出现一次的数字 - 力扣(LeetCode)
C++ 算法代码:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int val = 0;
for(auto e : nums) {
val ^= e;
}
return val;
}
};
2、杨辉三角
题目链接:
118. 杨辉三角 - 力扣(LeetCode)
C++ 算法代码:
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv(numRows);
for(int i = 0; i < numRows; i++) {
vv[i].resize(i + 1, 0);
vv[i].front() = vv[i].back() = 1;
}
for(int j = 0; j < numRows - 2; j++) {
for(int k = 0; k <= j; k++) {
vv[j + 2][k + 1] = vv[j + 1][k] + vv[j + 1][k + 1];
}
}
return vv;
}
};
结束语
到此,vector 的常见相关接口的学习就讲解完了。由于前面 string 学习的铺垫,vector 学起来就变得比较轻松了。后续将对 vector 进行模拟实现,也是对 vector 的接口用底层代码进行更深入的理解并加以日常使用。
C++ 参考文档: