1.1 = 不是 ==
从Algol派生出来的语言,如Pascal和Ada,用:=表示赋值而用=表示比较。而C语言则是用=表示赋值而用==表示比较。这是因为赋值的频率要高于比较,因此为其分配更短的符号。此外,C还将赋值视为一个运算符,因此可以很容易地写出多重赋值(如a = b = c),并且可以将赋值嵌入到一个大的表达式中。这种便捷导致了一个潜在的问题:可能将需要比较的地方写成赋值。因此,下面的语句好像看起来是要检查x是否等于y:
if(x = y)
foo();
而实际上是将x 设置为y的值并检查结果是否非零。在考虑下面的一个希望跳过空格、制表符和换行符的循环:
while(c == ' ' ||c = '\t' || c == '\n')
c = getc(f);
在与'\t'进行比较的地方程序员错误地使用=代替了==。这个“比较”实际上是将'\t'赋给c,然后判断c的(新的)值是否为零。因为'\t'不为零,这个“比较”将一直为真,因此这个循环会吃尽整个文件。这之后会发生什么取决于特定的实现是否允许一个程序读取超过文件尾部的部分。如果允许,这个循环会一直运行。一些C 编译器会对形如e1 = e2 的条件给出一个警告以提醒用户。当你趋势需要先对一个变量进行赋值之后再检查变量是否非零时,为了在这种编译器中避免警告信息,应考虑显式给出比较符。换句话说,将:
if(x = y)
foo();
//改写为:
if((x = y) != 0)
foo();
这样可以清晰地表示你的意图。
1.2 & 和 | 不是 && 和 ||
容易将==错写为=是因为很多其他语言使用=表示比较运算。 其他容易写错的运算符还有&和&&,或和||,这主要是因为C 语言中的&和|运算符于其他语言中具有类似功能的运算符大为不同。我们将在第4节中贴近地观察这些运算符。
1.3 多字符记号
一些C 记号,如/、*和=只有一个字符。而其他一些C记号,如/*和==,以及标识符,具有多个字符。当C编译器遇到紧连在一起的/和*时,它必须能够决定是将这两个字符识别为两个分离的记号还是一个单独的记号。C语言参考手册说明了如何决定:“如果输入流到一个给定的字符串为止已经被识别为记号,则应该包含下一个字符以组成能够构成记号的最长的字符串”。因此,如果/是一个记号的第一个字符,并且/后面紧随了一个*,则这两个字符构成了注释的开始,不管其他上下文环境。
下面的语句看起来像是将y 的值设置为x的值除以p所指向的值:
y = x/*p /* p 指向除数 */;
实际上,/*开始了一个注释,因此编译器简单地吞噬程序文本,直到*/的出现。换句话说,这条语句仅仅把y的值设置为x的值,而根本没有看到p。将这条语句重写为:
y = x / *p /* p 指向除数 */;
//或者干脆是
y = x / (*p) /* p指向除数 */;
它就可以做注释所暗示的除法了。这种模棱两可的写法在其他环境中就会引起麻烦。例如,老版本的C 使用=+表示现在版本中的+=。
这样的编译器会将
a=-1;
//视为
a =- 1;
//或
a = a - 1;
//这会让打算写
a = -1;
的程序员感到吃惊。
另一方面,这种老版本的C 编译器会将a=/*b;断句为a =/ *b;
尽管/*看起来像一个注释。
1.4 例外
组合赋值运算符如+=实际上是两个记号。因此,a + /* strange */= 1和 a += 1 是一个意思。看起来像一个单独的记号而实际上是多个记号的只有这一个特例。特别地,p - > a是不合法的。它和p -> a不是同义词。另一方面,有些老式编译器还是将=+视为一个单独的记号并且和+=是同义词。