<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">

<!-- top -->
<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.com</a>
<li><a href="http://www.marcocantu.com/books">
    Marco's Delphi Books</a>
<li><a     href="http://www.marcocantu.com/epascal">
    Essential Pascal - Web Site</a>
<li><a href="default.htm">
    Essential Pascal - Local Index</a>
</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>begin</b>
  ShowMessage ('Hello world!');
<b>end</b>;

<b>function</b> Double (Value: Integer) : Integer;
<b>begin</b>
  Double := Value * 2;
<b>end</b>;

<i>// or, as an alternative</i>
<b>function</b> Double2 (Value: Integer) : Integer;
<b>begin</b>
  Result := Value * 2;
<b>end</b>;</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>procedure</b> TForm1.Button1Click (Sender: TObject);
<b>begin</b>
  Hello;
<b>end</b>;
 
<b>procedure</b> TForm1.Button2Click (Sender: TObject);
<b>var</b>
  X, Y: Integer;
<b>begin</b>
  X := Double (StrToInt (Edit1.Text));
  Y := Double (X);
  ShowMessage (IntToStr (Y));
<b>end</b>;</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>Double</span></i><span lang=EN-US> 函数时,你不需要知道该函数的具体实现方法。如果以后发现了更好的双倍数计算方法,你只需要改变函数的代码,而调用函数的代码不必改变(尽管代码执行速度可能会加快!)。<i>Hello</i>
过程也一样,你可以通过改变这个过程的代码,修改程序的输出,<i>Button2Click </i>方法会自动改变显示结果。下面是改变后的代码:</span></p>

<pre><b>procedure </b>Hello;
<b>begin</b>
  MessageDlg (<i>'Hello world!'</i>, mtInformation, [mbOK]);
<b>end</b>;
</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++中,用符号
&amp表示引用;在VB中,没有<i>ByVal</i> 标示的参数都为引用。</span></p>

<p class=MsoPlainText style='text-indent:21.25pt'>下面是利用引用传递参数的例子,引用参数用<i><span
lang=EN-US>var</span></i>关键字标示:</p>

<pre><b>procedure</b> DoubleTheValue (<b>var</b> Value: Integer);
<b>begin</b>
  Value := Value * 2;
<b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>在这种情况下,参数既把一个值传递给过程,又把新值返回给调用过程的代码。当你执行完以下代码时:</p>

<pre><b>var</b>
  X: Integer;
<b>begin</b>
  X := 10;
  DoubleTheValue (X);</pre>

<p class=MsoPlainText style='text-indent:21.25pt'><span lang=EN-US>x变量的值变成了20,因为过程通过引用访问了<i>X</i>的原始存储单元,由此改变了<i>X</i>的初始值。</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>function</b> DoubleTheValue (<b>const</b> Value: Integer): Integer;
<b>begin</b>
  Value := Value * 2;      <i>// compiler error</i>
  Result := Value;
<b>end</b>;</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>function</b> Sum (const A: array of Integer): Integer;
<b>var</b>
  I: Integer;
<b>begin</b>
  Result := 0;
  <b>for</b> I := Low(A) <b>to</b> High(A) <b>do</b>
    Result := Result + A[I];
<b>end</b>;</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>Slice</span></i><span lang=EN-US> 函数,只传递数组的一部分元素(传递元素个数由<i>Slice</i> 函数的第二个参数指定)。下面是传递整个数组参数的例子:</span></p>

<pre><b>var</b>
  List: <b>array</b> [1..10] <b>of</b> Integer;
  X, I: Integer;
<b>begin</b>
  <i>// initialize the array</i>
  <b>for</b> I := Low (List) <b>to </b>High (List) <b>do</b>
    List [I] := I * 2;
  <i>// call</i>
  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 Integer</i>指令定义变量,而不仅仅是传递参数。</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>TVarRec</span></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>Variant</span></i><span lang=EN-US> 类型使用的<i>TVarData
</i>记录类型相混淆。这两种类型用途不同,而且互不兼容。甚至可容纳的数据类型也不同,因为<i>TVarRec</i> 支持Delphi 数据类型,而TVarData
支持OLE 数据类型。</span></p>


<p class=MsoPlainText style='text-indent:21.25pt'><i><span lang=EN-US>TVarRec</span></i><span
lang=EN-US> 记录类型结构如下:</span></p>

