在前面的章节中,我们已经学会了如何编写简单的 Pascal 程序。到此为止,虽然已经掌握了布尔表达式、流程控制结构等核心知识,但一个无法回避的事实是:目前能写的只是一个从 begin 开始、到 end 结束的“铁板一块”的大程序。如果有一段代码需要在五个不同的地方使用,我们就不得不把这段代码老老实实地复制粘贴五遍。(当然,如果仅仅是想把同一段代码连续执行五次,可以用 for 循环来解决。)
现在,这种局限将被彻底打破。本章将探讨过程(Procedures)和函数(Functions)的使用,它们统称为子程序(Subprograms)。我们将首先介绍子程序的基本概念,然后讲解如何在主程序中定义和放置子程序,接着探讨决定子程序调用范围的“作用域(Scope)”概念,最后讨论子程序的参数传递机制,并带你领略参数的一些高级用法。
认识子程序
正如前面章节所学,一个标准的 Pascal 程序结构如下:
program DoSomthing;
<声明部分>
begin
<程序语句>
end. { 程序 DoSomthing 结束 }
如前所述,对于小程序来说,这种结构勉强够用。但对于大型程序而言,尤其是当代码中存在大量重复片段时,这种单一线性结构就会显得捉襟见肘。解决之道何在?Pascal 允许创建子程序。顾名思义,子程序就像是一个嵌套在大程序内部的“微型程序”。以下是定义过程(子程序的两种类型之一)的标准结构:
procedure Whichever;
<声明部分>
begin
<过程语句>
end; { 过程 Whichever 结束 }
看起来是不是很眼熟?它与主程序格式最直观的区别仅在于两点:
- 使用了关键字
procedure;
- 结尾的
end 后面跟的是分号(;)而不是句号(.)。
后续我们还会提到其他一些细微差别。与主程序一样,子程序的执行也是从 begin 之后开始,直到遇到 end 时结束。
接下来的问题是:子程序应该放在程序的什么位置?
答案是:放在主程序的声明部分之后、主 begin 关键字之前。实际上,你可以把子程序看作是声明部分的最后一个环节(紧跟在所有的 label、const、type 和 var 声明之后)。有些读者可能会问:能不能在子程序内部再定义子程序?完全可以!就像在主程序中定义子程序一样,你甚至可以在子程序中嵌套定义子程序,理论上可以无限嵌套下去(当然,Turbo Pascal 编译器在实际编译时还是有其物理极限的)。
既然子程序已经就位,下一个问题自然是:如何使用它?
答案非常简单:直接通过名称调用即可。让我们把上面定义的过程嵌入到一个完整的程序中,并多次调用它来看看效果:
program Whatever;
const
Maximum = 10;
type
CRange = 0..Maximum;
var
Indx: CRange;
{ --- 子程序定义区 --- }
procedure Whichever;
const
Star = '*';
type
Days = (Mon, Tues, Wed, Thur, Fri, Sat, Sun);
var
Day: Days;
begin { 过程 Whichever 的主体 }
for Day := Mon to Fri do
Write(Star);
end; { 过程 Whichever 结束 }
{ --- 主程序主体 --- }
begin { 程序 Whatever 的主体 }
for Indx := 0 to Maximum do
begin
Whichever; { 调用子程序 }
WriteLn; { 换行 }
end;
end. { 程序 Whatever 结束 }
在这个示例中,我们在主程序和子程序中故意都包含了 const、type 和 var 声明。这样做的目的有两个:一是能清楚地看到子程序在代码中的确切位置;二是直观地感受到两者在结构上的高度相似性。
请仔细观察这段代码,试着推断一下它的运行结果。如果还不确定,请记住:Write 和 WriteLn 是内置过程(即系统自带,无需手动定义)。Write(Star) 会在屏幕上打印一个星号 *,而 WriteLn 则负责换行。
想好了吗?如果你猜出这个程序会打印 11 行,且每行包含 **5 个星号(*****)**,那么恭喜你,完全正确!每次调用 Whichever 过程时,它会打印 5 个星号;而主程序 Whatever 通过循环调用了 Whichever 共 11 次(从 0 到 10),并在每次调用后执行一次换行。