• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

DELPHI学习---过程和函数

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

Procedures and functions(过程和函数)
Procedures and functions: Overview(概述)
过程和函数统称为例程(routine),它们是自包含的语句块,能在程序的不同地方调用。函数在在执行时
能返回一个值,而过程不能。
因为函数调用能返回值,它们能被用在赋值和运算表达式中。比如,
I := SomeFunction(X);
调用SomeFunction 并且把它的结果赋给I。函数调用不能出现在赋值语句的左边。
过程调用能被用作一个完整的语句,并且,当启用扩展语法时({$X+}),函数调用也可以。比如,
DoSomething;
调用DoSomething 例程,若DoSomething 是一个函数,它的返回值被忽略。
过程和函数能递归调用。


Declaring procedures and functions(声明过程和函数)
Declaring procedures and functions: Overview(概述)
声明过程或函数时,要指定它的名称,它使用的参数(数目和类型),如果是函数的话,还有返回值的类
型,这一部分有时被称为原型、标题或头(prototype、heading、header)。然后你为它写一个代码块,当
过程或函数被调用时,它们将会执行。这一部分有时称为例程体或例程块(body、block)。
标准过程Exit 可出现在任何过程或函数中,它结束例程的执行,并立即把程序控制返回到例程调用的地
方。


Procedure declarations(过程声明)
一个过程声明有如下格式:
procedure procedureName(parameterList); directives;
localDeclarations;
begin
statements
end;
这里,procedureName 是任何有效标志符;statements 是一系列语句,当调用过程时它们被执行;
(parameterList)、directives; 和localDeclarations 是可选的。
• 要了解parameterList 的信息,请参考Parameters;
• 要了解directives 的信息,请参考Calling conventions、Forward and interface declarations、External
declarations、 Overloading procedures and functions、和Writing dynamically loadable libraries。如果包
含多个指示字,用分号把它们隔开;
• localDeclarations 定义局部标志符,要了解它的信息,请参考Local declarations。
这里是一个过程声明的例子:
procedure NumString(N: Integer; var S: string);
var
V: Integer;
begin
V := Abs(N);
S := '';
repeat
S := Chr(V mod 10 + Ord('0')) + S;
V := V div 10;
until V = 0;
if N < 0 then S := '-' + S;
end;
给定上面的声明,你能像这样调用NumString 过程:
NumString(17, MyString);
这个过程调用把值“17”赋给MyString(它必须是字符串变量)。
在过程的语句块中,你可以使用在localDeclarations 部分声明的变量和其它标志符;你也能使用参数列
表中的参数名称(像上面例子中的N 和S)。参数列表定义了一组局部变量,所以,不要在localDeclarations
部分重新声明它们;最后,你还可以使用过程声明所在范围中的任何标志符。
(以下内容摘自《Delphi 技术手册》
有些过程并不是普通的过程而是内置在编译器之中的,所以你无法获得它们的地址。一些过程(如Exit)
用起来就像是语言中的语句一样,但它们不是保留的关键字,你可以像使用其它过程一样使用它们。)


Function declarations(函数声明)
函数声明和过程声明类似,除了它要指定一个返回值的类似和返回值。函数声明有如下格式:
function functionName(parameterList): returnType; directives;
localDeclarations;
begin
statements
end;
这里,functionName 是任何有效标志符,returnType 是任何类型,statements 是一系列语句,当调用函数
时它们被执行;(parameterList)、directives; 和 localDeclarations 是可选的。
• 要了解parameterList 的信息,请参考Parameters;
• 要了解directives 的信息,请参考Calling conventions、Forward and interface declarations、External
declarations、 Overloading procedures and functions、和Writing dynamically loadable libraries。如果包
含多个指示字,用分号把它们隔开;
• localDeclarations 定义局部标志符,要了解它的信息,请参考Local declarations。
函数的语句块和过程遵循相同的规则:在语句块内部,你可以使用在localDeclarations 部分声明的变量
和其它标志符、参数列表中的参数名称,以及函数声明所在范围的所有标志符。除此之外,函数名本身
也扮演一个特殊的变量,它和内置的变量Result 一样,存储函数的返回值。