<pre><b>type</b>
  TVarRec = <b>record</b>
    <b>case</b> Byte <b>of</b>
      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>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>每种记录都有一个<i><span lang=EN-US>VType</span></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 const</b>): Extended;
<b>var</b>
  I: Integer;
<b>begin</b>
  Result := 0;
  <b>for</b> I := Low(Args) <b>to</b> High (Args) <b>do</b>
    <b>case</b> Args [I].VType <b>of</b>
      vtInteger: Result :=
        Result + Args [I].VInteger;
      vtBoolean:
        <b>if</b> Args [I].VBoolean <b>then</b>
          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>end</b>; <i>// case</i>
<b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>我已在例<span lang=EN-US>OpenArr中加了这段代码,该例在按下设定的按钮后调用<i>SumAll</i>
函数。</span></p>

<pre><b>procedure </b>TForm1.Button4Click(Sender: TObject);
<b>var</b>
  X: Extended;
  Y: Integer;
<b>begin</b>
  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>end</b>;</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>begin</b>
  <i>{here goes your code}</i>
<b>end</b>;</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>forward</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>在后面应该补上该过程的完整代码,不过该过程代码的位置不影响对它的调用。下面的例子没什么实际意义,看过后你会对上述概念有所认识:</p>

<pre><b>procedure </b>DoubleHello; <b>forward</b>;

<b>procedure </b>Hello;
<b>begin</b>
  <b>if</b> MessageDlg (<i>'Do you want a double message?'</i>,
      mtConfirmation, [mbYes, mbNo], 0) = mrYes <b>then</b>
    DoubleHello
  <b>else</b>
    ShowMessage (<i>'Hello'</i>);
<b>end</b>;

<b>procedure </b>DoubleHello;
<b>begin</b>
  Hello;
  Hello;
<b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>上述方法可用来写递归调用:即<i><span
lang=EN-US>DoubleHello</span></i><span lang=EN-US> 调用Hello,而Hello也可能调用<i>DoubleHello</i>。当然,必须设置条件终止这个递归,避免栈的溢出。上面的代码可以在例DoubleH
中找到,只是稍有改动。</span></p>


<p class=MsoPlainText style='text-indent:21.25pt'>尽管<span lang=EN-US> forward 过程声明在Delphi中不常见,但是有一个类似的情况却经常出现。当你在一个单元(关于单元的更多内容见下一章)的interface
部分声明一个过程或一个函数时,它被认为是一个forward声明,即使没有forward关键字也一样。实际上你不可能把整个例程的代码放在interface 部分,不过你必须在同一单元中提供所声明例程的实现。</span></p>


<p class=MsoPlainText style='text-indent:21.25pt'>类内部的方法声明也同样是<span lang=EN-US>forward声明,当你给窗体或其组件添加事件时,
Delphi会自动产生相应的代码。在TForm 类中声明的事件是forward 声明,事件代码放在单元的实现部分。下面摘录的源代码中有一个Button1Click
方法声明:</span></p>

<pre><b>type</b>
  TForm1 = <b>class</b>(TForm)
    ListBox1: TListBox;
    Button1: TButton;
    <b>procedure</b> Button1Click(Sender: TObject);
  <b>end</b>;</pre>

<FONT FACE="楷体_GB2312"  SIZE=6 color=BROWN>过程类型</FONT>

<p class=MsoPlainText style='text-indent:21.25pt'><span lang=EN-US>Object
Pascal 的另一个独特功能是可定义过程类型。过程类型属于语言的高级功能,Delphi 程序员不会经常用到它。因为后面章节要讨论相关的内容(尤其是“方法指针”
Delphi用得特别多),这里不妨先了解一下。如果你是初学者,可以先跳过这部分,当学到一定程度后再回过头阅读这部分。</span></p>

<p class=MsoPlainText style='text-indent:21.25pt'><span lang=EN-US>Pascal 中的过程类型与C语言中的函数指针相似。过程类型的声明只需要参数列表;如果是函数,再加个返回值。例如声明一个过程类型,该类型带一个通过引用传递的整型参数:</span></p>

<pre><b>type</b>
  IntProc = <b>procedure</b> (<b>var </b>Num: Integer);</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>这个过程类型与任何参数完全相同的例程兼容(或用<span
lang=EN-US>C语言行话来说,具有相同的函数特征)。下面是一个兼容例程:</span></p>

<pre><b>procedure </b>DoubleTheValue (<b>var </b>Value: Integer);
<b>begin</b>
  Value := Value * 2;
<b>end</b>;</pre>

