关于C语言中的'\?'和%%
一、引语
我们知道C/C++中有四个非字母的字符需要用转义字符来表示:单引号 ' , 双引号 " , 反斜杆 \ 和问号 ?
显然这是因为它们本身有特殊的含义,所以要用转义字符来表示他们自己,单引号'用来表示单个字符,双引号"用来表示字符串,反斜杆\是转义字符的标志,那么问号?呢,我们似乎想不出问号有什么特殊含义,它为什么还要用转义字符来表示它本身呢。
我们通过实验可以发现,不管是否使用转义字符,问号?都能准确地输出问号本身。
#include <stdio.h>
int main() {
printf("question mark ? and escaped \?\n");
return 0;
}
输出结果是:question mark ? and escaped ?
所以问号转义字符\?有什么存在的必要吗,为什么不用转义字符也能正常输出问号,甚至连一个编译警告都没有?
二、trigraph
为什么问号需要转义字符来表示它本身,显然是因为它确实是有特殊含义的。
我们先来了解一个词trigraph,trigraph是三字母词,又叫三连字。这个特性在现在是不受欢迎和极少使用的,但在早年条件艰苦的时候,我们的计算机键盘不像现在可以打出各种字符,所以就用三个字符连起来表示一个打不出的字符,具体如下:
Trigraph: ??( ??) ??< ??> ??= ??/ ??' ??! ??-
Replacement: [ ] { } # \ ^ | ~
举个例子,下面是一个简单的C语言程序:
#include <stdio.h>
int main() {
printf("[]");
return 0;
}
在早年键盘上没有那九个字符的时候,程序员们就这么写:
??=include <stdio.h>
int main() ??<
printf("??(??)");
return 0;
??>
将 trigraph 替换成对应的字符发生在预处理之前,因此 trigraph 可以在源码中的任何位置都可以用,包括字符串内,函数体开头,预处理指令等。
实测中,codeblocks的GCC编译器默认不使用trigraph特性,当你代码中出现了以上三字母词时,会给出一个warning。如果有兴趣实验一下这个trigraph特性,可以在菜单栏中点击Settings -- Compiler -- Compiler settings -- Other compiler options,然后添加编译参数-trigraphs,即可使用trigraph特性。
言归正传,显然问号?是trigraph的一个标志,如我们要输出"??("时最好写成"\?\?(",防止编译器将其误解释为一个trigraph。
三、C语言标准允许问号?代表它本身
为什么不用转义字符\?也能准确输出?本身,因为C标准允许这么做
C11 §6.4.4.4 Character constants Section 4
The double-quote "
and question-mark ?
are representable either by themselves or by the escape sequences \"
and \?
, respectively, but the single-quote '
and the backslash \
shall be represented, respectively, by the escape sequences \'
and \\
.
上面这条标准说明了问号?和双引号"允许代表他们本身的字符,而单引号'和反斜杠\则必须用转义字符来表示他们本身的字符,可能有人会有疑问,在printf中不能单用"来输出双引号"啊,必须要用\",这是因为"在字符串中有特殊含义,不用转义字符就会产生冲突。如果我们输出单个字符putchar('"');则是允许的,不需要写putchar('\"'); 说明"是能代表它本身的。
四、关于printf中要用%%来输出一个%,而不是\%
我们知道对于本身有特殊含义的字符,要用转义字符来表示它本身,也知道%在printf中是格式说明符的一个标志,但其他转义字符都是前面加一个反斜杠\,%前面为什么是一个%。
实际上,%%与转义字符无关,它与printf如何处理格式说明符有关,转义字符对所有字符串有效,并在编译时完成,格式说明符(%)仅在一些函数使用,并在运行时使用。简而言之,在字符串或单个字符中,%都能代表它本身,不需要也不能用%%来表示一个%,只在像printf这样使用%作为格式说明符的函数中规定使用%%来明确表示一个%字符。