比如,
function WF: Integer;
begin
WF := 17;
end;
定义了一个叫做WF 的常量函数,它没有任何参数,并且返回值总是17。它和下面的声明是相同的:
function WF: Integer;
begin
Result := 17;
end;
下面是一个更复杂的函数声明:
function Max(A: array of Real; N: Integer): Real;
var
X: Real;
I: Integer;
begin
X := A[0];
for I := 1 to N - 1 do
if X < A[I] then X := A[I];
Max := X;
end;
在语句块内部,你可以重复给Result 或函数名赋值,只要这个值的类型和函数声明的返回值类型相同即
可。当函数的执行结束时,Result 或函数名最后被赋予的值当作函数的返回值。比如,
function Power(X: Real; Y: Integer): Real;
var
I: Integer;
begin
Result := 1.0;
I := Y;
while I > 0 do
begin
if Odd(I) then Result := Result * X;
I := I div 2;
X := Sqr(X);
end;
end;
Result 和函数名总是表示同一个值,因此
function MyFunction: Integer;
begin
MyFunction := 5;
Result := Result * 2;
MyFunction := Result + 1;
end;
返回值11。但Result 和函数名并不是能完全互换的,当函数名出现在赋值语句的左边时,编译器假设它
用来跟踪(存储)返回值(就像Result);在任何其它情况下,编译器把它解释为对它的递归调用。而对
Result,它可以作为变量用在运算、类型转换、集合构造器、索引以及调用其它例程。
只要启用了扩展语法({$X+}),Result 在每个函数中被隐含声明,不要试图重新声明它。
若还没有给Result 或函数名赋值,程序就结束了,则函数的返回值没有被定义(undefined)。
(以下内容摘自《Delphi 技术手册》
并不是所有的函数都是真正的函数;有些是内置在编译器中的。这种差别通常并不重要,因为内置函数
看上去和用起来都像是普通的函数,但是你无法获得内置函数的地址。)

Calling conventions(调用约定)
在声明过程或函数时,你可以使用下面的指示字之一来指明调用约定:register、pascal、cdecl、stdcall
以及safecall。比如,
function MyFunction(X, Y: Real): Real; cdecl;
...
调用约定决定了参数被传递给例程的顺序,它们也影响从堆栈中删除参数、传递参数时寄存器的使用,
以及错误和异常处理。默认的调用约定是register。
• register 和pascal 调用从左到右传递参数,也就是说,最左边的参数最早被计算并传递,最右边的
参数最后被计算和传递;cdecl、stdcall 和safecall 调用从右到左传递参数;
• 除了cdecl 调用,过程和函数在返回之前从堆栈中移除参数,而使用cdecl,当调用返回时,调用者
从堆栈中移除参数;
• register 调用能使用多达3 个CPU 寄存器传递参数,而其它调用则全部使用堆栈传递参数;
• safecall 调用实现了异常“防火墙”,在Windows 下,它实现了进程间COM 错误通知。
下面的表格对调用约定进行了总结:
指示字                参数顺序           Clean-up           使用寄存器传递参数?
register           Left-to-right      Routine                Yes
pascal             Left-to-right      Routine                No
cdecl               Right-to-left      Caller                   No
stdcall            Right-to-left       Routine                No
safecall           Right-to-left       Routine                No
默认的register 调用是最有效的,因为它通常避免了要创建堆栈结构(stack frame)(访问公布属性的方
法必须使用register);当调用来自C/C++编写的共享库中的函数时,cdecl 是有用的;通常,当调用外部
代码时,推荐使用stdcall 和safecall。在Windows 中,系统API 使用stdcall 和safecall,其它操作系统
通常使用cdecl(注意,stdcall 比cdecl 更有效)。
声明双重接口的方法必须使用safecall;保留pascal 调用是为了向后兼容性。要了解更多的调用约定的
信息,请参考Program control。
指示字near、far 和export 用在16 位Windows 编程中,它们对32 位程序没有影响,保留它们是为了向
后兼容性。


