🔥铅笔小新z:个人主页
🎬博客专栏:C++学习
💫滴水不绝,可穿石;步履不休,能至渊。

引言
在C++标准模板库(STL)中,vector是最重要、最常用的容器之一。它提供了动态数组的功能,能够自动管理内存,同时保持了数组的随机访问特性。本文将深入探讨vector的各个方面,从基本使用到高级特性,再到底层实现原理。
1. vector的基本介绍
1.1 vector是什么?
vector是一个序列容器,表示可以改变大小的数组。与普通数组不同,vector能够动态增长和收缩,自动处理内存管理。它支持快速随机访问,在尾部插入和删除元素效率高,但在中间或头部插入删除效率相对较低。
1.2 学习vector的三个境界
- 能用:掌握基本接口,能够在实际编程中使用vector
- 明理:理解vector的工作原理和内部机制
- 能扩展:能够根据需要自定义或扩展vector的功能
1.3 vector 相关接口
2. vector的基本使用
2.1 vector的构造函数
cpp
// 1. 默认构造函数 - 创建空vector
vector<int> v1;
// 2. 构造并初始化n个val
vector<int> v2(5, 10); // 5个元素,每个都是10
// 3. 拷贝构造
vector<int> v3(v2);
// 4. 使用迭代器范围构造
int arr[] = {1, 2, 3, 4, 5};
vector<int> v4(arr, arr + 5);
2.1.1 默认构造函数
cpp
vector()
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
2.1.2 构造并初始化n个val
cpp
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
2.1.3 拷贝构造
cpp
vector(const vector<T>& v)
{
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}
2.2 vector的迭代器
迭代器提供了访问容器元素的统一方式:
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 正向迭代器
for(auto it = v.begin(); it != v.end(); ++it)
{
cout << *it << " ";
}
// 反向迭代器
for(auto rit = v.rbegin(); rit != v.rend(); ++rit)
{
cout << *rit << " ";
}
// 范围for循环(C++11)
for(auto& elem : v)
{
cout << elem << " ";
}
2.3 vector的空间管理
vector有两个重要的容量概念:
- size: 当前容器中实际元素的数量
- capacity: 容器在不重新分配内存的情况下可以容纳的元素数量
cpp
vector<int> v;
cout << "size: " << v.size() << endl; // 0
cout << "capacity: " << v.capacity() << endl; // 0
cout << "empty: " << v.empty() << endl; // true
v.reserve(100); // 预分配100个元素的空间
cout << "capacity after reserve: " << v.capacity() << endl; // 100
v.resize(50); // 改变size为50,多出的位置用0填充
cout << "size after resize: " << v.size() << endl; // 50
2.4 vector的容量增长策略
vector的容量增长策略在不同实现中有所不同:
cpp
void TestVectorExpand()
{
vector<int> v;
size_t sz = v.capacity();
cout << "capacity growth:\n";
for(int i = 0; i < 100; ++i)
{
v.push_back(i);
if(sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed: " << sz << endl;
}
}
}
不同编译器的实现差异:
- VS系列编译器:按大约1.5倍增长
- g++编译器:按2倍增长
这种差异是因为不同版本的STL实现不同,不要固化认为vector总是2倍增长。
2.5 vector的增删查改操作
cpp
vector<int> v = {1, 2, 3};
// 1. 添加元素
v.push_back(4); // 尾部插入
v.insert(v.begin() + 1, 5); // 在指定位置插入
// 2. 删除元素
v.pop_back(); // 尾部删除
v.erase(v.begin()); // 删除指定位置元素
v.clear(); // 清空所有元素
// 3. 访问元素
cout << v[0] << endl; // 使用operator[],不检查边界
cout << v.at(0) << endl; // 使用at(),会检查边界
// 4. 查找元素(需要包含<algorithm>)
auto it = find(v.begin(), v.end(), 3);
if(it != v.end())
{
cout << "Found: " << *it << endl;
}
// 5. 交换两个vector
vector<int> v2 = {10, 20, 30};
v.swap(v2); // 交换v和v2的内容
3. vector迭代器失效问题(重点)
迭代器失效是使用vector时需要特别注意的问题。迭代器失效意味着迭代器指向的内存空间已经无效,继续使用会导致未定义行为(通常程序崩溃)。
3.1 vector的底层存储结构
vector在内存中是一段连续的存储空间:
cpp
template<class T>
class vector {
private:
T* _start; // 指向数据起始位置
T* _finish; // 指向最后一个元素的下一个位置
T* _end_of_storage; // 指向存储空间末尾
// ...
};
当你获取一个迭代器时,它本质上是一个指针(或指针的封装):
cpp
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin(); // it本质上是一个 int*,指向第一个元素
内存布局如下:
地址:0x1000 0x1004 0x1008 0x100C 0x1010 0x1014...
数据: 1 2 3 4 5 ...
↑
it (指向0x1000)
3.2 导致迭代器失效的操作
情况1:空间重新分配
cpp
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
// 以下操作都可能导致迭代器失效
v.resize(100); // 扩容
v.reserve(100); // 扩容
v.push_back(6); // 可能导致扩容
v.insert(v.begin(), 0); // 可能导致扩容
v.assign(100, 8); // 重新赋值,可能改变容量
// 错误:it可能已经失效
while(it != v.end())
{ // 可能访问已释放的内存
cout << *it << " ";
++it;
}
扩容过程:
- 在堆上申请新的、更大的连续内存空间
- 将旧内存中的数据拷贝到新内存
- 释放旧内存空间
- 更新vector的内部指针到新内存
关键问题: 迭代器it仍然指向旧的、已被释放的内存地址:
旧内存(已被释放):0x1000 0x1004 0x1008...
↑
it (仍然指向这里,但内存已无效)
新内存(当前有效):0x2000 0x2004 0x2008...
1 2 3 ...
解决方案:在可能导致扩容的操作后,重新获取迭代器。
情况2:元素删除
cpp
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin() + 2; // it指向元素3
v.erase(v.begin() + 1); // 删除元素2
删除过程:
删除前: [1, 2, 3, 4, 5]
↑
it指向3
删除元素2:
1. 删除位置1的元素: [1, _, 3, 4, 5]
2. 向前移动元素3-5: [1, 3, 4, 5]
删除后: [1, 3, 4, 5]
↑
it指向4(不是原来的3了)
解决方案:使用erase的返回值更新迭代器。
cpp
// 正确删除vector中所有偶数的示例
vector<int> v = {1, 2, 3, 4};
auto it = v.begin();
while(it != v.end())
{
if(*it % 2 == 0)
{
it = v.erase(it); // erase返回下一个有效迭代器
} else
{
++it;
}
}
情况3:插入元素导致内存移动
即使不扩容,插入操作也可能导致迭代器失效:
cpp
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin() + 2; // it指向元素3
v.insert(v.begin() + 1, 99); // 在位置1插入99
插入操作需要移动元素:
插入前: [1, 2, 3, 4, 5]
↑
it指向3
插入过程:
1. 向后移动元素2-5: [1, _, 2, 3, 4, 5]
2. 在位置1插入99: [1, 99, 2, 3, 4, 5]
插入后: [1, 99, 2, 3, 4, 5]
↑
it仍然指向原来的内存位置,但现在是元素2
虽然it指向的内存地址仍然有效,但元素已经改变了位置,原来的迭代器不再指向期望的元素。
情况4:编译器的差异处理
不同编译器对迭代器失效的处理严格程度不同:
- VS编译器:检测严格,失效后立即报错
- g++编译器:检测较宽松,可能继续运行但结果错误
cpp
// 在g++下可能不崩溃,但结果错误
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
v.reserve(100); // 扩容,迭代器失效
// 可能不崩溃,但输出错误
while(it != v.end())
{
cout << *it << " "; // 未定义行为
++it;
}
3.3 string的迭代器失效问题
与vector类似,string也有迭代器失效问题:
cpp
string s = "hello";
auto it = s.begin();
s.resize(20, '!'); // 可能导致扩容,迭代器失效
// 错误:使用可能已失效的迭代器
while(it != s.end())
{
cout << *it; // 可能崩溃
++it;
}
3.4 具体操作分析
(1) resize(n, val) 可能扩容
cpp
vector<int> v = {1, 2, 3, 4, 5}; // 容量5
auto it = v.begin();
v.resize(100, 0); // 需要扩容到至少100
// 旧内存不够 → 申请新内存 → 拷贝数据 → 释放旧内存
// it失效!
(2) reserve(n) 可能扩容
cpp
vector<int> v = {1, 2, 3, 4, 5}; // 容量5
auto it = v.begin();
v.reserve(100); // 容量从5扩大到100
// 必须重新分配内存 → it失效!
(3) push_back(val) 可能触发扩容
cpp
vector<int> v = {1, 2, 3, 4, 5}; // 容量5,已满
auto it = v.begin();
v.push_back(6); // 需要扩容
// 容量不足 → 重新分配内存 → it失效!
(4) insert(pos, val) 可能触发扩容或移动
cpp
vector<int> v = {1, 2, 3, 4, 5}; // 容量5
auto it = v.begin() + 2;
v.insert(v.begin(), 0); // 在开头插入
// 两种情况:
// 1. 如果容量不足:扩容 → it完全失效(指向释放的内存)
// 2. 如果容量足够:元素向后移动 → it指向的元素改变
(5) assign(n, val) 完全重新分配
cpp
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
v.assign(100, 8); // 替换所有内容
// 清空现有元素 → 可能需要重新分配内存 → it失效!
3.5 为什么访问失效迭代器会导致问题?
对于已释放的内存(情况1)
cpp
vector<int> v = {1, 2, 3};
auto it = v.begin();
v.reserve(100); // 重新分配内存,释放旧内存
cout << *it; // 访问已释放的内存!
可能的后果:
- 程序崩溃:访问无效内存地址(段错误)
- 读取垃圾值:内存已被其他数据覆盖
- 未定义行为:任何事情都可能发生
对于元素位置改变(情况2和3)
cpp
vector<int> v = {1, 2, 3};
auto it = v.begin() + 1; // 指向2
v.insert(v.begin(), 0); // 插入元素
cout << *it; // 输出2?不,可能是其他值!
结果是逻辑错误:迭代器不指向期望的元素。
3.6 正确的做法
方案1:重新获取迭代器
cpp
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
// 执行可能使迭代器失效的操作
v.resize(100);
// 重新获取迭代器
it = v.begin(); // 重要:重新赋值
// 现在可以安全使用
while(it != v.end())
{
cout << *it << " ";
++it;
}
方案2:使用索引替代迭代器
cpp
vector<int> v = {1, 2, 3, 4, 5};
size_t index = 0; // 使用索引而不是迭代器
v.resize(100); // 扩容
// 索引不受影响(只要不超过size)
if(index < v.size())
{
cout << v[index]; // 安全
}
方案3:使用返回值更新迭代器
cpp
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
// insert返回新插入元素的位置
it = v.insert(it, 0); // 在开头插入0,it更新为指向新元素
// erase返回被删除元素的下一个位置
it = v.erase(it); // 删除it指向的元素,it更新为下一个元素
3.7 总结:迭代器失效的核心原因
| 操作 | 失效原因 | 影响范围 |
|---|---|---|
| resize/reserve | 内存重新分配 | 所有迭代器、指针、引用失效 |
| push_back | 可能触发内存重新分配 | 如果扩容,所有迭代器失效 |
| insert | 1. 可能扩容 2. 元素移动 | 1. 所有迭代器失效 2. 部分迭代器指向错误元素 |
| erase | 元素向前移动 | 被删除及之后位置的迭代器失效 |
| assign/clear | 完全重新分配或清空 | 所有迭代器失效 |
根本原因:vector保证元素在内存中连续存储。为了维持这种连续性,当需要更多空间或插入删除元素时,必须移动元素或重新分配内存,这会导致原有的地址引用失效。
重要原则:在修改vector容量的操作后,永远假设所有现有的迭代器、指针和引用都已失效,除非操作文档明确说明它们保持有效。
4. vector在算法题中的应用
4.1 只出现一次的数字
cpp
class Solution
{
public:
int singleNumber(vector<int>& nums)
{
int result = 0;
for(int num : nums)
{
result ^= num; // 利用异或性质
}
return result;
}
};
4.2 杨辉三角
cpp
class Solution
{
public:
vector<vector<int>> generate(int numRows)
{
vector<vector<int>> triangle(numRows);
for(int i = 0; i < numRows; ++i)
{
triangle[i].resize(i + 1, 1); // 每行有i+1个元素,初始化为1
// 计算中间元素
for(int j = 1; j < i; ++j)
{
triangle[i][j] = triangle[i-1][j-1] + triangle[i-1][j];
}
}
return triangle;
}
};
4.3 删除排序数组中的重复项
cpp
class Solution
{
public:
int removeDuplicates(vector<int>& nums)
{
if(nums.empty()) return 0;
int k = 0;
for(int i = 1; i < nums.size(); ++i)
{
if(nums[i] != nums[k])
{
nums[++k] = nums[i];
}
}
return k + 1;
}
};
5. vector的模拟实现
5.1 vector的基本框架
cpp
namespace my
{
template<class T>
class vector
{
private:
T* _start; // 指向数据起始位置
T* _finish; // 指向最后一个元素的下一个位置
T* _end_of_storage; // 指向存储空间末尾
public:
// 构造函数
vector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}
// 析构函数
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
// 容量相关
size_t size() const { return _finish - _start; }
size_t capacity() const { return _end_of_storage - _start; }
bool empty() const { return _start == _finish; }
// 元素访问
T& operator[](size_t pos) { return _start[pos]; }
const T& operator[](size_t pos) const { return _start[pos]; }
// 迭代器
T* begin() { return _start; }
T* end() { return _finish; }
const T* begin() const { return _start; }
const T* end() const { return _finish; }
// 预留空间
void reserve(size_t n)
{
if(n > capacity())
{
T* new_start = new T[n];
size_t sz = size();
// 深拷贝元素
for(size_t i = 0; i < sz; ++i)
{
new_start[i] = _start[i];
}
delete[] _start;
_start = new_start;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
// 添加元素
void push_back(const T& val)
{
if(_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = val;
++_finish;
}
// 交换数据
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
// 赋值运算符重载
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
// 插入数据
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
// 扩容
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
// 删除数据
iteraror erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it != end())
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
};
}
5.2 使用memcpy的问题
在实现vector的扩容时,不能简单地使用memcpy进行内存拷贝:
cpp
// 错误示例:使用memcpy拷贝自定义类型
void reserve(size_t n)
{
if(n > capacity())
{
T* new_start = new T[n];
// 错误:对于管理资源的自定义类型,memcpy是浅拷贝
memcpy(new_start, _start, size() * sizeof(T));
delete[] _start; // 释放旧空间,可能导致资源重复释放
_start = new_start;
// ... 其他更新
}
}
问题分析:
memcpy是二进制内存拷贝,执行的是浅拷贝- 对于管理资源的自定义类型(如
string),浅拷贝会导致多个对象共享同一资源 - 释放旧空间时,资源被释放,新空间中的对象指向已释放的资源
正确做法:使用深拷贝
cpp
// 正确做法:使用深拷贝
void reserve(size_t n)
{
if(n > capacity())
{
T* new_start = new T[n];
size_t sz = size();
// 深拷贝:调用元素的拷贝构造函数或赋值运算符
for(size_t i = 0; i < sz; ++i)
{
new_start[i] = _start[i]; // 调用T的赋值运算符或拷贝构造函数
}
// 析构旧元素
for(size_t i = 0; i < sz; ++i)
{
_start[i].~T(); // 显式调用析构函数
}
delete[] _start;
_start = new_start;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
6. 动态二维数组的实现
vector可以方便地实现动态二维数组:
cpp
// 创建n行的二维数组
vector<vector<int>> createMatrix(int n)
{
vector<vector<int>> matrix(n);
for(int i = 0; i < n; ++i)
{
matrix[i].resize(i + 1, 1); // 第i行有i+1个元素
}
return matrix;
}
// 访问二维数组
void printMatrix(const vector<vector<int>>& matrix)
{
for(int i = 0; i < matrix.size(); ++i)
{
for(int j = 0; j < matrix[i].size(); ++j)
{
cout << matrix[i][j] << " ";
}
cout << endl;
}
}
7. 使用vector的最佳实践
-
预分配空间 :如果知道大概需要多少元素,使用
reserve()预分配空间,避免频繁扩容。 -
谨慎使用迭代器:在可能修改容量的操作后,不要使用旧的迭代器。
-
选择合适的访问方式:
- 随机访问:使用
operator[]或at() - 遍历:使用范围for循环或迭代器
- 性能敏感:考虑使用指针访问
- 随机访问:使用
-
注意元素的拷贝成本:存储大对象时,考虑存储指针或使用移动语义。
-
善用swap :使用
swap()快速清空vector或交换两个vector的内容。
cpp
// 快速清空vector(释放内存)
vector<int> v(1000000);
vector<int>().swap(v); // 清空v并释放内存
// C++11更简洁的方式
v.clear();
v.shrink_to_fit(); // 请求释放未使用的内存
总结
vector是C++中最重要、最常用的容器之一。掌握vector不仅需要了解其基本用法,还需要深入理解其内部工作原理,特别是迭代器失效、内存管理和性能特征。通过合理使用vector,可以编写出高效、安全的C++代码。
在实际开发中,应根据具体需求选择合适的容器。vector适合需要频繁随机访问、尾部插入删除的场景。如果需要频繁在中间插入删除,可能需要考虑list或deque;如果需要快速查找,可能需要考虑set或map。
通过深入理解vector,我们不仅能够更好地使用这个容器,还能够学习到C++内存管理、模板编程、异常安全等高级主题,为成为更优秀的C++程序员打下坚实基础。
希望这篇长文对你有帮助!如果觉得不错,欢迎点赞、收藏、分享~

作者:铅笔小新z
日期:2025 年 12 月 16 日
目标:一篇讲透 C++ vector,从使用到底层实现。