前言在C++的语言基础当中,除了物件导向、事件驱动的概念之外,模版设计(Template)也是非常重要的一环。然而,C++的开发人员能够善用模版设计的并不多。模版设计这个好物,一般还有一个名称,就是泛型 (Generic),这个好物在Delphi 2009 之后,也已经被加入到 Object Pascal里面了,只是我们实在很少用到它。
然而,江湖一点诀,说破没秘诀,大家对于泛型的少用,很多是因为不知道有这个功能,其次是知道有这个功能却不知道怎么使用。
所以,我们这一篇就来深入浅出的介绍一下『泛型』是什么,顺便用几个简单的范例来使用『泛型』吧。 泛型? 样板? 揭起它的神秘面纱所谓的泛型、样板,其实就是在写code的时候,把需要先定义好型别的宣告用一个关键字 <T> 来取代,未来真正在使用的时候,把T改成真正的型别,就可以让这段code适用于多种不同的型别了。
这样说明,如果您就听懂了,那应该也不需要来看这篇文章,表示您的悟性颇高,属于非常有能力的Programmer。(谜之音:喵的,听的懂我跟你姓! 这不是跟我大学资料结构或物件导向程式设计老师说的一样嘛?)
用实例来说明吧,我们说地球话,才不会被赶回火星……….
以前,写资料结构作业的时候,或者用 Object Pascal 写程式的时候,如果我们要用Delphi 来实作一个堆叠,我们通常会这么想:
可以用底下这张图片来帮助思考:
以一个存放『整数』的堆叠来说,最最基本的宣告一般就会写成这样: TMyStack = class (TObject) Private FElements: array[0..5] of Integer; // 用来存放元素的阵列. Public Function push(element: integer) : integer; // 可以传回 push进去的元素放在什么位置. Function pop: integer; // 直接传回最后一个元素.
Constructor Create(); ovevrride; reintroduce; Destructor Destory(); override; End;
实作的程式码我就不写了,最近实在太多学生上网到处找作业的答案范本。
这段程式码宣告了一个名为 TMyStack 的堆叠类别 (Stack Class),里面是有很多问题的,例如 FElements 只能放 6 个整数,有元素个数的限制,因为我们前面说过这是一个存放『整数』的堆叠,所以 push 方法的参数是整数型别,pop 方法所回传的资料也是整数型别。 先来解决资料长度限制的问题我记不清是从 Delphi 5 还是 Delphi 7开始,Object Pascal就被赋予了可变长度阵列的功能,可以透过 setLength 来调整阵列的长度,宣告的写法可以写成:
Var varLengIntArray : array of Integer;
调整长度的作法则是: setLength(varLengIntArray, 20);
后面的数字就是阵列调整后的长度。
这样的作法,让上面的整数堆叠阵列脱离了固定长度的限制,改写过的 Class 宣告就会变成: TMyStack = class (TObject) Private FElements: array of Integer; // 用来存放元素的阵列. FElementCount: integer; Public Function push(element: integer) : integer; // 可以传回 push进去的元素放在什么位置. Function pop: integer; // 直接传回最后一个元素.
Property count: integer; read FElementCount;
Constructor Create(); ovevrride; reintroduce; Destructor Destory(); override; End; 这样修改以后,push跟pop方法里面也都要有相对应的程式修改,例如在 Create的时候,就要先对 FElementCount 做初始化,push 跟 pop 方法里面,也得调整长度:
Constructor TMyStack.Create(); Begin Inherited Create();
FElementCount := 0; // 初始化,把元素个数设为 0; setLength(FElements, 0); // 初始化,把阵列长度也设为 0; end;
function TMyStack.push(element: integer) : integer; begin Inc(self.FElementCount); // 把元素个数加一 setLength(FElements, self.FElementCount); // 把阵列长度也多加一个
self.FElements[self.FElementCount -1] := element; // 把要 push 的元素放在新增的 // 阵列位置上 End;
Function TMyStack.pop: integer; Begin Result := self.FElements[self.FElementCount -1]; // 把最后一个元素回传.
Dec(self.FElementCount); // 把元素个数减一 setLength(FElements, self.FElementCount); // 把阵列长度也多减掉一个 end;
这样修改完以后,整数堆叠就没有长度限制了。
但是,我们只需要整数堆叠吗? 会不会明天要一个字串堆叠? 后天会不会要一个自定 record 或者 class 的堆叠?
如果每次需要堆叠,就要重写一次上面的程式码,而要修改的地方只有型别,那不是烦死人了?如果又好死不死遇到堆叠里面要加一些额外的功能(客户的想像力永远走在我们前面, 你说是吧?),那所有堆叠的程式码要一个一个去修改,光想像就很想对电脑下毒手……… 那有没有什么方法,可以让我们写一个堆叠,就可以存放所有型别? 当然有,泛型,就是我们的救赎啊…….. 要使用泛型,我们得在 use 区段里面引入 System.Generics.Collections,这里面有非常多的好物可以用。
我们首先把前面已经改过的类别宣告,再做一些小调整,使用TArray<T> 这段程式码来取代 array of Integer,让 FElements 可以容纳各种型别的资料:
TMyStack<T> = class (TObject) Private FElements: TArray<T>; // 用来存放元素的阵列. FElementCount: integer; Public Function push(element: T) : integer; // 可以传回 push进去的元素放在什么位置. Function pop: T; // 直接传回最后一个元素.
Property count: integer; read FElementCount;
Constructor Create(); ovevrride; reintroduce; Destructor Destory(); override; End;
实作的程式码则需要修改为: Constructor TMyStack<T>.Create(); Begin Inherited Create();
FElementCount := 0; // 初始化,把元素个数设为 0; setLength(FElements, 0); // 初始化,把阵列长度也设为 0; end;
function TMyStack<T>.push(element: T) : integer; begin Inc(self.FElementCount); // 把元素个数加一 setLength(FElements, self.FElementCount); // 把阵列长度也多加一个
self.FElements[self.FElementCount -1] := element; // 把要 push 的元素放在新增的 // 阵列位置上 End;
Function TMyStack<T>.pop: T; Begin Result := self.FElements[self.FElementCount -1]; // 把最后一个元素回传.
Dec(self.FElementCount); // 把元素个数减一 setLength(FElements, self.FElementCount); // 把阵列长度也多减掉一个 end;
我的老天鹅啊,这真是太方便了吧,程式码这样写就好了? 那使用上要怎么用? 就这样: Var integerStack : TMyStack<Integer>; begin integerStack := TMyStack<Integer>.Create; try integerStack.push(79); integerStack.push(7); integerStack.push(21); integerStack.push(13); finally integerStack.Free; end; end;
上述这段程式码,在 finally执行以前,就会建立出以下图为范例的堆叠资料了:
我们也可以做字串堆叠: Var
stringStack : TMyStack<String>; begin stringStack := TMyStack<String>.Create; try stringStack.push(‘这’); stringStack.push(‘就’); stringStack.push(‘是’); stringStack.push(‘泛’); stringStack.push(‘型’); stringStack.push(‘啊’); finally integerStack.Free; end; end;
上述这段程式码,在 finally执行以前,建立出来的堆叠资料则如下图:
这样一来,程式码都没有变,我们只在使用 TMyStack<T> 这个 Class 的时候,在宣告、建立Class的时候指明要使用什么型别,就能够自由的把一份程式码用在各种不同型别上了,是不是很方便?
在 System.Generics.Collections 里面,TList<T>更是好用,以前我们得要自己做TObjectList,才能透过所有物件都是从 TObject 衍生出来的特性建立出可以储存物件的List,而且每次使用的时候还得做型别转换才能正确使用。
现在透过 TList<T>,这些额外的程式码、型别转换的工作就都省下来了,甚至连TStack<T>, TQueue<T>, 也都有提供,是不是也让您想要玩玩看了呢?
泛型说穿了,就是把原本我们需要先写明的型别,用<T>这个关键字取代掉,而改以在实际宣告、使用的时候才叙明型别,这样一来,真的省下好多好多程式码,也省下很多时间可以做其他更有意义的事情了,当然,这些事情还是要我们自己去发掘的,大家加油! |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论