为什么"\?"是C/C++中的转义序列?
为什么"\?"是C/C++中的转义序列?
C/C++中有四个特殊的非字母字符需要进行转义:单引号\\\'
,双引号\\\"
,反斜线\\\\
和问号\\?
。显然是因为它们具有特殊的含义。单引号\'
用于单个字符,双引号\"
用于字符串字面量,反斜线\\
用于转义序列,但为什么?
也是其中之一呢?\n我今天在一本教材中读到了转义序列的表格,意识到我从来没有转义过?
,也从未遇到过问题。为了确认,我在GCC下进行了测试:\n
#includeint main(void) { printf("question mark ? and escaped \?\n"); return 0; }
\n以及C++版本:\n
#includeint main(void) { std::cout << "question mark ? and escaped \?" << std::endl; return 0; }
\n两个程序的输出结果都是:question mark ? and escaped ?
\n所以我有两个问题:\n
- \n
- 为什么
\\?
是转义序列字符之一? - 为什么非转义的
?
也能正常工作?甚至没有警告。
\n
\n
\n
\n更有趣的事实是,在其他一些语言中,转义的\\?
也可以像?
一样使用。我在Lua/Ruby中进行了测试,虽然我没有找到相关的文档,但它也是正确的。
为什么在C/C++中“\?”是一个转义序列字符?
因为它是特殊的。这个问题引出了三字符序列的三字符序列,而C/C++预处理器会用对应的单个字符替换以下这些三字符序列。(C11 §5.2.1.1和C++11 §2.3)
三字符序列: ??( ??) ??< ??> ??= ??/ ??' ??! ??- 替换: [ ] { } # \ ^ | ~
三字符序列现在几乎没有什么用处了,主要用于混淆目的。一些例子可以在IOCCC中看到。
GCC默认不支持三字符序列,如果代码中有三字符序列,将会发出警告,除非启用了-trigraphs
选项。-trigraphs
选项下,下面这个例子中的第二个\?
是有用的:
printf("\?\?!\n");
如果不对?
进行转义,则输出将会是|
。
关于三字符序列的更多信息,请参考旧代码中的神秘行“??!??!”。
为什么非转义的?
可以正常工作,甚至没有警告?
因为根据标准,?
(和双引号"
)可以直接表示自身:
C11 §6.4.4.4 字符常量 第4节 双引号"和问号?可以表示自身,也可以通过转义序列\"和\?表示,但单引号'和反斜杠\必须通过转义序列\'和\\表示。
在C++中也是类似的:
C++11 §2.13.2 字符字面值 第3节 某些非图形字符,包括单引号’、双引号"、问号?和反斜杠\,可以根据表格6进行表示。双引号"和问号?可以直接表示自身,也可以通过转义序列\"和\?表示,但单引号’和反斜杠\必须通过转义序列\'和\\表示。如果反斜杠后面的字符不是指定的字符,行为是未定义的。转义序列指定一个单个字符。
这一行:例如,gcc默认不支持三字符序列,除非启用了-trigraphs选项。在这种选项下,\?在某些情况下是有用的:
是误导性的。它似乎在说,即使你不使用-trigraphs
选项,gcc也会将??!
解释为|
。
许多人使用-std=c++98
或-std=c99
等来禁用gnu语言变种,因此启用三字符序列支持非常常见。
如何表示双引号"
本身?不进行转义的情况下,如何在字符串中输入双引号?
标准对此并不清楚。我认为"
不能在字符串字面值中使用,但可以在单引号中使用'"'
或'\"'
,但'''
是无效的,必须使用'\''
。
标准是明确的:一个字符常量可以是转义序列,也可以是“源字符集的任何成员,除了单引号'、反斜杠\或换行符字符”。一个字符串字面值的字符可以是转义序列,也可以是“源字符集的任何成员,除了双引号"、反斜杠\或换行符字符”。
我不认为三字符序列只是“几乎没用了”。据我了解,在生产代码中寻找三字符序列的任何用途都未能找到任何有意义的用途,除了编译器测试套件、演示三字符序列的工作原理等。这听起来更准确(也许不太客气)的说法应该是“一项从一开始就从未真正有用过的功能,不应该存在于语言中”。
感谢指出标准,花了我几个小时才弄清楚为什么std::string "\'"(反斜杠后面跟着单引号)会返回单引号而不是反斜杠...我只需要转义std::string "\\\'"即可。
后来更新:值得一提的是,C++17最终完全删除了三字符序列(参见C.4.1 5.2;此前和此后都没有提到过它们),除了删除,它们再也没有在任何地方被提及过。