Google C++每周贴士 #3: 字符串拼接,operator+对比StrCat()

(原文链接:https://abseil.io/tips/3 译者:[email protected])

每周贴士 #3: 字符串拼接,operator+对比StrCat()

  • 最初发布于2012-05-11
  • 更新于2017-09-18
  • 校订于2018-01-22

当审查代码的的人对程序员说“别用字符串拼接操作符,太低效了”,程序员常常会一脸吃惊:std::string::operator+怎么会低效呢?难道这不是很难出错的方式吗?

事实证明,这种低效没有那么简单明了。在实践中,下面两段代码执行时间相差不多:

std::string foo = LongString1();
std::string bar = LongString2();
std::string foobar = foo + bar;

std::string foo = LongString1();
std::string bar = LongString2();
std::string foobar = absl::StrCat(foo, bar);

然而,同样的结论却不适用于以下两段代码:

std::string foo = LongString1();
std::string bar = LongString2();
std::string baz = LongString3();
string foobar = foo + bar + baz;

std::string foo = LongString1();
std::string bar = LongString2();
std::string baz = LongString3();
std::string foobar = absl::StrCat(foo, bar, baz);

为什么这两个例子中的执行时间比较会有不同?我们可以通过拆解表达式foo + bar + baz得到答案。C++中没有“三个参数”的操作符重载,因此这个表达式必须调用两次string::operator+。在这两次调用过程中,会有一个临时字符串被构造(和存储)。所以std::string foobar = foo + bar + baz等价于:

std::string temp = foo + bar;
std::string foobar = std::move(temp) + baz;

请注意,在foobar被赋值以前,foobar存储的内容必须被复制到一个临时区域中。(更多关于std::move的内容,请参阅Tip of the Week #77: Temporaries, moves, and copies.)

C++11至少允许第二个拼接不需要创建一个新的string对象:std::move(temp) + baz等价于std::move(temp.append(baz))。但是,那个临时字符串(temp,译者注)最初申请的空间可能不足以存下最终的字符串内容,这种情况下会重新申请一次内存(reallocation)(以及复制一次)。因此,在最坏情况下,n次字符串拼接需要重新申请O(n)次内存。

这时最好改用absl::StrCat(),一个不错的辅助函数(定义在absl/strings/str_cat.h中)。它会计算必要的字符串长度,按此预留空间,然后将所有输入数据放进输出的空间中(优化过的O(n)时间复杂度)。类似地,对于如下情况:

foobar += foo + bar + baz;

可以使用absl::StrAppend()(实现了类似优化):

absl::StrAppend(&foobar, foo, bar, baz);

另外,absl::StrCat()absl::StrAppend()还接收非字符串类型:你可以用absl::StrCat/absl::StrAppend转换int32_tuint32_tint64_tuint64_tfloatdoubleconst char*string_view(转换为string,译者注),例如:

std::string foo = absl::StrCat("The year is ", year);

你可能感兴趣的:(C++每周贴士,C++,Tips,of,the,Week)