62 / 80 · C++11 · 约 9 分钟
vector:增长、容量与失效规则
vector 提供连续存储和常数时间下标访问,但容量不等于已构造元素数。理解 reserve、resize 与重分配的区别,才能正确估算追加成本,并避免把旧指针、迭代器和尾后位置带过修改操作。
size 管对象,capacity 管存储
普通 vector<T> 的元素连续存放,适合顺序扫描、随机访问和需要连续数组的接口;vector<bool> 是特殊化,不能照搬普通元素引用与连续存储的结论。size() 是现存元素数,capacity() 是无需重新分配便能容纳的元素数,两者都是常数时间查询。
reserve(n) 不增加元素;仅当 n 大于当前容量时才重新分配,成功后容量至少为 n。resize(n) 改变元素数:缩小时销毁尾部对象,增大时构造新对象。默认分配器下,vector<int> 新增的默认插入元素为零。预留了空间仍不能访问 v[v.size()],因为那里没有可由容器下标访问的元素。
摊还常数不是每次常数
尾部单次追加具有摊还常数复杂度:一段追加序列的总成本可被平均,而触发重分配的某一次仍可能移动或复制全部已有元素。常见实现按某个倍率增长,但标准不保证两倍、起始容量或具体增长序列。中间插入还要移动后缀,不能借助多余容量把它变成常数时间。
知道最终规模时一次 reserve 很有用;每次追加前都 reserve(size()+1) 则可能迫使反复搬迁,破坏通常的增长效率。缩小 resize 不缩容量;shrink_to_fit 只是非强制请求,不是释放到精确大小的承诺。复杂度描述的是随元素数的增长关系,元素自身昂贵的移动也必须计算在实际成本内。
把修改点当作有效性边界
重分配使全部元素指针、引用、迭代器及旧 end() 失效。没有重分配的插入,只保留插入点之前的这些句柄;尾插因此保留已有元素句柄,却仍使旧 end() 失效。删除使删除点及其后的迭代器、引用失效,使用 erase 返回的新位置继续遍历。
示例先预留容量,再验证尾插不会破坏首元素引用;随后只通过容器重新取得位置。不要用解引用旧指针来“检测是否失效”,失效后访问本身就可能是未定义行为。长期保存业务身份时,稳定编号通常比依赖元素地址更容易维护。
容易答错的地方
- reserve 只预留存储,不能把 capacity 范围当成合法下标范围;合法下标始终小于 size。
- 没有重新分配不等于没有失效:中间插入、删除以及旧 end() 都有各自规则。
运行一个例子
最低标准 C++11 · 完整程序 · 下载 .cpp
#include <cassert>
#include <iostream>
#include <vector>
int main() {
std::vector<int> values{10, 20};
values.reserve(4);
assert(values.size() == 2 && values.capacity() >= 4);
int* first = &values.front();
values.push_back(30);
assert(*first == 10);
assert(first == &values.front());
values.resize(5);
assert(values[3] == 0 && values[4] == 0);
values.resize(3);
auto next = values.erase(values.begin() + 1);
assert(next != values.end() && *next == 30);
assert((values == std::vector<int>{10, 30}));
std::cout << values[0] << ' ' << values[1] << '\n';
}
在本地编译
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread stl-vector.cpp -o example && ./example预期结果
10 30
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
从空 vector<int> 开始 reserve(100),随后需要恰好 100 个零。应该追加 100 次,还是可以直接下标赋值?给出最直接的代码。
查看参考答案
直接写 v.resize(100); 即可;默认分配器会把新增 int 初始化为零。此前的 reserve 可以保留,但并非必要。仅 reserve 后 size 仍为零,循环写 v[i] 越界;若数据不是默认值,可使用 resize 后赋值,或 reserve 后逐个 push_back。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。