DELPHI学习---结构类型

Structured types (结构类型)

结构类型的一个实例可包含多个值。结构类型包括集合、数组、记录,也包括类、类引用(class-reference)

和接口类型。除了集合只能包含有序值以外,结构类型可以包含其它的结构类型,且结构的层次不受限

制。

默认情况下,一个结构类型的值被圆整为字(word)或者双字(double-word),这样访问起来更迅速。

当声明一个结构类型时,可以包含关键字packed,这将对数据的存储进行压缩(并不是压缩,只是不再

圆整数据,而保留它的自然结构)。比如:

type TNumbers = packed array[1..100] of Real;

使用packed 使数据访问变慢,并且在使用字符数组的情况下,能影响类型兼容性。

{*-----------------------------------------------------------------------------------------

Packed关键字是与编译有关的!!!

在默认情况下,Delphi编译器是对数据进行优化的!!!

例如:

TX = Record

A : Char;

B : Integer;

End;

在这个记录中,TX的大小应该是5个字节,但是由于Delphi的优化,Tx的大小变成了8个字节,所以为了得到

TX的真实大小,这个时候就要使用Packed关键字了,禁止进行优化!!!一般Packed关键字在使用C++/C的

API汉书中用得比较多,一般Delphi内部自己的程序不需要使用Packed关键字,除非你想得到某个对象的真实大小!!!

为什么优化:

举一个现实生活中的例子,假设有两个杯子,一个装满了水,另一个只装了1/3的水,那么实际物理上这些水

占用了两个杯子,因为我们不能说占了4/3的杯子,因为我们没办法得到2/3的杯子。在这一杯子就是CPU中的寄

存器,TX对象中的A,虽然只有一个字节,但它却占用了一个寄存器的空间(4个字节,这就是32位计算机,如果

是64位计算机,一个寄存器的大小将变成8个字节),所以经过优化的结果是TX的大小是8。优化的数据可以提高

执行效率,因为我们不必关心杯子里到底有多少水,1/4?1/3?1/2...,我们只要关心那些水占据了一个杯子,

这样会节省大量的分析杯子里有多少水的时间!!!这就是Packed的作用!!!

加Packed就是为了

1、使用sizeOf得到记录的真实大小。

2、节省内存空间。

3、保证与其他程序通讯的正确性。

....

比如你编写一个通讯程序,定义的记录大小为10用来存储现金,如果这时你让Delphi优化

了这个记录,天知道金额会变成什么!!!

packed record

是压缩记录类型

----------------------------------------------------------------------------------------*}

Sets(集合)

集合是同一种有序类型的值的聚集,它们包含的值没有内在的顺序,且一个值在集合中包含两次并没有

实际意义。

一个集合类型的取值范围,是构成它的有序类型(称为基础类型)的幂,也就是说,集合可能的值是基

础类型的所有子集,也包含空集。基础类型可能的值不要超过256 个,并且它们的序数必须在0 到255

之间。任何像下面的形式:

set of baseType

声明一个集合类型,这里,baseType 是一个合适的有序类型。

因为基础类型的值是有限的,因此,集合类型通常使用子界类型来定义。比如下面的声明:

type

TSomeInts = 1..250;

TIntSet = set of TSomeInts;

它声明一个叫做TIntSet 的集合类型,它的值是从1 到250 之间所有可能的选择。你也可以使用下面的语

句达到同样的目的:

type TIntSet = set of 1..250; //用set of 的形式定义

有了上面的声明,你就可以像下面这样构造集合了:

var Set1, Set2: TIntSet;

...

Set1 := [1, 3, 5, 7, 9]; //集合用[]来赋值

Set2 := [2, 4, 6, 8, 10]

你也可以直接使用set of …构造直接声明变量:

var MySet: set of 'a'..'z';

...

MySet := ['a','b','c'];

其它集合类型的实例包括:

set of Byte

set of (Club, Diamond, Heart, Spade)

set of Char;

运算符in 判断集合的成员关系:

if 'a' in MySet then ... { do something } ;

