Delphi容器類之---TList、TStringList、TObjectList,以及一個例程的代碼分析


轉載自:http://blog.csdn.net/jqandjq/article/details/5429137

 

  看了這里標題,大家可能以為我會談TListBox控件,那就錯了。我要談的是Delphi提供給我們的具有列表性質的類:TStringList、TList和TObjectList。TStringList用來存放字符串,TList存放指針,而TObjectList則存放對象(Object)

  在我們使用Delphi的過程中,有很多數據的存儲是要靠 數組解決的。雖然Delphi現在已經支持了可變數組,不過總有那么點缺陷:我們不能在刪除一個項之后,使后邊的項目自動前靠。因此,說說Delphi現成的List還是很有價值的。

 

TStringList                   

  TStringList源碼在 classes.pas里面。

Text屬性和Strings屬性

  在TStringList里面,那些String被一行一行地存儲存。TStringList.Text返回全部的String。如果第一、二、三行分別是/'aa/'、/'bb/'、/'cc/'的話,那么Text返回的是“/'aa/'+#13#10+/'bb/'+#13#10+/'cc/'+#13#10” (不包括雙引號 )。所有的String都被TStringList用回車符和換行符(#13#10)連接起來。如果一次向Text賦值的話,Text就會被自動地分割成儲存在TStringList里。這里充分地體現出TStringList的一個很實用的價值:它能讓我們逐行處理String。假如我們要操作第4行,只需要操作TStringList[3]。相信大家會問,TStringList明明是一個類,為什么能當數組那樣子用呢?其實,我們在寫TStringList[3]的時候,就是在寫TStringList.Strings[3]。Strings是TStringList的一個缺省屬性。數組性的缺省屬性就是這樣子使用的。如果大家在編寫類的時候要使用到這么一個功能的話,刻意參考如方法

property AProperty[I: Integer] read *** write ***;
default;

  Strings是一個可讀寫的屬性。這也就是說,大家並不僅可以獲取第N行的內容,也可以改變第N行的內容。因此我們需要知道TStringList里String的總數。TStringList的屬性Count則可以滿足我們的需求。

  上面已經說過,Text是返回所有字符串的屬性。向Text賦值時,TStringList能夠自動地把Texxt分成一行一行的,然后存儲在TStringList里(當然,TStringList里面並不完全時這么存儲的,詳細的過程可以參見TStringList和TStrings的代碼)。這樣,Strings返回的字符串就是沒有回車和換行的。但是,如果我們向Strings賦值的字符串有回車和換行,那么會出現什么情況呢?此時,Strings就會把哪個字符串斷成幾行,插入到原來的位置上。如果TStringListt只有這么些功能的話,那我就不必專門拿出來講了——我是說,TStringList能讓我們任意地插入或刪除某行,這就要用到TStringList提供的方法

  TStringList里的每一個字符都有自己的位置標號(從0開始)。

 

Add和Append方法                

function Add(const S: String): Integer;
procedure Append(const S: String);

   Add方法向TStringList的尾行添加一行String(在這里和下面我們都假設輸入的字符串沒有回車和換行,否則Strings將被分割)。參數S代表的是要插入的字符串的內容。Add的返回值代表了新的字符串在TStringList的位置——也就是最后一行的位置,即新的Count減去一。

  Append方法和Add唯一不同的地方就是沒有返回值。

 

Insert方法                    

procedure Insert(Index: Integer; const S: String);

   Insert方法向TStringList插入一行字符串。在Insert里,我們可以自由地選擇字符串插入的位置。參數S代表要插入的字符串的內容,Index代表要插入的位置。

 

Delete方法                    

procedure Delete(Index: Integer);

  Delete方法刪除某行字符串,我們同樣可以自由地選擇刪除任意一行字符串。參數Index代表要刪除的那一行字符串的位置。

  0是第一個數據。

 

IndexOf方法                   

function IndexOf(const S: String): Integer;

   IndexOf查找某一字符串在TStringList里的位置。參數S代表要查找的字符串。如果TStringList里面不存在S的話,則返回-1。

 

Move和Exchange方法              

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

   TStringList另外還提供了兩個方法——Move和Exchange。

  Move方法能把一行字符串抽出來並插入到另一個指定的為位置上,參數CurIndex代表要移動的字符串的位置,NewIndex代表字符串新的位置。

  Exchange方法則能將隨便兩個字符串交換。參數Index1和Index2代表兩行需要交換的字符串的位置。

 

