 是 4 还是 1?C语言里单引号和双引号)
参考参考Andrew Koenig《C 陷阱与缺陷第二版》1.5节目录书上结论sizeof混用——出现错误巨坑——printf(\n)yes 和 yes译注书上结论1.5 节开头就把结论摆出来了C 语言中的单引号与双引号含义迥异在某些情况下如果把两者弄混编译器并不会检测报错从而在运行时产生难以预料的结果。然后给了两句定义用单引号引起的一个字符实际上代表一个整数整数值对应于该字符在编译器采用的字符集中的序列值。因此对于采用 ASCII 字符集的编译器而言a的含义与 0141八进制或者 97十进制严格一致。用双引号引起的字符串代表的却是一个指向无名数组起始字符的指针该数组被双引号之间的字符以及一个额外的二进制值为零的字符\0初始化。接着给了个等价写法。这两段是同一件事#include stdio.h int main() { printf(Hello world\n); /* 等价于 */ char hello[] { H, e, l, l, o, , w, o, r, l, d, \n, 0 }; printf(hello); return 0; }数组末尾那个0——就是书里说的额外的二进制值为零的字符。字符串字面量比它看起来的长度多一个字节。也就是单引号包的是值双引号包的是地址。sizeofcpp#include stdio.h int main() { printf(sizeof(a) %u\n, (unsigned)sizeof(a)); printf(sizeof(\a\) %u\n, (unsigned)sizeof(a)); printf(sizeof(char) %u\n, (unsigned)sizeof(char)); printf(sizeof(int) %u\n, (unsigned)sizeof(int)); printf(sizeof(yes) %u\n, (unsigned)sizeof(yes)); printf(sizeof(\yes\) %u\n, (unsigned)sizeof(yes)); return 0; }c#include stdio.h int main() { printf(sizeof(a) %u\n, (unsigned)sizeof(a)); printf(sizeof(\a\) %u\n, (unsigned)sizeof(a)); printf(sizeof(char) %u\n, (unsigned)sizeof(char)); printf(sizeof(int) %u\n, (unsigned)sizeof(int)); printf(sizeof(yes) %u\n, (unsigned)sizeof(yes)); printf(sizeof(\yes\) %u\n, (unsigned)sizeof(yes)); return 0; }有趣的是两种语言下运行结果并不完全相同c:sizeof(a) 4 sizeof(a) 2 sizeof(char) 1 sizeof(int) 4 sizeof(yes) 4 sizeof(yes) 4cpp:sizeof(a) 1 sizeof(a) 2 sizeof(char) 1 sizeof(int) 4 sizeof(yes) 4 sizeof(yes) 4sizeof(a) 2好理解一个a加一个\0。sizeof(yes) 4 , 同理sizeof(a) 4就怪了。它只说明一件事C 里字符常量的类型是int不是char。书里那句实际上代表一个整数是字面意思不是修辞。那.cpp为什么给 1因为 C 把这条规则改了——C 里字符常量的类型就是char。是两种语言的规则不同。混用——出现错误书里接着往下推因为用单引号括起的一个字符代表一个整数而用双引号括起的一个字符代表一个指针如果两者混用那么编译器的类型检查功能将会检测到这样的错误。例如char* slash ‘/’在编译时将会生成一条错误消息因为/并不是一个字符指针。页底部那条译注说现在的编译器一般能够检测到在函数调用时混用单引号和双引号的情形——确实检测到了。但检测到和拦住是两件事一边是 warning一边是 error中间隔着一个能正常生成的 exe。巨坑——printf(\n)书里紧接着的那段然而某些 C 编译器对函数参数并不进行类型检查特别是对 printf 函数的参数。因此如果用printf(\n);来代替正确的printf(\n);则会在程序运行的时候产生难以预料的错误而不会给出编译器诊断信息。printf(\n)传给 printf 的第一个参数是整数 10printf 拿它当字符串指针用去读地址0x0A。这里和书上有个小出入VS 2022 是会给诊断的而且是两条C4047 C4024。但诊断归诊断它并没有拒绝编译。再看一个反面例子——同样传字符常量putchar(\n)用/W4编译零警告运行正常putchar(\n)同一个字符常量传给printf是要命的指针传给putchar是完美正确的int。决定的不在单引号在被调函数期待的是值还是地址。yes和yes书里第三段讲多字符常量整型数一般为 16 位或 32 位的存储空间可以容纳多个字符一般为 8 位因此有的 C 编译器允许在一个字符常量以及字符串常量中包括多个字符。也就是说用yes代替yes不会被该编译器检测到。后者即yes的含义是依次包含y、e、s以及空字符\0的 4 个连续内存单元的首地址。前者即yes的含义并没有准确地进行定义但大多数 C 编译器理解为一个整数值由y、e、s所代表的整数值按照特定编译器实现中定义的方式组合得到。因此这两者如果在数值上有什么相似之处也完全是一种巧合而已。#include stdio.h int main() { printf(a %d 0x%08X\n, a, (unsigned)a); printf(ab %d 0x%08X\n, ab, (unsigned)ab); printf(yes %d 0x%08X\n, yes, (unsigned)yes); printf(abcd %d 0x%08X\n, abcd, (unsigned)abcd); return 0; }a 97 0x00000061 ab 24930 0x00006162 yes 7955827 0x00796573 abcd 1633837924 0x61626364规律很干净y、e、s从高位往低位塞像一个左对齐的 int。字符数不影响sizeof永远是 4。顺手试abcde编译器直接拒收。最多吃 4 个字符。译注译注在 Borland C v5.5 和 LCC v3.6 中采取的做法是忽略多余的字符最后的整数值即第一个字符的整数值而在 Visual C 6.0 和 GCC v2.95 中采取的做法是依次用后一个字符覆盖前一个字符最后得到的整数值即最后一个字符的整数值。忽略多余的字符Borland / LCC 这一派的做法是只认第一个字符后面的当没看见。按这个规则yes应该等于y也就是1210x79。这个描述是自洽的——忽略多余的所以留下的一定是第一个。忽略多余的字符这句话是最容易被读错的一句。它字面上像是在说覆盖。但这里的内容和实测有出入。真正在做的是移位累加plaintext读 y → 0x00000079 读 e → 0x00007965 旧值左移 8 位新字符进最低字节 读 s → 0x00796573 再来一次所以这句话该这么读每一个新字符进来都会把前面所有字符往前挤一个字节。覆盖指的是挤位不是丢弃。