每个集合类型可包含空集,用[]来表示。

Arrays(数组)

一个数组是由相同类型的(称为基础类型)、经过索引的元素组成的聚集。因为每个元素有唯一的索引,

所以,数组和集合不同,它可以包含多个相同的值。数组可以静态分配内存,也可以动态分配。

Static arrays(静态数组)

静态数组类型以下面的格式声明:

array[indexType1, ..., indexTypen] of baseType

这里,每个indexType 是有序类型并且范围不超过2G。因为indexType 是数组的索引,所以,数组包含

的元素个数由indexType 的范围限定。在实际应用中,indexType 通常是整数子界类型。

最简单的是一维数组,它只有一个indexType,比如:

var MyArray: array[1..100] of Char;

声明了一个变量MyArray,它是一个有100 个字符的数组。给定上面的声明,MyArray[3]表示数组中的

第3 个字符。若声明了一个静态数组,虽然并没有给每一个元素赋值,但未用的元素仍分配内存并包含

一个随机值,这和未初始化的变量类似。

A multidimensional array is an array of arrays. For example,

一个多维数组是数组的数组,比如:

type TMatrix = array[1..10] of array[1..50] of Real;

就等价于

type TMatrix = array[1..10, 1..50] of Real;

不论用哪种方式声明,它表示一个有500 个实数值的数组。一个TMatrix 类型的变量MyMatrix,可使用

这样的索引:MyMatrix[2,45],或像这样:MyMatrix[2][45]。同样,

packed array[Boolean,1..10,TShoeSize] of Integer;

就等价于

packed array[Boolean] of packed array[1..10] of packed array[TShoeSize] of

Integer;

标准函数Low 和High 作用于数组类型(的标志符)或变量,它们返回数组第1 个索引(类型)的最小

值和最大值;Length 返回数组第1 维的元素个数。

一维、

一维、压缩的(packed)、Char 类型的静态数组称为packed string,它和字符串类型兼容,也和其它具有

相同元素个数的packed string 兼容。请参考Type compatibility and identity。

array[0..x] of Char 类型的数组,是0 下标开始的字符数组,它用来存储零结尾字符串,并且和PChar 类

型兼容。参考Working with null-terminated strings。

Dynamic arrays(动态数组)

动态数组没有固定大小和长度,相反,当你给它赋值或把它传给SetLength 函数时,它的内存被重新分

配。动态数组以下面的形式声明:

array of baseType

比如

var MyFlexibleArray: array of Real;

声明一个实数类型的一维动态数组。声明并没有为MyFlexibleArray 分配内存,要在内存中创建数组,要

调用SetLength。比如,以上面的声明为例:

SetLength(MyFlexibleArray, 20);

分配一个由20 个实数构成的数组,索引号从0 到19。动态数组的索引总是整数,并从0 开始。动态数

组变量实际是指针,并和长字符串一样使用引用计数进行管理。要取消动态数组的分配,给它的变量赋

值nil,或者把变量传给Finalize。在没有其它引用的情况下,这两种方法都将消除数组。0 长度动态数

组的值为nil。不要对一个动态数组变量使用运算符‘^’,也不要对它使用New 或Dispose 过程。

若X 和Y 是同一类型的动态数组变量,X := Y 使X 指向和Y 相同的数组(在这个操作之前,不必给X

分配内存)。不像字符串和静态数组,动态数组不会在被写之前自动拷贝。比如,在下面的代码执行后

var

A, B: array of Integer;

begin

SetLength(A, 1);

A[0] := 1;

B := A;

B[0] := 2;

end;

A[0]的值是2(若A 和B 是静态数组,A[0]仍然是1)。

使用索引给动态数组赋值(比如,MyFlexibleArray[2] := 7),不会为数组重新分配内存;编译时,索引

边界检查也不会给出提示。

当比较动态数组变量时,是比较它们的引用(这里的值是一个地址),而不是它们的值。所以,下面的代

码执行后

var

A, B: array of Integer;

begin

SetLength(A, 1);

