Object Pascal 过程与函数

过程与函数

过程与函数是实现一定功能的语句块,是程序中的特定功能单元。可以在程序的其他地方被调用,也可以进行递归调用。过程与函数的区别在于过程没有返回值,而函数有返回值。


1.过程与函数的定义

过程与函数的定义包括过程原型或函数原型、过程体或函数体的定义。过程定义的形式如下:

procedure ProcedureName(ParameterList); directives;
var
    LocalDeclarations;
begin
    statements
end;

ProcedureName 是过程名,是有效的标识符。ParameterList 为过程的参数列表,需要指明参数的个数和数据类型。Directives 是一些关于函数的指令字, 如果设置多个, 应该用分号隔开。LocalDeclarations 中定义了该函数中需要使用的一些临时变量,通常也称作本地变量。在Begin 与End 之间是过程调用时实现特定功能的一系列语句。ParameterList、Directives、LocalDeclarations 和Statements 都是可选部分。

函数的定义与过程非常类似,只是使用的保留字不同,而且多了一个返回值类型。具体形式如下:

function FunctionName(ParameterList): ReturnType; directives;
var
    LocalDeclarations;
begin
    statements
end;

 

可以将函数需要返回的数值赋值给变量Result。如果函数体中存在着一些由于判断而产生的分支语句时,就要在每一个分支中设置返回值。通常要根据函数的返回值来确定下一步的操作。注意,这里与Visual C 和Visual C++不一样,把一个值赋给Result,函数并不会结束。


2.参数

函数定义时参数列表中的参数称为形参,函数调用时参数列表中的参数称为实参。在定义的函数原型中,多个参数之间用分号隔开,同一类型的参数可以放在一起,以逗号隔开。函数调用的时候,在函数原型中,多个参数之间用逗号隔开。
一般来说,形参列表和实参列表完全匹配是指参数的个数一样,而且顺序排列的数据类型也完全一致。对于普通的函数,如果编译器发现实参的数据类型与形参的数据类型不匹配,会将实参的数据类型进行一次或多次的“提升”,比如把Integer 类型转换为Double 类型。可以为过程和函数的参数指定默认数值。指定默认数值的参数要放在参数列表的后部,而没有指定默认数值的参数要放在参数列表的前部。在函数调用的时候,可以为设置了默认值的参数指定一个新值。在函数体中,各语句使用的是指定的新值,如果没有指定新值,则使用默认值。同样,如果存在多个设置了默认值的参数,只有在前面的参数指定了新值后,后面的参数才可以指定新值。

下面的例子定义了一个OutputNum 函数,可以将一个浮点数按指定的精度输出。通过这个例子,读者可以体会函数中参数的使用: 

program Project1;
{$APPTYPE CONSOLE}

uses Sysutils; //为了使用函数Format

function OutputNum(number:double;n:integer = 5):Boolean;
var
    Str : string; //浮点数显示输出的内容

begin
    if n <= -1 then //小数点后的位数要大于或等于0
        begin
            Result:=False;
            Exit; //退出显示函数
        end
    else
        begin
            // 设置显示的格式
            Str := Format('%*.*f', [10, n, number]);
            Result := True ;
            Writeln(Str); //显示数据
        end;
end;

begin
    OutputNum(12.345); //n 默认为5
    OutputNum(123,3); //参数对数据类型进行升级
    //下面一句代码不正确,故屏蔽掉
    //OutputNum(123.456789,9.13); //参数对数据类型不能降级
    //可以根据函数的返回值确定下一步的操作
    if OutputNum(123.456789,-3) = False then
        Writeln('输出失败。') ;
    Readln;
end.

运行结果如下:

12.34500
123.000
输出失败。

 

这里有几点需要说明:

  • 为了使用函数Format,需要在Uses 语句中将Sysutils 单元包含进去。
  • 由于小数点后的位数不可以设置为负数,所以当出现负数时,OutputNum 函数返回False,并调用Exit 函数立刻退出OutputNum 函数。
  • 在语句OutputNum(123,3)中,首先将整型常数123 转换为浮点型常数,然后进行参数传递。

最常用的参数有3 种,分别为数值参数变量参数常量参数
数值参数在运行过程中只改变其形参的值,不改变其实参的值,即参数的值不能传递到过程的外面。试看下面的例程:

procedure Calculate(CalNo:Integer);
begin
    CalNo := CalNo*10;
