IBM主机技术一本通
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

6.4 PERFORM语句

循环是一个非常重要的程序设计结构,这在我们后面介绍结构程序设计的时候,你更能看到这一点。当我们希望重复执行一段程序代码时,就要用到循环结构。绝大多数程序设计语言提供多种循环语句,比如,WHILE、FOR和REPEAT等,用来完成不同类型的循环结构。

COBOL只提供了一种循环结构,即PERFORM语句。但PERFORM语句有几种不同的变化形式。每种不同的形式对应于其他程序设计语言中的不同循环类型。

在介绍PERFORM语句之前,我们先复习一下COBOL程序段(Paragraph)的含义。段是一块程序代码并为它取了一个名字,即段名。段名是由程序员根据COBOL命名规则定义的名字。段名总是由句号(.)结束。一段程序可以有许多条语句和句子,并不是每条语句都需要句号结束,但最后一条语句必须以句号结束。当碰到别的段或程序结尾时,本段就结束了。

下面是PERFORM语句的第1种格式。

格式1:

这是唯一不是循环结构的PERFORM语句。它的功能是将程序的控制权转去执行另一块代码。这段代码从BeginProc段开始,如果指定了EndPorc,则到EndProc结束;如果没有指定EndProc,则只执行到段BeginProc的结束。当指定的程序段执行完后,程序的控制权会转回到紧跟在PERFORM语句后面的语句上。BeginProc和EndProc必须是同一节里面的段名。

PERFORM…THRU命令让计算机将从BeginProc到EndProc之间的段或节当成一个完整的程序块来处理,这样一条PERFORM语句就可以执行从BeginProc到EndProc之间的所有程序段。

但在实际的程序设计实践中,通常BeginProc和EndProc在同一个程序段中。其中,BeginProc是段的最开始语句,而EndProc紧跟在BeginProc的最后一条语句后面。当执行某一段程序过程中碰到一些特殊条件需要结束程序段的执行时,这时使用PERFORM Thru就非常方便。

以前的COBOL语言的PERFORM语句只能执行别处的程序段(循环体),但现在的COBOL语言提供了所谓的内置(In-Line)PERFORM语句,这样,要执行的程序段(循环体)就在PERFORM语句本身中。这时,PERFORM语句必须以END-PERFORM结束,要执行的程序段或循环体直接写在PERFORM和END-PERFORM之间,下面是一个内置PERFORM的例子。

        PERFORM VARYING Ws-Index FROM 1 BY 1 UNTIL Ws-Index > 5
            MOVE Data-Fld(Ws-Index) TO Rpt-Fld(Ws-Index)
        END-PERFORM

内置PERFORM通常用于要执行的程序段,通常只会执行一次,而不会在程序中多次执行时使用。在内置PERFORM语句中不能出现句号(.),它由END-PERFORM结束。因此,当你使用外置PERFORM语句时,就不能使用END-PERFORM短语。

接下来的3种PERFORM语句都是循环结构,你可以很容易找到每种循环结构在其他程序设计语言中的等价结构。下面的第2种格式的PERFORM语句相当于其他程序设计语言的REPEAT循环,用来指定执行程序段的次数。其中,如果是外置循环(Out-of-Line),BeginProc和EndProc指定循环体的程序段的开始和结束位置,Count变量指定循环的次数。这种结构也支持内置PERFORM语句,这时循环体(LoopStatements)直接写在PERFORM语句内,也是由END-PERFORM结束。

格式2:

第3种格式的PERFORM语句是PERFORM UNTIL语句,相当于其他程序设计语言的DO WHILE结构,循环体的执行次数由循环的结束条件(Condition)决定。如果使用外置PERFORM结构,循环体就是从BeginProc到EndProc之间的一段程序。如果是内置PERFORM,循环体就是直接写在PERFORM语句中的一段程序(LoopStatements)。循环结束的条件可以在执行循环体之前检查(With Test Before),也可以在执行循环体之后检查(With Test After)。系统默认值为WITH TEST BEFORE,因此,当你在PERFORM语句中没有明确表明几时检查循环条件是否满足时,系统默认就是在执行循环体之前检查条件是否满足。

格式3:

图6.1是PERFORM…UNTIL语句的流程图,相信你能够很容易理解它们

图6.1 PERFORM…UNTIL语句的流程图

第4种格式的PERFORM语句是PERFORM VARYING格式,类似于其他语言的FOR循环语句。每次循环时循环变量(Identifier1或IndexName)都会跟着变化,它的起始值由FROM短语中的变量(Identifier2等)指定,每次循环完后,循环变量的值会增加(减少)由BY短语指定的值(Identifier3等),循环结束的条件取决于UNTIL短语中指定的条件(Condition1)是否满足。像上面的PERFORM语句一样,循环结束条件可以在执行循环体之前(With Test Before)检查,也可以在执行完循环体之后(With Test After)检查,默认值是WITH TEST BOFORE。如果是外置循环,循环体就是从BeginProc到EndProc之间的一段程序;如果是内置循环,循环体就是写在PERFORM和END-PERFORM之间的一段程序代码(StatementBlock)。

