2.11条件表达式
if (a > b)
z = a;
else
z = b;
上面的语句计算 a 和 b 中的最大值并存入 z。而使用三元操作符 ? : 的条件表达式,为这个结构及类似结构提供了另一种写法。在如下表达式
expr1 ? expr2 : expr3
中,首先对 expr1 求值。如果值非0 (为真),则对 expr2 求值,而得到的也是整个条件表达式的值。若 exp1 的值为0(为假),则对 expr3 求值,得到整个表达式的值。 expr2 和 expr3 中只会有一个表达式被求值。因此,把 z 设为 a 和 b 的最大值就能写成:
z = a > b ? a : b; /* z = max(a,b) */
需要注意,条件表达式确实是一个表达式,它能出现在任何其他表达式可以出现的地方。如果 expr2 和 expr3 的类型不同,则结果的类型取决于本章前面讨论过的转换规则。例如, 如果 f 是 float 而 n 是 int,则表达式
(n > 0) ? f : n
的类型是 float, 不管 n 是否大于 0。
条件表达式的第一个表达式两边不用加上括号,因为 ?: 的优先级非常低,仅高于赋值。然而加上括号是明智的,因为这会让条件表达式的条件部分看起来更明显。
条件表达式可以带来更简洁的代码。例如,下面这个循环会打印数组的 n 个元素,每行10个,中间用空格隔开,每行(包括最后一行)末尾以换行符结束。
for (i = 0; i < n; ++i)
printf("%6d%c", a[i], (i%10==9 || i== n-1) ? '\n' : ' ');
每10个元素后面打印一个换行符,第n个元素后面也要。其他所有元素后面打印空格。代码可能看起来有点绕,但比对应的 if-else 写法要紧凑。还有个不错的例子是
printf("You have %d items%s.\n", n, n==1 ? "" : "s");
练习2-10、重写 把大写字母转小写的 lower 函数,用条件表达式,不用 if-else。
2.12优先级和求值顺序
表2-1总结了所有操作符的优先级和结合性,包括那些我们还没讲到的。同一行的操作符有相同的优先级;行按优先级降序排列,例如 操作符 * / % 的优先级相同,而它们三个的优先级都比二元操作符 + - 要高。括号“操作符” ( ) 指的是函数调用。操作符 -> 和 . 用来访问结构的成员,还有 sizeof(对象的大小),都会在第6章讲到。第5章会讨论 * (通过指针引用)和 & (对象的地址),而第3章会讨论逗号操作符。
注意位操作符 & ^ | 优先级低于 == 和 !=。这隐含说明,用来测试位的表达式如
if ((x & MASK) == 0) ...
里面必须全部用括号包起来,才能得到想要的结果。
C,和大多数语言一样,并没有指定一个操作符中多个操作数的求值顺序。(操作符 && || ?: 和逗号操作符除外)。例如语句
x = f() + g()
f 可能 在 g 之前求值,可能相反;这样如果 f 或 g 改变了对方依赖的一个变量值,则 x 的值依赖于求值顺序。应对这种情况,可以用临时变量来保存中间结果,以保证所需的顺序。
类似的,函数的多个参数的求值顺序也是没有规定的,因此语句
printf("%d %d\n", ++n, power(2,n)); /* WRONG */
用不同的编译器可以产生不同的结果,依赖于 n 是否在 power 被调用之前自增。当然,解决方法是写成
++n;
printf("%d %d\n", n, power(2,n));
函数调用,嵌套的赋值语句,以及自增和自减操作符,都会带来“副作用”——作为表达式求值的副产品,某些变量被改变了。在任何涉及副作用的表达式中,都可能存在对表达式中变量的更新顺序的微妙依赖。这种代码令人难受。来看一个典型的:
a[i] = i++;
问题是,数组下标用的是 i 的老值还是新值?不同的编译器可以用不同的方式来解释,并基于不同的解释生成不同的结果。标准有意地不明确规定大部分的这类问题。表达式中的副作用(对变量赋值)何时发生,这个决定权交给了编译器,因为最佳的选择强烈依赖于机器架构。(标准的确规定了,所有参数的副作用生效在函数被调用之前,但这无法解决上面的 printf 问题。)
结论就是:不管在任何语言中,写出依赖于求值顺序的代码都是糟糕的编程实践。自然地,有必要知道应该避免什么,但如果你不知道在各种不同的机器上是分别如何实现的,你也不会忍不住去利用某种特定的实现。
(第二章完)