<p class="note"><b>注意</b>:在<span lang=EN-US>16位Delphi中,如果要将例程用作过程类型的实际值,必须用far指令声明该例程。</span></p>


<p class=MsoPlainText style='text-indent:21.25pt'>过程类型能用于两种不同的目的:声明过程类型的变量;或者把过程类型(也就是函数指针)作为参数传递给另一例程。利用上面给定的类型和过程声明,你可以写出下面的代码:</p>

<pre><b>var</b>
  IP: IntProc;
  X: Integer;
<b>begin</b>
  IP := DoubleTheValue;
  X := 5;
  IP (X);
<b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>这段代码与下列代码等效:</p>

<pre><b>var</b>
  X: Integer;
<b>begin</b>
  X := 5;
  DoubleTheValue (X);
<b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>上面第一段代码明显要复杂一些,那么我们为什么要用它呢?因为在某些情况下,调用什么样的函数需要在实际中决定,此时程序类型就很有用。这里不可能建立一个复杂的例子来说明这个问题,不过可以探究一下简单点的例子,该例名为<span
lang=EN-US>ProcType。该例比前面所举的例子都复杂,更接近实际应用。</span></p>

<p class=MsoPlainText style='text-indent:21.25pt'>如图<span lang=EN-US>6.3所示,新建一个工程,在上面放两个radio按钮和一个push按钮。例中有两个过程,一个过程使参数的值加倍,与前面的<i>DoubleTheValue</i>过程相似;另一个过程使参数的值变成三倍,因此命名为TripleTheValue</span></p>

<p class="slug">图 6.3: 例 ProcType 窗体

<p><img src="epf0603.gif" width=305 height=179 border=0 alt="">

<pre><b>procedure</b> TripleTheValue (<b>var </b>Value: Integer);
<b>begin</b>
  Value := Value * 3;
  ShowMessage (<i>'Value tripled: '</i> + IntToStr (Value));
<b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>两个过程都有结果显示,让我们知道他们已被调用。这是一个简单的程序调试技巧,你可以用它来检测某一代码段是否或何时被执行,而不用在代码中加断点。</p>


<p class=MsoPlainText style='text-indent:21.25pt'>当用户按<span lang=EN-US>Apply 按钮,程序会根据radio按钮状态选择执行的过程。实际上,当窗体中有两个radio按钮时,你只能选择一个,因此你只需要在Apply
按钮的OnClick 事件中添加代码检测radio按钮的值,就能实现程序要求。不过为了演示过程类型的使用,我舍近求远选择了麻烦但有趣的方法:只要用户选中其中一个radio按钮,按钮对应的过程就会存入过程变量:</span></p>

<pre><b>procedure </b>TForm1.DoubleRadioButtonClick(Sender: TObject);
<b>begin</b>
  IP := DoubleTheValue;
<b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>当用户按<span lang=EN-US>Apply 按钮,程序就执行过程变量保存的过程:</span></p>

<pre><b>procedure </b>TForm1.ApplyButtonClick(Sender: TObject);
<b>begin</b>
  IP (X);
<b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>为了使三个不同的函数能访问<span
lang=EN-US>IP和 X变量,需要使变量在整个窗体单元中可见,因此不能声明为局部变量(在一个方法中声明)。一个解决办法是,把这些变量放在窗体声明中:</span></p>

<pre><b>type</b>
  TForm1 = <b>class</b>(TForm)
    ...
  <b>private</b>
    <i>{ Private declarations }</i>
    IP: IntProc;
    X: Integer;
  <b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>学完下一章,你会更清楚地了解这段代码的意思,目前只要能知道怎样添加过程类型定义、怎样修改相应的代码就行了。为了用适当的值初始化上面代码中的两个变量,你可以调用窗体的<span
lang=EN-US>OnCreate 事件(激活窗体后,在Object Inspector中选择这一事件,或者双击窗体)。此外最好仔细看一看上例完整的源代码。</span></p>


<p class="note">在第九章的<span lang=EN-US>
Windows 回调函数一节,你能看到使用过程类型的实例</span></p>

<FONT FACE="楷体_GB2312"  SIZE=6 color=BROWN>函数重载</FONT>

<p class=MsoPlainText style='text-indent:21.25pt'>重载的思想很简单:编译器允许你用同一名字定义多个函数或过程,只要它们所带的参数不同。实际上,编译器是通过检测参数来确定需要调用的例程。</p>


