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

本文介绍了ObjectPascal中的运算符重载技术,包括如何定义和使用自定义加法、比较等运算符,以及隐式和显式类型转换、交换性、类型提升的规则和注意事项。
摘要由CSDN通过智能技术生成
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转换为适当的记录类型,以适应赋值目标。在第二个情况下,由于类型提升,转换发生在赋值之后。而在第三个情况下,显式强制转换在第一个值上执行了隐式转换,因此执行的是记录之间的自定义加法操作。

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

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值