delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作

P344-365 接口

  "接口" 的概念和 "类" 特别是 "抽象类" 近似, Delphi 之初并没有接口, 后来(Delphi 3)为了支持 COM 引入了接口, 再后来发展成为 Delphi 重要的语言特性.

  使用 COM 步骤可能是这样的:
    1.程序在使用组件之初, 先联系 "接口"; 这应该是从注册表中查询.
    2.找到后, 如果此接口还没有被实现, 马上调用 "类厂" 建立起对象, 并同时给接口计数器加 1.
    3.可能会不止一个程序在使用同一个接口, 但每有使用则 "计数器+1", 每次用完则 "计数器-1".
    4.当接口的使用计数为 0 时, 系统自动释放类厂为该接口建立的对象(垃圾回收机制).

下面用代码来理解这段话:

Type 
IFace = interface  //声明一个接口
    procedure proc;
  end;

  TAType = class(TInterfacedObject, IFace)  //调用接口
   constructor Create;  //构造函数
    destructor Destroy; override;    //解析函数
   procedure proc;  //在存在构造函数或解析函数时,方法属性一律写在最后,否则出错
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

{ TAType }

constructor TAType.Create;
begin
  inherited;
  Form2.memo1.lines.add('TAType  Constructor!');
end;

destructor TAType.Destroy;
begin
  inherited;
  Form2.memo1.lines.add('TAType  Destroy!');
end;

procedure TAType.proc;
begin
  Form2.memo1.lines.add('TAType.proc!');
end;

procedure TForm2.Button1Click(Sender: TObject);
var
  A: IFace;    //注意这里,这里声明的是一个接口
begin
   A:= TAType.Create ;    //然而这里去是用的类的构造函数,这里是接口的一个妙用.接口变量可以接一个实现了该接口的类 
   A.proc;
  //A:=nil;    //主动释放内存,同样的,拥有它的类也会被释放掉!
   Form2.memo1.lines.add('---end---');
end;

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第1张图片

  与类相比,接口侧重于封装,并提供与类之间一种比继承更宽松一点的连接.

除了宣告抽象类(拥有抽象方法的类别),在 Object Pascal 里面我们也可以撰写纯粹的抽象类;也就是只包含虚拟抽象方法的类别。透过使用特别的关键词,interface 来定义一组作为接口(interfaces)的数据型别。

从技术面来看,接口不算是类,虽然接口可以重组类.因为类可以建立实体,但是接口不行.接口可以被一个或者多个类调用,所以这些实体就可以算是支持了或者调用了该接口.讲人话就是: 接口是用来给类调用的抽象类.

类与接口的对比:

1.  接口类型的变量会修改接口计数器,跟类类型的变量不同,接口提供了一系列的自动内存管理机制

2.  一个类可以继承多个接口(要列在基础类后面):

  TMyClass = class(父类, 接口1, 接口2, ...) 
      //Some Code
  end;

  但是一个接口只能从另一个接口继承过来,而不能同时继承多个接口,如果继承的对象是根接口Iinterface,则可省略.

IMyInterface1 = interface(Iinterface)
    function Func1: Integer;
    function Func2: Integer;
  end;

3.  所有的类都是从TObject中衍生而来,所有的接口都是从IInteface衍生出来的,两都是各自独立正交的架构.

4.  不成文约定:所有类名,除异常类型外以E开头外,其他都以T开头,而所有接口都以字母I开头.

5.  接口只有方法与属性没有字段

6.  接口成员都是公开的,不需要private/protected/public/published等修饰语

7.  因为接口只声明、无实现, 所以也用不到继承与覆盖相关的修饰(virtual、dynamic、abstract、override).

8.  不管实现接口的类有多么丰富, 接口只拥有自己声明的成员.

9.  实现接口的类一般继承于 TInterfacedObject, 直接从 TObject 继承会增加一些麻烦而重复的工作.

10.  接口在用完后会自动释放, 并同时释放拥有它的类; 这很方便, 但同时带来很多问题.

11. 每个接口具有唯一的标识符,称为GUID,在接口内按下Ctrl+Shift+G,IDE会自动产生一组GUID  

 IFace = interface
  //GUID,平常是由系统分配的,所以基本不用理会.这里只是演示
    ['{E5AC8E60-2DBE-43C5-B679-27731A56F3D4}']  
    procedure proc;
  end;

接口的声明与调用:

  和类的声明一样,接口也是在interface区域下用关键字type进行声明:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