SetLength(B, 1);

A[0] := 2;

B[0] := 2;

end;

A = B 返回False,但A[0] = B[0]返回True。

要截断一个动态数组,把它传给SetLength 或Copy,并把返回的值赋给数组变量(SetLength 通常更快)。

比如,若A 是一个动态数组,执行A := SetLength(A, 0, 20),除A 的前20 个元素外,其它都将被截取掉。

一旦一个动态数组被分配内存,你可以把它传给几个标准函数:Length、High 和Low。Length 返回数组

的元素个数,High 返回最大数组的最大索引(也就是Length-1),Low 返回0。对于长度为0 的数组,

High 返回-1(得到反常结果High < Low)。

注意:有些函数或过程在声明时,数组参数表示为array of baseType,没有指明索引类型。比如,

function CheckStrings(A: array of string): Boolean;

这表明,函数可用于(指定的)基础类型的所有数组,而不管它们的大小和索引,也不管它们是静态分

配还是动态分配。请参考Open array parameters。

Multidimensional dynamic arrays(多维动态数组)

要声明多维动态数组,使用复合array of ... 结构,比如,

type TMessageGrid = array of array of string;

var Msgs: TMessageGrid;

声明一个二维字符串数组。要实例化这个数组,应用两个整数参数调用SetLength。比如,若I 和J 是整

数变量,

SetLength(Msgs,I,J);

给它分配内存,Msgs[0,0]表示它的一个元素。

你也能创建不规则的多维动态数组。第一步是调用SetLength,给它传递参数作为前面的(几个)索引。

比如,

var Ints: array of array of Integer;

SetLength(Ints,10);

为Ints 分配了10 行,但没有分配列。接下来,你能每次分配一个列(给它们指定不同的长度),比如,

SetLength(Ints[2], 5);

使Ints 的第3 行有5 个元素。此时(即使其它列没有被分配),你能给它的第3 行赋值,比如,Ints[2,4] :=

6。

下面的例子使用动态数组(IntToStr 函数在SysUtils 单元声明)来创建一个字符串的三角形矩阵。

var

A : array of array of string;

I, J : Integer;

begin

SetLength(A, 10);

for I := Low(A) to High(A) do

begin

SetLength(A[I], I);

for J := Low(A[I]) to High(A[I]) do

A[I,J] := IntToStr(I) + ',' + IntToStr(J) + ' ';

end;

end;

Array types and assignments(数组类型和赋值)

Arrays are assignment-compatible only if they are of the same type. Because Pascal uses name-equivalence for

types, the following code will not compile.

只有数组是相同类型时,它们才是赋值兼容的。因为Pascal 使用‘名称’代表‘类型’,所以下面的代

码无法编译:

Data types, variables and constants

- 59 -

var

Int1: array[1..10] of Integer;

Int2: array[1..10] of Integer;

...

Int1 := Int2;

要使赋值能够工作,要如下声明变量

var Int1, Int2: array[1..10] of Integer;

type IntArray = array[1..10] of Integer;

var

Int1: IntArray;

Int2: IntArray;

Records(记录)

记录(类似于其它语言中的结构)表示不同种类的元素的集合,每个元素称为“字段”,声明记录类型时

要为每个字段指定名称和类型。声明记录的语法是

type recordTypeName = record

fieldList1: type1;

...

fieldListn: typen;

end

这里,recordTypeName 是一个有效标志符,每个type 表示一种类型,每个fieldList 是一个有效标志符或

用逗号隔开的标志符序列,最后的分号是可选的。(哪个分号?是最后一个字段的,还是end 后面的?)

比如,下面的语句声明了一个记录类型TDateRec:

type

TDateRec = record

Year: Integer;

Month: (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec);

Day: 1..31;

end;

TDateRec 包含3 个字段:一个整数类型的Year,一个枚举类型的Month,和另一个子界类型的Day。标

志符Year、Month 和Day 是TDateRec 的字段,它们的行为就像变量。声明并不会为Year、Month 和Day

