万普插件库

jQuery插件大全与特效教程

C语言入门:选择与循环语句功能介绍,一看就懂

第 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

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 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语句也可以写成下面这样(改变用粗体表示):

if (value < 2500.00f) commission = 30.00f + .017f * value; else if (value >= 2500.00f && value < 6250.00f) commission = 56.00f + .0066f * value; else if (value >= 6250.00f && value < 20000.00f) commission = 76.00f + .0034f * value; ...

程序仍能正确运行,但新增的这些条件是多余的。例如,第一个if子句测试value的值是否小于2500,如果小于2500则计算佣金。当到达第二个if测试(value >= 2500.00f && value < 6250.00f)时,value不可能小于2500,因此一定大于等于2500。条件value >= 2500.00f总是为真,因此加上该测试没有意义。

5.2.4 “悬空else”的问题

当if语句嵌套时,千万要当心著名的“悬空else”的问题。思考下面这个例子:

if (y != 0) if (x != 0) result = x / y; else printf("Error: y is equal to 0\n");

上面的else子句究竟属于哪一个if语句呢?缩进格式暗示它属于最外层的if语句。然而,C语言遵循的规则是else子句应该属于离它最近的且还未和其他else匹配的if语句。在此例中,else子句实际上属于最内层的if语句,因此正确的缩进格式应该如下所示:

if (y != 0) if (x != 0) result = x / y; else printf("Error: y is equal to 0\n");

为了使else子句属于外层的if语句,可以把内层的if语句用花括号括起来:

if (y != 0) { if (x != 0) result = x / y; } else printf("Error: y is equal to 0\n");

这个示例表明了花括号的作用。如果把花括号用在本节第一个示例的if语句上,那么就不会有这样的问题了。

5.2.5 条件表达式

C语言的if语句允许程序根据条件的值来执行两个操作中的一个。C语言还提供了一种特殊的运算符,这种运算符允许表达式依据条件的值产生两个值中的一个。

条件运算符(conditional operator)由符号?和符号:组成,两个符号必须按如下格式一起使用:

[条件表达式] 表达式1 ? 表达式2 : 表达式3

表达式1、表达式2和表达式3可以是任何类型的表达式,按上述方式组合成的表达式称为条件表达式(conditional expression)。条件运算符是C运算符中唯一一个要求3个操作数的运算符。因此,它通常被称为三元(ternary)运算符。

应该把条件表达式表达式1?表达式2:表达式3读作“如果表达式1成立,那么表达式2,否则表达式3”。条件表达式求值的步骤如下:首先计算出表达式1的值,如果此值不为零,那么计算表达式2的值,并且计算出来的值就是整个条件表达式的值;如果表达式1的值为零,那么表达式3的值是整个条件表达式的值。

下面的示例对条件运算符进行了演示:

int i, j, k; i = 1; j = 2; k = i > j ? i : j; /* k is now 2 */ k = (i >= 0 ? i : 0) + j; /* k is now 3 */

在第一个对k赋值的语句中,条件表达式i > j ? i : j根据i和j的大小关系返回其中一个的值。因为i的值为1、j的值为2,表达式i > j比较的结果为假,所以条件表达式的值2被赋给k。在第二个对k赋值的语句中,因为表达式i >= 0比较的结果为真,所以条件表达式(i >= 0 ? i : 0)的值为1,然后把这个值和j相加得到结果3。顺便说一下,这里的圆括号是非常必要的,因为除赋值运算符以外,条件运算符的优先级低于先前介绍过的所有运算符。

条件表达式使程序更短小但也更难以阅读,因此最好避免使用。然而,在少数地方仍会使用条件表达式,其中一个就是return语句。许多程序员把

if (i > j) return i; else return j;

替换为

return i > j ? i : j;

printf函数的调用有时会得益于条件表达式。代码

if (i > j) printf("%d\n", i); else printf("%d\n", j);

可以简化为

printf("%d\n", i > j ? i : j);

条件表达式也普遍用于某些类型的宏定义中(?14.3节)。

5.2.6 C89中的布尔值

