为什么C/C++中字符串字面量声明必须是单行的?

20

在C++中,为什么不允许使用类似以下的多行字符串字面量?

string script =
"
      Some
   Formatted
 String Literal
";

我知道可以通过在每个换行符前面放置反斜杠来创建多行字符串字面量。我正在编写一种类似于C的编程语言,并希望允许轻松创建多行字符串(如上面的示例)。

有没有任何技术原因避免使用这种类型的字符串字面量?否则,我将不得不使用类似Python的三引号字符串字面量(我不想这样做):

string script =
"""
      Some
   Formatted
 String Literal
""";

为什么C/C++字符串字面量的声明必须是单行?


3
维基百科有关于这个主题的条目,涵盖了多种编程语言:http://en.wikipedia.org/wiki/String_literal - pcent
1
C语言没有string类型。你是不是想说char* - Daniel Pryden
其实我在谈论C/C++,但还是谢谢你的纠正! - Rizo
6
我说过那是吗?我只是在提及这两种语言。 - Rizo
2
@Rizo,两种语言都没有特别的字符串数据类型,尤其是C语言。因此,在一个语言中缺少另一个语言具有的功能时,引用这两种语言是不合逻辑的。 - Natalie Adams
我甚至不希望有内置的多行字符串字面量:它们会不断破坏缩进。 - Wolf
10个回答

33

简短的回答是“因为语法禁止多行字符串文字。”我不知道除了历史原因之外是否有其他很好的原因。

当然,有办法绕过这个限制。你可以使用行连接:

const char* script = "\
      Some\n\
   Formatted\n\
 String Literal\n\
";

如果\出现在行末,换行符将在预处理期间被删除。

或者,你可以使用字符串字面量拼接:

const char* script = 
"      Some\n"
"   Formatted\n"
" String Literal\n";

相邻的字符串字面值在预处理期间被连接起来,因此它们最终会在编译时成为一个单一的字符串字面值。

使用任何一种技术,字符串字面量最终都会变成如同下面所写的形式:

const char* script = "      Some\n   Formatted\n  String Literal\n";

9
始终使用第二种形式;这可以解决在某些版本的Microsoft编译器中处理长文字时出现的错误。 - Donal Fellows
1
我认为他更想知道的是技术动机。他并不关心绕过障碍的方法,而是写自己的语言。在C中如何解决这个问题并不重要。 - NoMoreZealots
1
@NoMoreZealots:嗯,现在OP已经表明了。很多人提问时并不知道有解决方法,所以建议解决方法通常是有帮助的。话虽如此,在C语言中允许字符串字面量跨越多行并不是一个特别大的改变;就我个人而言,我无法想象这样的改变会破坏任何现有的符合标准的代码。 - James McNellis
@Donal Fellows:或者干脆不要使用微软编译器 ; ) 。 - Grant Paul
1
@chpwn:并非每个人都有选择的余地,而且至少从历史上看,微软编译器生成的代码比gcc更好(即更快),这对于生产构建非常有吸引力。由于两种编写长字面值的方式都差不多同样可读,所以没有理由选择会导致问题的版本。 - Donal Fellows

17
必须考虑到 C 语言并不是为“应用”编程语言而编写的,而是系统编程语言。可以毫不夸张地说,C 语言专门设计用来重写 Unix。在这种情况下,没有 EMACS 或 VIM,用户界面都是串行终端。在没有多行文本编辑器的系统上,多行字符串声明似乎有点无意义。此外,在那个特定时间点想要编写操作系统的人不会将字符串操作作为主要问题。UNIX 脚本工具集,例如 AWK 和 SED 等传统 UNIX 脚本工具集,证明了他们不使用 C 语言来进行重要的字符串操作。
其他注意事项:在 70 年代早期(C 语言编写时),提交程序时使用穿孔卡很常见,第二天再回来取程序。将带多行字符串文字的程序编译要花费更多的处理时间吗?实际上不是。对于编译器来说,它可能是更少的工作量。但是大多数情况下您还是需要第二天回来拿程序。但是填写穿孔卡的人不会在程序中放入不必要的大量文本。
在现代环境中,除了设计者的喜好之外,可能没有理由不包括多行字符串文字。从语法角度来看,这可能更简单,因为在解析字符串文字时不必考虑换行符。

1
实际上,Unix的1969年版本是Unix家族树中AT&T / Bell Labs分支的原始版本(当时Kernighan和Ritchie的雇主)。该操作系统在72年左右从ASM移植到C语言。 AT&T在70年代末公开了源代码,这使它成为操作系统课程的绝佳候选,因此BSD作为Unix的教育版本诞生了。 - NoMoreZealots

7

除了现有的答案之外,你可以使用C++11的原始字符串字面值来解决这个问题,例如:

#include <iostream>
#include <string>

int main() {
   std::string str = R"(a
b)";
   std::cout << str;
}

/* Output:
a
b
*/

现场演示。


[n3290: 2.14.5/4]: [ Note: A source-file new-line in a raw string literal results in a new-line in the resulting execution string-literal. Assuming no whitespace at the beginning of lines in the following example, the assert will succeed:

const char *p = R"(a\
b
c)";
assert(std::strcmp(p, "a\\\nb\nc") == 0);

—end note ]