Forward and interface declarations(Forward 声明和接口部分的声明)
在声明过程或函数时,用forward 指示字取代例程块(包括局部变量声明和语句),比如,
function Calculate(X, Y: Integer): Real; forward;
这就声明了一个叫做Calculate 的函数,在forward 声明后的某个地方,例程必须进行定义声明,包括例
程块。Calculate 的定义声明看起来这样:
function Calculate;
... { declarations }
begin
... { statement block }
end;
通常,定义声明不必重新列出例程使用的参数或者返回值类型,但如果这样做的话,它必须和forward
声明完全一致(除了默认参数能被忽略)。若forward 声明是一个重载的过程或函数,则必须在定义声明
中重新列出它的参数。
在forward 声明和它的定义声明之间,除了声明外不能有其它内容。定义声明可以是external 或
assembler,但不能是另外的forward 声明。
forward 声明的目的是把过程或函数标志符的作用域提前,这允许在它被实际定义之前,其它过程和函
数可以进行调用。
除了能使你更灵活地组织代码外,forward 声明对相互递归调用(mutual recursion)有
时是必须的。
在单元的接口(interface)部分,forward 指示字是不允许的,但是,接口部分的过程头或函数头,它们
的行为就像forward 声明,它们的定义声明必须出现在实现(implementation)部分。在单元的接口部分
声明的例程,在本单元的其它任何位置都可以访问;对于使用本单元的其它单元或程序,这些例程也是
可以访问的。


External declarations(External 声明)
在声明过程或函数时,用external 指示字取代例程块,能允许你调用和程序分开编译的例程。外部例程
可以来自目标文件或动态调入库(dynamically loadable library)。
当导入一个带有可变数目参数的C++函数时,要使用varargs 指示字。比如,
function printf(Format: PChar): Integer; cdecl; varargs;
varargs 指示字只能用于外部例程,并且只能使用cdecl 调用约定。
链接目标文件
要调用目标文件中的例程,首先要使用$L(或$LINK)编译器指示字把目标文件链接到你的程序中。比
如,
在Windows 下: {$L BLOCK.OBJ}
在Linux 下: {$L block.o}
把BLOCK.OBJ(Windows)或block.o (Linux)链接到程序或它所在的单元。然后,声明你想调用的
函数和过程:
procedure MoveWord(var Source, Dest; Count: Integer); external;
procedure FillWord(var Dest; Data: Integer; Count: Integer); external;
现在,你就能调用来自BLOCK.OBJ 或block.o 的MoveWord 和FillWord 例程了。
像上面的声明,经常用来访问由汇编语言编写的外部例程,你也可以直接在Object Pascal 源代码中放置
汇编语言写的例程。关于这方面的信息,请参考Inline assembler code。


从库导入函数
要从一个动态调入库(.so 或.DLL)导入例程,把如下格式的指示字
external stringConstant;
放在一个正常的过程头或函数头的尾部。这里,stringConstant 是用单引号括起来的库文件的名称。比如,
在Windwos 下
function SomeFunction(S: string): string; external 'strlib.dll';
从strlib.dll 导入一个叫做SomeFunction 的函数。
在Linux 下,
function SomeFunction(S: string): string; external 'strlib.so';
从strlib.so 导入一个叫做SomeFunction 的函数。
在导入例程时,它的名称可以和库中的名称不同。如果你这样做,在external 指示字中指定它的原始名
称。
external stringConstant1 name stringConstant2;
这里,第一个stringConstant 给出了库文件的名称,第二个stringConstant 是例程的原始名称。
在Windows 下:比如,下面的声明从user32.dll(Windows API 的一部分)导入一个函数。
function MessageBox(HWnd: Integer; Text, Caption: PChar; Flags: Integer): Integer;
stdcall; external 'user32.dll' name 'MessageBoxA';
函数的原始名称是MessageBoxA,但导入后的名称是MessageBox。

你可以使用一个数字代替名称,来指定你要导入的例程:
external stringConstant index integerConstant;
这里,integerConstant 是输出表(export table)中例程的索引。
在Linux 下:比如,下面的声明从libc.so.6 导入一个标准系统函数。
function OpenFile(const PathName: PChar; Flags: Integer): Integer; cdecl;
external 'libc.so.6' name 'open';
函数的原始名称是open,但导入后的名称是OpenFile。
在你的导入声明中,要保证例程的名称没有拼写错误,并且大小写一致。但在以后调用这些例程时,它
们是不区分大小写的。

关于库的更多信息,请参考Libraries and packages。


