• Delphi 的操作符重载 - Record 结构体


    操作符重载是语法上的一些基础用法,但之前我不太了解。最近看了下文档,写了一下测试代码,记录在这里。

    基础知识

    首先看这个链接:

    Class Operators in Delphi

    这个链接文章的作者是 Marco Cantu,一个资深的 Delphi 专家。他在这篇文章里面的代码例子:

    1. type
    2. TNumber = class
    3. private
    4. FValue: Integer;
    5. procedure SetValue(const Value: Integer);
    6. public
    7. property Value: Integer read FValue write SetValue;
    8. class operator Add (a, b: TNumber): TNumber;
    9. class operator Implicit (n: TNumber): Integer;
    10. end;
    11. //---------- 上述代码的用法 ----------
    12. a, b, c: TNumber;
    13. ...
    14. c := a + b;
    15. ShowMessage (IntToStr (c));

    解释一下:对于 TNumber 这个东西(一个 class,一个类),这个类型的变量,可以直接用加法符号,直接相加。就是因为它有 class operator add(a, b: TNumber) 这样的代码在里面。

    问题是,这段代码在我的 Delphi 10.4 CE 版上编译无法通过,IDE 提示语法错误。

    我把 TNumber = class 改为 TNumber = record 结果可以编译通过。

    这里的 class operator 就是对加法这个操作符,做了一个【重载】,针对这个话题,Delphi 官方的文档链接在下面:

    Operator Overloading (Delphi) - RAD Studio (embarcadero.com)

    上述链接的文章的标题是:Operator Overloading

    我的测试代码

    以下代码在 Delphi 10.4 CE 版上测试通过。

    1. type
    2. TMyEvent = procedure(const i: Integer) of object;
    3. TForm1 = class(TForm)
    4. Button1: TButton;
    5. Label1: TLabel;
    6. Memo1: TMemo;
    7. Button2: TButton;
    8. procedure Button1Click(Sender: TObject);
    9. procedure Button2Click(Sender: TObject);
    10. private
    11. { Private declarations }
    12. procedure DoOnEvent(const i: Integer);
    13. procedure DoOnEvent2(const i: Integer);
    14. public
    15. { Public declarations }
    16. end;
    17. // class operator 在 record 里面可用。
    18. TNumber = record
    19. private
    20. FValue: Integer;
    21. FValue2: Integer;
    22. FOnEvent: TMyEvent;
    23. procedure SetValue(const Value: Integer);
    24. public
    25. property Value: Integer read FValue write SetValue;
    26. property MyEvent: TMyEvent read FOnEvent write FOnEvent;
    27. class operator Add(a, b: TNumber): TNumber;
    28. class operator Implicit(n: TNumber): Integer; //Implicit 这个单词:隐含的,这里的定义,是指在代码里面可以直接拿 TNumber 当作一个 Integer 用。
    29. class operator Explicit(i: Integer): TNumber;
    30. end;
    31. var
    32. Form1: TForm1;
    33. implementation
    34. {$R *.dfm}
    35. { TNumber }
    36. class operator TNumber.Add(a, b: TNumber): TNumber;
    37. begin
    38. Result.FValue := a.FValue + b.FValue;
    39. end;
    40. class operator TNumber.Explicit(i: Integer): TNumber;
    41. begin
    42. Result.FValue := i;
    43. end;
    44. class operator TNumber.Implicit(n: TNumber): Integer;
    45. begin
    46. Result := n.FValue;
    47. end;
    48. procedure TNumber.SetValue(const Value: Integer);
    49. begin
    50. Self.FValue := Value;
    51. Self.FOnEvent(Value);
    52. end;
    53. procedure TForm1.Button1Click(Sender: TObject);
    54. var
    55. a, b, c: TNumber;
    56. begin
    57. a.MyEvent := Self.DoOnEvent;
    58. b.MyEvent := Self.DoOnEvent2;
    59. c.MyEvent := Self.DoOnEvent;
    60. a.Value := 2;
    61. b.Value := 5;
    62. c := a + b;
    63. Label1.Caption := c.Value.ToString;
    64. Memo1.Lines.Add(IntToStr(C)); // class operator Implicit(n: TNumber): Integer; 这句话的作用,就是 C: TNumber 可以直接当 Integer 用。
    65. end;
    66. procedure TForm1.Button2Click(Sender: TObject);
    67. var
    68. a: TNumber;
    69. i: Integer;
    70. begin
    71. i := 3;
    72. a := TNumber(i); //class operator Explicit(i: Integer): TNumber; 这句让 a := TNumber(i) 起作用。但不能直接 a := i;
    73. end;
    74. procedure TForm1.DoOnEvent(const i: Integer);
    75. begin
    76. Memo1.Lines.Add('Event1: ' + i.ToString);
    77. end;
    78. procedure TForm1.DoOnEvent2(const i: Integer);
    79. begin
    80. Memo1.Lines.Add('Event2: ' + i.ToString);
    81. end;

    上述代码的解释:

    1. 对于一个 Record 结构体类型来说,可以通过操作符重载的方式,把加号操作符,重新定义。也就是 TNumber 这个结构体的这部分代码:

    1. TNumber = record
    2. FValue: Integer;
    3. class operator Add(a, b: TNumber): TNumber; //重新定义了加法
    4. //重新定义的加法的实现
    5. class operator TNumber.Add(a, b: TNumber): TNumber;
    6. begin
    7. Result.FValue := a.FValue + b.FValue;
    8. end;

    有了上述重载加法符号,则下面的代码就可以正常执行:

    1. procedure TForm1.Button1Click(Sender: TObject);
    2. var
    3. a, b, c: TNumber;
    4. begin
    5. a.Value := 2;
    6. b.Value := 5;
    7. c := a + b;
    8. Label1.Caption := c.Value.ToString;
    9. end;

    上述代码里面,a, b, c 三个 TNmber 类型的结构体变量,可以直接用加法符号来相加!

    2. 操作符里面还有两个玩意:Implicit 和 Explicit,关于它的代码如下:

    1. TNumber = record
    2. private
    3. FValue: Integer;
    4. public
    5. class operator Implicit(n: TNumber): Integer;
    6. class operator Explicit(i: Integer): TNumber;
    7. end;
    8. //实现代码如下
    9. class operator TNumber.Explicit(i: Integer): TNumber;
    10. begin
    11. Result.FValue := i;
    12. end;
    13. class operator TNumber.Implicit(n: TNumber): Integer;
    14. begin
    15. Result := n.FValue;
    16. end;

    这两个 class operator 究竟是什么意思呢?用翻译软件翻译这个英文单词,也不知道它究竟是什么意思,干嘛用的。用测试代码看看它能干什么:

    1. procedure TForm1.Button1Click(Sender: TObject);
    2. var
    3. a, b, c: TNumber;
    4. begin
    5. a.MyEvent := Self.DoOnEvent;
    6. b.MyEvent := Self.DoOnEvent2;
    7. c.MyEvent := Self.DoOnEvent;
    8. a.Value := 2;
    9. b.Value := 5;
    10. c := a + b;
    11. Label1.Caption := c.Value.ToString;
    12. // class operator Implicit(n: TNumber): Integer; 这句话的作用,就是 C: TNumber 可以直接当 Integer 用。
    13. Memo1.Lines.Add(IntToStr(C));
    14. end;

    对 Implicit 这个操作符的重载代码,让程序可以把这个 TNumber 结构体的变量,直接当作整数来用,把它放到 IntToStr() 函数里面,编译器不会报错,运行得到正确的结果!

    继续看另外一个玩意的测试代码:

    1. procedure TForm1.Button2Click(Sender: TObject);
    2. var
    3. a: TNumber;
    4. i: Integer;
    5. begin
    6. i := 3;
    7. //class operator Explicit(i: Integer): TNumber;
    8. //这句让 a := TNumber(i) 起作用。但不能直接 a := i;
    9. a := TNumber(i);
    10. Memo1.Lines.Add(IntToStr(a));
    11. end;

    上述代码中,可以直接通过类型转换,把一个整数 i 变成一个 TNumber 类型的结构体!编译不会出错,执行结果正确。这个功能的实现就依靠了前面的对 Explicit 这个操作符的重载代码。

    Record 结构体中的事件

    一个类,可以在类定义里面,定义和事件有关的代码。然后就可以让这个类的实例对象,在需要触发某个事件的时候,触发一个事件的代码。前面我贴的完整测试代码里面,在一个 Record 结构体定义中,我也定义了一个事件变量,并且在使用一个 Record 变量实例中,给事件赋值了,测试到事件代码被执行了。

    首先,我定义了一个用来做事件的方法类型:

    1. type
    2. TMyEvent = procedure(const i: Integer) of object;

    假设你要的事件的方法类型,Delphi 自己本身就带了,那直接用,也不用自己定义了。比如 Delphi 自己有的一个类型,是使用 Delphi 的时候最常见的类型:

    TNotifyEvent = procedure(Sender: TObject) of object;

    平常见到的 OnClick 啊什么的,就是这个 TNotifyEvent 类型。

    然后我在这个 Record 结构体里面,定义了一个事件变量:

    1. TNumber = record
    2. private
    3. FValue: Integer;
    4. FOnEvent: TMyEvent; //事件的变量
    5. procedure SetValue(const Value: Integer);
    6. public
    7. property MyEvent: TMyEvent read FOnEvent write FOnEvent; //事件
    8. property Value: Integer read FValue write SetValue; //属性
    9. end;
    10. //给它的属性 Value 设置属性时,触发事件。
    11. procedure TNumber.SetValue(const Value: Integer);
    12. begin
    13. Self.FValue := Value;
    14. Self.FOnEvent(Value);
    15. end;

    这里,给一个 Record 结构体,可以有 private 和 public 关键词,也可以有 property 关键词,简直和一个类一样了。

    这里的 property MyEvent: TMyEvent 就是一个事件!

    使用这个事件的代码如下:

    1. procedure TForm1.Button1Click(Sender: TObject);
    2. var
    3. a, b, c: TNumber;
    4. begin
    5. //结构体变量,可以有事件!
    6. a.MyEvent := Self.DoOnEvent; //为变量 a 的事件赋值方法;
    7. b.MyEvent := Self.DoOnEvent2; //为变量 b 的事件赋值;
    8. c.MyEvent := Self.DoOnEvent; //为变量 c 的事件赋值;
    9. a.Value := 2;
    10. b.Value := 5;
    11. c := a + b;
    12. Label1.Caption := c.Value.ToString;
    13. end;
    14. //事件方法如下:
    15. procedure TForm1.DoOnEvent(const i: Integer);
    16. begin
    17. Memo1.Lines.Add('Event1: ' + i.ToString);
    18. end;
    19. procedure TForm1.DoOnEvent2(const i: Integer);
    20. begin
    21. Memo1.Lines.Add('Event2: ' + i.ToString);
    22. end;

    上述代码的执行结果是在 Memo1 里面看到两个不同的方法被执行了。

    总结

    操作符重载

    可以给 Record 结构体类型,重载操作符,比如加号,就是 add,重新用代码定义。还有很多其它操作符,也可以做重载,看本文前面的官方文档链接。从我自己写的这个测试代码来看,它可以让我们在使用 Record 结构体的时候,可以直接写 c := a + b; 这样的代码,如果没有重载 add 这个操作符号,可能就要这样写:

    1. var
    2. a, b, c: TNumber;
    3. begin
    4. a.Value := 3;
    5. b.Value := 5;
    6. c.Value := a.Value + b.Value;
    7. //如果没有给 TNumber 重载 add 操作符,就不能写以下代码:
    8. c := a + b;
    9. end;

    那么,看起来,这里的作用就是让以后使用 TNumber 这个结构体的时候,简单一点,少写一些代码。还有没有其它的作用?需要继续尝试才知道。或者有文档资料介绍。

    属性和事件

    Record 类型,可以和 class 类型一样,可以定义属性和事件!

    当然,Record 结构体类型,也可以有方法和函数。

    那么,一个 Record 结构,和一个类,有什么区别呢?需要思考。

  • 相关阅读:
    G1 垃圾回收器
    HTML+CSS之如何找BUG
    Linux 域名和DNS
    Fiddler抓包工具的使用
    基于STM32的物联网体感控制机械臂
    MySQL绕过WAF实战技巧
    RocketMQ学习笔记(三)——消息丢失的场景及解决方案
    JVM内存和垃圾回收-08.方法区
    想从0开始设计一个风控核查核查项目。
    切分pdf并提取内容
  • 原文地址:https://blog.csdn.net/pcplayer/article/details/126515363