std::vector 是 C++ 标准库(<vector> 头文件)中最核心的动态数组容器,封装了可自动扩容的连续内存空间,兼具数组的随机访问效率和动态扩容的灵活性,是日常开发中使用频率最高的容器。以下从核心特性、内存模型、基础用法、高级操作、实现细节、性能优化、避坑要点七个维度,全面讲解 vector 的所有关键知识点。
一、核心特性(与原生数组的对比)
vector 本质是动态连续数组,核心优势是 “动态扩容 + 随机访问”,对比原生数组(T[])的核心差异如下:
| 特性 | std::vector | 原生数组(T []) |
|---|---|---|
| 内存管理 | 自动扩容 / 释放,无需手动管理 | 固定长度,扩容需手动 new/delete |
| 随机访问 | O (1) 效率(同数组),支持 []/at() | O (1) 效率,仅支持 [] |
| 长度灵活性 | 动态增删元素,size() 可动态变化 | 长度固定,无法直接增删 |
| 越界检查 | at() 抛 out_of_range 异常 | 无检查,越界为未定义行为(UB) |
| 功能丰富度 | 内置插入、删除、排序、遍历等方法 | 需依赖 <algorithm> 手动实现 |
| 内存连续性 | 元素存储在连续内存(同数组) | 连续内存 |
核心头文件与命名空间
#include <vector> // 核心头文件
#include <algorithm> // 排序、查找等算法(可选)
using namespace std; // 否则需写 std::vector
二、内存模型(底层实现核心)
vector 的底层是连续的内存块,通过三个核心指针 / 成员管理内存(简化版实现):
template <class T>
class vector {
private:
T* start_; // 指向数组起始位置(第一个元素)
T* finish_; // 指向数组末尾的下一个位置(已用空间的末尾)
T* end_of_storage_; // 指向内存块末尾的下一个位置(总容量的末尾)
};
size():已用元素个数 =finish_ - start_;capacity():总容量 =end_of_storage_ - start_;- 内存布局:
[start_, finish_)是已用空间,[finish_, end_of_storage_)是空闲空间。
1. 扩容机制(核心)
当 push_back()/insert() 等操作导致 size() == capacity() 时,vector 会自动扩容,核心步骤:
- 分配新内存:新容量通常为原容量的 2 倍(部分编译器为 1.5 倍,如 GCC);
- 拷贝 / 移动元素:将原内存中的元素拷贝(C++11 前)或移动(C++11 后)到新内存;
- 释放原内存:销毁原内存中的元素,释放原内存;
- 更新指针:
start_/finish_/end_of_storage_指向新内存。
扩容示例(直观理解)
vector<int> v;
cout << v.size(); // 0
cout << v.capacity(); // 0(初始容量为0)
v.push_back(1); // 容量不足,扩容到 1
cout << v.capacity(); // 1
v.push_back(2); // 容量不足,扩容到 2
cout << v.capacity(); // 2
v.push_back(3); // 容量不足,扩容到 4(2倍)
cout << v.capacity(); // 4
v.push_back(4); // 容量足够,不扩容
cout << v.capacity(); // 4
v.push_back(5); // 容量不足,扩容到 8(2倍)
cout << v.capacity(); // 8
2. 预分配容量(reserve)
频繁扩容会导致 “内存重新分配 + 元素拷贝”,性能损耗大,可通过 reserve(n) 提前预分配容量:
vector<int> v;
v.reserve(100); // 预分配 100 个元素的容量,size() 仍为 0
cout << v.size(); // 0
cout << v.capacity(); // 100
// 后续添加 100 个元素不会触发扩容
for (int i = 0; i < 100; ++i) {
v.push_back(i); // 无扩容,性能最优
}
3. 收缩容量(shrink_to_fit)
C++11 引入 shrink_to_fit(),将容量缩减至与实际大小一致(释放空闲内存),但这是 “请求” 而非 “强制”(编译器可忽略):
vector<int> v;
v.reserve(100);
v.push_back(10);
cout << v.capacity(); // 100
v.shrink_to_fit();
cout << v.capacity(); // 10(容量匹配 size())
三、基础用法(必掌握)
1. 构造与初始化
vector 提供多种构造方式,覆盖不同场景:
| 构造方式 | 示例 | 说明 |
|---|---|---|
| 默认构造 | vector<int> v; | 空 vector,size=0,capacity=0 |
| 指定大小构造 | vector<int> v(5); | 5 个元素,默认值为 0(int) |
| 指定大小 + 初始值 | vector<int> v(5, 10); | 5 个元素,每个值为 10 |
| 拷贝构造 | vector<int> v2(v1); | 深拷贝 v1 的所有元素 |
| 移动构造 | vector<int> v2(std::move(v1)); | 转移 v1 的资源,O (1) 开销 |
| 迭代器范围构造 | vector<int> v(v1.begin(), v1.end()); | 从迭代器范围构造 |
| 列表初始化(C++11) | vector<int> v = {1,2,3,4,5}; | 直接初始化元素 |
示例:构造函数
// 基础构造
vector<int> v1; // 空
vector<int> v2(3); // [0,0,0]
vector<int> v3(3, 5); // [5,5,5]
vector<int> v4(v3); // [5,5,5]
vector<int> v5 = {1,2,3}; // [1,2,3]
vector<int> v6(v5.begin()+1, v5.end()); // [2,3]
// 移动构造(v5 变为空)
vector<int> v7 = std::move(v5);
cout << v5.empty(); // true
cout << v7.size(); // 3
2. 元素访问
提供 4 种访问方式,各有适用场景:
| 访问方式 | 语法 | 越界处理 | 适用场景 |
|---|---|---|---|
| 下标运算符 | v[i] | 无检查(UB) | 性能优先,确认索引合法 |
| at () 方法 | v.at(i) | 抛 out_of_range 异常 | 安全性优先,索引可能越界 |
| 首元素 | v.front() | 空 vector 时 UB | 快速获取第一个元素 |
| 尾元素 | v.back() | 空 vector 时 UB | 快速获取最后一个元素 |
| 数据指针 | v.data() | 返回 T* 指向首元素 | 兼容 C 接口(如 C 函数) |
示例:元素访问
vector<int> v = {10,20,30,40};
cout << v[1]; // 20(无越界检查)
cout << v.at(2); // 30(越界抛异常)
cout << v.front(); // 10
cout << v.back(); // 40
// 兼容 C 接口
int* ptr = v.data();
cout << ptr[3]; // 40
3. 元素添加
| 添加方式 | 语法 | 说明 | 时间复杂度 |
|---|---|---|---|
| push_back | v.push_back(5); | 尾部添加元素 | 平均 O (1)(扩容时 O (n)) |
| emplace_back | v.emplace_back(5); | 尾部直接构造元素(C++11) | 平均 O (1)(比 push_back 高效) |
| insert | v.insert(pos, val) | 指定位置插入元素 | O (n)(后续元素需后移) |
| emplace | v.emplace(pos, val) | 指定位置直接构造元素(C++11) | O (n)(比 insert 高效) |
关键区别:push_back vs emplace_back
push_back:先构造元素,再拷贝 / 移动到 vector 尾部;emplace_back:直接在 vector 尾部的内存中构造元素,避免拷贝 / 移动,性能更优(尤其对大对象)。
示例:元素添加
vector<int> v = {1,2,3};
// 尾部添加
v.push_back(4); // [1,2,3,4]
v.emplace_back(5); // [1,2,3,4,5]
// 指定位置插入
auto it = v.begin() + 2;
v.insert(it, 10); // [1,2,10,3,4,5]
v.emplace(it, 20); // [1,2,20,10,3,4,5]
// 插入多个元素
v.insert(v.end(), {6,7,8}); // [1,2,20,10,3,4,5,6,7,8]
4. 元素删除
| 删除方式 | 语法 | 说明 | 时间复杂度 |
|---|---|---|---|
| pop_back | v.pop_back(); | 删除尾部元素 | O(1) |
| erase | v.erase(pos); | 删除指定位置元素 | O (n)(后续元素需前移) |
| erase(范围) | v.erase(beg, end); | 删除指定范围元素 | O(n) |
| clear | v.clear(); | 清空所有元素(size=0,capacity 不变) | O (n)(销毁所有元素) |
示例:元素删除
vector<int> v = {1,2,3,4,5,6};
v.pop_back(); // 删除尾部 → [1,2,3,4,5]
v.erase(v.begin()+2); // 删除索引2 → [1,2,4,5]
v.erase(v.begin()+1, v.begin()+3); // 删除索引1-2 → [1,5]
v.clear(); // 清空 → size=0,capacity 仍为原容量
cout << v.size(); // 0
cout << v.capacity(); // 6(未变)
5. 常用状态判断
| 方法 | 功能 | 示例 |
|---|---|---|
| empty() | 判断是否为空(size=0) | if (v.empty()) { ... } |
| size() | 返回元素个数 | cout << v.size(); |
| capacity() | 返回总容量 | cout << v.capacity(); |
| max_size() | 返回最大可容纳的元素个数(系统限制) | cout << v.max_size(); |
四、高级操作(C++11+ 特性)
1. 移动语义(C++11)
vector 实现了移动构造和移动赋值,避免深拷贝,提升性能:
// 移动构造:v1 转移资源给 v2,v1 变为空
vector<int> v1 = {1,2,3};
vector<int> v2 = std::move(v1);
// 移动赋值
vector<int> v3;
v3 = std::move(v2);
2. 范围 for 循环(C++11)
vector<int> v = {1,2,3,4,5};
// 只读遍历
for (int num : v) {
cout << num << " "; // 1 2 3 4 5
}
// 修改元素
for (int& num : v) {
num *= 2; // 每个元素乘2 → [2,4,6,8,10]
}
3. 迭代器(遍历核心)
vector 支持随机访问迭代器(最灵活的迭代器类型),可进行算术运算(+/-/++/--):
| 迭代器类型 | 语法 | 说明 |
|---|---|---|
| 普通迭代器 | v.begin()/v.end() | 指向首元素 / 尾后位置,可修改元素 |
| 常量迭代器 | v.cbegin()/v.cend() | 只读,不可修改元素(C++11) |
| 反向迭代器 | v.rbegin()/v.rend() | 反向遍历(从尾到首) |
示例:迭代器遍历
vector<int> v = {1,2,3,4,5};
// 普通迭代器(修改元素)
for (auto it = v.begin(); it != v.end(); ++it) {
*it += 10; // [11,12,13,14,15]
}
// 常量迭代器(只读)
for (auto it = v.cbegin(); it != v.cend(); ++it) {
cout << *it << " "; // 11 12 13 14 15
}
// 反向迭代器
for (auto it = v.rbegin(); it != v.rend(); ++it) {
cout << *it << " "; // 15 14 13 12 11
}
4. 算法结合(<algorithm>)
vector 是连续内存,完美适配 STL 算法,常用算法如下:
| 算法 | 功能 | 示例 |
|---|---|---|
| sort | 排序(默认升序) | sort(v.begin(), v.end()); |
| reverse | 反转元素顺序 | reverse(v.begin(), v.end()); |
| find | 查找元素(返回迭代器) | auto it = find(v.begin(), v.end(), 5); |
| count | 统计元素出现次数 | int cnt = count(v.begin(), v.end(), 3); |
| remove | 移除指定元素(需配合 erase) | v.erase(remove(v.begin(), v.end(), 2), v.end()); |
示例:算法使用
vector<int> v = {3,1,4,1,5,9,2,6};
// 排序(升序)
sort(v.begin(), v.end()); // [1,1,2,3,4,5,6,9]
// 反转
reverse(v.begin(), v.end()); // [9,6,5,4,3,2,1,1]
// 查找元素 5
auto it = find(v.begin(), v.end(), 5);
if (it != v.end()) {
cout << "找到 5,索引:" << it - v.begin() << endl; // 索引 2
}
// 移除所有 1(remove+erase 是标准用法)
v.erase(remove(v.begin(), v.end(), 1), v.end()); // [9,6,5,4,3,2]
5. swap(交换两个 vector)
swap 交换两个 vector 的内部指针(start_/finish_/end_of_storage_),时间复杂度 O (1),无元素拷贝:
vector<int> v1 = {1,2,3};
vector<int> v2 = {4,5,6};
v1.swap(v2); // v1=[4,5,6], v2=[1,2,3]
五、实现细节(深入理解)
1. 内存分配器(Allocator)
vector 依赖 STL 分配器(默认 std::allocator<T>)管理内存,分配器负责 allocate()(分配内存)和 deallocate()(释放内存)。可自定义分配器,但日常开发极少用到:
// 自定义分配器示例(简化版)
template <class T>
struct MyAllocator {
using value_type = T;
T* allocate(size_t n) {
return static_cast<T*>(malloc(n * sizeof(T)));
}
void deallocate(T* p, size_t n) {
free(p);
}
};
// 使用自定义分配器
vector<int, MyAllocator<int>> v;
2. 元素的构造与析构
vector 严格区分 “内存分配” 和 “元素构造”:
reserve(n):仅分配内存,不构造元素(size()不变);resize(n):分配内存 + 构造 / 析构元素(size()变为 n);- 若
n > size():新增元素用默认值 / 指定值构造; - 若
n < size():销毁多余元素。
- 若
示例:resize vs reserve
vector<int> v = {1,2,3};
v.reserve(5); // size=3,capacity=5(无元素构造)
v.resize(5); // size=5,capacity=5(新增 2 个元素,值为 0)
v.resize(2); // size=2,capacity=5(销毁最后 3 个元素)
3. 迭代器失效(核心坑点)
vector 的连续内存特性导致某些操作会使迭代器 / 指针 / 引用失效,核心规则如下:
| 操作 | 迭代器失效情况 |
|---|---|
push_back()/emplace_back() | 扩容时所有迭代器失效;未扩容时仅尾后迭代器失效 |
insert()/emplace() | 插入位置后的迭代器失效;扩容时所有迭代器失效 |
erase() | 删除位置后的迭代器失效;尾后迭代器失效 |
resize()/reserve() | 扩容时所有迭代器失效;否则仅尾后迭代器失效 |
clear() | 所有迭代器失效(尾后迭代器除外) |
swap() | 原迭代器指向交换后的元素(不失效,指向新位置) |
示例:迭代器失效处理
vector<int> v = {1,2,3,4,5};
auto it = v.begin() + 2; // 指向 3
v.push_back(6); // 未扩容,it 仍有效
cout << *it; // 3
v.reserve(10); // 扩容,it 失效(野指针)
// cout << *it; // 未定义行为
// 正确做法:重新获取迭代器
it = v.begin() + 2;
cout << *it; // 3
六、性能优化(实战技巧)
1. 提前 reserve 避免扩容
频繁 push_back() 小元素时,提前 reserve 可将时间复杂度从 O (n) 降至 O (1):
// 优化前:多次扩容
vector<int> v;
for (int i = 0; i < 1000000; ++i) {
v.push_back(i); // 扩容约 20 次(2^0 → 2^19)
}
// 优化后:无扩容
vector<int> v;
v.reserve(1000000);
for (int i = 0; i < 1000000; ++i) {
v.push_back(i); // 无扩容,性能提升显著
}
2. 优先使用 emplace_back 而非 push_back
emplace_back 直接在 vector 尾部构造元素,避免临时对象的拷贝 / 移动:
// 自定义大对象
struct BigObj {
int a;
string s;
BigObj(int a_, string s_) : a(a_), s(s_) {}
};
vector<BigObj> v;
v.push_back(BigObj(1, "hello")); // 构造临时对象 → 移动/拷贝到尾部
v.emplace_back(2, "world"); // 直接在尾部构造,无临时对象
3. 批量插入 / 删除
避免循环中单次 insert()/erase()(每次 O (n)),优先批量操作:
// 优化前:循环 insert,O(n²) 复杂度
vector<int> v;
for (int i = 0; i < 1000; ++i) {
v.insert(v.begin(), i); // 每次插入都要移动所有元素
}
// 优化后:先构造临时 vector,再拼接,O(n) 复杂度
vector<int> temp;
temp.reserve(1000);
for (int i = 0; i < 1000; ++i) {
temp.push_back(i);
}
v.insert(v.begin(), temp.rbegin(), temp.rend()); // 批量插入
4. 避免频繁 shrink_to_fit
shrink_to_fit() 会触发内存重新分配,频繁调用反而降低性能,仅在确定不再扩容时使用。
七、避坑要点(高频错误)
1. 越界访问
v[i]越界是未定义行为(可能崩溃、篡改内存);- 优先用
v.at(i)(抛异常),或先检查i < v.size():vector<int> v = {1,2,3}; // 错误:越界 cout << v[5]; // UB // 正确:检查边界 if (5 < v.size()) { cout << v[5]; } else { cout << "索引越界"; }
2. 迭代器失效后使用
迭代器失效后继续解引用,会导致未定义行为,核心解决方法:
- 插入 / 删除后重新获取迭代器;
- 利用
erase()的返回值(指向删除位置的下一个迭代器):vector<int> v = {1,2,3,4,5}; auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) { it = v.erase(it); // erase 返回下一个有效迭代器 } else { ++it; } }
3. 混淆 size () 和 capacity ()
size()是 “已用元素个数”,capacity()是 “总容量”;- 遍历只能用
size(),不能用capacity():vector<int> v; v.reserve(5); // 错误:capacity=5,但 size=0,循环不会执行 for (int i = 0; i < v.capacity(); ++i) { v[i] = i; // UB(越界) }
4. 空 vector 访问 front ()/back ()
空 vector 调用 front()/back() 是未定义行为,需先检查 empty():
vector<int> v;
// 错误:空 vector
// cout << v.front();
// 正确:先检查
if (!v.empty()) {
cout << v.front();
}
5. 拷贝大 vector 导致性能问题
大 vector 拷贝(如 vector<int> v2 = v1)会深拷贝所有元素,耗时严重,优先用移动语义(std::move)或引用:
vector<int> v1(1000000, 1);
// 错误:深拷贝,耗时
vector<int> v2 = v1;
// 正确:移动,O(1)
vector<int> v3 = std::move(v1);
// 或用引用
vector<int>& v4 = v1;
八、核心总结(必记知识点)
- 核心本质:动态连续数组,兼具数组的随机访问效率和动态扩容的灵活性;
- 核心概念:
size():已用元素个数,capacity():总容量;reserve(n):预分配容量(不构造元素),resize(n):调整大小(构造 / 析构元素);- 扩容规则:默认 2 倍扩容,扩容时元素拷贝 / 移动,迭代器失效;
- 关键操作:
- 添加:
push_back()(拷贝)/emplace_back()(直接构造); - 删除:
pop_back()(尾部)/erase()(指定位置); - 遍历:迭代器、范围 for、
[];
- 添加:
- 性能优化:提前
reserve避免扩容,优先emplace_back,批量操作; - 避坑重点:
- 迭代器失效后禁止使用,
erase()需接收返回值; - 区分
size()和capacity(),避免越界访问; - 大 vector 优先用移动语义,避免深拷贝。
- 迭代器失效后禁止使用,
vector是面试中经常被问到的,除了能回答出基本的用法以及底层,最好要对底层的内存分配器有深入了解。
2435



被折叠的 条评论
为什么被折叠?



