看看Delphi中的列表(List)和泛型


前言

        最開始學習數據結構的時候,鏈表,堆棧,隊列,數組,似乎只是一堆概念,隨着使用中慢慢接觸,其對應的模型,功能,一個個躍到眼前,變成了復雜模型數據處理中的最重要的部分。---By Murphy 20180424

1,列表

Delphi中的列表有很多,從數據結構上可以分作:TList(數據鏈),TQueue(隊列),TStack(堆棧),TDictionary(字典表)等等; 而從功能上又可以分作TList(列表),TObjectList(對象列表),TComponentList(組件列表),TStringList(字串列表),TClassList(類列表)。

列表的數據結構性,決定了列表的基礎方法,基本都是以增刪改查,計數,排序為主;而功能列表中,進一步增強了對每個節點元素的檢查和限制,從而增加了針對這種數據類型的更貼合的功能,例如:對象的自動銷毀,或僅結構移除,構建排序比較規則等等。現在就讓我們看幾種具有代表性的列表

a,TList

我們首先來看看TList在DelphiRX10中的源碼定義

  TList = class(TObject)
  private
    FList: TPointerList;     
    FCount: Integer;
    FCapacity: Integer;
  protected
    function Get(Index: Integer): Pointer;
    procedure Grow; virtual;
    procedure Put(Index: Integer; Item: Pointer);
    procedure Notify(Ptr: Pointer; Action: TListNotification); virtual;
    procedure SetCapacity(NewCapacity: Integer);
    procedure SetCount(NewCount: Integer);
  public
    type
      TDirection = System.Types.TDirection;

    destructor Destroy; override;
    function Add(Item: Pointer): Integer;                                                                               //添加一個新元素
    procedure Clear; virtual;                                                                                                  //清空列表
    procedure Delete(Index: Integer);                                                                                    //刪除對象元素並銷毀
    class procedure Error(const Msg: string; Data: NativeInt); overload; virtual;
    class procedure Error(Msg: PResStringRec; Data: NativeInt); overload;
    procedure Exchange(Index1, Index2: Integer);                                                                //交換對象
    function Expand: TList;                                                                                                     //擴展當前對象容量
    function Extract(Item: Pointer): Pointer; inline;                                                                  //移除對象元素,而不是銷毀,並且返回對象指針
    function ExtractItem(Item: Pointer; Direction: TDirection): Pointer;                                   //從當前對象指針開始定向定位,查找到對應對象並移除  
    function First: Pointer; inline;                                                                                             //當前對象指針移動到第一個元素
    function GetEnumerator: TListEnumerator;                                                                       //一個抽象列表,沒看懂這里的擴展性設計
    function IndexOf(Item: Pointer): Integer;                                                                           //檢索元素
    function IndexOfItem(Item: Pointer; Direction: TDirection): Integer;
    procedure Insert(Index: Integer; Item: Pointer);                                                                //插入一個對象元素
    function Last: Pointer;                                                                                                       //當前對象指針移動到最后一個元素上
    procedure Move(CurIndex, NewIndex: Integer);                                                              //將當前對象移動位置
    function Remove(Item: Pointer): Integer; inline;                                                               //移除對象
    function RemoveItem(Item: Pointer; Direction: TDirection): Integer;
    procedure Pack;                                                                                                               //清空空對象

   //TListSortCompare = function (Item1, Item2: Pointer): Integer;
   //TListSortCompareFunc = reference to function (Item1, Item2: Pointer): Integer;
    procedure Sort(Compare: TListSortCompare);                                                                //排序函數,這里TListSortCompare對應的是排序函數的指針
    procedure SortList(const Compare: TListSortCompareFunc);                                         //TListSortCompareFunc是對應TListSortCompare的匿名函數,方法定義更為靈活
    procedure Assign(ListA: TList; AOperator: TListAssignOp = laCopy; ListB: TList = nil);  //指定內容來源
    property Capacity: Integer read FCapacity write SetCapacity;                                         //空間計數
    property Count: Integer read FCount write SetCount;                                                      //元素計數
    property Items[Index: Integer]: Pointer read Get write Put; default;
    property List: TPointerList read FList;                                                                              //指針列表
  end

