一、vector 的简介
在 C++ 中,std::vector 是标准模板库(STL)提供的一种动态数组容器,它能够自动管理内存,支持高效地随机访问、动态扩容和元素增删。换句话说,vector 其实就相当于数据结构中使用数组实现的动态顺序表。
在 STL 中的各个容器的设计都是非常相似的,比如:都具有构造、析构、增删查改等基本操作等。
Vector 是 C++ STL 中的动态数组容器,支持自动内存管理、随机访问及增删操作。本文详解其类型定义、构造函数、迭代器遍历、空间管理(size/capacity)及增删查改接口。重点剖析了 vector 的底层模拟实现,包括三个指针成员变量、扩容机制、深拷贝与浅拷贝区别(memcpy 风险)、以及迭代器失效场景(容量变化与元素删除)。通过代码示例展示了常见用法及注意事项,适合学习 C++ 数据结构与算法基础。

在 C++ 中,std::vector 是标准模板库(STL)提供的一种动态数组容器,它能够自动管理内存,支持高效地随机访问、动态扩容和元素增删。换句话说,vector 其实就相当于数据结构中使用数组实现的动态顺序表。
在 STL 中的各个容器的设计都是非常相似的,比如:都具有构造、析构、增删查改等基本操作等。
如果要简单理解的话,也可以这样说:C++ 其实是将数据结构给封装起来的而已。但这种说法并不全面,在 C++ 中其实对于容器还有更多的高级特性,比如:内存管理、算法适配、接口标准化等。
因为 vector 就像一个动态的顺序表,所以它的内容是可以存储任何数据的,比如:int,char,double 等内置类型;还可以存储自定义类型,比如:struct 定义的类型,class 定义的类型(比如 string 类)等等。所以我们来看它的实现都是使用模板来实现的。
其中的 class T 就是一个模板,而 class Alloc = allocator 是一个空间配置器,内存池,在现阶段我们可以不用考虑这个。就只需要知道它是通过模板实现的就行了。
所以我们使用 vector 时就必须要指明类型:v1 的类型是 std::vector,即一个存储 int 的动态数组。v2 的类型是 std::vector,即一个存储 char 的动态数组。v3 的类型是 std::vector,即一个存储 string 的动态数组。
这时我们既然知道了 vector 是通过模板实现的,那么它是不是可以代替任何类型呢?其实这是不行的。比如它是不可以完全代替 string 类的。其原因有两个:string 要求最后有\0,可以更好兼容 C 语言接口;string 有很多他的专用接口函数。
所以我们对于不同类型的数据使用 vector 时都必须要注意 vector 本身的使用。
在认识 vector 时我们肯定会去查阅文档的,如果我们直接去看 vector 的各个构造函数可能是看不懂的,比如有许多看不懂的类型,这其实是因为 vector 类中通过 typedef 封装了其他类型。
其中的 value_type 就是第一个模板参数(T),allocator_type 就是第二个模板参数 (Alloc)。这些类型定义会在 vector 的许多成员函数中使用到。
下图是 vector 的几个构造函数声明:
其中我们当下阶段可以不用考虑:const allocator_type& alloc = allocator_type(),后面会详细讲解。当然我们也可以了解一下它的意思:调用构造函数时没有显式提供 alloc 参数,则使用默认构的 allocator_type 对象。
所以我们可以将这里的构造函数重新总结一下:
| 序号 | 函数签名 | 说明 |
|---|---|---|
| 1 | vector() | 无参构造 |
| 2 | vector (size_type n, const value_type& val = value_type()) | 构造并用 n 个 val 初始化 |
| 3 | template vector (InputIterator first, InputIterator last) | 使用迭代器进行初始化构造 |
| 4 | vector (const vector& x) | 拷贝构造 |
使用实例:
#include<vector>
using namespace std;
void test1() {
vector<int> v1; // 创建一个空的 vector
vector<int> v2(4, 100); // 创建一个包含 4 个整型元素的 vector,每个元素都是 100
vector<int> v3(v2.begin(), v2.end()); // 使用 v2 的迭代器范围 [first, last) 内的元素初始化 v3
vector<int> v4(v3); // 使用 v3 拷贝构造一个 v4
}
这里我们可以补充一下:当然对象会在出函数作用域时自动调用析构函数的。
迭代器有两种:一种是正向的迭代器,一种是反向的迭代器。正向的迭代器通过迭代器移动可以从前往后遍历 vector 对象。反向的迭代器通过迭代器移动可以从后往前遍历 vector 对象。
在 vector 中,迭代器其实就是指针。
| 方向 | 函数 | 说明 |
|---|---|---|
| 正向 | begin() | 获取第一个数据位置。它有两个函数:iterator begin(); 可读可写;const_iterator begin() const; 只能读 |
| 正向 | end() | 获取最后一个数据的下一个位置。它有两个函数:iterator end(); 可读可写;const_iterator end() const; 只能读 |
| 反向 | rbegin() | 获取最后一个数据位置。它有两个函数:reverse_iterator rbegin(); 可读可写;const_reverse_iterator rbegin() const; 只能读 |
| 反向 | rend() | 获取第一个数据前一个位置。它有两个函数:reverse_iterator rend(); 可读可写;const_reverse_iterator rend() const; 只能读 |
所以迭代器都是前开后闭的区间。即:加入 v 是 vector 对象,则 [v.begin(), v.end()) 或 [v.rbegin(), v.rend())
使用示例:
#include<iostream>
#include<vector>
using namespace std;
void test2() {
vector<int> v1; // push_back 是逐步向 v1 中尾插入数据的函数
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
// 正向输出
vector<int>::iterator it = v1.begin();
while (it != v1.end()) {
cout << *it << ' ';
it++;
}
cout << endl;
// 反向输出
vector<int>::reverse_iterator rit = v1.rbegin();
while (rit != v1.rend()) {
cout << *rit << ' ';
rit++;
}
cout << endl;
}
所以我们 vector 的遍历方法也就有三种,如下所示:
#include<iostream>
#include<vector>
using namespace std;
void test3() {
vector<int> v1; // push_back 是逐步向 v1 中尾插入数据的函数
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
// 遍历方法 1:for 循环
for (int i = 0; i < v1.size(); i++) {
cout << v1[i] << ' ';
}
cout << endl;
// 遍历方法 2:迭代器输出
vector<int>::iterator it = v1.begin(); //auto it = v1.begin(); //常常是这样写的,因为比较简单
while (it != v1.end()) {
cout << *it << ' ';
it++;
}
cout << endl;
// 遍历方法 3:范围 for
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
}
vector 中有几个与空间相关的比较常用的函数:
| 函数 | 说明 |
|---|---|
| size_type size() const; | 获取数据个数 |
| size_type capacity() const; | 获取容量大小 |
| bool empty() const; | 判断是否为空 |
| void resize(size_type n, value_type val = value_type()); | 改变 vector 的 size |
| void reserve(size_type n); | 改变 vector 的 capacity |
前面的 size、capacity、empty 都比较简单,只是获取数据即可。后面的 resize、reserve 需要重点了解。 需要注意的有以下几点:
vector 的扩容策略因编译器 STL 实现而异,如 VS 中 capacity 通常按 1.5 倍增长,而 G++ 则按 2 倍增长,故不应固化扩容倍数认知。reserve(n) 可预先分配至少容纳 n 个元素的内存,可以避免频繁扩容带来的性能损耗,适用于我们预先知道元素数量的场景。resize(n) 则会调整 vector 的 size 为 n,新增元素会被初始化,多余元素会被销毁,其操作比 reserve 更复杂。
使用示例:
void test4() {
vector<int> v1;
cout << "empty:" << v1.empty() << endl;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
cout << "empty:" << v1.empty() << endl;
cout << "size:" << v1.size() << endl;
cout << "capacity:" << v1.capacity() << endl;
v1.resize(20);
cout << "size:" << v1.size() << endl;
cout << "capacity:" << v1.capacity() << endl;
v1.reserve(100);
cout << "size:" << v1.size() << endl;
cout << "capacity:" << v1.capacity() << endl;
}
关于增删改查的常用函数有以下几种:
| 操作 | 函数 | 说明 |
|---|---|---|
| 增 | push_back | 尾插 |
| 增 | insert | 在 position 之前插入 val |
| 删 | pop_back | 尾删 |
| 删 | erase | 删除 position 位置的数据 |
| 改 | operator[ ] | 像数组一样访问 |
| 改 | swap | 交换两个 vector 的数据空间 |
| 查 | find | 查找。(注意这个是算法模块实现,不是 vector 的成员接口) |
vector 的尾插使用比较简单,
void test5() {
vector<int> v1;
v1.push_back(1); // 尾插 1
v1.push_back(2); // 尾插 2
v1.push_back(3); // 尾插 3
v1.push_back(4); // 尾插 4
v1.push_back(5); // 尾插 5
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
}
我们需要注意在 vector 中是没有头插的。但我们可以通过 insert 实现,但通过 insert 进行插入有多个函数重载需要注意。
第一个是在 position 位置插入 val,第二个是在 position 位置插入 n 个 val,第三个是在 position 位置插入一个由迭代器范围 [ first, last ) 定义的元素序列。要注意上面的参数都是迭代器参数。
void test6() {
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
vector<int>::iterator it=v1.begin();
v1.insert(v1.begin()+2, 6); //在第 2 个位置之前插入 6
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
v1.insert(v1.begin()+2, 6, 10); //在第 2 个位置之前插入 6 个 10
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
int arr[] = { 11,12,13,14 };
v1.insert(v1.begin()+2, arr, arr+3); //在第 2 个位置之前插入 arr 的 [arr,arr+3) 的数据
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
}
进行删除操作时,尾删比较好使用,就不做解释了。
对于 erase 有两个函数重载,它们都是传的迭代器参数:
使用示例:
void test7() {
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
v1.erase(v1.begin()); //删除第一个元素
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
v1.erase(v1.begin(),v1.begin()+2); //删除当前的 [v1.begin(),v1.begin()) 的两个元素
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
}
因为 vector 是使用数组实现的,,所以通过运算符重载 operator[ ] 可以实现对 vector 对象的访问及修改。这里就不多说了。
对于两个 vector 对象,使用 swap 就可以高效交换两个 vector 对象的内容。
void test8() {
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
vector<int> v2(3,6);
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
for (auto e : v2) {
cout << e << ' ';
}
cout << endl;
v1.swap(v2); // 交换 v1 和 v2
for (auto e : v1) {
cout << e << ' ';
}
cout << endl;
for (auto e : v2) {
cout << e << ' ';
}
cout << endl;
}
对于查找,vector 里面没有提供,所以我们可以使用算法模块 find 实现。使用算法模块需要包含头文件:
在 STL 源码底层里面,实现 vector 主要是靠使用三个迭代器实现的,也可以是称为指针(因为 vector 的迭代器就是指针)。即:
图示如下:
要注意我们模拟时需要我们自己来定义一个命名空间域,并且由于 vector 是可以存储多种类型的,所以我们需要使用模板实现,所以成员变量定义就可以如下所示:
namespace MyTest {
template<class T> class vector {
public:
//重定义可以和库里的保持一致,便于理解
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}
namespace MyTest {
template<class T> class my_vector {
public:
typedef T* iterator;
typedef const T* const_iterator;
//迭代器
iterator begin() { return _start; }
iterator end() { return _finish; }
const iterator begin() const { return _start; }
const iterator end() const { return _finish; }
//构造
my_vector() :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { }
my_vector(size_t n, const T& val = T()) //如果没有传 val,则 val 会去调用它的默认构造
{
resize(n, val);
}
my_vector(int n, const T& val = T())
{
resize(n, val);
}
template<class InputIterator>
my_vector(InputIterator first, InputIterator last)
{
while (first != last) {
push_back(*first);
++first;
}
}
//拷贝构造 - 深拷贝
my_vector(const my_vector<T>& v)
{
_start = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++) {
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
void swap(my_vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
my_vector<T>& operator=(my_vector<T> v)//传值,会调用拷贝构造
{
swap(v);
return *this;
}
//析构
~my_vector()
{
//清除空间资源
if (_start) {
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
//空间相关操作
//获取数据个数
size_t size() const { return _finish - _start; }
//获取容量
size_t capacity() const { return _end_of_storage - _start; }
//[]运算符重载
T& operator[](size_t pos)
{
assert(pos < size()); //断言位置合理性
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size()); //断言位置合理性
return _start[pos];
}
//扩容 (重要)
void reserve(size_t n)
{
if (n > capacity()) {
size_t sz = size();//这里需要保存一下原数据的个数
T* tmp = new T[n];
if (_start) {
//如果 vector 不为空,则要拷贝数据
for (int i = 0; i < size(); i++) {
tmp[i] = _start[i];
}
delete[] _start;//释放原空间
}
//对该对象进行赋值
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}//扩容完成
}
//改变 size
void resize(size_t n, const T val = T())
{
if (n < size()) {
//如果 n 比当前 size 小,只需要移动 finish 即可
_finish = _start + n;
} else {
//如果比 size 大,则需要先扩容,再补充元素
reserve(n);
while (_finish != _start + n) {
*_finish = val;
_finish++;
}
}
}
//在 pos 位置插入 x
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish); //判满
if (_finish == _end_of_storage) {
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + len;//防止迭代器失效 (因为扩容后空间地址会改变)
}
//移动数据
iterator end = _finish - 1;
while (end >= pos) {
*(end + 1) = *end;
end--;
}
//插入
*pos = x;
_finish++;
return pos;
}
//尾插
void push_back(const T& x) { insert(end(), x); }
//删除 pos 位置的数据
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it != _finish) {
*(it - 1) = *it;
++it;
}
_finish--;
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}
迭代器失效是指迭代器指向的对象不再有效,继续使用该迭代器会导致未定义行为。对于 vector 而言,迭代器失效主要发生在以下两种情况:
原因
我们在进行插入数据时往往会执行扩容操作。由于 vector 在内存中是连续存储的,所以当空间不足时,它会分配一块更大的内存 (通常是当前容量的 2 倍),将原有元素复制过去,然后释放旧内存。这个过程之后,如果之前使用了迭代器,则所有迭代器都会指向旧的内存地址,因此就会失效。
比如:在 VS 下的迭代器失效
#include<vector>
#include<iostream>
using namespace std;
int main() {
vector<int> v = { 1, 2, 3 };
auto it = v.begin(); // 指向第一个元素
v.push_back(4); // 可能触发扩容
// 此时 it 可能已经失效
cout << *it << endl; //此时迭代器就失效了,在 VS 下就是报错
return 0;
}
运行后:
避免方法
预留空间:使用 reserve() 预先分配足够空间。谨慎使用需要扩容的操作:在需要保持迭代器有效的操作中,避免使用可能导致扩容的操作。
原因
当使用 erase() 删除 vector 中的元素时,会导致后续元素向前移动,被删除元素之后的所有元素的迭代器都会失效,因为元素移动后,迭代器指向的位置可能被覆盖,从而失效,继续使用这些失效的迭代器会导致不可预测的程序行为,即未定义行为。
比如:
#include<vector>
#include<iostream>
using namespace std;
int main() {
vector<int> v = { 1, 2, 3 };
auto it = v.begin(); // 指向第一个元素
v.erase(it); // 删除操作后,就元素移动了
cout << *it << endl; // 此时迭代器就失效了,这是未定义行为,在 VS 下会报错
return 0;
}
运行后:
解决方法
每次删除时都返回新的迭代器。
总结一下
在 vector 中,通过插入或删除迭代器对象后,就不能再访问这个迭代器了。因为我们认为此时迭代器是失效的,访问时结果是未定义的,我们不能这么使用。
同时我们需要知道,不同编译器对迭代器失效的处理也是不同的,比如 VS 是强制检查,比较严格;g++ 的处理就比较宽松。
使用 memcpy 而出现的问题常常出现在我们来模拟实现 vector 时出现。比如我们在模拟实现 vector 的拷贝构造函数时,如果使用 memcpy 来进行拷贝,即:
my_vector(const my_vector<T>& v) {
_start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T) * v.size()); // 注意这样写是有问题的
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
这样写会出现的问题是:深浅拷贝的问题。可以说这样写的效果就是浅拷贝的效果。所以当我们写一个自定义的类型的 vector 时,就会出现问题,比如 string。
那么我们再执行以下代码:
int main() {
MyTest::my_vector<string> v;
v.push_back("11111");
v.push_back("22222");
v.push_back("33333");
v.push_back("44444");
MyTest::my_vector<string> v2(v);
return 0;
}
这段代码的执行效果如下:
其中 v 和 v2 是指向同一个空间的。
原因分析
memcpy 是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中。如果拷贝的是内置类型的元素,memcpy 既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为 memcpy 的拷贝实际是浅拷贝。
所以如果对象中涉及到资源管理时,千万不能使用 memcpy 进行对象之间的拷贝,因为 memcpy 是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。那么我们对于上面的代码,要使自定义类型也可以使用的话就可以这样写:
my_vector(const my_vector<T>& v) {
_start = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++) {
_start[i] = v._start[i];
}
/*memcpy(_start, v._start, sizeof(T) * v.size());*/
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
这样就是深拷贝:
我们在模拟实现构造函数时有三个函数的实现:
//构造
my_vector() :_start(nullptr), _finish(nullptr), _end_of_storage(nullptr) { }
//第二个
my_vector(size_t n, const T& val = T()) //如果没有传 val,则 val 会去调用它的默认构造
{
resize(n, val);
}
//第三个
template<class InputIterator>
my_vector(InputIterator first, InputIterator last)
{
while (first != last) {
push_back(*first);
++first;
}
}
其中的第二个和第三个的构造函数实现是有问题的。因为它们是很像的。当我们定义 vector 的对象时,编译器会调用最合适它的一个函数,比如这段代码:
MyTest::my_vector<int> v(4, 6); // 调用的是第三个构造函数
这里会优先调用的是第三个构造函数,但第三个函数是必须要用迭代器才能正常使用的,否则就不会正常运行:
所以在我们模拟实现 vector 的构造函数时,就需要注意这个情况。
在 vector 的库里面解决这个问题的方法就是将第二个函数进行了改进,就是将第二个函数进行重载一下,即以下代码:
my_vector(size_t n, const T& val = T()) //如果没有传 val,则 val 会去调用它的默认构造
{
resize(n, val);
}
my_vector(int n, const T& val = T())
{
resize(n, val);
}
template<class InputIterator>
my_vector(InputIterator first, InputIterator last)
{
while (first != last) {
push_back(*first);
++first;
}
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online