Overloading procedures and functions(重载过程和函数)
你能使用相同的名称在一个作用域声明多个例程,这叫做重载。重载例程必须使用overload 指示字,并
且它们有不同的参数列表。
比如,考虑下面的声明
function Divide(X, Y: Real): Real; overload;          //加overload指示字
begin
Result := X/Y;
end;
function Divide(X, Y: Integer): Integer; overload;       //加overload指示字  
begin
Result := X div Y;
end;
这些声明创建了两个函数,它们都叫做Divide,使用不同类型的参数。当调用Divide 时,编译器通过查
看实参的类型来决定哪个函数被调用。比如,Divide(6.0, 3.0)调用第一个Divide 函数,因为它的参数是
实数类型。
You can pass to an overloaded routine parameters that are not identical in type with those in any of the routine’s
declarations, but that are assignment-compatible with the parameters in more than one declaration. This happens
most frequently when a routine is overloaded with different integer types or different real types-for example,
你能给一个重载的例程传递这样的参数:它们的类型和所有例程声明中的参数类型都不同,但又和多个
例程声明中的参数是赋值兼容的。对于使用了不同的整数(或实数)类型的重载例程来说,这种情况最
经常发生,比如,
procedure Store(X: Longint); overload;
procedure Store(X: Shortint); overload;
在这种情况下,只要不产生模棱两可,编译器将调用如下的例程:传递给它的实参和它声明的参数相匹
配,并且声明的参数范围最小。(记住,实数相关的常量值总是Extended 类型。)
重载例程必须能以参数的数目或类型区分开来,因此,下面的声明将产生编译错误:
function Cap(S: string): string; overload;
...
procedure Cap(var Str: string); overload;
...
但声明
function Func(X: Real; Y: Integer): Real; overload;
...
function Func(X: Integer; Y: Real): Real; overload;
...
是合法的。
当重载例程被声明为forward、或在单元的接口部分声明时,在它的定义声明部分必须重新列出它的参
数。
编译器能区分两个包含AnsiString/Pchar 以及WideString/WideChar 的重载例程。在这种情况下,字符串
常量或文字被解释为native 字符串或字符类型,也就是AnsiString/Pchar。
procedure test(const S: String); overload;
procedure test(const W: WideString); overload;
var
a: string;
b: widestring;
begin
a := 'a';
b := 'b';
test(a); // 调用String 版本
test(b); // 调用widestring 版本
test('abc'); // 调用 String 版本
test(WideString('abc')); // 调用widestring 版本
end;
在声明重载例程时能使用Variant 类型的参数。Variant 类型被认为比简单类型更通用,精确的类型匹配
比Variant 更有优先权。If a Variant is passed into such an overload situation, and an overload that takes a
Variant exists in that parameter position, it is considered to be an exact match for the Variant type.
这对浮点类型有些影响。浮点类型通过大小进行匹配,如果在调用时,参数类型和传递的浮点类型不完
全匹配、但有一个Variant 类型的参数,则采用Variant 类型,而不是其它较小的浮点类型(the Variant is
taken over any smaller float type)。
比如
procedure foo(i: integer); overload;
procedure foo(d: double); overload;
procedure foo(v: Variant); overload;
var
v: Variant;
begin
foo(1); // integer 版本
foo(v); // Variant 版本
foo(1.2); // Variant 版本(float literals -> extended 精度)
end;
这个例子调用foo 的Variant 版本,而不是double 版本,因为常数1.2 隐含是extended 类型,它和double
类型不完全匹配。Extended 和Variant 类型也不是完全匹配,但Variant 类型更通用(尽管double 类型比
extended 类型更小)。
foo(Double(1.2));
这个类型转换不起作用,你应该使用指定类型的常数。
const d: double = 1.2;
begin
foo(d);
end;
上面的代码工作正常,将调用double 版本。
const s: single = 1.2;
begin
foo(s);
end;
上面的代码也是调用double 版本,相对于Variant,single 和double 更匹配,
当声明一组重载例程时,避免浮点类型解释为Variant 类型的最好办法,是在声明Variant 版本的同时,
为每一种浮点类型(Single、Double、Extended)声明一个版本。
若在重载例程中使用默认参数,要当心不明确的参数调用。关于更多这方面的信息,请参考Default
parameters and overloaded routines。
通过在调用例程时限定它的名称,能减少重载例程潜在的影响。比如,Unit1.MyProcedure(X, Y)只能调
用在Unit1 单元声明的例程,若Unit1 中没有和指定的名称以及参数相匹配的例程,错误将发生。
关于在类的继承层次中发布重载方法,请参考Overloading methods;关于在共享库中输出重载例程,请
参考The exports clause。


