本文代码已同步Github

一、为什么需要vector

vector的本质就是动态数组,为什么不使用原始数组呢?

核心原因有以下三点:

  1. 自动管理内存:彻底告别手动 new[]delete[],杜绝内存泄漏,析构时自动清理。
  2. 极致的访问速度:内存连续,对 CPU 缓存极其友好,遍历和随机访问([])速度是所有容器中最快的之一。
  3. 动态扩容且零开销:既能像数组一样支持 O(1) 的随机访问,又能动态增减长度,且可通过 .data() 无缝传递给底层 C 接口。

总结一下:在绝大多数**“尾部增删,随机访问”**的情况下,vector是最好的默认选择

二、vector的介绍

C++ STL 中的 vector 是一个封装了动态大小数组的顺序容器(序列容器)。

它的核心本质是**“可以自动扩容的数组”**,在内存中占据连续的内存空间。

核心特性(三大支柱)

  • 动态扩容:元素个数可以随时变化。当空间不足时,vector 会自动分配一块更大的新内存(通常按当前容量的 2倍1.5倍 增长),将旧元素拷贝/移动过去,然后释放旧空间。
  • 连续存储:所有元素在内存中紧挨着排列,因此支持快速的随机访问(通过 []at() 访问元素的时间复杂度为 O(1))。
  • 尾部高效:在末尾添加或删除元素(push_back / pop_back)非常快(均摊时间复杂度为 O(1))。

总结一下:如果需要一个能灵活增加元素访问速度快内存连续的容器,vector是最优先考虑的选择

三、常用接口

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对象生命周期结束时清理资源,自动调用;


最后来看赋值重载运算符

在这里插入图片描述

注:两个已经存在的对象赋值才会调用赋值运算符重载函数;否则会调用拷贝构造

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这些一眼就能看懂是什么意思;

后面的resizereserve也不陌生,无非就是修改sizecapacity

我们先来看resize

在这里插入图片描述

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

我们来测试一下

在这里插入图片描述

显然, 程序符合上述叙述


接着我们来看reserve

在这里插入图片描述

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

同样我们来测试一下

在这里插入图片描述

显然,当n < capacity时,sizecapacity均不会改变

4、元素访问

在这里插入图片描述

operator[]是这组函数的核心接口;

operator使得我们像数组一样通过下标来访问元素

我们通过遍历来测试

在这里插入图片描述

5、修改操作

在这里插入图片描述

同样地,我们整理出核心接口

vector 增删查改接口说明
push_back(重点)尾插
pop_back(重点)尾删
find查找。(注意这个是算法模块实现,不是 vector 的成员接口)
insert在 position 之前插入 val
erase删除 position 位置的数据
swap交换两个 vector 的数据空间

根据string的经验,push_backpop_back也就是尾插尾删

find<algorithm.h>中的查找,非成员函数;

inserterase同理;

我们来观察一下inserterase的参数

//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倍扩容的;

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、区分reserveresize

我们通过一张表格来区分

对比维度reserve(n)resize(n) / resize(n, val)
核心作用预留内存空间(改变 capacity改变元素个数(改变 size
是否构造元素。只申请裸内存,不创建任何对象。若 n > size,会构造新元素(默认值或 val);若 n < size,会析构尾部多余元素
对 size() 影响不变(只影响 capacity改变size() 直接变成 n
能否用 [] 访问不能reservesize 没变,访问 v[i](超出原 size)是越界resize 后新位置已构造合法对象,可用 [] 访问
典型用途性能优化:提前知道数据总量,一次性分配好内存,避免频繁扩容造成拷贝开销实际扩容/缩容:业务上需要确确实实增加或减少容器中的元素个数

五、vector的注意事项

  1. reserve 只改容量(capacity),不改元素个数(size),预留后不能直接越界访问。
  2. resize 会改变元素个数(size),增加时会构造默认值,减少时会析构尾部元素。
  3. 避免频繁扩容:已知数据量时提前 reserve,否则每次扩容都要拷贝/移动所有元素,代价很大。
  4. 删除元素后:被删位置后的迭代器会失效;size 变小但 capacity 不变,内存不会主动归还。
  5. 什么时候用 vector:大多数情况都优先选它(默认容器),除非你频繁在头部/中间插入删除,或需要迭代器长期稳定。

acity),不改元素个数(size)**,预留后不能直接越界访问。
2. resize 会改变元素个数(size),增加时会构造默认值,减少时会析构尾部元素。
3. 避免频繁扩容:已知数据量时提前 reserve,否则每次扩容都要拷贝/移动所有元素,代价很大。
4. 删除元素后:被删位置后的迭代器会失效;size 变小但 capacity 不变,内存不会主动归还。
5. 什么时候用 vector:大多数情况都优先选它(默认容器),除非你频繁在头部/中间插入删除,或需要迭代器长期稳定。

如果觉得有帮助,可以关注Github项目持续更新

更多推荐