10  基本计算()选择控制结构

在上章我们可以看到,赋值语句之所以能够驱动计算过程的运行,除了赋值语句本身构成完整的运算求值指令之外,计算机本身所具有的串行机制保证了,只要把执行语句与说明语句串列下来,然后保证数据对象流的畅通,计算机就能够按照语句序列的次序依次执行下来,从而完成相应的计算任务。

不过分析一下我们的实际计算任务,就会发现真正完全可以只需要按照一个固有计算序列算下来,就可以完成计算的问题只是少数简单的问题而已,稍微复杂点的问题往往都需要在计算过程当中出现某种判别问题,使得计算途径可以分支,对不同的情形,会有不同的计算途径与不同的计算结果;还会出现反复进行同一种运算,然后再在适当的条件下跳出循环的情形,如果只是使用串行序列来描述此时的运算过程,会使得程序的写作充满重沓的语句。显然这些类型的计算任务,或者是语句串行序列无法描述的,或者是描述发生令人难以忍受的。

所以一种描述计算过程的语言,除了可以构成串行的计算序列之外,还必须能够控制这个序列有可能出现的选择分支与循环的情形,这就是所谓语言的控制结构。

本章和下章,我们将分别讨论FORTRAN所提供的两种类型的控制结构以及相应的控制语句:

    选择控制结构;

    循环控制结构。

把程序序列当中的控制结构清晰地加以强调出来,实际上既有助于程序的阅读,也是符合人的思维的清晰性原则的,因此控制结构的规范化正是编程语言追求结构化的一个主要着力点,但是在早期FORTRAN版本里面,并没有完全意识到结构化编程风格的重要性,因此引入了一些常常破坏结构的转移控制语句,而FORTRAN语言的现代版本还没有完全放弃它们,但不提倡使用,这些转移控制语句我们在下章也将简略地讨论到。

10.1  执行顺序

一个完整的FORTRAN程序,或程序单位,就给出了一个由可执行语句引导的语句执行序列,这个序列就是语句在程序里面出现的序列,称为正常运行序列。

但是某些可执行结构或可执行语句,能够控制实际的语句运行不止是按照语句书写的序列进行,这样的可执行结构或语句包括控制结构和分支语句。

控制语句执行序列有两种基本风格:

    使用可执行结构,使得程序能够选择性地运行程序序列里某个特定位置的语句块或结构。

    使用能够分支到程序其他任意位置的语句的可执行语句。

一般说来,上面的第一种方式具有更好的可阅读性和可维护性,因此我们将详细讨论第一种方式的实现,而第二种方式则放在下章末尾简略说明一下。

10.2  块与可执行结构

所谓控制结构由一个或多个语句块和结构构成,其中必定显式地或隐式地包含控制逻辑语句。根据相应的逻辑控制条件,就可以选择性地运行某个特定的语句块与结构。

一个块就是一个由0个或多个语句与结构构成的序列,句法形式(R801)为:

    [execution-part-construct]…

构成块的语句与结构的序列构成一个程序单位,是一个某种意义上的整体,即或者整个块被执行,或者整个块都不被执行,不能够出现块的某个部分被执行的情形。不过也可能出现在整个块被执行的情况下,块内的某些语句没有被执行的情况,例如在块内的比较靠前的分支语句,常常能够制止它后面的语句得到执行,但是从语法意义上来看,整个块的功能得到了完整的实现,因此这种情形也被认为是块的完全执行。

所谓结构就是由一个或多个语句块或者结构,加上作用于这些块的控制语句构成。

    结构通常在块前具有一个初始语句,而在块后具有一个终止语句;

    当结构包含不止一个块时,结构必定包含了用来选择执行哪个块的条件语句,而一个块是否被执行,正是由结构的作为条件语句的控制逻辑语句当中的表达式决定的;

    结构也有可能在块之间放置特定的语句,用来决定相应的块是否被执行;

    DO结构专门用来决定一个块可以被连续地执行的次数。

