本文整理自网络,侵删。
Delphi中的记录可以具有任何数据类型的字段。当一条记录具有普通(非托管)字段(如数字或其他枚举值)时,对于编译器没有太多工作要做。创建和处理记录包括分配内存或摆脱内存位置。
如果记录具有由编译器管理的类型的字段(例如字符串或接口),则编译器需要注入额外的代码来管理初始化或终结。例如,对字符串进行引用计数,因此当记录超出范围时,记录内的字符串需要减少其引用计数,这可能导致为该字符串取消分配内存。因此,当您在代码的一部分中使用这种托管记录时,编译器会自动在该代码周围添加一个try-finally块,并确保即使出现异常也清除了数据。长期以来就是这种情况。换句话说,托管记录已成为Delphi语言的一部分。
带有初始化和终结运算符的记录除了编译器对托管记录所做的默认操作之外,Delphi记录类型还支持自定义的初始化和完成。您可以使用自定义的初始化和完成代码声明记录,而不管其字段的数据类型如何,并且可以编写此类自定义的初始化和完成代码。这可以通过在记录类型中添加特定的新运算符来实现(如果需要,可以有一个运算符而没有另一个)。以下是一个简单的代码段:
type TMyRecord = record Value: Integer; class operator Initialize (out Dest: TMyRecord); class operator Finalize (var Dest: TMyRecord); end;请记住,您需要为两个类方法都编写代码。例如,当记录它们的执行或初始化记录值时,我们还将记录对内存位置的引用,以查看哪个记录正在执行每个单独的操作:
class operator TMyRecord.Initialize (out Dest: TMyRecord);begin Dest.Value := 10; Log('created' + IntToHex (Integer(Pointer(@Dest))));end;
class operator TMyRecord.Finalize (var Dest: TMyRecord);begin Log('destroyed' + IntToHex (Integer(Pointer(@Dest))));end;这种构造与以前可用于记录的构造之间的巨大差异是自动??调用。如果您编写类似下面的代码的代码,则可以调用Initializer和Finalizer,最后得到由编译器为您的托管记录实例生成的try-finally块。
procedure LocalVarTest;var my1: TMyRecord;begin Log (my1.Value.ToString);end;使用此代码,您将获得如下日志:
created 0019F2A8
10
destroyed 0019F2A8
另一种情况是使用内联变量,例如:
begin var t: TMyRecord; Log(t.Value.ToString);这会使您在日志中获得相同的顺序。
赋值运算符:=分配复制记录字段的所有数据。尽管这是一个合理的默认值,但是当您具有自定义数据字段和自定义初始化时,您可能想要更改此行为。因此,对于“自定义托管记录”,您还可以定义一个赋值运算符。使用:=语法调用new运算符,但将其定义为Assign:
type TMyRecord = record Value: Integer; class operator Assign (var Dest: TMyRecord; const [ref] Src: TMyRecord);运算符定义必须遵循非常精确的规则,包括将第一个参数作为参考参数,将第二个参数作为通过引用传递的const。如果不这样做,则编译器将发出如下错误消息:
[dcc32 Error] E2617 First parameter of Assign operator must be a var parameter of the container type
[dcc32 Hint] H2618 Second parameter of Assign operator must be a const[Ref] or var parameter of the container type
有一个示例案例,调用了Assign运算符:
var my1, my2: TMyRecord;begin my1.Value := 22; my2 := my1;生成此日志(记录中包含序列号):
created 5 0019F2A0
created 6 0019F298
5 copied to 6
destroyed 6 0019F298
destroyed 5 0019F2A0
注意,破坏顺序与构造顺序相反。
像上面的示例一样,如果将赋值运算符与赋值操作结合使用,也可以将赋值运算符与初始化内联变量一起使用。这里有两种不同的情况:
var my1: TMyRecord;begin var t := my1; Log(t.Value.ToString);
var s: TMyRecord; Log(s.Value.ToString);该日志:
created 6 0019F2A8
created 7 0019F2A0
6 copied to 7
10
created 8 0019F298
10
destroyed 8 0019F298
destroyed 7 0019F2A0
destroyed 6 0019F2A8
在第一种情况下,创建和分配就像在具有非局部变量的常规方案中一样发生。在第二种情况下,只有定期初始化。
将托管记录作为参数传递当作为参数传递或由函数返回时,托管记录也可以不同于常规记录。以下是显示各种情况的例程:
procedure ParByValue (rec: TMyRecord);procedure ParByConstValue (const rec: TMyRecord);procedure ParByRef (var rec: TMyRecord);procedure ParByConstRef (const [ref] rec: TMyRecord);function ParReturned: TMyRecord;每个日志执行以下操作:
ParByValue创建一个新记录,并调用赋值运算符(如果有)来复制数据,并在退出过程时销毁临时副本。ParByConstValue不进行复制,也不进行任何调用。ParByRef不进行复制,不进行调用。ParByConstRef不进行复制,不进行调用。ParReturned创建一个新记录(通过Initialize),并在返回时调用Assign运算符(如果调用如下),并删除临时记录:my1 := ParReturned;例外和托管记录当引发异常时,与对象不同,即使没有显式的try,finally块存在,通常也会清除记录。这是根本的区别,也是托管记录真正有用的关键。
procedure ExceptionTest;begin var a: TMRE; var b: TMRE; raise Exception.Create('Error Message');end;在此过程中,有两个构造函数调用和两个析构函数调用。同样,这是托管记录的根本区别和关键特征。有关基于托管记录的简单智能指针,请参见下一部分。
另一方面,如果在托管记录的初始化程序中引发了异常,则与常规对象不同,不会调用匹配的析构函数。
托管记录数组如果定义托管记录的静态数组,则会在点声明处进行初始化,以调用Initialize运算符:
var a1: array [1..5] of TMyRecord; // call herebegin Log ('ArrOfRec');他们超出范围时都被摧毁。如果定义托管记录的动态数组,则将使用数组大小??(带有SetLength)的数组调用初始化代码:
var a2: array of TMyRecord;begin Log ('ArrOfDyn'); SetLength(a2, 5); // call here
http://www.delphifmx.com/node/74
相关阅读 >>
Delphi keydown与keyup、keypress的区别
Delphi自动以管理员身份在vista 和 windows7 下运行程序
用Delphi通过setupapi.dll列举和停用硬件设备
更多相关阅读请进入《Delphi》频道 >>