如果使用AFTER短语,表示该PERFORM语句有两层循环,即内循环和外循环,内循环由AFTER短语后面的部分定义,其格式跟由VARYING短语定义的外循环的格式是完全一样的。

格式4:

下面是PERFORM VARYING语句的一个例子,该PERFORM语句重复执行DISPLAY WS-Index变量(循环体)直到WS-Index大于3,WS-Index的初始值是1,每次循环执行后加1。该循环一共会执行3次(WS-Index分别是1、2和3),当WS-Index变为4时,循环结束条件满足,循环结束。

        PERFORM VARYING WS-Index FROM 1 BY 1 UNTIL WS-Index > 3
            DISPLAY WS-Index
        END-PERFORM.

图6.2是上述PERFORM语句的流程图,它清楚说明了程序的执行过程。

图6.2 PERFORM…VARYING语句的流程图

下面是带有AFTER短语的PERFORM VARYING语句,见到AFTER短语,你就知道它有两层循环,即内循环和外循环。内循环由WS-Idx2来主导,它的初始值为4,每次循环后减1,因为BY短语后的值为负值(-1)直到WS-Idx2小于1为止。内循环会执行4次(WS-Idx2的值分别为4、3、2和1)。外循环由WS-Idx1主导,其初始值为1,每次循环后加2,直到大于5为止,所以,总共会循环3次(WS-Idx1的值分别是1、3和5)。这样,整个循环体会执行12次(3×4=12)。

        PERFORM LoopBody VARYING WS-Idx1 FROM 1 BY 2 UNTIL WS-Idx1 > 5
                          AFTER WS-Idx2 FROM 4 BY -1 UNTIL WS-Idx2 < 1

图6.3是带有AFTER短语的PERFORM VARYING语句的流程图,它说明了语句的执行过程。

图6.3 带有AFTER短语的PERFORM VARYING语句的流程图

6.4.1 程序例子(PERFORM语句)

本次的程序依然沿用前面演示(Magic)报表的风格。第48行到第53行的内置PERFORM语句使用了VARYING短语,用来循环执行内置循环体,从报表中我们知道该循环体一个执行了3次,每次除了累积循环次数外,还对表(数组)进行了赋值,从报表的注解中我们可以看到,WS-TABLE的前3个表项的值已经变成了‘V’,即VICTORY,成功了。实际上,PERFORM VARYING格式通常都是与表或数组一起使用的,因为每次循环后,下表都会变化。内置PERFORM没有离开程序主体,因此,它仍然处于程序的第1层。报表的第3行支持了我们的描述,这行报表是由第54行到第59行的代码生成的。

第62行的代码执行第113行到第120行的程序段PARA-A。报表第4行告诉我们,PARA-A程序段只执行了一次,它处于程序的第2层。第63行到第68行的代码生成报表的第5行,程序在执行完PARA-A后回到主程序,即第1层。

第72行的PERFORM语句执行从PARA-B(第122行)到PARA-B-EXIT(第139行)之间的一段程序。如果不出意外,从第122行到第139行的程序要全部执行一遍,但在现实的程序设计实践中,因为业务是千变万化的,不可能每种业务情形写一段程序,那样程序就太长了,读起来不好懂,按现在流行的说法,也不环保(程序越长占用的磁盘越多,打印出来也浪费纸),所以,你会看到,每一段程序会处理一组而不是一种业务情形,程序通过各种手法比如判断、比较的方法,将每种业务情形的处理放到同一个程序段中。这样,当某种类型的业务处理完了后,就要退出当前的程序段而不是一股脑做到程序段的结尾。这样,使用PERFORM THRU的方法,就可以让执行完的业务情形方便地跳出当前的程序段。

你可能会说,你怎么在程序的第125行和第134行使用了万恶不赦的GO TO语句,这是结构程序设计绝对不能容忍的。但实际上,这种本模块内跳到程序段结尾的自顶向下的GO TO语句是良性的,它反而有利于提高程序的可读性。关于这一点,我们会在后面的结构程序设计部分做专门的讨论。

从第6行的报表我们可以看出,程序是从PARA-B的中间跳出来的,因为它所对应的业务逻辑已经处理完了。第73行到第78行的程序逻辑告诉我们,PERFORM语句又回到主模块,这在报表的第7行得到了验证。

第81行的PERFORM语句执行PARA-C段4次,第8行的报表证明程序正确地做到了这一点。第82行到第86行的程序生成报表的第9行,告诉我们再次回到了主程序模块。

