我需要存储一个临时的记录列表,考虑使用 TList
进行实现,但我不确定如何使用 TList
进行操作,想知道是否这种方式最好,并了解一些如何实现的示例。
TList
子类。这里有一个快速的示例控制台应用程序以进行演示:program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
PMyRec=^TMyRec;
TMyRec=record
Value: Integer;
AByte: Byte;
end;
TMyRecList=class(TList)
private
function Get(Index: Integer): PMyRec;
public
destructor Destroy; override;
function Add(Value: PMyRec): Integer;
property Items[Index: Integer]: PMyRec read Get; default;
end;
{ TMyRecList }
function TMyRecList.Add(Value: PMyRec): Integer;
begin
Result := inherited Add(Value);
end;
destructor TMyRecList.Destroy;
var
i: Integer;
begin
for i := 0 to Count - 1 do
FreeMem(Items[i]);
inherited;
end;
function TMyRecList.Get(Index: Integer): PMyRec;
begin
Result := PMyRec(inherited Get(Index));
end;
var
MyRecList: TMyRecList;
MyRec: PMyRec;
tmp: Integer;
begin
MyRecList := TMyRecList.Create;
for tmp := 0 to 9 do
begin
GetMem(MyRec, SizeOf(TMyRec));
MyRec.Value := tmp;
MyRec.AByte := Byte(tmp);
MyRecList.Add(MyRec);
end;
for tmp := 0 to MyRecList.Count - 1 do
Writeln('Value: ', MyRecList[tmp].Value, ' AByte: ', MyRecList[tmp].AByte);
WriteLn(' Press Enter to free the list');
ReadLn;
MyRecList.Free;
end.
这样可以消除一些问题:
正如Remy和Warren都说的那样,这会多做一点工作,因为你需要在添加新记录时分配内存。
MyRecList.Delete(i)
会发生什么?正确的方法是重写 Delete
方法或捕获 Notify(cnRemoved)
。此外,如果记录包含动态类型字段(动态数组、字符串),则必须在释放之前对其进行 Finalize
处理,以避免内存泄漏。 - Fr0sTAdd
方法(重载),其参数为所有记录字段,并在此类方法中分配该记录。当然,这仅适用于具有少量字段的记录。 - TLama首先,如果您想将经典的TList与记录结合使用,则需要:
将列表与记录结合需要进行大量的“指针和堆管理”工作,这种技术只适用于专家。
您所要求的替代方案仍然使用名为“TList”的东西,包括使用泛型.collections样式的带有记录类型的TList,它将具有TList的所有好处,但需要您基本上执行整个记录复制才能将数据放入其中。
实现您所要求的最惯用的Delphi方法之一是:
使用具有类类型而非记录的TList或TObjectList。通常在这种情况下,您最终会子类化TList或TObjectList。
使用记录类型的动态数组,但请注意,对数组类型进行排序比使用TList更难,并且在运行时扩展数组类型的速度不如TList快。
使用generics.Collections TList与您的类。这样,每当您想要使用具有不同类的列表时,就可以避免子类化TList或TObjectList。
下面是一个展示动态数组的代码示例:
TMyRec = record
///
end;
TMyRecArray = array of TMyRec;
procedure Demo;
var
myRecArray:TMyRecArray;
begin
SetLength(myRecArray,10);
end;
以下是关于为什么使用TList与Record类型不容易的一些背景信息:
对于Class类型,TList更适合使用,因为类类型的变量'TMyClass', 其中'type TMyClass = class .... end;' 可以很容易地被“引用”为一个指针值,这就是TList所持有的。
在Delphi中,Record类型的变量是Value-Types,而类值被隐式地作为by-reference值。你可以把by-reference值看作是隐形指针。你不需要解引用它们才能访问它们的内容,但当你将其添加到TList时,你实际上只是向TList添加了一个指针,而不是复制或分配任何新内存。
Remy的答案展示了如何精确地做到你想要的,我写我的答案只是因为我想警告你关于你所要求的细节,并建议你考虑其他替代方案。
TDynArray
,您可以像使用 TList
那样访问任何动态数组(如 TIntegerDynArray = array of integer
或 TRecordDynArray = array of TMyRecord
),例如 Count、Add、Insert、Delete、Clear、IndexOf、Find、Sort
等属性和方法,还有一些新方法,例如 LoadFromStream、SaveToStream、LoadFrom
和 SaveTo
,可快速地对任何动态数组进行二进制序列化,甚至包含字符串或记录 - 还提供了 CreateOrderedIndex
方法以根据动态数组内容创建个体索引。您还可以将数组内容序列化为 JSON,如果需要的话,也可以使用 Slice、Reverse
或 Copy
方法。type
TPerson = packed record
sCountry: string;
sFullName: string;
sAddress: string;
sCity: string;
sEmployer: string;
end;
TPersons = array of TPerson;
var
MyPeople: TPersons;
(...)
procedure SavePeopleToStream(Stream: TMemoryStream);
var aPeople: TPerson;
aDynArray: TDynArray;
begin
aDynArray.Init(TypeInfo(TPersons),MyPeople);
aPeople.sCountry := 'France';
aPeople.sEmployer := 'Republique';
aDynArray.Add(aPeople);
aDynArray.SaveToStream(Stream);
end; // no try..finally Free needed here
还有一个 TDynArrayHashed
类,它允许对动态数组内容进行内部哈希。它非常快速,并且能够哈希任何类型的数据(有用于字符串的标准哈希函数,但您也可以提供自己的哈希函数 - 甚至可以定制哈希算法)。
请注意,TDynArray
和 TDynArrayHashed
只是现有动态数组变量的包装器。因此,您可以根据需要初始化一个 TDynArray
包装器,以更有效地访问任何本机 Delphi 动态数组。
TList
实现。但它绝对是类似于TList
的实现,比使用TList
更快(因为记录是按块分配的)。TList
不适用于存储记录,而是指针。在我们的包装器中,有一些TList
没有的方法,例如哈希、内部保存或加载到内存或流、使用外部整数查找索引进行排序、Slice
方法等等。例如,在我们的框架中,我们使用几行代码来存储编译后的SQL语句缓存。 - Arnaud BouchezSynCommons.pas
和 SynLZ.pas
,以及 Synopse.inc
。 - Arnaud Boucheztype
pRec = ^sRec;
sRec = record
Value: Integer;
...
end;
var
List: TList;
Rec: pRec;
I: Integer;
begin
List := TList.Create;
try
for I := 1 to 5 do begin
GetMem(Rec);
try
Rec^.Value := ...;
...
List.Add(Rec);
except
FreeMem(Rec);
raise;
end;
end;
...
for I := 0 to List.Count-1 do
begin
Rec := pRec(List[I]);
...
end;
...
for I := 0 to List.Count-1 do
FreeMem(pRec(List[I]));
List.Clear;
finally
List.Free;
end;
end;
PMyRec
表示指向TMyRec
的指针。阅读使用指针的RTL/VCL的任何代码即可看到示例。将其称为“PointerToARecord”是冗余且冗长的。我们知道你不喜欢指针;但这并不意味着它们在语言中没有用处。 - Ken White使用 System.Generics.Collections 中的泛型 TList。如果需要通过引用来访问泛型 TList 中的记录且不想复制记录,请使用 List.List - 直接访问 TList 数组。
MyList := TList<TTestRec>.Create;
[...]
var lRecP: PTestRec; // (PTestRec = ^TTestRec)
lRecP := @MyList.List[i];
TOwnedList = class(TList)
private
FPtrSize: integer;
protected
procedure Notify(Ptr: Pointer; Action: TListNotification); override;
public
constructor Create(const APtrSize: integer);
end;
constructor TOwnedList.Create(const APtrSize: integer);
begin
inherited Create();
FPtrSize := APtrSize;
end;
procedure TOwnedList.Notify(Ptr: Pointer; Action: TListNotification);
var
LPtr: Pointer;
begin
inherited;
if (Action = lnAdded) then begin
GetMem(LPtr, FPtrSize);
CopyMemory(LPtr, Ptr, FPtrSize); //May use another copy kind
List^[IndexOf(Ptr)] := LPtr;
end else if (Action = lnDeleted) then begin
FreeMem(Ptr, FPtrSize);
end;
end;
使用方法:
...
LList := TOwnedList.Create(SizeOf(*YOUR RECORD TYPE HERE*));
LList.Add(*YOU RECORD POINTER HERE*);
...
我们在这里遇到了一个类似的问题,涉及到记录的通用列表。希望以下伪代码能够帮助解决。
type
PPat = ^TPat;
TPat = record
data: integer;
end;
...
var
AList: TList<PPat>;
...
procedure TForm1.Button1Click(Sender: TObject);
var
obj: PPat;
begin
obj := AList[0];
obj.data := 1;
Assert(obj.data = AList[0].data); // correct
end;
procedure TForm1.FormCreate(Sender: TObject);
var
obj: PPat;
begin
AList := TList<PPat>.Create;
GetMem(obj, SizeOf(TPat)); // not shown but need to FreeMem when items are removed from the list
obj.data := 2;
AList.Add(obj);
end;
一切都取决于您想要存储的数据类型。
您可以考虑使用TCollection
和TCollectionItem
。
这是我从一个工作单元编辑而来的代码。我在其中使用TCollection
从文件夹中读取报表定义列表。每个报表由一种模板和一个SQL语句组成,必须与文件名一起存储。
由于它已经过编辑,并且使用了一些我自己的单元(例如TedlFolderRtns将文件读入内部列表),因此该示例足够简单易懂。通过几次替换,您就可以根据自己的需要进行调整。
在帮助文档中查找TCollection,您可以用它做很多事情。它可以将您的代码处理得像一个类似结构。
unit cReports;
interface
uses
SysUtils, Classes, XMLDoc, XMLIntf, Variants,
// dlib - Edelcom
eIntList, eProgSettings,eFolder ;
type
TReportDefItem = class(TCollectionItem)
private
fSql: string;
fSkeleton: string;
fFileName: string;
procedure Load;
procedure SetFileName(const Value: string);
public
constructor Create(Collection:TCollection); override;
destructor Destroy ; override;
property FileName: string read fFileName write SetFileName;
property Sql : string read fSql write fSql;
property Skeleton : string read fSkeleton write fSkeleton;
end;
TReportDefList = class(TCollection)
private
function OsReportFolder: string;
function GetAction(const Index: integer): TReportDefItem;
public
constructor Create(ItemClass: TCollectionItemClass);
destructor Destroy; override;
procedure LoadList;
function Add : TReportDefItem;
property Action [ const Index:integer ]: TReportDefItem read GetAction;
end;
implementation
{ TReportDefList }
constructor TReportDefList.Create(ItemClass: TCollectionItemClass);
begin
inherited;
end;
destructor TReportDefList.Destroy;
begin
inherited;
end;
function TReportDefList.Add: TReportDefItem;
begin
Result := TReportDefItem( Add() );
end;
function TReportDefList.GetAction(const Index: integer): TReportDefItem;
begin
if (Index >= 0) and (Index < Count)
then Result := TReportDefItem( Items[Index] )
else Result := Nil;
end;
procedure TReportDefList.LoadList;
var Folder : TedlFolderRtns;
i : integer;
Itm : TReportDefItem;
begin
Folder := TedlFolderRtns.Create;
try
Folder.FileList( OsReportFolder,'*.sw.xml', False);
for i := 0 to Folder.ResultListCount -1 do
begin
Itm := Add();
Itm.FileName := Folder.ResultList[i];
end;
finally
FreeAndNil(Folder);
end;
end;
function TReportDefList.OsReportFolder: string;
begin
Result := Application.ExeName + '_RprtDef';
end;
{ TReportDefItem }
constructor TReportDefItem.Create(Collection: TCollection);
begin
inherited;
fSql := '';
fSkeleton := '';
end;
destructor TReportDefItem.Destroy;
begin
inherited;
end;
procedure TReportDefItem.Load;
var XMLDoc : IXMLDocument;
TopNode : IXMLNode;
FileNode : IXmlNode;
iWebIndex, iRemoteIndex : integer;
sWebVersion, sRemoteVersion: string;
sWebFileName: string;
begin
if not FileExists(fFileName ) then Exit;
XMLDoc := TXMLDocument.Create(nil);
try
XMLDoc.LoadFromFile( fFileName );
XMLDoc.Active := True;
TopNode := XMLDoc.ChildNodes.FindNode('sw-report-def');
if not Assigned(TopNode) then Exit;
FileNode := TopNode.ChildNodes.First;
while Assigned(FileNode) do
begin
fSql := VarToStr( FileNode.Attributes['sql'] );
fSkeleton := VarToStr( FileNode.Attributes['skeleton'] );
FileNode := FileNode.NextSibling;
end;
XMLDoc.Active := False;
finally
XMLDoc := Nil;
end;
end;
procedure TReportDefItem.SetFileName(const Value: string);
begin
if fFileName <> Value
then begin
fFileName := Value;
Load;
end;
end;
end.
用法如下:
fReports := TReportDefList.Create( TReportDefItem );
fReports.LoadList();
Generics.Collections.TList<T>
。我认为这值得考虑。 - David Heffernan