vector扩容非严格2倍,主流实现用1.5倍或2倍;建议预估大小后调用reserve避免多次重分配;deque无传统扩容,list无扩容但缓存不友好;string类似vector,支持sso优化。

vector 的扩容策略是倍增,但不是严格 2 倍
标准只要求 vector 扩容时保证摊还时间复杂度为 O(1),没规定具体倍数。主流实现(libstdc++、libc++、MSVC)实际采用 1.5 倍或 2 倍增长:GCC 用 1.5 倍(old_capacity + old_capacity / 2),MSVC 用 2 倍。这意味着插入 N 个元素可能触发 ⌈log₁.₅N⌉ 次重分配 —— 看似不多,但若在循环中反复 push_back 而不预估,小数据量下也容易触发多次拷贝。
实操建议:
- 明确知道最终大小?直接调用
reserve(n),它只分配内存不构造对象,后续push_back不会触发扩容 - 不确定上限但有经验范围?比如日志缓冲通常 ≤ 1024 条,
reserve(1024)比默认从 0 开始增长更稳 - 避免在循环内写
v.push_back(x); v.shrink_to_fit();—— 这会主动放弃预留空间,下次插入立刻又分配
deque 的“扩容”本质是增加分段控制块,不挪动已有元素
deque 不是连续内存,底层是固定大小的缓冲区数组(如每块 512 字节)+ 控制块(记录各缓冲区地址)。push_front 或 push_back 时,若首/尾缓冲区满,就新增一个缓冲区并更新控制块 —— 已有元素完全不动,无拷贝开销。所以它没有传统意义的“扩容重分配”,也没有 reserve 接口。
注意点:
立即学习“C++免费学习笔记(深入)”;
- 随机访问仍是 O(1),但常数比
vector大(需两次指针解引用) - 迭代器失效规则宽松:仅在对应端插入时,另一端的迭代器仍有效;但中间插入(
insert)会失效所有迭代器 - 若你真正需要的是两端高效插入+稳定迭代器,
deque比 “手动管理 vector + 两头预留” 更可靠
list 和 forward_list 根本不扩容,但代价是缓存不友好
list 是双向链表,forward_list 是单向链表,每次 push_back 或 push_front 都独立 new 一个节点。没有“容量”概念,自然无扩容问题。但这带来两个硬伤:
- 每次插入都要堆分配,小对象(如
int)的分配开销远超数据本身 - 节点内存不连续,遍历时 CPU 缓存命中率低,实测 10 万次顺序访问,
vector<int></int>通常比list<int></int>快 3–5 倍
除非你频繁在任意位置插入/删除且对遍历性能不敏感,否则别用链表替代 vector 来“避免扩容” —— 这是用更重的代价解决伪问题。
string 的扩容行为和 vector 几乎一致,但 C++11 后有 Small String Optimization
string 底层也是动态数组,扩容策略与 vector<char></char> 相同(如 libstdc++ 也用 1.5 倍)。但现代实现普遍启用 SSO:小字符串(常见长度 ≤ 15 或 22 字节)直接存在对象内部,不走堆分配。这意味着短字符串的 += 操作可能完全不触发扩容。
关键提醒:
- SSO 是实现细节,不可依赖具体阈值;
sizeof(string)可查内部缓冲大小(如 GCC 8 中通常是 32 字节,含 size/capacity 字段) - 一旦超出 SSO 容量,它立刻退化为普通动态分配,后续增长规则和
vector完全一致 - 如果处理的是已知长字符串(如读取文件内容),
reserve()依然有效,且能避免 SSO 切换时的额外判断开销
reserve 的调用点离首次插入越近越好,晚于第一次 push_back 就可能已经发生过一次不必要的分配。










