C++ vector size 与 capacity

在使用 vector 容器时,我们除了调用 size() 查看里面有几个元素之外,还会遇到一个常跟它一起出现的成员函数 capacity()。

很多初学的小伙伴容易把 “元素数量(size)” 和 “底层容量(capacity)” 混为一谈。在 C++ 中,与 vector 大小和容量相关的常用成员函数如下表所示。

大小与容量常用函数
函数 说明 是否改变元素数量
size() 获取当前实际存放的元素个数 否
capacity() 获取重新分配内存前最多能容纳的元素个数 否
reserve(n) 提前预留至少容纳 n 个元素的底层容量 否(只改变 capacity)
resize(n) 将实际元素数量调整为 n 是(直接改变 size)
shrink_to_fit() 释放多余的空闲容量,使 capacity 贴合 size 否(只缩减 capacity)

size() 与 capacity() 有什么区别?

这两个概念放在一起对比其实很好理解。你可以想象一间教室里一共摆放了 50 张桌子,但目前只坐了 30 名学生。

这里的 30 就是 size(),代表当前实际已经存放了多少个元素。而 50 就是 capacity(),代表当前这块内存最多可以容纳多少个元素,如下图所示。

vector 中 size() 与 capacity() 的区别

看到这里,可能有些小伙伴会问:“为什么 vector 不干脆有几个元素就只申请几个位置,而要提前多准备一些容量呢?”

这是因为 vector 的元素在内存中必须是连续存放的。如果每次调用 push_back() 添加元素时都重新申请一块更大的内存,再把原有元素一个个搬过去,程序运行就会变得很慢。

因此,当现有空间装满时,vector 会自动成倍扩充底层容量,留出一些空位供后续快速放入数据。

示例 1:观察自动扩容过程

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v;

    for (int i = 1; i <= 4; i++) {
        v.push_back(i * 10);
        std::cout << "size=" << v.size() << ", ";
        std::cout << "capacity=" << v.capacity() << '\n';
    }
    return 0;
}

运行结果如下。

size=1, capacity=1
size=2, capacity=2
size=3, capacity=4
size=4, capacity=4

分析:

在这个例子中,我们通过循环依次向空容器中添加 4 个整数。存入前 2 个元素时,size() 和 capacity() 同步增加到了 2。

当存入第 3 个元素时,原来的 2 个位置已经不够用了,于是 vector 自动申请了一块能容纳 4 个元素的新内存。此时 size() 是 3,而 capacity() 变成了 4,多出来的 1 个空位刚好直接供第 4 个元素使用,无需再次扩容。

不同编译器的自动扩容倍数略有差异(如 GCC 通常按 2 倍扩容,MSVC 通常按 1.5 倍扩容),但 capacity() 始终大于或等于 size()。因此在对容器做循环遍历或按下标访问元素时,有效下标范围永远只看 size()。

注意 size() 代表真实存在的元素个数,而 capacity() 只是底层准备好的容量。千万不要因为存入第 3 个元素后 capacity() 变成了 4 就提前去访问 v[3],预留的空位上还没有存放真实元素,直接访问会导致下标越界。

预留容量:reserve()

既然 vector 在容量不够时会自动重新分配内存并搬移元素,那如果我们提前就知道接下来大概要装入 100 个元素,能不能让它一次性把 100 个空位直接准备好呢?这时我们就可以使用 reserve() 成员函数,其中 reserve 是 “预留” 的意思。

语法:

容器名.reserve(n);

说明:

调用 reserve(n) 会通知容器提前把底层容量扩充到至少能容纳 n 个元素。如果传入的 n 小于或等于当前的 capacity(),容器什么也不会做,不会把已有容量改小。

这里初学者最容易踩坑的地方是:reserve(n) 只负责 “往教室里提前搬入空桌子”,并不会创建任何真实元素,因此调用后 size() 不会发生改变。

示例 2:使用 reserve() 预留容量

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v;
    v.reserve(10);    // 提前预留 10 个元素的容量

    std::cout << "预留后 size:" << v.size() << '\n';
    std::cout << "预留后 capacity:" << v.capacity() << '\n';

    v.push_back(15);
    v.push_back(25);

    std::cout << "添加后 size:" << v.size() << '\n';
    std::cout << "添加后 capacity:" << v.capacity() << '\n';
    return 0;
}

运行结果如下。

预留后 size:0
预留后 capacity:10
添加后 size:2
添加后 capacity:10

