深入理解C++中的vector容器
目录
一、vector简介
二、vector的基本使用
1. vector的构造
2. 常用接口
三、vector的底层原理
1. 扩容机制
2. 迭代器失效问题
3. 删除元素时的迭代器处理
四、vector的模拟实现
总结
一、vector简介
vector是C++标准模板库(STL)中最常用的序列式容器之一,它表示一个可以动态改变大小的数组。与普通数组相比,vector具有以下特点:
1. 动态扩容:vector的大小可以自动增长,不需要手动管理内存
2. 随机访问:支持通过下标快速访问任意元素
3. 连续存储:元素在内存中是连续存储的,类似于数组
二、vector的基本使用
1. vector的构造
vector提供了多种构造函数:
vector<int> v1; // 无参构造
vector<int> v2(10, 5); // 构造并初始化10个5
vector<int> v3(v2); // 拷贝构造
vector<int> v4(v3.begin(), v3.end()); // 迭代器范围构造
2. 常用接口
vector提供了丰富的接口来操作容器:
v.push_back(10); // 尾部插入元素
v.pop_back(); // 删除尾部元素
v.size(); // 获取元素个数
v.capacity(); // 获取容量
v.empty(); // 判断是否为空
v[0]; // 通过下标访问元素
v.at(0); // 通过at方法访问元素
v.front(); // 访问第一个元素
v.back(); // 访问最后一个元素
三、vector的底层原理
1. 扩容机制
vector的扩容是一个重要的性能考虑点。当当前容量不足以容纳新元素时,vector会进行扩容:
- VS编译器下:按1.5倍增长
- GCC编译器下:按2倍增长
vector<int> v;
for(int i = 0; i < 100; ++i) {v.push_back(i);// 观察capacity变化
}
为了避免频繁扩容带来的性能损耗,可以使用`reserve`预分配空间:
vector<int> v;
v.reserve(100); // 预先分配100个元素的空间
2. 迭代器失效问题
vector的迭代器失效是一个常见陷阱,主要发生在以下情况:
1. 扩容操作:`resize`、`reserve`、`push_back`等可能导致扩容的操作
2. 插入操作:`insert`可能导致扩容
3. 删除操作:`erase`会使得被删除元素及其后的迭代器失效
错误示例:
vector<int> v = {1,2,3,4,5};
auto it = v.begin();
v.push_back(6); // 可能导致扩容
cout << *it; // 危险!it可能已经失效
正确做法:在可能导致迭代器失效的操作后,重新获取迭代器
vector<int> v = {1,2,3,4,5};
auto it = v.begin();
v.push_back(6);
it = v.begin(); // 重新获取迭代器
cout << *it;
3. 删除元素时的迭代器处理
删除元素时需要特别注意迭代器的处理:
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;}
}
四、vector的模拟实现
1. 核心框架
一个简单的vector模拟实现需要包含以下成员:
template<class T>
class vector {
private:T* _start; // 指向数据起始位置T* _finish; // 指向最后一个元素的下一个位置T* _endofstorage; // 指向存储容量的末尾
public:// 各种成员函数...
};
2. 深拷贝问题
在实现vector时,特别是`reserve`和`resize`等函数时,不能简单地使用`memcpy`进行拷贝,因为这会导致浅拷贝问题:
// 错误的实现方式
void reserve(size_t n) {T* tmp = new T[n];memcpy(tmp, _start, sizeof(T)*size());delete[] _start;_start = tmp;// ...其他成员调整
}
对于包含资源管理的自定义类型(如string),上述实现会导致内存泄漏或程序崩溃。正确的做法是使用拷贝构造:
// 正确的实现方式
void reserve(size_t n) {T* tmp = new T[n];for(size_t i = 0; i < size(); ++i) {tmp[i] = _start[i]; // 调用拷贝赋值}delete[] _start;_start = tmp;// ...其他成员调整
}
五、vector的应用实例
1. 杨辉三角
vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);for(int i = 0; i < numRows; ++i) {vv[i].resize(i+1, 1);}for(int i = 2; i < numRows; ++i) {for(int j = 1; j < i; ++j) {vv[i][j] = vv[i-1][j] + vv[i-1][j-1];}}return vv;
}
2. 只出现一次的数字
int singleNumber(vector<int>& nums) {int value = 0;for(auto e : nums) {value ^= e;}return value;
}
总结
vector是C++中最常用的容器之一,掌握它的使用和底层原理对于编写高效、安全的代码至关重要。关键点包括:
1. 理解vector的动态扩容机制
2. 注意迭代器失效问题
3. 合理使用reserve预分配空间优化性能
4. 在模拟实现时注意深拷贝问题
通过深入理解vector的工作原理,我们能够更好地利用这一强大工具,编写出更加高效的C++代码。