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