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

5.3.4 运算符重载

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

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

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

type
  TPointRecord = record
  public
    class operator Add(A, B: TPointRecord): TPointRecord;

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

var
  A, B, C: TPointRecord;
begin
  C := 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 示例,其中定义了记录的一些运算符:

type
  TPointRecord = record
  private
    X, Y: Integer;
  public
    procedure 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;
begin
  Result.X := A.X + B.X;
  Result.Y := A.Y + B.Y;
end;

class operator TPointRecord.Explicit(A: TPointRecord): string;
begin
  Result := Format('(%d:%d)', [A.X, A.Y]);
end;

class operator TPointRecord.Implicit(X1: Integer): TPointRecord;
begin
  Result.X := X1;
  Result.Y := 10;
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  A, B, C: TPointRecord;
begin
  A.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;
begin
  Result.X := A.X + B;
  Result.Y := A.Y + B;
end;

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

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

var
  A: TPointRecord2;
begin
  A.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;
begin
  Result := 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转换为适当的记录类型,以适应赋值目标。在第二个情况下,由于类型提升,转换发生在赋值之后。而在第三个情况下,显式强制转换在第一个值上执行了隐式转换,因此执行的是记录之间的自定义加法操作。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

程序媛的mac修炼手册-- 如何彻底卸载Python

啊,前段时间因为想尝试chatgpt的API,需要先创建一个python虚拟环境来安装OpenAI Python library. 结果,不出意外的出意外了,安装好OpenAI Python library后,因为身份认证问题,根本就没有获取API key的权限…

Redis之缓存穿透问题解决方案实践SpringBoot3+Docker

文章目录 一、介绍二、方案介绍三、Redis Docker部署四、SpringBoot3 Base代码1. 依赖配置2. 基本代码 五、缓存优化代码1. 校验机制2. 布隆过滤器3. 逻辑优化 一、介绍 当一种请求,总是能越过缓存,调用数据库,就是缓存穿透。 比如当请求一…

新版Java面试专题视频教程——多线程篇②

新版Java面试专题视频教程——多线程篇② 0. 问题汇总0.1 线程的基础知识0.2 线程中并发安全0.3 线程池0.4 使用场景 1.线程的基础知识2.线程中并发锁3.线程池3.1 说一下线程池的核心参数(线程池的执行原理知道嘛)3.2 线程池中有哪些常见的阻塞队列Array…

Java Web演化史:从Servlet到SpringBoot的技术进程及未来趋势

引言 在快速演进的IT世界里,Java Web开发始终屹立不倒,它不仅承担着历史的厚重,也始终面向未来。 自诞生之日起,Java Web技术就在不断地进化,以适应不同时代的需求。 本文将回顾Java Web开发的重要里程碑,…

django rest framework 学习笔记-实战商城2

01收货地址模型类和视图定义_哔哩哔哩_bilibili 本博客借鉴至大佬的视频学习笔记 地址信息的管理:增删改查的实现 # 序列化器配置 class AddrSerializer(serializers.ModelSerializer):"""收货地址的模型序列化器"""class Meta:mo…

喀秋莎画中画怎么设置 喀秋莎画中画视频怎么导出 喀秋莎什么意思 camtasia studio下载

画中画视频,顾名思义,就是在一个视频中有两个画面,游戏解说、微课等类型的视频常常就以画中画的形式出现。作为一款专业的视频编辑软件,使用camtasia可以轻松地制作画中画视频并导出。接下来我将为大家介绍:喀秋莎画中…

HEVC视频编解码标准学习笔记-1

