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

  • 相关阅读:
    ORACLE NOLOGGING研究
    由2013星光大道总决赛同步程序猿怎样成功?
    configure: error: mysql configure failed. Please check config.log for more information.
    The 2014 ACM-ICPC Asia Mudanjiang Regional Contest(2014牡丹江区域赛)
    about service in android
    极客标签互动课程系列 - Javascript生成SVG动画素描特效
    sql -- 移除数据中的换行符和回车符
    前端project师必需知识点
    spring装配集合
    【java项目实践】具体解释Ajax工作原理以及实现异步验证username是否存在+源代码下载(java版)
  • 原文地址:https://www.cnblogs.com/dennieschang/p/9203615.html
Copyright © 2011-2022 走看看