我們可以看到,這個TList算所有列表之源,具備了所有列表類型的基礎方法屬性。

b, TObjectList

這種List的單元Contnrs需要額外引用一下,我們在這個類的源碼中可以看到,幾乎所有的方法名跟TList都是一致的,只不過元素對象參數由TPoint變成了TObject,另外,還增加了以下兩個新的方法:

    constructor Create(AOwnsObjects: Boolean); overload;       //重載了一個構造方法,這里可以在創建的時候直接指定其擁有者,如果是False,就跟TList差別很大了,相當於這個ObjectList是獨立於元素存在的。

                                                                                                  //這時候元素的空間需要額外的管理

    property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects;   //這個是ObjectList的一個屬性,可以由構造方法帶進來,也可以使用時改變。

c,TComponetList

這個是直接繼承TObjectList的,同樣的,所有元素由基礎對象改成了組件。但是這個類中也增加了一個方法:

     procedure HandleFreeNotify(Sender: TObject; AComponent: TComponent);            //這個方法對應着procedure TComponent.FreeNotification(AComponent: TComponent);方法

這個方法使得其管理的對象釋放更為靈活,釋放一個元素組件的時候,不需要操作ComponentList ,可以直接操作元素,並通過元素組件的方法自動觸發TComponentList來處理鏈結構。

 

d,TStringList

TStringList跟TList並不是一脈相承的,而是繼承TStrings的,而TStrings是繼承TPersistent的,其方法完全是為字串服務的,跟TList有相似點,其變化又比TList系列更為復雜。因為其具備

一系列增加和管理對象的方法:

property Objects[Index: Integer]: TObject read GetObject write PutObject;

function AddObject(const S: string; AObject: TObject): Integer; virtual;

function IndexOfObject(AObject: TObject): Integer; virtual;

procedure InsertObject(Index: Integer; const S: string; AObject: TObject); virtual;

function ToObjectArray: TArray<TObject>;

除此之外,還具備字典配對的功能:

     property Names[Index: Integer]: string read GetName;

    property KeyNames[Index: Integer]: string read GetKeyName;

    property Values[const Name: string]: string read GetValue write SetValue;
    property ValueFromIndex[Index: Integer]: string read GetValueFromIndex write SetValueFromIndex;

這使得其功能和擴展性都顯得極為強大,TStrings是個抽象類,而TStringList增加了Sort方法和對象耦合屬性OwnsObjects。 當沒有使用對象和字典配對的時候,TStringList看起來就象一個簡單的字串數組。

e,TClassList

這個類似乎不需要額外談的,類中類的列表形態,批量處理的情景除非是特別復雜的模型才可以用到。其繼承類是TList,沒有對象管理的擁有者功能。

 

2,泛型

泛型的出現,直接弄得各種List似乎都變成了一種兼容的特例模式了。泛型的可以選擇任何類型的數據元素,並可以以任何數據結構模式進行組合。雖然在數據定義階段略顯復雜,但是作為一種強力的數據擴展

可以應用的場景也多得多。

泛型的引用單元:Generics.Collections,泛型的定義模式:

 

 

a,泛型數據類型的構成基礎

我們先來看看泛型單元中的前四個數據定義,他們是泛型構成的基礎件:TArray(TObject),TEnumerator<T>(TObject),TEnumerable<T>(TObject),TListHelper(TObject)

  TArray = class
  private
    class procedure QuickSort<T>(var Values: array of T; const Comparer: IComparer<T>;
      L, R: Integer); static;
    class procedure CheckArrays(Source, Destination: Pointer; SourceIndex, SourceLength, DestIndex, DestLength, Count: NativeInt); static;
  public
    class procedure Sort<T>(var Values: array of T); overload; static;
    class procedure Sort<T>(var Values: array of T; const Comparer: IComparer<T>); overload; static;
    class procedure Sort<T>(var Values: array of T;
      const Comparer: IComparer<T>; Index, Count: Integer); overload; static;

    class function BinarySearch<T>(const Values: array of T; const Item: T;
      out FoundIndex: Integer; const Comparer: IComparer<T>;
      Index, Count: Integer): Boolean; overload; static;
    class function BinarySearch<T>(const Values: array of T; const Item: T;
      out FoundIndex: Integer; const Comparer: IComparer<T>): Boolean; overload; static;
    class function BinarySearch<T>(const Values: array of T; const Item: T;
      out FoundIndex: Integer): Boolean; overload; static; static;

    class procedure Copy<T>(const Source: array of T; var Destination: array of T; SourceIndex, DestIndex, Count: NativeInt); overload; static;
    class procedure Copy<T>(const Source: array of T; var Destination: array of T; Count: NativeInt); overload; static;
  end;

