【问题标题】:How to store dynamic arrays in a TList?如何将动态数组存储在 TList 中?
【发布时间】:2011-03-03 22:58:32
【问题描述】:

我需要存储未知数量的组。每个组都有未知数量的元素/项目。 这是我的“组”:

 TGroup= array of Integer;     <------ dynamic array (as you can see) :)

我想使用 TList 来保存我的组。这个想法是我可能想稍后访问这些组并向它们添加更多项目。

我有这个代码,但我不能让它工作:

TYPE
   TGroup= array of Integer;                              // Each group has x items (x can be from 1 to 10000)


procedure TForm1.FormCreate(Sender: TObject);
VAR CurGroup: TGroup;
    grp, item: Integer;
    Groups: TList;                                        // can contain up to 1 million groups
begin
 Groups:= TList.Create;

 { Init }
 for grp:= 1 to 4  DO                                     // Put a dummy item in TList
  begin
   SetLength(CurGroup, 1);                                // Create new group
   Groups.Add(@CurGroup);                                 // Store it
  end;

 CurGroup:= NIL;                                          // Prepare for next use

 for grp:= 1 to 4  DO                                     // We create 4 groups. Each group has 3 items
  begin
    CurGroup:= Groups[Groups.Count-1];                    // We retrieve the current group from list in order to add more items to it

    { We add few items }
    for item:= 0 to 2  DO
     begin
       SetLength(CurGroup, Length(CurGroup)+1);           // reserve space for each new item added
       CurGroup[item]:= Item;
     end;

    Groups[Groups.Count-1]:= @CurGroup;                   // We put the group back into the list
  end;

 { Verify }
 CurGroup:= NIL;
 CurGroup:= Groups[0];
 Assert(Length(CurGroup)> 0);                             // FAIL
 if  (CurGroup[0]= 0)
 AND (CurGroup[1]= 1)
 AND (CurGroup[2]= 2)
 then Application.ProcessMessages;                        

 FreeAndNil(Groups);
end;

注意:代码是完整的。您可以将其粘贴到您的 Delphi (7) 中进行尝试。

【问题讨论】:

  • 你所说的“组”是指具有同一元素的联合岩浆,并且每个元素都有一个逆元素?
  • @Andreas - 不。对我来说,该组只是一个数字列表(数组)。这是我对“组”的定义:TGroup= 整数数组;

标签: delphi delphi-7


【解决方案1】:

哦,这在较新版本的 Delphi 中会好很多……您可以使用通用的 TList。 var 组:TList;

最好的办法是使用另一个动态数组:Groups: array of TGroup;

原因是动态数组是编译器管理和引用计数的。 TList 只对指针进行操作。在尝试将 dynarray 放入 TList 时,没有直接的方法可以保持引用计数的正常。

您遇到的另一个问题是将动态数组变量的 堆栈地址 添加到 TList,而不是实际数组。表达式@CurGroup 是“CurGroup 变量的地址”,它是一个局部变量,在堆栈上。

【讨论】:

  • +1。我已经放弃为最近的 Delphi 开箱即用的东西寻找解决方案。
  • 我使用了 'array of TGroup' 并且它有效。但我只是按照您对 TList 的建议从“TGroup 数组”“升级”。我想让它也适用于 TList。
  • @Altar,您认为 TList 的哪一点是一个出色的解决方案?如果使用动态数组有效,为什么不坚持呢?就像我说的,让它与 TList 一起工作可能会比它可能值得付出更多的努力。如果必须使用 TList,最好将 TGroup 包装在 TObject 后代中,并将其添加到 TList 而不是数组中。然后,您只需将 TList“数组”转换为您的后代对象类型即可获得 dynarray。看过 Delphi XE 的新 Starter 版本吗?只需 149 美元。
  • @Allen:TList 与动态数组相比有几个优点,最明显的是AddRemoveInsert 方法。但正如您所指出的,没有可用的泛型使得使用 TList 成为托管数据类型的真正痛苦。
  • 我想我会回到“TGroup 数组”。
【解决方案2】:

我已经围绕动态数组 RTTI 创建了一个包装器。

