第 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。
表5-1 关系运算符
关系运算符的优先级低于算术运算符。例如,表达式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)。因为单独一个=字符表示赋值运算符,所以“等于”运算符是两个紧邻的=字符,而不是一个=字符。“不等于”运算符也是两个字符,即!和=。
表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),较简单的表达式可以构建出更加复杂的逻辑表达式。!是一元运算符,&&和||是二元运算符。
表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)...
注意用运算符||代替运算符&&。
5.2.1 复合语句
注意,在if语句模板中,语句是一条语句而不是多条语句:
if (表达式) 语句
如果想用if语句处理两条或更多条语句,该怎么办呢?可以引入复合语句(compound statement)。复合语句由一对花括号,以及花括号内的声明和语句混合而成。可以有多个声明和多条语句,也可以都没有。在后一种情况下,复合语句只有一对花括号,它什么也不做。典型地,通过在一组语句周围放置花括号,可以强制编译器将其作为一条语句来处理。
下面是一个复合语句的示例:
{ line_num = 0; page_num++; }
为了表示清楚,通常将一条复合语句放在多行内,每行有一条语句,如下所示:
{ line_num = 0; page_num++; }
注意,每条内部语句仍然是以分号结尾的,但复合语句本身并不是。
下面是在if语句内部使用复合语句的形式:
if (line_num == MAX_LINES) { line_num = 0; page_num++; }
复合语句也常出现在循环和其他需要多条语句(但C语言的语法要求一条语句)的地方。
5.2.2 else子句
if语句可以有else子句:
[带有else子句的if语句] if (表达式) 语句 else 语句
如果圆括号内的表达式的值为0,那么就执行else后边的语句。
下面是一个含有else子句的if语句的示例:
if (i > j) max = i; else max = j;
注意,两条“内部”语句都是以分号结尾的。
if语句包含else子句时,出现了布局问题:应该把else放置在哪里呢?和前面的例子一样,许多C程序员把它和if对齐排列在语句的起始位置。内部语句通常采用缩进格式;但是,如果内部语句很短,可以把它们与if和else放置在同一行中:
if (i > j) max = i; else max = j;
C语言对可以出现在if语句内部的语句类型没有限制。事实上,在if语句内部嵌套其他if语句是非常普遍的。考虑下面的if语句,其功能是找出i、j和k中所存储的最大值并将其保存到max中:
if (i > j) if (i > k) max = i; else max = k; else if (j > k) max = j; else max = k;
if语句可以嵌套任意层。注意,把每个else同与它匹配的if对齐排列,这样做很容易辨别嵌套层次。如果发现嵌套仍然很混乱,那么不要犹豫,直接增加花括号就可以了:
if (i > j) { if (i > k) max = i; else max = k; } else { if (j > k) max = j; else max = k; }
为语句增加花括号(即使有时并不是必需的)就像在表达式中使用圆括号一样,这两种方法都可以使程序更加容易阅读,同时可以避免出现编译器不能像程序员一样去理解程序的问题。
有些程序员在if语句(以及重复语句)中尽可能多地使用花括号。遵循这种惯例的程序员为每个if子句和每个else子句都使用一对花括号:
if (i > j) { if (i > k){ max = i; } else { max = k; } } else { if (j > k) { max = j; } else { max = k; } }
即便在不必要的情况下也使用花括号,这样做有两个好处。首先,由于很容易添加更多的语句到任何if或else子句中,程序变得更容易修改;其次,这样做可以在向if或else子句中增加语句时避免由于忘记使用花括号而导致错误。
5.2.3 级联式if语句
编程时常常需要判定一系列的条件,一旦其中某一个条件为真就立刻停止。“级联式”if语句常常是编写这类系列判定的最好方法。例如,下面这个级联式if语句用来判定n是小于0、等于0,还是大于0:
if (n < 0) printf("n is less than 0\n"); else if (n == 0) printf("n is equal to 0\n"); else printf("n is greater than 0\n");
虽然第二个if语句是嵌套在第一个if语句内部的,但是C语言程序员通常不会对它进行缩进,而是把每个else都与最初的if对齐:
if (n < 0) printf("n is less than 0\n"); else if (n == 0) printf("n is equal to 0\n"); else printf("n is greater than 0\n");
这样的安排带给级联式if语句独特的书写形式:
if (表达式) 语句 else if (表达式) 语句 ... else if (表达式) 语句 else 语句
当然,这种格式中的最后两行(else 语句)不是总出现的。这种缩进级联式if语句的方法避免了判定数量过多时过度缩进的问题。此外,这样也向读者证明了这组语句只是一连串的判定。
请记住,级联式if语句不是新的语句类型,它仅仅是普通的if语句,只是碰巧有另外一条if语句作为else子句(而且这条if语句又有另外一条if语句作为它自己的else子句,以此类推)。
程序 计算股票经纪人的佣金
当股票通过经纪人进行买卖时,经纪人的佣金往往根据股票交易额采用某种变化的比例进行计算。表5-4显示了实际支付给经纪人的费用金额。
表5-4 支付股票经纪人实际费用
最低收费是39美元。下面的程序要求用户输入交易额,然后显示出佣金的数额:
Enter value of trade: 30000 Commission: $166.00
该程序的重点是用级联式if语句来确定交易额所在的数值范围。
broker.c
/* Calculates a broker"s commission */ #include <stdio.h> int main(void) { float commission, value; printf("Enter value of trade: "); scanf("%f", &value); if (value < 2500.00f) commission = 30.00f + .017f * value; else if (value < 6250.00f) commission = 56.00f + .0066f * value; else if (value < 20000.00f) commission = 76.00f + .0034f * value; else if (value < 50000.00f) commission = 100.00f + .0022f * value; else if (value < 500000.00f) commission = 155.00f + .0011f * value; else commission = 255.00f + .0009f * value; if (commission < 39.00f) commission = 39.00f; printf("Commission: $%.2f\n", commission); return 0; }
级联式if