1. 为什么需要掌握vector?
作为C++标准模板库(STL)中最常用的容器之一,vector几乎出现在每个C++项目中。它本质上是一个动态数组,但比传统数组强大得多。我在实际开发中发现,90%需要使用数组的场景都可以用vector替代,而且更安全、更方便。
vector最大的优势在于其动态扩容特性。想象一下你有一个会"自动长大"的数组:当空间不足时,它会自动申请更大的内存,把原有数据搬过去。这个特性让开发者从繁琐的内存管理中解放出来,可以更专注于业务逻辑。
2. vector的核心特性解析
2.1 内存管理机制
vector内部使用连续的内存空间存储元素,这与普通数组一致。但不同的是,vector会维护两个重要属性:
- size:当前存储的元素数量
- capacity:当前分配的内存可容纳的元素数量
当size达到capacity时,vector会自动扩容。典型的扩容策略是申请当前容量2倍的新空间,然后将元素从旧空间拷贝到新空间。这个过程虽然耗时,但通过reserve()方法可以预先分配足够空间,避免频繁扩容。
提示:在已知元素数量的情况下,先用reserve()预分配空间可以显著提升性能
2.2 时间复杂度分析
理解各种操作的时间复杂度对写出高效代码至关重要:
- 随机访问:O(1) - 和数组一样快
- 尾部插入/删除:平均O(1)
- 中间插入/删除:O(n) - 需要移动后续元素
- 扩容操作:O(n) - 需要拷贝所有元素
3. vector的构造与初始化
3.1 基本构造方法
cpp复制// 空vector
vector<int> v1;
// 包含5个元素,默认初始化为0
vector<int> v2(5);
// 包含5个元素,每个都初始化为10
vector<int> v3(5, 10);
// 使用初始化列表
vector<int> v4 = {1, 2, 3};
// 拷贝构造
vector<int> v5(v4);
// 移动构造(C++11)
vector<int> v6(std::move(v4));
3.2 高级构造技巧
cpp复制// 从数组构造
int arr[] = {1, 2, 3};
vector<int> v7(arr, arr + sizeof(arr)/sizeof(arr[0]));
// 从另一个vector的部分元素构造
vector<int> v8(v5.begin(), v5.begin() + 2);
// 使用assign方法重置内容
v8.assign(3, 100); // 3个100
v8.assign({4,5,6}); // 初始化列表
4. 容量管理实战
4.1 容量查询方法
cpp复制vector<int> v(10);
cout << "size: " << v.size(); // 元素数量:10
cout << "capacity: " << v.capacity(); // 实际容量:>=10
cout << "empty: " << v.empty(); // 是否为空:0(false)
4.2 容量调整策略
cpp复制vector<int> v;
// 预分配空间 - 避免后续插入时的多次扩容
v.reserve(1000);
for(int i=0; i<1000; i++) {
v.push_back(i); // 不会触发扩容
}
// 释放多余空间(C++11)
v.shrink_to_fit();
经验:在循环前调用reserve()是提升vector性能的最简单有效方法
5. 元素访问的多种方式
5.1 安全与不安全访问对比
cpp复制vector<int> v = {1, 2, 3};
// 不安全但快速的访问方式
int a = v[10]; // 未定义行为,可能崩溃
// 安全的访问方式
try {
int b = v.at(10); // 抛出std::out_of_range异常
} catch(const std::out_of_range& e) {
cerr << e.what() << endl;
}
5.2 特殊位置访问
cpp复制vector<int> v = {1, 2, 3};
// 首元素
int first = v.front(); // 1
// 末元素
int last = v.back(); // 3
// 底层指针(谨慎使用)
int* p = v.data();
*p = 10; // 直接修改第一个元素
6. 元素修改全攻略
6.1 添加元素的最佳实践
cpp复制vector<string> names;
// 传统添加方式
names.push_back("Alice");
// C++11更高效的emplace_back
names.emplace_back("Bob"); // 避免临时对象创建
// 在指定位置插入
names.insert(names.begin(), "Eve");
// 批量插入
names.insert(names.end(), {"Charlie", "David"});
6.2 删除元素的正确姿势
cpp复制vector<int> nums = {1, 2, 3, 4, 5, 6};
// 删除尾部元素
nums.pop_back();
// 删除指定位置
nums.erase(nums.begin());
// 删除范围
nums.erase(nums.begin()+1, nums.begin()+3);
// 清空所有
nums.clear();
注意:erase()会返回指向下一个有效元素的迭代器,这在循环中删除时很有用
7. 迭代器的高级用法
7.1 标准迭代模式
cpp复制vector<int> v = {1, 2, 3};
// 正向遍历
for(auto it = v.begin(); it != v.end(); ++it) {
cout << *it << " ";
}
// 反向遍历
for(auto it = v.rbegin(); it != v.rend(); ++it) {
cout << *it << " ";
}
7.2 迭代器失效问题
vector的某些操作会使迭代器失效,这是常见陷阱:
cpp复制vector<int> v = {1, 2, 3, 4};
auto it = v.begin() + 2;
v.insert(v.begin(), 0); // 插入可能导致扩容
// 此时it可能失效!
// 安全做法:重新获取迭代器
it = v.begin() + 3;
8. 性能优化技巧
8.1 减少不必要的拷贝
cpp复制vector<vector<int>> matrix;
// 低效做法:创建临时vector并拷贝
vector<int> row;
row.push_back(1);
matrix.push_back(row);
// 高效做法:直接构造
matrix.emplace_back(initializer_list<int>{1});
8.2 使用swap技巧释放内存
cpp复制vector<int> v(1000);
// 传统clear()不释放内存
v.clear();
cout << v.capacity(); // 可能还是1000
// 使用swap技巧真正释放内存
vector<int>().swap(v);
cout << v.capacity(); // 0
9. 实际项目中的经验教训
9.1 避免在循环中判断empty()
cpp复制// 低效写法
while(!v.empty()) {
process(v.back());
v.pop_back();
}
// 高效写法
while(!v.empty()) {
process(v.back());
v.pop_back();
}
9.2 谨慎使用data()指针
cpp复制vector<int> v = {1, 2, 3};
int* p = v.data();
v.push_back(4); // 可能导致扩容,p失效
// 此时使用p是危险的
*p = 10; // 未定义行为
10. vector与其他容器的选择
虽然vector很强大,但并非所有场景都适用:
- 需要频繁在头部插入/删除:考虑deque
- 需要频繁在任意位置插入/删除:考虑list
- 需要快速查找:考虑set/map
- 元素数量固定:考虑array(C++11)
选择容器的黄金法则:根据最频繁的操作来选择最适合的容器。