操作符重载是语法上的一些基础用法,但之前我不太了解。最近看了下文档,写了一下测试代码,记录在这里。
基础知识
首先看这个链接:
这个链接文章的作者是 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 结构,和一个类,有什么区别呢?需要思考。