//接口只有属性和方法,没有字段!并且只能继承一个祖先,如果是根接口,可以省略不写
 //接口1   
  IFace1 = interface
    function Fn1(Str: string): string; //抽象方法,只有声明没有实体,按Ctrl+Shift+c无响应
  end;
//接口2
  IFace2 = interface
    procedure Proc2;    //抽象方法,只有声明没有实体,按Ctrl+Shift+c无响应
  end;

//类调用上面的两个接口,注意先后顺序,先类,后接口,不然报错.
//接口里有什么方法,调用的类里面就要定义什么方法,说白了就是抄一份下来
//否则会提示 接口方法 IFace1.test 没有实现部分
  TAType = class(TInterfacedObject, IFace1, IFace2)  //先类后接口
   function Fn1(Str: string): string;//这里要按Ctrl+Shift+c来完成方法实体
    procedure Proc2;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TAType }
//方法实体
function TAType.Fn1(str: string): string;
begin
  Result := ('Fn1:' + str);
end;
//方法实体
procedure TAType.Proc2;
begin
  ShowMessage('Fn2');
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  TA: TAType;
begin
  Memo1.Clear;
  Memo1.Lines.Add(TA.Fn1('哈哈哈'));//接口调用
  TA.Proc2;//接口调用
end;

end.

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第2张图片

 接口属性:

{此接口声明了一个 Name 属性; 因为接口没有字段, read/write 都只能从方法}
  IMyInterface = interface
    function GetName : string;
    procedure SetName(val : string);
    property Name : string read GetName write SetName;
  end;

  {类实现的是接口的读写方法, 属性还是属于接口的; 类可以提供一个储存属性的字段}
  TMyClass = class(TInterfacedObject, IMyInterface)
  private
    FName: string;
  public
    function GetName: string;
    procedure SetName(val: string);
  end;

 接口可以被多次调用.因为接口的方法是抽象的,实体部分要到调用接口的类里面才具现,所以类调用接口的方法时,方法能执行什么功能,还不是你说的算?

type
  //接口
  IMyInterface1 = interface
    function Func(a,b: Integer): Integer;
  end;
//调用接口的类1,这里的方法执行的是加法
  TAdd = class(TInterfacedObject, IMyInterface1)
  public
    function Func(a: Integer; b: Integer): Integer;  //Result:= a+b;
    destructor Destroy; override;
  end;
//调用接口的类2,这里的方法执行的是乘法
  TMul = class(TInterfacedObject, IMyInterface1)
  public
    function Func(a: Integer; b: Integer): Integer;  //Result:= a*b;
    destructor Destroy; override;
  end;

  弱化引用[weak]使变量的接口计数器不被修改.

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  IFace = interface
    procedure test;
  end;

  Ttype = class(TInterfacedObject, IFace)
    destructor Destroy; override;
    procedure test;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ Ttype }

destructor Ttype.Destroy;
begin
  Form1.Memo1.Lines.Add('Destroy');  //在实体中调用控件的话,要加上完整的对象位置.
  inherited;
end;

procedure Ttype.test;
begin
  Form1.Memo1.Lines.Add('test');
end;

调用:

procedure TForm1.Button1Click(Sender: TObject);
var
//  [weak]
  a: IFace;
begin
   a:=  Ttype.Create ;
   a.test ;
   Form1.Memo1.Lines.Add('---')
end;

 可以看到,在不使用 weak 弱化引用时,程序正常执行.

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第3张图片

 如果把 [weak] 解除注释,则会出现内存错误.因为当a被创建时,因为weak的原因,a的接口计数器没有被加1,仍然等于0,所以系统就自动执行了destroy解析函数,把a给翻译掉了.当再执行a.test时,因为对象已经不存在,所以就会内存报错

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第4张图片

 [unSafe]和[weak]不同,它不会主动去清除接口计数器,它是憜性的,下面的例子还是延用上面的代码,可以看到Destroy程序并没有被执行

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第5张图片

再看一个网上的例子:

type
  TA=class(TInterfacedObject)
 
  end;
 
procedure TForm1.Button1Click(Sender: TObject);
var
  a:IInterface;
  [weak]aweak:IInterface;
