vector初识——从动态数组到STL核心容器

本文代码已同步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对象生命周期结束时清理资源,自动调用;


最后来看赋值重载运算符

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

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

后面的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的参数

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、区分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)**,预留后不能直接越界访问。

  1. resize 会改变元素个数(size) ,增加时会构造默认值,减少时会析构尾部元素。

  2. 避免频繁扩容 :已知数据量时提前 reserve,否则每次扩容都要拷贝/移动所有元素,代价很大。

  3. 删除元素后 :被删位置后的迭代器会失效;size 变小但 capacity 不变,内存不会主动归还。

  4. 什么时候用 vector:大多数情况都优先选它(默认容器),除非你频繁在头部/中间插入删除,或需要迭代器长期稳定。

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

相关推荐
AI视觉网奇1 小时前
glb读取颜色
开发语言·python
yyds_yyd_100861 小时前
3731. 找出缺失的元素(2026.08.04)
c++·leetcode
呱呱巨基1 小时前
CMake基础
linux·c++·笔记·学习
程序员-Benothing2 小时前
如何实现高并发系统订单30分钟自动关闭?
开发语言·c#·wpf
郝学胜-神的一滴3 小时前
Python 高级编程 026:内置数据结构之骈文纵论
开发语言·数据结构·python·程序人生·软件工程
隐积11 小时前
3.1.7.Qt容器大家族(3):QVariant——万能盒子
开发语言·qt
Tri_Function13 小时前
动态规划DP1(c++)
c++
名字还没想好☜14 小时前
Next.js ‘use client‘ 到底加在哪:Server/Client Components 边界与常见报错
开发语言·前端·javascript·react·next.js
初级代码游戏15 小时前
iOS开发 Swift 速记1:变量和基本数据类型
开发语言·ios·swift