过程与函数

过程与函数

过程的声明形式如下:

1
2
3
4
procedure  过程名;
begin
// 过程实现语句;
end;

带参数的过程的声明形式如下:

1
2
3
4
procedure  过程名 (形参列表);
begin
// 过程实现语句;
end;

函数的声明形式如下:

1
2
3
4
function  函数名 (形参列表) : 返回值类型;
begin
// 函数实现语句;
end;

过程相当于没有返回值的函数。

函数返回值

Result 变量

Result 是函数中内置的变量,函数结束后,会默认返回此值

1
2
3
4
function GetMyValue: integer;
begin
Result := 100; // Result 变量做返回值
end;

函数名作返回值

除了用 Result 变量作返回值之外,函数名也可以当作返回值:

1
2
3
4
function GetMyValue: integer;
begin
GetMyValue := 100; // 函数名做返回值
end;

forward 声明

过程和函数可以使用 forward 进行前置声明,把具体实现往后推:

1
function GetMyValue: integer; forward;

external 声明

使用 external 声明表示过程和函数是来自源文件外部。

来自外部 obj

1
2
{$L BLOCK.OBJ}
function ExtFunctionName1: integer; external;

来自外部 dll

1
2
function ExtFunctionName1: integer; external user32;
function ExtFunctionName1: integer; external 'DllName.dll';

来自外部 dll 的指定函数名

1
function ExtFunctionName1: integer; external user32 name 'MessageBoxW';

来自 dll 并延迟加载

external 加上 delayed 指示字

1
2
3
4
5
6
7
function GetSystemMetricsForDpi(nIndex; Integer; dpi: UINT): Integer;
stdcall; external user32 name ‘GetSystemMetricsForDpi’ delayed;

if (TOSVersion.Major >= 10) and (TOSVersion.Build >= 14393) then
begin
NMetric := GetSystemMetricsForDpi (SM_CXBORDER, 96);
end;

参数传递方式

按值传递

1
2
3
4
procedure  ProcName1 (arg1, arg2: Integer; arg3: Double);
begin
// 具体实现
end;

形参列表的参数名前面没有加任何关键字,默认就是 按值传递 方式

引用传递

1
2
3
4
procedure  ProcName2 (var arg1: Integer; var arg2: Double);
begin
// 具体实现
end;

形参中的参数名前面加上 var 关键字,表示该参数是引用传递。形参是实参的引用,对形参的修改,会改变实参的值。

常量传递

1
2
3
4
procedure  ProcName3 (const arg1: Integer);
begin
// 具体实现
end;

形参中的参数名前面加上 const 关键字,表示该参数是作为常量传递。

注: 如果是指针类型,表示指针为常量,不能更改指针的值,但是指针指向的内容可以被修改

输出式传递

形参中的参数名前面加上 out 关键字,表示该参数是输出式参数。类似于 C#out 参数。

1
2
3
4
procedure ProcName4 (s: string; out leng: integer);
begin
leng := length(s);
end;

out 方式的参数与引用传递差不多。有一点点区别:out 参数不用具备初始值,它只用来把数据输出给调用方,调用者在调用前会把实参进行清空。

out 方式的参数常用于 COM 编程。

默认参数

过程与函数的声明中运行使用默认参数:

1
2
3
4
5
6
7
procedure ProcName5 (param1: integer = 100; param2: string = 'ABCD');
begin
leng := length(s);
end;

ProcName5(); // 等于 ProcName5(100, 'ABCD');
ProcName5(200); // 等于 ProcName5(200, 'ABCD');

重载

允许多个同名的过程或者函数,但是参数个数或类型不同。这个是重载,需要加上 overload 指示字:

1
2
function Min (A,B: Integer): Integer; overload;
function Min (A,B: Int64): Int64; overload;
  • 需要加上 overload 指示字
  • 参数个数或者类型必须有不同
  • 注意不要与默认参数的过程或函数有冲突

inline 内联

内联常常用于优化程序运行速度。在调用的时候,把过程或函数的代码直接内嵌到调用方进行展开,省去了调用的流程开支。

1
2
3
function add(X, Y: integer): integer; inline;
begin
end;