LoadFromFile和SaveToFile方法         

procedure LoadFromFile(const FileName: String);
procedure SaveToFile(const FileName: String);

   TStringList的LoadFromFile和SaveToFile兩個方法,使得我們對文本文件得操作變得非常方便。參數FileName代表目標文本文件的文件名。

  例如我們在編寫記事本的時候,用到的TMemo.Lines就是TString(TStringList的父類,功能幾乎相同,只是因為TString時虛類,我們無法創建並使用)。在保存的時候只需要一行代碼:

TMemo.Lines.SaveToFile(FileName);

  非常方便。

 

Names和Values屬性               

  TStringList還有一項特殊功能:可以把TStringList當成ini 控制器使用。不過它沒有Section。現在我就來介紹TStringList的兩個屬性:Names和Values。

  如果TStringList的內容是這樣子的:

ID=1
Name=Vczh
PositionID=8
Tel=00000000

  那么,Names[2]就返回"positionID",Values[/'PositionID/']就返回"8"。其中"Names"是只讀屬性,而"Values"則可寫。TStringList使我們不必拘泥於ini 和注冊表。關於TStringLisst沒有Section這個問題,我們完全可以在Names里面你做點手腳,只要程序能夠識別就行。

  TStringList還有一個可以存放Object的功能。但是我個人認為使用TObjectList比較好,因為TObjectList在這方面提供了比TStringList更多的功能。

  下面我提供一個例程來介紹Values屬性。

  新建一個工程保存,並在dpr文件所在的文件夾里建立一個叫做"Config.txt"的文件,並輸入一下內容

Name=VCZH
Address=Somewhere
Email=vczh@162.com

  然后,建立一個窗體。並在TForm1的Private區段里建立變量:

SL:TstringList;

  這個例程的功能實施編輯Config.txt里的Name、Address、Email。SL在程序啟動時讀入Config.txt文件;按下Cancel按鈕則退出;按下OK按鈕則改變SL的內容並保存在Config.txt文件中;當程序再次運行時,改變后的內容就會顯示再三個文本框里 。代碼如下

procedure TForm1.FormCreate(Sender: TObject);
begin
    SL:= TStringList.Create;
    {獲取當前程序文件所在的文件夾名稱以獲得Config.txt文件的路徑}
    SL.LoadFromFile(ExtractFilePath(paramStr(0))+/'Config.txt/');  {  ExtractFilePath(paramStr(0))獲取當前執行的程序所在的路徑  }
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    SL.Free;
end;

procedure TForm1.btnCancelClick(Sender: TObject);
begin
    Close;
end;

procedure TForm1.FormShow(Sender: TObject);
begin
    {通過上面介紹的Values屬性獲得各個字段的內容}
    edtName.Text:= SL.Value[/'Name/'];
    edtAddress.Text:= SL.Values[/'Address/'];
    edtEmail.Text:= SL.Values[/'Email/'];
end;

procedure TForm1.btnOKClick(Sender: TObject);
begin
    SL.Values[/'Name/']:= edtName.Text;
    SL.Values[/'Address/']:= edtAddress.Text;
    SL.Values[/'Email/']:= edtEmail.Text;
    SL.SaveToFile(ExtractFilePath(ParamStr(0))+/'Config.txt/');
    Close;
end;

  

  

TList                        

TList簡介

  TList所在位置:Classes.pas

  在我剛接觸TList的時候,TList搞得我迷霧重重,都是Capacity屬性惹的禍。我查了Delphi的幫助,它說Capacity是Tlist的最大容量,又在什么地方說MaxInt div 4是TList的最大容量。最后我搞明白了,Capacity是臨時的,MaxInt div 4 才是真正的最大容量。只要你的內存受得了就行,算起來一共是4G。

  在TList內部有一個FList指針指向一個Pointer數組,Capacity就是這個數組的大小。奇怪的是Capacity是可寫的。我當時就在想,如果一直使用Add直到超出Capacity的范圍,會怎么樣呢?為了解決這個問題,我特地打開了TList的代碼,結果發現如下幾行(注釋是我自己加的):

function TList.Add(Item: Pointer): Integer;
begin
    {返回Item所在的位置}
    Result:= FCount;
    {如果FList數組被填滿了裝不下新的Item,那么TList自動增加Capacity,也就是FList指向的數組的大小}
    if Result = FCapacity then
        Grow;
    {擴大了FList的大小之后,就能把Item放進數組了}
    FList^[Result]:= Item;
    Inc(FCount);
    if Item <> nil then
        Notify(Item, InAdded);
    {給TList一個信號,通知TList已經加上一個新的Item}
end;

procedure TList.Grow;
var
    Delta: Integer;
begin
    {增加的規則是,如果數量小於或等於8,那么增加4;那么增加在8之上,小於或等於64,那么增加16;如果數量比64還大,那么一次增加大約1/4的空間}
    if FCapacity > 64 then
        Delta:= FCapacity div 4
    else
        Delta:= 4;
    {改變數組的大小}
    SetCapacity(FCapacity + Delta);
end;

  既然Capacity會自動增加,那么還要Capacity干什么呢?還不如使用鏈表。不過我后來意識到,在使用鏈表額時候,取得某個位置的指針比數組困難,要用比較費時間的循環。TList剛好解決了這個問題。我們既可以把TList當成數組,也可以把它當成鏈表。

 

TList的Items屬性和List屬性            

  TList除了保存的對象是指針之外,其他地方都與TStringList很像,所以下面只介紹兩者的不同之處。我們同樣可以使用TList或者TList.Items獲得某一位置的指針。如果嫌TList.Items是屬性沒有效率的話,這里還有一個List屬性,指向內部的FList,可以這樣使用:

TList.List^

  介紹到TList的Items,就要提到TList的缺省數組屬性: property Items[Index: Integer]: Pointer read Get write Put; default;

  見下面的代碼

uses Contnrs;  // 這個單元包含Delphi新增的容器對象,比如TObjectList


// TList的缺省數組屬性 property Items[Index: Integer]: Pointer read Get write Put; default;

procedure TForm1.Button1Click(Sender: TObject);
var
  List: TList; 
begin
  List := TObjectList.Create;  //我很懶,釋放的工作由TObjectList代勞
  try
    List.Add(TObject.Create);
    List.Add(TObject.Create);
    ShowMessage(TObject(List[0]).ClassName);  // 以數組下標方式訪問,等同於List.Items[0]
finally
    List.Free;
  end;
end;

  

 

TList的First和Last屬性              

  TList提供了First和Last兩個屬性,分別返回第一個和最后一個指針。

 

TList的Delete和Remove方法           

  TList也提供了一個Remove方法。與Delete不同的是,Delete刪除的是已知位置的指針,Remove刪除的是已知指針。只要TList包含有你想刪除的指針,你就可以使用

Remove(Pointer);

  Remove的返回值是指針在還沒有被刪除之前的位置,使用方法如下

procedure Delete(Index: Integer);
function Remove(Item: Pointer): Integer;

  

Tlist的Pack方法                  

  TList還有一個Pack方法。它能夠把所有不是nil的指針聚在一起,同時把Count的值改變,這樣,所有沒有用的指針就會被刪除,但是並不會減少Capacity。如果你想把沒有用的空間都釋放的話,可以把Capacity設置成Count。

 

TList的Notify                   

  最后,我想說的是Protected里的Notify。大家在Add的代碼里面就能看到,在Insert、Delete之類的代碼里我們也能看到Notify的蹤跡。既然FList的內容已經被改變了,Notify還要做什么工作呢?看一下Notify的代碼:

TListNotification = (InAdded, InExtracted, InDeleted);
procedure TList.Notify(Ptr: Pointer; Action: TListNotification);
begin

end;

  留着一個空的Notify有什么用呢?再看它的聲明:

procedure Notify(Ptr: Pointer; Action: TListNotification);
virtual;

  原來Notify是一個虛函數,當我們因為有特殊要求而繼承TList累的話,只要TList的內容一改變,我們就能得到通知。不過前提是我們要覆蓋Notify這個procedure。

 

TObject                       

TObjectList簡介                  

  TObjectList所在位置:Contnrs.pas

  TObjectList中有一個不可缺少的屬性:OwnsObjects。如果OwnsObjects是True(缺省值)的話,那么TObjectList會在適當的時候把它從列表中的Object釋放掉。

 

TStringList和TList的一個示例講解          

  現在,讓我們用一個例子來結束我對Delphi的List的介紹。這個例子是一個管理人員信息的程序。不過因為這只是一個示例,所以這個程序只是簡單的ConsoleApplication。使用ConsoleApplication來做示例程序可以免掉一些界面設計的工作。這個程序需通過Index來管理人員信息。人員信息包括Name、Telephone和Address。

  程序通過TStringList來讀取人員信息文件,然后用那些指向Record的指針把信息存放在TList里並修改。代碼如下:

