零基础入门学习C语言:带你学C带你飞
上QQ阅读APP看书,第一时间看更新

第2章 了不起的分支和循环

2.1 分支结构

视频讲解

目前所有的例子都是遵循最简单的程序设计结构——顺序结构,也就是所谓的“一条路走到黑”,程序所做的事情就是从上到下依次执行每一条语句。

但是在现实生活中,我们的程序常常需要进行判断和选择。比如,判断用户的年龄是否满18周岁?判断用户的性别是否为女生?判断用户输入的年份是否为闰年?这些都是需要让程序进行判断和选择的,称为分支结构程序设计。

2.1.1 关系运算符

在C语言中,使用关系运算符来比较两个数的大小关系,如图2-1所示。

图2-1 关系运算符

关系运算符都是双目运算符,其结合性均为从左到右。值得注意的是,关系运算符的优先级低于算术运算符,高于赋值运算符。另外,图2-1中的这6个关系运算符,<、<=、>、>=的优先级相同,高于==和!=(==和!=的优先级相同)。

2.1.2 关系表达式

使用关系运算符将两边的变量、数据或表达式连接起来,称为关系表达式。下面是一些关系表达式的例子。

·1 < 2。

·a > b。

·a <= 1+b。

·'a'+'b' <= 'c'。

·(a=3) > (b=5)。

关系表达式得到的值是一个逻辑值,即判断结果为“真”或“假”。如果结果为“真”,关系表达式的值为1;如果为“假”,关系表达式的值则为0。下面是两个例子。

·关系表达式1 < 2的值为真,所以该关系表达式的值为1。

·关系表达式'a'+'b' <= 'c',因为字符'a'、'b'、'c'对应的ASCII码分别是97、98、99,即97+98 <= 99,值为假,所以该表达式的值为0。

请看下面例子:

程序实现如下:

2.1.3 逻辑运算符

关系运算符获得的是一个逻辑值,逻辑值只有“真”或“假”,没有什么“可能”“也许”“大概”等模棱两可的东西。

在C语言中,如果需要同时对两个或两个以上的关系表达式进行判断,那么就需要用到逻辑运算符。比如,一个程序限定只能由年满18周岁的女生使用,那么用户想要使用这个程序,就需要满足以下两个条件。

(1)年满18周岁;

(2)女生。

C语言提供了三种逻辑运算符,见表2-1。

表2-1 逻辑运算符

注意:

逻辑运算符的优先级是不一样的,作为单目运算符的逻辑非(!)优先级最高,接下来是逻辑与(&&),最后才是逻辑或(||)。

2.1.4 逻辑表达式

用逻辑运算符将两边的变量、数据或表达式连接起来,称为逻辑表达式,下面是一些逻辑表达式的例子。

·3 > 1 && 1 < 2。

·3+1 || 2 == 0。

·!(a+b)。

·!0+1 < 1 || !(3+4)。

·'a' - 'b' && 'c'。

1)3 > 1 && 1 < 2。

由于关系运算符的优先级比逻辑运算符高,所以先运算两个关系运算符,变成1 &&1,结果为1。

2)3+1 || 2 == 0

有读者看到这个就懵了!不是说逻辑运算符的两边只能是逻辑值吗?逻辑值不是只能是真或假,用1和0来表示吗?那么这个3+1是什么意思?其实是这样的,关系表达式和逻辑表达式得到的值都是一个逻辑值,也就是表示真的1和表示假的0。但是当判断一个值的逻辑值的时候,以0表示假,以任何非0的数表示真。一个是编译系统告诉我们的结果,一个是让编译系统去判断,两者方向不同。因此3+1=4,为非0值,所以逻辑或的左边为真,右边2==0明显是不成立的,右边为假,但对于逻辑或来说,只要存在一个为真,结果就为真。

3)!(a+b)

这里有个小括号,根据优先级规则先计算小括号里面的内容。也就是将变量a的值和变量b的值相加,如果它们的和为0,那么逻辑非的结果就是真;如果它们的和不为0,那么逻辑非的结果则是假。

4)!0+1 < 1 || !(3+4)

0的逻辑非结果为真,也就是1,1+1 < 1明显是不成立的,所以逻辑或的左边为假,右边3+4的值是7,非0,所以逻辑非的结果是假。逻辑或的左右两边均为假,则结果为假。

5)'a' - 'b' && 'c'

在编译器的“眼中”所有的字符对应的都是ASCII码,因为字符'a'、'b'、'c'对应的ASCII码分别是97、98、99,所以逻辑与左边97 - 98的值为非0,表示真,右边99也是非0,也表示真,因此结果为真。

下面用程序验证上述结论:

程序实现如下:

2.1.5 短路求值

短路求值(short-circuit evaluation)又称最小化求值,是一种逻辑运算符的求值策略。只有当第一个运算数的值无法确定逻辑运算的结果时,才对第二个运算数进行求值。

C语言对于逻辑与、逻辑或均采用短路求值的方式,如果没有注意到这一点,程序就很可能出现隐含的BUG,比如下面这段程序:

程序实现如下:

第一个逻辑表达式:因为a先被赋值为0,则逻辑与左边为假,所以根据短路求值的原则,右边无须再进行计算(b变量的值没有被改变),直接得到该逻辑表达式的值为假,也就是0。

第二个逻辑表达式:因为a先被赋值为1,则逻辑或左边为真,所以根据短路求值的原则,右边无须再进行计算(b变量的值没有被改变),直接得到该逻辑表达式的值为真,也就是1。

2.1.6 if语句

视频讲解

分支结构的作用就是让C语言的代码根据条件执行不同的语句或程序块,但光有关系表达式和逻辑表达式还不足以实现,实现分支结构还需要学习一个新的语句——if语句。

if语句的实现有三种形式,下面逐一介绍。

1.第一种

第一种if语句是最简单的,if后边小括号内填写返回逻辑值的表达式,当然也可以直接填入一个逻辑值,当填入的这个值为非0的时候,编译系统就会认为这个逻辑值是真;只有当填入0的时候,才被认为是假。

下面通过例子演示一下:

程序实现如下:

程序运行之后,如果输入的值是18,即if后边小括号内的表达式的值为真,那么执行if语句的内容;如果输入的值是16,即表达式的值为假,if语句的内容则不被执行。

上面例子中属于if语句的内容是用大括号括起来的,并且做了缩进。但是乐于尝试的读者可能发现下面代码也同样可以执行:

程序实现如下:

上面代码可以正常执行,是因为C语言不会强制要求代码一定要写得很规范,但是适当的缩进可以让我们的代码一目了然。除非是想参加C语言国际混乱代码大赛(The International Obfuscated C Code Contest),这是一项国际程序设计赛事,从1984年开始,每年举办一次,目的是写出最有创意的、最难以理解的C语言代码,那么你完全可以把C语言的代码写成如图2-2所示这样。

图2-2 C语言国际混乱代码大赛的代码

回归主题,大括号的作用是什么呢?

在C语言中,使用分号结束一个语句。在表达式后边加一个分号,就变成了一个完整的C语言语句。如果使用大括号将几个语句包括起来,那么这几个语句就构成了程序块。一个程序块在编译系统看来,就是一个整体。

什么时候需要将几个语句变成一个整体呢?

比如,希望if后边的表达式为真的时候,执行几个语句,那么就需要用大括号将它们包括起来:

程序实现如下:

第一种if语句表达出来的意思是“如果条件成立,就……”。

2.第二种

接下来学习第二种if语句,它表达出来的意思是“如果条件成立,就……;否则就……”。

请看下面的例子:

程序实现如下:

3.第三种

if语句形式允许扩充各种条件的判断,语法格式是:

下面这个小程序实现的功能是按成绩评级:

·90分及以上:A。

·80~89分:B。

·70~79分:C。

·60~69分:D。

·低于60分:E。

程序实现如下:

2.1.7 switch语句

视频讲解

C语言还提供了另外一种多分支选择的语句——switch语句,语法格式是:

这里每个case后边的常量是匹配switch后边表达式的值,如果表达式计算出来的值与常量表达式2的值相等,那么就直接跳转到case常量表达式2的位置开始执行,如果上边所有的case均没有匹配,那么就执行default后面的内容。但是default是可选的,如果没有default,并且上边所有的case均不匹配,那么switch语句不执行任何动作。

从语法结构上来看,switch语句要比if-else-if更为简洁。现在尝试使用switch语句代替if-else-if语句,写一个通过评级反推出分数范围的程序。

程序实现如下:

这可不是我们想要的结果,但是问题出在哪儿呢?

问题就出在C语言并没有我们想象得那么“智能”,这些case和default,它们事实上都是“标签”,用来标志一个位置而已。switch跳到某个位置之后,就会一直往下执行,所以这里还需要配合一个break语句,让代码在适当的位置跳出switch语句。

当程序执行到break语句的时候就不会再继续往下走了,它会跳出switch语句并开始执行下一条语句。将代码修改如下:

程序实现如下:

2.1.8 分支结构的嵌套

如果在一个if语句中包含另一个if语句,就称为if语句的嵌套,也叫分支结构的嵌套,如图2-3所示。

图2-3 if语句的嵌套

下面请根据图2-4所示的流程图编写代码。

图2-4 代码流程图

流程图是算法、工作流或流程的一种框图表示,它以不同类型的框代表不同种类的步骤,每两个步骤之间则以箭头连接。这种表示方法便于说明解决已知问题的方法,广泛应用于分析、设计、记录及操控许多领域的流程或程序。

【扩展阅读】如果不清楚流程图怎么设计,可访问http://bbs.fishc.com/thread-68123-1-1.html或扫描图2-5所示二维码进行查阅。

图2-5 使用流程图来描述程序

实现代码如下:

程序实现如下:

2.1.9 悬挂else

给大家举个例子说明什么是悬挂else。比如,你计划约小花同学周末一起去看电影,接下来就有两个问题了:第一,小花那天有没有空;第二,那天下不下雨。但主要还是看第一个问题,只要小花有空,下雨可以带伞。写成代码如下:

程序实现如下:

这样看上去似乎没什么问题,但如果这样输入:

天呐,在女神有空,天公做美的情况下,居然打印“女神没空!!!!!T_T”,这是为什么呢?

分析代码:这段代码本意应该有两种情况,女神有空或没空。对于女神有空的情况,如果天下雨,就提醒你带伞;对于女神没空的情况,就很遗憾了。然而,这段代码实际上所做的却并非如此,原因在于C语言中有这样的规则:else始终与最接近的if匹配,所以你们都被程序的缩进骗了,事实上编译器看到程序的代码是这样的:

解决的方法很简单,加上一个大括号即可:

所以,最好的习惯就是if-else语句后面,无论是一个语句还是多个语句,都使用大括号括把它们包括起来。只要养成这样的习惯,就可以防止发生类似悬挂else这样的问题。

代码像下面这样写会更好一些:

如果你的屏幕偏小,上边的大括号又占了太多的纵向空间,导致一个屏幕页面可显示的内容十分有限,那么可以这样写:

上面哪一种写法都可以,但是选择一种之后就要坚持一直使用它,不要换来换去,不然自己写的代码,过段时间自己看着都别扭了。

2.1.10 等于号带来的问题

分支结构的代码很容易陷入另一个陷阱,那就是等于号带来的问题。作为条件的关系表达式中,经常需要判断一个值是否与另一个值相等。继续刚才的故事,比如看完电影,你就要判断小花是否有男朋友了,代码如下:

正当小花对你说没有(N)的时候,程序打印出来的却是:

这个问题大家应该马上就能检查出来,因为我们标题写着呢:等于号带来的问题。

if (hasBF='Y')本意是判断hasBF变量的值是否等于'Y',结果由于少写了一个等于号,就变成了赋值表达式。所以无论hasBF变量里边存放的值是什么,在这里都被赋值为'Y'。

大家千万不要小看这个问题,如果你的代码变得庞大,那么从奇怪的结果中要反推出问题的所在,就变得异常困难了。那么有没有办法解决这个问题呢?其实是有的,只需要将两个操作数调一下位置即可:

将值写在表达式的左边,将变量写在表达式的右边。这样做的好处是让编译器帮你做检查。因为如果少录入了一个等于号,“'Y'=hasBF”这个表达式是不成立的,因为编译器永远也不会允许给一个常量赋值。