HOWTO · C++

在 C++ 中声明多行字符串

本文演示了如何在 C++ 中声明一个多行字符串。

本页内容

当你需要处理较长的文本,或希望在源代码中保留排版格式时,多行字符串就很重要。C++ 没有专门的多行字符串类型,但提供了几种构建方式:相邻字符串字面量、原始字符串字面量、反斜杠-换行拼接以及预处理器字符串化。这些技术的主要区别在于,源代码中的换行是否会变成字符串值中的换行。

本指南将通过可运行的示例逐一介绍每种方法,并在最后进行比较,帮助你为代码选择合适的方案。

使用相邻字符串字面量构建多行字符串

当编译器看到两个或多个双引号字符串字面量彼此相邻、仅由空白字符分隔时,会在编译时把它们拼接成单个值。把一个字面量拆到多行源代码中纯粹是排版选择——除非你显式写出 \n,否则不会在值中添加换行;如果单词之间应保持分隔,也必须显式包含空格。

运行以下示例;代码中的字符串与输出按原样保留英文:

本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>
#include <string>

int main() {
  std::string message =
      "I love"
      " getting my doubts solved"
      " by the help of Delft Stack Tutorials.";

  std::cout << message << std::endl;
  return 0;
}
I love getting my doubts solved by the help of Delft Stack Tutorials.

当目标是 const char *——指向该字面量的只读指针——而不是 std::string 对象时,相邻字面量拼接的工作方式完全相同;示例中的字符串与输出仍为英文:

#include <cstdio>

int main() {
  const char *lines =
      "Line 1 "
      "Line 2 "
      "Line 3";

  std::printf("%s\n", lines);
  return 0;
}
Line 1 Line 2 Line 3

如果文本会被修改或传给能够处理字符串的 API,请选择 std::string;对于只用于打印或检查的静态文本,const char * 更合适。

使用原始字符串字面量保留换行

C++11 引入了原始字符串字面量,使物理换行能够直接存在于字面量内部。R"( 与 )" 之间的所有内容——包括换行、缩进、引号和反斜杠——都会原样成为值的一部分,因此不需要转义序列;示例中的字符串与输出仍为英文:

#include <iostream>
#include <string>

int main() {
  std::string page = R"(I love
getting my doubts solved
by the help of Delft Stack Tutorials.)";

  std::cout << page << std::endl;
  return 0;
}
I love
getting my doubts solved
by the help of Delft Stack Tutorials.

由于内容会被逐字复制,请注意两个细节:紧跟在起始 R"( 之后输入的换行会被存入值中,续行前的缩进也会被保留。如果文本中会出现 )" 序列,可以选择自定义分隔符,例如 R"tag(...)tag"——此时结束标记必须带有该 tag,因此文本中普通的 )" 不会造成影响。

使用反斜杠-换行拼接延续字符串字面量

如果反斜杠是源代码行的最后一个字符,它会在编译前把该行与下一行拼接起来。在字符串字面量内部,这能让字面量跨越多行源代码,但被拼接的换行会被移除——它不会在值中产生换行。这正是它与 \n 转义的关键区别,后者确实会添加换行;示例中的字符串与输出仍为英文:

#include <cstdio>

int main() {
  const char *text =
      "First line\n"
      "Second line\n\
      Third line\n";

  std::printf("%s", text);
  return 0;
}
First line
Second line
      Third line

输出展示了两种机制的作用:每个 \n 都会写入一个换行,而反斜杠拼接只会连接源代码行——续行开头的前导空格会成为值中的真实字符。这种对空白的敏感性是主要隐患:为了可读性输入的缩进最终会进入字符串,而反斜杠后若有任何字符,即使只是一个空格,也会使拼接失效。只有当你能完全控制周边排版时才应使用拼接。

了解宏字符串化的局限

可变参数宏可以使用字符串化运算符把参数转换为字符串字面量,例如 #define MULTILINE_STRING(...) #__VA_ARGS__。传入分布在多行上的文本会得到一个带引号的字面量——但预处理器会把每一串空白字符(包括换行)规范化为一个空格;示例中的字符串与输出仍为英文:

#include <iostream>
#include <string>

#define MULTILINE_STRING(...) #__VA_ARGS__

int main() {
  std::string message = MULTILINE_STRING(
      I love getting my doubts solved by the help of Delft Stack Tutorials.);

  std::cout << message << std::endl;
  return 0;
}
I love getting my doubts solved by the help of Delft Stack Tutorials.

这使宏字符串化只是一种受限选项,而不是通用的多行字符串工具。它无法保留换行;参数文本会按预处理记号处理,而不是当作字符串内容处理(因此散落的标点可能产生出乎意料的结果);基于宏的代码也更难阅读和调试。只有当预处理器驱动的代码确实需要把记号转换为文本时,才应使用它。

选择合适的多行字符串技术

  • 原始字符串字面量:当换行、缩进或 \、" 等字符应原样保留在值中时,这是默认选择——非常适合嵌入文本块、JSON、SQL 或正则表达式。
  • 相邻字符串字面量:在保持源代码行较短的同时精确控制值;每个空格和 \n 都是显式的,因此不会混入任何意外内容。
  • 反斜杠-换行拼接:较脆弱且对缩进敏感;主要存在于遗留代码和宏定义中,在宏定义里仍然需要用它让定义跨越多行。
  • 宏字符串化:只适合确实需要把预处理记号转换为文本的、以预处理器为主的代码。

对于确实跨越多行的值,请使用原始字符串字面量。对于为了排版而在源代码中拆成多行的长单行值,相邻字面量最清晰——并且它与 std::string 或 const char * 都能自然搭配。