end;

用以下例程调用Calculate 函数:

Calculate(Number);

Number 进入Calculate 函数后,会把Number 实参拷贝给形参CalNo,在此过程中CalNo 增大10倍,但并未传递出来,所以说Number 值并未改变。形参和实参占用不同的内存地址,在过程或函数被调用时,将实参的值复制到形参占用的内存中。因此,在跳出过程或函数后,形参和实参的数值是不同的,但实参的值并不发生变化。

如果想改变传入的参数值,就需要使用变量参数,即在被调用程序的参数表中的形参前加上保留字Var。例如:

procedure Calculate(var CalNo : Integer);

则CalNo 并不在内存中占据一个位置,而是指向实参Number。当一个实参被传递时,任何对形参所作的改变都会反映到实参中,这是因为两个参数指向同一个地址。将上一个例程中的形参CalNo前面加上Var,再以同样的程序调用它,则在第2 个编辑框中会显示计算的结果,把第1 个编辑框中的数值放大10 倍。这时形参CalNo 和实参Number 的值都是Nnmber 初始值的10 倍。

如果过程或函数执行时要求不改变形参的值,最有保证的办法是使用常量参数。在参数表的参数名称前加上保留字Const 就可以使一个形参成为常量参数。使用常量参数代替数值参数可以保护用户的参数,使用户在不想改变参数值时不会意外地将新的值赋给这个参数。下面的例子可以帮助读者加深理解:

 
program Project1;
{$APPTYPE CONSOLE}

type
    PInteger = ^Integer; //定义指针类型

procedure P1(var N:Integer); //引用参数传递
begin
    N:=N+1 ;
end;

procedure P2(N:Integer); //普通参数传递
begin
    N:=N+2;
end;

procedure P3(PT:PInteger); //传递指针参数
begin
    PT^:=PT^+3;
end;

var
    i:Integer;
begin
    i:=1;
    P1(i); //将i 的值增加1
    Writeln('i:',i);
    P2(i); //希望将i 加2,但没有实现
    Writeln('i:',i);
    P3(@i); //将i 加3    
    Writeln('i:',i);
    Readln;
end.

 

 

运行结果如下:

i:2
i:2
i:5

这里有几点需要说明:

  • 一开始变量i 的数值为1,经过P1 过程的处理,将i 加1,所以显示的第1 个i 的数值为2,这时使用的是引用参数传递。
  • 在过程P2 中,将形参的数值增加了2,实际上i 并没有增加,所以显示的第2 个i 的数值仍然为2。在这种情况下,正常的做法可以使用函数的返回值,例如:
Result:=N+2;

在调用函数的时候使用:

i:=P2(i);
  • 在过程P3 中,传递的是变量I 的指针,所以操作是针对i 进行的,第3 次显示i 的数值是5。


3.过程与函数的调用约定
  在调用过程或函数的时候,如果参数列表中具有多个参数,那么参数传递给过程或函数的顺序会对结果产生一定的影响。对于不同的语言,参数传递的顺序是不同的:Pascal 语言是按照从左向右的顺序进行传递的,而C 语言是按照从右向左的顺序来传递的。

  为了确定传递的顺序,可以在过程或函数定义的时候,在Directives 部分利用指令字指定传递的顺序。来自Delphi 的联机帮助的数据,如表1-13 所示,其中列举了Directives 部分可使用的关于函数调用约定的指令字。
表1-13 定义过程与函数时对调用约定起作用的指令字

Directive 指令 Register Pascal Stdcall safecall Cdecl
参数传递顺序 从左向右 从左向右 从右向左 从右向左 从右向左


可以通过下面的例子查看参数传递的顺序:

program Project1;
{$APPTYPE CONSOLE}

function P1:Integer; //该函数将作为GetMax 函数的第1 个参数
begin
    Writeln('P1');
    Result:=0;
end;

function P2:Integer; //该函数将作为GetMax 函数的第2 个参数
begin
    Writeln('P2 ') ;
    Result:=1;
end;

//参数的传递方式采用pascal 方式
function GetMax(N1:Integer; N2:Integer):Integer;pascal;
begin
    Result:=N1+N2;
end;

begin
    GetMax(P1,P2);
end.

运行结果如下:


1 P1

2 P2

如果将GetMax 函数定义处的Directives 部分由Pascal 改为Stdcall,则运行结果变为:

P2
P1

