C 语言编程常见错误 2

2008-02-23 05:28:12来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折

我对原文作了一定的删改,使之符合当今的 C 语言标准。假如原作者觉得不好的话,请跟帖告知,我将马上删除。谢谢!


1. = 不等于 ==

从 Algol 派生出来的语言,如 Pascal 和 Ada,用 := 表示赋值而用 = 表示比较。而 C 语言则是用 = 表示赋值而用 == 表示比较。这是因为赋值的频率要高于比较,因此为其分配更短的符号。此外,C 能够多重赋值(如 a = b = c),并且能够将赋值嵌入到一个大的表达式或语句中。这种便捷导致了一个潜在的问题:需要用比较的地方却写成了赋值。下面的语句看起来似乎是要检查 x 是否等于 y :
if ( x = y )
foo();
而实际上是将 y 的值赋值给 x ,并检查结果是否非零。再看看下面的一个希望跳过空格、制表符和换行符的循环:
while ( c == ' ' || c = '\t' || c == '\n' )
c = getc(f);
在应该和 '\t' 进行比较的地方程式员错误地使用了 =,而不是==。这个“比较”实际上是将'\t' 赋给 c,然后判断 c 的(新的)值是否为零。因为 '\t' 不为零,所以这个“比较”一直为真,因此这是个死循环。
一些编译器会对形如 e1 = e2 的条件给出一个警告以提醒用户。当您确实需要对一个变量进行赋值,然后再检查变量是否“非零”时,为了避免这种警告信息,应显式给出比较符。也就是将:
if ( x = y )
foo();
改写为:
if ( ( x = y ) != 0 )
foo();


2. 多字符符号

一些 C 符号,如 /、* 或 =,只有一个字符。更有些 C 符号,如 /* 、 == 或标识符,具备多个字符。当编译器碰到紧连在一起的 / 和 * 时,他必须决定是将这两个字符识别为两个符号还是个单独的符号。C 语言标准规定:“假如一个字符被识别为符号,则应该包含下一个字符看看包含此字符后构成的字符串是否仍然能够构成符号,假如能够则继续包含下一个字符,一直到不能构成符号为止。”。因此,假如 / 是符号的第一个字符,并且 / 后面紧随着一个 *,则这两个字符构成注释符开始标记。下面的语句看起来像是将 y 的值配置为 x 的值除以 p 所指向的值:
y = x/*p /* p 指向除数 */;
实际上,因为 /* 是注释符开始标记,因此编译器会简单地“吞噬”程式文本,直到 */ 出现为止。换句话说,这条语句仅仅把 y 的值配置为 x 的值,而根本没有看到 p。我们应该将这条语句改为:
y = x / *p /* p 指向除数 */;
或:
y = x / (*p) /* p指向除数 */;


3. else 问题

考虑下面的程式片断:
if ( x == 0 )
if ( y == 0 )
error();
else {
z = x y;
f(&z);
}
写这段程式的程式员的目的明显是想将情况分为两种:x == 0 和x != 0。在第一种情况中,假如 y == 0,则调用 error()。第二种情况中,程式执行 z = x y; 和 f(&z); 。
然而, 这段程式的实际效果却大为不同。其原因是 else 总是和离他最近的 if 相关联。上面那段代码其实等价于:
if ( x == 0 ) {
if ( y == 0 )
error();
else {
z = x y;
f(&z);
}
}
也就是说,当 x != 0 发生时什么也不做。假如要达到我们想要的效果,应该改成:
if ( x == 0 ) {
if ( y == 0 )
error();
} else {
z = z y;
f(&z);
}


4. 表达式求值顺序

一些运算符以一种已知的、特定的顺序对其操作数进行求值。但另一些则不是。例如下面的表达式:
a < b && c < d
C 标准规定 a < b 首先被求值。假如 a 确实小于 b,c < d 必须紧接着被求值以计算整个表达式的真假性。但假如 a 大于或等于 b,则 c < d 根本不会被求值。而对 a < b 求值时,到底是先取 a 的值,还是先取 b 的值,标准并没有定义。
C 中只有四个运算符(&&、||、?: 和 ,)指定了求值顺序。&& 和 || 最先对左边的操作数进行求值,而右边的操作数只有在需要的时候才进行求值。而 ?: 运算符中的三个操作数中,先对最左边的进行求值,然后根据他的值决定到底应该求中间的操作数的值,还是求最右边的操作数的值。逗号运算符(,)的求值顺序为从左到右。
C 中任何其他运算符的操作数的求值顺序都是未定义的。特别要说的是,赋值运算符也没有对求值顺序做出任何确保。
出于这个原因,下面这种将数组 x 中的前 n 个元素复制到数组 y 中的方法是不可行的:
j = 0;
while ( j < n )
y[j] = x[j ];
因为标准没有确保 y[j] 在 j 增长之前被求值。到底 y[j] 先求值,还是 x[j ] 先求值是依赖编译器的!所以我们不应该这么写!另一种方案基于同样的原因也不可行:
j = 0;
while ( j < n )
y[j ] = x[j];
下面的代码才是正确的:
j = 0;
while ( j < n ) {
y[j] = x[j];
j ;
}
当然,也能够这么写:
for ( j = 0; j < n; j )
y[j] = x[j];


5. &&、|| 和 ! 运算符

C 规定 0 代表“假”,非零代表“真”。这些运算符返回 1 表示“真”而返回 0 表示“假”。&& 和 || 运算符假如能够通过左边的操作数确定整个表达式的真假性,就不会对右边的操作数进行求值。!10 返回 0,因为 10 非零;10 && 12 返回 1,因为 10 和 12 的值都不是 0;10 || 12 也是 1,因为 10 非零。这个表达式中的 12 不会被求值,因为左边的 10 就足够确定整个表达式为真。同理 :10 || f() 中的 f() 也不会被求值。


6. 下标从零开始

C 语言中,一个具备 n 个元素的数组中没有下标为 n 的元素,元素的下标是从 0 到n-1。下面的程式可能会崩溃:
int i, a[10];

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇: 基于嵌入式SoPC的以太网接口设备

下一篇: C 语言编程常见错误 1