分配内存,只有在实例化时才进行分配,像下面的样子:

var Record1, Record2: TDateRec;

上面的变量声明创建了两个TDateRec 实例,分别叫做Record1 和Record2。

你可以用记录名作限定符、通过字段名来访问字段:

Record1.Year := 1904;

Record1.Month := Jun;

Record1.Day := 16;

或使用with 语句:

with Record1 do

begin

Year := 1904;

Month := Jun;

Day := 16;

end;

现在,你可以把Record1 的值拷贝给Record2:

Record2 := Record1;

因为字段名的范围被限定在记录本身,你不必担心字段名和其它变量发生冲突。

Instead of defining record types, you can use the record ... construction directly in variable declarations:

除了定义记录类型,你也可以使用record ...构造直接声明变量:

var S: record

Name: string;

Age: Integer;

end;

但是,这样不能让你重复使用类型声明,并且,这样声明的类型不是赋值兼容的,即使它们(记录)的

结构完全相同。

Variant parts in records(记录中的变体部分,变体记录)

一个记录类型能拥有变体部分,它看起来就像case 语句,在声明中,变体部分必须跟在其它字段的后面。

要声明一个变体记录,使用下面的语法:

type recordTypeName = record

fieldList1: type1;

...

fieldListn: typen;

case tag: ordinalType of

constantList1: (Variant1);

...

constantListn: (Variantn);

end;

声明的前面部分(直到关键字case)和标准记录类型一样,声明的其余部分(从case 到最后一个可选的

分号,)称为变体部分,在变体部分

???? tag 是可选的,它可以是任何有效标志符。如果省略了tag,也要省略它后面的冒号(:)。

???? ordinalType 表示一种有序类型。

???? 每个constantList 表示一个ordinalType 类型的常量,或者用逗号隔开的常量序列。在所有的常量

中,一个值不能出现多次。

???? 每个Variant 是一个由逗号隔开的、类似于fieldList: type 的声明列表,也就是说,Variant 有下面

的形式:

fieldList1: type1;

...

fieldListn: typen;

这里,每个fieldList 是一个有效标志符,或是由逗号隔开的标志符列表,每个type 表示一种类型,

最后一个分号是可选的。这些类型不能是长字符串、动态数组、变体类型或接口(都属于动态管

理类型),也不能是包含上述类型的结构类型,但它们可以是指向这些类型的指针。

变体记录类型语法复杂,但语义却很简单:记录的变体部分包含几个变体类型,它们共享同一个内存区

域。你能在任何时候,对任何一个变体类型的任何字段读取或写入,但是,当你改变了一个变体的一个

字段,又改变了另一个变体的一个字段时,你可能覆盖了自己的数据。如果使用了tag,它就像记录中

非变体部分一个额外的字段,它的类型是ordinalType。

变体部分有两个目的。首先,假设你想创建这样一个记录:它的字段有不同类型的数据,但你知道,在

一个(记录)实例中你永远不需要所有的字段,比如:

type

TEmployee = record

FirstName, LastName: string[40];

BirthDate: TDate;

case Salaried: Boolean of

True: (AnnualSalary: Currency);

False: (HourlyWage: Currency);

end;

这里的想法是,每个雇员或者是年薪,或者是小时工资,但不能两者都有。所以,当你创建一个TEmployee

的实例时,没必要为每个字段都分配内存。在上面的情形中,变体间的唯一区别在于字段名,但更简单

的情况是字段拥有不同的类型。看一下更复杂的例子:

type

TPerson = record

FirstName, LastName: string[40];

BirthDate: TDate;

case Citizen: Boolean of

True: (Birthplace: string[40]);

False: (Country: string[20];

EntryPort: string[20];

EntryDate, ExitDate: TDate);

end;

type

TShapeList = (Rectangle, Triangle, Circle, Ellipse, Other);

TFigure = record

case TShapeList of

Rectangle: (Height, Width: Real);

Triangle: (Side1, Side2, Angle: Real);

Circle: (Radius: Real);

Ellipse, Other: ();

