(delphi11最新学习资料) Object Pascal 学习笔记---第5章第3节(自定义托管记录)

5.3.5 运算符和自定义托管记录

​ 在 Delphi 语言中,有一组特殊的运算符可用于记录,以定义自定义托管记录。在此之前,请允许我回顾一下记录内存初始化的规则,以及普通记录和托管记录之间的区别。

​ Delphi 中的记录可以包含任何数据类型的字段。当记录具有普通(非托管)字段(如数值或其他枚举值)时,编译器无需做太多工作。创建和处置记录只需分配或释放内存区域即可。(请注意,默认情况下,Delphi 不会对记录进行零初始化,但会对数组进行零初始化,正如我们稍后将学习的,也会对新对象实例进行零初始化)。

​ 如果记录的字段属于编译器管理的类型(如字符串或接口),编译器需要注入额外的代码来管理初始化或终止。例如,字符串是有引用计数的,因此当记录超出作用域时,记录中的字符串需要减少其引用计数,这可能会导致为字符串去释放内存。因此,当你在某部分代码使用托管记录时,编译器会自动在代码周围添加一个 try-finally 块,以确保即使出现异常也能清除数据。长期以来,Delphi 语言中的托管记录一直是这种情况。

​ 从 10.4 开始,除了编译器为托管记录执行的默认操作外,Delphi 记录类型还支持自定义初始化(initialization)和终止化(finalization)。无论记录字段的数据类型如何,您都可以声明带有自定义初始化和最终化代码的记录,也可以编写此类自定义初始化和最终化代码。这些记录被称为 “自定义托管记录”。

​ 开发人员可以通过在记录类型中添加一个或多个特定的新操作符,将记录转化为自定义托管记录:

  • Initialize 运算符在为记录分配内存后调用,允许您编写代码来设置字段的初始值
  • Finalize 运算符在为记录释放内存之前调用,允许您执行任何必要的清理
  • Assign 运算符在将记录数据复制到相同类型的另一条记录时调用,因此您可以以自定义方式从一条记录复制信息到另一条记录

注解:由于托管记录的清理即使在发生异常时也会执行(编译器会自动生成try-finally块),它们通常被用作保护资源分配或执行清理操作的替代方式。我们将在第9章的“使用托管记录还原光标”部分中看到此用法的示例。

记录的 InitializeFinalize 运算符

​ 我们用以下简单的代码片段介绍初始化和终止化:

type
  TMyRecord = record
    Value: Integer;
    class operator Initialize(out Dest: TMyRecord);
    class operator Finalize(var Dest: TMyRecord);
  end;

​ 当然,您需要为这两个类方法编写代码,例如,可以记录其执行情况或初始化记录的 Value 字段。在本例(ManagedRecords_101 示例项目的一部分)中,我对 Value 字段进行了初始化,并记下了对内存位置的引用,以便查看执行每个操作的记录:

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;

​ 这种构造机制与以前的记录机制的区别在于自动调用。如果你编写了类似下面的代码,你就可以同时调用初始化和终止化代码,最后由编译器为你的托管记录实例生成一个 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);

这将在日志中产生相同的序列。

赋值运算符

​ 一般来说,赋值操作符(:=)会直接复制记录字段的所有数据。编译器也会正确处理具有托管类型(如字符串)的记录。

​ 如果有自定义数据字段和自定义初始化,则您可能需要更改默认行为。因此,您也可以为自定义托管记录定义赋值操作符。新操作符使用 := 语法调用,但定义为 Assign

class operator Assign(var Dest: TMyRecord; const [ref] Src: TMyRecord);

运算符定义必须遵循非常精确的规则,包括第一个参数必须是引用传递(var)的参数,将第二个参数是引用传递的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

请注意,销毁的顺序与构建的顺序相反,最后创建的记录是第一个销毁的。

将托管记录作为参数传递

​ 托管记录在作为参数传递或由函数返回时,其工作方式也与普通记录不同。下面的几个例程展示了各种情况:

procedure ParByValue(Rec: TMyRecord);
procedure ParByConstValue(const Rec: TMyRecord);
procedure ParByRef(var Rec: TMyRecord);
procedure ParByConstRef(const [ref] Rec: TMyRecord);
function ParReturned: TMyRecord;

现在,无需逐一检查每个日志(您可以运行ManagedRecords_101演示来查看它们),这是信息摘要:

  • 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; // 在这里调用初始化
begin
  Log('ArrOfRec');

当超出作用域时,它们就会被全部销毁。如果定义了托管记录的动态数组,则在调用初始化代码时,要确定数组的大小(使用 SetLength):

var
  A2: array of TMyRecord;
begin
  Log('ArrOfDyn');
  SetLength(A2, 5); // 在这里调用初始化
  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: Object Pascal是一种强类型语言,对数据类型的定义、声明以及数据赋值和传递操作等有严格的语法规则。它的数据类型可以分为标准数据类型和高级数据类型,还支持自定义类型。 Delphi是一种基于Object Pascal的编程语言,用于桌面、移动、Web和控制台软件开发。它在1995年首次亮相之前的最初开发过程中,被称为一个未命名的产品的代号。 通过将数据库工具和连接性作为新的Pascal产品的核心部分,Delphi成为了一个强大的数据库产品,并成功挤压了Borland的Pascal工具在市场上与Visual Basic-C竞争的地位。这使得Delphi成为传统Windows开发工具中的领导者,并击败了微软在市场上的主导地位。123 #### 引用[.reference_title] - *1* [【2.Delphi语法基础】2.Object Pascal数据类型](https://blog.csdn.net/chenhaiy/article/details/122929788)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] - *2* *3* [2021年关于Delphi/Object Pascal编程语言的现状和历史](https://blog.csdn.net/xyzhan/article/details/119244320)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值