COBOL编程入门:从结构到数据处理
在当今快速迭代的软件世界里,我们谈论的是微服务、容器化和AI驱动开发。但就在这些前沿技术的背后,全球仍有超过2200亿行COBOL代码默默支撑着银行转账、社保发放和航空订票系统。它诞生于1959年,却从未真正退出舞台——尤其是在金融与政府核心系统中,COBOL依然是那个“不能倒”的基石。
这不是一门追求炫技的语言。它的设计哲学是清晰、稳定与可维护性。如果你曾为一段晦涩难懂的脚本头疼过,那么看到IF SALES IS GREATER THAN TARGET THEN GIVE BONUS这样的语句时,或许会心一笑:这不就是用英语写逻辑吗?
让我们从一个真实的业务场景出发:生成一份简单的利息计算表。假设你需要读取一笔本金和若干利率,逐年输出复利总额。这个需求看似简单,但它涉及文件操作、循环控制、算术运算和格式化输出——正好覆盖了COBOL的核心能力。
****************************************************************** * * * SIMPLE INTEREST CALCULATION PROGRAM * * VERSION 1.0 * * COPYRIGHT (C) 2025 * * ALL RIGHTS RESERVED * * * ****************************************************************** IDENTIFICATION DIVISION. PROGRAM-ID. INTTABLE. AUTHOR. TECH_TRAINER. DATE-WRITTEN. 25-04-05. DATE-COMPILED. 25-04-05. *----------------------------------------------------------------* * PURPOSE : TO GENERATE A SIMPLE INTEREST TABLE * *----------------------------------------------------------------* ENVIRONMENT DIVISION. CONFIGURATION SECTION. SOURCE-COMPUTER. IBM-Z. OBJECT-COMPUTER. IBM-Z. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT INPUT-FILE ASSIGN TO "INFILE.DAT". SELECT OUTPUT-FILE ASSIGN TO "OUTFILE.LST". DATA DIVISION. FILE SECTION. FD INPUT-FILE LABEL RECORD IS STANDARD. 01 IN-RECORD. 05 PRINCIPAL-IN PIC 9(6)V99. 05 RATE-COUNT PIC 99. 05 FILLER PIC X(10). FD OUTPUT-FILE BLOCK 0 RECORDS LABEL RECORD STANDARD RECORDING F. 01 OUT-LINE PIC X(80). WORKING-STORAGE SECTION. 01 WS-DATA. 05 YEARS PIC 99 VALUE 10. 05 CURR-YEAR PIC 99. 05 INTEREST-RATES OCCURS 5 TIMES PIC V999 VALUE 0.05, 0.06, 0.07, 0.08, 0.09. 05 AMOUNT-TABLE OCCURS 5 TIMES PIC Z,ZZZ,ZZ9.99. 05 HEADER-LINE PIC X(80) VALUE 'YEAR RATE-1 RATE-2 RATE-3 RATE-4 RATE-5'. PROCEDURE DIVISION. MAIN-LOGIC SECTION. OPEN INPUT INPUT-FILE. OPEN OUTPUT OUTPUT-FILE. READ INPUT-FILE INTO WS-DATA. IF END-OF-FILE PERFORM USE-DEFAULT-VALUES. WRITE OUT-LINE FROM HEADER-LINE AFTER ADVANCING PAGE. PERFORM PROCESS-YEARS VARYING CURR-YEAR FROM 1 BY 1 UNTIL CURR-YEAR > YEARS. CLOSE INPUT-FILE, OUTPUT-FILE. STOP RUN. USE-DEFAULT-VALUES. MOVE 10000.00 TO PRINCIPAL-IN. MOVE 5 TO RATE-COUNT. EXIT. PROCESS-YEARS. MOVE CURR-YEAR TO OUT-LINE(1:2). MOVE SPACES TO OUT-LINE(3:). PERFORM CALCULATE-AMOUNTS VARYING WS-I FROM 1 BY 1 UNTIL WS-I > RATE-COUNT. WRITE OUT-LINE AFTER ADVANCING 1 LINE. CALCULATE-AMOUNTS. COMPUTE AMOUNT-TABLE(WS-I) = PRINCIPAL-IN * (1 + INTEREST-RATES(WS-I) * CURR-YEAR). STRING AMOUNT-TABLE(WS-I) DELIMITED BY SIZE INTO OUT-LINE WITH POINTER POSN. END PROGRAM INTTABLE.这段程序虽显冗长,但其结构之严谨令人安心。每一个动作都有明确归属:哪里打开文件,哪里做计算,哪里写结果,一目了然。这种“按部就班”的风格,正是大型团队协作维护的关键所在。
程序结构的设计哲学
COBOL程序由四个固定顺序的“部”构成,缺一不可:
- IDENTIFICATION DIVISION:程序的身份证明,包含ID、作者、日期等元信息。
- ENVIRONMENT DIVISION:定义运行环境,如主机型号和文件映射关系。
- DATA DIVISION:声明所有变量与文件结构。
- PROCEDURE DIVISION:存放执行逻辑。
层级上遵循Division → Section → Paragraph → Sentence → Statement → Clause的嵌套规则。比如:
PROCESS-DATA SECTION. VALIDATE-INPUT. IF EMPLOYEE-ID NOT NUMERIC DISPLAY 'INVALID ID' GO TO ERROR-HANDLER.这里的PROCESS-DATA SECTION.是节,VALIDATE-INPUT.是段,后面两行是两个独立的语句组成的句子。这种层次感让复杂流程也能保持条理清晰。
早期COBOL受限于打孔卡,因此对源码列位置有严格要求。虽然现代编译器支持自由格式,但传统布局仍被广泛沿用:
1 6 7 8 11 12 72 73 80 ┌────┬─────┬─┬────┬───────────────────────────────────────────────┬────┬────┐ │标号区│注释区 │A│B区 │ 正文区 │ 注释 │ └────┴─────┴─┴────┴───────────────────────────────────────────────┴────┴────┘关键点在于:
- 第7列为注释标志(*或/)
- A区(8–11列)只能放DIVISION、SECTION、01/77级项或段名
- B区(12–72列)是语句正文区域
- 73–80列为忽略区,可用于版本标记
哪怕是最简单的“Hello World”,也必须完整包含四大部:
IDENTIFICATION DIVISION. PROGRAM-ID. HELLO. AUTHOR. BEGINNER. DATE-WRITTEN. 25-04-05. ENVIRONMENT DIVISION. DATA DIVISION. PROCEDURE DIVISION. DISPLAY 'HELLO, WORLD FROM COBOL!'. STOP RUN.没有捷径可走。这种强制性的规范,反而减少了因风格差异导致的理解成本。
数据如何被定义与存储
COBOL的数据定义极其细致,几乎到了“啰嗦”的程度,但也因此避免歧义。
常量使用语义化关键字表示:
-ZERO,SPACES,HIGH-VALUES,LOW-VALUES,QUOTES
- 可配合ALL构造重复字符串:MOVE ALL '*' TO STR得到"******"
结构通过数字层级组织:
-01–49:组合项(可嵌套子字段)
-77:独立工作变量
-88:条件名(布尔状态)
-66:重命名已有字段组
例如一个日期时间结构:
01 WS-DATETIME. 05 WS-DATE. 10 WS-YEAR PIC 9(4). 10 WS-MONTH PIC 99. 10 WS-DAY PIC 99. 05 WS-TIME. 10 WS-HOUR PIC 99. 10 WS-MINUTE PIC 99. 10 WS-SECOND PIC 99.访问时可用WS-YEAR OF WS-DATETIME明确路径。
核心是PICTURE子句,用于描述数据形态:
| 符号 | 含义 |
|---|---|
9 | 数字位 |
V | 隐含小数点(不占空间) |
S | 符号位(EBCDIC编码) |
A | 字母字符 |
X | 任意字符 |
Z | 数值零显示为空格 |
$ | 货币符号 |
CR/DB | 借贷标记 |
像PIC Z,ZZ9.99CR这样的格式,专为财务报表设计,能自动将负数显示为1,234.56CR。
内存存储方式影响性能与兼容性:
-DISPLAY格式:每位数字单独编码(F1 F2 F3),人类可读但占空间
-COMP-3(压缩十进制):每两数字压缩成一字节,末半字节存符号,节省空间
-COMP(二进制):以机器码存储,适合频繁计算
选择哪种取决于用途:显示优先选DISPLAY,数据库交互常用COMP-3,计数器则多用COMP。
运算与赋值的细节艺术
MOVE是最基础的操作,但行为依类型而异:
- 数值传送按小数点对齐,不足补零,超出截断高位
- 字符左对齐,右补空格或截断
- 组合项直接逐字节复制,无类型转换
更聪明的是MOVE CORRESPONDING,只复制同名字段:
MOVE CORR PAYREC TO PRINTREC.即使两个结构布局不同,只要字段名匹配(如EMP-ID,SALARY),就能自动映射。
基本算术指令语法直白:
ADD A TO B SUBTRACT A FROM B MULTIPLY A BY B DIVIDE A INTO B GIVING C REMAINDER D真正强大的是COMPUTE,支持复杂表达式:
COMPUTE NET = (GROSS - TAX) * (1 + BONUS-RATE) + OVERTIME_PAY支持括号、幂运算(**)、四则混合,并可通过ROUNDED实现四舍五入:
COMPUTE AVG = (A + B + C) / 3 ROUNDED别忘了溢出保护——ON SIZE ERROR能捕获数值越界:
MULTIPLY HOURS BY RATE GIVING PAY ON SIZE ERROR DISPLAY 'PAY TOO LARGE!' MOVE ZERO TO PAY.这在处理大额交易时至关重要。
字符串操作:连接、拆分与替换
尽管COBOL不是为文本处理设计的,但仍有三把利器:
STRING:拼接字符串
STRING FIRST DELIMITED BY SPACE LAST DELIMITED BY SIZE INTO FULL-NAME WITH POINTER POS ON OVERFLOW ...可以指定分隔符、起始指针和溢出处理。
UNSTRING:按分隔符分解
UNSTRING INPUT-LINE DELIMITED BY ',' INTO FIELD1, FIELD2, FIELD3 TALLYING IN COUNT.还能捕获实际提取长度、分隔符内容,甚至追踪成功赋值次数。
INSPECT:扫描与替换
INSPECT TEXT TALLYING COUNTER FOR ALL 'X' INSPECT TEXT REPLACING ALL 'A' BY 'B' INSPECT TEXT TALLYING TALLY REPLACING LEADING '0' BY SPACE支持限定范围(BEFORE/AFTER某字符串),常用于清洗前导零或统计字符频次。
这些命令看似笨拙,但在批量处理固定格式报文时非常高效。
控制流:从判断到循环
COBOL的条件判断丰富多样:
- 关系比较:A > B
- 类型检查:IF FIELD IS NUMERIC
- 符号判断:IS POSITIVE,NEGATIVE
- 条件名:基于88级定义的状态判断
- 逻辑组合:AND,OR,NOT
分支结构有两种主流写法:
传统的IF-ELSE:
IF SALARY > 50000 MOVE 'SENIOR' TO GRADE ELSE MOVE 'JUNIOR' TO GRADE END-IF.以及类似switch-case的EVALUATE:
EVALUATE TRUE WHEN GRADE = 'A' PERFORM BONUS-A WHEN GRADE = 'B' PERFORM BONUS-B WHEN OTHER DISPLAY 'NO BONUS' END-EVALUATE.后者更适合多路分支,代码更整洁。
循环统一用PERFORM实现:
PERFORM INIT-VARS THRU CLEAR-END *> 执行一段逻辑 PERFORM LOGIC 10 TIMES *> 固定次数 PERFORM SCAN UNTIL DONE *> 条件循环 PERFORM VARYING I FROM 1 BY 1 UNTIL I > 100 *> 类似for支持嵌套循环:
PERFORM OUTER VARYING I FROM 1 BY 1 UNTIL I > 10 AFTER J FROM 1 BY 1 UNTIL J > 10.每个段建议以EXIT结尾作为标记:
CLEANUP-EXIT. EXIT.便于其他地方用PERFORM ... THRU CLEANUP-EXIT安全跳转。
表(Table)即数组
COBOL称数组为“表”,用OCCURS定义:
01 MONTHLY-SALES. 05 SALES-AMT OCCURS 12 TIMES PIC 9(6)V99.下标从1开始,最大不超过定义次数。
动态长度需借助DEPENDING ON:
01 SCORES OCCURS 1 TO 10 TIMES DEPENDING ON COURSE-COUNT.注意:依赖字段不能是表内成员。
初始化方式多样:
-VALUE子句直接赋初值
-INITIALIZE清零或置空
-PERFORM循环赋值
访问方式包括:
- 下标:SALES-AMT(3)
- 索引(INDEXED BY):效率更高,支持SET IDX UP BY 1
检索提供两种机制:
-SEARCH:线性查找
-SEARCH ALL:二分查找(要求KEY已排序)
SEARCH ALL EMP-TABLE WHEN EMP-ID(IDX) = TARGET-ID MOVE EMP-NAME(IDX) TO RESULT.SEARCH ALL性能优异,但必须确保数据有序且搜索条件包含主键。
文件操作:批处理的灵魂
文件处理是COBOL的强项。先在FILE-CONTROL中映射物理文件:
SELECT CUSTOMER-FILE ASSIGN TO "CUST.DAT".再在DATA DIVISION中描述结构:
FD CUSTOMER-FILE. 01 CUST-REC. 05 CUST-ID PIC X(6). 05 CUST-NAME PIC X(20). 05 BALANCE PIC S9(6)V99.标准读写流程如下:
OPEN INPUT CUSTOMER-FILE. READ CUSTOMER-FILE AT END SET EOF TO TRUE. PERFORM UNTIL EOF PROCESS RECORD READ CUSTOMER-FILE NEXT RECORD AT END SET EOF TO TRUE END-PERFORM. CLOSE CUSTOMER-FILE.对于输出文件,则用OPEN OUTPUT和WRITE ... FROM。
整个过程强调稳健性,每一步都需显式控制。
子程序调用:模块化之道
通过CALL调用外部程序,参数双向传递:
CALL 'CALCTAX' USING INCOME, RATE, TAXOUT.被调程序需在LINKAGE SECTION声明接口:
DATA DIVISION. LINKAGE SECTION. 01 L-INCOME PIC 9(6). 01 L-RATE PIC V999. 01 L-TAX PIC 9(5). PROCEDURE DIVISION USING L-INCOME, L-RATE, L-TAX. COMPUTE L-TAX = L-INCOME * L-RATE. GOBACK.参数一一对应,修改直接影响原变量。这是典型的“引用传递”,也是实现功能解耦的重要手段。
实用语句速览
一些高频命令值得牢记:
-ACCEPT:获取系统时间、日期或输入
-DISPLAY:输出调试信息
-OPEN/CLOSE:管理文件资源
-READ/WRITE:记录级IO
-INITIALIZE:清空字段(设为0或空格)
-SET:调整索引或指针
-GO TO:跳转(慎用,破坏结构化)
它们构成了日常开发的“工具箱”。
COBOL或许不再时髦,但它教会我们的是一种工程思维:清晰的结构划分、严谨的数据定义、可控的执行流程。当你面对百万行级别的遗留系统时,那些看似繁琐的规定,恰恰成了抵御混乱的防火墙。
掌握它,不只是为了修老系统,更是为了理解——在一个强调敏捷与创新的时代,为何还有如此多的关键业务宁愿“慢一点”,也要“稳一点”。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考