视频编解码标准H.265/HEVC(High Efficiency Video Coding)通过将视频数据编码为更高效格式,大幅改善了视频流的压缩效率。这里主要介绍Tile、Slice和CTU的定义,以及介绍这些技术组件之间的相互关系。 CTU(编码树单元&…

Chrome插件精选 — 缓存清理

Chrome实现同一功能的插件往往有多款产品,逐一去安装试用耗时又费力,在此为某一类型插件挑选出比较好用的一款或几款,尽量满足界面精致、功能齐全、设置选项丰富的使用要求,便于节省一个个去尝试的时间和精力。 1. Chrome清理大师…

基于深度学习的红肉新鲜过期判决系统matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 系统构成与流程 4.2 模型训练与优化 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ...............................................…

phar反序列化原理及利用

phar是什么? phar 是 PHP 的一种归档文件格式,类似于 ZIP 或 TAR 文件,它可以包含多个文件和目录,并且可以像访问普通文件系统一样在 PHP 中进行访问。在php 5.3 或更高版本中默认开启 在php.ini中配置如下时,才能生成…

线性代数:线性方程组解的结构

目录 齐次/非齐次方程组的解 Ax 0 的解的性质 定理 Ax b 的解的性质 相关证明 例1 例2 例3 齐次/非齐次方程组的解 Ax 0 的解的性质 定理 Ax b 的解的性质 相关证明 例1 例2 例3

docker容器常见操作

目录 一、认识容器 1.1、docker用到的内核技术 1.2、namespace 1.3、Control Group 1.4、LXC与docker区别 二、docker环境准备 2.1、安装docker 2.2、docker daemon环境管理 三、镜像、容器和仓库 3.1、镜像常见操作 3.2、配置镜像加速器 命名空间 3.3、非官方镜像仓…

亿道丨防爆工业平板哪家好丨防爆平板电脑pad:防什么?

在爆炸性环境中,工作安全是至关重要的。防爆工业平板作为一种特殊设计的设备,不仅能够抵御爆炸风险,还提供高效的工作性能。本文将介绍防爆工业平板的防护功能以及其在各个行业中的应用。 在许多行业,如石油、化工、矿山和制药等领…

【RT-DETR有效改进】Best Paper | DAttention (DAT)可变形注意力机制和动态采样点

一、本文介绍 本文给大家带来的是RT-DETR改进DAT(Vision Transformer with Deformable Attention)的教程,其发布于2022年CVPR2022上同时被评选为Best Paper,由此可以证明其是一种十分有效的改进机制,其主要的核心思想是:引入可变…

考研高数(高阶导数的计算)

1.归纳法 常见高阶导数 2.泰勒展开式 3.莱布尼兹公式 4.用导数定义证明导函数在某一点连续的例题

亿道丨三防平板电脑厂商哪家好丨麒麟系统三防平板PAD

随着科技的飞速发展,人们对于移动设备的需求越来越高。然而,在不同的行业应用场景下,常规的智能平板往往无法满足特殊的工作要求。,亿道三防平板,将高可靠性与卓越性能高度结合,为各行各业提供卓越的移动解…

stm32——hal库学习笔记(ADC)

这里写目录标题 一、ADC简介(了解)1.1,什么是ADC?1.2,常见的ADC类型1.3,并联比较型工作示意图1.4,逐次逼近型工作示意图1.5,ADC的特性参数1.6,STM32各系列ADC的主要特性 …

华为OD机试真题-用连续自然数之和来表达整数-2023年OD统一考试(C卷)---python代码免费

题目: 代码 """ 题目分析: 一个整数 连续的自然数之和表示(非负整数)输入: 一个整数T[1,1000] 输出: 输出多个表达式,自然数个数最少优先输出 最后一行, 输出“Result : 个数…

[计网底层小探索]:实现并部署多线程并发Tcp服务器框架(基于生产者消费者模型的线程池结构)

文章目录 一.网络层与传输层协议sockaddr结构体继承体系(Linux体系)贯穿计算机系统的网络通信架构图示: 二.实现并部署多线程并发Tcp服务器框架线程池模块序列化反序列化工具模块通信信道建立模块服务器主体模块任务回调模块(根据具体应用场景可重构)Tips:DebugC代码过程中遇到…

4.Spring MVC入门

文章目录 1. HTTP协议2. Spring MVC2.1. 三层架构2.2. MVC(解决表现层的问题)2.3. 核心组件 3. Thymeleaf3.1. 模板引擎3.2. Thymeleaf3.3. 常用语法 代码 1. HTTP协议 网址:https://www.ietf.org/ (官网网址) https:…