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。