在C++标准模板库(STL)中,vector是最常用的动态数组容器之一。它提供了动态扩容的能力,同时保持了数组随机访问的高效性。与普通数组相比,vector能够自动管理内存,开发者无需手动分配和释放内存空间。
vector底层实现本质上是一个动态分配的数组,通过三个指针来管理:
这种设计使得vector具有以下核心特性:
在实际开发中,vector特别适合以下场景:
注意:虽然vector提供了动态扩容的能力,但频繁的扩容操作会导致性能下降。在已知元素数量的情况下,建议提前使用reserve()预留足够空间。
vector提供了多种构造函数以适应不同的初始化需求:
cpp复制// 默认构造 - 创建空vector
vector<int> v1;
// 填充构造 - 创建包含10个值为1的元素
vector<int> v2(10, 1);
// 范围构造 - 用v2的部分元素初始化v3
vector<int> v3(v2.begin()+1, v2.end()-1);
// 拷贝构造 - 创建v2的副本
vector<int> v4(v2);
初始化时常见问题及解决方案:
vector<int> v(arr, arr+arr_size)vector支持多种迭代器类型:
迭代器使用示例:
cpp复制vector<int> vec = {1, 2, 3, 4, 5};
// 正向遍历
for(auto it = vec.begin(); it != vec.end(); ++it) {
cout << *it << " ";
}
// 反向遍历
for(auto rit = vec.rbegin(); rit != vec.rend(); ++rit) {
cout << *rit << " ";
}
// 范围for循环(底层也是迭代器)
for(int num : vec) {
cout << num << " ";
}
迭代器失效的典型场景:
经验法则:任何可能改变vector容量的操作后,都应重新获取迭代器,不要保存旧的迭代器。
vector提供了一系列容量管理接口:
cpp复制vector<int> v;
// 获取当前元素数量
size_t size = v.size();
// 获取当前分配的内存容量
size_t cap = v.capacity();
// 检查是否为空
bool isEmpty = v.empty();
// 调整元素数量
v.resize(10); // 扩容到10,新增元素默认初始化
v.resize(15, 1); // 扩容到15,新增元素初始化为1
v.resize(5); // 缩容到5
// 预留内存空间
v.reserve(100); // 预分配100个元素的空间
容量增长策略在不同平台有差异:
实际开发建议:
vector提供多种元素访问方式,各有适用场景:
| 访问方式 | 示例 | 特点 | 安全性检查 |
|---|---|---|---|
| operator[] | vec[5] | 最快,无检查 | 无 |
| at() | vec.at(5) | 边界检查,越界抛异常 | 有 |
| front()/back() | vec.front() | 访问首/末元素 | 无 |
| data() | int* p = vec.data() | 获取底层数组指针 | 无 |
性能测试表明,operator[]比at()快约30%,在确保索引安全的情况下应优先使用。
vector的插入删除操作有多个变体:
cpp复制vector<int> vec = {1, 2, 3};
// 尾部插入
vec.push_back(4); // 拷贝插入
vec.emplace_back(5); // 原地构造,效率更高
// 中间插入
auto it = vec.begin() + 1;
vec.insert(it, 10); // 在位置1插入10
vec.emplace(it, 20); // 在位置1原地构造20
// 删除操作
vec.pop_back(); // 删除末尾元素
vec.erase(vec.begin()); // 删除首元素
vec.erase(vec.begin(), vec.begin()+2); // 删除范围元素
性能优化建议:
vector内存管理的核心是减少不必要的重新分配:
cpp复制// 不好的做法 - 可能导致多次重新分配
vector<int> vec;
for(int i=0; i<1000000; ++i) {
vec.push_back(i);
}
// 好的做法 - 预先分配足够空间
vector<int> vec;
vec.reserve(1000000);
for(int i=0; i<1000000; ++i) {
vec.push_back(i);
}
高级内存技巧:
cpp复制vector<int>().swap(vec); // 清空并释放所有内存
cpp复制vec.shrink_to_fit(); // 请求释放未使用的容量
cpp复制vector<string> vec1 = getLargeVector();
vector<string> vec2 = std::move(vec1); // 移动而非拷贝
简化版vector类的基本框架:
cpp复制template<typename T>
class Vector {
public:
// 迭代器类型定义
typedef T* iterator;
typedef const T* const_iterator;
// 构造函数等接口...
private:
iterator _start; // 指向数据块开始
iterator _finish; // 指向最后一个元素的下一个位置
iterator _end_of_storage; // 指向存储空间的末尾
};
迭代器本质就是原生指针,这使得vector迭代器具有最高效率。begin()返回_start,end()返回_finish。
push_back的核心逻辑:
cpp复制void push_back(const T& value) {
if(_finish == _end_of_storage) { // 检查是否需要扩容
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = value; // 在末尾插入元素
++_finish; // 调整_finish指针
}
insert操作的实现要点:
cpp复制iterator insert(iterator pos, const T& value) {
assert(pos >= _start && pos <= _finish);
if(_finish == _end_of_storage) { // 可能扩容
size_t offset = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + offset; // 扩容后重新计算pos位置
}
// 向后移动元素
for(iterator it = _finish; it != pos; --it) {
*it = *(it-1);
}
*pos = value; // 插入新元素
++_finish;
return pos;
}
vector实现需要考虑异常安全:
移动语义优化示例:
cpp复制// 移动构造函数
Vector(Vector&& other) noexcept
: _start(other._start),
_finish(other._finish),
_end_of_storage(other._end_of_storage) {
other._start = other._finish = other._end_of_storage = nullptr;
}
迭代器失效的两种主要情形:
空间重新分配导致的失效:
cpp复制vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4); // 可能导致扩容
cout << *it; // 危险!it可能已失效
元素删除导致的失效:
cpp复制vector<int> v = {1, 2, 3, 4};
auto it = v.begin() + 2;
v.erase(v.begin()); // 删除会影响后续元素位置
cout << *it; // 危险!it可能指向错误元素
解决方案:
cpp复制for(auto it = v.begin(); it != v.end(); ) {
if(condition(*it)) {
it = v.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
案例:处理百万级数据时的优化
cpp复制// 未优化版本
vector<Data> processData(const vector<Input>& input) {
vector<Data> result;
for(const auto& item : input) {
result.push_back(transform(item));
}
return result;
}
// 优化版本
vector<Data> processDataOptimized(const vector<Input>& input) {
vector<Data> result;
result.reserve(input.size()); // 关键优化1:预分配空间
for(const auto& item : input) {
result.emplace_back(transform(item)); // 关键优化2:使用emplace_back
}
// 关键优化3:如果允许修改输入,可考虑移动语义
return result; // 关键优化4:NRVO或移动语义避免拷贝
}
优化前后性能对比:
二维vector的几种初始化方式:
cpp复制// 方式1:逐个初始化
vector<vector<int>> matrix1;
for(int i=0; i<rows; ++i) {
matrix1.push_back(vector<int>(cols, 0));
}
// 方式2:一次性初始化
vector<vector<int>> matrix2(rows, vector<int>(cols, 0));
// 方式3:使用resize
vector<vector<int>> matrix3;
matrix3.resize(rows);
for(auto& row : matrix3) {
row.resize(cols, 0);
}
性能关键点:
cpp复制vector<int> matrix(rows * cols); // 连续内存
// 访问元素:matrix[row * cols + col]
cpp复制vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> num_map;
for(int i = 0; i < nums.size(); ++i) {
int complement = target - nums[i];
if(num_map.count(complement)) {
return {num_map[complement], i};
}
num_map[nums[i]] = i;
}
return {};
}
cpp复制void rotate(vector<int>& nums, int k) {
k %= nums.size();
reverse(nums.begin(), nums.end());
reverse(nums.begin(), nums.begin()+k);
reverse(nums.begin()+k, nums.end());
}
大规模数据读取优化:
cpp复制// 常规做法 - 较慢
int n;
cin >> n;
vector<int> v(n);
for(int i=0; i<n; ++i) {
cin >> v[i];
}
// 优化做法1 - 关闭同步
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 优化做法2 - 批量读取
char buffer[1<<20];
cin.read(buffer, sizeof(buffer));
char* p = buffer;
int n = strtol(p, &p, 10);
vector<int> v(n);
for(int i=0; i<n; ++i) {
v[i] = strtol(p, &p, 10);
}
使用vector
压缩存储:
cpp复制// 存储100万个0-255的值
vector<unsigned char> compressed(1000000); // 只用1MB
延迟分配:
cpp复制vector<unique_ptr<LargeObject>> v;
v.reserve(1000); // 只分配指针空间
for(int i=0; i<1000; ++i) {
v.emplace_back(make_unique<LargeObject>());
}
| 容器 | 随机访问 | 头部插入 | 尾部插入 | 中间插入 | 内存分配 |
|---|---|---|---|---|---|
| vector | O(1) | O(n) | O(1) | O(n) | 偶尔大块 |
| deque | O(1) | O(1) | O(1) | O(n) | 多块 |
| list | O(n) | O(1) | O(1) | O(1) | 频繁小块 |
选择vector的情况:
选择其他容器的情况:
实际开发中常组合使用多种容器:
cpp复制// 案例:统计词频并输出TopN
vector<pair<string, int>> getTopWords(const vector<string>& docs, int topN) {
unordered_map<string, int> word_counts;
// 统计词频
for(const auto& doc : docs) {
istringstream iss(doc);
string word;
while(iss >> word) {
++word_counts[word];
}
}
// 转移到vector排序
vector<pair<string, int>> sorted_words(word_counts.begin(), word_counts.end());
sort(sorted_words.begin(), sorted_words.end(),
[](const auto& a, const auto& b) { return a.second > b.second; });
// 返回TopN
if(sorted_words.size() > topN) {
sorted_words.resize(topN);
}
return sorted_words;
}
这种组合利用了unordered_map的快速查找和vector的快速排序特性。