第5章 选择语句
不应该以聪明才智和逻辑分析能力来评判程序员,而要看其分析问题的全面性。
尽管C语言有许多运算符,但是它所拥有的语句却相对较少。到目前为止,我们只见过两种语句:return语句(2.2节)和表达式语句(4.5节)。根据对语句执行顺序的影响,C语言的其余语句大多属于以下3大类。
- 选择语句(selection statement)。if语句和switch语句允许程序在一组可选项中选择一条特定的执行路径。
- 重复语句(iteration statement)。while语句、do语句和for语句支持重复(循环)操作。
- 跳转语句(jump statement)。break语句、continue语句和goto语句导致无条件地跳转到程序中的某个位置。(return语句也属于此类。)
C语言还有其他两类语句,一类是复合语句(把几条语句组合成一条语句),一类是空语句(不执行任何操作)。
本章讨论选择语句和复合语句。(第6章会介绍重复语句、跳转语句和空语句。)在使用if语句之前,我们需要介绍逻辑表达式:if语句可以测试的条件。5.1节说明如何用关系运算符(<、<=、>和>=)、判等运算符(==和!=)和逻辑运算符(&&、||和!)构造逻辑表达式。5.2节介绍if语句和复合语句,以及可以在一个表达式内测试条件的条件运算符(?:)。5.3节描述switch语句。
5.1 逻辑表达式
包括if语句在内的某些C语句都必须测试表达式的值是“真”还是“假”。例如,if语句可能需要检测表达式i < j
,真值将说明i是小于j的。在许多编程语言中,类似i < j
这样的表达式都具有特殊的“布尔”类型或“逻辑”类型。这样的类型只有两个值,即假和真。而在C语言中,诸如i < j
这样的比较运算会产生整数:0(假)或1(真)。先记住这一点,下面来看看用于构建逻辑表达式的运算符。
5.1.1 关系运算符
C语言的关系运算符(relational operator)(表5-1)和数学上的<、>、≤ 和 ≥运算符相对应,只是用在C语言的表达式中时参数的结果是0(假)或1(真)。例如,表达式10<11的值为1,而表达式11 < 10的值为0。
关系运算符可以用于比较整数和浮点数,也允许比较混合类型的操作数。因此,表达式1 < 2.5 的值为1,而表达式5.6 < 4 的值为0。
符号 | 含义 |
---|---|
< |
小于 |
> |
大于 |
<= |
小于等于 |
>= |
大于等于 |
关系运算符的优先级低于算术运算符。例如,表达式 i + j < k - 1 意思是(i + j)<(k - 1)。关系运算符都是左结合的。
表达式i < j < k 在C语言中是合法的,但可能不是你所期望的含意。因为<运算符是左结合的,所以这个表达式等价于
(i < j ) < k
换句话说,表达式首先检测i是否小于j,然后用比较后产生的结果(1或0)来和k进行比较。这个表达式并不是测试j是否位于i和k之间。(在本节后面将会看到,正确的表达式应该是i < j && j < k。)
5.1.2 判断运算符
C语言中表示关系运算符的符号与其他许多编程语言中的相同,但是判等运算符(equality operator)却有着独一无二的形式(表5-2)。因为单独一个=字符表示赋值运算符,所以“等于”运算符是两个紧邻的=字符,而不是一个=字符。“不等于”运算符也是两个字符,即!和=。
符号 | 含义 |
---|---|
== | 等于 |
!= | 不等于 |
和关系运算符一样,判等运算符也是左结合的,也是产生0(假)或1(真)作为结果。然而,判等运算符的优先级低于关系运算符。例如,表达式i < j == j < k等价于表达式(i < j) == (j < k)。如果i < j 和 j < k的结果同为真或同为假,那么这个表达式的结果为真。
聪明的程序员有时会巧妙利用关系运算符和判等运算符返回整数值这一事实。例如,依据i是否小于、大于或等于j,得出表达式(i >= j) + (i == j)的值分别是0、1或2。然而,这种技巧编码通常不是一个好主意,因为这样会使程序难以阅读。
5.1.3 逻辑运算符
利用逻辑运算符(logical operator)与、或和非(表5-3),较简单的表达式可以构建出更加复杂的逻辑表达式。!是一元运算符,而&&和||是二元运算符。
符号 | 含义 |
---|---|
! | 逻辑非 |
&& | 逻辑与 |
|| | 逻辑或 |
逻辑运算符所产生的结果是0或1。操作数的值经常是0或1,但这不是必需的。逻辑运算符将任何非零值操作符作为真值来处理,同时将任何零值操作数作为假值来处理。
逻辑运算符的操作如下:
- 如果表达式的值为0,那么!表达式的结果为1;
- 如果表达式1和表达式2的值都是非零值,那么表达式1&&表达式2的结果为1;
- 如果表达式1或表达式2的值中任意一个是(或者两者都是)非零值,那么表达式1||表达式2的结果为1。
在所有其他情况下,这些运算符产生的结果都为0。
运算符&&和运算符||都对操作数进行“短路”计算。也就是说,这些运算符首先计算出左操作数的值,然后计算右操作数;如果表达式的值可以仅由左操作数的值推导出来,那么将不计算右操作数的值。思考下面的表达式:
(i != 0) && (j / i > 0)
为了得到此表达式的值,首先必须计算表达式(i != 0)的值。如果i不等于0,那么需要计算表达式(j / i > 0)的值,从而确定整个表达式的值为真还是为假。但是,如果i等于0,那么整个表达式的值一定为假,所以就不需要计算表达式(j / i > 0)的值了。短路计算的优势是显而易见的,如果没有短路计算,那么表达式的求值将会导致除以零的运算。
要注意逻辑表达式的副作用。有了运算符&&和运算符||的短路特性,操作数的副作用并不会总发生。思考下面的表达式:
i > 0 && ++j > 0
虽然j因为表达式计算的副作用进行了自增操作,但是并不总是这样。如果i > 0的结果为假,将不会计算表达式++j > 0,那么j也就不会进行自增。把表达式的条件变成++j > 0 && i > 0,就可以解决这种短路问题。或者更好的办法是单独对j进行自增操作。
运算符!的优先级和一元正负号的优先级相同,运算符&&和运算符||的优先级低于关系运算符和判等运算符。例如,表达式i < j && k == m 等价于表达式(i < j) && (k == m)。运算符!是右结合的,而运算符&&和运算符||都是左结合的。
5.2 if语句
if语句允许程序通过测试表达式的值从两种选项中选择一种。if语句的最简单格式如下:
[if语句] if (表达式)语句
注意,表达式两边的圆括号是必需的,它们是if语句的组成部分,而不是表达式的内容。还要注意的是,与在其他一些语言中的用法不同,单词then没有出现在圆括号的后边。
执行if语句时,先计算圆括号内表达式的值。如果表达式的值非零(C语言把非零值解释为真值),那么接着执行圆括号后边的语句。下面是一个示例:
if (line_num == MAX_LINES) {
line_num = 0;
}
如果条件line_num == MAX_LINES为真(有非零值),那么执行语句line_num = 0;。
不要混淆==(判等)运算符和=(赋值)运算符。语句if(i == 0)…
测试i是否等于0,而语句if(i = 0)…
则是先把0赋值给i,然后测试赋值表达式的结果是否非零值。在这种情况下,测试总是会失败的。
把==运算符与=运算符相混淆是最常见的C语言编程错误,这也许是因为=在数字(和其他许多编程语言)中意味着“等于”。如果注意到应该正常出现运算符 == 的地方出现的是运算符=,有些编译器会给出警告。
通常,if语句中的表达式能判定变量是否落在某个数值范围内。例如,为了判定0≤i<n是否成立,可以写成
[惯用法]if (0 <= i && i < n)...
为了判定相反的情况(i在范围之外),可以写成
[惯用法]if (i < 0 || i >= n)...
注意用运算符||代替运算符&&。