多年以来,C语言一直缺乏适当的布尔类型,C89标准中也没有定义布尔类型。因为许多程序需要变量能存储假或真值,所以缺少布尔类型可能会有点麻烦。针对C89的这一限制,一种解决方法是先声明一个int型变量,然后将其赋值为0或1:

int flag; flag = 0; ... flag = 1;

虽然这种方法可行,但是它对程序的可读性没有多大贡献。这是因为该方法没有明确地表示flag的赋值只能是布尔值,并且也没有明确地指出0和1就是表示假和真。

为了使程序更易于理解,C89的程序员通常使用TRUE和FALSE这样的名字定义宏:

#define TRUE 1 #define FALSE 0

现在对flag的赋值有了更加自然的形式:

flag = FALSE; ... flag = TRUE;

为了判定flag是否为真,可以用

if (flag == TRUE) ...

或者只写

if (flag) ...

后一种形式更好,一是它更简洁,二是当flag的值不是0或1时程序也能正确运行。

为了判定flag是否为假,可以用

if (flag == FALSE) ...

或者

if (!flag) ...

为了发扬这一思想,甚至可以定义一个可用作类型的宏:

#define BOOL int

声明布尔变量时可以用BOOL代替int:

BOOL flag;

现在就非常清楚flag不是普通的整型变量,而是表示布尔条件。(当然,编译器仍然把flag看作int型变量。)在后面的章节中,我们将介绍一些更好的方法,可以使用类型定义(?7.5节)和枚举(?16.5节)在C89中设置布尔类型。

5.2.7 C99中的布尔值

长期缺乏布尔类型的问题在C99中得到了解决。 C99提供了_Bool型,因此在C语言的这一版本中,布尔变量可以声明为

_Bool flag;

_Bool是整数类型(更准确地说是无符号整型),因此_Bool变量实际上就是整型变量;但是和一般的整型不同,_Bool只能赋值为0或1。一般来说,往_Bool变量中存储非零值会导致变量赋值为1:

flag = 5; /* flag is assigned 1 */

对于_Bool变量来说,算术运算是合法的(不过不建议这样做),它的值也可以被打印出来(显示0或者1)。当然,_Bool变量也可以在if语句中测试:

if (flag) /* tests whether flag is 1 */ ...

除了_Bool类型的定义,C99还提供了一个新的头,这使得操作布尔值更加容易。该头提供了bool宏,用来代表_Bool。如果程序中包含了,可以这样写:

bool flag; /* same as _Bool flag; */

头还提供了true和false两个宏,分别代表1和0。于是可以写

flag = false; ... flag = true;

头使用起来非常方便,因此在后面的程序中需要使用布尔变量时都用到了这个头。

5.3 switch语句

在日常编程中,常常需要把表达式和一系列值进行比较,从中找出当前匹配的值。在5.2节我们已经看到,级联式if语句可以达到这个目的。例如,下面的级联式if语句根据成绩的等级显示出相应的英语单词:

if (grade == 4) printf("Excellent"); else if (grade == 3) printf("Good"); else if (grade == 2) printf("Average"); else if (grade == 1) printf("Poor"); else if (grade == 0) printf("Failing"); else printf("Illegal grade");

C语言提供了switch语句作为这类级联式if语句的替代。下面的switch语句等价于前面的级联式if语句:

switch (grade) { case 4: printf("Excellent"); break; case 3: printf("Good"); break; case 2: printf("Average"); break; case 1: printf("Poor"); break; case 0: printf("Failing"); break; default: printf("Illegal grade"); break; }

执行这条语句时,变量grade的值与4、3、2、1和0进行比较。例如,如果值和4相匹配,那么显示信息Excellent,然后break语句(?6.4节)把控制传递给switch后边的语句。如果grade的值和列出的任何选项都不匹配,那么执行default分支的语句,显示消息Illegal grade。

switch语句往往比级联式if语句更容易阅读。此外,switch语句往往比if语句执行速度快,特别是在有许多情况要判定的时候。

switch语句最常用的格式如下:

[switch语句]

switch (表达式) { case 常量表达式 : 语句 ... case 常量表达式 : 语句 default : 语句 }