<p class=MsoPlainText style='text-indent:21.25pt'>下面是从<span lang=EN-US>VCL的数学单元(Math
Unit)中摘录的一系列函数:</span></p>

<pre><b>function</b> Min (A,B: Integer): Integer; <b>overload</b>;
<b>function</b> Min (A,B: Int64): Int64; <b>overload</b>;
<b>function</b> Min (A,B: Single): Single; <b>overload</b>;
<b>function</b> Min (A,B: Double): Double; <b>overload</b>;
<b>function</b> Min (A,B: Extended): Extended; <b>overload</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>当调用方式为<span lang=EN-US>Min
(10, 20)时,编译器很容易就能判定你调用的是上列第一个函数,因此返回值也是个整数。</span></p>


<p class=MsoPlainText style='text-indent:21.25pt'>声明重载函数有两条原则:</p>

<ul>
<li>每个例程声明后面必须添加overload 关键字。
<li>例程间的参数个数或(和)参数类型必须不同,返回值不能用于区分各例程。
</ul>


<p class=MsoPlainText style='text-indent:21.25pt'>下面是<span lang=EN-US>ShowMsg 过程的三个重载过程。我已把它们添加到例OverDef
中(一个说明重载和确省参数的应用程序):</span></p>

<pre><b>procedure</b> ShowMsg (str: string); <b>overload</b>;
<b>begin</b>
  MessageDlg (str, mtInformation, [mbOK], 0);
<b>end</b>;

<b>procedure</b> ShowMsg (FormatStr: string;
  Params: <b>array of const</b>); <b>overload</b>;
<b>begin</b>
  MessageDlg (Format (FormatStr, Params),
    mtInformation, [mbOK], 0);
<b>end</b>;

<b>procedure</b> ShowMsg (I: Integer; Str: string); <b>overload</b>;
<b>begin</b>
  ShowMsg (IntToStr (I) + <i>' '</i> + Str);
<b>e</b>nd;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>三个过程分别用三种不同的方法格式化字符串,然后在信息框中显示字符串。下面是三个例程的调用:</p>

<pre>ShowMsg (<i>'Hello'</i>);
ShowMsg (<i>'Total = %d.'</i>, [100]);
ShowMsg (10, <i>'MBytes'</i>);</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>令我惊喜的是<span lang=EN-US>Delphi的代码参数技术与重载过程及函数结合得非常好。当你在例程名后面键入左圆括号时,窗口中会显示所有可用例程的参数列表,当你输入参数时,Delphi会根据所输入参数的类型过滤参数列表。从图6.4你可看到,当开始输入一个常量字符串时,Delphi只显示第一个参数为字符串的两个ShowMsg例程参数列表,滤掉了第一个参数为整数的例程。</span></p>

<p class="slug">图 6.4: 窗口中代码参数提示条显示的重载例程参数    
<p><img src="epf0604.gif" width=544 height=376 border=0 alt="">

<p class=MsoPlainText style='text-indent:21.25pt'>重载例程必须用<span lang=EN-US>overload关键字明确标示,你不能在同一单元中重载没有overload标示的例程,否则会出现错误信息:
&quot;Previous declaration of '&lt;name&gt;' was not marked with the 'overload'
directive.&quot;。不过你可以重载在其他单元中声明的例程,这是为了与以前的Delphi版本兼容,以前的Delphi版本允许不同的单元重用相同的例程名。无论如何,这是例程重载的特殊情况不是其特殊功能,而且不小心会出现问题。</span></p>

<p class=MsoPlainText style='text-indent:21.25pt'>例如在一个单元中添加以下代码:</p>

<pre><b>procedure</b> MessageDlg (str: string); <b>overload</b>;
<b>begin</b>
  Dialogs.MessageDlg (str, mtInformation, [mbOK], 0);
<b>end;</b></pre>

<p class=MsoPlainText style='text-indent:21.25pt'>这段代码并没有真正重载原始的<span
lang=EN-US>MessageDlg 例程,实际上如果键入:</span></p>

<pre>MessageDlg (<i>'Hello'</i>);</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>你将得到一个有意思的错误消息,告诉你缺少参数。调用本地例程而不是<span
lang=EN-US>VCL的唯一途径是明确标示例程所在单元,这有悖于例程重载的思想:</span></p>

<pre>OverDefF.MessageDlg (<i>'Hello'</i>);</pre>

<FONT FACE="楷体_GB2312"  SIZE=6 color=BROWN>确省参数</FONT>

