每周技巧 #3:字符串拼接:`operator+` 与 `StrCat()`
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #3: String Concatenation and operator+ vs. StrCat()。
原文最初作为 TotW #3 发布于 2012 年 5 月 11 日。
更新于 2022 年 11 月 16 日。
用户常常会惊讶地听到评审者说:“不要用字符串拼接运算符,它没那么高效。”string::operator+ 怎么会低效呢?这种东西难道也容易写错吗?
事实证明,这种低效并不是非黑即白的。实践中,下面两段代码的执行时间非常接近:
|
|
但是,下面这两段代码就不是这样了:
|
|
这两种情况为什么不同,可以从拆开 foo + bar + baz 这个表达式开始理解。C++ 中没有三参数运算符重载,因此这个操作必然会调用两次 string::operator+。而在这两次调用之间,它会构造并保存一个临时字符串。所以 std::string foobarbaz = foo + bar + baz 实际上等价于:
|
|
特别要注意,foo 和 bar 的内容必须先复制到一个临时位置,然后才会放入 foobarbaz。(关于 std::move 的更多内容,见 技巧 #77。)
C++11 至少允许第二次拼接不创建新的字符串对象:std::move(temp) + baz 等价于 std::move(temp.append(baz))。不过,临时对象最初分配的缓冲区可能不够容纳最终字符串,此时就需要重新分配内存,并再次复制。因此,在最坏情况下,由 n 次字符串拼接组成的链式表达式需要 O(n) 次重新分配。
更好的做法是使用 absl::StrCat()。这是 absl/strings/str_cat.h 中一个很好用的辅助函数:它会计算所需字符串长度,预留对应大小,然后把所有输入数据拼接到输出中。这是经过良好优化的 O(n) 做法。类似地,对于下面这样的代码:
|
|
请使用 absl::StrAppend(),它会执行类似的优化:
|
|
此外,absl::StrCat() 和 absl::StrAppend() 不只适用于字符串类型。你可以使用 absl::StrCat/absl::StrAppend 转换 int32_t、uint32_t、int64_t、uint64_t、float、double、const char* 和 string_view,例如:
|
|
更多信息见 absl::StrCat() and absl::StrAppend() for String Concatenation。