switch语句十分复杂,下面逐一看一下它的组成部分。

  • 控制表达式。switch后边必须跟着由圆括号括起来的整型表达式。C语言把字符(?7.3节)当成整数来处理,因此在switch语句中可以对字符进行判定。但是,这不适用于浮点数和字符串。
  • 分支标号。每个分支的开头都有一个标号,格式如下:

    case 常量表达式

    常量表达式(constant expression)很像普通的表达式,只是不能包含变量和函数调用。因此,5是常量表达式,5 + 10也是常量表达式,但n + 10不是常量表达式(除非n是表示常量的宏)。分支标号中常量表达式的值必须是整数(字符也可以)。

  • 语句。每个分支标号的后边可以跟任意数量的语句,并且不需要用花括号把这些语句括起来。(好好享受这一点,这可是C语言中少数几个不需要花括号的地方。)每组语句的最后一条通常是break语句。

C语言不允许有重复的分支标号,但对分支的顺序没有要求,特别是default分支不一定要放置在最后。

case后边只可以跟随一个常量表达式。但是,多个分支标号可以放置在同一组语句的前面:

switch (grade) { case 4: case 3: case 2: case 1: printf("Passing"); break; case 0: printf("Failing"); break; default: printf("Illegal grade"); break; }

为了节省空间,程序员有时还会把几个分支标号放置在同一行中:

switch (grade) { case 4: case 3: case 2: case l: printf("Passing"); break; case 0: printf("Failing"); break; default: printf("Illegal grade"); break; }

可惜的是,C语言不像有些编程语言那样有表示数值范围的分支标号。

switch语句不要求一定有default分支。如果default不存在,而且控制表达式的值和任何一个分支标号都不匹配的话,控制会直接传给switch语句后面的语句。

break语句的作用

现在仔细讨论一下break语句。正如已经看到的那样,执行break语句会导致程序“跳”出switch语句,继续执行switch后面的语句。

需要break语句是由于switch语句实际上是一种“基于计算的跳转”。对控制表达式求值时,控制会跳转到与switch表达式的值相匹配的分支标号处。分支标号只是一个说明switch内部位置的标记。在执行完分支中的最后一条语句后,程序控制“向下跳转”到下一个分支的第一条语句上,忽略下一个分支的分支标号。如果没有break语句(或者其他某种跳转语句),控制将从一个分支继续流向下一个分支。思考下面的switch语句:

switch (grade) { case 4: printf("Excellent"); case 3: printf("Good"); case 2: printf("Average"); case 1: printf("Poor"); case 0: printf("Fai1ing"); default: printf("Illegal grade"); }

如果grade的值为3,那么显示的消息是

GoodAveragePoorFailingIllegal grade

忘记使用break语句是编程时常犯的错误。虽然有时会故意忽略break以便多个分支共享代码,但通常情况下省略break是因为疏忽。

故意从一个分支跳转到下一个分支的情况是非常少见的,因此明确指出故意省略break语句的情况是一个好主意:

switch (grade) { case 4: case 3: case 2: case 1: num_passing++; /* FALL THROUGH */ case 0: total_grades++; break; }

如果没有注释,将来可能有人会通过增加多余的break语句来修正“错误”。

虽然switch语句中的最后一个分支不需要break语句,但通常还是会放一个break语句在那里,以防止将来增加分支数目时出现“丢失break”的问题。

程序 显示法定格式的日期

英文合同和其他法律文档中经常使用下列日期格式:

Dated this_________day of____________, 20___.

下面编写程序,用这种格式来显示英文日期。用户以月/日/年的格式输入日期,然后计算机显示出“法定”格式的日期:

Enter date (mm/dd/yy): 7/19/14 Dated this 19th day of July, 2014.

可以使用printf函数实现格式化的大部分工作。然而,还有两个问题:如何为日添加“th”(或者“st”“nd”“rd”),以及如何用单词而不是数字来显示月份。幸运的是,switch语句可以很好地解决这两个问题:我们用一个switch语句显示日期的后缀,再用另一个switch语句显示出月份名。

date.c

