18-不要自作聪明
5.3.6 不要自作聪明
如果一次用太多递增运算符,自己都会糊涂。例如,利用递增运算符改进 squares.c 程序(程序清单5.4),用下面的 while 循环替换原程序中的 while 循环:
while (num < 21)
{
printf("%10d %10d\n", num, num*num++);
}
这个想法看上去不错。打印 num ,然后计算 num num 得到平方值,最后把 num 递增 1 。但事实上,修改后的程序只能在某些系统上能正常运行。该程序的问题是:当 printf() 获取待打印的值时,可能先对最后一个参数( `numnum++)求值,这样在获取其他参数的值之前就递增了num` 。所以,本应打印:
5 25
却打印成:
6 25
它甚至可能从右往左执行,对最右边的 num ( ++ 作用的 num )使用 5 ,对第2个 num 和最左边的 num 使用 6 ,结果打印出:
6 30
在C语言中,编译器可以自行选择先对函数中的哪个参数求值。这样做提高了编译器的效率,但是如果在函数的参数中使用了递增运算符,就会有一些问题。
类似这样的语句,也会导致一些麻烦:
ans = num/2 + 5*(1 + num++);
同样,该语句的问题是:编译器可能不会按预想的顺序来执行。你可能认为,先计算第1项( num/2 ),接着计算第2项( 5 * (1 + num++) )。但是,编译器可能先计算第2项,递增 num ,然后在 num/2 中使用 num 递增后的新值。因此,无法保证编译器到底先计算哪一项。
还有一种情况,也不确定:
n = 3;
y = n++ + n++;
可以肯定的是,执行完这两条语句后, n 的值会比旧值大 2 。但是, y 的值不确定。在对 y 求值时,编译器可以使用 n 的旧值( 3 )两次,然后把 n 递增 1 两次,这使得 y 的值为 6 , n 的值为 5 。或者,编译器使用 n 的旧值( 3 )一次,立即递增 n ,再对表达式中的第2个 n 使用递增后的新值,然后再递增 n ,这使得 y 的值为 7 , n 的值为 5 。两种方案都可行。对于这种情况更精确地说,结果是未定义的,这意味着C标准并未定义结果应该是什么。
遵循以下规则,很容易避免类似的问题:
- 如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增或递减运算符;
- 如果一个变量多次出现在一个表达式中,不要对该变量使用递增或递减运算符。
另一方面,对于何时执行递增,C还是做了一些保证。我们在本章后面的“副作用和序列点”中学到序列点时再来讨论这部分内容。