Delphi中的容器类 *List ( 一)

Delphi中的容器类(List)

Delphi中的容器类

Delphi 5开始VCL中增加了一个新的Contnrs单元,单元中定义了8个新的类,全部都是基于标准的TList 类。 

  TList

  TList 类实际上就是一个可以存储指针的容器类,提供了一系列的方法和属性来添加,删除,重排,定位,存取和排序容器中的类,它是基于数组的机制来实现的容器,比较类似于C++中的VectorJava中的ArrayListTList 经常用来保存一组对象列表,基于数组实现的机制使得用下标存取容器中的对象非常快,但是随着容器中的对象的增多,插入和删除对象速度会直线下降,因此不适合频繁添加和删除对象的应用场景。下面是TList类的属性和方法说明:

属性

描述

Count: Integer;

返回列表中的项目数

Items[Index: Integer]: Pointer; default

通过以0为底的索引下标直接存取列表中的项目

 

方法

类型

描述

Add(Item: Pointer): Integer;

函数

用来向列表中添加指针

Clear;

过程

清空列表中的项目

Delete(Index: Integer);

过程

删除列表中对应索引的项目

IndexOf(Item: Pointer): Integer;

函数

返回指针在列表中的索引

Insert(Index: Integer; Item: Pointer);

过程

将一个项目插入到列表中的指定位置

Remove(Item: Pointer): Integer;

函数

从列表中删除指针

 

名称

类型

描述

Capacity: Integer;

property

可以用来获取或设定列表可以容纳的指针数目

Extract(Item: Pointer): Pointer;

function

Extract 类似于Remove 可以将指针从列表中删除,不同的是返回被删除的指针。 

Exchange(Index1, Index2: Integer);

procedure

交换列表中两个指针

First: Pointer;

function

返回链表中的第一个指针

Last: Pointer;

function

返回链表中最后一个指针

Move(CurIndex NewIndex: Integer);

procedure

将指针从当前位置移动到新的位置

Pack;

procedure

从列表中删除所有nil指针

Sort(Compare: TListSortCompare);

procedure

用来对链表中的项目进行排序,可以设定Compare参数为用户定制的排序函数

  TObjectList

  TObjectList 类直接从TList 类继承,可以作为对象的容器。TObjectList类定义如下: 

TObjectList = class(TList)
 ...
public
  constructor Create; overload;
  constructor Create(AOwnsObjects: Boolean); overload;
  function Add(AObject: TObject): Integer;
  function Remove(AObject: TObject): Integer;
  function IndexOf(AObject: TObject): Integer;
  function FindInstanceOf(AClass: TClass;
  AExact: Boolean = True; AStartAt: Integer = 0):
  Integer;
  procedure Insert(Index: Integer; AObject: TObject);
  property OwnsObjects: Boolean;
  property Items[Index: Integer]: TObject; default;
end;

   不同于TList类,TObjectList类的Add, Remove, IndexOf, Insert等方法都需要传递TObject对象作为参数,由于有了编译期的强类型检查,使得TObjectListTList更适合保存对象。此外TObjectList对象有OwnsObjects属性当设定为True (默认值),同TList类不同,TObjectList对象将销毁任何从列表中删除的对象。无论是调用Delete, Remove, Clear 方法,还是释放TObjectList对象,都将销毁列表中的对象。有了TObjectList类,我们就再也不用使用循环来释放了对象。这就避免了释放 链表对象时,由于忘记释放链表中的对象而导致的内存泄漏。另外要注意的是OwnsObjects属性不会影响到Extract方法,TObjectListExtract方法行为类似于TList只是从列表中移除对象引用,而不会销毁对象

   TObjectList 对象还提供了一个FindInstanceOf 函数,可以返回只有指定对象类型的对象实例在列表中的索引。如果AExact 参数为True,只有指定对象类型的对象实例会被定位,如果AExact 对象为FalseAClass 的子类实例也将被定位。AStartAt 参数可以用来找到列表中的多个实例,只要每次调用FindInstanceOf 函数时,将起始索引加1,就可以定位到下一个对象,直到FindInstanceOf 返回-1。下面是代码示意:

var
 idx: Integer;
begin
 idx := -1;
  repeat
  idx := ObjList.FindInstanceOf(TMyObject, True, idx+1);
   if idx >= 0 then
   ...
  until(idx < 0);
end;

  TComponentList

  Contnrs单元中还定义了TComponentList 类,类定义如下:

TComponentList = class(TObjectList)
 ...
public
  function Add(AComponent: TComponent): Integer;
  function Remove(AComponent: TComponent): Integer;
  function IndexOf(AComponent: TComponent): Integer;
  procedure Insert(Index: Integer; AComponent: TComponent);
  property Items[Index: Integer]: TComponent; default;
end;//
Extract

   注意TComponentList 是从TObjectList类继承出来的,它的Add, Remove, IndexOf, Insert Items 方法调用都使用TComponent 类型的参数而不再是TObject类型,因此适合作为TComponent对象的容器。TComponentList 类还有一个特殊的特性,就是如果链表中的一个组件被释放的话,它将被自动的从TComponentList 链表中删除。这是利用TComponentFreeNotification方法可以在组件被销毁时通知链表,这样链表就可以将对象引用从链表中删除 的。  

  TClassList

Contnrs单元中还定义了TClassList类,类定义如下:

//类名好像是

TClassList = class(TList)
protected
  function GetItems(Index: Integer): TClass;
  procedure SetItems(Index: Integer; AClass: TClass);
public
  function Add(aClass: TClass): Integer;
  function Remove(aClass: TClass): Integer;
  function IndexOf(aClass: TClass): Integer;
  procedure Insert(Index: Integer; aClass: TClass);
  property Items[Index: Integer]: TClass
   read GetItems write SetItems; default;
end;

  不同于前面两个类,这个类继承于TList的类只是将Add, Remove, IndexOf, InsertItems 调用的参数从指针换成了TClass元类类型。

  TOrderedList, TStackTQueue

  Contnrs单元还定义了其它三个类:TOrderedList, TStackTQueue,类型定义如下:

TOrderedList = class(TObject)
private
 FList: TList;
protected
  procedure PushItem(AItem: Pointer); virtual; abstract;
 ...
public
  function Count: Integer;
  function AtLeast(ACount: Integer): Boolean;
  procedure Push(AItem: Pointer);
  function Pop: Pointer;
  function Peek: Pointer;
end;


TStack = class(TOrderedList)//  先进后出
protected
  procedure PushItem(AItem: Pointer); override;
end;
TQueue = class(TOrderedList)//队列  先进先出
protected
  procedure PushItem(AItem: Pointer); override;
end;

   要注意虽然TOrderedList 并不是从TList继承的,但是它在内部的实现时,使用了TList来储存指针。另外注意TOrderedList类的PushItem 过程是一个抽象过程,所以我们无法实例化 TOrderedList 类,而应该从TOrderedList继承新的类,并实现抽象的PushItem方法。TStack TQueue 正是实现了PushItem抽象方法的类, 我们可以实例化TStack TQueue类作为后进先出的堆栈(LIFO)和先进先出的队列(FIFO)。下面是这两个的的方法使用说明: 

  ·           Count 返回列表中的项目数。

  ·           AtLeast 可以用来检查链表的大小,判断当前列表中的指针数目是否大于传递的参数值,如果为True表示列表中的项目数大于传来的参数。 

  ·           对于TStackPush 方法将指针添加到链表的最后,对于TQueuePush 方法则将指针插入到链表的开始。

  ·           Pop返回链表的末端指针,并将其从链表中删除。 

  ·           Peek返回链表的末端指针,但是不将其从链表中删除。 

  TObjectStackTObjectQueue

  Contnrs单元中最后两个类是TObjectStackTObjectQueue类,类的定义如下:

TObjectStack = class(TStack)
public
  procedure Push(AObject: TObject);
  function Pop: TObject;
  function Peek: TObject;
end;
TObjectQueue = class(TQueue)
public
  procedure Push(AObject: TObject);
  function Pop: TObject;
  function Peek: TObject;
end;

  这两个类只是TStackTQueue 类的简单扩展,在链表中保存的是TObject的对象引用,而不是简单的指针。

  TIntList

  到目前为止,我们看到的容器类中保存的都是指针或者对象引用(对象引用其实也是一种指针)。

  那么我们能不能在链表中保存原生类型,如IntegerBoolean或者Double等呢。下面的我们定义的类TIntList 类就可以在链表中保存整数,这里我们利用了整数和指针都占用4个字节的存储空间,所以我们可以直接将指针映射为整数。

unit IntList;
interface
uses
 Classes;
type
 TIntList = class(TList)
  protected
   function GetItem(Index: Integer): Integer;
   procedure SetItem(Index: Integer;
    const Value: Integer);
  public               
   function Add(Item: Integer): Integer;
   function Extract(Item: Integer): Integer;
   function First: Integer;
   function IndexOf(Item: Integer): Integer;
   procedure Insert(Index, Item: Integer);
   function Last: Integer;
   function Remove(Item: Integer): Integer;
   procedure Sort;
   property Items[Index: Integer]: Integer
    read GetItem write SetItem; default;
  end;
implementation
{ TIntList }
function TIntList.Add(Item: Integer): Integer;
begin
 Result := inherited Add(Pointer(Item));
end;
function TIntList.Extract(Item: Integer): Integer;
begin
 Result := Integer(inherited Extract(Pointer(Item)));
end;
function TIntList.First: Integer;
begin
 Result := Integer(inherited First);
end;
function TIntList.GetItem(Index: Integer): Integer;
begin
 Result := Integer(inherited Items[Index]);
end;
function TIntList.IndexOf(Item: Integer): Integer;
begin
 Result := inherited IndexOf(Pointer(Item));
end;
procedure TIntList.Insert(Index, Item: Integer);
begin
  inherited Insert(Index, Pointer(Item));
end;
function TIntList.Last: Integer;
begin
 Result := Integer(inherited Last);
end;
function TIntList.Remove(Item: Integer): Integer;
begin
 Result := inherited Remove(Pointer(Item));
end;
procedure TIntList.SetItem(Index: Integer;
  const Value: Integer);
begin
  inherited Items[Index] := Pointer(Value);
end;
function IntListCompare(Item1, Item2: Pointer): Integer;
begin
  if Integer(Item1) < Integer(Item2) then
  Result := -1
  else if Integer(Item1) > Integer(Item2) then
  Result := 1
  else
  Result := 0;
end;
            
procedure TIntList.Sort;
begin
  inherited Sort(IntListCompare);
end;
end.

  扩展TList,限制类型的对象列表 

Begin Listing Two - TMyObjectList
TMyObject = class(TObject)
public
  procedure DoSomething;
end;
TMyObjectList = class(TObjectList)
protected
  function GetItems(Index: Integer): TMyObject;
  procedure SetItems(Index: Integer; AMyObject: TMyObject);
public
  function Add(aMyObject: TMyObject): Integer;
  procedure DoSomething;
  function Remove(aMyObject: TMyObject): Integer;
  function IndexOf(aMyObject: TMyObject): Integer;
  procedure Insert(Index: Integer; aMyObject: TMyObject);
  property Items[Index: Integer]: TMyObject
   read GetItems write SetItems; default;
end;
...
{ TMyObjectList }
function TMyObjectList.Add(AMyObject: TMyObject): Integer;
begin
 Result := inherited Add(AMyObject);
end;
procedure TMyObjectList.DoSomething;
var
 i: Integer;
begin
  for i := 0 to Count-1 do
  Items[i].DoSomething;
end;
function TMyObjectList.GetItems(Index: Integer): TMyObject;
begin
 Result := TMyObject(inherited Items[Index]);
end;
function TMyObjectList.IndexOf(AMyObject: TMyObject):
 Integer;
begin
 Result := inherited IndexOf(AMyObject);
end;
procedure TMyObjectList.Insert(Index: Integer;
 AMyObject: TMyObject);
begin
  inherited Insert(Index, AMyObject);
end;
function TMyObjectList.Remove(AMyObject: TMyObject):
 Integer;
begin
 Result := inherited Remove(AMyObject);
end;
procedure TMyObjectList.SetItems(Index: Integer;
 AMyObject: TMyObject);
begin
  inherited Items[Index] := AMyObject;
end;
End Listing Two

  TStrings

   出于效率的考虑,Delphi并没有象C++Java那样将字符串定义为类,因此TList本身不能直接存储字符串,而字符串列表又是使用非常广泛 的,为此Borland提供了TStrings类作为存储字符串的基类,应该说是它除了TList类之外另外一个最重要的Delphi容器类。

   要注意的是TStrings类本身包含了很多抽象的纯虚的方法,因此不能实例化后直接使用,必须从TStrings类继承一个基类实现所有的抽象的纯虚 方法来进行实际的字符串列表管理。虽然TStrings类本身是一个抽象类,但是它应该说是一个使用了Template模式的模版类,提供了很多事先定义 好的算法来实现添加添加、删除列表中的字符串,按下标存取列表中的字符串,对列表中的字符串进行排序,将字符串保存到流中。将每个字符串同一个对象关联起 来,提供了键-值对的关联等等。

  因为TStrings类本身是个抽象类,无法实例化,因此Delphi提供了一个TStringListTStrings的子类提供了TStrings类的默认实现,通常在实际使用中,我们都应该使用TStringList类存储字符串列表,代码示意如下:

var TempList: TStrings;   
begin
 TempList := TStringList.Create;
 try  
TempList.Add(‘
字符串1’);

 finally  
TempList.Free;
   
 end;
end;

  TStrings类的应用非常广泛,很多VCL类的属性都是TStrings类型,比如TMemo组件的Lines属性,TListBoxItems属性等等。下面将介绍一下TStrings类的常见用法。

  TStrings类的常见的用法

  根据下标存取列表中的字符串是最常见的一种操作,用法示意如下:

StringList1.Strings[0] := '字符串1';

  注意在Delphi中,几乎所有的列表的下标都是以0为底的,也就是说Strings[0]是列表中的第一个字符串。另外,由于Strings属性是字符串列表类的默认属性,因此可以省略Strings,直接用下面的简便方法存取字符串:

StringList1[0] := '字符串1';

  定位一个列表中特定的字符串的位置,可以使用IndexOf方法,IndexOf方法将会返回在字符串列表中的第一个匹配的字符串的索引值,如果没有匹配的字符串则返回-1。比如我们可以使用IndexOf方法来察看特定文件是否存在于文件列表框中,代码示意如下:

if FileListBox1.Items.IndexOf('TargetFileName') > -1 ...

   有一点不方便的是TStrings类没有提供一个方法可以查找除了第一个匹配字符串外其他同样匹配的字符串的索引,只能是自己遍历字符串列表来实现,这 点不如C++中的模版容器类以及相关的模版算法强大和方便。下面是一个遍历字符串列表的示意,代码遍历列表框中的所有字符串,并将其全部转化为大写的字符 串:

procedure TForm1.Button1Click(Sender: TObject);var Index: Integer;
begin
 for Index := 0 to ListBox1.Items.Count - 1 do  
ListBox1.Items[Index] := UpperCase(ListBox1.Items[Index]);
end;

  前面我们看到了,要想向字符串列表中添加字符串,直接使用Add方法就可以了,但是Add方法只能将字符串加入到列表的末尾,要想在列表的指定位置添加字符串,需要使用Insert方法,下面代码在列表的索引为2的位置添加了字符串:

StringList1.Insert(2, 'Three');

  如果要想将一个字符串列表中的所有字符串都添加到另一个字符串列表中,可以使用AddStrings方法,用法如下:

StringList1.AddStrings(StringList2); 

  要想克隆一个字符串列表的所有内容,可以使用Assign方法,例如下面的方法将Combox1中的字符串列表复制到了Memo1中:

  Memo1.Lines.Assign(ComboBox1.Items);

  要注意的是使用了Assign方法后,目标字符串列表中原有的字符串会全部丢失。

  同对象关联

   前面说了我们可以将字符串同对象绑定起来,我们可以使用AddObject或者InsertObject方法向列表添加同字符串关联的对象,也可以通过 Objects属性直接将对象同特定位置的字符串关联。此外TStrings类还提供了IndexOfObject方法返回指定对象的索引,同样的 Delete,ClearMove等方法也可以作用于对象。不过要注意的是我们不能向字符串中添加一个没有同字符串关联的对象。

  同视图交互

   刚刚学习使用Delphi的人都会为Delphi IDE的强大的界面交互设计功能所震惊,比如我们在窗体上放上一个ListBox,然后在object Inspector中双击它的Items属性(TStrings类型),在弹出的对话框中,见下图,我们输入一些字符串后,点击确定,关闭对话框,就会看 到窗体上的ListBox中出现了我们刚才输入的字符串。

Delphi中的容器类 <wbr>*List <wbr>( <wbr>一)

  可以我们在TStrings和默认的实现类TStringList的源代码中却找不到同ListBox相关的代码,那么这种界面交互是如何做到的呢?

  秘密就在于TListBoxItems属性类型实际上是TStrings的基类TListBoxStrings类,我们看一下这个类的定义:

 TListBoxStrings = class(TStrings)
 private
  ListBox: TCustomListBox;
 protected

 public
  function Add(const S: string): Integer; override;
  procedure Clear; override;
  procedure Delete(Index: Integer); override;
  procedure Exchange(Index1, Index2: Integer); override;
  function IndexOf(const S: string): Integer; override;
  procedure Insert(Index: Integer; const S: string); override;
  procedure Move(CurIndex, NewIndex: Integer); override;
 end;

  可以看到TListBoxStrings类实现了TStrings类的所有抽象方法,同时在内部有一个ListBox的私有变量。我们再看一下TListBoxStringsAdd方法:

function TListBoxStrings.Add(const S: string): Integer;
begin
 Result := -1;
 if ListBox.Style in [lbVirtual, lbVirtualOwnerDraw] then exit;
 Result := SendMessage(ListBox.Handle, LB_ADDSTRING, 0, Longint(PChar(S)));
 if Result < 0 then raise EOutOfResources.Create(SInsertLineError);
end;

  可以看到TListBoxStrings在内部并没有保存添加的字符串,而是直接向Windows的原生列表盒控件发送消息实现的代码添加,而Windows的原生列表盒是一个MVC的组件,当内部的数据发生变化时,会自动改变视图显示,这就是为什么我们在设计器中输入的字符串会立刻显示在窗体列表框中的原因了。

   于是我们也就知道为什么BorlandTStrings设计为一个抽象的类而没有提供一个默认的存储方式,就是因为很多的界面组件在内部对数据的存储 有很多不同的方式,Borland决定针对不同的组件提供不同的存储和交互方式。同样的我们要编写的组件如果有TStrings类型的属性,同时也要同界 面或者其它资源交互的话,不要使用TStringList来实现,而应该从TStrings派生出新类来实现更好的交互设计。

  还有一点 要说明的是,DelphiIDE只在使用Delphi的流机制保存组件到窗体设计文件DFM文件中的时,做了一些特殊的处理,能够自动保存和加载 PublishedTStrings类型的属性,下面就是一个ListBox储存在窗体设计文件DFM中文本形式示意(在窗体设计阶段,我们可以直接使 View As Text右键菜单命令看到下面的文本),我们可以注意到在设计时我们输入的Items的两个字符串被保存了起来:

 object ListBox1: TListBox
  Left = 64
  Top = 40
  Width = 145
  Height = 73
  ItemHeight = 16
  Items.Strings = (
   'String1'
   'String2')
  TabOrder = 1
 end

  随后如果运行程序时,VCL库会使用流从编译进可执行文件的DFM资源中将Items.Strings列表加载到界面上,这样就实现了设计是什么样,运行时也是什么样的所见即所得。

  键-值对

  在实际开发过程中,我们经常会碰到类似于字典的定位操作的通过键查找相应值的操作,比如通过用户名查找用户相应的登陆密码等。在C++Java中,标准模版库和JDK都提供了Map类来实现键-值机制,但是DelphiVCL库却没有提供这样的类,但是TStrings类提供了一个简易的Map替代的实现,那就是Name-Value对。

   对于TStrings来说,所谓的Name-Value对,实际上就是’Key=Value’这样包含=号的分割的字符串,等号左边的部分就是 Name,等号右边的部分就是ValueTStrings类提供了IndexOfNameValues等属性方法来操作Name-Value对。下面 是用法示意:

var
 StringList1:TStrings;
Begin
 StringList1:=TStringList.Create;
 //添加用户名-密码对
 StringList1.Add(‘hubdog=aaa’);
 StringList1.Add(‘hubcat=bbb’);
 ….
 //根据用户名hubdog查找密码
 Showmessage(StringList1.Values[StringList1.IndexOfName(‘hubdog’)]);
End;

   从Delphi7开始,TStrings类增加了一个NameValueSeparator属性,我们可以通过这个属性修改默认的Name-Value 分割符号为=号以外的其它符号了。还要说明的是,TStringsName-Value对中的Name可以不唯一,这有点类似于C++中的 MultiMap,这时通过Values[Names[IndexOfName]]下标操作取到的值不一定是我们所需要的,另外TStrings类的 Name-Value对的查找定位是采用的遍历的方式,而不同于JavaC++中的Map是基于哈希表或者树的实现,因此查找和定位的效率非常低,不适 用于性能要求非常高的场景。不过从Delphi6开始,VCL库中在IniFiles单元中提供了一个基于哈希表的字符串列表类 THashedStringList类可以极大的提高查找定位的速度。


posted @ 2013-04-13 15:29  Wishmeluck  阅读(611)  评论(0编辑  收藏  举报