(delphi11最新学习资料) Object Pascal 学习笔记---第5章第3节(运算符重载)

5.3.4 运算符重载

​ 另一个与记录相关的 Object Pascal 语言特性是运算符重载,即在数据类型上自己定义标准操作(加法、乘法、比较等)的能力。基本思想是你可以实现一个加法运算符(一个特殊的 Add 方法),然后使用 + 符号来调用它。要定义运算符,你需要使用 class operator 关键字的组合。

注解:通过重用现有的保留字,语言设计者成功地做到了对现有代码没有产生影响。他们最近在关键字组合中经常这样做,比如 strict privateclass operatorclass var

​ 这里的 class 与类方法有关,这是我们将在更后面的章节中要探讨的概念(在第12章)。在指令之后,你写出运算符的名称,例如 Add:

typeTPointRecord = recordpublicclass operator Add(A, B: TPointRecord): TPointRecord;

然后使用 + 符号调用 Add 运算符,如你所期望:

varA, B, C: TPointRecord;
beginC := A + B;

那么有哪些可用的运算符呢?基本上是语言的整个运算符集,因为你不能定义全新的运算符:

  • 强制类型转换运算符: ImplicitExplicit
  • 一元运算符:Positive, Negative, Inc, Dec, LogicalNot, BitwiseNot,
    Trunc, 和 Round
  • 比较运算符:Equal, NotEqual, GreaterThan, GraterThanOrEqual, LessThan, 和LessThenOrEqual
  • 二元运算符:Add, Subtract, Multiply, Divide, IntDivide, Modulus,ShiftLeft, ShiftRight, LogicalAnd, LogicalOr, LogicalXor, BitwiseAnd, BitwiseOr, 和 BitwiseXor.
  • 托管记录运算符:Initialize、Finalize、Assign(有关这三个在 Delphi 10.4 中添加的运算符的详细信息,请参见下一节“运算符和自定义托管记录”)

​ 在调用运算符的代码中,你不是使用这些名称,而是使用相应的符号。你仅在定义中使用这些特殊名称,使用 class operator 前缀以避免任何可能的命名冲突。例如,您可以在一条记录中同时使用 Add 方法和 Add 操作符,而不会造成命名冲突。

​ 在定义这些运算符时,你要明确指定参数,然后当“调用”完全匹配参数时才能应用该运算符。要把两个不同类型的值相加,你必须指定两个不同的 Add 操作,因为每个操作数都可以是表达式的第一个或第二个条目。实际上,运算符的定义不提供自动交换的能力。此外,你必须非常精确地指定类型,因为自动类型转换不适用。这往往意味着定义运算符的多个重载版本,并使用不同类型的参数。

​ 另一个需要注意的重要因素是,可以定义两种特殊的数据转换操作符,即 ImplicitExplicit。第一个用于定义隐式类型转换(或静默转换),应该是完美的且不会有损失。第二个,Explicit,只有在从一个类型的变量向另一个类型进行显式类型转换时才能调用。这两个操作符共同定义了允许在给定数据类型之间进行的类型转换。

​ 请注意,ImplicitExplicit 运算符都可以根据函数的返回类型进行重载,而重载方法通常无法做到这一点。实际上,在进行类型转换时,编译器知道预期的结果类型,并可以找出要应用的类型转换操作。例如,我编写了 OperatorsOver 示例,其中定义了记录的一些运算符:

typeTPointRecord = recordprivateX, Y: Integer;publicprocedure SetValue(X1, Y1: Integer);class operator Add(A, B: TPointRecord): TPointRecord;class operator Explicit(A: TPointRecord): string;class operator Implicit(X1: Integer): TPointRecord;end;

以下是记录方法的实现:

class operator TPointRecord.Add(A, B: TPointRecord): TPointRecord;
beginResult.X := A.X + B.X;Result.Y := A.Y + B.Y;
end;class operator TPointRecord.Explicit(A: TPointRecord): string;
beginResult := Format('(%d:%d)', [A.X, A.Y]);
end;class operator TPointRecord.Implicit(X1: Integer): TPointRecord;
beginResult.X := X1;Result.Y := 10;
end;

使用这样的记录非常简单,你可以编写如下代码:

procedure TForm1.Button1Click(Sender: TObject);
varA, B, C: TPointRecord;
beginA.SetValue(10, 10);B := 30;C := A + B;Show(string(C));
end;

第二个赋值(B := 30;)使用了隐式运算符,由于缺少强制转换,而 Show 调用使用了强制转换符号以激活显式类型转换。此外,Add 运算符并不修改其参数;相反,它返回一个全新的值。

注解:运算符返回新值的事实使得我们更难考虑对类进行运算符重载。如果操作符创建了一个新的临时对象,谁来处理它呢?

运算符重载的背后

这是一个相当高级的简短部分,你可能希望首次阅读时跳过。

从技术上讲,你可以使用运算符的内部限定全称(如 &&op_Addition)来调用运算符, 前缀为&& ,这个技术鲜为人知。例如,你可以将记录和写法的总和重写如下(完整列表请参阅演示):

C := TPointRecord.&&op_Addition(A, B);

尽管我认为只有极少数的情况需要这样做。(定义运算符的全部目的是能够使用比普通方法名或更丑陋的直接调用生成的方法名更友好的表示法。)

实现交换性

​ 假设您想把一个记录与一个整数相加。您可以定义以下操作符(OperatorsOver 示例代码中提供了该操作符,但记录类型略有不同)::

class operator TPointRecord2.Add(A: TPointRecord2; B: Integer): TPointRecord2;
beginResult.X := A.X + B;Result.Y := A.Y + B;
end;

注解:我之所以为新类型而不是现有类型定义这个操作符,是因为同一结构已经定义了整数到记录类型的隐式转换,因此我已经可以添加整数和记录,而无需定义特定的操作符。这个问题将在下一节中作进一步解释。

现在你可以合法地将浮点值添加到记录中:

varA: TPointRecord2;
beginA.SetValue(10, 20);A := A + 10;

然而,如果你尝试编写相反的加法:

A := 30 + A;

这将失败并显示错误:

[dcc32 Error] E2015 Operator not applicable to this operand type

实际上,正如我所提到的,对于应用于不同类型变量的运算符来说,交换性不是自动的,而必须由重复调用或调用(如下所示)运算符的另一个版本来明确实现:

class operator TPointRecord2.Add(B: Integer; A: TPointRecord2): TPointRecord2;
beginResult := A + B; // 实现交换性
end;

隐式转换和类型提升

​ 需要注意的是,调用重载运算符的规则解析与调用方法的传统规则解析不同。在类型自动提升的情况下,一个表达式有可能最终调用不同版本的重载操作符,从而导致调用含糊不清。这就是为什么在编写 Implicit 运算符时需要非常小心的原因。

​ 考虑一下前面示例中的这些表达式:

A := 50;
C := A + 30;
C := 50 + 30;
C := 50 + TPointRecord(30);

它们都是合法的!在第一种情况下,编译器会将 30 转换为正确的记录类型;在第二种情况下,转换发生在赋值之后;在第三种情况下,显式转换会强制对第一个值进行隐式转置,因此执行的加法是记录之间的自定义加法。换句话说,第二个操作的结果与其他两个操作不同,这一点在输出(显示 X 和 Y 值)和这些语句的扩展版本中都有突出显示:

// 输出
(80:20)
(80:10)
(80:20)
// 扩展语句
C := A + TPointRecord(30);
// 即: (50:10) + (30:10)
C := TPointRecord (50 + 30);
// 即: 80 转换为 (80:10)
C := TPointRecord(50) + TPointRecord(30);
// 即: (50:10) + (30:10)

在第一个情况下,编译器将30转换为适当的记录类型,以适应赋值目标。在第二个情况下,由于类型提升,转换发生在赋值之后。而在第三个情况下,显式强制转换在第一个值上执行了隐式转换,因此执行的是记录之间的自定义加法操作。

​ 需要特别注意的是,这种类型提升会导致表达式最终调用不同版本的重载运算符,可能会引发模棱两可的调用。因此,在编写隐式运算符时,需要特别小心。

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

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

相关文章

【VIP专属】Python应用案例——基于TensorFlow 2.3建立RNN搭配Word2Vec Embedding进行文本分类

目录 一、数据准备 二、实验流程 1、加载数据集 ​2、分词处理

启动node服务报错Error: listen EACCES: permission denied 0.0.0.0:5000

启动node服务报错: 解决方案: 将监听端口改成3000或者其他 修改后结果: 参考原文: Error: listen EACCES: permission denied_error when starting dev server: error: listen eacc-CSDN博客

高级面试:什么是移动语义(Move Semantics)?它是如何提高性能的?

高级面试:什么是移动语义(Move Semantics)?它是如何提高性能的? 移动语义(Move Semantics)是 C11 引入的一项重要特性,它允许对象的资源(如堆上分配的内存)在…

链式前向星

