1. vector容器概述
在C++标准模板库(STL)中,vector是最基础也是最重要的容器之一。它本质上是一个动态数组,能够自动管理内存并在运行时根据需要调整大小。与普通数组相比,vector提供了更灵活的元素管理方式,让开发者从繁琐的内存管理中解放出来。
vector的底层实现是一个连续的内存空间,这使得它支持随机访问,访问任意元素的时间复杂度都是O(1)。同时,vector会自动处理扩容问题,当当前容量不足时,它会分配更大的内存空间并将原有元素复制到新空间。不同编译器的扩容策略可能不同,VS通常采用1.5倍扩容,而g++则采用2倍扩容。
提示:虽然vector的自动扩容很方便,但频繁扩容会导致性能损耗。如果事先知道大概需要多少空间,建议使用reserve()预分配足够容量。
2. vector的构造与初始化
2.1 基本构造函数
vector提供了多种构造函数来满足不同场景下的初始化需求:
cpp复制// 默认构造 - 创建空vector
std::vector<int> vec1;
// 填充构造 - 创建包含5个值为10的元素
std::vector<int> vec2(5, 10);
// 范围构造 - 从数组初始化
int arr[] = {1,2,3,4,5};
std::vector<int> vec3(arr, arr+5);
// 拷贝构造 - 从另一个vector初始化
std::vector<int> vec4(vec3);
2.2 C++11新增的初始化方式
C++11引入了更简洁的初始化语法:
cpp复制// 列表初始化
std::vector<int> vec5 = {1,2,3,4,5};
// 移动构造 - 高效转移资源
std::vector<int> vec6(std::move(vec5));
移动构造函数特别适合临时vector的传递,它避免了不必要的拷贝操作,直接"窃取"临时对象的内部资源。
2.3 运算符重载
vector重载了赋值运算符,使得拷贝操作更加直观:
cpp复制std::vector<int> vec7 = vec6; // 调用拷贝构造函数
vec7 = vec6; // 调用赋值运算符
需要注意的是,这些操作都是深拷贝,对于大型vector可能会有性能开销。
3. vector的遍历方式
3.1 下标访问
最基础的遍历方式是使用下标运算符[]:
cpp复制for(size_t i = 0; i < vec.size(); ++i) {
std::cout << vec[i] << " ";
}
注意:[]运算符不进行边界检查,越界访问会导致未定义行为。如果需要安全访问,可以使用at()成员函数,它在越界时会抛出std::out_of_range异常。
3.2 迭代器遍历
迭代器提供了更通用的容器访问方式:
cpp复制// 使用普通迭代器
for(std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
// 使用const迭代器(只读访问)
for(std::vector<int>::const_iterator cit = vec.cbegin(); cit != vec.cend(); ++cit) {
std::cout << *cit << " ";
}
// 使用auto简化(C++11)
for(auto it = vec.begin(); it != vec.end(); ++it) {
std::cout << *it << " ";
}
3.3 范围for循环(C++11)
范围for循环是最简洁的遍历方式:
cpp复制// 只读遍历
for(const auto& elem : vec) {
std::cout << elem << " ";
}
// 修改元素
for(auto& elem : vec) {
elem *= 2;
}
范围for循环底层也是使用迭代器实现的,但语法更加简洁直观,是现代C++推荐的方式。
4. vector容量管理
4.1 size与capacity
vector有两个重要的容量相关概念:
- size(): 当前存储的元素数量
- capacity(): 当前分配的内存能够容纳的元素数量
cpp复制std::vector<int> vec;
vec.reserve(100); // 预分配100个元素的空间
std::cout << "size: " << vec.size() << "\n"; // 输出0
std::cout << "capacity: " << vec.capacity() << "\n"; // 输出100
4.2 resize与reserve
resize()和reserve()是调整vector容量的两个重要方法:
cpp复制std::vector<int> vec;
// reserve只影响capacity,不影响size
vec.reserve(10); // capacity=10, size=0
// resize影响size,必要时也影响capacity
vec.resize(5); // size=5, capacity=10 (填充默认值0)
vec.resize(8, 1); // size=8, capacity=10 (新增元素初始化为1)
vec.resize(3); // size=3, capacity=10 (删除末尾元素)
4.3 shrink_to_fit
shrink_to_fit()可以释放多余的内存,使capacity等于size:
cpp复制std::vector<int> vec(100);
vec.resize(10);
vec.shrink_to_fit(); // capacity从100降到10
注意:shrink_to_fit()是请求而非强制,具体实现可能不会完全释放多余内存。
5. vector元素操作
5.1 添加元素
push_back()是最常用的添加元素方法:
cpp复制std::vector<int> vec;
vec.push_back(1); // {1}
vec.push_back(2); // {1,2}
vec.push_back(3); // {1,2,3}
C++11引入了emplace_back(),它可以直接在容器内构造元素,避免临时对象的创建和拷贝:
cpp复制struct Point {
Point(int x, int y) : x(x), y(y) {}
int x, y;
};
std::vector<Point> points;
points.emplace_back(1, 2); // 直接在容器内构造Point对象
5.2 插入元素
insert()允许在任意位置插入元素:
cpp复制std::vector<int> vec = {1,3,4};
vec.insert(vec.begin()+1, 2); // {1,2,3,4}
插入操作会导致插入点后的所有元素向后移动,时间复杂度为O(n)。
5.3 删除元素
pop_back()删除最后一个元素:
cpp复制std::vector<int> vec = {1,2,3};
vec.pop_back(); // {1,2}
erase()可以删除指定位置或范围的元素:
cpp复制std::vector<int> vec = {1,2,3,4,5};
vec.erase(vec.begin()+2); // 删除第三个元素 → {1,2,4,5}
vec.erase(vec.begin(), vec.begin()+2); // 删除前两个元素 → {4,5}
clear()可以清空所有元素,但不改变capacity:
cpp复制vec.clear(); // size=0, capacity不变
6. vector高级用法与性能优化
6.1 避免不必要的拷贝
vector在扩容时会重新分配内存并拷贝所有元素,对于大型对象这会带来性能问题。有几种解决方案:
- 使用移动语义(C++11):
cpp复制std::vector<std::string> vec;
std::string largeStr = "very long string...";
vec.push_back(std::move(largeStr)); // 移动而非拷贝
- 存储指针或智能指针:
cpp复制std::vector<std::unique_ptr<LargeObject>> vec;
vec.push_back(std::make_unique<LargeObject>());
6.2 高效swap
vector的swap操作非常高效,它只是交换内部指针而非元素:
cpp复制std::vector<int> vec1(1000000);
std::vector<int> vec2;
vec1.swap(vec2); // 高效交换,时间复杂度O(1)
这个特性常被用来释放vector占用的内存:
cpp复制std::vector<int> vec(1000000);
std::vector<int>().swap(vec); // 释放vec的内存
6.3 自定义分配器
vector允许指定自定义的内存分配器:
cpp复制template<typename T>
class CustomAllocator {
// 实现分配器接口
};
std::vector<int, CustomAllocator<int>> vec;
这在某些特殊场景下很有用,比如内存池、共享内存等。
7. vector常见问题与解决方案
7.1 迭代器失效问题
vector的某些操作会导致迭代器失效:
cpp复制std::vector<int> vec = {1,2,3,4};
auto it = vec.begin() + 2;
vec.push_back(5); // 可能导致迭代器it失效
常见的导致迭代器失效的操作包括:
- 插入元素(push_back, insert等)
- 删除元素(pop_back, erase等)
- 扩容操作(resize, reserve等)
解决方案:
- 在修改操作后重新获取迭代器
- 使用索引而非迭代器
- 预留足够容量避免扩容
7.2 性能陷阱
-
频繁扩容:未预分配足够容量导致频繁扩容
- 解决方案:使用reserve()预分配
-
前端插入:vector不适合频繁在头部插入
- 解决方案:考虑使用deque或list
-
大型对象存储:存储大型对象时拷贝开销大
- 解决方案:使用指针或移动语义
7.3 多线程安全
vector不是线程安全的容器。在多线程环境下需要额外的同步机制:
cpp复制std::vector<int> vec;
std::mutex mtx;
// 线程1
{
std::lock_guard<std::mutex> lock(mtx);
vec.push_back(1);
}
// 线程2
{
std::lock_guard<std::mutex> lock(mtx);
vec.push_back(2);
}
8. vector与其他容器的比较
8.1 vector vs array
| 特性 | vector | array(C风格数组) |
|---|---|---|
| 大小 | 动态可变 | 固定 |
| 内存管理 | 自动 | 手动 |
| 访问速度 | 相同(O(1)) | 相同(O(1)) |
| 功能扩展 | 丰富(迭代器,算法等) | 有限 |
8.2 vector vs list
| 特性 | vector | list |
|---|---|---|
| 内存布局 | 连续 | 非连续 |
| 随机访问 | O(1) | O(n) |
| 插入删除 | 尾部O(1),其他O(n) | 任意位置O(1) |
| 缓存友好性 | 高 | 低 |
8.3 vector vs deque
| 特性 | vector | deque |
|---|---|---|
| 内存布局 | 单块连续 | 多块连续 |
| 头部操作 | O(n) | O(1) |
| 扩容成本 | 高(全量拷贝) | 低(新增块) |
| 迭代器失效 | 更频繁 | 相对较少 |
在实际开发中,选择哪种容器取决于具体的使用场景和性能需求。vector因其简单高效,在大多数情况下都是首选。