目录
一、前言
二、本节代码示例
三、什么是vector的元素空间和内存空间
3.1 元素空间:已经真正存在的元素个数
3.2 内存空间:已经分配好的容量大小
3.3 一定要区分:size不是capacity
(1)size()
(2)capacity()
3.4 capacity显示的不是字节数
四、vector空间控制中最常用的几个函数
4.1 resize():改变元素数量
4.2 reserve():预留内存容量
4.3 resize() 和 reserve() 的本质区别
(1)resize(n)
(2)reserve(n)
4.4 clear():清空元素
4.5 shrink_to_fit():请求缩容
五、为什么vector效率高,但扩容也会有开销
六、动态扩容实验分析
6.1 这段代码的思路是什么
6.2 这段代码优化
6.3 为什么vector不会每次只扩1个空间
七、本节重点总结
7.1 size() 和 capacity() 的区别
7.2 resize() 和 reserve() 的区别
7.3 clear() 和 shrink_to_fit() 的区别
7.4 vector效率的本质
八、小结
一、前言
在前面的几篇内容中,我们已经学习了vector的基础使用,包括:
vector的定义与初始化- 元素访问与遍历
- 搜索、删除、插入与排序
到这里,你已经知道vector是一个非常好用的动态数组容器。但如果想真正理解它为什么适合工程开发,仅仅会“用”还不够,还要进一步理解它背后的两个重要概念:
元素数量和内存容量。
很多初学者在学习vector时,最容易把下面这两个概念混在一起:
size()capacity()
表面上看,它们都和“大小”有关,但其实它们表示的是两件完全不同的事情:
size():当前已经存了多少个元素capacity():当前已经分配了多少个元素位置的内存容量
这两个概念搞清楚之后,你才会真正理解:
- 为什么
push_back()有时候很快,有时候会慢一点 - 为什么
clear()后元素没了,但内存不一定立刻变小 - 为什么
reserve()可以提前提高效率 - 为什么
resize()和reserve()虽然都能“变大”,但本质完全不同
本节我们就结合代码,系统讲清楚vector的元素空间与内存空间显示和控制。
二、本节代码示例
先把本节要分析的核心代码放出来:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v1; vector<int> vdata(10); // 查看元素数量 cout << "v1.size():" << v1.size() << endl; cout << "vdata.size():" << vdata.size() << endl; // 查看容量大小 cout << "v1.capacity():" << v1.capacity() << endl; cout << "vdata.capacity():" << vdata.capacity() << endl; // resize:改变元素数量 v1.resize(8); // reserve:预留容量 v1.reserve(16); cout << "after resize and reserve\n"; cout << "v1.size():" << v1.size() << endl; cout << "v1.capacity():" << v1.capacity() << endl; // push_back:尾部新增元素 v1.push_back(99); for (auto v : v1) cout << v << "|"; cout << endl; // clear:清空元素 v1.clear(); cout << "after clear\n"; cout << "v1.size():" << v1.size() << endl; cout << "v1.capacity():" << v1.capacity() << endl; // shrink_to_fit:尝试缩容 v1.shrink_to_fit(); cout << "after shrink_to_fit\n"; cout << "v1.size():" << v1.size() << endl; cout << "v1.capacity():" << v1.capacity() << endl; cout << "----------------\n"; // 观察动态扩容 int c = v1.capacity(); for (int i = 0; i < 200; i++) { v1.push_back(i); if (c != v1.capacity()) { cout << "capacity from " << c << " to " << v1.capacity() << endl; c = v1.capacity(); } } return 0; }这段代码虽然不长,但里面把vector的很多关键机制都串起来了,非常适合用来理解vector的空间管理逻辑。
三、什么是vector的元素空间和内存空间
3.1 元素空间:已经真正存在的元素个数
vector中所谓的“元素空间”,可以先理解为:
当前容器里真正已经存在、可以访问的元素数量。
这个数量通过:
v.size()来查看。
例如:
vector<int> v1; vector<int> vdata(10);此时:
cout << "v1.size():" << v1.size() << endl; cout << "vdata.size():" << vdata.size() << endl;输出为:
v1.size():0 vdata.size():10说明:
v1现在一个元素都没有vdata现在已经有 10 个元素了
也就是说,size()关心的是:
逻辑上有多少个元素存在。
3.2 内存空间:已经分配好的容量大小
vector还有一个很重要的概念叫容量,通过:
v.capacity()来查看。
例如:
cout << "v1.capacity():" << v1.capacity() << endl; cout << "vdata.capacity():" << vdata.capacity() << endl;输出为:
v1.capacity():0 vdata.capacity():10这里表示:
v1当前没有元素,也没有额外分配存储空间vdata当前不仅有 10 个元素,而且已经分配了足够容纳 10 个元素的内存
所以capacity()关心的是:
当前底层已经分配了多少“可容纳元素的位置”。
3.3 一定要区分:size不是capacity
这是本节最核心的地方。
size()和capacity()的区别可以直接理解为:
(1)size()
当前已经存储的元素数量
(2)capacity()
当前已经分配好的内存容量,表示最多还能在不重新扩容的情况下容纳多少元素
比如:
vector<int> v; v.resize(8); v.reserve(16);此时:
size()可能是 8capacity()可能是 16
意思就是:
- 现在真正存在 8 个元素
- 但底层已经预留了 16 个元素的位置
也就是说,后面再继续插入一些元素时,只要不超过 16,就不需要重新申请内存。
3.4 capacity显示的不是字节数
这里还要特别提醒一下:
capacity()返回的不是“字节数”,而是:
能容纳多少个当前类型的元素。
例如:
vector<int> v; v.reserve(16);这里capacity()返回的是 16,不是 64。
虽然对于int来说,如果每个int占 4 字节,那么底层大致可能需要 16 × 4 = 64 字节左右的空间,但capacity()本身显示的是“元素个数容量”,不是字节数。
四、vector空间控制中最常用的几个函数
4.1 resize():改变元素数量
代码:
v1.resize(8);这句的作用是:
把v1的元素数量改成 8。
因为v1原来是空的,所以执行完后,v1中就会真正出现 8 个元素。
对于int类型,这些新元素通常会被默认初始化为0。
所以执行完后,v1的逻辑状态大致是:
0 0 0 0 0 0 0 0此时:
v1.size()为 8v1.capacity()至少也要能容纳 8 个元素
所以resize()的关键点是:
它不只是调整内存,更重要的是调整“元素个数”。
4.2 reserve():预留内存容量
代码:
v1.reserve(16);这句的作用是:
至少把底层容量扩充到 16。
但是要注意:
reserve()只影响容量,不影响当前元素数量。
也就是说,如果原来:
size() = 8capacity() = 8
执行:
v1.reserve(16);之后通常会变成:
size() = 8capacity() = 16
所以reserve()的本质是:
提前申请好内存,避免后续频繁扩容。
这在工程里非常常用,因为它可以减少反复申请和搬移内存带来的性能开销。
4.3 resize() 和 reserve() 的本质区别
这是非常容易考、也非常容易写错的点。
(1)resize(n)
改变的是元素数量
- 如果变大,会新增元素
- 如果变小,会删掉多余元素
(2)reserve(n)
改变的是容量大小
- 只保证底层内存至少够大
- 不会新增元素
- 不会改变
size()
可以直接记一句:
resize管“有几个元素”,reserve管“底下准备多大空间”。
4.4 clear():清空元素
代码:
v1.clear();这句的作用是:
清空所有元素。
执行后:
size()会变成 0
但是要特别注意:
clear() 不保证释放底层容量。
也就是说,元素没了,不代表容量一定也归零。
比如清空前如果:
size() = 9capacity() = 16
那么清空后很可能变成:
size() = 0capacity() = 16
因为它只是把元素删除了,但底层那块内存还可能保留着,方便后续继续使用。
4.5 shrink_to_fit():请求缩容
代码:
v1.shrink_to_fit();这句的作用通常理解为:
- 让容器尽量把多余容量缩掉,使容量更贴近当前元素数量.
- 内存适应元素 有多少元素就分配多少内存
例如当前:
size() = 0capacity() = 16
执行:
v1.shrink_to_fit();之后很多实现中可能会变成:
size() = 0capacity() = 0
但这里一定要严谨一点说明:
shrink_to_fit()是“请求缩容”,不是“强制必须缩到多少”。
调用shrink_to_fit()后,容器会尝试释放多余内存,使容量更贴近当前元素数量。
五、为什么vector效率高,但扩容也会有开销
这是理解vector性能的关键。
vector之所以访问快,是因为它底层通常是连续内存空间,和普通数组很像,所以支持:
- 下标访问快
- 遍历效率高
- CPU 缓存友好
但是vector在不断push_back()时,并不是每加一个元素就只做一件小事。
如果当前容量已经满了,就会发生:
(1)重新申请一块更大的内存
(2)把原来元素搬过去
(3)释放旧内存
(4)再插入新元素
这个过程就叫扩容。
所以vector的效率特点可以总结为:
- 平时访问很快
- 尾插一般也很快
- 但一旦扩容,会有额外开销
这也是为什么:
如果你提前知道大概要放多少数据,最好先用reserve()预留空间。
六、动态扩容实验分析
后面这段代码,就是专门用来观察容量变化的:
int c = 0; //元素空间动态变化 for (int i = 0; i < 200; i++) { v1.push_back(i); if (c != v1.capacity()) { cout << "v1.capacity():" << c << endl; } c = v1.capacity(); }这段代码的核心目的,是想观察:
随着元素不断增加,vector的容量是如何变化的。
6.1 这段代码的思路是什么
(1)不断push_back(i)
让v1里的元素越来越多
(2)每插入一次,就检查当前容量是否变化
如果容量变化了,说明刚刚发生了扩容
(3)把扩容过程打印出来
这样就能看到vector不是每次只增加 1 个容量,而是会按某种增长策略扩容
6.2 这段代码优化
原代码里写的是:
if (c != v1.capacity()) { cout << "v1.capacity():" << c << endl; } c = v1.capacity();这里打印的是旧值c,不是变化后的新容量。
所以如果想让观察更直观,建议改成:
int c = v1.capacity(); for (int i = 0; i < 200; i++) { v1.push_back(i); if (c != v1.capacity()) { cout << "capacity from " << c << " to " << v1.capacity() << endl; c = v1.capacity(); } }这样输出会更清楚,比如类似:
capacity from 0 to 1 capacity from 1 to 2 capacity from 2 to 4 capacity from 4 to 8 capacity from 8 to 16 ...当然,不同编译器、不同标准库实现,扩容策略可能略有不同,但通常都不是“每次只加1”。
6.3 为什么vector不会每次只扩1个空间
如果每次插入一个元素,都只扩容 1 个位置,那么会频繁发生:
- 申请内存
- 拷贝旧数据
- 释放旧空间
这样效率会非常差。
所以vector通常会采用一种“按比例增长”的方式,例如:
- 扩到 2 倍
- 或者扩到更大的某个倍数
这样就能减少扩容次数,提高整体效率。
这也是vector性能设计里非常重要的一点。
七、本节重点总结
7.1 size() 和 capacity() 的区别
(1)size()
当前真正存在的元素个数
(2)capacity()
当前底层已分配好的容量大小
7.2 resize() 和 reserve() 的区别
(1)resize(n)
改变元素数量
会真正新增或删除元素
(2)reserve(n)
改变容量大小
只扩内存,不新增元素
7.3 clear() 和 shrink_to_fit() 的区别
(1)clear()
清空元素,但不一定释放容量
(2)shrink_to_fit()
尝试缩小容量,使其更贴近当前元素数量
7.4 vector效率的本质
(1)访问快,因为底层是连续空间
(2)尾插通常也快
(3)扩容时会有额外开销
(4)提前reserve()可以减少扩容次数,提高效率
八、小结
本节我们从“能不能用 vector”,进一步走到了“vector 为什么这么设计”。
vector 不仅在管理元素,还在管理一块可动态变化的连续内存空间。
也正因为如此,学习vector时一定要分清两件事:
- 现在有多少元素
- 现在底层预留了多少空间