Delphi⼩谈之TListTobjectListTstringList篇
【转】⼩谈之TList/TobjectList/TstringList 篇
Delphi提供给我们的具有列表性质的类:TstringList、TList和TObjectList。TstringList⽤来存放字符串,TList存放指针,⽽TObjectList则存放对象(Object)。
在我们使⽤Delphi的过程中,有很多数据的存储是要靠数组解决的。虽然Delphi现在已经⽀持了可变数组,不过总有那么点缺陷:我们不能在删除⼀个项后,使后边的项⾃动前靠。因此,说说Delphi现成的List还是有价值的。
TstringList (Classes.pas)
在 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]的时候,就是在写stringList.Strings[3]。Strings是TstringList的⼀个缺省属性。
数组性的缺省属性就是这样⼦使⽤的。如果⼤家在编写类的时候要⽤到这么⼀个功能的话,可以参照如下⽅法:
property AProperty[I: Integer] read * write *;default;。 Strings 是⼀个可读写的属性。这也就是说,⼤家不仅可以获得第N ⾏的内容,也可以改变第N ⾏的内容。因此我们需要知道TstringList ⾥String的总⾏数。TstringList的属性Count则可以满⾜我们的需要。
上⾯已经说过,Text是⼀个返回所有字符串的属性。向Text赋值时,TstringList能够⾃动地把Text分成⼀⾏⼀⾏的,然后储存在TstringList⾥(当然,TstringList⾥⾯并不完全是这么储存的,详细过程建议看TstringList和TStrings的代码)。
这样,Strings返回的字符串就是没有回车和换⾏的。但是,如果我们向Strings赋值的字符串有回车和换⾏,那么会出现什么情况呢?此时,Strings就会把那个字符串断成⼏⾏,插⼊到原来的位置上。如果TstringList只有这么些功能的话,那我就不必专门拿出来讲了——我是说,TstringList能让我们任意地插⼊或删除某⾏,这就要⽤到TstringList提供的⽅法。
function Add(const S: string): Integer;
procedure Append(const S: string);
Add ⽅法向TstringList的末尾添加⼀⾏String(在这⾥和下⾯我们都假设输⼊的字符串没有回车和换⾏,否则String将被分割)。参数S 代表的是要插⼊的字符串的内容。Add的返回值代表了新的字符串在TstringList的位置——也就是最后⼀⾏的位置,即新的Count 减去⼀。
Append ⽅法与Add 唯⼀不同的地⽅是没有返回值。
procedure Insert(Index: Integer; const S: string);
Insert⽅法向TstringList插⼊⼀⾏字符串。在Insert⾥,我们可以⾃由地选择字符串插⼊的位置。参数S 代表要插⼊的字符串的内
容,Index代表要插⼊的位置。
procedure Delete(Index: Integer);
Delete ⽅法删除某⾏字符串,我们同样可以⾃由地选择删除任意⼀⾏字符串。参数Index代表要删除的那⼀⾏字符串的位置。
学习笔记
function IndexOf(const S: string): Integer;
IndexOf查某⼀字符串在TstringList⾥的位置。参数S代表要查的字符串。如果TstringList⾥不存在S的话,则返回-1。
procedure Move(CurIndex, NewIndex: Integer);
procedure Exchange(Index1, Index2: Integer);
TstringList另外还提供了两个⽅法—— Move和Exchange。Move⽅法能把⼀⾏字符串抽出来并插⼊到另⼀个指定的位置上。参数CurIndex代表要移动的字符串的位置,NewIndex 代表字符串新的位置。
Exchange⽅法则能将随便两⾏字符串交换。参数Index1和Index2代表两⾏需要交换的字符串的位置。
procedure LoadFromFile(const FileName: string);
procedure SaveToFile(const FileName: string);
TstringList 的LoadFromFile和SaveToFile两个⽅法,使得我们对⽂本⽂件的操作变得⾮常⽅便。参数F
ileName 代表⽬标⽂本⽂件的⽂件名。例如我们在编写记事本的时候,⽤到的TMemo.Lines 就是TStrings(TstringList的⽗类,功能⼏乎相同,只是因为TStrings是虚类,我们⽆法创建并使⽤)。在保存的时候只需⼀⾏代码:TMemo.Lines.SaveToFile(FileName),⾮常⽅便。
TstringList还有⼀项特殊功能:可以把TstringList当成ini控制器使⽤。不过它没有Section。现在我就来介绍TstringList的两个属性:Names和Values。
如果TstringList的内容是这样⼦的:
ID=1
Name=Vczh
PositionID=8
Tel=00000000
那么,Names[2]就返回“PositionID”,Values[\’PositionID\’]就返回“8”。其中“Names”是只读属性,⽽“Value”则可写。TstringList使我们不必拘泥于ini和注册表。关于TstringList没有Section这个问题,我们完全可以在Names ⾥做点⼿脚,只要程序能够识别就⾏(⽂章末尾处的范例⾥将出现此⽅法供⼤家参照)。
TstringList还有⼀个可以存放Object的功能。但是我个⼈认为使⽤TObjectList⽐较好,因为TObjectList在这⽅⾯提供了⽐TstringList更多的功能。
下⾯我提供⼀个例程来介绍Values属性。
新建⼀个⼯程保存,并在dpr⽂件所在的⽂件夹⾥建⽴⼀个叫做“”的⽂件,并输⼊以下内容:
Name=VCZH
Address=Somewhere
Email=test@163
然后,建⽴⼀个窗体。如图所⽰:并在TForm1 的Private 区段⾥建⽴⼀个变量:
SL:TstringList;
这个例程的功能是编辑 ⾥的Name 、Address、Email。SL在程序启动时读⼊⽂件;按下Cancel按钮则退出;按下OK 按钮则改变SL 的内容并保存在⽂件⾥;当程序再次运⾏时,改变后的内容就会显⽰在三个⽂本框⾥。代码如下: procedure TForm1.FormCreate(Sender: TObject);
begin
SL:=TstringList.Create;
{ 获取当前程序⽂件所在的⽂件夹名称以获得C o n f i g . t x t ⽂件的路径 }
SL.LoadFromFile(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
{ 通过上⾯介绍的V a l u e s 属性获得各个字段的内容 }
edtName.Text:=SL.Values[\’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 (Classes.pas)
在我刚开始接触TList的时候,TList搞得我迷雾重重,都是Capacity属性惹的祸。我查了Delphi的帮助,它说Capacity是TList的最⼤容量,⼜在什么地⽅说MaxIntdiv 4是TList的最⼤容量。最后我搞明⽩了,Capacity是临时的,MaxInt div4才是真正的最⼤容量。只要你的内存受得了就⾏,算起来⼀共是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, lnAdded);
{ 给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
if FCapacity > 8 then
Delta := 16
else
Delta := 4;
{ 改变数组⼤⼩ }
SetCapacity(FCapacity + Delta);
end;
既然Capacity会⾃动增加,那么还要Capacity⼲什么呢?还不如使⽤链表。不过我后来意识到,在使⽤链表的时候,取得某个位置的指针⽐数组困难,要⽤⽐较费时间的循环。TList刚好解决了这个问题。我们既可以把TList当成数组,也可以把它当成链表。
TList 除了保存的对象是指针以外,其它地⽅都与TstringList很像。所以接下来我只介绍⼆者不同之处。
我们同样可以使⽤TList或者TList.Items获得某⼀位置的指针。如果嫌TList.Items是属性没有效率的话,这⾥还有⼀个List属性,指向内部的FList,可以这么⽤:
TList.List^。
TList提供了First和Last两个属性,分别返回第⼀个和最后⼀个指针。
TList 也提供了⼀个Remove⽅法。与Delete不同的是,Delete删除的是已知位置的指针,Remove删除的是已知指针。只要TList包含有你想删除的指针,就可以使⽤Remove(Pointer)。Remove的返回值是指针在还没有被删除之前的位置。使⽤⽅法如下:
procedure Delete(Index: Integer);
function Remove(Item: Pointer): Integer;
TList还有⼀个Pack⽅法。它能够把所有不是nil的指针聚在⼀起,同时把Count的值改变,这样,所有没⽤的指针就会被删除,但是并不会减少Capacity。如果你想把没⽤的空间都释放掉的话,可以把Capacity设置成Count。
最后,我想说的是Protected⾥的Notify。⼤家在Add的代码⾥就能看到,在Insert、Delete之类的代码⾥我们也能看得到Notify的踪迹。既然FList的内容已经被改变了,Notify还要做什么⼯作呢?
看⼀下Notify的代码:学习笔记
TListNotification = (lnAdded, lnExtracted, lnDeleted);
procedure TList.Notify(Ptr: Pointer; Action:
TListNotification);
begin
end;
留着⼀个空的Notify 有什么⽤呢?再看它的声明:
procedure Notify(Ptr: Pointer; Action: TListNotification);virtual;
原来Notify 是⼀个虚函数,当我们因为有特殊要求⽽继承TList类的话,只要TList 的内容⼀改变,我们就能得到通知。不过前提是我们要覆盖Notify这个Procedure。
TObjectList (Contnrs.pas)
TObjectList中有⼀个不可缺少的属性:OwnsObjects。如果OwnsObjects是True(缺省值)的话,那么TObjectList会在适当的时候把它列表中的Object释放掉。
现在,让我们⽤⼀个例⼦来结束我对Delphi的List的介绍。这个例⼦是⼀个管理⼈员信息的程序。不过因为这只是⼀个⽰例,所以这个程序只是⼀个简单的ConsoleApplication。使⽤Console Application来
做⽰例程序可以免掉⼀些界⾯设计的⼯作。这个程序通过Index来管理⼈员信息。⼈员信息包括Name、Telephone和Address。程序通过TstringList来读取⼈员信息⽂件,然后⽤那些指向Record的指针把信息存放在TList⾥并修改。代码如下:
程序代码
program Info;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
PInfo=^TInfo;
TInfo=record
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;
{ 修改⼈员信息的程序 }delphi app
procedure Command_Edit;
var I:Integer;
Name,Tel,Address:String;
P:PInfo;
begin
write(\’ 请输⼊要修改的⼈员信息的序号:\’);
readln(I);
if(I<0)or(I>=List.Count)then
writeln(\’ 输⼊超出范围。\’)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论