C++深入体验之旅三:分支结构

来源:互联网 发布:excel导入数据 编辑:程序博客网 时间:2024/05/20 20:02

1.if语句

对于可能发生的事情,我们平时经常会说“如果……,那么……”。语文里,我们叫它条件复句。“如果”之后的内容我们称为条件,当条件满足时,就会发生“那么”之后的事件。我们来看这样一句英语:If mark>90, cout <<”GOOD!” <<endl.把它翻译成中文就是:如果分数大于90,则输出GOOD。
其实在程序设计中,也是用“如果”来描述可能发生的情况的。它和刚才的那句英语很相似,具体的语法格式是:


我们把若干句语句放在一个大括号中,称为语句块。运行到该if语句,当条件满足时,就会执行语句块内的内容。我们也可以用流程图(图4.1.1)来表示if语句。请注意,if语句的结束是没有分号的,分号只是属于语句块中的语句。

一、条件——关系运算

当我们判断一个条件的时候,依赖于这个条件是真是假。说到真和假,我们不难想到布尔型数据(参见3.1),因为它就是分别用0和1来表示真和假。显然条件的位置上应该放置一个布尔型的数据。然而,光靠死板的0和1两个数仍然无法描述可能发生着变化的各种情况。那么我们如何让电脑来根据实际情况做出判断呢?
这里我们要引入关系运算。之前的加减乘除和取余数之类的操作,结果都是整型或实型数据。而关系运算的结果则是布尔型数据,也就是说它们的结果只有两种——真或假。
所谓关系运算,是判断操作符两边数据的关系。这些关系一共有六种,分别是:等于、大于、小于、大于等于、小于等于、不等于。如下表所示:

当操作符两边的数据符合操作符对应的关系时,运算结果为真,否则为假。比如5>1的结果是1(真),’a’==’a’的结果也是1(真);而3<=2的结果为0(假)等等。特别要注意,==和=是两个不同的操作符,前者是判断操作符两边数据的关系,后者是把右面的表达式的结果赋值给左边。
下面我们来看一段程序:(程序4.1.1)
#include "iostream.h" int main() {    int a,b;    cout <<"请输入两个数:";    cin >>a >>b;    if (a>b)//如果a比b大,则将两个数交换    {       int temp;//创建一个临时变量       temp=a;       a=b;       b=temp;    }    cout <<a <<" " <<b <<endl;//将两个数从小到大输出    return 0; } 

第一次运行结果:
请输入两个数:1 5 1 5 

第二次运行结果:
请输入两个数:3 2 2 3 

算法时间:交换
交换是程序设计中最基础最常用的一种操作。它的算法在现实生活中也有着形象的操作。交换两个变量里的数据就好像交换AB两个碗中的水。我们必须再拿一个碗来(临时变量),将A碗里面的水先倒到这个临时的碗里,再将B碗的水倒到空的A碗里,最后把临时碗里的水再倒回B碗,那么就完成了这个工作。对照着这个过程去阅读代码是不是有些理解了呢?至于这个算法的代码,也是非常好记的。记住把临时变量放在首位,然后把任一变量放在等号的右边,下一句语句开头的必然也是这个变量。简单地记就是首尾相连。(程序4.1.1的代码中用相同的颜色表示出“首尾相连”。)
这个程序完成的工作是将两个无序的整数从小到大地输出。即如果第一个数比第二个数大,先交换再输出,否则直接输出。

二、条件——逻辑运算

学校评三好学生,候选人必须要德智体全面发展才能够评上;学校开运动会,运动员只要在某一个项目上是全校第一就能够获得冠军。现实生活中,有些条件会很严格,要数项同时满足时才算符合条件;而有些条件又会很松,只要符合其中某一项就算符合条件了。在程序设计中,我们也会遇到这样的问题。
平时,我们往往是用“并且”和“或”两个词来描述这些情况的。而在程序设计中,我们用逻辑运算来描述。我们平时称它们“与”(相当于并且)、“或”、“非”。“逻辑与”的操作符是&&,“逻辑或”的操作符是||,“非”的操作符是!。下面三个真值表说明了各逻辑运算的结果。

上面两表的第一行和第一列分别是逻辑操作符两侧的值,右下角带有灰色底纹的四格是经过运算后的结果。

如果我们用集合A和集合B分别来描述两个不相同的条件A和B,那么A&&B表示要满足集合A与集合B的交集;A||B表示要满足集合A与集合B的并集;!A表示要满足集合A的补集。
在上一章的3.3简单表达式中,我们提到了运算的次序。在程序设计中,我们把这种运算的次序称作操作符(Operator)的优先级。那么,关系操作符和逻辑操作符的优先级是怎么样的呢?
和简单表达式一样,括号的优先级仍然是最高的。无论什么情况都应该先从左到右地计算括号内的结果。当算术操作符、关系操作符和逻辑操作符处于同一级的括号中时,则分别从左向右地依次进行逻辑非运算、算术运算(遵循算术运算的优先级)、关系运算、逻辑与运算和逻辑或运算。(记作“不算关羽活”)。
下面我们来实践一下,看一段程序:(程序4.1.2)

#include "iostream.h" int main() {    int mark;    cout <<"请输入成绩(0~100): ";    cin >>mark;    if (mark>=80 && mark <=100) cout <<"Good!" <<endl;    if (mark>=60 && mark <80) cout <<"So so" <<endl;    if (mark>=0 && mark <60) cout <<"Please work harder!" <<endl;    if (mark<0 || mark >100) cout <<"ERROR!" <<endl;    return 0; } 

第一次运行结果:
请输入成绩(0~100):100 Good! 

第二次运行结果:
请输入成绩(0~100):75 So so 

第三次运行结果:
请输入成绩(0~100):59 Please work harder! 

第四次运行结果:
请输入成绩(0~100):105 ERROR! 

我们可以看到,将关系运算和逻辑运算配合使用,可以将数值有效地分段。以上这段程序的功能就是按照不同段的数值输出不同的结果,如果输入的数值超出正常的取值范围,则输出出错信息。

算法时间:纠错
熟悉电脑软件的同学都知道,不少软件或程序有时候会有漏洞(Bug),使得程序的安全性或稳定性受到影响。而产生这些漏洞的部分原因就是程序员在设计程序时有所疏漏,忘记了去考虑一些可能引起错误的特殊情况。我们把这些可能引起程序异常的情况称为临界情况。比如在a/b中,b=0就是一种临界情况。如果不考虑到这种情况,则可能导致除数为零而使整个程序崩溃。我们学会了if语句以后就能够从一定程度上避免一些可以预知的错误,把那些临界情况引入纠错程序。(比如输出出错信息,或及时中止程序)

三、&&和||的妙用

有时候我们做数学题目会遇到这样的问题——(1+5*8)/6*0/(5/6+2),当我们发现整个式子是乘式,并且有一个乘数为0的时候,则会不再做更多的计算,把结果脱口而出。因为无论后面的乘数是什么,都无法改变结果了。
根据真值表我们知道,在逻辑与中,只要有一个假则整个表达式的结果为假;在逻辑或中,只要有一个是真则整个表达式的结果为真。我们发现逻辑与、逻辑或和上面所说的例子有着相似之处,那么电脑会不会像我们一样,不再做更多无所谓的计算呢?
答案是肯定的。即在一个或多个连续的逻辑与中,一旦出现一个假,则结果为假,处于该位置以后的条件不再做更多判断;在一个或多个连续的逻辑或中,一旦出现一个真,则结果为真,处于该位置以后的条件也不再做更多判断。比如: 

if (m!=0 && n/m<1){ cout <<”OK” <<endl; } 

当m=0时,电脑不会去尝试用n/m了,而是直接跳过整句语句。这样,我们就能够避免除数为零的错误了。

2.if…else…语句

平时我们在说“如果……,那么……”的时候,还经常和“否则……”连用。比如:如果明天天气好,就开运动会,否则就不开。按照我们上一节学的内容,我们只能这样说:如果明天天气好,就开运动会;如果明天天气不好,就不开运动会。虽然这样也能够把意思表达清楚,但是语句显得冗长,要是条件再多一些则更是杂乱。可见,在程序设计中,如果没有“否则……”语句将会多么麻烦。
和平时说话的习惯一样,“否则”应该与“如果”连用,其语法格式为:

if (条件)   语句块1; else    语句块2; 

运行到该语句时,当条件满足,则运行语句块1中的语句;当条件不满足,则运行语句块2中的语句。我们也可以用流程图(图4.2.1)来直观地表示if……else……语句。和if语句一样,else语句的结尾是没有分号的。
我们来看一段程序:(程序4.2.1)
#include "iostream.h" int main()  {    int a,b,max;    cout <<"请输入两个数:"<<endl;    cin >>a >>b;    if (a>=b)//如果a大于等于b,则把a的值放到max中    {       max=a;    }    else//否则把b的值放到max中    {       max=b;    }    cout <<"较大的数是" <<max <<endl;    return 0; } 
第一次运行结果:
请输入两个数: 1 5 较大的数是5 

第二次运行结果:
请输入两个数: 5 8 较大的数是8 
通过以上程序,我们基本上可以了解if……else……的使用了。

if...else...嵌套使用

 