这只是初稿,但它的灵感来自您的问题以及缺少 TList 方法的事实。

type
  TGroup: array of integer;

var 
  Group: TGroup;
  GroupA: TDynArray;
  i, v: integer;
begin
  GroupA.Init(TypeInfo(TGroup),Group); // associate GroupA with Group
  for i := 0 to 1000 do begin
    v := i+1000; // need argument passed as a const variable
    GroupA.Add(v);
  end;
  v := 1500;
  if GroupA.IndexOf(v)<0 then // search by content
    ShowMessage('Error: 1500 not found!');
  for i := GroupA.Count-1 downto 0 do
    if i and 3=0 then
      GroupA.Delete(i); // delete integer at index i
end;

这个TDynArray 包装器也适用于字符串数组或记录数组...只需要打包记录并且没有引用计数字段(字节、整数、双精度...)或字符串引用计数字段(其中没有 Variant 或 Interface)。

IndexOf() 方法将按内容搜索。那是例如对于记录数组,所有记录字段(包括字符串)必须匹配。

在我们的源代码存储库中查看the SynCommons.pas unit 中的TDynArray。适用于从 Delphi 6 到 XE,并处理 Unicode 字符串。

TTestLowLevelCommon._TDynArray 方法是与此包装器关联的自动化单一测试。您将在此处找到记录数组示例和更多高级功能。

我目前正在实施SaveToStreamLoadToStream 方法...

也许是一种在所有 Delphi 版本中使用类泛型功能的新方法。

编辑:

我在TDynArray 记录/对象中添加了一些新方法:

  • 现在您可以将动态数组内容保存到字符串或从字符串加载(使用LoadFromStream/SaveToStreamLoadFrom/SaveTo 方法)- 它将使用专有但非常快速的二进制流布局;
  • 您可以通过两种方式对动态数组内容进行排序:就地(即交换数组元素内容)或通过外部整数索引查找数组(使用CreateOrderedIndex 方法 - 在这种情况下,您可以对同一数据有多个订单);
  • 您可以指定任何自定义比较函数,并且有一个新的Find 方法可以使用快速二分查找(如果可用)。

这些新方法的工作原理如下:

var
  Test: RawByteString;
...
  Test := GroupA.SaveTo;
  GroupA.Clear;
  GroupA.LoadFrom(Test);
  GroupA.Compare := SortDynArrayInteger;
  GroupA.Sort;
  for i := 1 to GroupA.Count-1 do
    if Group[i]<Group[i-1] then
      ShowMessage('Error: unsorted!');
  v := 1500;
  if GroupA.Find(v)<0 then // fast binary search
    ShowMessage('Error: 1500 not found!');

仍然更接近通用范式,速度更快,并且适用于 Delphi 6 到 XE...

