36

我需要存储一个临时的记录列表,并且认为这TList将是一个好方法吗?但是,我不确定如何使用 a 来执行此操作,TList并且想知道这是否是最好的,以及是否有人有任何示例说明如何执行此操作?

4

8 回答 8

28

最简单的方法是创建自己的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 所说,这需要更多的工作,因为您必须在添加新记录时分配内存。

于 2011-04-26T23:36:24.537 回答
19

首先,如果您想将经典的 TList 与 Records 结合起来,您需要:

  1. 将记录分配到堆上,而不是堆栈上。像 Remy 一样使用 GetMem。
  2. 获取记录的地址并将其添加到 TList。
  3. 从列表中删除项目并使用它时,取消引用它:
  4. 之后记得释放和清理。

将列表与记录相结合需要大量的“指针和堆管理”工作,以至于这种技术只能在专家的能力范围内。

您所要求的替代方案仍然使用称为“TList”的东西,包括使用带有记录类型的 generics.collections 样式 TList,这将具有 TList 的所有优点,但基本上需要您做很多完整的事情-记录副本以将数据放入其中。

执行您要求的最惯用的 Delphi 方法是:

  1. 使用带有类类型的 TList 或 TObjectList 而不是记录。通常,在这种情况下,您最终会继承 TList 或 TObjectList。

  2. 使用记录类型的动态数组,但请注意,对数组类型进行排序更难,并且在运行时扩展数组类型不如使用 TList 快。

  3. 将 generics.Collections TList 与您的类一起使用。这使您可以避免每次要使用具有不同类的列表时对 TList 或 TObjectList 进行子类化。

显示动态数组的代码示例:

 TMyRec = record
    ///
 end;

 TMyRecArray = array of TMyRec;

 procedure Demo;
 var
    myRecArray:TMyRecArray;
 begin
    SetLength(myRecArray,10);
 end;

现在了解一些关于为什么 TList 不容易与 Record 类型一起使用的背景信息:

TList 更适合与 Class 类型一起使用,因为类型为 'TMyClass' 的变量,其中 'type TMyClass = class .... end;' 可以很容易地“引用”为指针值,这就是 TList 所拥有的。

Record 类型的变量在 Delphi 中是值类型,而类值是隐式引用值。您可以将按引用值视为隐形指针。您不必取消引用它们来获取它们的内容,但是当您将它添加到 TList 时,您实际上只是添加了指向 TList 的指针,而不是复制或分配任何新内存。

雷米的回答从字面上告诉你如何做想做的事,我写我的答案只是因为我想警告你你所问的细节,并建议你也考虑替代方案。

于 2011-04-26T23:05:51.990 回答
11

您可以查看我们的TDynArray 包装器。它在一个开源单元中定义,从 Delphi 6 一直到 XE。

使用,您可以使用类似的属性和方法TDynArray访问任何动态数组(如TIntegerDynArray = array of integerTRecordDynArray = array of TMyRecord) ,例如和一些新方法,例如和允许对任何动态数组进行快速二进制序列化,甚至包含字符串或记录 -也可以使用一种方法来创建个人根据动态数组内容索引。如果您愿意,还可以将数组内容序列化为 JSON。或方法也可用。TListCount, Add, Insert, Delete, Clear, IndexOf, Find, SortLoadFromStream, SaveToStream, LoadFromSaveToCreateOrderedIndexSlice, ReverseCopy

它将处理记录的动态数组,甚至记录中的记录,其中包含字符串或其他动态数组。

使用外部Count变量时,您可以加快在引用的动态数组中添加元素的速度。

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类,它允许动态数组内容的内部散列。它非常快并且能够散列任何类型的数据(有字符串的标准散列器,但您可以提供自己的 - 甚至可以自定义散列函数)。

请注意,TDynArrayandTDynArrayHashed 只是现有动态数组变量的包装器。因此,您可以TDynArray根据需要初始化包装器,以更有效地访问任何本机 Delphi 动态数组。

于 2011-04-27T05:40:09.143 回答
4

您可以为此使用 TList,例如:

type
  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;
于 2011-04-26T23:00:07.510 回答
4

使用 System.Generics.Collections 中的 Generiс TList。如果您需要通过引用访问通用 TList 中的记录并且不复制记录: 使用List.List - 直接访问 TList 的数组。

MyList := TList<TTestRec>.Create; 
[...] 

var lRecP: PTestRec; // (PTestRec = ^TTestRec)
lRecP := @MyList.List[i]; 

现在您无需复制即可访问 Tlist 数组中的记录。

于 2019-12-26T17:02:29.990 回答
1

这完全取决于您要存储的数据类型。

您可以考虑使用TCollectionand 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();
于 2011-04-27T04:07:12.127 回答
1

我们刚刚在这里遇到了一个类似的问题,其中包含一个通用的记录列表。希望以下伪代码有所帮助。

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;
于 2011-07-28T23:42:54.587 回答
1

如果使用不存在泛型的旧版 Delphi,请考虑从 TList 继承并覆盖 Notify 方法。添加项目时,分配内存,复制添加的指针内存内容并覆盖列表中的内容。删除时,只需释放内存。

  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*);
...
  • 请注意,在我使用 CopyMemory(LPtr, Ptr, FPtrSize) 的地方,您可以使用另一种复制方法。我的列表旨在存储带有指针引用的记录,因此它不管理它的字段内存。
于 2019-01-31T21:40:01.297 回答