这本书可以在 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
演示的代码很长,不过能看到这里的朋友应该能看得懂,先说结论吧
普通接口在使用时,有接口计数器介入,而在泛型接口里是没有接口计数器介入的.
泛型接口可能有多重约束,比如构造函数的约束,以及不同类型的泛型函数的约束.说人话就是使用具有接口约束的泛型类型,具有接口的优点,而去掉了接口的缺点.
<然后我发现,网上居然有文章,是跟书上的一模一样,所以我就直接搬过来吧>
通过接受一个限定的参数,这个参数是实现某个接口的类,比较起直接接受泛型,而限制这个泛型的类要更加灵活。也就是通常所说的面向接口式的编程。这样可以达到调用实现了这个接口的各种泛型的实例。这种对泛型使用接口约束的应用,在.net框架中有很广泛的应用.
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)。同时也提供了 Capacity跟 Count 属性。只是 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中能有一种东西,只要出了它的作用域,它就能自动析构!
标签:begin,end,D11,对象,delphi,477,泛型,class,procedure From: https://www.cnblogs.com/yoooos/p/16979400.html