這是額外為TArray定義的一段代碼,本人看得不是特別懂,感覺這個主要的功能是為System中的TArray<T>定義作一個擴展。

注意,TArray<T>的定義是出現在System單元中:TArray<T> = array of T;

這個TArray擴展為后續的泛型定義,提供了一個本體。接下來,我們看看兩個抽象類:TEnumerator<T>,TEnumerable<T>.

  TEnumerator<T> = class abstract
  protected
    function DoGetCurrent: T; virtual; abstract;
    function DoMoveNext: Boolean; virtual; abstract;
  public
    property Current: T read DoGetCurrent;
    function MoveNext: Boolean;
  end;

 

  TEnumerable<T> = class abstract
  private
  {$HINTS OFF}
    function ToArrayImpl(Count: Integer): TArray<T>; // used by descendants
  {$HINTS ON}
  protected
    function DoGetEnumerator: TEnumerator<T>; virtual; abstract;
  public
    destructor Destroy; override;
    function GetEnumerator: TEnumerator<T>;
    function ToArray: TArray<T>; virtual;
  end;

這兩個抽象類,TEnumerable是所有泛型的基類,定義出泛型的數據實體是TArray<T>模式,並且引用了另外一個抽象方法類TEnumerator ,使得泛型具備獲取當前元素Current以及MoveNext導航功能。

這個設計很重要,我們可以看到Current和MoveNext在這里不再是數據結構的屬性,而是作為元素基礎功能出現的。

最后,我們再來看看一個附加的結構體:TListHelper = record ,它十分復雜,我沒有太多時間去詳細研究,暫時看到它是作為泛型的擴展屬性使用的,所以就認為它是一個泛型的幫助信息擴展吧。

b,TList<T>

