在COBOL中有几类典型结构的表。这几类典型结构的表在大体上可分为下标表和索引表两大类。另外,根据表的重复次数定义又有定长表和变长表。此外,表还允许嵌套,因此还有嵌套表。这几类表均符合表的基本定义,都是一组连续存储的相似数据的集合。但每种类型的表在此基础上又各有特色。以下几种类型的表两两之间各自存在排它性,即一个表不可能同时为此两种类型的表。
- 下标表和索引表。
- 定长表和变长表。
其余各表之间都是并行的。例如一个表可同时为嵌套表和索引表,或同时为定长表、下标表和嵌套表等等。
对于下标表,本章将主要介绍OCCURS语句、PERFORM VARYING语句和FUNCTION语句。并且,该部分内容还将涉及到下标的基本概念和表的初始化。最后,还将介绍COBOL中三种基于表的典型数据查找方式,即直接查找方式、顺序查找方式、二分查找方式。
对于索引表的操作,将主要介绍基于该表操作的SET语句,SEARCH语句,SEARCH ALL语句。另外还将比较索引表和下标表的异同,并分析索引表的内部存储结构。
下标表
也叫Subscripted表,通过下标组织和访问表中定义的数据条目。此种类型的表是最常见的表,其他各种类型的表的结构可以说都是由该表的结构派生而来的。
在COBOL中,下标相当于各数据在表中的编号。以下标管理数据的表也就是下标表。下标表实际上就是通常所说的最基本的表。因此,对于下标表的定义方式,同上节中表的定义方式类似。这里不妨继续延用上节的例子,则下标表的结构定义如下。
01 ANNUAL-DEPOSIT-TABLE.
05 DEPOSITS PIC 9(5)
OCCURES 12 TIMES.
此外,由于作为下标表,因此还需再定义一个下标。下标名称任意指定,但通常是在对应的表的条目后加上SUB,以利区分。定义语句如下。
05 DEPOSITS-SUB PIC 99 USAGE IS COMP.
将下标定义语句加入到表的基本结构定义语句中就可定义一个下标表了。对于上节零存整取系统的例子而言,完整的下标表的定义语句如下。
01 ANNUAL-DEPOSIT-TABLE.
05 DEPOSITS PIC 9(5)
OCCURES 12 TIMES.
05 DEPOSITS-SUB PIC 99 USAGE IS COMP.
这里,ANNUAL-DEPOSIT-TABLE 是该下标表的名称;DEPOSITS是该表的一个条目名,其*包括12个相似数据;DEPOSITS-SUB是该表的下标。
在表中,相似数据被指定为同一个名称,作为表的一个条目。那么,如何访问这些拥有同一个名称的不同数据?这里就要用到下标了。下标相当于表中各数据的编号,以此指定和访问表中定义的各项数据。
仍然通过上例进行讲解。对应上面定义的下标表,原始数据结构即在表的基本用途一节中“不使用表的情况”下的定义。如下。
05 DEC-DEPOSIT PIC 9(5)
当使用此前定义的下标表来访问上面定义的不同月份的存款额时,就需要用到下标了。访问不同月份存款额所对应的含有不同下标的条目依此如下。
- 访问JAN-DEPOSIT对应于DEPOSITS (1)
- 访问FEB-DEPOSIT对应于DEPOSITS (2)
- 访问MAR-DEPOSIT对应于DEPOSITS (3)
- ……
- 访问DEC-DEPOSIT对应于DEPOSITS (12)
在本例中的下标表里已定义了下标,即DEPOSITS-SUB。因此,通常是用DEPOSITS-SUB变量保存1到12这12个数字,以访问所对应的不同月份的数据。
使用下标DEPOSITS-SUB访问数据,也就是将表的数据条目和下标相组合。组合后形成的表示方式如下。
DEPOSITS (DEPOSITS-SUB)
该表示方式可看作是一项独立的数据,如某一个月的存款额等。DEPOSITS-SUB根据定义为不超过两位数字的整数。当DEPOSITS-SUB为5时,以上数据就为5月份的存款额;当DEPOSITS-SUB为10时,以上数据就为10月份的存款额,依此类推。
这样,通过在数据条目后使用下标,就可以访问表中任意一项具体的数据了。此外,下标作为一个数值类型的变量,还可以指定相关数据项或进行条件判断。
使用下标指定相关数据项,通常是通过“+”,“-”号实现的。比如,若要比较3月份和5月份存款额的大小,则可通过以下代码进行。
MOVE 3 TO DEPOSITS-SUB
IF DEPOSITS (DEPOSITS-SUB)>= DEPOSITS (DEPOSITS-SUB + 2)
IF DEPOSITS (DEPOSITS-SUB)> DEPOSITS (DEPOSITS-SUB + 2)
ELSE
MOVE ‘EQUAL’ TO MAX-MONTH
END-IF
ELSE
MOVE ‘MAY’ TO MAX-MONTH
该段代码中,首先将“3”MOVE到下标DEPOSITS-SUB中。这样,表示三月份存款额数据的代码便如下了。
DEPOSITS (DEPOSITS-SUB)
对于5月份的存款额,同样也可以通过这种方式进行表示,但这样就略嫌麻烦。若此前在下标中已保存有数据,则可通过“+”,“-”号来访问相关数据。本例中由于下标DEPOSITS-SUB已经保存了数字“3”,故可直接表示5月份的数据如下。
DEPOSITS (DEPOSITS-SUB + 2)
这时,DEPOSITS-SUB仍然保存数字“3”。但括号中的下标通过运算已为数字“5”了,表示的是5月份的存款数据。当然,也可先得到5月份的数据,再以5月份的数据为基准得到3月份的数据。实现方式见以下代码,效果和上面的是等同的。
MOVE 5 TO DEPOSITS-SUB
IF DEPOSITS (DEPOSITS-SUB - 2)>= DEPOSITS (DEPOSITS-SUB)
IF DEPOSITS (DEPOSITS-SUB - 2)> DEPOSITS (DEPOSITS-SUB)
ELSE
MOVE ‘EQUAL’ TO MAX-MONTH
END-IF
ELSE
MOVE ‘MAY’ TO MAX-MONTH
对于使用下标进行条件判断,同使用其他变量进行一样。
关于下标的格式要求,主要有以下几点注意的地方。
(1)下标必须为整型数据。
(2)下标既可为具体数字,也可为在数据部定义的一个变量。
通过具体数字定义的下标包含在以下示例语句中。
DEPOSITS (DEPOSITS-SUB)
↑
此处至少应有一个以上的空格(含一个)
最后,关于第(4)点和第(6)点需要再次强调一下,这两点内容是十分重要的。同时,在初学COBOL进行开发时,也是最容易被忽视的。下面特将此二点要求提取出来,列举如下。
- 在定义下标时,提倡在后面加上USAGE IS COMP代码。该代码不添加亦不会报错,但添加后可以提高该数据访问的效率。
- 在定义拥有下标结构的数据时,括号和前面的数据条目名称之间至少应有一个空格。
使用PERFORM VARYING语句处理表中数据
如何使用PERFORM VARYING语句处理表中数据呢?下面仍然结合具体实例进行讲解。
以员工工资管理系统为例,首先进行简化模型提取。提取模型只包括一个员工一年12个月的工资,即只含有12个数据项。将这12个数据定义为下标表结构如下。
SALARIES (10) + SALARIES (11) + SALARIES (12) .
显然,这种方法非常浪费时间。既然表中定义的数据都是相似数据,并且都通过下标有着对应的数据表示方式。那么,通过对下标的相关处理,必然可以简化操作。利用PERFORM VARYING语句进行对表的操作,正是基于这个原理。
使用PERFORM VARYING语句计算全年总工资的代码如下。
PERFORM 200-ADD-TO-TOTAL
VARYING SALARY-SUB FROM 1 BY 1
UNTIL SALARY-SUB > 12
12”对应于原型结构中的x3。
其中200-ADD-TO-TOTAL处理过程的代码如下。
200-ADD-TO-TOTAL.
ADD SALARIES (SALARY-SUB)
TO ANNUAL-TOTAL.
是不是很像其他语言的for 或者 while 语句?
该段代码实现的功能是将SALARIES (SALARY-SUB)的值添加到ANNUAL-TOTAL数据中。由于SALARIES (SALARY-SUB)数据的下标——SALARY-SUB的值是从“1”到“12”的,并且,每步处理过程后,SALARY-SUB的值都相应地增加“1”。因此,以上利用PERFORM VARYING语句进行处理的代码实现了计算全年总工资的功能。
通过上例可以看到,使用PERFORM VARYING语句大大方便了处理过程。实际上,利用PERFORM VARYING语句处理表中数据,主要是利用了表中下标这一特征属性的。PERFORM VARYING语句主要用到了循环结构。基于数据的相关性,以及下标的连续性,通过循环结构进行数据处理,正是该语句的本质特征。
在上面用到的的工资管理系统模型中,使用PERFORM VARYING语句计算全年总工资的代码如下。
PERFORM 200-ADD-TO-TOTAL
VARYING SALARY-SUB FROM 1 BY 1
UNTIL SALARY-SUB > 12
……
200-ADD-TO-TOTAL.
ADD SALARIES (SALARY-SUB)
TO ANNUAL-TOTAL.
该段代码实现的功能,实际上同样也可以使用PERFORM语句完成,代码如下。
MOVE 1 TO SALARY-SUB.
PERFORM 200-ADD-TO-TOTAL
UNTIL SALARY-SUB > 12
……
200-ADD-TO-TOTAL.
ADD SALARIES (SALARY-SUB)
TO ANNUAL-TOTAL.
ADD 1 TO SALARY-SUB.
由此可见,PERFORM VARYING语句和PERFORM语句都具有循环结构。但PERFORM VARYING语句在循环结构的基础上,还增加了一个步进的功能。该功能用于对表进行操作是十分方便的。
初始化
硬性编码方式
硬性编码方式,主要是通过VALUE语句实现的。VALUE跟在每个具体数据项之后(非数据条目)。VALUE语句指定的值也就是该数据的初始化值。对表中每个数据项初始化后,也就实现了对整个表的初始化。
例如,对于保存一周7天的数据的表,通过硬性编码方式初始化代码如下。
05 PIC X(10) VALUE ‘SUNDAY’.
01 WEEK-TABLE-ONE REDEFINES WEEK-VALUES.
05 DAYS PIC X(10) OCCURS 7 TIMES.
可以看到,使用硬性编码方式初试化表,实际上可依此通过以下三步组成。
(1)使用VALUE语句给表中需要用到的各数据赋初值,并写入存储空间。
(2)使用REDEFINES语句为以上数据存储空间指定另一个名称,即表名。
(3)使用OCCURS语句建立表。
这里需要特别注意的是,REDEFINES语句仅仅是为以上存储空间另指定一个名称而已。该语句并没有再另外单独开辟一块存储空间。
掌握对表初始化的一些灵活技巧,对于实际编码将带来一定的方便之处。下面分别介绍两种常用的灵活技巧。
(1)直接在01级数据项后对整张表进行初始化,方式如下。
01 SAMPLE-TABLE-ONE VALUE ‘ABCDE’.
05 SAMPLE-ITEMS-ONE PIC X
OCCURS 5 TIMES.
该方式和使用下面初始化代码实现的效果是一样的。
01 SAMPLE-DATA-ONE.
05 FILLER PIC X VALUE ‘A’.
05 PIC X VALUE ‘B’.
05 PIC X VALUE ‘C’.
05 PIC X VALUE ‘D’.
05 PIC X VALUE ‘E’.
01 SAMPLE-TABLE-ONE REDEFINES SAMPLE-DATA-ONE.
05 SAMPLE-ITEMS-ONE PIC X OCCURS 5 TIMES.
(2)直接在数据项后对其进行初始化,方式如下。
01 SAMPLE-TABLE-TWO.
05 SAMPLE-ENTRY-TWO OCCURS 2 TIMES.
10 SAMPLE-ITEM-NUM PIC 99 VALUE 00.
10 SAMPLE-ITEM-DATA PIC XX VALUE ‘AA’.
该方式和使用下面初始化代码实现的效果是一样的。
01 SAMPLE-DATA-TWO.
05 FILLER PIC 99 VALUE 00.
05 PIC XX VALUE ‘AA’.
05 PIC 99 VALUE 00.
05 PIC XX VALUE ‘AA’.
01 SAMPLE-TABLE-TWO REDEFINES SAMPLE-DATA-TWO.
05 SAMPLE-ENTRY-TWO OCCURS 2 TIMES.
10 SAMPLE-ITEM-NUM PIC 99.
10 SAMPLE-ITEM-DATA PIC XX.
表查找
直接查找
在使用表进行数据查找时,首先应明确数据编号这个概念。数据编号是由人为指定的,用于代表相应数据的一种编号。编号通常为数字形式,也可看作一项数据,并不等同于下标。
用于直接查找的表中数据的编号和存储位置是相同的,因此在定义该表中不必指定数据编号。数据的编号直接由其所在位置确定。因此,定义用于直接查找的表时往往只用包含一个条目,即数据的条目。例如,对于一个大学里全体人员的管理系统,可简单的将各人员的数据定义如下。
01 UNI-ROLE-TABLE.
05 ROLE PIC X(10) /*大学里各人员对应的角色*/
OCCURES 8 TIMES. /*共有8种不同的角色*/
在实际应用中,还需将该表进行初始化。这里可通过上节所讲到的任意一种初始化表的方法进行初始化。即硬性编码方式或输入载入方式。不妨假设没有对应的输入加载数据文件,则可通过硬性编码方式初始化表,代码如下。
01 UNI-ROLE-VALUES.
05 FILLER PIC X(10) VALUE ‘FRESHMAN’.
05 PIC X(10) VALUE ‘SOPHOMORE’.
05 PIC X(10) VALUE ‘JUNIOR’.
05 PIC X(10) VALUE ‘SENIOR’.
05 PIC X(10) VALUE ‘MASTER’.
05 PIC X(10) VALUE ‘DOCTOR’.
05 PIC X(10) VALUE ‘FACULTY’.
05 PIC X(10) VALUE ‘STAFF’.
01 UNI-ROLE-TABLE REDEFINES UNI-ROLE-VALUES.
05 ROLE PIC X(10) OCCURS 8 TIMES.
初始化后的表结构如表所示。
Freshman
Sophomore
Junior
Senior
Master
Doctor
Faculty
Staff
以上表格所包含的内容,从上到下依次为:大一学生、大二学生、大三学生、大四学生、研究生、博士生、全体教员和全体职员。其中每项数据所在表中的编号和其存储位置都是一一对应的。并且编号从数字1开始。例如,大一学生对应的编号为1,大二学生对应的编号为2,等等。
对于上例所定义的表,可以通过默认的编号对数据进行直接查找。例如,当需要访问研究生这一项数据时,可依照以下步骤进行。
- 首先,找到表中数据的存储位置,得到该数据对应的编号。对于本例而言,研究生对应于“Master”数据项,在表中存储位置为5。因此,该数据的编号即为5。
- 其次,将编号移动到数据条目的下标中,得到ROLE(5)。
- 最后,通过ROLE(5)直接访问该表中的研究生数据。
总之,对于用于直接查找方式的表而言,知道了表中数据的存储位置,也就可以得到该数据的编号。反之,若知道一个数据在表中的编号,也就知道了该数据的存储位置。
顺序查找
下面以一个银行账户管理系统为例子,具体分析在通常情况下是如何进行顺序查找的。对于银行账户管理系统而言,最主要的数据是以下两条。
- 账号
- 账号所对应的用户名
下面仍然针对顺序查找提取一个简化的模型。该模型只包含以上两类数据,并且假设用户数为10人。对此,可定义银行账户管理表如下。
以上表格中的数据都是任意指定的。另外,这里需要再强调一下,数据编号和下标并不是一个概念。对于本例而言,数据编号就是账号ACCOUNT-NUM。该数据编号用以代表相应数据,即用户名ACCOUNT-NAME。数据通常是有具体意义的,如ACCOUNT-NAME数据表示人名。而编号则是没有具体意义的,只是为了更方便地表示数据。
通过上表可以看出,此处数据编号和数据存放的位置并不是一致的。为更好地说明这一点,银行账户表中的数据编号,数据以及数据存放位置的对应关系如表所示。
表 数据编号,数据和数据存放位置的对应关系
数据编号 |
数据 |
数据存放位置 |
021845984 |
TOM |
1 |
547874956 |
HARRY |
2 |
201874698 |
PITTER |
3 |
004847624 |
SMITH |
4 |
478206045 |
JONES |
5 |
032874512 |
ROBIN |
6 |
064840574 |
SIMON |
7 |
120873954 |
FRANK |
8 |
657842216 |
SANDY |
9 |
689574402 |
SUE |
10 |
对于该表而言,不妨假设需要查找数据编号(即账号)为“064840574”的数据(即用户名)。则使用顺序查找方式的代码如下。
05 ACCOUNT-SUB PIC 99 USAGE IS COMP. /*定义下标*/
05 FOUND-FLAG PIC X. /*定义找到标志*/
05 KEY-NUM PIC 9(9). /*该变量用于存放指定的数据编号*/
05 FOUND-NAME PIC X(10). /*该变量用于存放查找到的数据*/
……
MOVE ‘N’ TO FOUND-FLAG. /*将找到标志设为默认值‘N’*/
MOVE ‘064840574’ TO KEY-NUM. /*将指定的数据编号MOVE到KEY-NUM变量中*/
PERFORM 100-FIND-NAME
VARYING ACCOUNT-SUB FROM 1 BY 1
UNTIL ACCOUNT-SUB > 10
OR
FOUND-FLAG = ‘Y’.
以上实际上是通过PERFORM VARYING语句从表的第一条数据开始,一条条顺次查找的。因此,这也是该查找方式被称为顺序查找方式的原因。
对于上面的100-FIND-NAME具体每一步的查找过程,代码如下。
100-FIND-NAME.
IF ACCOUNT-NUM (ACCOUNT-SUB) = KEY-NUM
MOVE ‘Y’ TO FOUND-FLAG
MOVE ACCOUNT-NAME (ACCOUNT-SUB) TO FOUND-NAME
END-IF.
通过顺序查找方式查找后,最终找到的数据应该为“SIMON”。整个查找过程共进行了7次查找