C++ vector:reserve 与 resize 的区别及常见误区
reserve(n) 提前准备容量,不改变元素数量;resize(n) 把元素数量调整为 n。因此,调用 reserve(100) 后,并不能直接通过 v[99] 写入第 100 个元素。判断下标是否合法,要看 size(),不是 capacity()。
文章目录
先区分 size 和 capacity
把 vector 理解为一排连续存储的元素:size() 表示当前元素数量;capacity() 表示无需重新分配存储时,可以容纳的元素数量。容量中尚未使用的部分,不是可以随意按下标访问的 vector 元素。
对于普通 std::vector<T>,合法元素下标满足:
0 ≤ i < size() ≤ capacity()

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 已有接口。
- C++ 标准工作草案:[vector.capacity],核对容量、reserve 和 resize 的规则。
- C++ 标准工作草案:[vector.modifiers],核对追加、重新分配和引用失效规则。
支持
如果这篇文章对你有帮助,欢迎支持本站。
二维码可点击放大。更多支持方式见支持页面。


