delphi D11编程语言手册 学习笔记(P424-477) 泛型

 

 这本书可以在 Delphi研习社②群 256456744 的群文件里找到. 书名: Delphi 11 Alexandria Edition.pdf

 

泛型在C++中叫做类型模板(template classes),单从字面上理解,模板是将一个事物的结构规律予以固定化、标准化的成果,它体现的是结构形式的标准化

也就是说泛型中的类是不确定的,把指定的类型放进出,出来的就是什么样的类型,避免同样格式的类写N遍!像类和接口一样,泛型也有个不成文的约定,就是以字母T来表示泛型.

泛型大多数情况下是用来处理集合对象的.这也是泛型的最基础用法.

TKeyValue<T> = class  //定义泛型
  private
    FKey: string;
    FValue: T;
    procedure SetKey(const Value: string);
    procedure SetValue(const Value: T);
  public
    property Key: string read FKey write SetKey;
    property Value: T read FValue write SetValue;
  end;

{ TKeyValue<T> }

procedure TKeyValue<T>.SetKey(const Value: string);
begin
  FKey := Value;
end;

procedure TKeyValue<T>.SetValue(const Value: T);
begin
  FValue := Value;
end;

//调用
var
  //这里把泛型声明为string类型,你也可以声明为更多类型
  //但是一旦确定了这个类型,你就不再传入不兼容的值了,比如 test.setvalue(100)就会出错
test: TKeyValue
<string>;   begin test := TKeyValue<string>.Create; test.SetValue('abc');  //设置参数要与声明的类型一致,不然会产生错误 ShowMessage(test.FValue); //show abc test.Free; end;

内联变量使用泛型:

begin
   var kvi:=TkeyValue<integer>.Create;
end;

除了类,泛型还可以用于定义数组,结构,返回值,方法和参数

TArr<T> = array[0..9] of T; {定义一个泛型数组}

TRecord<T> = record // 或者是 TRecord = record 都可以
    class procedure ArrayAdd(var Arr: TRecord<T>; const item: T); static; //结构中的 class 方法必须是 static 的
  end;

下面这个例子叫泛型函数,仅限于告知各位,泛型可以这么用,但是极不推荐大家用,也是由书上例子引伸开来的,我极度怀疑是作者在装B.虽然在type时完全合法合规,但是很难处理各类型间的运算问题!

type
  TTese<TInput1, TInput2, TRenturn>= class
  public
    function Fa(value1: TInput1; value2: TInput2): TRenturn;
  end;

{ TTese<TInput1, TInput2, TRenturn> }
{ 首先是这参数就不能直接使用,然后就是TReturn的类型也没有确定下来.根本就是在自找苦吃 }
function TTese<TInput1, TInput2, TRenturn>.Fa(value1: TInput1; value2: TInput2): TRenturn;
begin
  Result := value1 + value2; //E2015 运算符不适用于此运算对象类型
end;

泛型类型的兼容性规则

下面的代码会因为类型不兼容导致出错,尽管它们的结构是模一样的

type
  TArrayOf10 = array[1..10] of Integer;
procedure TForm1.Button1Click(Sender: TObject);
var
  Array1: TArrayOf10;
  Array2: TArrayOf10;
  Array3, array4: array[1..10] of Integer;
begin
  Array1 := Array2;
  Array2 := Array3; // Error,一个是类数组,一个是数组
  Array3 := array4;
  array4 := Array1; // Error
end;

 解决的方案是用泛型进行定义.以获得类型兼容.

type
  TIntGenericArray<T>= class
    arr: array[1..10] of T;// 定义成泛型,而不是显类数组
  end;

  TGenericArray = TIntGenericArray<Integer>;// 定义为整形类数组

procedure TForm1.Button1Click(Sender: TObject);
var
  Array1: TIntGenericArray<Integer>;  //整形类数组
  Array2: TIntGenericArray<Integer>;  //整形类数组
  Array3, array4: TGenericArray;  //整形类数组
begin
  Array1 := TIntGenericArray<Integer>.Create;
  Array2 := Array1;
  Array3 := Array2;
  array4 := Array3;
  Array1 := array4;
end;

通过继承泛型来进行兼容

type
  TIntGenericArray<T>= class
    arr: array[1..10] of T;
  end;

  TGenericArray = class(TIntGenericArray<Integer>);//继承

procedure TForm1.Button1Click(Sender: TObject);
var
  Array1: TIntGenericArray<Integer>;
  Array2: TIntGenericArray<Integer>;
  Array3, array4: TGenericArray;
begin
  Array1 := TIntGenericArray<Integer>.Create;
  Array3 := TGenericArray.Create;
  Array1 := Array3;  //两者兼容
end;

 泛型类型每实例化一次,编译器就会产生一个新的实体,每个实体之间都是独立存在,不会共享源码的. 甚至于在跨单元引用泛型时,编译器也会被迫在目标单元再生成一份相同的源码

这会导致程序体积增大!解决的方案是把泛型里的方法定义在一个非泛型的类里面,然后在目标单元,再定义一个泛型来继承这个普通类.书上是这么说,但是我感觉不太对劲,方法都在普通类里写完了,我还要你这泛型做什么啊?不理解

 

 

泛型类型的函数

  1.Default(T):如果我们传入一个非泛型的类型,它会返回空值,或0,或nil.

  2.TypeInfo(T): 返回当前泛型实例的指针.常作: GetTypeName(.TypeInfo(T));

  3.SizeOf(T):返回内存地址的大小字节.在32位系统返回4Bytes,64位则返回8Bytes;

 

    4.IsManagedType(T)返回一个布尔值.表示该类型在内存中是否受管控,如果是字符串或者动态数组的话,会返回True;

  5.HasWeakRef(T)这个函数是跟支持ARC的编译器相关的,用来判断内存引用是否为弱引用 .需要特定的内存管理支持.

  6.GetTypeKind(T)与TypeInfo(T)类似,但要比TypeInfo(T)更全面.

type

TSampleClass <T> = class
private
  FData: T;
public
  procedure Zero;
  function GetDataSize: Integer;
  function GetDataName: string;
end;
function TSampleClass<T>.GetDataSize: Integer; begin   Result := SizeOf (T); end;
function TSampleClass<T>.GetDataName: string; begin   Result := GetTypeName (TypeInfo (T)); end;
procedure TSampleClass<T>.Zero; begin   FData := Default (T); end;

泛型约束(不建议深入)

  在泛型类型中,我们能对泛型类型的值所做的处理很少 ,通过下面的几个约束,我们能让泛型值做更多的事件.

  1.类型限制(Class Constraints)

    该约束要求T必须是一个类型:<T: class>  这样做的好处是,可以让泛型使用TObject的任何方法了,包括虚拟方法.

type

TSampleClass <T: class> = class
private
  FData: T;
public
  procedure One;
  function ReadT: T;
  procedure SetT (t: T);
end;

procedure TSampleClass<T>.One;    
begin
if Assigned (FData) then
  begin
    Form30.Show ('ClassName: ' + FData.ClassName);//使用TObject的方法
    Form30.Show ('Size: ' + IntToStr (FData.InstanceSize));//使用TObject的方法
    Form30.Show ('ToString: ' + FData.ToString);//使用TObject的方法
  end;
end;
//其他略

//调用 sample1: TSampleClass<TButton>;
 sample2: TSampleClass<TStrings>; sample3: TSampleClass<Integer>; // E2511 Type parameter 'T' must be a class type

  你也可以定义成:<T: Record>,但这跟普通的记录类型并没有多大不同,意义不大 

 2.特定的类型约束

  以下面的举例为例,这种泛型类型只支持组件类型,也就是TComponent的衍生类.用处太少(基本没什么用)

type

TCompClass <T: TComponent> = class

3.接口约束

  演示的代码很长,不过能看到这里的朋友应该能看得懂,先说结论吧

  普通接口在使用时,有接口计数器介入,而在泛型接口里是没有接口计数器介入的.

  泛型接口可能有多重约束,比如构造函数的约束,以及不同类型的泛型函数的约束.说人话就是使用具有接口约束的泛型类型,具有接口的优点,而去掉了接口的缺点.

  <然后我发现,网上居然有文章,是跟书上的一模一样,所以我就直接搬过来吧>

  通过接受一个限定的参数,这个参数是实现某个接口的类,比较起直接接受泛型,而限制这个泛型的类要更加灵活。也就是通常所说的面向接口式的编程。这样可以达到调用实现了这个接口的各种泛型的实例。这种对泛型使用接口约束的应用,在.net框架中有很广泛的应用.

  Delphi 泛型 接口约束的实例 转

 

4.预设构造函数约束<T: class, constructor>

  注意这里的构造函数是没有参数的. 

type

  TConstrClass <T: class, constructor> = class
private
  FVal: T;
public
  constructor Create;
  function Get: T;
end;

constructor TConstrClass<T>.Create;
begin
FVal := T.Create;
end;

 

 

泛型容器(STL)

编译器自带的泛型容器,可以通过引用System.Generics.Collections单元调用.

type

TList<T> = class

TQueue<T> = class

TStack<T> = class

TDictionary<TKey,TValue> = class

TObjectList<T: class> = class(TList<T>)

TObjectQueue<T: class> = class(TQueue<T>)

TObjectStack<T: class> = class(TStack<T>)
TObjectDictionary<TKey,TValue> = class(TDictionary<TKey,TValue>)

  1.TList<T>列表容器

  它包含了所有原有的方法,像是 Add, Insert, Remove, 以及 IndexOf(LastIndexOf 是从后面找; 也可用 List.Contains(str) 判断是否包含 str)。同时也提供了 CapacityCount 属性。只是 Items 变成了 Item,而且是默认属性(可以直接用变量名称加上方括号来存取,不用透过属性名称),不过我们不常直接用这种方式存取。

  泛型容器单元(Generics.Collections)[1]: TList<T>

  TQueue队列列表, 先进先出(从头开始删除):参考仓库准则,先进先出 
  TQueue 主要有三个方法、一个属性:  Enqueue(入列)、Dequeue(出列)、Peek(查看下一个要出列的元素);  Count(元素总数).  泛型容器单元(Generics.Collections)[2]: TQueue<T>

 

  TStack堆栈列表,后进先出(从后面开始删除):像弹匣一样,后压入的子弹会被最先打出去. 

  TStack 主要有三个方法、一个属性:Push(压栈)、Pop(出栈)、Peek(查看下一个要出栈的元素);Count(元素总数). 泛型容器单元(Generics.Collections)[3]: TStack<T>

  TDictionary 类似哈希表.是所有泛型容器中最值得我们花时间来学习的一种类型.用来存储对象时,TDictionary 的效率要比TStringList高得多.  泛型容器单元(Generics.Collections)[4]: TDictionary<T>  

uses Generics.Collections;

procedure TForm1.Button1Click(Sender: TObject);
var
  Dictionary: TDictionary<string,Integer>;
  b: Boolean;
  T: Integer;
begin
  Dictionary := TDictionary<string,Integer>.Create();

  {添加}
  Dictionary.Add('n1'111);
  Dictionary.Add('n2'222);
  Dictionary.Add('n3'333);

  {判断指定的 Key 是否存在}
  b := Dictionary.ContainsKey('n1');
  ShowMessage(BoolToStr(b, True));   {True}
  b := Dictionary.ContainsKey('n4');
  ShowMessage(BoolToStr(b, True));   {False}

  {判断指定的 Value 是否存在}
  b := Dictionary.ContainsValue(111);
  ShowMessage(BoolToStr(b, True));   {True}
  b := Dictionary.ContainsValue(999);
  ShowMessage(BoolToStr(b, True));   {False}

  {使用 AddOrSetValue 时, 如果 Key 存在则替换值; 此时如果用 Add 将发生异常}
  Dictionary.AddOrSetValue('n1'123);
  ShowMessage(IntToStr(Dictionary['n1'])); {123}

  {使用 AddOrSetValue 时, 如果 Key 不存在则同 Add}
  Dictionary.AddOrSetValue('n4'444);
  ShowMessage(IntToStr(Dictionary['n4'])); {444}

  {尝试取值}
  if Dictionary.TryGetValue('n2', T) then
    ShowMessage(IntToStr(T)); {222}
  
  Dictionary.Free;
end;
2.对象容器,包括
type

TObjectList<T: class> = class(TList<T>)
TObjectQueue<T: class> = class(TQueue<T>)
TObjectStack<T: class> = class(TStack<T>)

一旦对象被从列表中删除,对象即被释放.

已经有了: TList<T>、TQueue<T>、TStack<T>、TDictionary<TKey,TValue>
为什么还有: TObjectList<T>、TObjectQueue<T>、TObjectStack<T>、TObjectDictionary<TKey,TValue> ?

还记得 Classes.TList 和 Contnrs.TObjectList 的主要区别吗?
如果元素是对象, Contnrs.TObjectList 在删除元素时会同时释放对象, 而 Classes.TList 不会.

同样在这里, Generics.Collections.TObjectList<T> 会同时释放对象, 而 Generics.Collections.TList<T> 不会.
其他也是一样.

 

泛型容器单元(Generics.Collections)[5]: TObject...<T> 系列

再谈泛型接口

在泛型接口中,不需要GUID作为该接口的标识(或称IID).编译器会在泛型接口实例化时自动建立一个IID,算是隐式定义吧.
type
  IGetValue<T>= interface
    function GetValue: T;
    procedure SetValue(Value: T);
  end;

  TGetValue<T> = class(TInterfacedObject, IGetValue<T>)
  private
    FValue: T;
  public
    constructor Create(Value: T);
    destructor Destroy; override;
    function GetValue: T;
    procedure SetValue(Value: T);
  end;
{ TTest<T> }

constructor TGetValue<T>.Create(Value: T);
begin
  Form1.Memo1.Lines.Add('TTest<T>.Create');
end;

destructor TGetValue<T>.Destroy;
begin
  Form1.Memo1.Lines.Add('TTest<T>.Destroy');
  inherited;
end;

function TGetValue<T>.GetValue: T;
begin
  Result := FValue;
end;

procedure TGetValue<T>.SetValue(Value: T);
begin
  FValue := Value;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  AVal: TGetValue<string>;  {也可以定义为接口AVal:IGetValue<string>;因为和标准接口一样,接口变量也是可以直接用被调用的类来赋值的 }
begin
  AVal := TGetValue<string>.Create(Caption); { Caption: vcl.controls.TCaption }
  try
    Form1.Memo1.Lines.Add('TGetValue value: ' + AVal.GetValue);
  finally
    AVal.Free;
  end;
end;

预先定义的泛型接口

  Generics.Default 单元文件定义了两个用来比较泛型的接口:

  1.  IComparer<T>拥有一个 Compare 方法
  2.    IEqualityComparer<T>则拥有 Equals GetHashCode 方法

type

TComparer<T> = class(TInterfacedObject, IComparer<T>)
TEqualityComparer<T> = class(TInterfacedObject, IEqualityComparer<T>)

TCustomComparer<T> = class(TSingletonImplementation,IComparer<T>, IEqualityComparer<T>)
TStringComparer = class(TCustomComparer<string>)

智能指针(Smart Pointer ) :在构造函数中传入要管理的堆对象的引用,在析构函数里FreeAndNil这个堆对象的引用

智能指针的概念是从C++编程语言而来的.
我们知道,栈对象的声明周期由后台管理,栈对象在声明时进行构造,当方法退出或者类被销毁时(此时栈对象为类的成员变量),栈对象的生命周期也会随着结束,后台自动会调用它们的析构函数并释放栈空间。 而堆对象必须由程序员手动的释放,如果一个方法只有一两个堆对象我们还能应付的过来,但是当堆对象非常多,而且堆对象一般都要经过多个方法的传递、赋值,传递到最后,非常容易忘了delete,造成内存泄露。 能不能让后台也去自动管理堆对象的释放呢?前辈们想到一个办法,就是让一个栈对象包含一个堆对象的引用,当栈对象被后台自动释放时,会调用栈对象的析构函数,于是,在栈对象的析构函数里写下delete堆对象指针的语句。这样,就完成了后台间接管理堆对象

Delphi中的interface
从智能指针的简介中我们可以了解到,要使用智能指针,我们必须得捕获到栈对象的构造函数,将堆对象的指针传入栈对象,由栈对象保存堆对象的指针;还必须捕获到栈对象的析构函数,在栈对象的析构函数里进行对构造函数所传入堆对象指针delete。在c++很容易做到这一点,但是经上面分析,我们无法对Delphi的栈对象进行构造和析构的捕获。  我们可以换一种角度思考,不一定非要是栈对象,只要在Delphi中能有一种东西,只要出了它的作用域,它就能自动析构!

智能指针(Smart Pointer)的实现

 



posted @ 2022-12-19 09:43  一曲轻扬  阅读(688)  评论(0编辑  收藏  举报