(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 运算符

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

typeTMyRecord = recordValue: 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);
beginDest.Value := 10;Log('Created' + IntToHex(Integer(Pointer(@Dest)))));
end;class operator TMyRecord.Finalize(var Dest: TMyRecord);
beginLog('Destroyed' + IntToHex(Integer(Pointer(@Dest)))));
end;

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

procedure LocalVarTest;
varMy1: TMyRecord;
beginLog(My1.Value.ToString);
end;

使用上述代码,您将获得类似于以下内容的日志(地址将有所不同):

Created 0019F2A8
10
Destroyed 0019F2A8

另一个场景是使用内联变量,例如:

beginvar 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 运算符的一个示例:

varMy1, My2: TMyRecord;
beginMy1.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;
beginvar A: TMRE;var B: TMRE;raise Exception.Create('Error Message');
end;

​ 在这个过程里,有两次构造函数调用和两次析构函数调用。同样,这也是托管记录的根本区别和关键特征。

托管记录的数组

​ 如果定义了托管记录的静态数组,则会在声明时调用 Initialize 操作符对其进行初始化:

varA1: array[1..5] of TMyRecord; // 在这里调用初始化
beginLog('ArrOfRec');

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

varA2: array of TMyRecord;
beginLog('ArrOfDyn');SetLength(A2, 5); // 在这里调用初始化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/696982.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大语言模型LangChain本地知识库:向量数据库与文件处理技术的深度整合

文章目录 大语言模型LangChain本地知识库:向量数据库与文件处理技术的深度整合引言向量数据库在LangChain知识库中的应用文件处理技术在知识库中的角色向量数据库与文件处理技术的整合实践挑战与展望结论 大语言模型LangChain本地知识库:向量数据库与文件…

【Unity】MySql +Navicat 安装教程

问题描述 在使用Unity开发的时候,有的时候我们是需要使用Mysql数据库的,本教程使用的MySql 和Navicat均为免安装版 ❶mysql安装 1.下载mysql解压至任意目录,此处以“C:\mysql-5.6.39-winx64”为例. mysql百度云连接: 链接&…

Java的递归【详解】

1.认识递归基础知识 什么是方法递归? 递归是一种算法,在程序设计语言中广泛应用。 从形式上说:方法调用自身的形式称为方法递归( recursion)。 递归的形式: 直接递归:方法自己调用自己。 间接递…

【监控】Spring Boot+Prometheus+Grafana实现可视化监控

目录 1.概述 2.spring actuator 3.Prometheus 3.1.介绍 3.2.使用 1.client端的配置 2.server端的配置 4.grafana 5.留个尾巴 1.概述 本文是博主JAVA监控技术系列的第四篇,前面已经聊过了JMX、Spring actuator等技术,本文我们就将依托于Spring …

利用docker一键部署LLaMa到自己的Linux服务器,有无GPU都行、可以指定GPU数量、支持界面对话和API调用,离线本地化部署包含模型权重合并

利用docker一键部署LLaMa到自己的Linux服务器,有无GPU都行、可以指定GPU数量、支持界面对话和API调用,离线本地化部署包含模型权重合并。两种方式实现支持界面对话和API调用,一是通过搭建text-generation-webui。二是通过llamma.cpp转换模型为转换为 GGUF 格式,使用 quanti…

Leetcode日记 889. 根据前序和后序遍历构造二叉树

Leetcode日记 889. 根据前序和后序遍历构造二叉树 给定两个整数数组,preorder 和 postorder ,其中 preorder 是一个具有 无重复 值的二叉树的前序遍历,postorder 是同一棵树的后序遍历,重构并返回二叉树。 如果存在多个答案&#…

【Flink集群RPC通讯机制(三)】AkkaRpcActor设计与实现:接收RPC消息以及处理逻辑

文章目录 1. 创建Receiver2. 进行消息处理 RPC请求发送后接收方的处理逻辑 在RpcEndpoint中创建的RemoteRpcInvocation消息,最终会通过Akka系统传递到被调用方。例如TaskExecutor向ResourceManager发送SlotReport请求的时候,会在TaskExecutor中将Resourc…

petalinux_zynq7 驱动DAC以及ADC模块之二:petalinux

petalinux_zynq7 C语言驱动DAC以及ADC模块之一:建立IPhttps://blog.csdn.net/qq_27158179/article/details/136234296在上一篇,建立了ADC和DAC两个IP。这里继续。本文在 petalinux默认配置的基础上,添加了python和qt。再编译出sdk可以给x86主…

汽车智能座舱中 显示屏市场战略趋势分析 中篇

今天主要讲讲主流车厂显示屏的趋势。 主流车厂的中控&液晶仪表屏的尺寸及趋势汇总 奔驰 奔驰A级 10.2510.25 奔驰C级 12.310.25 奔驰GLA 10.2510.25 奔驰E级 12.312.3 奔驰S级 12.312.8 1、奔驰的仪表几乎都为液晶仪表,几乎所有车型都有HUD的选配&#xff…

大功率应用中的厚膜电阻散热器的设计?

在许多大功率应用中,例如电机和电源,电源电阻器位于主电源线中。它们的目的是防止损坏或提供一定程度的控制。 在这些应用中,电阻器承受恒定的、相对较高的电流。当电流流过电阻器时,它会产生热量。这种热能必须消散到环境中&…

1、WEB攻防-通用漏洞SQL注入MYSQL跨库ACCESS偏移

用途:个人学习笔记,欢迎指正! 前言: 为了网站和数据库的安全性,MYSQL 内置有 ROOT 最高用户,划分等级,每个用户对应管理一个数据库,这样保证无不关联,从而不会影响到其他…

Autosar-WdgM配置详解-3

1.11生成代码解析 1.11.1MasterSWC代码解析 在MasterSWC的RE_TestRun这个runnable里会调用两个检测点函数,我们可以在两个检测点函数之间,加入我们所需要监控的代码。 Rte_Call_RPort_StartCheckPoint_CheckpointReached(); Rte_Call_RPort_EndCheckPoint_CheckpointReac…

C#串口 Modbus通讯工具类

一、安装Modbus包 二、创建modbushelper类 1、打开串口 public bool IfCOMOpend; //用于实例内的COM口的状态 public SerialPort OpenedCOM;//用于手动输入的COM转成SERIAL PORT /// <summary> /// 打开串口 /// </summary> /// <param name="COMname&quo…

unity小工具-非实时的值变化监听器

项目里有代码专门监听网络环境的变化&#xff0c;特别是在下载中&#xff0c;如果遇到断网或者切换为移动网络&#xff0c;可能需要触发提醒等等。这种需求可能是通用的&#xff0c;于是便写了一个通用的监听代码。是 using System; using System.Collections; using System.C…

c++服务器开源项目Tinywebserver运行

c服务器开源项目Tinywebserver运行 一、Tinywebserver介绍二、环境搭建三、构建数据库四、编译Tinywebserver五、查看效果 Tinywebserver是github上一个十分优秀的开源项目&#xff0c;帮助初学者学习如何搭建一个服务器。 本文讲述如何在使用mysql跟该项目进行连接并将项目运行…

python 层次分析(AHP)

文章目录 一、算法原理二、案例分析2.1 构建指标层判断矩阵2.2 求各指标权重2.2.1 算术平均法&#xff08;和积法&#xff09;2.2.2 几何平均法&#xff08;方根法&#xff09; 2.3 一致性检验2.3.1 求解最大特征根值2.3.2 求解CI、RI、CR值2.3.3 一致性判断 2.4 分别求解方案层…

利用Ubuntu22.04启动U盘对电脑磁盘进行格式化

概要&#xff1a; 本篇演示利用Ubuntu22.04启动U盘的Try Ubuntu模式对电脑磁盘进行格式化 一、说明 1、电脑 笔者的电脑品牌是acer(宏碁/宏基) 开机按F2进入BIOS 开机按F12进入Boot Manager 2、Ubuntu22.04启动U盘 制作方法参考笔者的文章&#xff1a; Ubuntu制作Ubun…

【OpenAI官方课程】第五课:ChatGPT文本转换Transforming

欢迎来到ChatGPT 开发人员提示工程课程&#xff08;ChatGPT Prompt Engineering for Developers&#xff09;&#xff01;本课程将教您如何通过OpenAI API有效地利用大型语言模型&#xff08;LLM&#xff09;来创建强大的应用程序。 本课程由OpenAI 的Isa Fulford和 DeepLearn…

缓存篇—缓存雪崩

什么是缓存雪崩 通常我们为了保证缓存中的数据与数据库中的数据一致性&#xff0c;会给 Redis 里的数据设置过期时间&#xff0c;当缓存数据过期后&#xff0c;用户访问的数据如果不在缓存里&#xff0c;业务系统需要重新生成缓存&#xff0c;因此就会访问数据库&#xff0c;并…

QEMU源码全解析 —— virtio(22)

接前一篇文章&#xff1a;QEMU源码全解析 —— virtio&#xff08;21&#xff09; 前几回讲解了virtio驱动的加载。本回开始讲解virtio驱动的初始化。 在讲解virtio驱动的初始化之前&#xff0c;先要介绍virtio配置的函数集合变量virtio_pci_config_ops。实际上前文书也有提到…