1.8 过程与函数
过程与函数是实现一定功能的语句块,是程序中的特定功能单元。可以在程序的其他地方被调用,也可以进行递归调用。过程与函数的区别在于过程没有返回值,而函数有返回值。
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 2 |
|
如果将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.9 规范化命名
在系统开发的过程中,常常要为变量、类、对象、函数和文件等命名。一般在开发需求或设计阶段就必须制定出一套完整、实用的命名规则。这样,在很大程度上可以提高系统开发的效率,便于不同模块之间的接口,方便系统的维护。
在制定命名规则的时候,一个基本的原则就是便于使用、便于维护、风格统一。另外还应注意下面几点:
- 命名时要采用英文单词,而不要使用中文拼音,尤其不要使用中文拼音第1 个字母的组合。在使用英文单词命名时,尽量采用统一、简单、贴切的词语,尽可能使用完整的单词或音节。
- 有些名称可以采用几个英文单词的组合。在组合过程中,尽量不要使用下划线来分隔单词,最好采用大小写混写的方式来实现。
- 对于保留字和指令字可以统一全部小写,而对于一些常量名可以全部大写。
- 有些名称可以是“动词+对象”组合而成,也可以是“对象+动词”组合而成。一般来说,“动词+对象”比较符合平常的语法习惯。无论采用哪种方法,整体上都应该统一。
- 在有些情况下,要考虑到与Delphi 集成开发环境的统一。例如在Delphi 集成开发环境中,普通类的名称一般以T 开头,异常类的名称一般以E 开头。
- 在对菜单命令的标识号命名的时候,应将所属菜单项的名称包含进去。比如对于“文件”菜单项中的菜单命令,可以将标识号命名为FileOpen、FileClose 等。
- 对于一些表示集合意义的名称,可以使用名词的复数形式。比如窗口的集合,可以使用Windows,而不要使用WindowCollection。
参考: http://chanlei001.blog.163.com/blog/static/3403066420117261154351
http://www.cnblogs.com/del/archive/2007/12/04/982167.html
http://blog.csdn.net/hudie1234567/article/details/6403419
Object Pascal 语法之语言基础(四)