【例10-1 下面给出的可执行结构控制了一个块:

    IF(K<=1)THEN        IF结构的初始语句

       X=K**2              !块的第一个语句

       Y=SIN(X)          !块的第二个语句

    END IF                  IF结构的终止语句

FORTRAN提供了三种能够控制块的可执行结构:

    IF结构

    CASE结构

    DO结构

块的一般规则如下:

首先执行块内的第一个语句或结构,随后的执行顺序就是语句的排列顺序,除非中间包含控制结构或语句改变这个顺序。

一个块作为一个整体,必须是完整地被包含在一个结构当中;一个块也可以是空块,即不包含任何语句与结构。

在块内允许出现分支结构或控制结构,使得运行序列能够转移到该块内的特定语句或结构。

在块内的任意位置,都可以强制运行的退出。

从块的外部通过分支到达块的内部,哪怕是块内的第一个可执行语句,都是禁止的。

块内可以引用过程。

结构可以具有结构名称。

结构名称在结构的初始语句和终止语句当中的出现,必须是成对的,也就是说或者同时出现在这两个位置,或者都不出现。

在上章我们讨论了WHERE结构与FORALL结构,它们似乎同样具有控制结构的功能,但实质上它们都是属于赋值语句。

WHERE结构可以包含多个块,但是除了块内的某些数组元素被过滤网过滤掉,从而不参与计算之外,每个块内的每个语句,都必须被执行。

FORALL结构只包含一个块,FORALL对这个块的执行控制,不是反复地运行这个块,而是其中的每个语句都必须对指标值集合里的所有元素执行一次,然后才转入下一个语句。在FORALL结构里也可以引入过滤网,以排除掉不符合条件的计算。

10.3  IF结构和IF语句

一个IF结构至多选择结构里的一个语句与结构的块来得到执行。而一个IF语句至多控制一个语句的执行。

在下章后面会简略讨论到的算术IF语句与这里的IF语句没有关系,算术IF语句是一种分支语句,并且是过时的。

10.3.1  IF结构

IF结构可以包含多个块与多个逻辑表达式,还可能包含ELSE语句与ELSE IF语句。

逻辑表达式总是放置在相应的块之前,因此IF结构的执行总是从逻辑表达式开始的,一旦逻辑表达式取真值,则相应的块获得执行,整个IF结构内至多只有一个块能够被选择执行,而如果没有ELSE语句的话,可能没有块满足执行的条件。

一旦被选择执行的块运行完毕,或者不存在满足执行条件的块,则整个IF结构的运行被终止。

1. IF结构的形式

IF结构的句法形式(R802)为:

    [if-construct-name] IF(scalar-logical-expression)THEN

           block

    [ELSE IF(scalar-logical-expression)THEN[if-construct-name]

           block]….

    [ELSE [if- construct-name]

           block]

    END IF [if-construct-name]

IF结构的一般规则如下:

至多只有一个结构里的块被执行,或者没有块被执行。

● ELSE IF语句不能跟随在ELSE语句后面。

不能出现分支到ELSE IF语句或ELSE语句的情形。

IF结构里的任意块都可以分支到END IF语句,即随时可以退出IF结构。

结构名称必须成对出现在IF-THEN语句和END IF语句当中。

● ELSE IF语句和ELSE语句里的结构名称是可选的,如果有的话,必须和IF-THEN语句里的结构名称一致。

在同一个作用域单位内,同一个结构名称不能用于不同的命名结构。

2. IF结构的运行

结构里的逻辑表达式按照序列位置执行,直到某个表达式为真值,然后紧跟该表达式后面的块得到执行,该块运行完毕之后,整个IF结构即退出运行。

在第一个真值表达式后面如果还存在真值表达式,则它们不影响对执行块的选择。

如果在该IF结构内(而不是它的子结构内)找不到真值表达式,则ELSE语句后面的块被执行;如果没有ELSE语句,则退出IF结构。

