文章

Delphi函数的提前宣告/预先宣告/函数递归呼叫

预先宣告
当我们需要使用一个识别符号,编译程序必须预先得知这个符号,并且必须
知道这个识别符号将会指向的参考地址。为了满足这个需求,我们必须在使
用任何识别符号之前先提供完整的定义。然而,有些情况下这个要求很难达
成。如果程序 A 呼叫程序 B,而程序 B 又呼叫程序 A,当我们开始撰写这
个程序代码的时候,我们等于是在呼叫一个编译程序还没看到的程序。
在这个情形下(还有很多情形也会有类似的情况发生),我们可以先宣告一个
函式或者程序,把完整的名称、参数都宣告好,但不用提供完整的程序代码。
要做到这一点,我们只要把完整的程序或函数名称宣告写好,最后加上一个
forward 关键词即可,例如:
procedure NewHello; forward;
在程序代码后段,我们再把完整的程序代码写好(这种写法,实作的程序代
码跟预先宣告必须位于同一个单元文件里面),这样一来,我们就可以在完
整的程序代码还没出现之前,就直接呼叫它了,以下就是这样的例子:

procedure DoubleHello; forward;
procedure NewHello;
begin
 if MessageDlg ('Do you want a double message?',
 TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbYes, TMsgDlgBtn.mbNo], 0) = mrYes then
 DoubleHello
 else
 ShowMessage ('Hello');
end;
procedure DoubleHello;
begin
 NewHello;
 NewHello;
end;

上面的程序片段中所用到的 MessageDlg,是 FMX 框架里所提供的一个简
单的方法,让我们可以透过对话框询问使用者进行确认(在 VCL 框架里面
也有类似的方法,也很容易使用),其参数是讯息、对话框的种类、我们要
显示给用户看到的按钮,最后的回传值则是用户点击的按钮种类。
这样的功能让我们可以写出互相呼叫的递归情形:DoubleHello 呼叫 Hello,
但 Hello 也可以呼叫DoubleHello。换句话说,如果用户一直点选 Yes 按钮,
讯息就会一直被显示,而且每次点选 Yes 的时候,确认对话框就会再多问两次。
在递归的程序代码里面,一定要有递归的终止条件,
以避免相互呼叫直到堆栈溢出(stack overflow)的情形发生。


函式在呼叫的时候,是使用堆栈来记录参数、回传值、局部变量等内存空
间的。如果一个函数持续呼叫自己,成为了无穷循环,堆栈所使用的记忆
空间(通常是预先定义好的固定大小)将会很快用完,使程序不正常停止,这
种错误情形,就是大家所熟知的堆栈溢出(stack overflow)。而最近几年,也
有个很热门的程序开发网站用了这个域名提供给程序人员一个问题交换讨
论的平台,这我想应该就不用介绍了:http://www.stackoverflow.com/

虽然在 Object Pascal 里面已经不常使用预先宣告,但有个类似的案例还是很
常见。当我们在一个单元文件的 interface 区段宣告程序或函式时,这个宣告
就已经被视为预先宣告了,虽然我们宣告的时候并没有用上 forward 这个关
键词。实际上我们本来就无法在 interface 区段撰写程序代码,同时我们则必
须要在 interface 区段有宣告函式的同一个单元文件里面,把该函式或程序进
行实作。

递归函数
关于之前我提到的递归,我们先来看一个比较特别的例子(透过两个函式互
相呼叫),然后再观察一个比较传统的递归案例,也就是函数调用自己。使
用递归也常用来当做另类的循环实作方法。
从传统的例子看起,假设我们要计算一个数字的多次方,而手边没有适当的
函数(其实这个函式在 Object Pascal 的 RTL 里面就有了)。那我们就只能从数
学上的定义来分析了,例如 2 的 3 次方,就是 2 乘自己乘三次,也就是 2x2x2.
所以实作这个函式的一个方法,可以是写一个 for 循环,执行三次(或者是几
次方,就乘几次),把乘法的计算结果再乘以要计算的基底数字:

function PowerL (Base, Exp: Integer): Integer;
var
 I: Integer;
筆記
103
begin
 Result := 1;
 for I := 1 to Exp do
 Result := Result * Base;
end;

另一个替代方案,则是直接乘以该函式的下一次方执行结果,直到下一次方
为 0,因为任何数字的 0 次方都是 1,所以我们可以把 0 次方当成是递归调
用的终止条件,把这个函式以递归方式实作:

function PowerR (Base, Exp: Integer): Integer;
var
 I: Integer;
begin
 if Exp = 0 then
 Result := 1
 else
 Result := Base * PowerR (Base, Exp - 1);
end;

这个程序的递归版本执行起来并没有比 for 循环的版本来的快,也没有比较
容易读懂。然而像是在分析程序结构时(例如树状结构),要处理的元素并不
是固定的,因此要用一个 for 循环来处理也几乎是不可能的,因此递归在这
种案例中,就显得格外有用了

原文来自:Delphi函数的提前宣告/预先宣告/函数递归呼叫,尊重自己,尊重每一个人;转发请注明来源!
0 0

发表评论