C++宝典
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第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++中的程序控制语句。在实际开发过程中,用户需要经常使用各种控制语句,根据实际的需要来控制程序的走向。灵活地使用各种控制语句,可以给开发带来各种便利。希望读者能够在实际开发中,综合使用各种语句。