Local declarations(局部声明)
函数体或过程体经常以声明局部变量开始,它们用在例程的语句块中。这里也可以包括常量、类型以及
例程声明。局部标志符的作用域被限制在声明它的例程中。
嵌套例程
有时,函数和过程在它的局部声明块中包含其它函数和过程。比如,下面声明了一个叫做DoSomething
的过程,它包含一个嵌套过程。
procedure DoSomething(S: string);
var
X, Y: Integer;
procedure NestedProc(S: string);
begin
...
end;
begin
...
NestedProc(S);
...
end;
嵌套例程的作用域限制在声明它的过程或函数中,在我们的例子中,NestedProc 只能在DoSomething 内
部调用。

关于嵌套例程的实际应用,请参考SysUtils 单元的DateTimeToString 过程、ScanDate 函数以及其它例程。

Parameters(参数)
Parameters: Overview(概述)
大多数过程头和函数头包含参数列表,比如,在函数头
function Power(X: Real; Y: Integer): Real;
中,参数列表是(X: Real; Y: Integer)。
参数列表由一对括号包围,包含由分号隔开的一系列参数声明。每个声明中列出的参数名由逗号分开,
大多数情况下后面跟一个冒号和一个类型标志符,在一些情况下跟一个等号和一个默认值。参数名必须
是有效标志符。任何一个声明能以下面的一个关键字作为前缀:var、const 和out(参考Parameter
semantics)。比如:
(X, Y: Real)
(var S: string; X: Integer)
(HWnd: Integer; Text, Caption: PChar; Flags: Integer)
(const P; I: Integer)
参数列表指定了调用例程时必须传递的参数的数目、顺序和类型。若一个例程没有任何参数,声明时忽
略括号和标志符列表。
procedure UpdateRecords;
begin
...
end;
在过程体或函数体中,参数名(上面第一个例子中的X 和Y)能被用作局部变量,在局部声明中不能重
新声明参数名。


Parameter semantics(参数语义)
Parameter semantics: Overview(概述)
参数以下面几种方式进行分类:
• 每个参数分为value(数值参数)、variable(变量参数)、constant(常量参数)或out(out 参数),
默认是数值参数。关键字var、const 以及out 分别表示变量参数、常量参数和out 参数。
• 数值参数总是有类型的,而常量参数、变量参数和out 参数既可以是有类型的,也可以是无类型的。
• 数组参数有特殊规则。

文件类型以及包含文件的结构类型(的实例)只能作为变量参数传递。


Value and variable parameters(数值参数和变量参数)
大多数参数是数值参数(默认)或变量参数(var)。数值参数通过数值传递,而变量参数通过引用传递。
要了解这句话的意义,考虑下面的函数。
function DoubleByValue(X: Integer): Integer; // X 是数值参数
begin
X := X * 2;
Result := X;
end;
function DoubleByRef(var X: Integer): Integer; // X 是变量参数
begin
X := X * 2;
Result := X;
end;
这两个函数返回同样的结果,但只有第二个(DoubleByRef)能改变传给它的变量的值。假设我们这样
调用函数:
var
I, J, V, W: Integer;
begin
I := 4;
V := 4;
J := DoubleByValue(I); // J = 8, I = 4
W := DoubleByRef(V); // W = 8, V = 8
end;
这些代码执行后,传给DoubleByValue 的变量I,它的值和我们初始赋给它的值是相同的。但传给
DoubleByRef 的变量V,它有不同的值。
数值参数就像局部变量,它们的初始值是传给过程或函数的值。若把一个变量当作数值参数传递,过程
或函数创建它的一个拷贝,改变这个拷贝对原始变量没有影响,并且,当程序返回调用者时,这个拷贝
将被丢弃。
而另一方面,变量参数就像一个指针而不是一个拷贝,当程序返回调用者时,在函数或过程体中对它的
改变将被保留,(仅仅)参数名本身超出了作用域。
即使同一个变量被传给两个或多个参数,也不会创建它的拷贝,这通过下面的例子说明。
procedure AddOne(var X, Y: Integer);
begin
X := X + 1;
Y := Y + 1;
end;
var I: Integer;
begin
I := 1;
AddOne(I, I);
end;
这些代码执行后,I 的值是3。
如果例程声明了一个var 参数,你必须给它传递一个能被赋值的表达式,也就是一个变量、类型化常量
(typed constant,在{$J+}状态下)、dereferenced 指针、字段或者索引变量(indexed variable)。在前面的
例子中,DoubleByRef(7)会产生错误,而DoubleByValue(7)是合法的。
索引以及指针引用被当作var 参数时,在例程执行之前它们会被计算(一次)。