program Info[color=#0000ff];
{APPTYEP CONSOLE}
uses
	SysUtils, Classes;
type
	PInfo = ^TInnfo;
	TInfo = record
	begin
		Name:String;
		Tel: String;
		Address: String;
	end;

var
	SL: TStringList;
	List: TLIst;
	AppPath: String;
	
	{開辟一個新的PInfo指針,填入新信息並返回指針將在Command_Delete或FinaInfo里釋放}
	function MakeInfo(Name, Tel, Address: String): PInfo;
	var
		P: PInfo;
	begin
		New(P);
		P^.Name:= Name;
		P^.Tel:= Tel;
		P^.Address:= Address;
		{返回的指針將被保存在List里面}
		Result:= P;
	end;
		
	{在屏幕上打印所有可用的命令}
	procedure PrintMenu;
	begin
		writeln(/'=====菜單=====/');
		writeln(/'V---查看所有人員的信息/');
		writeln(/'A---增添新的人員信息/');
		writeln(/'D---刪除人員/');
		writeln(/'E---修改人員信息/');
		writeln(/'M---查看所有可用命令/');
		writeln(/'X---推出程序/');
	end;
	
	{修改人員信息的程序}
	procedure Command_Edit;
	var
		I: Integer;
		Name, Tel, Address: String;
		P: Pinfo;
	begin
		writeln(/'請輸入要修改的人員信息的序號:/');
		readln(I);
		
		if(I<0) or (I>=List.Count) then
			writeln(/'輸入超出范圍/')
		else
		begin
			{取得某個人員信息的指針}
			P:= List.Items[I];
			writeln(/'開始輸入人員信息(若某項信息不需要修改則留空):/');
			writeln(/'姓名:/');
			readln(Name);
			writeln(/'電話號碼:/');
			readln(Tel);
			writeln(/'地址:/');
			readln(Address);
		
			{保存輸入的信息}
			if Name<>/'/' then
				P^.Name:= Name;
			if Tel<>/'/' then
				P^.Tel:= Tel;
			if Address<>/'/' then
				P^Address:= Address;
			writeln(/'修改人員信息執行完畢。/');
		end;
	end;
	
	{增加人員信息程序}
	procedure Command_Add;
	var
		Name, Tel, Address: String;
	begin
		writeln(/'開始輸入人員信息:/');
		writeln(/'姓名:/');
		readln(Name);
		writeln(/'電話號碼:/');
		readln(Tel);
		writeln(/'地址:/');
		readln(Address);
		
		{使用MakeInfo生成TInfo的指針,並且加入到TList中}
		List.Add(MakeInfo(Name, Tel, Address));
		
		writeln(/'增加人員信息執行完畢/');
	end;
	
	{打印所有人員信息的程序}
	procedure Command_View;
	var
		I: Integer;
		P: PInfo;
	begin
		writeln(/'人員信息列表:/');
		for I:=0 to List.Count-1 do
		begin
			P:= List.Items[I];
			writeln(IntToStr(I)+ /'號==============/');
			writeln(/'姓名:/' + P^.Name);
			writeln(/'電話:/' + P^.Tel);
			writeln(/'地址:/' + P^.Address);
			
			{滿6個就暫停,剛好填滿一個屏幕}
			if I mod 6 = 5 then
			begin
				writeln(/'按回車鍵繼續 。/');
				readln;
			end;
		end;
		writeln;
	end;
	
	{刪除人員信息的程序}
	procedure Command_Delete;
	var
		I: Integer;
		P: PInfo;
	begin
		writeln(/'請輸入要刪除的人員信息的序號:/');
		readln(I);
		if(I<0) or(I>=List.Count) then
			writeln(/'輸入超出范圍/')
		else
		begin
			P:= List.Items[I];
			List.Delete(I);
			Dispose(P);
			writeln(/'刪除執行完畢/');
			writeln;
		end;
	end;
	
	{處理用戶輸入的命令,返回False表示退出}
	function GetCommand: Boolean;
	var
		C: Char;
	begin
		writeln(/'輸入命令並回車:/');
		readln(C);
		result:= True;
		case C of
			/'V/', /'v/': Command_View;
			/'A/',/'a/':Command_Add;
			/'D/',/'d/':Command_Delete;
			/'M/',/'m/':PrintMenu;
			/'X/',/'x/':result:=False;
			/'E/',/'e/':Command_Edit;
			else
				writeln(/'未知命令。/');
		end;
	end;
	
	{從Info.txt把人員信息加載到List}
	procedure LoadInfo;
	var
		I: Integer;
		Name, Tel, Address, Index: String;
	begin
		SL.LoadFromFile(AppPath+/'Info.txt/');
		for I:=0 to SL.Count div 3 - 1 do
		begin
			Index:= IntToStr(I)+/'./';
			{文件格式:Index.Field=Value,在這里使用Index.X區分不同序號的人員信息的字段名稱然后通過Values屬性讀取信息}
			Name:= SL.Values[Index + /'Name/'];
			Tel:= SL.Values[Index + /'Tel/'];
			Address:= SL.Values[Index + /'Address/'];
			List.Add(MakeInfo(Name, Tel, Address));
		end;
	end;
	
	{把TList里的人員信息保存到Info.txt}
	procedure SqveInfo;
	var
		I: Integer;
		P: PInfo;
	begin
		SL.Clear;{清空TStringList}
		for I:=0 to List.Count-1 do
		begin
			P:= List.Items;
			SL.Add(IntToStr(I) + /'.Name = /' + P^.Name);
			SL.Add(IntToStr(I) + /'.Tel = /' + P^.Tel);
			SL.Add(IntToStr(I) + /'.Address = /' + P^.Address);
		end;
		SL.SaveToFile(AppPath + /'Info.txt/');
	end;
	
	{初始化程序}
	procedure InitInfo;
	begin
		SL:= TStringList.Create;
		List:= TList.Create;
		{獲得本程序的路徑}
		AppPath:= ExtractFilePath(ParamStr(0));
	end;
	
	{清空程序使用的內存}
	procedure FinalInfo;
	var
		I: Integer;
	begin
		for I:=0 to List.Count-1 do
			Dispose(PInfo(List.Items));
		List.Free;
		SL.Free;
	end;
	
begin
{TODO -oUser -cConsole Main:Insert code here}
	InitInfo;
	LoadInfo;
	Writeln(/'Information Editor V1.0 by VCZH/');
	PrintMenu;
	
	while GetCommand do;{循環直到返回False}
	
	saveInfo;
	FinalInfo;
	
	writeln(/'謝謝使用。請按回車鍵退出程序/');
	readln;
end;

  在這個程序測試完畢(由於時間關系,並沒有執行嚴格的測試,所以一些小細節可能會沒有注意到,不過不會影響程序功能的正確性)時,Info.txt 的內容如下

0.Name=vczh
0.Tel=1234567
0.Address=Jupiter
1.Name= 陳梓瀚
1.Tel=8888888
1.Address=Venus
2.Name=chenzihan
2.Tel=9999999
2.Address=Mars
3.Name=Victor
3.Tel=0000000
3.Address=Saturn

  

  在循環開始時,I的值被指向0,然后Index的值就變成"0."。

  在執行Name:= SL.Values[Index + /'Name/']時,就等於執行Name:= SL.Values[/'0.Name/'],於是,"0.Name"這個字段的內容就被讀取,然后Name就變成了"vczh".

  接着進入下一個循環。

  程序就是通過在字段前加"Index." 這個字段的方法區分不同人員的信息。在讀取完一個人的信息之后,程序執行 List.Add(MakeInfo(Name, Tel, Address)),信息便存放在List里了。

  程序通過操作List增加、刪除或修改人員信息。在用戶退出程序時,長城些許將List里的所有信息保存到"Info.txt" 里。

  過程就是這樣的:程序先清空SL 里的內容,然后按照Info.txt 原來的文件格式填寫信息。因為Info.txt里的人員數目是會改變的,因此便不能使用TStringLiist.Values屬性修愛。而必須在清空TStringList后手動構建字段並填寫信息。

 


 

  經過這樣的介紹,大家對Delphi 包含的與List,有相似性質的類都有了一定的認識。Delphi VCL 開發組為我們准備了這么多既實用又有效率的功能。而List在龐大的Delphi VCL 里只是滄海一粟。所以我們今后應當繼續研究Delphi VCL,充分利用 VCL,為自己的項目增添光輝


免責聲明!

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



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