虽然不是规范性的,但这个注释和随后在[n3290: 2.14.5/5]中的示例是为了补充语法中的指示,即产生式r-char-sequence可以包含换行符(而用于普通字符串字面量的产生式s-char-sequence则不能)。


n3290 不是公开可用的。C++11 的最新草案是 n3337,请参见此处。我会写 lex.string#4 而不是 [n3290: 2.14.5/4] - Dr. Gut

6

其他人已经提到了一些出色的解决方法,我只是想解释一下原因。

原因很简单,C语言是在处理能力非常有限的时代创造出来的,编译器必须尽可能地简单和快速。现在,如果更新C语言(我在看着你,C1X),完全可以做到你想要的。然而,这种情况不太可能发生。主要是出于历史原因;这样的改变可能需要对编译器进行大量重写,因此可能会被拒绝。


1
请注意,C++0x 已经完成了这个功能(参见:原始字面量)。 - Billy ONeal
1
我只是在询问原因,而不是“优秀的解决方案”。 谢谢! 附言:我将使用多行字符串。 - Rizo
1
如果您使用基于行的编辑(例如“ed”或电传打字机),那么如果每个输入行都是解析器的单个语法元素,则会更容易。 - Martin Beckett
1
@Martin Beckett:首先,C语言从来没有单行语句;语句总是以分号结束。其次,即使C想迎合基于行的编辑风格,这也无法解释为什么他们不同时迎合多行编辑风格。 - Randolpho
1
@Tyler McHenry:但是当前年份的倒数第二位十六进制数字不是0,而是D... - caf
显示剩余6条评论

3

C预处理器以逐行方式工作,但使用词法标记。这意味着预处理器理解"foo"是一个标记。然而,如果C允许多行文字,预处理器就会遇到麻烦。考虑以下情况:

"foo
#ifdef BAR
bar
#endif
baz"

预处理器无法干预标记内部的内容-但它是逐行操作的。那么它应该如何处理这种情况呢?简单的解决方案是完全禁止多行字符串。

2
我真的不认为这会是一个问题。在预处理指令可以被评估之前,源代码必须被标记化。如果字符串文字允许包含换行符,那么这将只是一个单一的字符串文字标记。换行符在预处理期间是有意义的,但只在某些上下文中。 - James McNellis
预处理器不是逐行操作的,也不必干涉“令牌内部”:有反斜杠转义换行符。您的“包含#ifdef的字符串”示例将完全有效,其中包含反斜杠转义换行符。实际上,反斜杠转义换行符甚至可以发生在标识符内部。 - chisophugis

2
实际上,你可以这样分解:
string script =
"\n"
"      Some\n"
"   Formatted\n"
" String Literal\n";

编译器会将相邻的字符串字面值连接起来。

那与问题无关。 - Rizo
@Rizo:实际上,是有的。你在问为什么C语言没有某个功能。解释C语言以略微不同的方式处理事情是相关的。毕竟,如果没有多行字符串的方法,答案会大不相同。 - David Thornley
@David Thornley:然而,那不是我想问的。我想知道是否有任何理由不使用多行字符串。 - Rizo
@Rizo:如果一种语言允许某种方式,为什么还要允许另一种方式呢?Perl 的哲学是“做一件事有多种方法”,但并不是所有人都接受这种观点,很多语言设计者试图限制功能和能力的数量。 - David Thornley
那不是重点。我的语言必须支持多行字符串,因为它的设计是面向文本格式化的。这是一个必要的特性,而不是语法糖。这就是为什么任何类C的替代方案都与问题无关。我需要一种明确的方法来定义文本块--使用三重引号字符串(如Python所做的)或标准单引号字符串。我只想知道第二种方法是否有任何限制。顺便说一下,我找到的唯一限制是必须在字符串内使用反斜杠来写入引号字符;这在三重引号格式中不会发生。 - Rizo

1

字符串可以跨越多行,但每行都必须单独引用:

string script =
    "                \n"
    "       Some     \n"
    "    Formatted   \n"
    " String Literal ";

1
我正在编写一门类似于C的编程语言,并希望能够让用户轻松地编写多行字符串(就像上面的例子一样)。
实际上,没有任何理由阻止你创建一种允许多行字符串的编程语言。例如,Vedit Macro Language(一种基于C的脚本语言,专为VEDIT文本编辑器设计)就允许使用多行字符串,例如:
Reg_Set(1,"
      Some
   Formatted
 String Literal
")

如何定义语言语法取决于您自己。


0

你也可以这样做:

string useMultiple =  "this" 
                      "is "
                      "a string in C."; 

将一个字面量放在另一个字面量后面,不要使用任何特殊字符。


1
请注意,这不具有OP正在寻找的换行符。 - Billy ONeal

0

字面声明不必单行书写。

GPUImage 内联多行着色器代码。请查看其 SHADER_STRING 宏。


在底层,它只是将字符串字面量连接起来: #define STRINGIZE(x) #x #define STRINGIZE2(x) STRINGIZE(x) #define SHADER_STRING(text) @ STRINGIZE2(text)。SHADER_STRING 宏内的代码等同于 @"..." "..." "..." ...。(https://github.com/BradLarson/GPUImage/blob/master/framework/Source/GPUImageFilter.h) - Rizo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接