Constant parameters(常量参数)
一个常量参数(const)就像一个局部常量或者一个只读变量,常量参数和数值参数类似,但在过程或函
数中你不能给常量参数赋值,也不能把它当作变量参数传给另一个例程。(但是,当把一个对象引用当作
常量参数时,你仍然可以修改对象的属性。)
使用const 允许编译器对结构类型和字符串类型的参数做代码优化,也防止不小心把一个参数通过引用
传给另一个例程。
这里的例子是SysUtils 单元中的CompareStr 函数:
function CompareStr(const S1, S2: string): Integer;
因为S1 和S2 在CompareStr 中没有被修改,它们可以被声明为常量参数。


Out parameters(Out 参数)
Out 参数和变量参数类似,通过引用传递。但是,当使用out 参数时,传给例程的引用参数的初始值被
忽略。out 参数只是为了输出,也就是说,它告诉函数或过程在哪里存储输出,但不提供任何输入。
比如,考虑过程头声明
procedure GetInfo(out Info: SomeRecordType);
当你调用GetInfo 时,你必须给它传递一个SomeRecordType 类型的变量:
var MyRecord: SomeRecordType;
...
GetInfo(MyRecord);
但你不使用MyRecord 给GetInfo 传递任何值,MyRecord 只是一个容器,你希望GetInfo 用它存储得到
的信息。The call to GetInfo immediately frees(释放还是清零?) the memory used by MyRecord, before
program control passes to the procedure.


out 参数经常用在分布式对象模型中,比如COM 和CORBA。而且,当向函数或过程传递未初始化的变
量时,你应当使用out 参数。


Untyed parameters(无类型参数)
当声明var、const 和out 参数时,你可以省略类型说明(数值参数必须指定类型)。比如,
procedure TakeAnything(const C);
声明一个叫做TakeAnything 的过程,它可以接受任何类型的参数。当你调用这样一个例程时,你不能向
它传递numeral or untyped numeric constant。
在过程体或函数体中,无类型参数和每个类型都不兼容。要对一个无类型参数进行操作,你必须对它进
行转换。通常,编译器不会对无类型参数检验它的有效性。
下面的例子在Equal 函数中使用无类型参数,这个函数比较两个参数中指定数目的字节。
function Equal(var Source, Dest; Size: Integer): Boolean;
type
TBytes = array[0..MaxInt - 1] of Byte;
var
N: Integer;
begin
N := 0;
while (N < Size) and (TBytes(Dest)[N] = TBytes(Source)[N]) do
Inc(N);
Equal := N = Size;
end;
给定下面的声明
type
TVector = array[1..10] of Integer;
TPoint = record
X, Y: Integer;
end;
var
Vec1, Vec2: TVector;
N: Integer;
P: TPoint;
你可以如下调用Equal:
Equal(Vec1, Vec2, SizeOf(TVector)) // 比较Vec1 和Vec2
Equal(Vec1, Vec2, SizeOf(Integer) * N) // 比较Vec1 和Vec2 的前N 个元素
Equal(Vec1[1], Vec1[6], SizeOf(Integer) * 5) // 比较Vec1 的前5 个元素和后5 个元素
Equal(Vec1[1], P, 4) // 比较Vec1[1]和P.X、Vec1[2]和P.Y


String parameters(字符串参数)
About string parameters(关于字符串参数)
当例程使用短字符串(short-string)参数时,在声明时不能指定它们的长度。也就是说,声明
procedure Check(S: string[20]); // 语法错误
产生编译错误。但是
type TString20 = string[20];
procedure Check(S: TString20);
是有效的。特殊标志符OpenString 能用于声明可变长度的短字符串参数:
procedure Check(S: OpenString);
当编译器指示字{$H-}和{$P+}都起作用时,在声明参数时关键字string 等同于OpenString。
短字符串、OpenString、$H 和$P 是为了向后兼容性。在新代码中,使用长字符串来避免这种情况。
Array parameters(数组参数)
Array parameters: Overview(概述)
当例程使用数组参数时,你不能在声明参数时包含索引说明符。也就是说,声明
procedure Sort(A: array[1..10] of Integer); // 语法错误
产生编译错误,但
type TDigits = array[1..10] of Integer;
procedure Sort(A: TDigits);
是有效的。但在大多数情况下,开放数组参数是更好的办法。


