@[TOC]
第十章 泛型
泛型举例
function TSample<T>.GetF1: T;
begin
Result := self.F1;
end;
procedure TSample<T>.SetF1(value: T);
begin
self.F1 := i;
end;
var
IntegerObj: TSample<Integer>;
StringObj: TSample<String>;
begin
IntegerObj.P1 := 89;
StringObj.P1 := 'Delphi';
Writeln(IntegerObj.P1);
Writeln(StringObj.P1);
Readln;
end.
类的类型参数只能用于当前类或派生类中,但是类中使用的类型参数却不一定是类名称后指定的参数;
类型参数的有效范围等同于当前范围中声明的局部变量;
从泛型类派生子类时,父类可以有两种形式:带有形参的泛型类,带有实参泛型类;
父类
type
TSample<T> = class
private
type
TInnerRec<R> = record
i: R;
end;
TQux<T> = class
X: Integer;
end;
public
FSample: T;
function fun<F>(value: F): F;
end;
子类
type
T1<T> = class(TObject)
function M1: T;
end;
T2<T> = class(T1<T>)//or T2<A> = class(T1<A>) whatever
end;
T3<T> = class(T1<Integer>)
end;
T2<T>
的父类是一个带有形参的类T1<T>
;
T3<T>
的父类则是一个带有实参的类T1<Integer>
;
注释中的A可以更换,但是两者必须一致;
泛型接口和泛型记录的声明举例
type
R1<T> = record
F: T;
function M1<M>(v1: M; v2: T): M;
end;
I1<T> = Interface
End;
I2<T> = Interface(I1<T>)
End;
I2<T> = Interface(I1<Integer>)
End;
泛型例程指针的声明举例
type
fun<T> = function(s: T): T;
MyType = Integer;
function M1(value: Integer): MyType;
begin
end;
function M2(value: Integer): Integer;
begin
end;
procedure M3(value: Integer);
begin
end;
var
pF:fun<Integer>;
begin
pF := M2;
pF := M1;
pF := M3;//wrong
end.
注:类型参数可以出现在方法或例程的参数列表中,也可以作为返回值的类型;
泛型例程指针所指向的例程必须:
1.参数与返回值的类型结构必须相同,有返回值的函数指针不能指向没有返回值的过程,反之亦然;
2.参数列表必须与泛型例程指针声明时的形式一一对应,参数类型必须有相同的内部结构,二者相应参数的传参方式必须相同;
泛型类的实例化四条规则:
1.由于实例化发生在编译期间,运行时相当于调用了不同的类,所以如果在泛型类中定义了一个类变量,不同版本的类变量会保存各自的值;
2.如果一个泛型类中套嵌了另一个类,在实例化这个类时,需要实例化母类;
3.在实例化泛型类的子泛型类时,基类自动类型化;
type
T1<Tk> = class
i: Tk;
end;
T2<T> = class(T1<T>)
end;
var
obj: T2<String>;
begin
obj.i := 'Delphi';
end.
在使用String实例化T2后,编译器自动产生了String版本的T1;
类型参数的标识符如T不作为类识别的依据,在声明T2时,我们将T1中的Tk变成T,
这完全没有任何关系,只要基类和派生类的类型参数标识符相同即可;
4.类中的方法没有得到明确的类型值时,可根据实际的参数值进行推测,从而实例化整个类;
type
T1 = class
procedure test;
procedure M<Y>(S: Y);
end;
procedure T1.M<Y>(S: Y);
begin
end;
procedure T1.test;
begin
self.M<String>('wanginn');
self.M('waggg');
self.M(23);
end;
10.3泛型方法重载
重载方法的泛型版本和非泛型版本之间,编译器有限选择非泛型版本;
10.4泛型类型兼容
举例
type
GArray<T> = array [0..3] of T;
FourArray = GArray<Integer>;
var
v1: GArray<Integer>;
v2: FourArray;
begin
v1 := v2;
end.
编译会通过,因为泛型中不是通过数据类型的名称来判断,而是根据实际结构;
即使是在实例化时,依然检查类型参数所指向的最终类型是否相同;
举例
type
GArray<T> = array [0..3] of T;
Int = Integer; //new code
FourArray = GArray<Int>;
var
v1: GArray<Integer>;
v2: FourArray;
begin
v1 := v2;
end.
依然通过编译;
10.5泛型的限定
泛型的限定:通过某种方法可以限制泛型在实例化时能够接受的类型参数;
五种方案:接口限定,类名称限定,constructor关键词限定,class关键词限定,record关键词限定
接口限定举例
type
T1 = Interface(IInterface)
end;
T1 = class(TInterfaceObject, I1)
end;
TCon<T: IInterface> = class
end;
var
obj: Tcon<I1>;
begin
end.
注:在参数类型后加上冒号,后面接一个或多个接口名称,这样就限定了这个类型必须是一个类;
这个类实现了多少其他的接口无关紧要,关键是这个类或者这个类的祖先类,直接实现了指定的所有接口本身;
类名称限定举例
type
T1 = class
end;
T2 = class(T1)
end;
TCon<T: T1> = class
end;
var
obj: TCon(T2;)
begin
end.
在实例化TCon时,T的值只能是一个类或nil,如果是一个类,则必须是T1或T1的派生类;
但是Delphi不允许直接使用TObject来进行限定;
constructor关键词限定举例
type
TCon<T: constructor> = class
procedure test;
end;
procedure TCon<T>.test;
var
obj: T;
begin
obj := T.Create;
end.
这种限定一般很少使用,使用这种限定可以直接调用T的默认构造函数及无参数的Create函数而无需知道T到底什么类型;
当然T肯定是一个类,只有类才有默认构造函数;
class关键词限定举例
type
TCon<T: class> = class
end;
var
obj: TCon<TObject>;
这种方法可以限定前文提到的TObject;
此关键词可以与constructor联用,虽然没有什么意义;
record关键词限定举例
type
T1 = class
end;
TCon<T: record> = class
end;
var
o1: TCon<T1>; //wrong
o2: TCon<Integer>;
record限定了用于实例化的类型必须是值类型,即所有的简单类型加上记录类型;
class或record关键词不能和类名称联合限定;
record关键词也不能和class或constructor的任何一个联用;
10.6 TList类
TList类似于动态数组,是一个连续的队列,允许插队;
add添加到队尾;
insert插入指定位置;
List.Insert(3, 78)把78插入list作为第4个成员;
注:TList中成员序数绝对是从0开始,用户无法改变这点;
添加成员时,务必保证这个成员在原来的队列中存在;
AddRange和InsertRange,将数组添加到队列;
举例
program Project3;
{$APPTYPE CONSOLE}
uses
SysUtils, Generics.Collections;
var
ListArray: array [0..3] of Integer;
I: Integer;
List: TList<Integer>;
begin
List := TList<Integer>.Create;
for I := 0 to 3 do
ListArray[I] := I + 101;
for I := 0 to 4 do
List.Add(I + 1);
List.InsertRange(3, ListArray);
for I := 0 to List.Count - 1 do
Writeln(List[i]);//1,2,3,101,102,103,104,4,5
Readln;
end.
五种删除成员的方法:
Remove,Delete,DeleteRange,Extract,Clear;
remove删除值为value的成员并返回序数,如果很多个value,删除最小序数的,不存在则返回-1;
如2 7 3 7 5删除7,变成2 3 7 5,返回1;
Extract和Remove非常相似,区别是Extract返回被删除的成员的值;
不存在则返回T类型的默认值,如0,nil等
Delete直接返回指定序数的成员;
DeleteRange举例
1,2,3,4,5 >>> list.DeleteRange(1, 2) >>> 1, 4, 5
Clear清空所有成员;
Exchange将Index1和Index2指定的成员的值互换;
Move将CurIndex指定的值移动到NewIndex指定的位置,这两个成员要存在;
Sort将所有成员的值顺序排序,默认由小到大;
program Project4;
{$APPTYPE CONSOLE}
uses
SysUtils, Generics.Collections;
var
ListArray: array [0..3] of Integer;
I: Integer;
List: TList<Integer>;
begin
List := TList<Integer>.Create;
for I := 0 to 4 do
List.Add(I + 1);
List.Exchange(0, 4);
for I := 0 to List.Count - 1 do
begin
Write(List[I]);
Write(' ');
end;
Writeln;
List.Sort;
for I := 0 to List.Count - 1 do
begin
Write(List[I]);
Write(' ');
end;
Writeln;
List.Move(1, 3);
for I := 0 to List.Count - 1 do
begin
Write(List[I]);
Write(' ');
end;
Writeln;
Readln;
end.
查询队列中是否含有某个值的三种方法:Contains,IndexOf,LastIndexOf;
Contains用于查询队列中是否存在某个值与指定值相同,相同时可以用IndexOf返回此成员的序数;
如果存在多个这样的成员,IndexOf返回第一个成员的序数,LastIndexOf返回最后一个这样的成员的序数;
不存在时,都返回-1;
TList的属性Count:当前TList对象中的成员数目;
Capacity表示当前对象的容量,即能够存储的最大成员数目,设置这个属性值类似设置动态数组的成员数量;
Item索引,list.Item[2]
,可以省略写成list[2]
;
这里item是默认属性,所以可以省略