Delphi TList записей - PullRequest
       80

Delphi TList записей

30 голосов
/ 27 апреля 2011

Мне нужно сохранить временный список записей, и я подумал, что TList будет хорошим способом сделать это? Однако я не уверен, как это сделать с TList, и мне было интересно, было ли это лучше, а также есть ли у кого-нибудь примеры, как это сделать?

Ответы [ 7 ]

28 голосов
/ 27 апреля 2011

Самый простой способ - создать собственного потомка 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.

Это устраняет пару вещей:

  • Это обрабатывает освобождение памяти.
  • Вы не делаетенужно набрать все, чтобы использовать его.

Как сказали оба Реми и Уоррен, это немного больше работы, потому что вы должны распределять память при добавлении новых записей.

18 голосов
/ 27 апреля 2011

Во-первых, если вы хотите объединить классический TList с записями, вам необходимо:

  1. Распределить записи в куче, а не в стеке.Используйте GetMem, как сделал Реми.
  2. Возьмите адрес записи и добавьте его в TList.
  3. При удалении элемента из списка и использовании его разыменовывайте его:
  4. Не забудьте освободить иочистите, потом.

Для объединения списков с записями требуется так много работы по управлению указателями и кучей, что такая техника будет только в пределах возможностей эксперта.

Альтернативы тому, о чем вы просили, все еще используют что-то, называемое «TList», включая использование TList в стиле generics.collections с типами Record, которые будут иметь все преимущества TList, но потребуют от вас много-record-копии, чтобы получить данные в него.

Наиболее идиоматические способы 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 нелегко использовать с типами записей:

TList лучше подходит для использования с типами классов, поскольку переменная типа 'TMyClass', где 'тип TMyClass = class .... конец;'может быть легко «упомянута» как значение указателя, которое и хранит TList.

Переменные типа Record являются типами-значениями в Delphi, в то время как значения классов неявно являются ссылочными значениями.Вы можете думать о ссылочных значениях как о невидимых указателях.Вам не нужно разыменовывать их, чтобы получить их содержимое, но когда вы добавляете его в TList, вы фактически просто добавляете указатель на TList, а не делаете копию или выделяете какую-либо новую память.

Ответ Реми буквально показывает вам, как сделать точно то, что вы хотите, и я пишу свой ответ только потому, что хочу предупредить вас о деталях того, что вы спрашиваете,и предложить вам рассмотреть альтернативы тоже.

11 голосов
/ 27 апреля 2011

Вы можете взглянуть на нашу обертку TDynArray . Он определен в модуле с открытым исходным кодом, работающем от Delphi 6 до XE.

С помощью TDynArray вы можете получить доступ к любому динамическому массиву (например, TIntegerDynArray = array of integer или TRecordDynArray = array of TMyRecord), используя TList -подобные свойства и методы, например, Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort и некоторые новые методы, такие как LoadFromStream, SaveToStream, LoadFrom и SaveTo, которые позволяют выполнять быструю двоичную сериализацию любого динамического массива, даже содержащего строки или записи - также доступен метод CreateOrderedIndex для создания индивидуального индекса в соответствии с содержимым динамического массива. Вы также можете сериализовать содержимое массива в JSON, если хотите. Методы Slice, Reverse или Copy также доступны.

Он будет обрабатывать динамический массив записей и даже записи внутри записей со строками или другими динамическими массивами внутри.

При использовании внешней переменной 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, который позволяет внутреннее хеширование содержимого динамического массива. Он очень быстрый и может хэшировать любые данные (есть стандартные хэши для строк, но вы можете предоставить свои собственные - даже хеш-функцию можно настроить).

Обратите внимание, что TDynArray и TDynArrayHashed являются просто обертками вокруг существующей переменной динамического массива. Таким образом, при необходимости можно инициализировать оболочку TDynArray для более эффективного доступа к любому встроенному динамическому массиву Delphi.

4 голосов
/ 27 апреля 2011

Для этого вы можете использовать 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;
1 голос
/ 29 июля 2011

Мы только что столкнулись с подобной проблемой здесь с общим списком записей.Надеюсь, что следующий псевдо-код поможет.

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;
1 голос
/ 27 апреля 2011

Все зависит от типа данных, которые вы хотите сохранить.

Вы можете рассмотреть возможность использования 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();
0 голосов
/ 01 февраля 2019

Если вы используете более старую версию 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), вы можете использовать другой способ копирования.Мой список предназначен для хранения записи со ссылками указателя, поэтому он не управляет памятью своих полей.
...