34、STL容器(vector):vector用法、迭代器、容量操作
说实话,vector 是我在 C++ 项目里用得最多的容器,没有之一。你想想看,一个能自动扩容的动态数组,用起来跟普通数组一样快,还不用自己管内存——这谁不爱?
我记得刚入行那会儿,有个同事还在用 new[] 和 delete[] 手动管理数组,结果内存泄漏查了三天。后来我给他推荐了 vector,他拍着桌子说:怎么不早告诉我!
vector 的基本用法
vector 说白了就是一个模板类,定义在 <vector> 头文件里。它的用法非常直观:
#include <vector>
#include <iostream>
int main() {
// 创建一个空的 vector,存放 int 类型
std::vector<int> vec;
// 尾部添加元素
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);
// 像普通数组一样访问
std::cout << vec[0] << std::endl; // 输出 10
std::cout << vec.at(1) << std::endl; // 输出 20
// 遍历
for (size_t i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " ";
}
std::cout << std::endl;
return 0;
}
这里有个小细节:vec[2] 和 vec.at(2) 都能访问元素,但 at() 会做边界检查,越界时抛出异常。我个人习惯在调试阶段用 at(),上线后换成 []——毕竟性能差那么一点点。
迭代器:遍历的利器
迭代器是什么?你可以把它想象成一个「智能指针」,专门用来遍历容器里的元素。vector 的迭代器是随机访问迭代器,支持 ++、--、+n、-n 等操作。
std::vector<int> vec = {1, 2, 3, 4, 5};
// 正向迭代器
for (auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " "; // 输出 1 2 3 4 5
}
// 反向迭代器
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
std::cout << *it << " "; // 输出 5 4 3 2 1
}
// 常量迭代器(只读)
for (auto it = vec.cbegin(); it != vec.cend(); ++it) {
// *it = 10; // 编译错误!不能修改
std::cout << *it << " ";
}
auto 就用 auto,省得写一堆 std::vector<int>::iterator 这种又臭又长的类型名。C++11 之后,auto 就是为迭代器而生的。
容量操作:别让 vector 频繁扩容
vector 的底层是一块连续内存。当元素数量超过当前容量时,它会重新申请一块更大的内存,把旧元素搬过去,再释放旧内存。这个过程叫「扩容」。
扩容很贵!我在项目中遇到过一个问题:循环里不断 push_back,结果 vector 反复扩容,性能直接崩了。后来加了一行 reserve,速度提升了 10 倍。
来看几个关键的容量操作:
| 方法 | 作用 | 时间复杂度 |
|---|---|---|
size() |
返回当前元素个数 | O(1) |
capacity() |
返回当前容量(最多能存多少元素而不扩容) | O(1) |
reserve(n) |
预留至少 n 个元素的空间 | O(n) |
resize(n) |
改变元素个数为 n,多出来的用默认值填充 | O(n) |
shrink_to_fit() |
请求释放多余容量(C++11 起) | O(n) |
empty() |
判断是否为空 | O(1) |
std::vector<int> vec;
std::cout << "size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;
// 输出: size: 0, capacity: 0
// 预留 100 个元素的空间
vec.reserve(100);
std::cout << "capacity: " << vec.capacity() << std::endl;
// 输出: capacity: 100
// 添加 50 个元素
for (int i = 0; i < 50; ++i) {
vec.push_back(i);
}
std::cout << "size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;
// 输出: size: 50, capacity: 100(没有扩容!)
// 缩小到实际大小
vec.shrink_to_fit();
std::cout << "capacity: " << vec.capacity() << std::endl;
// 输出: capacity: 50(可能略大于 50,取决于实现)
reserve 只预留空间,不创建元素。如果你用 reserve(100) 之后直接 vec[0] = 10,那是未定义行为!必须先 resize 或者 push_back。
vector 扩容机制详解
为什么 vector 要按倍数扩容?我简单解释一下:如果每次只多申请一个元素的空间,那插入 n 个元素的时间复杂度是 O(n²)。而按 1.5 倍或 2 倍扩容,均摊下来每个插入操作是 O(1)。
不同的编译器实现不一样:
- GCC(libstdc++):扩容因子是 2
- Clang(libc++):扩容因子是 2
- MSVC:扩容因子是 1.5
嗯,这里要注意:扩容因子不是越大越好。2 倍扩容虽然均摊性能好,但内存浪费也大。1.5 倍扩容更节省内存,但扩容次数会多一些。我个人觉得不用纠结这个,除非你在做嵌入式或者内存极度受限的场景。
知识体系总览
下面这张图帮你理清 vector 的核心知识点:
实用技巧与避坑指南
最后分享几个我这些年积累的经验:
- 提前预留空间:如果你知道大概要存多少元素,先用
reserve把空间申请好。我在写网络消息处理模块时,预估每条消息最多 1024 字节,直接reserve(1024),性能提升很明显。 - 迭代器失效:vector 在扩容后,所有迭代器、指针、引用都会失效。我曾经在循环里一边
push_back一边用迭代器遍历,结果程序崩溃了。记住:插入或删除操作后,之前的迭代器就别用了。 - 用 emplace_back 代替 push_back:C++11 引入了
emplace_back,它直接在 vector 内部构造对象,省去了临时对象的拷贝。对于复杂类型,能快不少。 - shrink_to_fit 不是强制性的:它只是向系统「请求」释放多余内存,系统不一定会照做。如果你真的需要严格控制内存,不如重新构造一个 vector 然后 swap。
// 强制释放多余内存的经典写法
std::vector<int>(vec).swap(vec);
// 或者 C++11 之后
vec.shrink_to_fit();
总结一下:vector 是 C++ 里最实用的容器之一。掌握它的用法、迭代器和容量操作,能让你写出更高效、更安全的代码。别小看这些基础操作,我在面试时见过不少人连 reserve 和 resize 的区别都说不清楚——嗯,希望你不是其中之一。
公众号:蓝海资料掘金营,微信deep3321