当我们阅读一个句子时,我们并不去考虑组成这个句子的单词中单个字母的含义,而是把单词作为一个整体来理解。确实,字母本身并没有什么意义,我们总是将字母组成单词,然后给单词赋予一定的意义。对于C语言或其他语言编写的程序,道理也是一样的。
编译器中负责将程序分解为一个一个符号的那部分,一般称为”词法分析器”,在C语言中符号之间的空白将被忽略,包括空格符,制表符和换行符。
=和==
一般而言,更为常用的操作用更为简短的表达,以节省大家的时间。故符号=作为赋值,符号==作为比较运算,因为赋值比使用的更为频繁。
=的左边的操作数为左值(lvalue),右边的为右值(rvalue),关于lvalue和rvalue,详情请参阅《The C programming Language》(K&R/TCPL)的附录A.5和A.7。
潜在的问题代码:
if (x = y)
foo();
程序员的本意是判断(x == y)还是直接的赋值(x = y)呢?现在的gcc编译器在发现形如e1 = e2的表达式出现在条件判断的部分时,会给出相关的警告信息,如果编译的时候把警告打开的话。
比如下面这段代码:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int x = 1;
int y = 1;
if ( x = y)
printf(“x = y is true\n”);
exit(0);
}
不打开警告的编译方法和打开警告的编译方法分别为:
gcc -o prog prog.c
gcc -Wall -o prog prog.c
则可以看到如果打开警告,编译器会有提示”warning: suggest parentheses around assignment used as truth value”,如果不打开警告的话,就没有任何警告提示了。
作为一种解决方法和一种良好的编程习惯,作者建议写作显式比较的形式:
if ((x = y) != 0)
foo();
既能去除警告,也使得代码的意图一目了然,只是多了几个字符,何乐而不为!
词法分析的“贪心法”
C语言的符号可以是单字符号,形如/,*和=,也可以是多字符符号,形如/*和==,当编译器读入一个'/'后又跟着一个'*',那么编译器是如何判断的呢?法则就是贪心法,即编译器从左到右一个字符一个字符的读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已经不再可能组成一个有意义的符号。这个处理策略一般称作“贪心法”,有时也叫“大嘴法”,一句话概括来说,就是每个符号应该包括尽可能多的字符。Kernighan和Ritchie在《The C programming Language》 (K&R/TCPL)的附录A.2.1对这个方法这样描述:
If the input stream has been separated into tokens up to a given character, the next token is the longest string of characters that could constitute a token.
如果到某一个为止的输入流被分割成若干记号,那么,下一个记号就是后续字符序列中可能构成记号的最长的字符串。
但是我们要注意到,虽然编译器使用的是贪心法,但是也要遵循基本的词法规则,比如lvalue和rvalue的概念,这章中的练习1-4就是a+++++b的含义是什么,这里应该使用贪心法,不过也需要考虑到lvalue和rvalue的概念,不然很容易想当然的出错,第八章中给出的答案也只是说a++的结果不能作为lvalue,编译器不接受a++作为后面的++运算的操作数,只是说明了一下,没有系统的说明出处,一会我在详细的说明一下。
字符与字符串
这个相对容易理解多了,单引号和双引号嘛。下面说说两者的实质的区别:
单引号:单引号字符本质上是代表一个整数,其值对应于该字符在编译器采用的字符集中的序列值。对于普通的采用ASCII字符集的编译器,如gcc,'a'就是97或0141(八进制)。
双引号:双引号的字符串代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为零的字符'\0'初始化。
下面的语句:
printf("Hello world\n");
和
char hello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\n', '\0'}
printf(hello);
两者是等价的。
这个很好理解,用一种形而上学的理解记忆,一个串相当于N多字符的组合,要放在一起使用,当然只能用数组的形式了,也就是说串就是字符的无名数组形式了,既然无名,就只能使用指针来引用了,串就是这个指针而这个数组最后的'\0'代表串的结束(总得有一个东西来代表结束吧)。
关于这个结束字符串的东西,在《Expert C Programming: Deep C Secrets》(《C专家编程》)中的第二章的2.1小节的小启发中说是NUL,而NULL代表空指针,表示什么也不指向,而NULLL则说明需要检查一下是不是拼写错误了。这里的NUL其实ASCII码表0值的char字面意思,NUL并没有在标准C中定义,当然如果自己想用的话,#define一下也不错。
一句话总结就是,单引号的字符代表一个整数,双引号的则代表一个指针,混用的话编译器的类型检查功能会检测到这样的错误的,gcc就可以。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
printf('\n');
exit(0);
}
使用gcc编译情况如下(不加-Wall参数):
$ gcc -o prog prog.c
prog.c: In function ‘main’:
prog.c:6: warning: passing argument 1 of ‘printf’ makes pointer from integer without a cast
运行情况如下:
$ ./prog
Segmentation fault
因为标准C并没有指出编译器在类似错误的情况下的行为,所以常常是这种段错误了。
另外一个就是类似char c = 'yes'的编译器行为了,gcc是后一个字符直接覆盖前一个字符,所以c就是's'了,gcc也会有警告的:
$ cat prog.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char c = 'yes';
printf("%c\n", c);
exit(0);
}
编译情况如下:
$ gcc -o prog prog.c运行结果:
prog.c:6:11: warning: multi-character character constant
prog.c: In function ‘main’:
prog.c:6: warning: overflow in implicit constant conversion
$ ./prog
s
这章最后
这一章的最后,还是来说说上面提到的练习1-4的a+++++b。
按照贪心法,似乎可以理解为((a++)++)+b,不过书中的答案说a++不能作为lvalue,因此编译器不会接受a++作为后面++运算符俄操作数,为什么不能作为左值,出处在哪里?
出处就在《The C programming Language》(K&R/TCPL)的附录
A.7.3.4 Postfix Incrementation ... ... The result is not an lvalue.当然前缀的也不能作为lvalue,出处就是A.7.4.1 Prefix Incrementation Operators ... ... The result is not an lvalue. ^_^