使用 inline 指示字声明为内联优化模式。

特殊参数

无类型参数

无类型参数不能传值,只能指定 constvarout 方式来传递。

1
2
3
4
procedure func1(var param1; typeFlag: integer);
begin
// 对 param1 进行类型转换再使用
end;

无类型参数在过程或者函数中处理时,需要进行显式的类型转换后才能继续使用。

可以理解为调用方传递了一个无类型的指针,然后在过程或者函数处理过程中,先进行指针指向内容的类型转换,再进行处理。

短字符串参数

声明方法:

  • 一是直接将变量声明为 ShortString 类型;
  • 二是利用 string 后限定长度从而定义特定长度的字符串,如 string[9]
1
2
3
4
procedure do1(var S1: ShortString);
//
type MyString = string[20];
procedure do2(var S2: MyString);

短字符串参数实际上是字符数组参数。请参考下面的数组参数

数组参数

静态数组参数

数组传参不能直接指定数组的大小索引值等,例如:

1
procedure do1(var a: array[0..9] of integer);    // 编译不通过

应该用以下方式:

1
2
type MyArrayType = array[0..9] of integer;
procedure do1(var a: MyArrayType); // 编译可以通过

开放数组参数

开放数组参数,就是参数类型是 array 并不指定数组的大小:

1
procedure do2(var a: array of integer);

动态数组参数,就是先定义动态数组的类型,再声明过程或者函数:

1
2
3
type MyArrayType = array of integer;    // 先声明动态数组
procedure do3(var a: MyArrayType); // 再声明参数类型
// 注: (命名类型数组不再是开放数组)

开放数组参数和动态数组参数很相似,但是有一些细微区别:

  • 动态数组类型的参数只能接纳动态数组
  • 开放数组参数,处理可以接纳动态数组传递,还可以传递静态数组
1
2
3
4
5
6
7
8
var s1 : array[0..100] of integer;

procedure do2(var a: array of integer);
begin
// 过程实现代码
end;

do2(s1); // 可以传递静态数组给开放数组参数

无类型开放数组参数

无类型开放数组参数,是指参数是一个数组,其数组长度不确定,数组的每一项的类型也不确定。

声明方式如下:

1
procedure do2(const a: array of const);

Win32下实际是 array of TVarRec

Exit 和 Halt

Exit

Exit 用于退出当前过程或函数。相当于 C 语言的 return 语句。

在函数中,赋值给 Result 变量并不会退出函数,只有调用 Exit 内置过程可以退出函数体的执行。

在函数中,Exit 允许带一个参数,用于函数的返回值,传递参数必须与函数返回值类型兼容。

1
2
3
4
5
6
7
8
function function_name(aInteger: integer): string;
begin
if aInteger < 0 then
Exit('Negative') // Exit 可以退出函数并带上返回值
else
Result := 'Positive'; // Result 只能声明返回值,并不能退出函数,只能等函数自然返回
end;
end;

Halt

Halt 用于退出当前整个程序。相当于 C 语言的 exit() 函数。

Halt 过程带一个整数类型的参数,用于告知程序退出码。

1
procedure Halt(ExitCode:Integer = 0);

Terminate

调用 Application.Terminate 过程也可以退出当前程序

程序退出码

ExitCode

ExitCode 是一个全局变量,它用于保存程序的退出码。默认值为 0,程序结局运行后,表示当前程序没有发生任何错误正常退出。它定义在 System 单元里。

ExitProc

ExitProc 是一个全局变量,它定义在 System 单元里,用于保存当 exe 或者 dll 退出时,自动执行一个无参数的过程。EXE 退出或者 DLL 被卸载时,先执行 ExitProc 指向的过程,再执行 Finalization 的部分。

调用约定

32位 Windows 下函数对用有多种约定,分别有:

  • (1)register (fastcall方式,默认)
  • (2)stdcall
  • (3)cdecl
  • (4)safecall 规定被 COM 调用的函数所必须遵守的兼容规则
  • (5)pascal
