第5章 程序流程控制
本章包括
◆ 描述程序流程的基本方法
◆ 用于循环控制的while语句、do⋯while语句和for语句
◆ 用于流程跳转的goto语句
◆ 用于分支控制的if语句、if⋯else语句和switch语句
◆ 中止循环的break语句和continue语句
程序流程指的是程序指令的执行次序。根据逻辑学家的研究,一般用顺序、分支和循环就可以描述所有的程序流程。顺序是最自然的流程,如果不加以控制,程序中的语句就是以其编写时的次序执行的,所以本章主要介绍分支和循环。
5.1 程序流程的描述
描述程序流程有很多种方式,比如伪码、流程图、UML(统一建模语言)的顺序图和活动图等。伪码和流程图比较适合描述短小的程序,特别是解决某个具体问题的算法,它们清晰、精确,使阅读者可以很容易地了解算法的细节。而UML图则适合描述大型程序,特别是用面向对象方法开发的程序,它便于阅读者从系统、整体的高度了解整个软件系统。本节只介绍伪码和流程图方法,关于UML图,请读者参考专门的书籍。
5.1.1伪码
伪码即伪代码,就是用一种非常自然的、口语的方式来描述程序。伪码不受计算机语言语法的限制,行文非常自由,可以用任何熟悉的语言进行描述。相比代码,伪码可以更好地贴近人类的思维,更容易为阅读者所理解。例如,描述一个求三个数a,b,c中最大值的伪码如下:
step 1 比较a和b,将其中较大的值赋给x。
step 2 比较x和c,将其中较大的值赋给y。
step 3 输出y。
这样就完成了一个简单程序的流程描述。这段流程描述也可以当做程序的设计文本,在后续的编码过程中可以根据这段伪码进行编程。
5.1.2 流程图
流程图以图的形式来描述程序的流程。程序中的每一个或几个步骤用一个图形符号表示,并结合图形中的文字说明该步骤的功用。每个图形符号之间还可以用带箭头的线连接,用来表示步骤间的执行次序。上节中伪码的例子就可以用如图5-1所示的流程图进行描述。
图5-1 求三个数中的最大值
常用的流程图符号如表5-1所示。
表5-1 常用的流程图符号
5.2 分支
所谓分支,指的是根据预设的条件,选择不同的语句序列进行执行。在C++程序中,“条件”是用一个表达式表示的,该表达式的结果是一个bool值或者某个整数值。在程序执行过程中,如果某个条件得到了满足,或者不满足,那么程序就会沿着相应的语句序列顺序执行下去。至于如何选择,则要借助于特殊的控制语句if⋯else或switch⋯case。
5.2.1 if语句
使用if语句的作用是:只有当满足一定条件时,才执行后续的语句或语句块。
执行一条语句的if语句如下:
if ( <条件表达式> ) <语句>;
执行语句块的if语句如下:
if ( <条件表达式> ) { <语句1>; <语句2>; ⋯⋯ }
If语句的执行流程如图5-2所示。
图5-2 if语句的执行流程
由图可见,只有当满足“条件”时(即条件表达式的结果为true时),才会执行语句或语句块2。不满足“条件”时(即条件表达式的结果为false时),执行If语句后面的语句3。在C++语言中,条件是用条件表达式表示的。所谓条件表达式指的是一个结果为bool值的表达式,例如:
a < b a >= b a != b a == b a > b && a < 0
由于其他数据类型和bool型数据间存在隐式数据类型转换的关系,所以当用其他表达式作为条件表达式时,虽然其结果不是bool型,但仍然可以使用。例如,对于表达式“a + b”,如果其结果值是0,则转换成条件表达式的结果就是false;如果其结果值是非0,则转换成条件表达式的结果就是true。
当心
在实际编程中,经常会出现的一个错误是将“a = b”当成“a == b”。前者是一个赋值表达式,其结果是b的值,而后者是一个判断a和b是否相等的条件表达式,其结果是一个bool值。由于存在隐式类型转换关系,所以在编译时并不会报告错误,只有等到运行时才可能出现意料之外的结果。
5.2.2 if⋯else语句
if语句的语义是“如果⋯⋯,则⋯⋯”,C++还提供了一个if⋯else语句,表示“如果⋯⋯,则⋯⋯;否则⋯⋯”语义。简单的if⋯else语句语法如下:
if ( <条件表达式> ) <语句1>; else <语句2>;
执行语句块的if⋯else语句语法如下:
if ( <条件表达式> ) { <语句1>; <语句2>; ⋯⋯ } else { <语句3>; <语句4>; ⋯⋯ }
其流程图如图5-3所示。
图5-3 if⋯else语句的执行流程
与If语句的执行流程比较,可以得出if语句同if⋯else语句的区别:if语句没有处理条件不满足的情况,而if⋯else语句有。
下面用if⋯else语句求两个整数中的较大值,程序如示例代码5.1所示。
示例代码5.1
#include <cstdlib> #include <iostream> using namespace std; // 使用名称空间std int main(int argc, char *argv[]) // 主函数 { int a = 0, b = 0; // 用于比较大小的两个整型变量 cout<<"请输入两个整数:"<<endl; // 提示用户输入 cin>>a; // 输入第一个数 cin>>b; // 输入第二个数 cout<<"较大的整数是:"<<endl; // 输出提示 if( a <= b ){ // 如果a小于等于b cout<<b<<endl; // 输出较大值b } else{ // a不小于等于b,即a大于b cout<<a<<endl; // 输出较大值a } system("PAUSE"); // 等待用户输入 return EXIT_SUCCESS; // 主函数返回 }
建立一个控制台工程,并将上述代码复制到源文件中,编译、链接并执行,其结果如图5-4所示。
图5-4 求两个整数中的较大值结果
在上面的程序中,利用一个if⋯else语句判断输入的第一个整数是否小于等于第二个整数。如果是,则输出第二个整数;如果不是,则输出第一个整数。
5.2.3 if⋯else语句的嵌套
if⋯else语句可以嵌套,即其中的语句块可以是另外一个if⋯else语句,例如下面的形式:
if ( <条件表达式1> ) if ( <条件表达式2> ) <语句1>; else <语句2>; else if ( <条件表达式3> ) <语句3>; else <语句4>;
其中的每个语句又可以是一个语句块。在嵌套的if⋯else语句中,相配的if和else不是靠缩进决定的,而是遵循一定的规则。这个规则是:else和最后出现、并且没有匹配的if相匹配。例如在上述例子中,相互匹配的if和else分别是:第4行的else和第2行的if,第6行的else和第1行的if,第9行的else和第7行的if。
即便是程序员知道这样的匹配规则,在阅读上述代码时仍然会感到困难,比较好的方法是尽量使用复合语句。例如,上述例子可以改写如下:
if ( <条件表达式1> ) { if ( <条件表达式2> ) <语句1>; else <语句2>; <语句3> } else { if ( <条件表达式3> ) <语句4>; else <语句5>; <语句6> }
如此一改,if和else的匹配关系非常明确,程序阅读起来也十分方便,其流程图如图5-5所示。
图5-5 if⋯else语句嵌套的流程图
if⋯else语句的嵌套还有一种比较常见的形式,即if⋯else级联:
if ( <条件表达式1> ) 语句1; else if ( <条件表达式2> ) 语句2; else if( <条件表达式3> ) 语句3; ⋯⋯ else 语句n;
在执行上述语句时,程序依次计算每个条件表达式。一旦某个条件满足,则执行对应的语句或语句块,并且不再判断后面的条件。因此,每个条件表达式应当是互斥的。
下面利用if⋯else嵌套语句,评定学生成绩的档次。成绩的满分为100,分为5档:0~59为E,60~69为D,70~79为C,80~89为B,90~100为A。根据输入的成绩,输出学生成绩的档次。程序如示例代码5.2所示。
示例代码5.2
#include <cstdlib> #include <iostream> using namespace std; // 使用名称空间std int main(int argc, char *argv[]) // 主函数 { int degree = 0; // 保存成绩的变量 cout<<"请输入学生成绩:"<<endl; // 提示用户输入 cin>>degree; // 用户输入成绩 if( degree < 0 || degree > 100 ){ // 判断输入是否合法 cout<<"成绩只能在0~100之间"<<endl; // 输出不合法信息 return EXIT_SUCCESS; // 主函数返回,程序结束 } if( 0 <= degree && degree < 60 ){ // 如果成绩不及格 cout<<'E'<<endl; // E档 } else if( 60 <= degree && degree < 70 ){ // 60~69 cout<<'D'<<endl; // D档 } else if( 70 <= degree && degree < 80 ){ // 70~79 cout<<'D'<<endl; // C档 } else if( 80 <= degree && degree < 90 ){ // 80~89 cout<<'D'<<endl; // B档 } else if( 90 <= degree && degree <= 100 ){ // 90~100 cout<<'D'<<endl; // A档 } system("PAUSE"); // 等待用户反应 return EXIT_SUCCESS; // 主函数返回 }
建立一个控制台工程,并将上述代码复制到源文件中,编译、链接并执行,其结果如图5-6所示。
图5-6 判断成绩档次结果
在上面的代码中,通过一系列的if⋯else判断,决定了成绩所属的档次。各个if中的条件是互斥的,所以可以保证一个成绩只能对应一个档次。
其实在上述例子中,每个条件表达式还可以改进。例如对于表达式:
0<=degree && degree<60
可以省略前面的表达式,而写成:
degree < 60
这是因为第一个if语句,已经处理了degree<0以及100 < degree的情况。此时degree的值只能在0和100之间,所以不必再去判断degree是否大于等于0,只要判断degree是否小于60即可。同样对于第一个else if条件表达式也可以写成degree < 70,依此类推。
5.2.4 switch语句
if⋯else语句虽然可以根据表达式的结果,执行不同的语句,但是如果表达式存在多个可能的结果,则多个if⋯else语句未免显得过于烦琐。而且在编程过程中,很可能会由于疏忽而漏掉一个或多个if⋯else判断。为此C++提供了switch语句,用来在这种情况下替代if⋯else语句。其常见的使用方法如下:
switch ( expr ) { case <常量1>: <语句1>; break; case <常量2>: <语句2>; break; ⋯⋯ case <常量n>: <语句n>; break; default: <default语句>; }
switch语句由以下4部分构成:
◆ switch表达式。
◆ 一组case标签。
◆ 与一个或一组case标签相关联的语句或语句块。
◆ default标签。
switch表达式由switch关键字和一个表达式组成(被括号包围),其语法如下:
switch ( expr )
表达式expr的结果值是一个整数或者枚举值,或者其他可以转换成整数或枚举值的值。
switch表达式后面是一组由一对花括号“{”和“}”包围的case标签。所谓case标签是由关键字case后接一个常量表达式及冒号构成的。这个常量表达式将被用来与switch表达式的结果做比较。例如:
case 'a': case 123: case eSaturday: // eSaturday是表示周六的枚举值 case iVal: // iVal是一个符号常量,即const常量
在一个或一组case标签后面有跟这个(组)标签相关联的语句或语句块。每个语句块后面都要跟一个break语句,表示语句块的结束。例如:
case eSaturday: <语句1>; <语句2>; ⋯⋯ break; case eSunday: <语句4>; ⋯⋯
如果switch表达式的值是eSaturday,则执行第2行至第5行的语句。当执行到第5行时,发现有break语句,则停止执行switch语句,转而执行switch语句后面的语句,即switch结束花括号“}”后面的语句。如果没有第5行的break语句,则继续执行“case Sunday:”后面的语句,直至遇到一个break语句。
有的时候,程序员可能就是需要多个case标签对应同一个语句或语句块,则连续的多个case标签可以只加一个break语句。例如:
switch ( day ) { case eMonday: case eTuesday: case eWednesday: case eThursday: case eFriday: <语句1>; break; case eSaturday: case eSunday: <语句2>; break; }
在上述的switch语句中,对于工作日(周一到周五),执行语句1;对于周末,则执行语句2。
提示
何时不加break语句,取决于程序员的设计。虽然这样做没什么不妥,但对于后来维护程序的人则未免有些麻烦。因为后来者并不知道没有加上的break语句是故意为之,还是编程者的疏忽。所以,为了方便程序的维护,最好在不加break语句的地方加上注释,说明为什么不加。
switch语句通常还会有一个default标签,放在所有case标签的后面。default标签后直接跟一个冒号,中间只允许存在空格或制表符,而不是表达式。default标签的冒号后面是跟default相关联的语句或语句块。如果switch表达式与任意一个case标签都不匹配,则default标签后面的语句将被执行。
假设一个switch语句带有三个常量标签和default标签,则其执行过程如图5-7所示。
说明
与嵌套的if⋯else语句不同,switch语句在计算完“条件表达式”之后,并不依次判断其结果值是否与某个常量相等,而是直接跳到对应的标签处开始执行。例如,如果条件表达式计算的结果是常量3,则程序会直接跳到语句3开始执行,并不会依次判断结果是不是常量1和常量2。
图5-7 switch语句的执行过程
前面的例子用多个嵌套的if⋯else语句来判断成绩的档次,其过程未免显得有些烦琐,下面用switch语句对其进行改进,如示例代码5.3所示。
示例代码5.3
#include <cstdlib> #include <iostream> using namespace std; // 使用名称空间std int main(int argc, char *argv[]) // 主函数 { int degree = 0; // 保存成绩的变量 cout<<"请输入学生成绩:"<<endl; // 提示用户输入 cin>>degree; // 输入成绩 cout<<"成绩档次为:"; // 提示输出信息 switch( degree / 10 ){ // 根据成绩被10除的商,判断成绩档次 case 0: case 1: case 2: case 3: case 4: case 5: cout<<'E'<<endl; // E档,60分以下不及格,全为E档 break; case 6: cout<<'D'<<endl; // D档 break; case 7: cout<<'C'<<endl; // C档 break; case 8: cout<<'B'<<endl; // B档 break; case 9: cout<<'A'<<endl; // A档 break; default: cout<<"成绩只能在0~100之间"<<endl; // 输出不合法信息 } system("PAUSE"); // 等待用户反应 return EXIT_SUCCESS; // 主函数返回 }
建立一个控制台工程,并将上述代码复制到源文件中,编译、链接并执行,其结果如图5-8所示。
图5-8 用switch语句判断成绩档次结果
在上面的代码中,并没有直接根据成绩的范围求档次,而是根据成绩被10除的商来判断的。由于成绩是整数,所以对于每个档次,其所得商是固定的,如档次B,即成绩在80~89范围内,其中每个数除以10都得8。
5.3 循环
在本节中,将详细讲解程序控制中的循环结构。和前面类似,本节将详细讲解各种循环结构的功能和语法结构,并结合具体例子进行分析。
5.3.1 while语句
while语句用于控制一个语句或语句块循环执行。while语句包含一个条件判断,只有条件为真时,其对应的语句或语句块才会循环执行。执行一条语句的while语句语法如下:
while ( expr ) <语句>;
执行语句块的while语句语法如下:
while ( expr ) { <语句1>; <语句2>; ⋯⋯ }
在上述语法示例中,expr是一个表达式,也是while语句的循环条件。expr的结果应当是bool值,如果不是,则需要进行类型转换。
while(expr)之后的语句或语句块称为循环体。当程序执行到while(expr)时,先计算表达式expr的值,若其值为真,则执行循环体中的语句。循环体执行完一次后,再次判断expr的值,若为真,则再次执行循环体。如此反复,直到expr的值为false,然后执行while语句之后的语句。整个执行过程如图5-9所示。
图5-9 while语句的执行过程
前面计算成绩档次的程序有一个显著的缺点,那就是在程序的一次运行中,只能判断一个成绩的档次。如果要判断别的成绩的档次,需要重新运行程序,非常烦琐。显然,如果程序能够循环重复执行,就会非常方便。下面用while语句改进判断成绩档次的程序,如示例代码5.4所示。
示例代码5.4
#include <cstdlib> #include <iostream> using namespace std; // 使用名称空间std int main(int argc, char *argv[]) // 主函数 { int degree = 0; // 保存成绩的变量 cout<<"请输入学生成绩:"<<endl; // 提示用户输入 cin>>degree; // 输入成绩 while( 0 <= degree && degree <=100 ) // 只要成绩合法,就可以判断档次 { cout<<"成绩档次为:"; // 提示输出信息 switch( degree / 10 ){ case 0: case 1: case 2: case 3: case 4: case 5: cout<<'E'<<endl; // E档,60分以下不及格,全为E档 break; case 6: cout<<'D'<<endl; // D档 break; case 7: cout<<'C'<<endl; // C档 break; case 8: cout<<'B'<<endl; // B档 break; case 9: cout<<'A'<<endl; // A档 break; } cin>>degree; // 输入另外一个成绩 } system("PAUSE"); // 等待用户反应 return EXIT_SUCCESS; // 主函数返回 }
建立一个控制台工程,并将上述代码复制到源文件中,编译、链接并执行,其结果如图5-10所示。
图5-10 循环判断成绩档次结果
在示例代码中,首先使用一个while语句进行判断,只要输入的成绩合法,则后面的循环体就可以重复执行,然后判断成绩的档次。用户输入之后则重新判断表达式的值是否为true。
5.3.2 do⋯while语句
do⋯while语句的目的也是控制代码的循环执行,执行一条语句的do⋯while语句如下:
do <语句>; while ( expr );
执行一个语句块的do⋯while语句如下:
do { <循环体语句>; } while ( expr );
当心
do⋯while语句最后的分号表示语句的结束,不可省略,否则会导致编译错误。
与while语句不同,do⋯while语句先执行一次循环体,再判断循环条件是否满足。其执行过程如图5-11所示。
图5-11 do⋯while语句的执行过程
例如下面的do⋯while循环语句:
do{ cout<<"Hello World."<<endl; } while ( false );
虽然循环条件为false,但上述代码仍然输出“Hello World”。而对于while循环:
while (false ){ cout<<"Hello World"<<endl; }
当循环条件为false时,循环体不能执行,所以也没有任何输出。
5.3.3 for语句
for语句也用来控制代码的循环执行。for语句更加灵活,功能也更加强大。执行一条语句的for语句语法结构如下:
for ( <初始化>; <条件表达式>; <动作> ) <语句>;
执行一个语句块的for语句语法结构如下:
for ( <初始化>; <条件表达式>; <动作> ) { <语句1>; <语句2>; ⋯⋯ }
for后面的括号中是三条独立的语句,分别是初始化、条件计算和动作。for循环的执行过程是:
step 1 执行初始化语句,完成循环的初始化。
step 2 计算条件表达式,并检测其结果值是否为真。
step 3 如果条件为真,则执行循环体。
step 4 执行动作语句。
每次执行完循环体之后,则重复执行第2步至第4步。例如:
for( int i=0; i<10; i++ ) { cout<<i<<endl; }
其执行过程是:
step 1 声明并初始化变量i。
step 2 计算表达式i<10的值,其值是真,执行循环体,输出i的值。
step 3 执行动作i++。
step 4 重复执行第2步和第3步,不断输出i值,并增加i的值,直至i值为10,不满足条件,循环结束。
for语句的执行过程如图5-12所示。
图5-12 for语句的执行过程
for语句中的三条语句都可以是空白语句,但分号不可以省略。例如:
for ( ; <条件表达式>; <动作> ) for( ; ; <动作> ) for(<初始化>; ; ) for( ; ; )
以上都是合法的for语句。下面利用for循环,获取100以内2的幂,程序如示例代码5.5所示。
示例代码5.5
#include <cstdlib> #include <iostream> 03 using namespace std; // 使用名称空间std 05 int main(int argc, char *argv[]) // 主函数 { for( int power=1; power<=100; power*=2 )// 利用for循环求2的幂,限定100以内 { cout<<power<<endl; // 输出2的幂 } system("PAUSE"); // 等待用户反应 return EXIT_SUCCESS; // 主函数返回 }
建立一个控制台工程,并将上述代码复制到源文件中,编译、链接并执行,其结果如图5-13所示。
图5-13 利用for循环求100以内2的幂结果
在for循环语句中,初始化语句将变量power初始化为1,也是2的0次幂。条件表达式power<=100,限定这个幂是100以内的值。而动作语句power*=2则依次求2的幂,即后一个幂是前一个幂的2倍。
当心
在for循环的初始化语句中可以声明并初始化变量。根据C++标准规定,这个变量的作用域限于for语句,即从声明开始,直到最后一条循环体语句结束。但是,并不是所有的编译器都支持这一点。不管编译器是否支持,程序员在编程时都应当遵循标准,否则程序的可移植性就很难保证。
5.4 循环控制语句
在循环过程中,有时循环条件过于复杂,难以用一个表达式说明,需要借助特殊的手段控制循环过程。在C++程序中,这种特殊手段包括break语句和continue语句。
5.4.1 break语句
break语句的作用是终止整个循环,执行循环语句后面的语句。在循环中break语句通常与一个if语句配合使用。例如在下面的程序中使用break语句:
for ( int i=0; i<5; i++ ){ // for循环控制 if( i == 2 ){ // 如果i的值是2 break; // 结束循环 } cout<< i << ' '; // 输出i的值和一个空格 }
输出结果为“0 1 ”。当i的值为2时运行break语句,结束循环,1后面的值不再输出,虽然for语句中的条件表达式表明可以一直输出到4。
当心
break语句只能用在switch语句和循环语句中,否则会引起编译错误。
5.4.2 continue语句
continue语句的作用是终止本次循环,开始下一次循环。上面break语句实例中如果将第3行的语句改为continue,则输出结果是“0 1 3 4 ”。
continue语句只能用在循环语句中。
5.5 流程跳转语句goto
使用goto语句的目的是实现无条件分支,而这个分支只能是函数内部的分支。运行goto语句将跳转到同一函数内部的某个位置,该位置由一个标号表明。goto语句的语法如下:
goto label;
这里的label是用户定义的标识符标号。label只能用做goto的目标,并且必须由冒号结束。label处必须有程序语句,如果什么都不执行,则应当加一个空语句。
end: ; // 空语句 } // goto的目标不能是一个语句块的结束花括号,所以应当加一个空语句
goto语句应当与一个if语句配合使用,否则goto语句与目标标号之间的语句将被无条件跳过。例如:
for( int i=0; i<10; i++ ){ if( i == 5 ) goto END; cout<< i << endl; } END: cout<<"Jump here"<<endl;
如果没有if语句,程序不会输出任何整数;加上这个if语句,程序就会输出0~4的整数。
当心
编程时尽量避免使用goto语句,否则会使程序难以理解和维护。
5.6 小结
本章详细讲解了C++中的程序控制语句。在实际开发过程中,用户需要经常使用各种控制语句,根据实际的需要来控制程序的走向。灵活地使用各种控制语句,可以给开发带来各种便利。希望读者能够在实际开发中,综合使用各种语句。