begin
  a:=TA.Create;   //创建对象,复制给a,执行完成后引用计数器=1
  aweak:=a;       //由于aweak定义有[weak]属性,所以赋值给aweak后,引用计数器依旧为1,但aweak变量的地址被保存到一个weak关联列表中
  Memo1.Lines.Add(Format('Ptr:%d', [NativeInt(Pointer(aweak))]));
  a:=nil;         //由于引用计数器=1,执行此句后,计数器清0,对象被释放,同时与此对weak关联列表中所有变量也被赋值为nil,包括aweak变量.
  Memo1.Lines.Add(Format('Ptr:%d', [NativeInt(Pointer(aweak))]));
end;

 运行结果:
Ptr:16360080
Ptr:0
weak引用非常适合用于两个对象需要互相引用的情况下,如果以往的引用,将无法让引用计数器清0.

 方法别名:

  IFace = interface
    ['{3BA60C4E-CA74-489C-B74B-3FA791BC2843}']
    procedure Ptest;
    function Ftest: string;
  end;

  IFace2 = interface
  ['{324AB107-95F1-4145-B392-2E6F555822E3}']
    function Ftest: string;
  end;

  Ttype = class(TInterfacedObject, IFace, IFace2)
    destructor Destroy; override;
    procedure Ptest;
    function F1: string;
    function F2: string;
    function IFace.Ftest = F1;
    function IFace2.Ftest = F2;
  end;

但是我在调用时,调不出F1和F2,不知道问题在哪里,在两个群问了半天,也没有等来答案,如果有大佬知道答案,麻烦指点一下,谢谢啦

 

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第6张图片

 P366-392 类操作

  1.类方法与类函数.

    在类里面声明的过程或者函数,只要在前面加上关键字class,就会变成类方法或者类函数.     

    类方法不能在 private 和 protected 区;  

    类方法不能是虚方法;

    类方法只能使用类中的、在对象实例化以前的数据.

    类方法中包含一个隐藏参数self,它是指向类本身的一个参数

    字段不能定义在published区域.在没有任何修饰符的情况下,默认就是published.

  Ttest = class
  private
    class var
      i: Integer;
      var
      n: Integer;
  public
    var
      j: Integer;
    class var
      k: Integer;
    class procedure Ptest;
    class function Ftest(o: integer): Integer;
  end;
---------------------------------
{ Ttest }

class function Ttest.Ftest(o: integer): Integer;
begin
  Result := o * 5;
end;

class procedure Ttest.Ptest;
begin
  i := k * 2;
  ShowMessage(i.ToString);
 ShowMessage(Self.ClassName);
end;
//调用
procedure TForm1.Button1Click(Sender: TObject);
begin
  Ttest.i := 100;    //i=100
//  Ttest.n := 100;  //ERROR,需要create
//  Ttest.j := 100;   // ERROR,需要create
  Ttest.k := 100;   //k=100
  Ttest.Ptest;     //show 200(K*2) show Ttest
  ShowMessage(ttest.Ftest(10).ToString);//show 50(10*5) 
end;

上面的代码中,从头到尾都没有对TTest进行create过,但是对于在定义阶段有加class的方法和字段,跟全局变量一样都是可以直接拿来使用的.相反,没加class的字段,在直接使用时全部都出现了异常.因为他们都需要创建实例后才能正常使用.

警告:使用没有经过初始化(赋值)的字段会抛出异常错误!

类方法就是通过类名就可以访问的方法.或者你可以把它理解为命名空间也行.因为全局变量不被提倡,放到类里面就很好处理了,因为类里面有private,  strict private ,public ,published ,protected等各种保护限制.当你要调用这些类方法或者类函数时,你得这么写: 类名.类方法,或者 类名 .类函数,这样就达到命名空间的效果.

调用时,这个类可以完全不用create而直接使用它内部的类方法与类函数:

procedure TForm1.Button1Click(Sender: TObject);
var
  MyType: TMyType;
  str: string;
begin
  MyType.Ptest;
  str := MyType.F1;
  MyType.Destroy;
end;

  2.类的静态方法

{现在的 Delphi 不仅仅有类方法, 同时有: 
  类变量: class var
  类常量: class const
  类类型: class type
  类属性: class property

  静态类方法就是给类属性来调用的, 它可以存在于私有区(private), 
  譬如下面的 SetName 就是一个静态类方法:
}
TMyClass = class(TObject)
  private
    class var FName: string;
   class procedure SetName(const Value: string); static; {静态类方法又多了一个 static 指示字}
  published
   class property Name: string read FName write SetName;
end;

 静态类方法相比类方法

  1.静态方法多了一个关键字,就是在定义的末尾加上了Static.

  2.静态方法没有隐藏的self参数,可以被当作callBack函数传给Windows API函数.

静态方法的调用约定:

  只要记住Windows API参数调用是从右往左,D的调用方式跟他是想反的.

  凡是在D里面调用Windows API函数的,肯定要加 static;stdcall ; 进行转化

  

  下面这一段转自https://blog.csdn.net/aaa000830/article/details/79986275 

-----------------------------------------------------------------------------------------------------------------------

注: 使用错误,或者在该加的地方没有加,可能会出现"privileged instruction"错误,或者地址访问错误。

常见的调用惯例有register, pascal, cdecl, stdcall, safecall。函数的调用管理决定了参数如何传递给子过程,并从堆栈中退出,以及寄存器在参数传递中的使用,错误和异常的处理。Delphi中默认的调用惯例是register。
  1) register和pascal:参数从左向右传递,也就是说最左边的参数最先求值并传入,最右边的参数最后求值和传入。cdecl,stdcall和safecall则按从右向左方向。
  2) 对于除cdecl之外的所有调用惯例,函数/过程在返回的时候要把堆栈中的参数退栈。对cdecl惯例,调用者在被调用的过程返回后执行参数退栈操作
  3) register调用惯例最多能用3个CPU寄存器来传递参数,而其它调用惯例只能通过堆栈来传递参数
  4) safecall调用惯例实现了异常的防火墙。在Windows上实现了跨进程的COM错误通知机制。
  5) register调用效率最高,因为它避免了堆栈的创建。Delphi中published属性必须是register。
  6) cdecl常用于调用C/C++编写的共享库中的函数;但是,如果要调用外部代码,那么一般要用stdcall和safecall
  7) 在Windows上,系统的API都是stdcall和safecall;在其它操作系统上通常用cdecl(注意:stdcall比cdecl效率要高)
  8) 在dual-interface(双接口)方法中必须用safecall惯例。
  9) pascal惯例是为了向后兼容;near/far/export用于16位Window编程中的函数调用,在32位的应用程序中不发挥作用,仅仅是为了向后兼容。

  10)即使静态方法只有一个参数,也要标明调用约定,因为不同的方式,汇编会最后给你弄不同的返回方式.
下表进行了总结:
Calling conventions Parameter order Clean-up Passes parameters in registers?
register Left-to-right Routine Yes
pascal Left-to-right Routine No
cdecl Right-to-left Caller No
stdcall Right-to-left Routine No
safecall Right-to-left Routine No

--------------------转载结束-----------------------------------------

 静态类的属性(class property):

  Ttest = class
  private
    class var
      FMyName: string;
  public
    class function GetMyName: string; static;
    class procedure SetMyName(Value: string); static;
  //下面这两种方法是等价的,二选一
    class property MyName: string read GetMyName write SetMyName;
    class property DirectName: string read FMyName write FMyName;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Ttest.SetMyName('张飞');
  ShowMessage(Ttest.FMyName);  //show 张飞
  ShowMessage(Ttest.GetMyName);  //show 张飞
end;

{ Ttest }
class function Ttest.GetMyName: string;
begin
  Result := fmyname;
end;

class procedure Ttest.SetMyName(Value: string);
begin
  fmyname := Value;
end;

这是说一下class var 与 var的区别.var是用来与class var做区分的,算了,我文笔不太好,还是用代码来说明吧

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第7张图片

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第8张图片

 还有一种方法,就是把calss var变量写在限制区域的最后面,这样就不会影响到其他字段了,比如

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第9张图片

类的构建函数与解析函数.

  构建/解析函数优先级大于单元文件初始区(iniialization)与单元结束区(finalization).

  我们可以通过constructor和destructor关键字创建多个构建/解析函数,却不能使用class创建第二个类构建/类解析函数

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第10张图片

  单例模式  (Singleton Pattern)

Singleton Pattern模式的设计意图是:保证一个类仅有一个实例,并提供一个访问他的全局访问点。单例模式在应用开发中比较常见,如 Application 或 Logger。在面试考试中出现率很高,别看书上一面带过,实际上还是有点复杂的,可以在B站上面搜索看看.

在 Delphi 的以前版本中,实现单例模式比较“另类”,自从 Delphi 后期加入一些新的语法元素后,单例模式的实现显得更为标准,和 C++、Java 中的实现方法几乎一致,最主要原因就是 Delphi 加入了类变量的支持,关键字为”class var”。
Delphi 同时支持类属性,可以让单例的访问更为友好;同时需要注意,在实现单例模式时,一定不要忘记把类本身的 Create 构建函数隐藏,否则的话,单例的实现将没有意义。
下面的代码实现了单例模式的 TLogger:

type
  TLogger = class ( TObject )
  private
    class var
      FInstance: TLogger;
    class function GetInstance: TLogger; static;
  protected
    constructor Create;
  public
     procedure Login;
    procedure Logout;
    class procedure ReleaseInstance;
    class property Instance: TLogger read GetInstance;
  end; 

Delphi Class of 类引用

以下内容转自Delphi Class of 类引用 - 走看看

Delphi Class of 类引用也就是类的类型,也可说是指向类的指针

Type
    TControlCls = Class of TControl;
    function CreateComponent(ControlCls: TControlCls): TControl;
    begin
        result:=ControlCls.Create(Form1);
        ...
    end;

前者要求传入一个类, 而后者要求传入一个对象(类的实例)

type
MyClassRef=calss of CMyClass //表示MyClassRef为指向CMyClass或其父类的指针

类的引用就像指向类的指针一样
类引用就是类的类型,可以声明一个类引用变量赋给它一个类,可以通过这个变量创建对象的实例。

类的类,当你不确定调用的类模型时候用到类的类。也可以说是类指针~

System单元的TObject有如下方法:
function ClassType: TClass;

它就是获取对象的类类型,它的返回类型TClass就是class of TObject。

因为所有类都派生自TObject,所以所有对象都可以调用ClassType。比如:

procedure TForm1.Button1Click(Sender: TObject);
var
S: TStringList;
C: TClass;
begin
S := TStringList.Create;
C := S.ClassType;
ShowMessage(C.ClassName);//对话框会显示出来TStringList,相当于TStringList.ClassName
S.Free
end;

而C.ClassName调用的是TObject的类方法ClassName,

原型:class function ClassName: ShortString;

就是说不需要用实例化的对象去调用,直接用类去调用就行了;不过用对象调用也是可以的,因为对象空间也保存了类的VMT地址。

用类调用形如:TStringList.ClassName
用对象调用形如:S.ClassName

一个有意思的语法 :

class help for  类助手(我不知道该叫什么,书上译为类别助手), 对现有的类进行扩展,如果把类理解为一个容器,那么这条语法的作用是对现有的类的容器的成员进行扩展或者修改。

  TTest = class    //主类,正常定义
  private
    Fnumber: Integer;
    FText: string;
  public
    FText2: string;
    procedure increase;
    constructor Create;
  end;

  TTestHelp = class helper for TTest    //类助手,关键字为class Helper for后面跟主类名
  private
    procedure Show;  //可以扩充主类的属性和方法
  public
    procedure increase;   //将会覆盖掉原有的属性
    function test(): string;  //可以扩充主类的属性和方法
    constructor Create(); //将会覆盖掉原有的方法
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ ttest }

constructor TTest.Create();  
begin
  inherited;
  Fnumber := 100;
  FText := '123';
  FText2 := 'TTest.Create';
  Form1.Memo1.Lines.add(FText2);
end;

procedure TTest.increase;
begin
  Inc(Fnumber);
  Form1.Memo1.Lines.add(Fnumber.ToString);
end;

{ TTestHelp }

constructor TTestHelp.Create();
begin
  inherited;
  Fnumber := 100;
  FText := 'abc';
  FText2 := 'TTestHelp.Create';
  Form1.Memo1.Lines.add(FText2);
end;

procedure TTestHelp.increase;
begin
  Dec(Fnumber);
  Form1.Memo1.Lines.add(' TTestHelp.increase:' + Fnumber.ToString);
end;

procedure TTestHelp.Show;
begin
  Form1.Memo1.Lines.add('TTestHelp.Show');
end;

function TTestHelp.test(): string;
begin
  Result := 'TTestHelp.test';
  Form1.Memo1.Lines.add(Result);
end;

//调用
procedure TForm1.Button1Click(Sender: TObject);
var
  test: TTest;
begin
  test := TTest.Create;  
  test.show;
  test.test();
end;

输出如下:

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作_第11张图片

概括一下类助手的特点:

  1.主类必须是已存在的类,定义时也要注意先后顺序,先定义主类,再定义类助手,否则报错,这一点应该不难理解吧.

  2.类助手可以扩充主类的方法和属性.扩充的方法和属性可以是类方法,类变量与类属性,甚至还可以是虚拟方法!

  3.字段只能定义在主类中,类助手中不允许定义字段,否则会提报错: E2599 字段定义不允许在帮助器类型中

  4.主类和类助手是一体的,相当于把类助手的所有方法和属性都并到了主类里,同名即覆盖.

  包括构建函数和解析函数.也包括方法和属性的访问权限.比如主类里面是private,类助手是可以把它设置成public覆盖过去的.

  打个比方法,就像我们平时,把文件从A位置复制到B位置,然后中途弹出一个提示框,说存在同名文件,问你要不要覆盖,然后你选择了覆盖.

  5.引用外部资源的类助手文件,必须在USES区域中加入.

  6.编译器只承认最后一个类助手.注意!这里不是覆盖啦!是忽视最后一个类助手之前的所有类助手!!只取最后一个类助手进行覆盖!!然后你是不是想套娃?比如给类助手再配个助手?  

 TTestHelp2 = class helper for TTestHelp    //ERROR : E2021 需要class类型

  天真了吧!解决的方案之一是 真·套 "娃" ,这二是取别名(下面会讲)...重新定义一个类,让它继承原来的主类TTest,然后再给TTest2定义一个类助手,书中建议不要这么用,因为这会让类结构复杂化,源码难于解读.

   TTest2=class (TTest )
     //something
   end;
  TTestHelp2 = class helper for TTest2
  public
    procedure aaa;
  end;

  7.类助手的意义在于弥补主类功能上的不足,而不是覆盖.覆盖会使人解读混乱.希望读者能明白这一点.用下面这个例子帮助大家理解:

我们通常取ListBox当前选中的值,都是

ListBox1.Items [ListBox1.ItemIndex]

现在我觉得它使用起来太麻烦了,我需要按照我自己的方法来实现:

type
  TListboxHelper = class helper for TListBox  //扩展
    function ItemIndexValue: string;
  end;
function TListboxHelper.ItemIndexValue: string;
begin
  Result := '';
  if ItemIndex >= 0 then
    Result := Items [ItemIndex];
end;
//调用
str:= ListBox1.ItemIndexValue;

record helper for 与 class helper for相似,它是针对记录类型的.暂且叫它记录助手吧.

SysUils里的一些记录助手:

TGUIDHelper = record helper for TGUID
TStringHelper = record helper for string
TSingleHelper = record helper for Single
TDoubleHelper = record helper for Double
TExtendedHelper = record helper for Extended
TByteHelper = record helper for Byte
TShortIntHelper = record helper for ShortInt
TWordHelper = record helper for Word
TSmallIntHelper = record helper for SmallInt
TCardinalHelper = record helper for Cardinal
TIntegerHelper = record helper for Integer
TUInt64Helper = record helper for UInt64
TInt64Helper = record helper for Int64
TNativeUIntHelper = record helper for NativeUInt
TNativeIntHelper = record helper for NativeInt
TBooleanHelper = record helper for Boolean
TByteBoolHelper = record helper for ByteBool
TWordBoolHelper = record helper for WordBool
TLongBoolHelper = record helper for LongBool
TCurrencyHelper = record helper for Currency // added in Delphi 11

类型助手:

  我们上面说过,编译器对于类助手与记录助手,只会识别最后一个助手,除了真套娃,还有其他方法吗?书上说:定义一个类型别名.类型别名会被编译器当成一个全新的类型,所以它可以拥有它自己的类助手.打个比方:银行规定一个人只能开一个账户(我们抛开银行开户的逻辑不谈),张三开了一个账户之后 ,又取了个小名叫张飞又开了一个账户,这样张三就有了两个账户了(助手).当张三想用第一个账户时,他就是张三,当他想用第二的账户时,他只需要转换一下身份,变成张飞就可以了.

type
MyInt = type Integer;  //要取别名,因为系统中已存在 TIntegerHelper = record helper for Integer

TMyIntHelper = record helper for MyInt
    function AsString: string;
end;

function MyIntHelper.AsString: string;
begin
  Result := IntToStr (self);
end;

//调用
procedure TForm1.Button1Click(Sender: TObject);
var
MI: MyInt;
begin
MI := 10;

Show (MI.AsString);

// Show (MI.toString); // this doesn't work 
Show (Integer(MI).ToString)
end;

delphi D11编程语言手册 学习笔记(P344-392) 接口/类操作 - 一曲轻扬 - 博客园 (cnblogs.com)

你可能感兴趣的:(学习,笔记,java)