惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
Last Week in AI
Last Week in AI
P
Proofpoint News Feed
Engineering at Meta
Engineering at Meta
H
Help Net Security
F
Fortinet All Blogs
MyScale Blog
MyScale Blog
宝玉的分享
宝玉的分享
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 司徒正美
量子位
N
Netflix TechBlog - Medium
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Recorded Future
Recorded Future
博客园 - 三生石上(FineUI控件)
Vercel News
Vercel News
aimingoo的专栏
aimingoo的专栏
I
InfoQ
Microsoft Security Blog
Microsoft Security Blog
Scott Helme
Scott Helme
The Last Watchdog
The Last Watchdog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
AI
AI
WordPress大学
WordPress大学
Security Archives - TechRepublic
Security Archives - TechRepublic
Google Online Security Blog
Google Online Security Blog
U
Unit 42
V2EX - 技术
V2EX - 技术
MongoDB | Blog
MongoDB | Blog
Schneier on Security
Schneier on Security
博客园 - Franky
H
Heimdal Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Jina AI
Jina AI
W
WeLiveSecurity
P
Privacy & Cybersecurity Law Blog
Cloudbric
Cloudbric
B
Blog RSS Feed
N
News | PayPal Newsroom
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
I
Intezer
Hacker News - Newest:
Hacker News - Newest: "LLM"
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园_首页
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
雷峰网
雷峰网

博客园 - shappy

5.12默哀 台风 - shappy - 博客园 童谣 回家 Web Control中填写JavaScript报告"缺少对象"错误问题解决 Midas如何在服务器端自动产生流水号 Clientdataset关于record change by another user错误的总结 游凤凰 设定MSAgent的说话(Balloon)停留时间 获取Agent角色的动作列表 MSAgent简介 摘录 DelphiX简介 摘录 DelphiX的刷新 几种文字编码的介绍 获取/打开/关闭输入法 汉字编码标准与识别 摘录 Tclientdataset实现反向排序 关于Tclientdataset的bug 队列类
TList源码分析 摘录
shappy · 2007-05-14 · via 博客园 - shappy

^_^,都是原来笔记中摘录的东西,很钦佩作者的研究精神。
--shappy

看了这里标题,大家可能以为我会谈TListBox控件,那就错了。我要谈的是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]的时候,就是在写TstringList.Strings[3]。Strings是TstringList的一个缺省属性。数组性的缺省属性就是这样子使用的。如果大家在编写类的时候要用到这么一个功能的话,可以参照如下方法:
property AProperty[I: Integer] read *** write ***;
default;。
Strings 是一个可读写的属性。这也就是说,大家不仅可以获得第N 行的内容,也可以改变第N 行的内容。因此我们需要知道TstringList 里S t r i n g 的总行数。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两个方法,使得我们对文本文件的操作变得非常方便。参数FileName 代表目标文本文件的文件名。例如我们在编写记事本的时候,用到的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文件所在的文件夹里建立一个叫做“Config.txt”的文件,并输入以下内容:

Name=VCZH
Address=Somewhere
Email=vczh@163.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;
{ 获取当前程序文件所在的文件夹名称以获得C o n f i g . t x t 文件的路径 }
SL.LoadFromFile(ExtractFilePath(ParamStr(0))+\'Config.
txt\');
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 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, 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;

procedure TList.SetCapacity(NewCapacity: Integer);
begin
  if (NewCapacity < FCount) or (NewCapacity > MaxListSize) then
    Error(@SListCapacityError, NewCapacity);
  if NewCapacity <> FCapacity then
  begin
    ReallocMem(FList, NewCapacity * SizeOf(Pointer));
    FCapacity := NewCapacity;
  end;
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 的内容一改变,我们就能得到通知。不过前提是我们要覆盖N o t i f y 这个
Procedure。

TObjectList (Contnrs.pas)

T O b j e c t L i s t 中有一个不可缺少的属性:OwnsObjects。如果OwnsObjects是True(缺省值)的话,那么TObjectList会在适当的时候把它列表中的Object释放掉。

现在,让我们用一个例子来结束我对Delphi的List的介绍。这个例子是一个管理人员信息的程序。不过因为这只是一个示例,所以这个程序只是一个简单的ConsoleApplication。使用Console Application来做示例程序可以免掉一些界面设计的工作。这个程序通过Index来管理人员信息。人员信息包括Name、Telephone和Address。程序通过TstringList来读取人员信息文件,然后用那些指向Record的指针把信息存放在TList里并修改。代码如下:
 程序代码
program Info[color=#0000ff];

{$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;

{ 修改人员信息的程序 }
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(\' 输入超出范围。\')
else
begin
{ 取得某个人员信息的指针 }
P:=List.Items;
writeln(\' 开始输入人员信息(若某项信息不需要修改则留
空):\');
write(\' 姓名:\');
readln(Name);
write(\' 电话号码:\');
readln(Tel);
write(\' 地址:\');
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(\' 开始输入人员信息:\');
write(\' 姓名:\');
readln(Name);
write(\' 电话号码:\');
readln(Tel);
write(\' 地址:\');
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;
writeln(IntToStr(I)+\'号===================\');
writeln(\'姓名:\'+P^.Name);
writeln(\'电话:\'+P^.Tel);
writeln(\'地址:\'+P^.Address);

{ 满六个就暂停,刚好填满一个屏幕 }
if I mod 6=5 then
begin
writeln(\' 请按回车键继续。\');
readln;
end;
end;
writeln;
end;


{ 删除人员信息的程序 }
procedure Command_Delete;
var I:Integer;
P:PInfo;
begin
write(\' 请输入要删除的人员信息的序号:\');
readln(I);

if(I<0)or(I>=List.Count)then
writeln(\' 输入超出范围。\')
else
begin
P:=List.Items;
List.Delete(I);

Dispose(P);


writeln(\' 删除执行完毕。\');


writeln;


end;


end;


{ 处理用户输入的命令 }


function GetCommand:Boolean;


{ 返回False 表示退出 }


var C:Char;


begin


write(\' 输入命令并回车:\');


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 把人员信息加载入Tlist }


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 SaveInfo;


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 FinaInfo;


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;


FinaInfo;


writeln(\' 谢谢使用。请按回车键退出程序。\');


readln;


end.[/color]

在这个程序测试完毕(由于时间关系,并没有执行严格的测试,所以一些小细节可能会没有注意到,不过不会影响程序功能的正确性)时,Info.txt 的内容如下


在循环开始时,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里的人员数目是会改变的,因此便不能使用TstringList.Values属性修改,而必须在清空TstringList后手动构建字段并填写信息。


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


经过这样的介绍,大家对Delphi 包含的与List,有相似性质的类都有了一定的认识。Delphi VCL 开发组为我们准备了这么多既实用又有效率的功能。而List在庞大的Delphi VCL 里只是沧海一粟。所以我们今后应当继续研究Delphi VCL,充分利用 VCL,为自己的项目增添光辉。__