用户可以修改GetMax 函数定义处的Directives 部分为表1-13 中的其他数值,测试结果是否一致。

 

4.过程和函数的重载

可以在同一个作用范围内给不同的过程或函数取同一个名称,这种现象就叫做重载。这样可以使编程更方便。在重载的情况下,决定使用哪个过程或函数的依据是形参和实参的一致性,即参数个数、参数类型以及它们之间的顺序,不存在一个函数调用满足两个重载函数的情况。另外重载函数必须用指令字Overload 来进行说明,函数的返回值类型不同就不可以作为重载函数的依据。

下面的两个函数就是重载函数:

function Average(a:Integer; b:Integer):Double;overload; //求整形数据的平均值
function Average(a:Double; b:Double):Double;overload; //求实数数据的平均值

下面两条语句就调用了不同的函数:

Average(3.7,4.6); //调用的是第2 个重载函数
Average(3,4); //调用的是第1 个重载函数

 

如果又定义了一个重载函数如下:

function Average(a,b:Double;c:Double=0.0):Double;overload; //求3 个实数平均值

 

从上例可以看出,尽管参数的个数与上面的两个不同,但第3 个参数设置了一个默认值,所以当参数调用为语句Average(1.1,2.2);时,编译系统就不知道应该使用哪个重载函数了,因为第2 个重载函数和第3 个重载函数都可以满足要求,这样就会出现一个编译错误。


5.函数的递归调用

在Object Pascal 中,过程或函数必须先说明再调用。以上规则在递归调用时属于例外情况。所谓递归调用,是指函数A 调用函数B,而函数B 又调用函数A 的情况,或是指一个函数调用自身的特殊情况。在递归调用中,函数要进行前置,即在函数或过程的标题部分最后加上保留字Forward。下文的例子是一个递归调用的典型例子:

program Project1;
{$APPTYPE CONSOLE}

var
    Alpha:Integer;

procedure Test2(var A:Integer);forward;

//Test2 被说明为前置过程
procedure Test1(var A:Integer);
begin
    A:=A-1;
    if A>0 then
        Test2(A); //经前置说明,调用未执行的过程Test2
        writeln(A);
end;

procedure Test2(var A:Integer); //经前置说明的Test2 的执行部分
begin
    A:=A div 2;
        if A>0 then
            Test1(A); //在Test2 中调用已执行的过程Test1
end;

begin
    Alpha := 15; //给Alpha 赋初值
    Test1(Alpha); //第1 次调用Test1,递归开始
end.

程序开始时给Alpha 赋初值,并实现先减1 再除2 的循环递归调用,直到Alpha 小于0 为止。

 

规范化命名

在系统开发的过程中,常常要为变量、类、对象、函数和文件等命名。一般在开发需求或设计阶段就必须制定出一套完整、实用的命名规则。这样,在很大程度上可以提高系统开发的效率,便于不同模块之间的接口,方便系统的维护。
在制定命名规则的时候,一个基本的原则就是便于使用、便于维护、风格统一。另外还应注意下面几点:

  • 命名时要采用英文单词,而不要使用中文拼音,尤其不要使用中文拼音第1 个字母的组合。在使用英文单词命名时,尽量采用统一、简单、贴切的词语,尽可能使用完整的单词或音节。
  • 有些名称可以采用几个英文单词的组合。在组合过程中,尽量不要使用下划线来分隔单词,最好采用大小写混写的方式来实现。
  • 对于保留字和指令字可以统一全部小写,而对于一些常量名可以全部大写。
  • 有些名称可以是“动词+对象”组合而成,也可以是“对象+动词”组合而成。一般来说,“动词+对象”比较符合平常的语法习惯。无论采用哪种方法,整体上都应该统一。
  • 在有些情况下,要考虑到与Delphi 集成开发环境的统一。例如在Delphi 集成开发环境中,普通类的名称一般以T 开头,异常类的名称一般以E 开头。
  • 在对菜单命令的标识号命名的时候,应将所属菜单项的名称包含进去。比如对于“文件”菜单项中的菜单命令,可以将标识号命名为FileOpen、FileClose 等。
  • 对于一些表示集合意义的名称,可以使用名词的复数形式。比如窗口的集合,可以使用Windows,而不要使用WindowCollection。

 

posted @ 2014-07-09 15:38  ivantang  阅读(1366)  评论(0编辑  收藏  举报