end;

对每个记录类型的实例,编译器分配足够的内存以容纳最大变体类型的所有字段。可选的tag 和

constantLists(像上面例子中的Rectangle、Triangle 等)对于编译器管理字段没有任何作用,它们只是为

了程序员的方便。

使用变体记录的第二个原因是,你可以把同一个数据当作不同的类型进行处理,即使在编译器不允许类

型转换的场合。比如,在一个变体类型中,它的第一个字段是64 位实数,在另一个变体类型中,第一个

字段是32 位整数,你可以把一个值赋给实数(字段),然后再当作整数来读取它的前32 位值(比如,把

它传给一个需要整数参数的函数)。

File types(文件类型)

file 是由相同类型的元素组成的有序集合。标准I/O 例程使用内置(预定义)的TextFile 或Text 类型,它

们表示一个包含字符的文件,这些字符是以行的形式进行组织的。想了解更多关于文件输入和输出的信

息,请参考Standard routines and I/O(标准例程和I/O)。

要声明一个文件类型,使用下面的语法:

type fileTypeName = file of type

这里,fileTypeName 是任何有效的标志符,type 是一个固定大小的类型。指针类型(不管是隐含的还是

直接的)是不可以的,所以,文件不能包含动态数组、长字符串、类、对象、指针、变体类型、其它文

件或包含以上类型的结构类型。

比如,

type

PhoneEntry = record

FirstName, LastName: string[20];

PhoneNumber: string[15];

Listed: Boolean;

end;

PhoneList = file of PhoneEntry;

声明了一个记录姓名和电话号码的文件类型。

在声明变量时,你也可以直接使用file of ...结构,比如,

var List1: file of PhoneEntry;

单独的一个file 表示一个无类型文件:

var DataFile: file;

要了解更多信息,请参考Untyped files(无类型文件)。

数组和记录中不能包含文件类型。

Pointers and pointer types(指针和指针类型)

指针是一个表示内存地址的变量。当一个指针包含另一个变量的地址时,我们认为它指向这个变量在内

存中的位置,或指向数据存储的地方。对于数组或其它结构类型,指针指向的是结构中第一个元素的地

址。

指针被类型化以表示在它指定的位置上存储某一类型的数据。Pointer 类型表示一个任意类型的指针,而

指定类型的指针只表示特定类型的数据。指针在内存中占据4 个字节。

要了解指针如何工作,看下面的例子:

1 var

2 X, Y: Integer; // X 和Y 是整数变量

3 P: ^Integer; // P 指向一个整数

4 begin

5 X := 17; // 给X 赋值

6 P := @X; // 把X 的地址赋给P

7 Y := P^; // dereference P;把结果赋给Y

8 end;

第2 行声明X 和Y 为整数类型的变量,第3 行声明P 是一个指向整数的指针,这表明P 可以指向X 或

Y 的存储位置。第5 行把一个值赋给X,第6 行把X 的地址(用@X 表示)赋给P。最后,在第7 行,

取得P 所指位置的值(用P^表示)并把它赋给Y。这些代码执行后,X 和Y 有相同的值,即17。

@运算符,这里我们用来取得一个变量的地址,它同样可用于函数或过程。

^符号有两个用途,在我们的例子中都用到了。当它出现在一个类型标志符前面时:

^typeName

它表示一个指向typeName 类型的变量的指针;当它出现在一个指针变量的后面时:

pointer^

它表示对指针解除引用,换句话说,它返回在指针所指向的地址处保存的值。

我们的例子看起来是在兜圈子,它只不过是把一个变量的值复制给另一个变量而已,我们完全可以通过

一个简单的赋值语句来完成,但指针有几个用途:首先,理解指针能帮助你理解Object Pascal,因为经

常在代码中虽然没有明确使用指针,但它们却在背地里发挥作用。使用大的、动态分配内存(块)的任

何数据类型都使用指针。例如,长字符串就是一个隐含的指针,类变量也是;此外,一些高级的编程技

术需要使用指针。

