7.2 在if语句中添加else关键字

简单形式的if语句使您可以选择执行一条语句(可能是复合语句)或忽略它。C还可以通过使用if else形式来在两个语句间做出选择。让我们使用if else形式修改程序清单7.1中略显笨拙的程序段。

阅读 ‧ 电子书库

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

如果程序发现all_days是不等于0的,那么它应该知道all_days肯定等于0,这样就不必重新判断。使用if else,您可以将这段程序改写为如下形式:

阅读 ‧ 电子书库

仅仅进行了一次判断。如果if的判断表达式为真,则打印温度数据;如果其值为假,则打印警告消息。

注意,if else语句的通用形式为:

阅读 ‧ 电子书库

如果expression为真(非零),就执行statement1;如果expression为假或零,则执行跟在else后的那一条语句(statement2)。语句可以是简单的或复合的。C不要求缩排,但这是标准的风格。缩排使语句依赖于判断而执行这一事实显得一目了然。

如果希望在if和else之间有多条语句,必须使用花括号创建一个代码块。下面的结构违反了C语法,因为编译器期望if和else之间只有一条语句(单条的或复合的):

阅读 ‧ 电子书库

编译器会把printf()语句看作if语句的部分,将x++;语句看作一条单独的语句,而不把它作为if语句的一部分。然后会认为else没有所属的if,这是个错误。应该使用这种形式:

阅读 ‧ 电子书库

if语句使您能够选择是否执行某个动作。if else语句使您可以在两个动作之间进行选择。图7.1将两种语句做了比较。

阅读 ‧ 电子书库

图7.1 if和if else
7.2.1 另一个例子:介绍getchar()和putchar()

前面的多数程序所输入的内容都是数字。为了练习使用其他的形式,让我们来看一个面向字符的例子。您已经知道了怎样使用scanf()和printf()来以%c说明符读写字符,现在我们将接触专门为面向字符I/O而设计的一对C函数:getchar()和putchar()。

getchar()函数没有参数,它返回来自输入设备的下一个字符。例如,下面的语句读取下一个输入字符并将它的值赋给变量ch:

阅读 ‧ 电子书库

该语句与下面的语句有同样的效果:

阅读 ‧ 电子书库

putchar()函数打印它的参数。例如,下面的语句将先前赋给ch的值作为字符打印出来:

阅读 ‧ 电子书库

该语句与下面的语句有同样的效果:

阅读 ‧ 电子书库

因为这些函数仅仅处理字符,所以它们比更通用的scanf()和printf()函数更快而且更简洁。同样,注意到它们不需要格式说明符,因为它们只对字符起作用。这两个函数通常都在stdio.h文件中定义(而且,它们通常只是预处理器宏(macro),而不是真正的函数;我们将在16章“C预处理器和C库”中讨论类似函数的宏)。

下面将通过一个程序来说明这些函数是怎么工作的。这个程序再现输入的一行,但将每个非空格字符替换为该字符在ASCⅡ码序列中的后一个字符。空格还是作为空格重现。可以将希望程序做出的响应表述为“如果字符是空格,打印之;否则,打印它在ASCII序列中的下一个字符”。

C的代码看起来很像该陈述,这一点你可以在程序清单7.2中看到。

程序清单7.2 cypher1.c程序

阅读 ‧ 电子书库

下面是一个运行示例:

阅读 ‧ 电子书库

把这个循环和程序清单7.1中的循环进行比较。程序清单7.1中用scanf()返回的状态而不是输入元素的值来决定何时终止循环。而在程序清单7.2中,则是由输入元素的值本身决定何时终止循环,这种不同导致这个循环结构与前面的略有不同。这里,一个“读”语句在循环之前,另一个“读”语句在每次循环的结尾。然而,C灵活的语法使您可以通过将读取和判断合并为单个表达式来仿效程序清单7.1。那也就是说,您可以把这种形式的循环:

阅读 ‧ 电子书库

替换为下面的形式:

阅读 ‧ 电子书库

关键的一行是:

阅读 ‧ 电子书库

这体现了典型的C编程风格:将两个动作合并为一个表达式。C自由的格式可以使这行语句中的每个部分更清晰:

阅读 ‧ 电子书库

该动作将一个值赋给ch并将这个值与换行符进行比较。圆括号括住ch=getchar()使之成为“!=”运算符的左操作数。为了求得该表达式的值,计算机首先必须调用getchar()函数,然后将它的返回值赋给ch。因为赋值表达式的值就等于表达式左侧数的值,所以ch=getchar()的值就等于ch的新值。因此,读取ch之后,判断条件缩减为ch!=‘\n’,也就是说,ch不等于换行符。

这种独特的习惯用法在C编程中很常见,所以您应该熟悉它。也一定要记住适当地使用圆括号来组合子表达式。

所有的圆括号都是必需的。假如错误地这样使用:

阅读 ‧ 电子书库

!=运算符的优先级比=的要高,所以首先被计算的表达式是getchar()!=‘\n’。因为这是一个关系表达式,因此其值是0或者1(真或假)。然后这个值被赋给ch。遗漏了圆括号就意味着ch的值被赋为0或1,而不是getchar()返回的值。显然这不是我们所希望的。

下面的语句:

阅读 ‧ 电子书库

再次表明了字符实际上是作为整数被存储的。在表达式ch+1中,ch被扩展为int型以用于计算,然后int型的计算结果被传给putchar()。这个函数接受一个int型的参数,但仅仅使用最后一个字节来决定显示哪个字符。

7.2.2 ctype.h系列字符函数

注意到程序清单7.2的输出显示出句号被转换为斜杠;这是因为斜杠字符对应的ASCⅡ码比句号的ASCⅡ码大1。但是如果程序指明只转换字母,将所有的非字母字符(而不只是空格符)保留下来,将会更好。本章稍后讨论的逻辑运算符提供一种方法以判断字符是否不是空格、逗号,如此等等,但是罗列所有的可能性太麻烦。幸好,ANSI C有一系列标准的函数可以用来分析字符;ctype.h头文件包含了这些函数的原型。这些函数接受一个字符作为参数,如果该字符属于某特定的种类则返回非零值(真),否则返回零(假)。例如,如果isalpha()函数的参数是个字母,则它返回一个非零值。程序清单7.3通过使用该函数扩展了程序清单7.2;它也加入了刚才讨论过的精简后的循环结构。

程序清单7.3 cypher2.c程序

阅读 ‧ 电子书库

下面是一个运行示例;注意大写字母和小写字母被译码,而空格和标点符号则没有:

阅读 ‧ 电子书库

表7.1和表7.2列出了ctype.h头文件所包含的一些函数。有些函数提到了本地化,这里指的是能够指定一个本地以修改或扩展C的基本用法的C工具(例如,许多国家在书写十进制小数的时候,使用逗号来替代小数点,于是,特定的本地化工具就能够指定逗号在浮点小数的输出中起到小数点一样的作用。因此,就显示为123,45)。注意,映射函数并不改变原始的参数,它们只返回改变后的值。也就是说,下列语句不改变ch的值:

阅读 ‧ 电子书库

没有改变ch,若要改变ch,可以这样做:

阅读 ‧ 电子书库

表7.1 ctype.h的字符判断函数

 

 

函数名 为如下参数时,返回值为真
isalnum() 字母数字(字母或数字)
isalpha() 字母
isblank() 一个标准的空白字符(空格、水平制表符或者换行)或者任何其他本地化指定为空白符的字符
iscntrl() 控制符,例如Ctrl+B
isdigit() 阿拉伯数字
isgraph() 除空格符之外的所有可打印字符
islower() 小写字母
isprint() 可打印字符
ispunct() 标点符号(除空格和字母数字外的可打印字符)
isspace() 空白字符:空格、换行、走纸、回车、垂直制表符、水平制表符、或可能是其他本地化定义的字符
isupper() 大写字母
isxdigit() 十六进制数字字符
表7.2 ctype.h的字符映射函数

 

 

函数名 动  作
tolower() 如果参数是大写字符,返回相应的小写字符;否则,返回原始参数
toupper() 如果参数是小写字符,返回相应的大写字符;否则,返回原始参数
7.2.3 多重选择else if

日常生活经常会给我们提供两个以上的选择。可以用else if扩展if else结构来适应这种情况。我们来看一个例子。公用事业公司通常使计费依赖于客户使用能量的总数。这里有某个公司电力计费的费率,单位是千瓦时(kWh):

 

 

第一个 360kWh: 每kWh $0.12589
下一个 320kWh: 每kWh $0.17901
超过 680kWh: 毎$0.20971

如果您比较在意用电管理,可以编写程序来计算您的用电费用。程序清单7.4所示程序就是完成这一任务的第一步。

程序清单7.4 electric.c程序

阅读 ‧ 电子书库

阅读 ‧ 电子书库

下面是一个输出示例:

阅读 ‧ 电子书库

程序清单7.4用符号常量表示费率,以便这些常量可以很方便地被放置在一起。这样一旦电力公司改变费率(这是很可能的),就很容易更新这些被放在一起的费率。该清单也用符号表示了费率的分界点。它们也有可能改变。BASE1和BASE2根据费率和分界点来表示。这样,如果费率或分界点改变了,它们也会自动地更新。您可能回想起预处理器是不做计算的。程序中BASE1出现的地方将用0.12589*360.0代替。不用担心;编译器会求得该表达式的数值(45.3204)以便最终的程序代码使用45.3204而不是一个计算式。

程序的流程是简单明了的。该程序按照kwh的数值选择三个公式中的一个。图7.2示意了这个流程。应该特别注意的是仅当kwh大于360时程序才到达第一个else。所以,像程序注释所注明的那样,else if(kwh<=BREAK2)行实际上相当于要求kwh在360和680之间。同样,仅当kwh超过680,才能够到达最后一个else。最后请注意,BASE1和BASE2分别代表前360和680千瓦时的总费用。因此,当用电量超过这些值时,仅需要加上额外的费用。

阅读 ‧ 电子书库

图7.2 程序清单7.4 (electric.c)的程序流程

实际上,else if是您已经学过的形式的一种变化。例如,该程序的核心部分只不过是下面语句的另一种写法:

阅读 ‧ 电子书库

也就是说,程序所包含的一个if else语句是另一个if else语句的else语句部分。我们称第二个if else语句被嵌套(nested)在第一个里面。回忆一下,整个if else结构作为一条语句,这就是为什么不必将被嵌套的if else放在花括号中。然而使用花括号可以更清楚地表明这种特殊格式的含义。

两种形式完全等价。惟一的区别在于空格和换行的使用位置,而编译器会忽略这些差别。尽管如此,第一种形式还是更好些,因为它更清晰地展示出您做出了三种选择。这种格式更易于快速浏览程序时看清楚各个选择。只在必要的时候使用缩排的嵌套格式,比如在必须判断两个单独的量时。这种情形的一个例子是仅在夏季对超过680千瓦时的用电量加收10%的额外费用。

如同下段所展示的,可以把多个(当然,需要在编译器的限制范围内)所需的else if语句连成一串使用。

阅读 ‧ 电子书库

(这很可能是某个游戏程序的一部分,bonus描述您为下一轮游戏所获得的光子炸弹或食品的多少。)

说到编译器的限制,C99标准要求编译器最少支持127层嵌套。

7.2.4 把else与if配对

当有众多if和else的时候,计算机是怎样判断哪个if对应哪个else的?例如,考虑下面的程序段:

阅读 ‧ 电子书库

什么时候打印“Sorry, you lose a turn!”?是在number小于等于6时,还是在它大于等于12的时候?换句话说,这个else对应第一个还是第二个if呢?答案是,else对应第二个if。也就是说,您可能得到下列响应:

 

 

数 字 响 应
5 没有任何响应
10 You're close!
15 Sorry, you lose a turn!

规则是如果没有花括号指明,else与和它最接近的一个if相匹配(请参见图7.3)。

阅读 ‧ 电子书库

图7.3 if else的配对规则

第一个例子的缩排使得else好像是与第一个if匹配的。但要记住,编译器是忽略缩排的。如果真的希望else与第一个if匹配,可以这样写:

阅读 ‧ 电子书库

现在您可能得到下面的响应;

 

 

数 字 响 应
5 Sorry, you lose a turn!
10 You're close!
15 没有任何响应
7.2.5 多层嵌套的if

前面所看到的if…else if…else序列是嵌套if的一种形式,这是从一系列的二选一中进行选择的。当进行了特定的选择后又导致了额外的选择时将使用另一种嵌套if。例如,一个程序可能用if else来在男和女之间进行选择。在if else中的每个分支又可能包含另一个if else来区别不同收入的群体。

我们试着用这种形式的嵌套if来解决下面的问题:给定一个整数,显示所有能整除它的约数;如果没有约数,则报告该数是个素数。

该问题在编写代码之前需要预先计划好。首先,需要对程序进行总体设计。为了方便,程序需要用一个循环来使您能输入被测试的数。这样,在希望检验一个新数的时候就无须每次都重新运行程序。我们已经为这种循环研究出一个模型:

阅读 ‧ 电子书库

阅读 ‧ 电子书库

回忆一下,通过在循环判断条件中使用scanf(),程序试图读入一个数值并检查循环是否应该终止。下一步,需要计划怎么来找到除数。或许最显而易见的方法是这样的:

阅读 ‧ 电子书库

该循环检查界于2到num之间的所有数,看它们是否可以整除num。不幸的是,这种方法浪费了计算机的时间。我们可以做得更好。例如,考虑搜索144的约数。可以发现144%2为0,这意味着144能被2整除。如果明确地拿144除以2,可以得到72,这也是个约数,因此一次成功的num%div测试可以得到两个约数而不是一个。然而真正的性能指标在于循环测试界限的改变。为了弄清这是怎样工作的,看一下循环中所得到的成对的约数:2和72、3和48、4和36、6和24、8和18、9和16、12和12、16和9、18和8,如此等等。哦,在得到12和12这对数后,又开始得到与已找到的相同的约数(以相反的次序)。无须循环到143,在达到12后就可以停止。这就节省了大量的循环周期!

归纳以后,可以确定必须测试的数只要到num的平方根就可以了,而不必到num。对于像9这样的数,这并不会节省很多时间,但对于像10 000这样的数,差别就很大了。然而,我们不必在程序中计算平方根,而是像下面这样描述判断条件:

阅读 ‧ 电子书库

如果num为144,循环运行到div=12终止。如果num为145,循环运行到div=13终止。

利用这种方法判断胜于利用平方根判断有两个原因。第一,整数乘法比求平方根更快;第二,平方根函数还没有正式介绍。

我们还需要提出两个问题,然后才能准备开始编程。第一,如果测试的数是一个完全平方数怎么办?报告144可被12和12整除显然有些愚蠢,但可以用嵌套if语句来判断div是否等于num/div。如果是,程序将只打印一个约数而不是两个。

阅读 ‧ 电子书库