/* Prints a date in legal form */ #include int main(void) { int month, day, year; printf("Enter date (mm/dd/yy): "); scanf("%d /%d /%d", &month, &day, &year); printf("Dated this %d", day); switch (day) { case 1: case 21: case 31: printf("st"); break; case 2: case 22: printf("nd"); break; case 3: case 23: printf("rd"); break; default: printf("th"); break; } printf(" day of "); switch (month) { case 1: printf("January"); break; case 2: printf("February"); break; case 3: printf("March"); break; case 4: printf("April"); break; case 5: printf("May"); break; case 6: printf("June"); break; case 7: printf("July"); break; case 8: printf("August"); break; case 9: printf("September"); break; case 10: printf("October"); break; case 11: printf("November"); break; case 12: printf("December"); break; } printf(", 20%.2d.\n", year); return 0; }

注意,%.2d用于显示年份的最后两位数字。如果用%d代替的话,那么倒数第二位为零的年份会显示不正确(例如2005会显示成205)。

问与答

问:当我用=代替==时,我所用的编译器没有发出警告。是否有办法可以强制编译器注意这类问题?(p.59)

答:下面是一些程序员使用的技巧:他们习惯性地将

if (i == 0) ...

改写成

if (0 == i) ...

现在假设运算符= =意外地写成了=:

if (0 = i) ...

因为不可能给0赋值,所以编译器会产生一条出错消息。我没有用这种技巧,因为我觉得这样会使程序看上去很不自然。而且这种技巧也只能在判定条件中的一个操作数不是左值的时候使用。

幸运的是,许多编译器可以检测出if条件中=运算符的可疑使用。例如,GCC会在选中-Wparentheses选项或-Wall(所有情况都警告)选项时执行这样的检查。GCC允许程序员通过在if条件外面增加一对圆括号的方式来禁用该警告:

if ((i = j)) ...

问:针对复合语句,C语言的书好像使用了多种缩进和放置花括号的风格。哪种风格最好呢?

答:根据The New Hacker's Dictionary的内容,共有4种常见的缩进和放置花括号的风格。

  • K&R 风格,它是Brian W. Kernighan和Dennis M. Ritchie合著的《C程序设计语言》一书中使用的风格,也是本书中的程序所采用的风格。在此风格中,左花括号出现在行的末尾:

    if (line_num == MAX_LINES) { line_num = 0; page_num++; }

    K&R 风格通过不让左花括号单独占一行来保持程序紧凑。缺点是可能很难找到左花括号。(我认为这不是什么问题,因为内部语句的缩进可以清楚地显示出左花括号的位置。)顺便提一下,K&R 风格是Java中最常使用的。
    ?

  • Allman风格,它是以Eric Allman(sendmail和其他UNIX工具的作者)的姓氏命名的。每个左花括号单独占一行:

    if (line_num == MAX_LINES) { line_num = 0; page_num++; }

    这种风格易于检查括号的匹配。
    ?

  • Whitesmiths风格,它是因Whitesmiths C编译器而普及起来的,其中规定花括号采用缩进格式:

    if (line_num == MAX_LINES) { line_num = 0; page_num++; }

    ?

  • GNU风格,它用于GNU项目所开发的软件中,其中的花括号采用缩进形式,然后再进一步缩进内层的语句:

    if (line_num == MAX_LINES) { line_num = 0; page_num++; }

    使用哪种风格因人而异,没有证据表明哪种风格明显比其他风格更好。无论如何,坚持使用某种风格比选择适当的风格更重要。

问:如果i是int型变量,f是float型变量,那么条件表达式(i > 0 ? i: f)是哪一种类型的值?

答:如问题所述,当int型和float型的值混合在一个条件表达式中时,表达式的类型为float型。如果i > 0为真,那么变量i转换为float型后的值就是表达式的值。

问:为什么C99没有为布尔类型取一个更好的名字?(p.65)

答:_Bool不算好名字,是吧? C99没有采用bool和boolean这些更常见的名字,这是因为现有的C程序中可能已经定义了这些名字,再使用可能会导致编译错误。

问:明白了。但是为什么_Bool这个名字就不会影响已有的程序呢?

答:C89标准指出,以下划线开头,后跟一个大写字母的名字是保留字,程序员不应该使用。

* 问:本章中的switch语句模板被称为“最常用格式”,是否还有其他格式呢?(p.67)

答:事实上,本章中描述的switch语句格式极具通用性,但switch语句还有更具通用性的格式。例如,switch语句包含的标号(?6.4节)前面可以不放置单词case,这可能会产生有趣的(?)陷阱。假设意外地拼错了单词default:

switch (...) { ... defualt: ... }

因为编译器认为defualt是一个普通标号,所以不会检查出错误。

问:我已见过一些缩进switch语句的方法,哪种方法最好呢?

答:至少有两种常用方法。一种方法是在每个分支的分支标号后边放置语句:

switch (coin) { case 1: printf("Cent"); break; case 5: printf("Nickel"); break; case 10: printf("Dime"); break; case 25: printf("Quarter"); break; }

如果每个分支只有一个简单操作(本例中只有一次对printf函数的调用),break语句甚至可以和操作放在同一行中:

switch (coin) { case 1: printf("Cent"); break; case 5: printf("Nickel"); break; case 10: printf("Dime"); break; case 25: printf("Quarter"); break; }

另一种方法是把语句放在分支标号的下面,并且要对语句进行缩进,从而凸显出分支标号:

switch (coin) { case 1: printf("Cent"); break; case 5: printf("Nickel"); break; case 10: printf("Dime"); break; case 25: printf("Quarter"); break; }

在这种格式的一种变体中,每一个分支标号都和单词switch对齐排列。

当每个分支中的语句都较短小而且分支数相对较少时,使用第一种方法是非常好的。第二种方法更加适合于每个分支中的语句都很复杂或整体数量较多的switch语句。

练习题

5.1节

1. 下列代码片段给出了关系运算符和判等运算符的示例。假设i、j和k都是int型变量,请给出每道题的输出结果。

(a) i = 2; j = 3;

  k = i * j == 6;

   printf("%d", k);

(b) i = 5; j = 10; k = 1;

  printf("%d", k > i < j);

(c) i = 3; j = 2; k = 1;

  printf("%d", i < j == j < k);

(d) i = 3; j = 4; k = 5;

  printf("%d", i % j + i < k);

2. 下列代码片段给出了逻辑运算符的示例。假设i、j和k都是int型变量,请给出每道题的输出结果。

(a) i = 10; j = 5;

  printf("%d", !i < j);

(b) i = 2; j = 1;

  printf("%d", !!i + !j);

(c) i = 5; j = 0; k = -5;

  printf("%d", i && j || k);

(d) i = 1; j = 2; k = 3;

  printf("%d", i < j || k);

* 3. 下列代码片段给出了逻辑表达式的短路行为的示例。假设i、j和k都是int型变量,请给出每道题的输出结果。

(a) i = 3; j = 4; k = 5;

  printf("%d", i < j || ++j < k);

  printf("%d %d %d", i, j, k);

(b) i = 7; j = 8; k = 9;

  printf("%d", i – 7 && j++ < k);

  printf("%d %d %d", i, j, k);

(c) i = 7; j = 8; k = 9;

  printf("%d", (i = j) || (j = k));

  printf("%d %d %d", i, j, k);

(d) i = 1; j = 1; k = 1;

  printf("%d", ++i || ++j && ++k);

  printf("%d %d %d", i, j, k);

* 4. 编写一个表达式,要求这个表达式根据i小于、等于、大于j这3种情况,分别取值为-1、0、+1。

5.2节

* 5. 下面的if语句在C语言中是否合法?

if (n >= 1 <= 10) printf("n is between 1 and 10\n");

如果合法,那么当n等于0时会发生什么?

* 6. 下面的if语句在C语言中是否合法?

if (n == 1 - 10) printf("n is between 1 and 10\n");

如果合法,那么当n等于5时会发生什么?

7. 如果i的值为17,下面的语句显示的结果是什么?如果i的值为-17,下面的语句显示的结果又是什么?

printf("%d\n", i >= 0 ? i : -i);

8. 下面的if语句不需要这么复杂,请尽可能地加以简化。

if (age >= 13) if (age <= 19) teenager = true; else teenager = false; else if (age < 13) teenager = false;

9. 下面两个if语句是否等价?如果不等价,为什么?

if (score >= 90) if (score < 60) printf("A"); printf("F"); else if (score >= 80) else if (score < 70) printf("B"); printf("D"); else if (score >= 70) else if (score < 80) printf("C"); printf("C"); else if (score >= 60) else if (score < 90) printf("D"); printf("B"); else else printf("F"); printf("A");

5.3节

* 10. 下面的代码片段的输出结果是什么?(假设i是整型变量。)

i = 1; switch (i % 3) { case 0: printf("zero"); case 1: printf("one"); case 2: printf("two"); }

11. 表5-5给出了美国佐治亚州的电话区号,以及每个区号所对应地区最大的城市。

表5-5 美国佐治亚州电话区号及对应的主要城市

编写一个switch语句,其控制表达式是变量area_code。如果area_code的值在表中,switch语句打印出相应的城市名;否则switch语句显示消息“Area code not recognized”。使用5.3节讨论的方法,使switch语句尽可能地简单。

编程题

1. 编写一个程序,确定一个数的位数:

Enter a number: 374 The number 374 has 3 digits

假设输入的数最多不超过4位。提示:利用if语句进行数的判定。例如,如果数在0和9之间,那么位数为1;如果数在10和99之间,那么位数为2。

2. 编写一个程序,要求用户输入24小时制的时间,然后显示12小时制的格式:

Enter a 24-hour time: 21:11 Equivalent 12-hour time: 9:11 PM

注意不要把12:00显示成0:00。

3. 修改5.2节的broker.c程序,做出下面两种改变。

(a) 不再直接输入交易额,而是要求用户输入股票的数量和每股的价格。

(b) 增加语句用来计算经纪人竞争对手的佣金(少于2000股时佣金为每股33美元+3美分,2000股或更多股时佣金为每股33美元+2美分)。在显示原有经纪人佣金的同时,也显示出竞争对手的佣金。

4. 表5-6中展示了用于测量风力的蒲福风级的简化版本。

表5-6 简化的蒲福风级

编写一个程序,要求用户输入风速(海里/小时),然后显示相应的描述。

5. 在美国的某个州,单身居民需要缴纳表5-7中列出的所得税。

表5-7 美国某州单身居民个人所得税缴纳标准

编写一个程序,要求用户输入应纳税所得额,然后显示税金。

6. 修改4.1节的upc.c程序,使其可以检测UPC的有效性。在用户输入UPC后,程序将显示VALID或NOT VALID。

7. 编写一个程序,从用户输入的4个整数中找出最大值和最小值:

Enter four integers: 21 43 10 35 Largest: 43 Smallest: 10

要求尽可能少用if语句。提示:4条if语句就足够了。

8. 表5-8给出了从一个城市到另一个城市的每日航班信息。

表5-8 每日航班信息

编写一个程序,要求用户输入一个时间(用24小时制的时分表示)。程序选择起飞时间与用户输入最接近的航班,显示出相应的起飞时间和抵达时间。

Enter a 24-hour time: 13:15 Closest departure time is 12:47 p.m., arriving at 3:00 p.m.

提示:把输入用从午夜开始的分钟数表示。将这个时间与表格里(也用从午夜开始的分钟数表示)的起飞时间相比。例如,13:15从午夜开始是13×60+15 = 795分钟,与下午12:47(从午夜开始是767分钟)最接近。

9. 编写一个程序,提示用户输入两个日期,然后显示哪一个日期更早:

Enter first date (mm/dd/yy): 3/6/08 Enter second date (mm/dd/yy): 5/17/07 5/17/07 is earlier than 3/6/08

10. 利用switch语句编写一个程序,把用数字表示的成绩转换为字母表示的等级。

Enter numerical grade: 84 Letter grade: B

使用下面的等级评定规则:A为90~100,B为80~89,C为70~79,D为60~69,F为0~59。如果成绩高于100或低于0,则显示出错消息。提示:把成绩拆分成2个数字,然后使用switch语句判定十位上的数字。

11. 编写一个程序,要求用户输入一个两位数,然后显示该数的英文单词:

Enter a two-digit number: 45 You entered the number forty-five.

提示:把数分解为两个数字。用一个switch语句显示第一位数字对应的单词(“twenty”“thirty”等),用第二个switch语句显示第二位数字对应的单词。不要忘记11~19需要特殊处理。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言