编译器中负责将程序分解为一个一个符号的部分,一般称为“词法分析器”。
编译分为三个阶段预编译,编译,链接。在编译阶段,把C语言代码转换成汇编代码时要用到。
在C语言中,符号之间的空白(包括空格符、制表符或换行符)将被忽略。
1.1 =不同于==
注意:不要把==误用成=,同样也不要把=误用成==。
某些C编译器在发现形如e1=e2的表达式出现在循环语句的条件判断部分时,会给出警告消息以提醒程序员。当确实需要对变量进行赋值并检查该变量的新值是否为0时,为了避免来自该类编译器的警告,我们不应该简单关闭警告选项(通常在表达式的两端添加括号来关闭警告),而应该显式的进行比较。也就是说,下例
if(x = y)
foo();
应该写作:
if((x = y) != 0)
foo();
这种写法也使得代码的意图一目了然。
1.2 &和|不同于&&和||
注意:上面的这些符号之间不要进行混淆,因为混淆了编译器常常不会发出任何提示。
1.3 词法分析中的“贪心法”
规则概括:每一个符号应该包含尽可能多的字符。
注意:除了字符串与字符常量,符号的中间不能嵌有空白(空格符、制表符和换行符)。
例如,==是单个符号,而= =则是两个符号,下面的表达式
a---b
与表达式
a -- -b
的含义相同,而与
a - -- b
的含义不同。同样,如果/是为判断下一个符号而读入的第一个字符,而/之后紧接着*,那么无论上下文如何,这两个字符都将被当作一个符号/*,表示一段注释的开始。
例如:
y = x/*p; //p指向除数
而实际上,/*被理解为一段注释的开始,编译器将不断的读入字符,直到*/出现为止。也就是说,该语句直接将x的值赋给y,根本不会顾及后面出现的p。应该将上面的语句修改为下面的语句才能表达向表达的意思:
y = x/(*p);
1.4 整型常量
0开头的数字被视作八进制数。因此,10与010的含义截然不同。此外,许多C编译器会把8和9也作为八进制数字处理。例如0195的含义是1*82+9*81+5*80,也就是141(十进制)或者0265(八进制)。我们当然不建议这种写法,同时ANSIC标准也禁止这种用法。
注意:有时候,我们在上下文中为了格式对齐的需要,可能无意中将十进制数写成了八进制数。
1.5 字符与字符串
区分’'与""
用单引号引起的一个字符实际上代表一个整数,常常就是其ASCII码值。
用双引号引起的字符串,代表的是一个指向字符串常量区的指针。
如何理解’yes’?
其含义在编译器中没有准去的定义,但大多数编译器理解为:一个整数值,由’y’、‘e’、's’所代表的整数值按照特定编译器实现中定义的方式组合得到。
一般会有三种处理方式:
-
忽略多余的字符,最后的整数值即第一个字符的整数值。
-
依次用后一个字符覆盖前一个字符,最后得到的整数值即最后一个字符的整数值。VS6.0和GCC v2.95就是这种方式。
-
将在’'中的字符按照字节为单位一个个将其填入整型变量所在的内存空间中,下面给出一个例子(编译环境:VS2019)
#include<stdio.h> int main() { int a = '1234'; printf("%d", a); return 0; }
经过调试之后,就可以看到如下所示:
从这就可以看出,在VS2019中是直接将’'中的字符按照字节为单位将其一个 个存入到整型变量所在的内存空间中。
练习
-
问:为什么n–>0的含义是n-- >0而不是n- ->0?
答:根据大嘴法,即每一个符号应该包含尽可能多的字符规则,在编译器读入>之前,就已经将–作为单个符号了。
-
问:a+++++b的含义是什么?
答:上式唯一有意义的解析方式是:
a ++ + ++ b
可是,我们也注意到,根据”大嘴法“规则,上式应该被分解为:
a ++ ++ + b
这个式子从语法上来说是不正确的,它等价于:
((a++)++) +b
但是,a++的结果不能作为左值,因此编译器不会接收a++作为后面的++运算符的操作数。所以只能被理解为第一种。