谈一谈从 Delphi 2009 之后就支援的重要功能 – 泛型 (Generic)

前言

在C++的语言基础当中,除了物件导向、事件驱动的概念之外,模版设计(Template)也是非常重要的一环。然而,C++的开发人员能够善用模版设计的并不多。模版设计这个好物,一般还有一个名称,就是泛型 (Generic),这个好物在Delphi 2009 之后,也已经被加入到 Object Pascal里面了,只是我们实在很少用到它。

然而,江湖一点诀,说破没秘诀,大家对于泛型的少用,很多是因为不知道有这个功能,其次是知道有这个功能却不知道怎么使用。

所以,我们这一篇就来深入浅出的介绍一下『泛型』是什么,顺便用几个简单的范例来使用『泛型』吧。

泛型? 样板? 揭起它的神秘面纱

所谓的泛型、样板,其实就是在写code的时候,把需要先定义好型别的宣告用一个关键字 <T> 来取代,未来真正在使用的时候,把T改成真正的型别,就可以让这段code适用于多种不同的型别了。

这样说明,如果您就听懂了,那应该也不需要来看这篇文章,表示您的悟性颇高,属于非常有能力的Programmer。(谜之音:喵的,听的懂我跟你姓! 这不是跟我大学资料结构或物件导向程式设计老师说的一样嘛?)

用实例来说明吧,我们说地球话,才不会被赶回火星……….

以前,写资料结构作业的时候,或者用 Object Pascal 写程式的时候,如果我们要用Delphi 来实作一个堆叠,我们通常会这么想:

  • 堆叠嘛,资料要先进先出,所以要宣告一个阵列来储存资料
  • 然后要定义一个 Push 方法,把资料放进去
  • 也要定义一个Pop方法,把资料取出来
  • 因为是堆叠,所以是后进先出 (最后放进去的要最先被取出,只有一个进出口)

可以用底下这张图片来帮助思考:

 

以一个存放『整数』的堆叠来说,最最基本的宣告一般就会写成这样:

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>这个关键字取代掉,而改以在实际宣告、使用的时候才叙明型别,这样一来,真的省下好多好多程式码,也省下很多时间可以做其他更有意义的事情了,当然,这些事情还是要我们自己去发掘的,大家加油!

猜你喜欢

转载自www.cnblogs.com/dennieschang/p/9203615.html