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

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

基础知识

首先看这个链接:

Class Operators in Delphi

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

type
  TNumber = class
  private
    FValue: Integer;
    procedure SetValue(const Value: Integer);
  public
    property Value: Integer read FValue write SetValue;
    class operator Add (a, b: TNumber): TNumber;
    class operator Implicit (n: TNumber): Integer;
end;

//---------- 上述代码的用法 ----------

a, b, c: TNumber;
...
c := a + b;
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 版上测试通过。

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

  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    procedure DoOnEvent(const i: Integer);
    procedure DoOnEvent2(const i: Integer);
  public
    { Public declarations }
  end;

  // class operator 在 record 里面可用。
  TNumber = record
  private
    FValue: Integer;
    FValue2: Integer;
    FOnEvent: TMyEvent;

    procedure SetValue(const Value: Integer);
  public
    property Value: Integer read FValue write SetValue;
    property MyEvent: TMyEvent read FOnEvent write FOnEvent;

    class operator Add(a, b: TNumber): TNumber;
    class operator Implicit(n: TNumber): Integer;  //Implicit 这个单词:隐含的,这里的定义,是指在代码里面可以直接拿  TNumber 当作一个 Integer 用。
    class operator Explicit(i: Integer): TNumber;
  end;


var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TNumber }

class operator TNumber.Add(a, b: TNumber): TNumber;
begin
  Result.FValue := a.FValue + b.FValue;
end;

class operator TNumber.Explicit(i: Integer): TNumber;
begin
  Result.FValue := i;
end;

class operator TNumber.Implicit(n: TNumber): Integer;
begin
  Result := n.FValue;
end;

procedure TNumber.SetValue(const Value: Integer);
begin
  Self.FValue := Value;
  Self.FOnEvent(Value);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  a, b, c: TNumber;
begin
  a.MyEvent := Self.DoOnEvent;
  b.MyEvent := Self.DoOnEvent2;
  c.MyEvent := Self.DoOnEvent;

  a.Value := 2;
  b.Value := 5;
  c := a + b;

  Label1.Caption := c.Value.ToString;

  Memo1.Lines.Add(IntToStr(C));  // class operator Implicit(n: TNumber): Integer; 这句话的作用,就是 C: TNumber 可以直接当 Integer 用。
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  a: TNumber;
  i: Integer;
begin
  i := 3;
  a := TNumber(i); //class operator Explicit(i: Integer): TNumber; 这句让 a := TNumber(i) 起作用。但不能直接 a := i;

end;

procedure TForm1.DoOnEvent(const i: Integer);
begin
  Memo1.Lines.Add('Event1: ' + i.ToString);
end;

procedure TForm1.DoOnEvent2(const i: Integer);
begin
  Memo1.Lines.Add('Event2: ' + i.ToString);
end;

上述代码的解释:

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

TNumber = record
  FValue: Integer;
class operator Add(a, b: TNumber): TNumber; //重新定义了加法


//重新定义的加法的实现
class operator TNumber.Add(a, b: TNumber): TNumber;
begin
  Result.FValue := a.FValue + b.FValue;
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  a, b, c: TNumber;
begin
  a.Value := 2;
  b.Value := 5;
  c := a + b;

  Label1.Caption := c.Value.ToString;
end;

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

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

TNumber = record
  private
    FValue: Integer;
  public
    class operator Implicit(n: TNumber): Integer;  
    class operator Explicit(i: Integer): TNumber;
end;


//实现代码如下
class operator TNumber.Explicit(i: Integer): TNumber;
begin
  Result.FValue := i;
end;

class operator TNumber.Implicit(n: TNumber): Integer;
begin
  Result := n.FValue;
end;

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

procedure TForm1.Button1Click(Sender: TObject);
var
  a, b, c: TNumber;
begin
  a.MyEvent := Self.DoOnEvent;
  b.MyEvent := Self.DoOnEvent2;
  c.MyEvent := Self.DoOnEvent;

  a.Value := 2;
  b.Value := 5;
  c := a + b;

  Label1.Caption := c.Value.ToString;

  // class operator Implicit(n: TNumber): Integer; 这句话的作用,就是 C: TNumber 可以直接当 Integer 用。
  Memo1.Lines.Add(IntToStr(C));
end;

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

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

procedure TForm1.Button2Click(Sender: TObject);
var
  a: TNumber;
  i: Integer;
begin
  i := 3;

  //class operator Explicit(i: Integer): TNumber; 
  //这句让 a := TNumber(i) 起作用。但不能直接 a := i;
  a := TNumber(i);
  Memo1.Lines.Add(IntToStr(a));
end;

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

Record 结构体中的事件

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

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

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

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

TNotifyEvent = procedure(Sender: TObject) of object;

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

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

TNumber = record
  private
    FValue: Integer;
    FOnEvent: TMyEvent;        //事件的变量

    procedure SetValue(const Value: Integer);
  public
    property MyEvent: TMyEvent read FOnEvent write FOnEvent; //事件

    property Value: Integer read FValue write SetValue;  //属性
  end;


//给它的属性 Value 设置属性时,触发事件。

procedure TNumber.SetValue(const Value: Integer);
begin
  Self.FValue := Value;
  Self.FOnEvent(Value);
end;

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

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

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

procedure TForm1.Button1Click(Sender: TObject);
var
  a, b, c: TNumber;
begin
  //结构体变量,可以有事件!
  a.MyEvent := Self.DoOnEvent; //为变量 a 的事件赋值方法;
  b.MyEvent := Self.DoOnEvent2;  //为变量 b 的事件赋值;
  c.MyEvent := Self.DoOnEvent;  //为变量 c 的事件赋值;

  a.Value := 2;
  b.Value := 5;
  c := a + b;

  Label1.Caption := c.Value.ToString;
end;


//事件方法如下:
procedure TForm1.DoOnEvent(const i: Integer);
begin
  Memo1.Lines.Add('Event1: ' + i.ToString);
end;

procedure TForm1.DoOnEvent2(const i: Integer);
begin
  Memo1.Lines.Add('Event2: ' + i.ToString);
end;

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

总结

操作符重载

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

var
  a, b, c: TNumber;
begin
  a.Value := 3;
  b.Value := 5;
  c.Value := a.Value + b.Value;

  //如果没有给 TNumber 重载 add 操作符,就不能写以下代码:
  c := a + b;
end;

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

属性和事件

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

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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值