第1章 词法“陷阱”

​ 符号指的是程序的一个基本组成单元,其作用相当于句子中的单词。而组成符号的字符序列则不同,同一组字符序列在某个上下文环境中属于一个符号,而在另一种上下文环境可能属于完全不同的另一种符号。

如p->s = “->”,两个“-”的含义完全不同。

前者是符号->的组成部分,后者是字符串的组成部分。

1.1 =不同于==

“=”为赋值运算符,“==”为关系运算符。
我们有时候写代码会无意中将“==”写成“=”,如:

if (x = y)
break;
/*本意是比较x与y是否相等,相等则跳出循环,而写成“=”则成了,将y的值赋值给x,
并且检查该值是否为0。*/
while(c = ' ' || c == '\t' || c == '\n')
c = getc(f);
/* 因为 = 的优先级比关系运算符低,这句实际上是将' ' || c == '\t' || c == '\n' 的值
赋值给c,再判断值是否为0,因为‘ ’不等于0,这就导致赋给C的值恒为1,循环会一直进行下去直到
整个文件结束。*/

1.2 &和|不同于&&和||

​ 按位运算与逻辑运算也有不同,按位运算符会将每位进行运算后,产生一个值,而逻辑运算符是将每一位进行比较,最后得到0或1,如果使用错误会导致很多意想不到的错误或看起来正常的隐患。

1.3 词法分析中的“贪心法”

​ C语言中的某些符号如“/”、“*”、和“=”,只有一个字符长,称为单字符符号,而类似于“*/“、”==“以及标识符,包括了多个字符,称为多字符符号。C编译器读入字符/后,又跟了一个字符*,那么编译器就要做出判断,是分开还是将他们合起来作为一个符号对待。
​ C语言处理这种情况可以归纳为:每一个符号应该包含尽可能多的字符。即,程序从左到右一个一个字符地读入,如果字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可以,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。
​ 需要注意除了字符与字符常量,符号的中间不能嵌有空白(空格符、制表符和换行符)。如:==是单个符号,而== (是个空格)是两个符号。
例子:

a---b

a -- - b 含义相同,而与
a - -- b 含义不同。

​ 同样,如果/是判断下一个符号读入的第一个字符,而它后面紧跟着*,则无论上下文如何,这两个字符都会被当做一个符号“/*”,表示一段注释的开始。

​ 所以如下语句:
​ y = x/*p /* P指向除数 */
​ 本意是将x除以p指向的值,商赋给y,而实际上编译器会将/*理解为注释的开始,编译器将不断读入字符,直到*/出现为止。

​ 所以将语句改写成
​ y = x / *p 或
​ y = x/(*p)
​ 才是原意。

​ 老版本的C语言允许使用=+来代表现在+=的含义。这种编译器会将
​ a=-1;
​ 理解为
​ a =- 1;
​ 即
​ a = a - 1;
(怪不得平时最好要用空格分开,我以前最喜欢连着写了…. 不过现在使用的编译器好像都没有这种特性了。)
​ 那么在这种编译器下,上面提到的
​ a=/*b;
​ 会被当做
​ a =/ *b;
​ 即
​ a = a / *b;

1.4 整形常量

​ 如果一个整形常量的第一个字符是数字0,那么该常量会被视作八进制数。

1.5 字符与字符串

​ C语言中单引号和双引号含义不同,切勿弄混。
​ 单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字符集中的序列值。
​ 双引号引起的字符串,代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制为0的字符 ‘/0’初始化。

printf("Hello World\n");

char hello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\n', 0};
printf(hello);
是等效的。

课后题

1.为什么n-->0的含义是n-- >0而不是 n- ->0?
答:如前文所述,“贪心”。在读入-后,会扫描后面的符号,看是否可以构成一个整体,那么-和-可以构成自减符号,自然先结合。
  1. a+++++b的含义是什么?
    a++ ++ +b,等价于(a++)++ +b不会通过编译,因为自增自减不能当左值。