<html>
<head>
<link rel="stylesheet" type="text/css" href="pascal.css">
<title>Pascal精要title>
head>
<body TEXT="#000000" BGCOLOR="#FFFFFF" LINK="#551A8B" VLINK="#663366" ALINK="#FF0000">
<table border="0" width="100%" cellpadding="10">
<tr><td align="center">
<img src="books.gif" alt="Logo" width=249 height=100 border=0>td>
<td>
<ul><li><a href="http://www.marcocantu.com">
www.marcocantu.coma>
<li><a href="http://www.marcocantu.com/books">
Marco's Delphi Booksa>
<li><a href="http://www.marcocantu.com/epascal">
Essential Pascal - Web Sitea>
<li><a href="default.htm">
Essential Pascal - Local Indexa>
ul>td>tr>
<tr bgcolor="#FFC891"><td align="center">
<B><FONT FACE="楷体_GB2312" LANG="ZH-CN" SIZE=7>PASCAL<BR>精要
td>FONT>B>
<td align="center">
<B><FONT FACE="楷体_GB2312" LANG="ZH-CN" SIZE=6>第六章<BR>过程与函数FONT>B>
td>tr>
table>
<p class=MsoPlainText style='text-indent:21.25pt'>例程<span lang=EN-US>(routine)是Pascal
的一个重要概念,例程由一系列语句组成,例程名是唯一的,通过例程名你可以多次调用它,这样程序中只需要一个例程就够了,由此避免了代码多次重复,而且代码也容易修改维护。从这个角度看,你可以认为例程是一种基本的代码封装机制。介绍完Pascal
例程的语法后,我会回过头来举例说明这个问题。span>p>
<FONT FACE="楷体_GB2312" SIZE=6 color=BROWN>Pascal 过程与函数FONT>
<p class=MsoPlainText style='text-indent:21.25pt'><span lang=EN-US>Pascal中的例程有两种形式:过程和函数。理论上说,过程是你要求计算机执行的操作,函数是能返回值的计算。两者突出的不同点在于:函数能返回计算结果,即有一个返回值,而过程没有。两种类型的例程都可以带多个给定类型的参数。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>不过实际上函数和过程差别不大,因为你可以调用函数完成一系列操作,跳过其返回值<span
lang=EN-US>(用可选的出错代码或类似的东西代替返回值);也可以通过过程的参数传递计算结果(这种参数称为引用,下一部分会讲到)。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>下例定义了一个过程、两个函数,两个函数的语法略有不同,结果是完全相同的。p>
<pre><b>procedure b>Hello;
<b>beginb>
ShowMessage ('Hello world!');
<b>endb>;
<b>functionb> Double (Value: Integer) : Integer;
<b>beginb>
Double := Value * 2;
<b>endb>;
<i>// or, as an alternativei>
<b>functionb> Double2 (Value: Integer) : Integer;
<b>beginb>
Result := Value * 2;
<b>endb>;pre>
<p class=MsoPlainText style='text-indent:21.25pt'>流行的做法是用<span lang=EN-US>Result
给函数赋返回值,而不是用函数名,我认为这样的代码更易读。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>一旦定义了这些例程,你就可以多次调用,其中调用过程可执行操作;调用函数能计算返回值。如下:span>p>
<pre>
<b>procedureb> TForm1.Button1Click (Sender: TObject);
<b>beginb>
Hello;
<b>endb>;
<b>procedureb> TForm1.Button2Click (Sender: TObject);
<b>varb>
X, Y: Integer;
<b>beginb>
X := Double (StrToInt (Edit1.Text));
Y := Double (X);
ShowMessage (IntToStr (Y));
<b>endb>;pre>
<p class="note"><b>注意b>:现在不必考虑上面两个过程的语法,实际上它们是方法。只要把两个按钮<span
lang=EN-US>(button)放到一个Delphi 窗体上,在设计阶段单击它们,Delphi IDE将产生合适的支持代码,你只需要填上begin 和end
之间的那几行代码就行。编译上面的代码,需要你在窗体中加一个Edit控件。p>
<p class=MsoPlainText style='text-indent:21.25pt'>现在回到我前面提到过的代码封装概念。当你调用<i><span
lang=EN-US>Doublespan>i><span lang=EN-US> 函数时,你不需要知道该函数的具体实现方法。如果以后发现了更好的双倍数计算方法,你只需要改变函数的代码,而调用函数的代码不必改变(尽管代码执行速度可能会加快!)。<i>Helloi>
过程也一样,你可以通过改变这个过程的代码,修改程序的输出,<i>Button2Click i>方法会自动改变显示结果。下面是改变后的代码:span>p>
<pre><b>procedure b>Hello;
<b>beginb>
MessageDlg (<i>'Hello world!'i>, mtInformation, [mbOK]);
<b>endb>;
pre>
<p class="note"><b>提示b>:当调用一个现有的<span lang=EN-US>Delphi
函数、过程或任何VCL方法时,你应该记住参数的个数及其数据类型。不过,只要键入函数或过程名及左括号,Delphi 编辑器中会出现即时提示条,列出函数或过程的参数表供参考。这一特性被称为代码参数(<i>Code
Parameters)i> ,是代码识别技术的一部分。span>p>
<FONT FACE="楷体_GB2312" SIZE=6 color=BROWN>引用参数FONT>
<p class=MsoPlainText style='text-indent:21.25pt'><span lang=EN-US>Pascal 例程的传递参数可以是值参也可以是引用参数。值参传递是缺省的参数传递方式:即将值参的拷贝压入栈中,例程使用、操纵的是栈中的拷贝值,不是原始值。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>当通过引用传递参数时,没有按正常方式把参数值的拷贝压栈(避免拷贝值压栈一般能加快程序执行速度),而是直接引用参数原始值,例程中的代码也同样访问原始值,这样就能在过程或函数中改变参数的值。引用参数用关键字<i><span
lang=EN-US>var span>i>标示。p>
<p class="note">参数引用技术在大多数编程语言中都有,C语言中虽没有,但C++中引入了该技术。在C++中,用符号
&表示引用;在VB中,没有<i>ByVali> 标示的参数都为引用。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>下面是利用引用传递参数的例子,引用参数用<i><span
lang=EN-US>varspan>i>关键字标示:p>
<pre><b>procedureb> DoubleTheValue (<b>varb> Value: Integer);
<b>beginb>
Value := Value * 2;
<b>endb>;pre>
<p class=MsoPlainText style='text-indent:21.25pt'>在这种情况下,参数既把一个值传递给过程,又把新值返回给调用过程的代码。当你执行完以下代码时:p>
<pre><b>varb>
X: Integer;
<b>beginb>
X := 10;
DoubleTheValue (X);pre>
<p class=MsoPlainText style='text-indent:21.25pt'><span lang=EN-US>x变量的值变成了20,因为过程通过引用访问了<i>Xi>的原始存储单元,由此改变了<i>Xi>的初始值。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>通过引用传递参数对有序类型、传统字符串类型及大型记录类型才有意义。实际上Delphi总是通过值来传递对象,因为Delphi对象本身就是引用。因此通过引用传递对象就没什么意义(除了极特殊的情况),因为这样相当于传递一个引用到另一个引用。p>
<p class=MsoPlainText style='text-indent:21.25pt'><span lang=EN-US>Delphi 长字符串的情况略有不同,长字符串看起来象引用,但是如果你改变了该字符串的串变量,那么这个串在更新前将被拷贝下来。作为值参被传递的长字符串只在内存使用和操作速度方面才象引用,但是如果你改变了字符串的值,初始值将不受影响。相反,如果通过引用传递长字符串,那么串的初始值就可以改变。span>p>
<p class="note">Delphi 3增加了一种新的参数:out。out参数没有初始值,只是用来返回一个值。out参数应只用于COM过程和函数,一般情况下最好使用更有效的var参数。除了没有初始值这一点之外,out参数与var参数相同。span>p>
<FONT FACE="楷体_GB2312" SIZE=6 color=BROWN>常量参数FONT>
<p class=MsoPlainText style='text-indent:21.25pt'>除了引用参数外,还有一种参数叫常量参数。由于不允许在例程中给常量参数赋新值,因此编译器能优化常参的传递过程。编译器会选用一种与引用参数相似的方法编译常参(<span lang=EN-US>C++术语中的常量引用),但是从表面上看常参又与值参相似,因为常参初始值不受例程的影响。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>事实上,如果编译下面有点可笑的代码,<span
lang=EN-US>Delphi将出现错误:span>p>
<pre><b>functionb> DoubleTheValue (<b>constb> Value: Integer): Integer;
<b>beginb>
Value := Value * 2; <i>// compiler errori>
Result := Value;
<b>endb>;pre>
<FONT FACE="楷体_GB2312" SIZE=6 color=BROWN>开放数组参数FONT>
<p class=MsoPlainText style='text-indent:21.25pt'>与<span lang=EN-US>C语言不同,Pascal
函数及过程的参数个数是预定的。如果参数个数预先没有确定,则需要通过开放数组来实现参数传递。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>一个开放数组参数就是一个固定类型开放数组的元素。 也就是说,参数类型已定义,但是数组中的元素个数是未知数。见下例:p>
<pre><b>functionb> Sum (const A: array of Integer): Integer;
<b>varb>
I: Integer;
<b>beginb>
Result := 0;
<b>forb> I := Low(A) <b>tob> High(A) <b>dob>
Result := Result + A[I];
<b>endb>;pre>
<p class=MsoPlainText style='text-indent:21.25pt'>上面通过<span lang=EN-US>High(A)获取数组的大小,注意其中函数返回值
Result的应用, Result用来存储临时值。你可通过一个整数表达式组成的数组来调用该函数:span>p>
<pre>X := Sum ([10, Y, 27*I]);pre>
<p class=MsoPlainText style='text-indent:21.25pt'>给定一个整型数组,数组大小任意,你可以直接把它传递给带开放数组参数的例程,此外你也可以通过<i><span
lang=EN-US>Slicespan>i><span lang=EN-US> 函数,只传递数组的一部分元素(传递元素个数由<i>Slicei> 函数的第二个参数指定)。下面是传递整个数组参数的例子:span>p>
<pre><b>varb>
List: <b>arrayb> [1..10] <b>ofb> Integer;
X, I: Integer;
<b>beginb>
<i>// initialize the arrayi>
<b>forb> I := Low (List) <b>to b>High (List) <b>dob>
List [I] := I * 2;
<i>// calli>
X := Sum (List);pre>
<p class=MsoPlainText style='text-indent:21.25pt'>如果你只传递数组的一部分,可使用<span
lang=EN-US>Slice 函数,如下:span>p>
<pre>X := Sum (Slice (List, 5));pre>
<p class=MsoPlainText style='text-indent:21.25pt'>例<span lang=EN-US>OpenArr中可见到包括上面的完整代码(见图6.1)。span>p>
<p class="slug">图 6.1: 单击 Partial Slice 按钮显示的结果
<p><img src="epf0601.gif" width=348 height=262 border=0 alt="">
<p class="note">在Delphi 4中,给定类型的开放数组与动态数组完全兼容(动态数组将在第8章中介绍)。动态数组的语法与开放数组相同,区别在于你可以用诸如<i>array
of Integeri>指令定义变量,而不仅仅是传递参数。span>p>
<FONT FACE="楷体_GB2312" SIZE=5 color=BROWN>类型变化的开放数组参数FONT>
<p class=MsoPlainText style='text-indent:21.25pt'>除了类型固定的开放数组外,<span
lang=EN-US>Delphi 还允许定义类型变化的甚至无类型的开放数组。这种特殊类型的数组元素可随意变化,能很方便地用作传递参数。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>技术上,array of const 类型的数组就能实现把不同类型、不同个数元素组成的数组一下子传递给例程。如下面<span
lang=EN-US>Format 函数的定义(第七章中你将看到怎样使用这个函数):span>p>
<pre>function Format (const Format: string;
const Args: array of const): string;pre>
<p class=MsoPlainText style='text-indent:21.25pt'>上面第二个参数是个开放数组,该数组元素可随意变化。如你可以按以下方式调用这个函数:p>
<pre>N := 20;
S := <i>'Total:'i>;
Label1.Caption := Format (<i>'Total: %d'i>, [N]);
Label2.Caption := Format (<i>'Int: %d, Float: %f'i>, [N, 12.4]);
Label3.Caption := Format (<i>'%s %d'i>, [S, N * 2]);pre>
<p class=MsoPlainText style='text-indent:21.25pt'>从上可见,传递的参数可以是常量值、变量值或一个表达式。声明这类函数很简单,但是怎样编写函数代码呢?怎样知道参数类型呢?对类型可变的开放数组,其数组元素与<i><span
lang=EN-US>TVarRecspan>i><span lang=EN-US> 类型元素兼容。span>p>
<p class="note"><b>注意b>:不要把<i><span lang=EN-US>TVarRec
span>i>记录类型和<i><span lang=EN-US>Variantspan>i><span lang=EN-US> 类型使用的<i>TVarData
i>记录类型相混淆。这两种类型用途不同,而且互不兼容。甚至可容纳的数据类型也不同,因为<i>TVarReci> 支持Delphi 数据类型,而TVarData
支持OLE 数据类型。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'><i><span lang=EN-US>TVarRecspan>i><span
lang=EN-US> 记录类型结构如下:span>p>
<pre><b>typeb>
TVarRec = <b>recordb>
<b>caseb> Byte <b>ofb>
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
vtInterface: (VInterface: Pointer);
<b>endb>;pre>
<p class=MsoPlainText style='text-indent:21.25pt'>每种记录都有一个<i><span lang=EN-US>VTypespan>i><span
lang=EN-US> 域,乍一看不容易发现,因为它与实际意义的整型类型数据(通常是一个引用或一个指针)放在一起,只被声明了一次。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>利用上面信息我们就可以写一个能操作不同类型数据的函数。下例的<i><span
lang=EN-US>SumAll span>i>函数,通过把字符串转成整数、字符转成相应的序号、<span lang=EN-US>True布尔值加一,计算不同类型数据的和。这段代码以一个case语句为基础,虽然不得不经常通过指针取值,但相当简单,:span>p>
<pre><b>function b>SumAll (<b>const b>Args: <b>array of constb>): Extended;
<b>varb>
I: Integer;
<b>beginb>
Result := 0;
<b>forb> I := Low(Args) <b>tob> High (Args) <b>dob>
<b>caseb> Args [I].VType <b>ofb>
vtInteger: Result :=
Result + Args [I].VInteger;
vtBoolean:
<b>ifb> Args [I].VBoolean <b>thenb>
Result := Result + 1;
vtChar:
Result := Result + Ord (Args [I].VChar);
vtExtended:
Result := Result + Args [I].VExtended^;
vtString, vtAnsiString:
Result := Result + StrToIntDef ((Args [I].VString^), 0);
vtWideChar:
Result := Result + Ord (Args [I].VWideChar);
vtCurrency:
Result := Result + Args [I].VCurrency^;
<b>endb>; <i>// casei>
<b>endb>;pre>
<p class=MsoPlainText style='text-indent:21.25pt'>我已在例<span lang=EN-US>OpenArr中加了这段代码,该例在按下设定的按钮后调用<i>SumAlli>
函数。span>p>
<pre><b>procedure b>TForm1.Button4Click(Sender: TObject);
<b>varb>
X: Extended;
Y: Integer;
<b>beginb>
Y := 10;
X := SumAll ([Y * Y, <i>'k'i>, True, 10.34, '99999']);
ShowMessage (Format (
<i>'SumAll ([Y*Y, ''k'', True, 10.34, ''99999'']) => %n'i>, [X]));
<b>endb>;pre>
<p class=MsoPlainText style='text-indent:21.25pt'>在图<span lang=EN-US>6.2中,你可以看到调用函数的输出和例OpenArr的窗体。span>p>
<p class="slug">图 6.2: 例OpenArr的窗体,当按Untype按钮出现的信息框
<p><img src="epf0602.gif" width=454 height=268 border=0 alt="">p>
<FONT FACE="楷体_GB2312" SIZE=6 color=BROWN>Delphi 调用协定FONT>
<p class=MsoPlainText style='text-indent:21.25pt'><span lang=EN-US>32位的Delphi 中增加了新的参数传递方法,称为fastcall:只要有可能,传递到CPU寄存器的参数能多达三个,使函数调用操作更快。这种快速调用协定(Delphi
3确省方式)可用register 关键字标示。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>问题是这种快速调用协定与<span lang=EN-US>Windows不兼容,Win32
API 函数必须声明使用stdcall 调用协定。这种协定是Win16 API使用的原始Pascal 调用协定和C语言使用的cdecl 调用协定的混合体。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>除非你要调用外部<span lang=EN-US>Windows函数或定义Windows
回调函数,否则你没有理由不用新增的快速调用协定。 在后面你会看到使用stdcall 协定的例子,在Delphi帮助文件的Calling conventions
主题下,你能找到有关Delphi调用协定的总结内容。span>p>
<FONT FACE="楷体_GB2312" SIZE=6 color=BROWN>什么是方法?FONT>
<p class=MsoPlainText style='text-indent:21.25pt'>如果你使用过<span lang=EN-US>Delphi
或读过Delphi 手册,大概已经听说过“方法”这个术语。方法是一种特殊的函数或过程,它与类这一数据类型相对应。在Delphi 中,每处理一个事件,都需要定义一个方法,该方法通常是个过程。不过一般“方法”是指与类相关的函数和过程。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>你已经在本章和前几章中看到了几个方法。下面是<span
lang=EN-US>Delphi 自动添加到窗体源代码中的一个空方法:span>p>
<pre><b>procedure b>TForm1.Button1Click(Sender: TObject);
<b>beginb>
<i>{here goes your code}i>
<b>endb>;pre>
<FONT FACE="楷体_GB2312" SIZE=6 color=BROWN>Forward 声明FONT>
<p class=MsoPlainText style='text-indent:21.25pt'>当使用一个标识符(任何类型)时,编译器必须已经知道该标识符指的是什么。为此,你通常需要在例程使用之前提供一个完整的声明。然而在某些情况下可能做不到这一点,例如过程<span
lang=EN-US>A调用过程B,而过程B又调用过程A,那么你写过程代码时,不得不调用编译器尚未看到其声明的例程。span>p>
<p class=MsoPlainText style='text-indent:21.25pt'>欲声明一个过程或函数,而且只给出它的名字和参数,不列出其实现代码,需要在句尾加<span
lang=EN-US>forward 关键字:span>p>
<pre><b>procedure b>Hello; <b>forwardb>;pre>
<p class=MsoPlainText style='text-indent:21.25pt'>在后面应该补上该过程的完整代码,不过该过程代码的位置不影响对它的调用。下面的例子没什么实际意义,看过后你会对上述概念有所认识:p>