最后,指针有时是跳过Object Pascal 严格的(数据)类型匹配的唯一方法。使用一个通用指针(Pointer)

来引用一个变量,并把它转换为其它类型,然后重新引用它,这样你就可以把它作为任何类型对待。比

如,下面的代码把一个实数变量的值赋给一个整数变量。

type

PInteger = ^Integer;

var

R: Single;

I: Integer;

P: Pointer;

PI: PInteger;

begin

...

P := @R;

PI := PInteger(P);

I := PI^;

end;

当然,实数和整数有不同的存储格式,上面的赋值只简单地把R 的二进制数据赋给I,并不是实际转换。

除了使用@运算符,你也可以使用几个标准例程给一个指针赋值。New 和GetMem 过程把一个内存地址

赋给指针,而Addr 和Ptr 函数则返回一个指向特定变量或地址的指针。

像P1^.Data^表示的那样,对指针解除引用可用作限定符,也可以被限定。

保留字nil 是一个特殊常量,可赋给任何指针(类型)。当nil 被赋给一个指针时,指针不表示任何东西。

Pointer types(指针类型)

使用下面的语法,你能声明一个任意类型的指针,

type pointerTypeName = ^type

当定义一个记录类型(或其它数据类型)时,习惯上也就定义了一个此类型的指针,这使得处理更容易,

我们不需要拷贝一大块内存。

标准指针类型有许多理由存在,最通用的是Pointer,它可以指向任何数据类型,但不能对它解除引用,

在Pointer 类型变量的后面使用^运算符会引发编译错误。要访问一个Pointer 类型引用的变量,首先把它

转换为其它指针类型,然后再解除引用。

Character pointers(字符指针)

基本(fundamental)类型PAnsiChar 和PWideChar 分别表示AnsiChar 和WideChar 值的指针,一般(generic)

类型PChar 表示一个指向Char 的指针(在当前实现中,它表示AnsiChar)。这些字符指针用来操纵零结

尾字符串(参考Working with null-terminated strings)。

Procedural types(过程类型)

过程类型允许你把过程和函数作为“值”看待,它可以赋给变量或传给其它过程和函数。比如,假设你

定义了一个叫做Calc 的函数,它有两个整型参数并返回一个整数值:

function Calc(X,Y: Integer): Integer;

你可以把Calc 函数赋给变量F:

var F: function(X,Y: Integer): Integer;

F := Calc;

我们只取过程或函数头(heading)并把procedure 或function 后面的标志符去掉,剩下的就是过程类型

的名称。你可以在声明变量时直接使用这样的名称(就像上面的例子一样),也可以声明新类型:

type

TIntegerFunction = function: Integer;

TProcedure = procedure;

TStrProc = procedure(const S: string);

TMathFunc = function(X: Double): Double;

var

F: TIntegerFunction; {F 是一个无参数、返回整数值的函数}

Proc: TProcedure; { Proc 是一个无参数过程}

SP: TStrProc; { SP 是一个使用string 类型参数的过程}

M: TMathFunc; { M 是一个使用Double 类型参数、返回Double 值的函数}

procedure FuncProc(P: TIntegerFunction); { FuncProc 是一个过程,它的参数是

一个无参数、返回整数值的函数}

上面的所有变量都是过程指针,也就是指向过程或函数地址的指针。若想引用一个实例对象的方法(参

考Classes and objects),你需要在过程类型的名称后面加上of object。比如

type

TMethod = procedure of object;

TNotifyEvent = procedure(Sender: TObject) of object;

这些类型表示方法指针。方法指针实际上是一对指针:第一个存储方法的地址,第二个存储方法所属的

对象的引用。给出下面的声明

type

TNotifyEvent = procedure(Sender: TObject) of object;

TMainForm = class(TForm)

procedure ButtonClick(Sender: TObject);

...

end;

var

MainForm: TMainForm;

OnClick: TNotifyEvent

我们就可以进行下面的赋值:

OnClick := MainForm.ButtonClick;

两个过程类型是兼容的,如果它们具有