分析:

执行 v.reserve(10); 之后,底层容量 capacity() 直接变成了 10,但此时还没有放入数据,所以 v.size() 依然是 0。随后我们添加 2 个元素时,由于不超过预留容量,capacity() 保持 10 不变,size() 变成了 2。

正因为 reserve(10) 没有创建真实元素,刚调用完时 v.size() 依然为 0。此时不能直接使用下标赋值,而应该通过 push_back() 添加数据。

std::vector<int> v;
v.reserve(10);

// 正确方式
v.push_back(15);

// 错误方式
v[0] = 15;

调整元素数量:resize()

与只准备空位的 reserve() 不同,resize() 是真正用来修改容器中 “实际元素数量” 的成员函数。

语法:

容器名.resize(n);
容器名.resize(n, 初始值);

说明:

调用 resize(n) 时,容器会根据目标数量 n 与当前 size() 的大小关系自动执行 “多删少补”:

  • 扩大元素数量:当 n 大于当前 size() 时,会在末尾自动补齐新元素。如果不传第 2 个参数,数值元素默认补 0;如果传了第 2 个参数,则统一补为该初始值。
  • 缩小元素数量:当 n 小于当前 size() 时,只保留前 n 个元素,把末尾多出来的元素直接删掉。

示例 3:扩大与缩小元素数量

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {10, 20};

    v.resize(5, 99);    // 扩大到 5 个元素
    std::cout << "扩大后(size=" << v.size() << "):";
    for (int x : v) {
        std::cout << x << ' ';
    }
    std::cout << '\n';

    v.resize(3);        // 缩小到 3 个元素
    std::cout << "缩小后(size=" << v.size() << "):";
    for (int x : v) {
        std::cout << x << ' ';
    }
    std::cout << '\n';
    return 0;
}

运行结果如下。

扩大后(size=5):10 20 99 99 99
缩小后(size=3):10 20 99

分析:

在这个例子中,v 一开始只有 10 和 20 两个元素,两次调整的过程如下:

  • 第 1 次调整:执行 v.resize(5, 99); 后,程序保留原有的 10 和 20,并在末尾补上 3 个值为 99 的真实元素。此时 v.size() 变成 5,我们可以直接通过下标 0 到 4 访问它们。
  • 第 2 次调整:执行 v.resize(3); 后,容器只保留前 3 个元素,直接删掉末尾多出来的两个 99,v.size() 随之变成 3。

释放多余容量:shrink_to_fit()

当调用 clear() 清空元素,或者删除大量元素之后,vector 的实际元素数量 size() 会变小,但它底层已经申请好的容量 capacity() 并不会自动缩减回去。

如果我们在清空或缩减容器之后,确定不再需要那么大的内存了,可以调用 C++11 提供的 shrink_to_fit() 函数,请求容器释放多余的空闲容量。其中 shrink_to_fit 就是 “缩减到恰好合适” 的意思,平时较少用到,这里简单了解一下即可。

语法:

容器名.shrink_to_fit();

说明:

调用该函数后,主流编译器通常会把底层容量 capacity() 缩减到与当前的实际元素数量 size() 一致。

示例 4:释放多余的空闲容量

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v;
    v.reserve(100);
    v.push_back(10);
    v.push_back(20);

    v.clear();            // 清空所有元素
    std::cout << "clear 后 capacity:" << v.capacity() << '\n';

    v.shrink_to_fit();    // 释放多余空闲容量
    std::cout << "释放后 capacity:" << v.capacity() << '\n';
    return 0;
}

运行结果如下。

clear 后 capacity:100
释放后 capacity:0

分析:

执行 v.clear(); 之后,虽然元素已经清空,但之前预留的 100 个容量依然保留着。紧接着我们调用 v.shrink_to_fit();,容器就会把这部分闲置内存释放归还,capacity() 也随之降回了 0。

reserve() 与 resize() 的区别

reserve() 和 resize() 名字长得很像,小伙伴们在实际开发中只需要看自己想不想马上创建出 “真实元素”:

reserve(n):只准备空位、不创建元素。调用后只把底层 capacity() 扩大到至少 n,实际元素数量 size() 不变,后续我们仍需通过 push_back() 往里添加数据。

resize(n):直接改变真实元素个数。调用后 size() 直接变成 n,如果比原来大就会自动补齐默认元素,我们可以直接使用下标读写。

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号