<p class=MsoPlainText style='text-indent:21.25pt'><span lang=EN-US>Delphi 4 中添加了一个新功能,即允许你给函数的参数设定确省值,这样调用函数时该参数可以加上,也可以省略。下例把应用程序全程对象的MessageBox
方法重新包装了一下,用PChar 替代字符串,并设定两个确省值:</span></p>

<pre><b>procedure</b> MessBox (Msg: string;
  Caption: string = 'Warning';
  Flags: LongInt = mb_OK <b>or</b> mb_IconHand);
<b>begin</b>
  Application.MessageBox (PChar (Msg),
    PChar (Caption), Flags);
<b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>使用这一定义,你就可以用下面任一种方式调用过程:</p>

<pre>MessBox (<i>'Something wrong here!'</i>);
MessBox (<i>'Something wrong here!'</i>, <i>'Attention'</i>);
MessBox (<i>'Hello'</i>, <i>'Message'</i>, mb_OK);</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>从图6.5中可以看到,Delphi的
代码参数提示条会用不同的风格显示确省值参数,这样你就很容易确定哪个参数是可以省略的。</p>

<p class="slug">图 6.5: Delphi代码参数提示条用方括号标示确省值参数,调用时可以省略该参数
<p><img src="epf0605.gif" width=650 height=376 border=0 alt="">

<p class=MsoPlainText style='text-indent:21.25pt'>注意一点,<span lang=EN-US>Delphi 不产生任何支持确省参数的特殊代码,也不创建例程的多份拷贝,缺省参数是由编译器在编译时添加到调用例程的代码中。</span></p>

<p class=MsoPlainText style='text-indent:21.25pt'>使用确省参数有一重要限定:你不能“跳过”参数,如省略第二个参数后,不能把第三个参数传给函数:</p>

<pre>MessBox (<i>'Hello'</i>, mb_OK); <i>// error</i>  </pre>

<p class=MsoPlainText style='text-indent:21.25pt'>确省参数使用主要规则:调用时你只能从最后一个参数开始进行省略,换句话说,如果你要省略一个参数,你必须省略它后面所有的参数。</p>

<p class=MsoPlainText style='text-indent:21.25pt'>确省参数的使用规则还包括:</p>

<ul>
<li>带确省值的参数必须放在参数表的最后面。
<li>确省值必须是常量。显然,这限制了确省参数的数据类型,例如动态数组和界面类型的确省参数值只能是 nil;至于记录类型,则根本不能用作确省参数。
<li>确省参数必须通过值参或常参传递。引用参数 var不能有缺省值。
</ul>

<p class=MsoPlainText style='text-indent:21.25pt'>如果同时使用确省参数和重载可能会出现问题,因为这两种功能可能发生冲突。例如把以前<span
lang=EN-US>ShowMsg 过程改成:</span></p>

<pre><b>procedure </b>ShowMsg (Str: string; I: Integer = 0); <b>overload</b>;
<b>begin</b>
  MessageDlg (Str + <i>': '</i> + IntToStr (I),
    mtInformation, [mbOK], 0);
<b>end</b>;</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>编译时编译器不会提出警告,因为这是合法的定义。</p>

<p class=MsoPlainText style='text-indent:21.25pt'>然而编译调用语句:</p>

<pre>ShowMsg ('Hello');</pre>

<p class=MsoPlainText style='text-indent:21.25pt'>编译器会显示 <i><span
lang=EN-US>Ambiguous overloaded call to 'ShowMsg'</span></i><span lang=EN-US>.(<i>
不明确重载调用ShowMsg</i>)。注意,这条错误信息指向新定义的重载例程代码行之前。实际上,用一个字符串参数无法调用ShowMsg 过程,因为编译器搞不清楚你是要调用只带字符串参数的ShowMsg
过程,还是带字符串及整型确省参数的过程。遇到这种问题时,编译器不得不停下来,要求你明确自己的意图。</span></p>


<FONT FACE="楷体_GB2312"  SIZE=6 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'>下面几章将从字符串开始详细讲解<span
lang=EN-US>Pascal的一些编程元素。</span></p>

<h3><a href="ch07str.htm">下一章: 字符串操作</a></h3>

<p class="copyr">&copy; Copyright Marco Cant&ugrave;, Wintech Italia Srl 1995-2000</p>
</body>

</html>

