每周技巧 #64:原始字符串字面量

本节阅读量:

本文翻译自 Abseil 官网的 Tip of the Week #64: Raw String Literals

原文最初作为 totw/64 发布于 2013 年 12 月 9 日。

作者:Titus Winters

更新于 2017 年 10 月 23 日。

"(?:\"(?:\\\\\"|[^\"])*\"|'(?:\\\\'|[^'])*')"; – 一只猫从键盘上走过……或者也许是狐狸叫声……不,其实只是现实 C++ 代码里发现的一段高度转义的正则表达式。

你很可能曾经因为转义问题,在 C++ 中很难让正则表达式被正确理解。同样,在单元测试里嵌入 Protobuf 文本格式或 JSON 数据时,为了保留引号和换行,你大概也烦过。当你不得不使用大量转义(更糟时,是多层转义)时,代码清晰度会急剧下降。

幸运的是,C++11 有一个新特性可以消除这种转义需求:原始字符串字面量。

原始字符串字面量格式

原始字符串字面量有如下特殊语法:

1
R"tag(whatever you want to say)tag"

tag 是一个最多 16 个字符的序列(空 tag 也可以,而且很常见)。"tag( 之后、随后第一次出现的 )tag" 之前的字符,会被按字面值用作字符串字面量的内容。tag 可以包含除括号、反斜杠和空白字符之外的任何字符。

看看区别:

1
2
3
4
5
const char concert_17_raw[] =
    "id: 17\n"
    "artist: \"Beyonce\"\n"
    "date: \"Wed Oct 10 12:39:54 EDT 2012\"\n"
    "price_usd: 200\n";

对比:

1
2
3
4
5
const char concert_17_raw[] = R"(
    id: 17
    artist: "Beyonce"
    date: "Wed Oct 10 12:39:54 EDT 2012"
    price_usd: 200)";

特殊情况

注意,缩进规则和原始字符串字面量可以包含换行这一事实结合起来,会让你在如何缩进原始字符串块的第一行上陷入尴尬选择。因为文本 protobuf 会忽略空白,在这种情况下可以通过添加一个前导换行(解析器会忽略)来避免这个问题,但并不是所有原始字符串用途都这么宽容。

当字符串中恰好出现序列 )",从而不能把它当作结束分隔符时,非空 tag 很有用:

1
std::string my_string = R"foo(This contains quoted parens "()")foo";

结论

对我们大多数人来说,原始字符串字面量当然不是每天都会用到的工具,但有些时候,善用这个新语言特性会提升可读性。所以下次当你挠头思考到底需要两个 \\ 还是四个时,试试原始字符串字面量。即使正则表达式仍然很难,你的读者也会感谢你:

1
R"regexp((?:"(?:\\"|[^"])*"|'(?:\\'|[^'])*'))regexp";

每周技巧 #61:默认成员初始化器

上一节

每周技巧 #65:把东西放到该放的位置

下一节