本文代码已同步Github
一、为什么需要vector
vector的本质就是动态数组,为什么不使用原始数组呢?
核心原因有以下三点:
- 自动管理内存 :彻底告别手动
new[]和delete[],杜绝内存泄漏,析构时自动清理。 - 极致的访问速度 :内存连续,对 CPU 缓存极其友好,遍历和随机访问(
[])速度是所有容器中最快的之一。 - 动态扩容且零开销 :既能像数组一样支持
O(1)的随机访问,又能动态增减长度,且可通过.data()无缝传递给底层 C 接口。
总结一下:在绝大多数**"尾部增删,随机访问"**的情况下,vector是最好的默认选择
二、vector的介绍
C++ STL 中的 vector 是一个封装了动态大小数组的顺序容器(序列容器)。
它的核心本质是**"可以自动扩容的数组"**,在内存中占据连续的内存空间。
核心特性(三大支柱)
- 动态扩容 :元素个数可以随时变化。当空间不足时,
vector会自动分配一块更大的新内存(通常按当前容量的 2倍 或 1.5倍 增长),将旧元素拷贝/移动过去,然后释放旧空间。- 连续存储 :所有元素在内存中紧挨着排列,因此支持快速的随机访问 (通过
[]或at()访问元素的时间复杂度为 O(1))。- 尾部高效 :在末尾添加或删除元素(
push_back/pop_back)非常快(均摊时间复杂度为 O(1))。
总结一下:如果需要一个能灵活增加元素 、访问速度快 且内存连续 的容器,vector是最优先考虑的选择
三、常用接口
与string不同的是,vector的接口简洁了很多,只保留了核心接口;
1、默认成员函数
首先来看构造函数

| 构造函数声明 | 接口说明 |
|---|---|
| vector()(重点) | 无参构造 |
| vector (size_type n, const value_type& val = value_type()) | 构造并初始化 n 个 val |
| vector (const vector& x);(重点) | 拷贝构造 |
| vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |
构造函数有四个,我们逐个来使用

v1为空,v2为里面有10个1,v3同样也有10个1,v4有5个1;
当然,vector中不仅能存放内置类型,同样也能存放自定义类型;

接着来看析构函数

析构函数就是在vector对象生命周期结束时清理资源,自动调用;
最后来看赋值重载运算符

注:两个已经存在的对象 赋值才会调用赋值运算符重载函数;否则会调用拷贝构造
cpp
void test_constructor3()
{
vector<int> v1(5, 1);
vector<int> v2;
//拷贝构造
vector<int> v3 = v1;
//赋值重载
v2 = v1;
}
2、迭代器

我们整理出常用的核心接口:
| iterator 的使用 | 接口说明 |
|---|---|
| begin + end(重点) | 获取第一个数据位置的 iterator/const_iterator,获取最后一个数据的下一个位置的 iterator/const_iterator |
| rbegin + rend | 获取最后一个数据位置的 reverse_iterator,获取第一个数据前一个位置的 reverse_iterator |
对于迭代器,我们使用遍历来测试

3、容量

给出常用接口
| 容量空间 | 接口说明 |
|---|---|
| size | 获取数据个数 |
| capacity | 获取容量大小 |
| empty | 判断是否为空 |
| resize(重点) | 改变 vector 的 size |
| reserve(重点) | 改变 vector 的 capacity |
size,capacity,empty这些一眼就能看懂是什么意思;
后面的resize和reserve也不陌生,无非就是修改size和capacity
我们先来看resize

通过叙述我们发现:
如果n > size,就会更新size = n,同时根据val的值进行填充;
如果n < size就会把size后面的的元素删掉,同时更新size = n
如果n > capacity就会重新分配存储空间;
我们来测试一下

显然, 程序符合上述叙述
接着我们来看reserve

通过叙述我们发现:
如果n > capacity,便会重新分配空间,使capacity = n
其他情况不会引起空间的分配并且capacity不会改变
reserve不会引起size的改变
同样我们来测试一下

显然,当n < capacity时,size和capacity均不会改变
4、元素访问

operator[]是这组函数的核心接口;
operator使得我们像数组一样通过下标来访问元素
我们通过遍历来测试

5、修改操作

同样地,我们整理出核心接口
| vector 增删查改 | 接口说明 |
|---|---|
| push_back(重点) | 尾插 |
| pop_back(重点) | 尾删 |
| find | 查找。(注意这个是算法模块实现,不是 vector 的成员接口) |
| insert | 在 position 之前插入 val |
| erase | 删除 position 位置的数据 |
| swap | 交换两个 vector 的数据空间 |
根据string的经验,push_back和pop_back也就是尾插 和尾删
而find是<algorithm.h>中的查找,非成员函数;
insert和erase同理;
我们来观察一下insert和erase的参数
cpp
//single element (1)
iterator insert (iterator position, const value_type& val);
//fill (2)
void insert (iterator position, size_type n, const value_type& val);
iterator erase (iterator position);
iterator erase (iterator first, iterator last)
发现参数全部都是迭代器的形式;因此在调用这些接口时要传入迭代器;
最后来看swap

swap不仅交换数据,在交换前会判断扩容,开好空间之后再进行交换
下面我们来测试一下

6、非成员函数

非成员函数包含比较函数 和交换函数
交换函数本质上和修改操作 中的swap一样

下面我们来测试一下比较函数

四、vector的扩容机制
1、空间增长问题
我们先来测试一下vector的扩容机制

在Visual Stdio的环境下,capacity是按照1.5倍进行扩容的;
而在g++下,则是按照2倍扩容的;
cpp
g++运行结果:linux下使用的STL基本是按照2倍方式扩容
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128
那么,该怎样避免多次扩容导致效率低下的问题呢?
如果已经确定vector中要存储的元素大概个数,提前开好空间即可

2、区分reserve与resize
我们通过一张表格来区分
| 对比维度 | reserve(n) | resize(n) / resize(n, val) |
|---|---|---|
| 核心作用 | 只预留内存空间 (改变 capacity) |
改变元素个数 (改变 size) |
| 是否构造元素 | 否。只申请裸内存,不创建任何对象 | 是 。若 n > size,会构造 新元素(默认值或 val);若 n < size,会析构尾部多余元素 |
| 对 size() 影响 | 不变 (只影响 capacity) |
改变 (size() 直接变成 n) |
| 能否用 \[\] 访问 | 不能 。reserve 后 size 没变,访问 v[i](超出原 size)是越界 |
能 。resize 后新位置已构造合法对象,可用 [] 访问 |
| 典型用途 | 性能优化:提前知道数据总量,一次性分配好内存,避免频繁扩容造成拷贝开销 | 实际扩容/缩容:业务上需要确确实实增加或减少容器中的元素个数 |
五、vector的注意事项
- reserve 只改容量(capacity),不改元素个数(size),预留后不能直接越界访问。
- resize 会改变元素个数(size),增加时会构造默认值,减少时会析构尾部元素。
- 避免频繁扩容 :已知数据量时提前
reserve,否则每次扩容都要拷贝/移动所有元素,代价很大。 - 删除元素后 :被删位置后的迭代器会失效;
size变小但capacity不变,内存不会主动归还。 - 什么时候用 vector:大多数情况都优先选它(默认容器),除非你频繁在头部/中间插入删除,或需要迭代器长期稳定。
acity),不改元素个数(size)**,预留后不能直接越界访问。
-
resize 会改变元素个数(size) ,增加时会构造默认值,减少时会析构尾部元素。
-
避免频繁扩容 :已知数据量时提前
reserve,否则每次扩容都要拷贝/移动所有元素,代价很大。 -
删除元素后 :被删位置后的迭代器会失效;
size变小但capacity不变,内存不会主动归还。 -
什么时候用 vector:大多数情况都优先选它(默认容器),除非你频繁在头部/中间插入删除,或需要迭代器长期稳定。
如果觉得有帮助,可以关注Github项目持续更新