Pascal 过程与函数相关推荐

  1. pascal过程与函数

    Turbo  Pascal过程与函数调用 下面是Turbo  Pascal的标准过程. Abs 语法       Function Abs (r:Real):Real; Function Abs (r ...

  2. 浅谈Delphi过程与函数02 - 零基础入门学习Delphi21

    浅谈Delphi过程与函数02 让编程改变世界 Change the world by program 传值调用 过程和函数的形参与实参之间既可以通过传值的方式传递,也可以通过传地址的方式传递. 例子 ...

  3. Oracle编程入门经典 第11章 过程、函数和程序包

    目录 11.1          优势和利益... 1 11.2          过程... 1 11.2.1       语法... 2 11.2.2       建立或者替换... 2 11.2 ...

  4. ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!)

    原文:ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!) ORACLE PL/SQL编程之六: 把过程与函数说透(穷追猛打,把根儿都拔起!)   继上篇:ORACLE P ...

  5. oracle实验使用游标,Oracle数据库实验-PLSQL游标、过程、函数、包的使用

    Oracle数据库基础 实验5 PL/SQL游标.过程.函数.包的使用 [实验学时] 2学时 [实验目的] 1.了解PL/SQL语言中显式游标和隐式游标的概念和属性. 2.了解显式游标和隐式游标的差异 ...

  6. python是面向过程的吗_Python开发是面向过程、函数还是对象?

    Python虽然是解释型语言,但从设计之初就已经是一门面向对象的语言,对于Python来说一切皆为对象.正因为如此,在Python中创建一个类和对象是很容易的,当然如果习惯面向过程或者函数的写法也是可 ...

  7. 不是有效的函数或过程名_过程和函数

    VBA代码有两种组织形式,一种是过程,另一种就是函数.其实过程和函数有很多相同之处,除了使用的关键字不同之外,还有不同的是: 函数有返回值,过程没有. 函数可以在Access窗体,查询中像一般的Acc ...

  8. oracle dplsql.bsq,Oracle PLSQL语言初级教程之过程和函数

    Oracle PL/SQL语言初级教程之过程和函数 过程和函数 过程和函数都以编译后的形式存放在数据库中,函数可以没有参数也可以有多个参数并有一个返回值.过程有零个或多个参数,没有返回值.函数和过程都 ...

  9. oracle 函数 实现重载_Oracle 过程、函数、包、重载、自主事务

    子程序包的优点有如下: 模块化:通过子程序,可以将程序分解为可管理的,明确的逻辑模块. 可重用性:子程序在创建并执行后,就可以在任意数目的应用程序中使用. 可维持性:子程序可以简化维护操作,因为如果一 ...

最新文章

  1. 操作系统-命令解释程序(实验一)
  2. android 中intent跳转是灰色的,没有效果,显示intent = null
  3. Oracle 常用命令举例
  4. 《构建之法》 读书笔记(4)
  5. 2018年易语言FPS骨骼方框透视辅助开发
  6. Topic 9. SCI 文章第二张表—单因素回归分析表
  7. Xposed 企业微信 Hook 微信
  8. bailian2705
  9. vue跑项目npm run dev 时报错cannot GET /;node-sass安装失败问题
  10. 关于项目上线(新浪云)
  11. 网页炫酷特效拿来即可用(看板娘鼠标点击炫酷登录页面樱花特效生日祝福彩虹屁)
  12. 成立126年的通用电气GE投资区块链网络安全初创公司
  13. 《腾云-云计算和大数据时代网络技术揭秘》的收获与分享
  14. 为什么重写equals方法必须要重写hashCode方法
  15. 网络表示学习Network Representation Learning/Embedding
  16. c语言计算2的n次方代码,计算2的N次方........有什么错吗?
  17. 想要将PDF文件进行编辑该使用什么PDF编辑器
  18. Python英文语音识别终极手册
  19. 沁园春.雪输出诗句代码
  20. HNU OJ10320 穿越火线 简单模拟

热门文章

  1. 什么是根域名服务器?
  2. CVPR21小样本检测:蒸馏上下文助力小样本检测(代码已开源)
  3. 什么是寄存器(Register)?收藏
  4. 关于解决主机和虚拟机ping不通的问题
  5. 【GAN:Dense block:VIIF】
  6. mac os之监听触摸板(捏合、旋转、三指)
  7. 前端开发的辛酸史(2)之织梦教程
  8. FIR 线性相位系统 最小相位系统 滤波器延迟
  9. 欧拉角和四元数之间是如何转换的?
  10. rtp server