std::string 直接用 += 拼接慢是因为每次容量不足时需重新分配内存、拷贝旧数据并释放,N次拼接最坏导致O(N)次分配、总复杂度达O(N²)。

为什么 std::string 直接用 += 拼接会慢?
因为每次 += 都可能触发内存重分配:当当前容量不足时,std::string 会申请新内存、拷贝旧内容、释放旧内存。拼接 N 次,最坏情况发生 O(N) 次分配,总时间复杂度接近 O(N²)。
典型场景是循环中累积日志或构建 SQL:
std::string s;<br>for (int i = 0; i < 1000; ++i) {<br> s += "item" + std::to_string(i) + ",";<br>}
这实际可能分配几十次内存——尤其初始容量为 0 或很小的时候。
预分配足够空间:reserve() 是最简单有效的解法
如果你能预估最终长度(哪怕粗略),调用 reserve() 一次就能避免所有中间分配。
立即学习“C++免费学习笔记(深入)”;
- 先遍历一遍数据,累加各段长度(含分隔符),得到总长估算值
- 调用
s.reserve(total_length),之后所有+=都在原内存上追加 - 注意:
reserve()不改变size(),只影响capacity() - 如果预估偏小,仍会触发一次分配;偏大只是浪费一点内存,无性能损失
示例:
std::string s;<br>size_t total_len = 0;<br>for (int i = 0; i < 1000; ++i) {<br> total_len += 4 + std::to_string(i).length() + 1; // "item" + num + ","<br>}<br>s.reserve(total_len);<br>for (int i = 0; i < 1000; ++i) {<br> s += "item";<br> s += std::to_string(i);<br> s += ",";<br>}
用 std::string_view 避免临时字符串构造
上面例子中 "item" + std::to_string(i) + "," 会产生多个临时 std::string 对象,带来额外构造/析构开销。改用 std::string_view + 分段 append() 更轻量:
-
std::string_view是只读视图,不管理内存,零开销 - 用
s.append("item")、s.append(std::to_string(i))、s.append(",")替代+=表达式 -
append()同样受益于提前reserve(),且避免中间字符串对象
注意:std::to_string() 返回的是 std::string,无法避免其构造;但至少不再叠加字符串字面量的隐式转换开销。
极端性能场景:用 std::vector<char></char> 手动管理缓冲区
当拼接逻辑极复杂、或对延迟敏感(如高频网络协议打包),std::string 的异常安全和边界检查反而成为负担。此时可退回到裸缓冲区:
- 用
std::vector<char></char>预分配,用data()获取指针,按需写入 - 手动维护写入位置(
size_t pos = 0),用memcpy或循环赋值填充 - 最后用
std::string(data, size)构造结果(仅一次构造) - 绕过
std::string内部的多次检查与迭代器验证,实测可再提速 10%–20%
代价是失去自动增长、越界保护和语义清晰性,仅建议在 profiler 确认瓶颈在此处时采用。
真正难的不是知道该用 reserve(),而是意识到:很多“字符串拼接”其实根本不需要中间字符串对象——比如日志格式化,直接写进 std::vector<char></char> 或 std::ostringstream 的底层缓冲更干净。而最容易被忽略的,是 std::to_string() 本身在循环里就是个隐藏分配点,它比你想象的更重。


