這個類型的篇幅還是比較長的,比TList要復雜多了,既融合了TList的基礎定義,又加入了泛型的實現基礎,下面我們一起看一看其定義的過程。

  TList<T> = class(TEnumerable<T>)
  private type
    arrayofT = array of T;                                                                                           //定義了一個內部元數組類
  var
    FListHelper: TListHelper; // FListHelper must always be followed by FItems     //暫且認為是幫助信息類,不做過多分析
    FItems: arrayofT; // FItems must always be preceded by FListHelper                //利用元數組類實現了一個元數據實體
    FComparer: IComparer<T>;                                                                               //為排序而誕生的比較方法指針
    FOnNotify: TCollectionNotifyEvent<T>;                                                             //內部事件

    function GetCapacity: Integer; inline;                                                                 //得到整個泛型的內存空間
    procedure SetCapacity(Value: Integer); overload; inline;                                  //動態設置元素的Length
    procedure SetCount(Value: Integer); inline;                                                      //動態設置元素個數
    function GetItem(Index: Integer): T; inline;                                                        //根據序號獲得指定元素
    procedure SetItem(Index: Integer; const Value: T); inline; 
    procedure GrowCheck(ACount: Integer); inline;                                              //Helper相關
    procedure DoDelete(Index: Integer; Notification: TCollectionNotification); inline;      //根據序號刪除並銷毀某元素
    procedure InternalNotify(const Item; Action: TCollectionNotification);                       
    function InternalCompare(const Left, Right): Integer;
    property FCount: Integer read FListHelper.FCount write FListHelper.FCount;
  protected
    function ItemValue(const Item: T): NativeInt;                                                 //返回元素的值,NativeInt是內存中一種Int編碼值。
    function DoGetEnumerator: TEnumerator<T>; override;                               //重新構建元素的導航功能。
    procedure Notify(const Item: T; Action: TCollectionNotification); virtual;
  public
  type
    TDirection = System.Types.TDirection;                                                        //定義方向,用於List的定向操作
    TEmptyFunc = reference to function (const L, R: T): Boolean;                    //為空時的匿名函數
    TListCompareFunc = reference to function (const L, R: T): Integer;            //為排序比較時的匿名函數

   //再往下看基本就沒什么新方法了,功能都是結合TList和TEnumerable的功能重組.

   constructor Create; overload;
    constructor Create(const AComparer: IComparer<T>); overload;
    constructor Create(const Collection: TEnumerable<T>); overload;
    destructor Destroy; override;

    class procedure Error(const Msg: string; Data: NativeInt); overload; virtual;
{$IFNDEF NEXTGEN}
    class procedure Error(Msg: PResStringRec; Data: NativeInt); overload;
{$ENDIF  NEXTGEN}

    function Add(const Value: T): Integer; inline;

    procedure AddRange(const Values: array of T); overload;
    procedure AddRange(const Collection: IEnumerable<T>); overload; inline;
    procedure AddRange(const Collection: TEnumerable<T>); overload; inline;

    procedure Insert(Index: Integer; const Value: T); inline;

    procedure InsertRange(Index: Integer; const Values: array of T); overload;
    procedure InsertRange(Index: Integer; const Collection: IEnumerable<T>); overload;
    procedure InsertRange(Index: Integer; const Collection: TEnumerable<T>); overload;

    procedure Pack; overload;
    procedure Pack(const IsEmpty: TEmptyFunc); overload;

    function Remove(const Value: T): Integer; inline;
    function RemoveItem(const Value: T; Direction: TDirection): Integer; inline;
    procedure Delete(Index: Integer); inline;
    procedure DeleteRange(AIndex, ACount: Integer); inline;
    function ExtractItem(const Value: T; Direction: TDirection): T; inline;
    function Extract(const Value: T): T; inline;

    procedure Exchange(Index1, Index2: Integer); inline;
    procedure Move(CurIndex, NewIndex: Integer); inline;

    function First: T; inline;
    function Last: T; inline;

    procedure Clear; inline;

    function Expand: TList<T>; inline;

    function Contains(const Value: T): Boolean; inline;
    function IndexOf(const Value: T): Integer; inline;
    function IndexOfItem(const Value: T; Direction: TDirection): Integer; inline;
    function LastIndexOf(const Value: T): Integer; inline;

    procedure Reverse; inline;

    procedure Sort; overload;
    procedure Sort(const AComparer: IComparer<T>); overload;
    function BinarySearch(const Item: T; out Index: Integer): Boolean; overload;
    function BinarySearch(const Item: T; out Index: Integer; const AComparer: IComparer<T>): Boolean; overload;

    procedure TrimExcess; inline;

    function ToArray: TArray<T>; override; final;

    property Capacity: Integer read GetCapacity write SetCapacity;
    property Count: Integer read FListHelper.FCount write SetCount;
    property Items[Index: Integer]: T read GetItem write SetItem; default;
    property List: arrayofT read FItems;

    property OnNotify: TCollectionNotifyEvent<T> read FOnNotify write FOnNotify;

    type                                                                                                         //這里重新指定了元素導航的功能實現
      TEnumerator = class(TEnumerator<T>)
      private
        FList: TList<T>;
        FIndex: Integer;
        function GetCurrent: T;
      protected
        function DoGetCurrent: T; override;
        function DoMoveNext: Boolean; override;
      public
        constructor Create(const AList: TList<T>);
        property Current: T read GetCurrent;
        function MoveNext: Boolean;
      end;

    function GetEnumerator: TEnumerator; reintroduce; inline;
  end;

該類應該是泛型中使用最為廣泛的一個類,我們既可以用以后的類來組合使用,又可以重新定義其各項基礎功能實現擴展。

直接使用的模式: DataSetList: TList<TDataSet>;

重新定義的模式:

type

      TSpecList<T>=class(TList<T>)

     ....

end;

使用的時候直接將<T>實化:DataSetList: TSpecList<TDataSet>;

c,其他幾種使用頻率比較高的泛型:

TQueue<T> = class(TEnumerable<T>)                           //隊列泛型,定義幾乎跟TList<T> 一樣,所以這里的方法說明省去。