什么是链式前向星 链式前向星(Chained Forward Star)是一种用于表示稀疏图的数据结构。它主要用于解决图论中的一些算法问题,如最短路径、最小生成树等。 链式前向星通过两个数组来表示图的边和顶点信息: 边数组(Ed…

Anaconda下的pkgs占用空间13G,如何安全的清理(已解决)

方法一:让Anaconda自行决定清理 执行命令 conda clean -p 我的Anaconda安装在D盘,具体位置如下。你的应该也能找到对应的位置 D:\*****\**\Anaconda3\pkgs (base) C:\Users\Liu_J>conda clean -p WARNING: C:\Users\***\.conda\pkgs does not ex…

haproxy集成国密ssl功能

1. 概述 1.1 缘起 haproxy作为开源高性能http/tcp代理服务器得到了广泛应用,它可以支持国际ssl加密通信功能,但是对于国密ssl却一直没有被官方得到支持。随着国密标准规范的推广应用,以及等保2.0明确规定要求对网络通信中的报文或会话过程全文加密(三级),这样导致在haprox…

Conda管理Python不同版本教程

Conda管理Python不同版本教程 目录 0.前提 1.conda常用命令 2.conda设置国内源(以添加清华源为例,阿里云源同样) 3.conda管理python库 4.其它 不太推荐 pyenv管理Python不同版本教程(本人另一篇博客,姊妹篇&…

C语言菜鸟入门·数组简介

目录 1. 简介 2. 声明数组 3. 初始化数组 3. 访问数组元素 4. 获取数组长度 5. 数组名 1. 简介 在 C 语言中,数组是一种用来存储相同类型数据元素的集合。数组提供了一种便捷的方式来管理一系列相同类型的数据,可以按照索引来访问和操作数组…

netty的TCP服务端和客户端实现

第一步&#xff1a;引入依赖 <dependencies><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.90.Final</version></dependency></dependencies> 第二步&#xff1a;实…

探究网络工具nc(netcat)的使用方法及安装步骤

目录 &#x1f436;1. 什么是nc&#xff08;netcat&#xff09;&#xff1f; &#x1f436;2. nc&#xff08;netcat&#xff09;的基本使用方法 2.1 &#x1f959;使用 nc 进行端口监听 2.2 &#x1f959;使用 nc 进行端口扫描 2.3 &#x1f959;使用 Netcat 进行文件传输…

代码随想录算法训练营day20

题目&#xff1a;530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先 参考链接&#xff1a;代码随想录 530.二叉搜索树的最小绝对差 思路&#xff1a;我一开始想到的方法是先生成中序序列&#xff0c;然后对相邻两项的差进行计算&#xff0c;取…

【PCIE709-F】基于复旦微JFM7VX690T80 FPGA的全国产化8通道光纤双FMC接口数据处理平台

板卡概述 PCIE709-F是一款基于上海复旦微电子的28nm 7系列FPGA JFM7VX690T80的全国产化8通道光纤双FMC接口数据预处理平台&#xff0c;该板卡采用复旦微的高性能7系列FPGA作为实时处理器&#xff0c;实现4路10G SFP光纤以及1路QSFP通信接口、实现1路X8 PCIE数据传输的功能。板载…

Go 1.22 对 net/http 包的路由增强功能详解

目录 方法匹配&#xff08;Method Matching&#xff09; 通配符&#xff08;Wildcards&#xff09; 路径前缀匹配 优先规则 兼容性 API 变更 小结 参考资料 Go 1.22 版本对 net/http 包的路由功能进行了增强&#xff0c;引入了方法匹配&#xff08;method matching&…

【鸿蒙系统学习笔记】网络请求

一、介绍 资料来自官网&#xff1a;文档中心 网络管理模块主要提供以下功能&#xff1a; HTTP数据请求&#xff1a;通过HTTP发起一个数据请求。WebSocket连接&#xff1a;使用WebSocket建立服务器与客户端的双向连接。Socket连接&#xff1a;通过Socket进行数据传输。 日常…

SpringMVC回顾总结笔记

MVC是一种思想而SpringMVC是具体的实现&#xff08;Ioc和DI的关系&#xff09; 在创建项目的时候勾选的SpringWeb框架就是SpringMVC框架 与浏览器建立连接 默认返回的是一个 view 视图。需要添加ResponseBody说明返回的是json数据。RestController是ControllerResponseBody…

fastJSON 字符串转对象

一、fastJSON 包 dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.33</version> </dependency> 二、转普通对象 自定义对象A A aa JSONObject.parseObject("字符串", A.…

【数据结构】_队列

目录 1.概念 2.队列的使用 3.队列模拟实现 4.循环队列 5.双端队列 6.栈与队列的互相实现 6.1 用队列实现栈 6.2 用栈实现队列 1.概念 &#xff08;1&#xff09;队列是只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff1b; &am…

小米标准模组+MCU 快速上手开发(一)——之固件下载

小米标准模组+MCU 开发笔记之固件下载 背景技术名词简介● 小米IoT开发者平台● 小米IoT 模组● ESP系列简介问题描述 + 解决方式问题1:固件下载是否有示例,如何下载到硬件板卡中?问题2:固件下载的官方程序是什么?在哪里?该如何使用?问题3:固件下载时,Flash和Ram 有什…

CSB ---> (XXE)XML基础

本来今天想更一下CSbeacon上线多层的内网机器的&#xff0c;但是刚好今天是年后的第一节课&#xff0c;讲的是XXE的基础&#xff0c;那就来先盘一下基础&#xff01;&#xff01; 1.XXE XXE全称是XML External Entity即xml外部实体注入攻击&#xff01;其后果会导致用户…

Linux eject命令教程:如何控制可移动介质的弹出和收回(附案例详解和注意事项)

Linux eject命令介绍 eject命令在Linux中用于弹出可移动介质&#xff0c;通常是CD-ROM、软盘、磁带或JAZ或ZIP磁盘。您还可以使用此命令来控制一些多盘CD-ROM切换器&#xff0c;一些设备支持的自动弹出功能&#xff0c;以及关闭一些CD-ROM驱动器的光盘托盘。 Linux eject命令…