下面的图10-1给出了IF结构的运行示意图。

10-1  IF结构的运行流示意图

【例10-2

    IF(I<J)THEN

        X=Y*2.5

    ELSE IF(I>8.3)THEN

        X=0.0

        Y=100

    ELSE

        X=100

        Y=0.0

    END IF

上例的执行顺序就是:

    首先执行I<J,如果为真,则执行X=Y*2.5;如果为假,则执行紧跟后面的ELSE IF

    -THEN语句。

    进入ELSE IF-THEN语句后,首先执行I>8.3,如果为真,则执行X=0.0Y=100

    如果为假,则执行紧跟后面的ELSE语句。

    进入ELSE语句后,执行X=100Y=0.0

    然后执行END IF语句,退出该IF结构。

10.3.2  IF语句

    一个IF语句用在只需要控制一个语句的场合。

1. IF语句的形式

IF语句的句法形式(R807)为;

      IF(scalar-logical-expression)action-statement

【例10-3

    IF(X=0)Y=X+1

2. IF语句的运行

首先执行逻辑表达式,如果为真值,则执行IF语句里的作用语句(action-statement);如果为假,则退出该语句,执行程序里跟在IF语句后面的其他语句。

注意作用语句(action-statement)不能是一个IF语句,也不能是一个END语句。

如果逻辑表达式包含一个函数引用,那么逻辑表达式的运行可能产生修改作用语句的副作用,这是允许的。

作用语句能够改变变量,或输入输出系统条件,或控制语句的状态,作用语句的例子包括赋值,WRITEGO TO语句等;说明性语句,FORMAT语句,和ENTRY语句等不属于作用语句,而结构也不能看成是作用语句。

10.4  CASE结构

IF结构类似的是,CASE结构也可以包含多个块,而最终能够获得执行的最多只能是一个。

IF结构不同的是,CASE结构的选择是基于结构开头的SELECT CASE语句里的标量表达式的取值,这个取值被称为情况指标,它可以是离散型的各种标量值,例如整型,字符型,逻辑型,而在IF结构里,用来进行判别而提供选择条件的是逻辑型取值,即只有.TURE..FALSE.两个值。

得到情况指标之后,搜索所有的CASE语句里的情况选择符的值,如果有与情况指标匹配的,则执行相应的CASE语句里的块,否则执行具有默认选择符的CASE,如果不存在具有默认选择符的CASE,则退出。

使用CASE结构可以很自然地表达需要分情况考虑的问题,同时每种情况的定义必须是已知的。

【例10-4 设分段函数:

可以很自然地使用CASE结构描述如下:

    SELECT CASE(x>=0)

        CASE(.TRUE.)

           YSIN(X)

        CASE(.FALSE.)

           YCOS(X)

END SELECT

【例10-5 一年中十二个月的天数也可以用CASE结构表示如下:

MONTHDAY SELECT CASE(MONTH)

CASE(2)

DAYS28

CASE(46911)

                DAYS30

CASE(135781012)

                DAYS31

END SELECT MONTHDAY

10.4.1  CASE结构的形式

CASE结构的句法形式(R808)为:

    [case-construct-name]SELECT CASE(case-expression)

    [CASE(case-value-range-list)[case-construct-name]

        block]…

    [CASE DEFAULT[case-construct-name]

        block]

    END SELECT [case-construct-name]

其中的情况表达式(case-expression)为标量表达式,该表达式经过计算得到的取值为情况指标。

其中的情况值范围(case-value-range)即连同其括号称为情况选择符,表示它的取值所属的离散区间,也可以就是一个单独的值,它的句法形式(R814)为:

    case-value

    case-value

    case-value

    case-value case-value

即如果是区间的话,该区间可以是半边开的。

其中的情况值(case-value)是一个标量初始化表达式,与情况表达式的类型等属性一样。所谓初始化表达式即在编译时可以取值的表达式,本质上即常量表达式。它们的取值都只能是整型,字符型和逻辑型。