TThreadList<T> = class                                                   //線程泛型,實體是TList<T>,針對其控制增加了鎖定方法。

TStack<T> = class(TEnumerable<T>)                            //堆棧泛型,增加了Pop和Push方法,其他定義幾乎跟TList<T>一致。

  TPair<TKey,TValue> = record
    Key: TKey;
    Value: TValue;
    constructor Create(const AKey: TKey; const AValue: TValue);
  end;
  TDictionary<TKey,TValue> = class(TEnumerable<TPair<TKey,TValue>>)      //字典泛型,這個比較有用,數據是以Key和Value成對出現。主要是增加了Hash方法,各種元素操作也都以Key作為參數。

這些泛型都是基於數據結構的變化。

 

d,與對象相關的泛型擴展 TObjectList<T>,

TObjectList<T: class> = class(TList<T>)   

  private
    FOwnsObjects: Boolean;
  protected
    procedure Notify(const Value: T; Action: TCollectionNotification); override;
  public
    constructor Create(AOwnsObjects: Boolean = True); overload;
    constructor Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean = True); overload;
    constructor Create(const Collection: TEnumerable<T>; AOwnsObjects: Boolean = True); overload;
    destructor Destroy; override;
    property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects;                                         //主要就是增加了這個屬性,讓其元素耦合性降低
  end;

TObjectQueue<T: class> = class(TQueue<T>)

TObjectStack<T: class> = class(TStack<T>)

TObjectDictionary<TKey,TValue> = class(TDictionary<TKey,TValue>)

以上這四種泛型都是在原有泛型上的輕微擴展,同列表的對象擴展一樣。最主要的屬性就是OwnsObjects,使得整個泛型數據集,可以擁有對象空間的獨立管理能力。

 

3,小結

為什么我們要使用泛型?泛型數據的優勢和劣勢又各是什么呢?

其實Delphi引用泛型數據算比較落后的了,Java很早就有泛型概念,而FrameWork也是從2.0 開始就引入了泛型數據,而Delphi是從Delphi2009~Delphi2010才正式引入泛型。

泛型的早期模式,其實就是各種列表,也就是本篇的第1點所闡述的內容,對比泛型和列表,其實泛型能實現的方式列表都可以實現,而列表的異構元素結構,泛型是不適合的。

那么泛型相對列表有什么優勢呢?優勢主要體現在兩個方面:重用性,安全高效性。

a,重用性

這里舉個例子說明,我們如果需要兩個列表TStringxxxList,TIntxxxList,如果用列表繼承的概念,那就必須要寫兩個定義:

type TStringList=class(TList)

...這可能會有count方法

end;

type TIntxxxList=class(TList)

...這可能會有count方法

end;

 如果換作泛型,就只需要定義一次就好了,列表中通性的方法是固定的。

type TxxxList<T>=class(TList<T>)

...這里可能有count方法

end;

使用時,我們用TxxxList<string>,TxxxList<Int>就可以取代兩種List。

b,安全高效性

雖然用列表也可以實現子類型的存取,但是在使用過程中就免不了要進行類型轉換和判斷(裝箱和拆箱),即不安全,也會影響到系統效率。

例如:

  AllDataSets : TComponentList;                                 //用List進行一個數據集的存儲。

  AllDataSets := TComponentList.Create(False);        //非耦合性列表
  AllDataSets.Add(ADataSet);                                   //存放一個數據集

到這一步似乎跟泛型都沒什么差異,然而取的時候就比較麻煩了。

if AllDataSets.Items[ADataSetNo] is TDataSet then

 funxxx(AllDataSets.Items[ADataSetNo] as TDataSet);

這不僅僅意味着要承受各種非規則數據的干擾,還必須進行強制類型轉換,才能完成其初期設計的【數據集列表】這樣的概念。反觀泛型就要簡單得多:

定義:AllDataSets : TObjectList<TDataSet>;

AllDataSets := TObjectList<TDataSet>.Create(False);

AllDataSets.Add(ADataSet); 

我們在使用中,完全不擔心類型問題,直接調用就好了,而且即便真的有類型匹配的錯誤,在編譯期就可以將其呈現出來。

不求功能強大,但求小巧融洽


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM