vector 是 C 加加标准模板库里面的动态顺序容器,底层采用连续内存数组实现,可以自动扩容,能够存放任意类型的数据,支持随机访问,是日常开发最常用的容器,很多初学者容易把它和普通静态数组混淆,普通数组创建之后长度固定,无法动态增加元素,vector 解决了这个痛点,不需要提前写死数组大小,可以在程序运行过程中持续添加删除元素,它属于模板类,使用的时候需要指定存储的数据类型,必须引入头文件 vector,并且使用 std 命名空间或者写全 std 前缀,vector 内存是连续排布,所以可以像数组一样通过下标访问元素,同时自带插入删除查找扩容等成员函数,它的缺点是在中间位置插入删除元素效率偏低,因为需要移动后面所有元素,扩容的时候会重新申请更大内存,把原有数据拷贝到新内存再释放旧内存,理解底层扩容机制是掌握 vector 用法的核心。
基础定义与头文件引入
使用 vector 第一步必须引入头文件,写法为 include vector,代码里如果没有写 using namespace std,所有 vector 都要写成 stdvector,模板语法格式是 vector 数据类型 对象名称,支持存储基础类型也支持存储结构体类对象指针,还可以定义二维 vector 也就是 vector 嵌套 vector。
基础定义示例
vector int v 存储 int 类型
vector string v 存储字符串
vector double v 存储浮点型
vector vector int vv 二维 int 动态数组
常用成员函数清单
| 函数名称 | 功能说明 | 注意要点 |
|---|---|---|
| push_back | 尾部追加一个元素 | 尾部插入效率高,可能触发自动扩容 |
| pop_back | 删除尾部元素 | 不会返回被删除元素 |
| size | 获取当前元素个数 | 返回元素数量,不是内存容量 |
| capacity | 获取当前分配内存容量 | 扩容之后 capacity 会大于等于 size |
| at | 按下标访问元素 | 越界会抛出异常,比下标访问更安全 |
| front | 获取第一个元素 | 容器为空调用会报错 |
| back | 获取最后一个元素 | 容器为空调用会报错 |
| insert | 在指定迭代器位置插入元素 | 中间插入需要移动后续元素,性能差 |
| erase | 删除迭代器指向位置或者区间元素 | 删除之后迭代器会失效 |
| clear | 清空全部元素 | size 变成 0,但是 capacity 不会释放 |
| reserve | 提前预留内存容量 | 只扩容不增加元素个数,减少多次扩容拷贝 |
| resize | 修改元素个数 | 可以新增默认元素,也可以截断删除尾部元素 |
| swap | 交换两个 vector 全部数据 | 交换速度很快,只交换内部指针 |
遍历方式
第一种下标遍历,和普通数组写法一致,for 循环从 0 到 size 减一,使用 v i 访问元素,适合习惯数组写法的开发者。
第二种迭代器遍历,使用 vector int iterator it,通过迭代器自增访问,是标准库通用写法,兼容所有顺序容器。
第三种范围 for 循环,C 加加 11 之后支持,写法 for auto x v,直接遍历每一个元素,如果需要修改元素要加引用 auto x。
底层扩容原理
vector 初始创建的时候一般分配较小内存,当 push_back 添加元素,当前容量 capacity 已经装满,就会触发扩容,大部分编译器扩容策略是申请原来容量两倍的新内存,把旧内存里面全部元素复制或者移动到新内存,释放原来旧内存,然后继续添加新元素,这个拷贝过程会消耗性能,如果提前知道元素总数,可以调用 reserve 预先分配容量,避免多次扩容带来的开销,这里要区分 reserve 和 resize,reserve 只改容量,resize 改变实际元素数量,很多新手经常把这两个函数弄混。
插入删除与迭代器失效
在 vector 中间执行 insert 或者 erase 之后,插入位置后面的迭代器指针引用都会失效,因为内存元素发生移动,旧迭代器不再指向原来元素,继续使用会造成野指针程序崩溃,正确做法是接收 erase insert 函数返回的新迭代器,继续后续循环操作,尾部 push_back pop_back 一般不会造成前面迭代器失效,但是如果触发扩容,全部旧迭代器都会失效。
二维 vector 操作
二维 vector 本质是每一个元素本身又是一个 vector,创建之后可以逐行 push_back 添加子 vector,也可以提前指定行数,访问方式是 vv 行号 列号,适合存储矩阵类数据,同样支持动态增加行数和列数。
完整使用步骤
第一步引入头文件,声明命名空间,定义指定类型的 vector 对象。
第二步根据需求选择初始化方式,可以空容器创建,也可以初始化指定数量默认值,或者用另一个 vector 拷贝初始化。
第三步添加元素,优先使用 push_back 尾部追加,如果预知数据总量调用 reserve 预分配内存。
第四步遍历读取元素,选择下标迭代器或者范围 for。
第五步执行插入删除操作,重点留意迭代器失效问题。
第六步程序结束 vector 对象会自动析构,自动释放内存,不需要手动 delete,这也是相比原生数组优势。
综合来说 vector 适合需要动态长度连续内存随机访问的场景,适合存储动态列表矩阵数据集,不适合频繁在中间插入删除的业务,如果频繁中间增删,一般会选择 list 这类链表容器,开发的时候牢记 size 和 capacity 区别,理解扩容拷贝代价,处理好迭代器失效,就可以稳定正确使用 vector。
【常见问题】
问题 1 c 中 vector 为什么 push_back 之后迭代器失效
回答 1 c 中 vector 在 push_back 的时候如果内存已满触发自动扩容,系统会申请全新连续内存,拷贝原有元素,旧内存被释放,原来所有迭代器指向旧内存地址,所以 c 中 vector 的迭代器就会失效,继续访问会引发程序异常。
问题 2 c 中 vector 的 reserve 和 resize 有什么区别
回答 2 c 中 vector 的 reserve 作用是提前预留内存容量,只改变 capacity,不会新增实际元素,c 中 vector 的 resize 是直接修改元素个数,会新增默认初始化元素或者截断尾部元素,size 数值会发生变化。
问题 3 c 中 vector 可以存储自定义结构体吗
回答 3 c 中 vector 可以存储自定义结构体,定义的时候模板参数填写结构体名称即可,前提是这个结构体支持默认构造拷贝或者移动操作,c 中 vector 在扩容拷贝元素的时候会调用对应构造函数。
问题 4 c 中 vector clear 之后内存会立刻释放吗
回答 4 c 中 vector 执行 clear 之后,所有元素被销毁,size 变为 0,但是已经分配的 capacity 内存并不会自动释放,c 中 vector 如果想要释放多余内存,可以配合 swap 技巧或者 shrink_to_fit 函数收缩容量。
【免责申明】
本文内容基于 C 加加标准库 vector 通用语法进行讲解,不同编译器版本标准存在细微差异,代码示例仅用于学习演示,不保证适配所有编译环境,实际项目开发请以所用编译器官方文档和 C 加加标准规范为准,本文不构成项目开发方案保证。