【讨论】:

    【解决方案3】:

    我这里的机器上没有 D7,但您可以尝试类似的方法(控制台测试应用程序 - 它在 XE 中编译,没有提示或警告,但不确定 D7 将如何处理它):

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    uses
      SysUtils, Classes;
    
    type
      TGroup = array of Integer;
      THolder=class(TObject)
        Group: TGroup;
      end;
    
    var
      GroupList: TList;
      i: Integer;
    
    begin
      GroupList := TList.Create;
      for i := 0 to 2 do
      begin
        GroupList.Add(THolder.Create);
        with THolder(GroupList[GroupList.Count - 1]) do
        begin
          SetLength(Group, 3);
          Group[0] := i;
          Group[1] := i + 10;
          Group[2] := i + 20;
        end;
      end;
      for i := 0 to GroupList.Count - 1 do
      begin
        Writeln(i, ':0 ', THolder(GroupList[i]).Group[0]);
        Writeln(i, ':1 ', THolder(GroupList[i]).Group[1]);
        Writeln(i, ':2 ', THolder(GroupList[i]).Group[2]);
      end;
      // Not cleaning up list content, because we're exiting the app.
      // In a real app, of course, you'd want to free each THolder in 
      // the list, or use a TObjectList and let it own the objects.
      // You'd also want a try..finally to make sure the list gets freed.
      GroupList.Free;
      Readln;
    end.
    

    【讨论】:

    • 对我来说,Delphi 7 中一种完全合理的方法。
    • 为什么不直接使用TObjectList?
    • @A.Bouchez,因为这不是 OP 提出的问题。这个问题专门关于 TList。不过,我在代码中添加了对 TObjectList 的建议作为注释。
    【解决方案4】:

    您可能想尝试的另一件事是使用 TObjectList。 TObjectList 能够存储对象列表,因此您可以创建 TObject 的新类后代,然后使用这些对象管理 TObjectList。

    类似的东西(未经测试):

    type TGroupArray : array of Integer;
    
    type TGroup = class(Tobject)
      GroupArray : TGroupArray;
    end;
    
    GroupList : TobjectList;
    
    procedure TForm1.FormCreate(Sender: TObject);                                        
    var CurGroup : TGroup;
    begin  
    
    GroupList:= TObjectList.Create; 
    CurGroup := TGroup.Create;
    SetLength(CurGroup.GroupArray,1);
    CurGroup.GroupArray[0] := 10;
    
    GroupList.Add(CurGroup);
    
    RetreiveGroup := GroupList.Items[0];
    FreeandNil(GroupList);
    end;
    

    等等……

    【讨论】:

    • 我会这样做。而当您需要添加诸如“Name:String”之类的属性时,您只需将其添加到 TGroup 这是一个类。
    【解决方案5】:

    当你编码时:

     for grp:= 1 to 4  DO                                     // Put a dummy item in TList
      begin
       SetLength(CurGroup, 1);                                // Create new group
       Groups.Add(@CurGroup);                                 // Store it
      end;
    

    事实上,SetLength(CurGroup) 不会创建新组。 它将调整唯一一个现有 CurGroup 的大小。

    所以@CurGroup 不会改变:它总是堆栈上的某个地址,CurGroup 就是在其中添加的。您多次将相同的地址添加到列表中。

    所以你必须创建一个 TGroup 实例的动态数组,像这样:

    var GroupArray: array of TGroup;
    
    SetLength(GroupArray,4);
    for grp := 0 to high(GroupArray) do
    begin
      SetLength(GroupArray[grp],1);
      Groups.Add(@GroupArray[grp]);
    end;
    

    当然,GroupArray 必须在 Groups 需要它的所有时间内保持分配状态。所以你可能不得不把这个 GroupArray 作为一个属性放在类中,因为如果你在堆栈上创建这个 GroupArray,当方法退出并释放它的堆栈时,所有 GroupArray[] 项都将被释放。

    当然,GroupArray[] 将更直接地访问 TGroup 项... Groups[i] 将等于 GroupArray[]... 没有引用计数问题... 因为例如例如,如果您从 Groups[] 中的指针调整 TGroup 项的大小,我不确定您不会有任何内存泄漏...

    【讨论】:

    • 你好布切兹。 “你必须创建一个 TGroup 实例的动态数组” - - - 实际上这是我的初始代码。它完美地工作。我只是想“升级”到 TList,它比数组数组更简洁,因为它具有 Add() 方法。
    • "SetLength(CurGroup) 不会创建新组" - - - 你是对的。我尖叫着从一个更大的程序中提取代码以使其更易于阅读。在该代码中,列表的初始化实际上是正确的,因为我没有使用循环来初始化它(我没有多次添加相同的元素)。 - 但再次感谢!
    【解决方案6】:

    所以,基本上每个人 建议创建一个数组数组而不是使用 TList。好吧,我已经这样做了。我只是想从“数组数组”“升级”到 TList,因为它有 Add()。看起来我会回到我的原始代码。 谢谢大家,并为每个答案 +1。

    【讨论】:

    • 好吧,Allen 建议使用新的 Delphi,它可以让这种代码更简洁。
    • 是的。我知道。但我没有。所以,这个解决方案并不在手边(对我来说)。
    • 我建议使用 TObjectList
    • ...我刚刚在任何动态数组周围创建了一个包装器以添加 Add() 方法等... :)
    猜你喜欢
    • 1970-01-01
    • 2017-06-02
    • 2020-08-08
    • 2012-02-02
    • 2017-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-27
    相关资源
    最近更新 更多