Open array parameters(开放数组参数)
开放数组参数允许传递不同大小的数组到同一个过程或函数。要定义一个使用开放数组参数的例程,在
声明参数时使用array of type(而不是array[X..Y])。
function Find(A: array of Char): Integer;
声明了一个叫做Find 的函数,它接收任意长度的字符数组并返回一个整数。
注意:开放数组参数的语法和声明动态数组相似,但它们的意义不同。上面的例子创建了一个函数,它
可以接收由字符构成的任何数组,包括(但不限于)动态数组。对于必须是动态数组的参数,你需要指
定一个类型标志符:
type TDynamicCharArray = array of Char;
function Find(A: TDynamicCharArray): Integer;
关于动态数组的信息,请参考Dynamic arrays。
在例程体(body)中,开放数组参数遵循下列规则:
• 元素的下标总是从0 开始,第一个是0,第二个是1,依此类推。标准函数Low 和High 返回0 和
Length-1。SizeOf 函数返回传给例程的实际数组的大小;
• 它们只能通过元素进行访问,不允许给整个开放数组赋值;
• 它们只能被当作开放数组参数或无类型var 参数传给其它过程和函数,它们不能传给SetLength 函
数;
• 你可以传递一个变量而不是数组,变量的类型就是开放数组的基础类型,它被当作一个长度为1 的
数组。
当把一个数组当作开放数组数值参数传递时,编译器在例程的堆栈结构(stack frame)中创建一个本地
拷贝,传递大数组时要当心堆栈溢出。
下面的例子使用开放数组参数定义了一个Clear 过程,它把数组中的每个实数元素赋0;还定义了一个
Sum 函数,它计算实数数组的元素之和。
procedure Clear(var A: array of Real);
var
I: Integer;
begin
for I := 0 to High(A) do A[I] := 0;
end;
function Sum(const A: array of Real): Real;
var
I: Integer;
S: Real;
begin
S := 0;
for I := 0 to High(A) do S := S + A[I];
Sum := S;
end;
当调用使用开放数组参数的例程时,你可以向它传递开放数组构造器。


Variant open array parameters(Variant 开放数组参数)
Variant 开放数组参数允许你向一个过程或函数传递由不同类型的元素构成的数组。要定义这样一个例
程,指定array of const 作为参数的类型,这样
procedure DoSomething(A: array of const);
声明了一个叫做DoSomething 的过程,它能接收不同类型的数组。
array of const 结构等同于array of TVarRec。TVarRec 在System 单元定义,表示一个拥有变体部分的记
录,它能存储整数、布尔、字符、实数、字符串、指针、类、类引用、接口和变体类型的值。TVarRec
记录的VType 字段指示数组中每个元素的类型。一些类型以指针而不是以数值形式进行传递,特别是,
长字符串以指针类型传递,必须被转换为string。
下面的函数使用Variant 开放数组参数,它把传给它的每个元素用字符串表示,并把它们连接成一个单一
的字符串。函数中使用的字符串处理例程在SysUtils 单元定义。
function MakeStr(const Args: array of const): string;
const
BoolChars: array[Boolean] of Char = ('F', 'T');
var
I: Integer;
begin
Result := '';
for I := 0 to High(Args) do
with Args[I] do
case VType of
vtInteger: Result := Result + IntToStr(VInteger);
vtBoolean: Result := Result + BoolChars[VBoolean];
vtChar: Result := Result + VChar;
vtExtended: Result := Result + FloatToStr(VExtended^);
vtString: Result := Result + VString^;
vtPChar: Result := Result + VPChar;
vtObject: Result := Result + VObject.ClassName;
vtClass: Result := Result + VClass.ClassName;
vtAnsiString: Result := Result + string(VAnsiString);
vtCurrency: Result := Result + CurrToStr(VCurrency^);
vtVariant: Result := Result + string(VVariant^);
vtInt64: Result := Result + IntToStr(VInt64^);
end;
end;
我们可以传递一个开放数组构造器来调用这个函数(参考Open array constructors)。比如,
MakeStr(['test', 100, ' ', True, 3.14159, TForm])
返回字符串“test100 T3.14159TForm”。
Default parameters(默认参数)
Default parameters(默认参数)
在过程头或函数头中,你可以指定默认的参数值。默认值只允许出现在指定类型的常量参数和数值参数
中。要提供一个默认值,在参数声明中以等号和一个常量表达式(和参数的类型赋值兼容)作为结束。
比如,给定下面的声明
procedure FillArray(A: array of Integer; Value: Integer = 0);
下面的过程调用是相等的。
FillArray(MyArray);
FillArray(MyArray, 0);
一个多参数声明不能指定一个默认值,这样,虽然
function MyFunction(X: Real = 3.5; Y: Real = 3.5): Real;
是合法的,但
function MyFunction(X, Y: Real = 3.5): Real; // 语法错误
是非法的。
有默认值的参数必须出现在参数列表的最后,也就是说,在声明了一个有默认值的参数后,它后面的所
有参数也必须有默认值。所以,下面的声明是非法的。
procedure MyProcedure(I: Integer = 1; S: string); // 语法错误
在过程类型中指定的默认值会覆盖实际例程中指定的默认值。所以,给出下面的声明
type TResizer = function(X: Real; Y: Real = 1.0): Real;
function Resizer(X: Real; Y: Real = 2.0): Real;
var
F: TResizer;
N: Real;
语句
F := Resizer;
F(N);
导致(N, 1.0)传给Resizer。
默认参数局限于能被常量表达式所表示的值,所以,动态数组、过程、类、类引用或者接口类型的参数
除了nil 外不能给它们指定默认值,而记录、变体、文件、静态数组和对象类型则根本不能指定默认值。
关于调用有默认值的例程,请参考Calling procedures and functions。
Default parameters and overloaded routines(默认参数和重载例程)
若在重载例程中使用默认参数,要避免引起歧义。比如,考虑下面的代码
procedure Confused(I: Integer); overload;
...
procedure Confused(I: Integer; J: Integer = 0); overload;
...
Confused(X); // 要调用哪一个呢?
实际上,哪个过程都不会调用,代码产生编译错误。


Default parameters in forward and interface declarations(forward 声明中的
默认参数)

若例程是forward 声明或出现在单元的接口部分,则默认参数值(若有的话)必须在forward 声明或在
接口部分的声明中指定。这种情况下,定义(实现)声明中的默认值可以被忽略,但如果定义声明包含
默认值的话,它们必须和forward 声明或接口部分的声明一致。

Calling procedures and functions(调用过程和函数)
Calling procedures and functions(调用过程和函数)
当调用一个过程或函数时,程序控制从函数的调用点转到例程体中。调用例程时,你可以使用例程的名
称(带或不带限定符),也可以使用指向例程的过程变量。不管哪种情况,若例程声明使用参数,则调用
时必须传递参数,并且它们的顺序和类型必须一致。传递给例程的参数叫做实参,而声明例程时的参数
称为形参。
当调用例程时,记住
• 用来传递指定类型的常量参数和数值参数的表达式必须和相应的形参是赋值兼容的;
• 用来传递var 和out 参数的表达式必须和相应的形参类型相同,除非形参没有指定类型(无类型);
• 只有能被赋值的表达式可用作var 和out 参数;
• 如果例程的形参是无类型的,numerals and true constants with numeric values 不能用作实参。
当调用使用默认参数的例程时,第一个默认参数后面的实参也必须使用默认值,像SomeFunction(,,X)形
式的调用是非法的。
当一个例程全部使用默认参数、并且都使用默认值调用时,可以省略它的括号。比如,给定过程声明
procedure DoSomething(X: Real = 1.0; I: Integer = 0; S: string = '');
下面的调用是等同的。
DoSomething();
DoSomething;


Open array constructors(开放数组构造器)
开放数组构造器允许你在函数和过程调用中直接构造数组,它们只能被当作开放数组参数或Variant 开放
数组参数进行传递。
开放数组构造器和集合构造器类似,是由逗号隔开的表达式序列,并且被一对中括号包围。
比如,给定声明
var I, J: Integer;
procedure Add(A: array of Integer);
你可以使用下面的语句调用Add 过程
Add([5, 7, I, I + J]);
这等同于
var Temp: array[0..3] of Integer;
...
Temp[0] := 5;
Temp[1] := 7;
Temp[2] := I;
Temp[3] := I + J;
Add(Temp);
开放数组构造器只能当作数值参数或常量参数传递。构造器中的表达式必须和数组参数的基础类型是赋
值兼容的。对于Variant 开放数组参数,表达式可以是不同的类型。


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
virtualtree的使用(Delphi)发布时间:2022-07-18
下一篇:
Delphi连接Oracle控件ODAC的安装及使用(轉載)发布时间:2022-07-18
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap