C++ 中的 vector
1218 字
6 分钟
C++ 中的 vector
std::vector 是 C++ 中最重要、最常用的容器,没有之一。它的本质是动态数组(Dynamic Array)。
std::vector 是在堆(Heap)上管理一块连续的内存,可以存放任意类型的对象。
核心特性与底层原理
- 头文件:
#include <vector> - 内存模型:连续内存。这意味着它和 C 数组一样,支持通过指针偏移量快速访问,并且对 CPU 缓存(Cache)非常友好。
- 自动扩容:当存入数据量超过当前容量时,
std::vector就会申请一块更大的内存(通常是原来的 1.5 倍或 2 倍),将旧数据移动/拷贝过去,然后释放旧内存。
初始化与构造
#include <vector>
// 1. 默认构造(空 vector)std::vector<int> v1;
// 2. 指定大小和默认值std::vector<int> v2(10); // 10 个元素,默认初始化 0std::vector<int> v3(10, 5); // 10 个元素,每个都是 5
// 3. 列表初始化(C++11)std::vector<int> v4 = {1, 2, 3, 4};
// 4. 拷贝构造std::vector<int> v5(v4);
// 5. 迭代器范围构造(常用与从其他容器拷贝)int arr[] = {10, 20, 30}std::vector<int> v6(arr, arr + 3);容量与大小
| 函数 | 说明 | 备注 |
|---|---|---|
size() | 当前元素个数 | 实际存了多少个 |
capacity() | 当前分配的内存能存多少个 | capacity size |
empty() | 是否为空 | 推荐使用,比 size() == 0 更语义化 |
reserve() | 预分配内存 | 仅改变 capacity,不改变 size |
resize(n) | 改变元素个数 | 改变 size,如果变大则填充默认值 |
shrink_to_fit() | 释放未使用的内存(C++11) | 让 capacity 搜索到 size 大小 |
为什么 reserve 非常重要?
std::vector<int> v;v.reserve(1000); // 一次性分配好内存for (int i = 0; i < 1000; i++) { v.push_back(i); // 这里不会再发生内存重新分配,效率极高}增删查改
插入与添加
push_back(val):在尾部添加元素(会发生拷贝或移动)。emplace_back(arg...)(C++11):原地构造。直接在std::vector尾部构造对象,省去了一次临时对象的构造和拷贝 / 移动,效率通常更高。insert(it, val):在迭代器指向的位置插入。效率为 ,因为要移动后续所有元素。
删除
pop_back:删除尾部元素()。erase(it):删除指定位置元素(,后续元素前移)。clear():清空所有元素,szie变为 0,但capacity通常不变(内存不释放)。
访问
v[i]:下标访问,不检查越界。v.at[i]:检查越界,越觉抛std::out_of_range。v.front()/v.back():访问首尾。v.data():返回指向底层数组首元素的指针(T*)。常用于和 C 语言 API 交互。
迭代器失效
由于 std::vector 是连续内存,当结构发生变化时,指向旧内存的迭代器、指针、引用可能会失效。
- 扩容时失效:当
push_back导致std::vector扩容(reallocate)时,原内存被释放,所有指向原数据的迭代器 / 指针瞬间全部失效。 - 插入 / 删除时失效:当
insert或erase一个位置时,该位置之后的所有迭代器都会失效(因为数据移动了)。
std::vector<int> v = {1, 2, 3, 4};for (auto it = v.begin(); it != v.end(); ++it) { if (*it % 2 == 0) { v.erase(it); // 错误!erase 后 it 已失效,下一次 ++it 会崩溃 }}
// 正确写法(利用 erase 返回值更新迭代器)for (auto it = v.begin(); it != v.end()) { if (*it % 2 == 0) { it = v.erase(it); // erase 返回指向下一个元素的迭代器 } else { ++it; }}特殊版本:std::vector<bool>
这是一个历史遗留的 “坑”。为了节省空间,C++ 标准库特化了 std::vector<bool>,它不是存储 bool(1 字节),而是存储 bit(1 比特)。
后果:
- 你无法获得元素的地址:
&v[0]是非法的,因为无法寻址单个比特。 - 它的
operator[]返回的不是bool&,而是一个代理对象。 - 非线程安全:并发读写邻近的 bit 可能会导致数据竞争(因为它们位于同一个字节内)。
建议:如果需要存布尔值且不缺那点内存,用 std::vector<char> 或 std::deque<bool> 代替。如果确实需要位操作,考虑使用 std::bitset。
现代化操作
C++20:std::erase 和 std::erase_if
在 C++20 之前,要从 std::vector 中删除满足特定条件的所以元素,需要使用 “Erase-Remove Idiom”(v.erase(std::remove(...), v.end()),非常啰嗦。
C++20 简化了:
std::vector<int> v = {1, 2, 3, 4, 5, 6};
// 删除所有偶数std::erase_if(v, [](int x) { return x % 2 == 0; });最佳实践
- 优先使用
emplace_back:代替push_back,特别是存放复杂对象时。 - 善用
reserve:如果你能预估数据量,一定要先reserve,在数据量较大时,能够极大的优化性能。 - 避免头部/中间插入:在
std::vector头部插入数据(insert(begin(), val))是非常慢的(),如果有这种需求,请改用std::deque或std::list。 - 慎用
std::vector<bool>:除非你清楚你自己在做什么。 - 小心引用失效:在循环中做
push_back时,千万不要同时持有指向该std::vector内部元素的引用,一旦扩容,引用就变成悬空指针了。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!
C++ 中的 vector
https://bfmhno3.github.io/posts/vector-in-cpp/相关文章智能推荐
1
C++ 中的 stack
Note本文从本质需求出发解释栈(LIFO)为何存在,串联函数调用、递归、撤销与括号匹配等典型场景,并结合 C++ STL 的 std::stack 用法、异常安全设计与底层容器选择,最后配合多道题目示例完成从概念到实战的闭环。
2
C++ 中的 list
Note深入理解 C++ 中的 std::list 双向链表容器,探讨其底层原理、独有优势(如头部操作、接合等)、迭代器特性,以及与 std::vector 的选择权衡。
3
C++ 中的 string
Note深入理解 C++ 标准库中的 std::string 类:从基本用法到底层原理,涵盖初始化、容量管理、元素访问、修改操作、类型转换等核心知识点,以及 SSO 优化、string_view 等现代特性和最佳实践。
4
C++ 中的构造函数
Note深入理解 C++ 构造函数的本质:从资源管理到性能优化。涵盖默认构造、拷贝构造、移动构造,以及 RAII、explicit、constexpr 等关键机制。
5
C++ 中的初始化列表和列表初始化
Note深入理解 C++ 中初始化列表与列表初始化的区别:前者解决对象生命周期与内存模型问题,后者统一初始化语法并提供类型安全保障。
随机文章随机推荐