可以看到在CASE结构里,包含了三种语句:

    SELECT CASE语句;

    CASE 语句;

    END SELECT语句。

而在CASE语句当中,跟在关键词CASE后面的(case-value-range-list)DEFAULT就是情况选择符。

CASE结构的一般规则如下:

如果CASE结构具有名称的话,那么结构名称必须成对出现在SELECT CASE语句和END SELECT语句当中。

情况选择符后面如果出现结构名称的话,必须与SELECT CASE语句当中的结构名称一致。

具有情况选择符DEFAULTCASE语句是可选的,如果它出现在结构当中的话,上述CASE结构的句法形式并没有要求它放置在结构的最后。

在一个CASE结构里,情况表达式与所有的情况值都必须属于同一个类型,如果属于字符型,它们可以具有不同的长度,但是种别参数必须一致。

情况值范围里面出现冒号时,表示一个区间,而区间的构成隐含着相应的关系表达式。

情况值不能是逻辑型。

如果是字符型的话,则依据字符型的关系运算规则确定相应的取值范围。

【例10-6

    CASE(ABOUT”:“Z)

里面的情况值范围为按照与处理器相关的默认字符类型的关系运算,在ABOUTZ之间的所有字符串。

结构当中的表达式的取值完成后,必须保证最多只能有一个情况选择符的值与情况指标值匹配,也即不允许在不同的情况之间情况值出现重复的情形。

情况值DEFAULT与任何跟所有结构内的其他情况值都不匹配的情况指标值匹配。

【例10-7

    CALCU_AREA: SELECT CASE(OBJECT)

        CASE(RECTANGLE) CALCU_AREA

            AREA=LENGTH*WIDTH

       CASE(SQUARE) CALCU_AREA

            AREA=SIDE*SIDE

       CASE(CIRCLE) CALCU_AREA

            AREA=PI*RADIUS**2

    END SELECT CALCU_AREA

10.4.2  CASE结构的运行

首先计算SELECT CASE语句当中的作为标量表达式的情况指标,它的值最多与结构里的一个情况值匹配,与之匹配的情况值后面紧跟的块得到执行,然后退出结构。程序进入紧跟该结构的END SELECT语句后面的可执行语句或结构。

如果没有与情况指标值匹配的情况值,而结构里包含了CASE DEFAULT语句,按照DEFAULT的定义,情况值这时必定与之匹配,因此执行该语句后面的块,然后退出该结构。

如果结构当中不存在CASE DEFAULT语句,又没有与情况指标值匹配的情况值,那么退出该结构,不执行结构里的任何块。

所谓情况指标值与结构里的一个情况值匹配,包含如下情形:

    如果情况值为单独的一个值,则运用等价与非等价关系运算,看是否与指标值等价;

    如果情况值是一个区间,则按照下表10-1的定义:

10-1  不同情况值的匹配

情况值范围

匹配的定义

case-value1 case-value2

case-value1 .LE. case-index .LE. case-value2

case-value

case-value .LE. case-index

case-value

case-value .GE. case-index

       

注意不同CASE语句的情况值范围不能有重叠。

结构的执行最多只允许其中的一个块被执行。

只能从结构内部分支到该结构的END SELECT语句。

不允许分支到CASE语句,但可以分支到SELECT CASE语句。

下面的图10-2给出了CASE结构的运行流的示意图:

10-2  CASE结构的运行流示意图:

【例10-8

    INDEX=2

    SELECT CASE(INDEX)

       CASE(1)

         A=(12)

       CASE(2)

         A=(23)

       CASE DEFAULT

         A=(00)

    END SELECT

【例10-9 下面的例子描述了一个交通规则:

    COLOR=GREEN

    SELECT CASE(COLOR)

        CASE(RED)

           STOP

        CASE(YELLOW)

           CALL STOP_IF_YOU_CAN_SAFELY

        CASE(“GREEN”)

           CALL GO_AHEAD

        END SELECT