???? 相同的调用约定,

???? 相同类型的返回值(或没有返回值),并且具有

???? 相同数目的参数,并且相应位置上的类型也相同(参数名无关紧要)

过程指针和方法指针是不兼容的。nil 可以赋给任何过程类型。

嵌套的过程和函数(在其它例程中声明的例程)不能被用作过程类型值,内置的过程和函数也不可以。

若想使用内置的过程作为过程类型值,比如Length,你可以给它加一个包装:

function FLength(S: string): Integer;

begin

Result := Length(S);

end;

Procedural types in statements and expressions(语句和表达式中的

过程类型)

当一个过程变量出现在赋值语句的左边时,编译器期望右边是一个过程类型值。赋值操作把左边的变量

当作指针,它指向右边指示的过程或函数。但在其它情形,使用过程变量将调用它引用的过程或函数,

你甚至可以对过程变量传递参数:

var

F: function(X: Integer): Integer;

I: Integer;

function SomeFunction(X: Integer): Integer;

...

F := SomeFunction; // 把SomeFunction 赋给F

I := F(4); // 调用函数,把结果赋给I

在赋值语句中,左边变量的类型决定右边的过程(或方法)指针的解释,比如

var

F, G: function: Integer;

I: Integer;

function SomeFunction: Integer;

...

F := SomeFunction; // 把SomeFunction 赋给F

G := F; // 拷贝F 到G

I := G; // 调用函数,把结果赋给I

第1 个赋值语句把一个过程类型值赋给F,第2 个语句把这个值拷贝给另一个变量,第3 个语句调用引

用的函数并把结果赋给I。因为I 是一个整数变量,而不是过程类型,所以最后的赋值实际上是调用函数

(它返回一个整数值)。

在一些情况下,如何解释过程变量并不是很清楚,看下面的语句

if F = MyFunction then ...;

在此情况下,F 导致函数调用:编译器调用F 指向的函数,然后调用函数MyFunction,然后比较结果。

规则是,只要过程变量出现在表达式中,它就表示是调用引用的过程或函数。在上面的例子中,若F 引

用一个过程(没有返回值),或F 引用的函数需要参数,则引发编译错误。要比较F 和MyFunction 的过

程值,使用

if @F = @MyFunction then ...;

@F 把F 转换为无类型指针变量,它包含的是地址,@MyFunction 返回的是MyFunction 的地址。

要取得过程变量的内存地址(而不是它包含的地址),使用@@。比如,@@F 返回F 的地址。

@运算符也可以用来把一个无类型指针值赋给过程变量,比如

var StrComp: function(Str1, Str2: PChar): Integer;

...

@StrComp := GetProcAddress(KernelHandle, 'lstrcmpi');

调用GetProcAddress 函数,并使StrComp 指向结果。

过程变量可以是nil 值,但此时调用它会引发错误。要测试一个过程变量是否被赋值,使用标准函数

Assigned:

if Assigned(OnClick) then OnClick(X);

Declaring types(声明类型)

一个类型声明指定一个标志符,来表示一种数据类型。类型声明的语法为

type newTypeName = type

这里,newTypeName 是一个有效的标志符。比如,给定如下的类型声明

type TMyString = string;

你就可以声明变量

var S: TMyString;

一个类型标志符的范围不能包含类型声明本身(指针类型除外),所以举例来说,你不能在声明一个记录

时循环使用它。

当声明一个和已有类型完全相同的类型时,编译器把它看作是已有类型的别名。这样,在下面的声明中

type TValue = Real;

var

X: Real;

Y: TValue;

X 和Y 属于相同的类型。在运行时,没有办法区分TValue 和Real 类型。这通常有一些意义,但如果你

定义新类型的目的是利用RTTI(Runtime Type Information),比如,给某个类型赋一个属性编辑器,区

分‘不同名称’和‘不同类型’就变得重要了。这种情况下,你使用语法

type newTypeName = type type

例如

type TValue = type Real;

这将强制编译器创建一个不同的新类型TValue。