1
procedure func1(var param1; typeFlag: integer); forward; stdcall;
调用约定 参数传递顺序 清理方 是否用寄存器
register 未定义 过程、函数自身清理
pascal 从左到右 过程、函数自身清理
cdecl 从右到左 调用方
stdcall 从右到左 过程、函数自身清理
safecall 从右到左 过程、函数自身清理

varargs

varargs 指示字用于声明与 C 兼容的可变参数。varargs 必须与 cdecl 联用。

1
function printf(Format: PChar): Integer; cdecl; varargs;

过程类型

直接定义过程类型的变量,例如:

1
2
3
var F1: function(x: integer; y: double): integer;
var F2: function(y: double; x:integer): integer;
var F3: procedure(x: integer; y: double);

另一种方式,先声明类型别名,再定义变量:

1
2
type TIntProc = procedure (var Num: Integer);
var myProc: TIntProc;

将其他过程赋值给过程类型的变量,并通过过程类型的变量进行调用:

1
2
3
4
5
6
7
8
type TIntProc = procedure (var Num: Integer);
var myProc: TIntProc;

procedure proc1(var Num: Integer); external;

myProc := proc1; // 将 proc1 过程赋值给变量 myProc

myProc(100); // 调用

完全可以把变量名当作就是过程名或函数名。

注: 过程类型的变量,通过 @ 获取到的是过程代码的地址,使用 @@ 才能获取变量本身的地址。

匿名方法

匿名方法就是过程或函数不指定具体名称,直接给出实现。

例程引用

例程可以统称过程、函数、对象中的方法

声明例程类型举例如下:

1
2
3
type 
TProc1 = reference to procedure (N: Integer);
TFunc1 = reference to function (x: integer): integer;

匿名方法

普通的过程、函数、方法可以直接赋值给例程引用类型的变量,除外,更可以将一个匿名方法赋值给例程引用类型的变量,例如:

1
2
3
4
5
6
7
8
9
10
type 
TProc1 = reference to procedure (N: Integer);

var aproc : TProc1;

aproc := procedure (N: Integer) begin
// 匿名方法实现体
end;

aproc(1000); // 通过例程变量调用

匿名方法作实参传递

匿名方法作实参传递给其他过程或函数的形参,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type
AnIntProc = reference to procedure (N: Integer);

procedure CallTwice (Value: Integer; AnIntProc: TIntProc);
begin
AnIntProc (Value);
Inc (Value);
AnIntProc (Value);
end;

procedure TFormAnonymFirst.btnProcParamClick( Sender: TObject);
begin
// 下面把整个匿名方法作为参数传递给 CallTwice
CallTwice (48,
procedure (N: Integer) // 注意这里不加分号
begin
Show (IntToHex (N, 4));
end); // 匿名方法的end后也不加分号
CallTwice (100,
procedure (N: Integer) // 注意这里不加分号
begin
Show (FloatToStr(Sqrt(N)));
end); // 匿名方法的end后也不加分号
end;

变量捕获

匿名方法可以直接使用母过程/函数/方法中的变量,这种叫做变量捕获

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type
AnIntProc = reference to function (N: Integer) : integer;

procedure TFormAnonymFirst.btnProcParamClick(Sender: TObject);
begin
var proc1 : AnIntProc;
var M : integer = 8;
//
proc1 := function(N: Integer) : integer begin
Result := N + M; // 这里匿名函数直接使用了母方法的变量 M
end;
//
proc1(100);
end;
  • 匿名方法只能捕获其父例程中定义的局部变量或形参
  • 编译器内部实际上会创建一个隐藏的框架对象(frame object)来存储捕获的变量
  • 被捕获的对象的生命周期将会延迟到跟框架对象(frame object)一样

exports

exports 语句用于 DLL 导出函数。

格式:

1
exports entryName_1, ... , entryName_n;

导出名称与代码内部函数名称不同时:

1
2
exports
DoSomethingABC name 'DoSomething';

以索引导出:

1
2
exports
DoSomethingABC index 10086; // 极不推荐此方式

重载函数的导出方法:

1
2
3
exports
Divide(X, Y: Integer) name 'Divide_Ints',
Divide(X, Y: Real) name 'Divide_Reals';