跳到主要内容
C++ vector 容器使用与模拟实现 | 极客日志
C++ 算法
C++ vector 容器使用与模拟实现 综述由AI生成 Vector 是 C++ STL 中的动态数组容器,支持自动内存管理、随机访问及增删操作。其类型定义、构造函数、迭代器遍历、空间管理(size/capacity)及增删查改接口。重点剖析了 vector 的底层模拟实现,包括三个指针成员变量、扩容机制、深拷贝与浅拷贝区别(memcpy 风险)、以及迭代器失效场景(容量变化与元素删除)。通过代码示例展示了常见用法及注意事项,适合学习 C++ 数据结构与算法基础。
魔法巫师 发布于 2025/10/12 更新于 2026/6/4 72 浏览一、vector 的简介
在 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 的使用
1. vector 的类型重定义
在认识 vector 时我们肯定会去查阅文档的,如果我们直接去看 vector 的各个构造函数可能是看不懂的,比如有许多看不懂的类型,这其实是因为 vector 类中通过 typedef 封装了其他类型。
其中的 value_type 就是第一个模板参数(T),allocator_type 就是第二个模板参数 (Alloc)。这些类型定义会在 vector 的许多成员函数中使用到。
2. 构造函数
下图是 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;
{
vector< > v1;
;
;
;
}
void
test1
()
int
vector<int > v2 (4 , 100 )
vector<int > v3 (v2. begin(), v2. end())
vector<int > v4 (v3)
这里我们可以补充一下:当然对象会在出函数作用域时自动调用析构函数的。
3. 迭代器的使用 迭代器有两种:一种是正向的迭代器,一种是反向的迭代器。正向的迭代器通过迭代器移动可以从前往后遍历 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;
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;
v1. push_back (1 );
v1. push_back (2 );
v1. push_back (3 );
v1. push_back (4 );
v1. push_back (5 );
for (int i = 0 ; i < v1. size (); i++) {
cout << v1[i] << ' ' ;
}
cout << endl;
vector<int >::iterator it = v1. begin ();
while (it != v1. end ()) {
cout << *it << ' ' ;
it++;
}
cout << endl;
for (auto e : v1) {
cout << e << ' ' ;
}
cout << endl;
}
4. 与空间相关的使用 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;
}
5. 增删查改 操作 函数 说明 增 push_back 尾插 增 insert 在 position 之前插入 val 删 pop_back 尾删 删 erase 删除 position 位置的数据 改 operator[ ] 像数组一样访问 改 swap 交换两个 vector 的数据空间 查 find 查找。(注意这个是算法模块实现,不是 vector 的成员接口)
void test5 () {
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 中是没有头插的。但我们可以通过 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 );
for (auto e : v1) {
cout << e << ' ' ;
}
cout << endl;
v1. insert (v1. begin ()+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 );
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 );
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);
for (auto e : v1) {
cout << e << ' ' ;
}
cout << endl;
for (auto e : v2) {
cout << e << ' ' ;
}
cout << endl;
}
对于查找,vector 里面没有提供,所以我们可以使用算法模块 find 实现。使用算法模块需要包含头文件:
三、vector 的模拟实现
1. 成员变量的解释 在 STL 源码底层里面,实现 vector 主要是靠使用三个迭代器实现的,也可以是称为指针(因为 vector 的迭代器就是指针)。即:
start:指向第一个元素,标记数据起始。
finish:指向最后一个元素的下一个位置,标记数据结束。
end_of_storage:指向动态分配内存的末尾的下一个位置(即内存块的尾后位置),标记容量上限。
要注意我们模拟时需要我们自己来定义一个命名空间域,并且由于 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;
};
}
2. 模拟实现 vector 的总代码 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 ())
{
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) {
for (int i = 0 ; i < size (); i++) {
tmp[i] = _start[i];
}
delete [] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
void resize (size_t n, const T val = T())
{
if (n < size ()) {
_finish = _start + n;
} else {
reserve (n);
while (_finish != _start + n) {
*_finish = val;
_finish++;
}
}
}
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); }
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 的常见问题
1. 迭代器失效的问题 迭代器失效是指迭代器指向的对象不再有效,继续使用该迭代器会导致未定义行为。对于 vector 而言,迭代器失效主要发生在以下两种情况:
容量变化导致的失效 :当 vector 的容量 (capacity) 发生变化时,所有现有迭代器、指针和引用都会失效。
元素删除导致的失效 :当删除某个元素时,指向该元素及其之后所有元素的迭代器都会失效。
(1)容量变化导致 我们在进行插入数据时往往会执行扩容操作。由于 vector 在内存中是连续存储的,所以当空间不足时,它会分配一块更大的内存 (通常是当前容量的 2 倍),将原有元素复制过去,然后释放旧内存。这个过程之后,如果之前使用了迭代器,则所有迭代器都会指向旧的内存地址,因此就会失效。
#include <vector>
#include <iostream>
using namespace std;
int main () {
vector<int > v = { 1 , 2 , 3 };
auto it = v.begin ();
v.push_back (4 );
cout << *it << endl;
return 0 ;
}
预留空间:使用 reserve() 预先分配足够空间。谨慎使用需要扩容的操作:在需要保持迭代器有效的操作中,避免使用可能导致扩容的操作。
(2)元素删除导致 当使用 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;
return 0 ;
}
总结一下
在 vector 中,通过插入或删除迭代器对象后,就不能再访问这个迭代器了。因为我们认为此时迭代器是失效的,访问时结果是未定义的,我们不能这么使用。
同时我们需要知道,不同编译器对迭代器失效的处理也是不同的,比如 VS 是强制检查,比较严格;g++ 的处理就比较宽松。
2. 关于 memcpy 的拷贝问题(深浅拷贝) 使用 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 ;
}
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];
}
_finish = _start + v.size ();
_end_of_storage = _start + v.capacity ();
}
3. 模拟 vector 的构造函数重载的问题
my_vector () :_start(nullptr ), _finish(nullptr ), _end_of_storage(nullptr ) { }
my_vector (size_t n, const T& val = T ())
{
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 ())
{
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;
}
}
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online