我们知道了,if语句的主要功能是给程序提供一个分支。然而,有时候程序中仅仅多一个分支是远远不够的,甚至有时候程序的分支会很复杂,要在一个分支里面再有一个分支。根据if语句的流程图,我们不难想象如果要在分支里再形成分支,就应该在if语句中使用if语句。这类在一种语句的内部多次使用这种语句的现象叫做嵌套。
我们来看一段程序,熟悉一下if的嵌套。(程序4.2.2)
#include "iostream.h" int main() {    float a,b;    char oper;//创建一个字符型变量用于存放操作符    cout <<"请输入一个表达式(eg.1+2):" <<endl;    cin >>a >>oper >>b;//输入表达式,操作符处于中间    if (oper=='+')//如果操作符是加号    {       cout <<a <<oper <<b <<'=' <<a+b <<endl;//输出两数的和    }    else//否则    {       if (oper=='-')//如果操作符是减号       {          cout <<a <<oper <<b <<'=' <<a-b <<endl;//输出两数的差       }       else//否则       {          if (oper=='*')//如果操作符是乘号          {             cout <<a <<oper <<b <<'=' <<a*b <<endl;//输出两数的积          }          else//否则          {             if (oper=='/' && b!=0)//如果操作符为除号且除数不为零             {                cout <<a <<oper <<b <<'=' <<a/b <<endl;//输入两数的商             }             else//否则             {                cout <<"出错啦!" <<endl;//操作符不正确或除数为零,输出错误信息             }          }       }    }    return 0; } 

第一次运行结果:
请输入一个表达式(eg.1+2): 1.5+3 1.5+3=4.5 

第二次运行结果:
请输入一个表达式(eg.1+2): 8/0 出错啦! 

第三次运行结果:
请输入一个表达式(eg.1+2): 5p3 出错啦! 

以上这段程序能够基本实现表达式的识别。它所使用的if嵌套能够分辨出到底要进行什么运算,并且把引起错误的操作符或数据分支出来。

如何判断哪个if...else...是一对

当一个程序中出现多个if……else……的时候,也可能会引来一些麻烦的事情。因为每个if都具有和else配对的功能。那么我们在阅读一段程序的时候,怎么才能够知道哪个if和哪个else是在一起的呢?
如果你尝试过在VC++中输入程序4.2.2,那么你一定会发现,每输入一次{},括号内部的语句就会自动向右侧缩进一段。而if……else……正是根据括号和缩进来判断它们是不是匹配的。具体的规则是,else向上寻找最近的一个和它处于相同缩进位置的if配对,我们把这种规则理解为“门当户对”。很显然,如果你没有改变过自动产生的缩进位置,else不会去找一个比它更右边或者更左边的if的。

在有些高级语言中,是没有缩进的。缩进不仅是为了美观,也是为了让程序的层次更加分明。我们通过缩进就能很容易看出一段代码应该从哪里开始,运行到哪里结束。如果没有缩进的话,就要去找保留字,这给大型程序开发带来了麻烦。所以保持缩进是一种好习惯。

3.条件操作符(?)

随着程序越来与复杂,会在代码中出现越来越多的if语句。有些时候我们只要电脑做一个简单的判断,就要用占据多行的if语句,实在有点吓人,使得程序的可读性受到一定的影响。比如程序4.2.1中,使用标准格式写一段将较大数放入max中的语句占据了八行。即使是较简便的写法,也至少要占据两行。那么,C++是否还提供了更为简便的书写方法呢?
答案是肯定的,我们可以用一个问号来判断一个条件,具体的语法格式为:
(条件表达式)?(条件为真时的表达式):(条件为假时的表达式)
“……?……:……”称为条件操作符,它的运算优先级比逻辑或还低,是目前为止优先级最低的操作符。含有条件操作符的表达称为条件表达式。既然是表达式,它就应该有一个计算结果。而这个结果就是已经经过判断而得到的结果。我们可以定义一个变量来存放这个结果,也可以用输出语句把这个结果输出。但是,如果得到结果以后,既没有把它存放起来,也没把它输出来,那么做这个条件运算就失去意义了。
下面我们用条件操作符来改编一下程序4.2.1,看看条件表达式是如何使用的:

#include "iostream.h" int main() {    int a,b,max;    cout <<"请输入两个数:"<<endl;    cin >>a >>b;    max=(a>=b)?a:b;//如果a大于等于b,则把a的值放到max中,否则把b的值放到max中    cout <<"较大的数是" <<max <<endl;    return 0; } 

运行的结果就如同程序4.2.1,没有任何区别。而我们也达到了缩短代码的目的,增强了程序的可读性。

4.switch…case…语句

我们已经了解,if……else……可以用来描述一个“二岔路口”,我们只能选择其中一条路来继续走。然而,有时候我们会遇到一些“多岔路口”的情况,用if……else……语句来描述这种多岔路口会显得非常麻烦,而且容易把思路搅浑。比如程序4.2.2就是一个用if……else……语句描述的四岔路口(四种操作符),整个程序占据了将近一页。
如果我们把这些多岔路看作电路,那么用if……else……这种“普通双向开关”来选择某一条支路就需要设计一套很复杂的选路器。所以最简便的选路方法当然是做一个像下图那样的开关。(图4.4.1)
在C++中,也有这样的开关,那就是switch语句。它能够很简捷地描述出多岔路口的情况。具体的语法格式为:

switch(表达式) {    case 常量表达式1:    {       语句块1;       break;    }    ……    case 常量表达式n:    {       语句块n;       break;    }    default:    {       语句块n+1;    } } 

在switch语句中,我们要记住四个关键词,分别是switch、case、default和break。switch是语句的特征标志(图中标作sw);case表示当switch后的表达式满足某个case后的常量时,运行该case以后的语句块。要注意,任意两个case后的常量不能相等,否则switch将不知道选择哪条路走。default表示当表达式没有匹配的case时,默认(default)地运行它之后的语句块(图4.4.1中未标出);break表示分岔路已经到头,退出switch语句。
下面,我们就来用switch语句来改写程序4.2.2。箭头表明遇到break以后的运行情况。
#include "iostream.h" int main() {    float a,b;    char oper;    cout <<"请输入一个表达式(eg.1+2):" <<endl;    cin >>a >>oper >>b;    switch (oper)    {       case '+':       {          cout <<a <<oper <<b <<'=' <<a+b <<endl;          break;       }       case '-':       {          cout <<a <<oper <<b <<'=' <<a-b <<endl;          break;       }       case '*':       {          cout <<a <<oper <<b <<'=' <<a*b <<endl;          break;       }       case '/':       {          if (b!=0) cout <<a <<oper <<b <<'=' <<a/b <<endl;          else cout <<"出错啦!"<<endl;          break;       }       default:          cout <<"出错啦!"<<endl;    }    return 0; } 

上述程序的运行结果和程序4.2.2的运行结果一样。我们发现使用了switch语句以后,代码的平均缩进程度有所减少,阅读代码的时候更简洁易懂。所以,使用swith语句来描述这种多分支情况是很合适的。

试试看:
1、如果去除了case对应的break,则运行出来会是什么结果?
结论:如果去除了break,则不会退出switch而运行到别的支路里去。
2、如果程序4.2.2的default没有处在switch的结尾部分,那么运行出来会是什么结果?
结论:switch语句中最后一个分支的break可以省略,其它的break均不可以。
3、case后的常量能否是一个浮点型常量或双精度型常量?

switch的一些使用技巧

返回去看一下程序4.1.2,我们不难发现这个程序也是一个多分支结构。可是switch语句只能判断表达式是否等于某个值,而不能判断它是否处于某个范围。而要我们把处于某个范围中的每个值都作为一句case以后的常量,显然也太麻烦了。那么我们还能不能使用swith语句来描述这种范围型的多分支结构呢?
通过分析,我们发现了主要起区分作用的并不是个位上的数,而是十位上的数。如果我们能把十位上的数取出来,那么最多也就只有十个分支了,不是吗?下面我们就来看一下用switch语句改编的程序4.1.2。

#include "iostream.h" int main() {    int mark;    cout <<"请输入成绩(0~100): ";    cin >>mark;    switch(mark/20)    {       case 5:       {          if (mark>100)//100到119的情况都是mark/20==5,所以要用if语句再次过滤          {             cout <<"ERROR!" <<endl;             break;          }       }       case 4:       {          cout <<"Good!" <<endl;          break;       }       case 3:       {          cout <<"Soso" <<endl;          break;       }       case 2://根据前面试一试的结论,如果case没有对应的break,会运行到下一个case中       case 1:       case 0:       {          if (mark>=0)//同样要用if过滤负数          {             cout <<"Please work harder!" <<endl;             break;          }       }       default://其它情况都是出错       cout <<"ERROR!" <<endl;    }    return 0; } 

这个程序要比原来的程序4.1.2冗长一些。但是这里提到这个程序的目的是要教会大家一种使用switch的方法,即“以点盖面”。

算法时间:数据的转换
在程序设计中,我们经常会遇到这样的问题:我们希望处理的数据和电脑能够处理的数据可能有所不符。不符合的情况一般有两种,一种是范围不符合,另一种是类型不符合。对于范围不符合,我们一般考虑的是使用代数式对数据进行处理。比如C++中的随机函数能够产生一个0~32768之间的一个整数,如果我们希望得到一个0~10之间的随机数,那么就用它对10取余数,那么结果一定就在这个范围内。对于类型不符合,我们只好尽量用已有的数据类型来描述这种难以表达的类型。就如同电脑中用0和1表示真和假一样。




原创粉丝点击