C++ vector:reserve 与 resize 的区别及常见误区

黎 浩然/ 7 10 月, 2026/ C/C++/ 0 comments

reserve(n) 提前准备容量,不改变元素数量;resize(n) 把元素数量调整为 n。因此,调用 reserve(100) 后,并不能直接通过 v[99] 写入第 100 个元素。判断下标是否合法,要看 size(),不是 capacity()。

文章目录
  1. 先区分 size 和 capacity
  2. reserve 与 resize 对照
  3. 一段可以直接运行的 C++17 示例
  4. 常见错误:有容量,就能按下标写?
  5. 扩容后,旧引用为什么可能失效?
  6. 什么时候应该提前 reserve?
  7. 三个快速判断
  8. 参考资料

先区分 size 和 capacity

把 vector 理解为一排连续存储的元素:size() 表示当前元素数量;capacity() 表示无需重新分配存储时,可以容纳的元素数量。容量中尚未使用的部分,不是可以随意按下标访问的 vector 元素。

对于普通 std::vector<T>,合法元素下标满足:

0 ≤ i < size() ≤ capacity()

C++ vector 示意图:reserve 只增加可用容量,resize 增加实际元素数量
原创示意图。灰色方格表示未使用容量。为方便比较,图中假设 reserve(6) 后容量为 6;接口只保证容量至少为 6,实际可能更大。图中初始行也假定已有 6 个位置的容量。

reserve 与 resize 对照

操作 元素数量 size 容量 capacity 常见用途
reserve(n) 不变 n 超过现有容量时扩容,结果至少为 n 知道预计数量,随后逐个追加
resize(n) 变为 n 容量不足时可能增加;缩小元素数量不缩小容量 需要先得到 n 个元素,再按下标赋值
resize(n, value) 变为 n 同上 新增元素使用指定值

reserve 的参数也不是“追加多少容量”。如果 size 是 20,希望最终放入 100 个元素,应考虑 reserve(100),而不是把它误解为“再加 100”。

一段可以直接运行的 C++17 示例

#include <cassert>
#include <iostream>
#include <stdexcept>
#include <vector>
int main() {
    std::vector<int> v{10, 20};
    v.reserve(6);
    assert(v.size() == 2 && v.capacity() >= 6);
    std::cout << "reserve: size=" << v.size()
              << ", capacity>=6=" << (v.capacity() >= 6) << '\n';
    try { (void)v.at(2); }
    catch (const std::out_of_range&) { std::cout << "at(2): out_of_range\n"; }
    v.resize(4);
    assert((v == std::vector<int>{10, 20, 0, 0}));
    std::cout << "resize(4):";
    for (int x : v) std::cout << ' ' << x;
    std::cout << '\n';
    const auto capacity = v.capacity();
    v.resize(1);
    assert(v.size() == 1 && v.capacity() == capacity);
    std::cout << "resize(1): size=" << v.size()
              << ", capacity unchanged=" << (v.capacity() == capacity) << '\n';
    std::vector<int> empty;
    empty.reserve(0);
    empty.resize(0);
    assert(empty.empty());
}

编译运行:

clang++ -std=c++17 -Wall -Wextra -pedantic vector-reserve-resize.cpp -o demo
./demo

上面示例已实际编译执行,输出为:

reserve: size=2, capacity>=6=1
at(2): out_of_range
resize(4): 10 20 0 0
resize(1): size=1, capacity unchanged=1

这里没有打印某个固定 capacity 数值,而是验证 capacity() >= 6,因为不同标准库的具体分配策略可以不同。布尔值 1 表示对应条件成立。

resize(4) 在默认分配器下为 vector<int> 新增两个值为 0 的元素。对于类类型,需要看该类型的默认构造行为,不能推广成“所有类型新增后都为零”。resize(4, 7) 则用 7 初始化新增元素,已有的 10、20 不受影响。

常见错误:有容量,就能按下标写?

std::vector<int> v;
v.reserve(100);
// v[0] = 42;  // 错误:size 仍为 0,C++17 下越界访问是未定义行为。
v.push_back(42);  // 正确:先追加元素。

需要按下标填充固定数量的结果时,应先构造出元素:

std::vector<int> result(100);
result[0] = 42;  // 此时下标 0 合法。

at(i) 会检查下标,越界时抛出 std::out_of_range;C++17 的 operator[] 不提供这种异常检查。程序碰巧没崩溃,不代表访问有效。

扩容后,旧引用为什么可能失效?

扩容可能把已有元素转移到一块新的存储区域。于是,指向旧元素位置的指针、引用和迭代器就不能继续使用。这个机制与具体扩大几倍无关。

std::vector<int> v{10, 20};
int& first = v[0];
v.reserve(v.capacity() + 1);  // 假设未超过 max_size() 且分配成功,必然重新分配。
// 不再使用 first;应重新获取 v[0]。

若 reserve(n) 的 n 不超过当前 capacity,则不会重新分配,原有元素引用保持有效。若 resize 删除了尾部元素,指向这些被删除元素的引用当然也不再有效;不能把“没有扩容”理解为“所有引用永远有效”。

已有文章《C++ 函数参数按值、左值引用、右值引用传递》讨论了参数和对象之间的关系。这里还需要注意容器对对象生命周期与存储位置的管理。

什么时候应该提前 reserve?

若能合理估计最终规模,可以在追加前一次性预留。例如读取已知条数的记录、生成数量确定的结果。需要注意总容量与新增数量的区别:向已有 k 个元素的 vector 追加 m 个元素,需要考虑 k + m,并检查加法溢出和容器上限。

不要在每次 push_back 前都调用 reserve(size() + 1)。这种写法可能打乱标准库的增长策略,导致频繁重新分配。也不要认为标准要求 vector 每次扩容一定翻倍。

理解性能时,应区分两件事:发生重新分配的单次追加需要转移已有元素;通常使用 push_back 的连续追加具有摊还常数复杂度。提前 reserve 的价值是减少可避免的重新分配,并不是把所有操作都变成严格常数时间。

三个快速判断

  • 想减少后续追加的扩容:考虑 reserve。
  • 想立刻拥有 n 个可访问的元素:使用 resize 或带大小的构造函数。
  • 想缩小容量:resize 不负责这件事;shrink_to_fit 只是非强制请求,不能保证一定释放存储。

参考资料

以下标准草案链接是持续更新的文档,其中可能出现 C++17 之后加入的声明修饰或接口;本文示例仅使用 C++17 已有接口。

支持

如果这篇文章对你有帮助,欢迎支持本站。

微信支持二维码,点击查看大图
微信
支付宝支持二维码,点击查看大图
支付宝
Buy Me a Coffee,点击支持本站
Buy Me a Coffee

二维码可点击放大。更多支持方式见支持页面。

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*