第95行到第98行的PERFORM语句是最长,也是最复杂的PERFORM语句,它同时使用了VARYING和AFTER短语,让PERFORM语句的复杂程度上了一个台阶。这种格式的PERFORM语句完成的是两个循环的功能,即内循环和外循环,内循环是由AFTER短语领衔的,而外循环则是由VARYING短语主导的。看看报表的第20行,一共循环了20次,即循环体一共执行了20次。你能知道这20次是怎么来的吗?其实很简单,内循环每次执行4次(WS-Idx2分别是4、3、2和1),而外循环则一共执行了5次(WS-Idx1分别是1、3、5、7和9),这样整个的执行次数就是20(5×4)了。

第104行到第109行的程序完成报表的最后,即第11条记录,告诉我们,程序在完成PERFORM语句后重新回到了主程序,我们的PERFORM测试也就结束了。

如果你有兴趣,可以详细阅读下面的完整程序。

        000001  IDENTIFICATION DIVISION.
        000002 *
        000003  PROGRAM-ID. PERFORM1.
        000004  AUTHOR. NEWMAN LV.
        000005 *
        000006  ENVIRONMENT DIVISION.
        000007 *
        000008  INPUT-OUTPUT SECTION.
        000009 *
        000010  DATA DIVISION.
        000011 *
        000012
        000013  FILE SECTION.
        000014 *
        000015  WORKING-STORAGE SECTION.
        000016 *
        000017  01 RPT-HEADING.
        000018    03 FILLER          PIC X(20) VALUE SPACES.
        000019    03 RPT-HEAD     PIC X(25) VALUE '*MAGIC REPORT(PERFORM)*'.
        000020    03 FILLER          PIC X(27) VALUE SPACES.
        000021  01 RPT-SUB-HEADING.
        000022    03 FILLER          PIC X(25) VALUE '*------TYPES-------*'.
        000023    03 FILLER          PIC X(01) VALUE SPACE.
        000024    03 FILLER          PIC X(07) VALUE '*LEVEL*'.
        000025    03 FILLER          PIC X(01) VALUE SPACE.
        000026    03 FILLER          PIC X(08) VALUE '*TIMES*'.
        000027    03 FILLER          PIC X(01) VALUE SPACE.
        000028    03 FILLER          PIC X(20) VALUE '*-----COMMENTS-----*'.
        000029  01 RPT-DETAIL.
        000030    03 WS-TYPES         PIC X(25).
        000031    03 FILLER          PIC X(04) VALUE SPACE.
        000032    03 WS-LEVEL         PIC X(03).
        000033    03 FILLER          PIC X(04) VALUE SPACE.
        000034    03 WS-TIMES         PIC 9(02).
        000035    03 FILLER          PIC X(05) VALUE SPACE.
        000036    03 WS-COMMENTS      PIC X(20).
        000037  01 WS-ITEMS.
        000038    03 WS-IDX1          PIC 9(02).
        000039    03 WS-IDX2          PIC 9(02).
        000040    03 WS-COUNT         PIC 9(02).
        000041    03 WS-TABLE-G.
        000042       05 WS-TABLE OCCURS 5 TIMES PIC X(01).
        000043 *
        000044  PROCEDURE DIVISION.
        000045 * SHOW HEADING AND SUBHEADING
        000046      DISPLAY RPT-HEADING
        000047      DISPLAY RPT-SUB-HEADING
        000048 * IN LINE PERFORM.
        000049      MOVE ZEROS                 TO WS-COUNT
        000050      PERFORM VARYING WS-IDX1 FROM 1 BY 1 UNTIL WS-IDX1 > 3
        000051         MOVE 'V'                TO WS-TABLE(WS-IDX1)
        000052         ADD 1 TO WS-COUNT
        000053      END-PERFORM
        000054      MOVE 'IN-LINE PERFORM VARYING'  TO WS-TYPES
        000055      MOVE '1ST'                 TO WS-LEVEL
        000056      MOVE WS-COUNT              TO WS-TIMES
        000057      MOVE WS-TABLE-G            TO WS-COMMENTS
        000058      DISPLAY RPT-DETAIL
        000059      .
        000060 * PERFORM PARAGRAPH A.
        000061      MOVE ZEROS                 TO WS-COUNT
        000062      PERFORM PARA-A
        000063      MOVE 'GO BACK FROM PARA-A   '  TO WS-TYPES
        000064      MOVE '1ST'                 TO WS-LEVEL
        000065      MOVE ZEROS                 TO WS-TIMES
        000066      MOVE 'IN MAINBODY AGAIN'    TO WS-COMMENTS
        000067      DISPLAY RPT-DETAIL
        000068      .
        000069 * PERFORM PARAGRAPH B THRU B-EXIT.
        000070      MOVE ZEROS                 TO WS-COUNT
        000071      MOVE 2                    TO WS-IDX1
        000072      PERFORM PARA-B THRU PARA-B-EXIT
        000073      MOVE 'GO BACK FROM PARA-B   '  TO WS-TYPES
        000074      MOVE '1ST'                 TO WS-LEVEL
        000075      MOVE ZEROS                 TO WS-TIMES
        000076      MOVE 'IN MAINBODY AGAIN'    TO WS-COMMENTS
        000077      DISPLAY RPT-DETAIL
        000078      .
        000079 * PERFORM TIMES
        000080      MOVE ZEROS                 TO WS-COUNT
        000081      PERFORM PARA-C 4 TIMES
        000082      MOVE 'PERFORM TIMES         '  TO WS-TYPES
        000083      MOVE WS-COUNT              TO WS-TIMES
        000084      MOVE SPACES                TO WS-COMMENTS
        000085      DISPLAY RPT-DETAIL
        000086      .
        000087      MOVE 'GO BACK FROM PARA-B   '  TO WS-TYPES
        000088      MOVE '1ST'                 TO WS-LEVEL
        000089      MOVE ZEROS                 TO WS-TIMES
        000090      MOVE 'IN MAINBODY AGAIN'    TO WS-COMMENTS
        000091      DISPLAY RPT-DETAIL
        000092      .
        000093 * PERFORM VARYING AFTER
        000094      MOVE ZEROS                 TO WS-COUNT
        000095      PERFORM PARA-C VARYING WS-IDX1 FROM 1 BY 2
        000096                   UNTIL WS-IDX1 > 10
        000097                   AFTER WS-IDX2 FROM 4 BY -1
        000098                   UNTIL WS-IDX2 < 1
        000099      MOVE 'PERFORM VARYING...AFTER'  TO WS-TYPES
        000100      MOVE WS-COUNT              TO WS-TIMES
        000101      MOVE SPACES                TO WS-COMMENTS
        000102      DISPLAY RPT-DETAIL
        000103      .
        000104      MOVE 'GO BACK FROM PARA-C   '  TO WS-TYPES
        000105      MOVE '1ST'                 TO WS-LEVEL
        000106      MOVE ZEROS                 TO WS-TIMES
        000107      MOVE 'ABOUT TO GO HOME '    TO WS-COMMENTS
        000108      DISPLAY RPT-DETAIL
        000109      .
        000110 *
        000111      STOP RUN.
        000112 *
        000113  PARA-A.
        000114      ADD 1 TO WS-COUNT
        000115      MOVE 'PERFORM PARA-A        '  TO WS-TYPES
        000116      MOVE '2ND'                 TO WS-LEVEL
        000117      MOVE WS-COUNT              TO WS-TIMES
        000118      MOVE 'IN PARA-A'           TO WS-COMMENTS
        000119      DISPLAY RPT-DETAIL
        000120      .
        000121 *
        000122  PARA-B.
        000123      IF WS-IDX1 = 1
        000124         DISPLAY WS-IDX1
        000125         GO TO PARA-B-EXIT
        000126      .
        000127      IF WS-IDX1 = 2
        000128         ADD 1 TO WS-COUNT
        000129         MOVE 'PERFORM PARA-B THRU B-EXIT'  TO WS-TYPES
        000130         MOVE '2ND'              TO WS-LEVEL
        000131         MOVE WS-COUNT           TO WS-TIMES
        000132         MOVE 'LEAVING PARA-B NOW'   TO WS-COMMENTS
        000133         DISPLAY RPT-DETAIL
        000134         GO TO PARA-B-EXIT
        000135      .
        000136      IF WS-IDX1 < 0
        000137         DISPLAY WS-IDX1
        000138      .
        000139  PARA-B-EXIT.
        000140      EXIT.
        000141  PARA-C.
        000142      ADD 1 TO WS-COUNT
        000143      MOVE '2ND'                 TO WS-LEVEL
        000144      .

6.4.2 程序执行结果(PERFORM语句)

        000001                  *MAGIC REPORT(PERFORM)*
        000002 *------TYPES-------*     *LEVEL* *TIMES*  *-----COMMENTS-----*
        000003 IN-LINE PERFORM VARYING    1ST      03    VVV
        000004 PERFORM PARA-A              2ND      01    IN PARA-A
        000005 GO BACK FROM PARA-A         1ST      00    IN MAINBODY AGAIN
        000006 PERFORM PARA-B THRU B-EXI   2ND      01    LEAVING PARA-B NOW
        000007 GO BACK FROM PARA-B         1ST      00    IN MAINBODY AGAIN
        000008 PERFORM TIMES               2ND      04
        000009 GO BACK FROM PARA-B         1ST      00    IN MAINBODY AGAIN
        000010 PERFORM VARYING...AFTER    2ND      20
        000011 GO BACK FROM PARA-C         1ST      00    ABOUT TO GO HOME