2011-04-22 51 views
6

这是一个很常见的模式在我的代码:追加一个元素的动态数组

SetLength(SomeDynamicArray, Length(SomeDynamicArray)+1); 
SomeDynamicArray[High(SomeDynamicArray)] := NewElement; 

难道就没有办法在同一行做到这一点?

编辑:这是令人难以置信的低效率。我知道。我使用动态数组(在我自己的代码中,仅在我使用的个人项目中),因为它们是最容易使用的,我只需要尽可能少地使用代码完成任务。

+0

我想的东西SomeDynamicArray.Add(为newElement),也许增加一个QC? – 2011-04-22 13:56:21

+0

我知道使用动态数组的性能影响。现在,有人请回答我的问题吗? – 2011-04-22 14:03:12

+0

如果您正确使用动态数组,则没有使用动态数组的性能影响。恰恰相反,他们根本没有任何开销!但是如果上述模式在你的代码中“非常频繁”,我个人会对你的代码持怀疑态度。 – 2011-04-22 14:05:13

回答

15

这里是一个黑客泛型这仅适用于TArray<T>

type 
    TAppender<T> = class 
    class procedure Append(var Arr: TArray<T>; Value: T); 
    end; 

class procedure TAppender<T>.Append; 
begin 
    SetLength(Arr, Length(Arr)+1); 
    Arr[High(Arr)] := Value; 
end; 

用法:

var 
    TestArray: TArray<Integer>; 

begin 
    TAppender<Integer>.Append(TestArray, 5); 
end. 
+0

+1非常好,的确! – 2011-04-22 14:44:14

+0

他们是否修复了意味着高(arr)不会编译为TArray的错误? – 2011-04-22 14:57:33

+0

不知道该错误(在此之前从未使用TArray),但此代码在Delphi XE上编译得很好。 – 2011-04-22 15:17:58

4

这是导致内存碎片的反模式。请改用Generics.Collections.TList<T>并调用Add方法添加新项目。

没有一个班轮可以扩展数组并添加一个项目。如果您愿意,可以使用泛型创建自己的动态数组封装器。本质上这就是Generics.Collections.TList<T>

+0

+1是的,(D2009)TList类的增长功能远远好于单个项目增长。但是,根据列表的多大程度,您可能需要考虑创建后代并重写Grow以适应特定的场景。就目前来看,一旦容量超过64个项目,TList将永远不会增长超过25%。每当容量达到更高性能和“非易碎性”时,我们发现该清单翻倍。 – 2011-04-22 14:09:09

9

每当您拨打SetLength时,内存都会被重新分配。也许整个阵列需要复制到不同的位置。而你只是想将一个元素添加到数组中!

基本上:永远不要这样做。有两种方法。最简单的例子是,如果你事先知道数组的最大尺寸:

procedure Example1; 
var 
    data: array of string; 
    ActualLength: integer; 

    procedure AddElement(const Str: string); 
    begin 
    data[ActualLength] := Str; 
    inc(ActualLength); 
    end; 

begin 

    ActualLength := 0; 
    SetLength(data, KNOWN_UPPER_BOUND); 

    for ... 
    while ... 
     repeat ... 
     AddElement(SomeString); 

    SetLength(data, ActualLength); 

end; 

Here是这种方法的一个实际的例子。

如果你不知道任何上限先验,然后分配大块:

procedure Example2; 
const 
    ALLOC_BY = 1024; 
var 
    data: array of string; 
    ActualLength: integer; 

    procedure AddElement(const Str: string); 
    begin 
    if ActualLength = length(data) then 
     SetLength(data, length(data) + ALLOC_BY); 

    data[ActualLength] := Str; 
    inc(ActualLength); 
    end; 

begin 

    ActualLength := 0; 
    SetLength(data, ALLOC_BY); 

    for ... 
    while ... 
     repeat ... 
     AddElement(SomeString); 

    SetLength(data, ActualLength); 

end; 

Here是这种方法的一个实际的例子。

+0

+1提供更好的“成长”,而无需仿制药(并因此D2009 +) – 2011-04-22 14:10:37

+0

@an(或*将*)匿名downvoter:怎么是这样的*不*有用? @Andreas:谢谢! – 2011-04-22 14:58:49

+1

我发现这个职位不是有益的(downvoted它),因为:1)它不会回答这个规定2题),它告诉我的东西我已经知道3)建议的解决方案进去截然相反的是我需要的方向(代码简洁和简洁)4)它命令读者总是[不]做某事,这不适用于所有情况(如我的)。 – 2011-04-22 15:38:34

2

如果你有德尔福2009年或以后,你真的想缩短上面这段代码,你可以尝试像

type 
    DataArray<T> = record 
    Data: array of T; 
    procedure Append(const Value: T); 
    function Count: integer; 
    end; 


{ DataArray<T> } 

procedure DataArray<T>.Append(const Value: T); 
begin 
    SetLength(Data, length(Data) + 1); 
    Data[high(Data)] := Value; 
end; 

function DataArray<T>.Count: integer; 
begin 
    result := length(Data); 
end; 

然后,你可以做

procedure TForm1.FormCreate(Sender: TObject); 
var 
    data: DataArray<string>; 
begin 
    data.Append('Alpha'); 
    data.Append('Beta'); 
    Caption := IntToStr(data.Count) + ': ' data.Data[0] + ' & ' + data.Data[1]; 
end; 
+0

是否有可能改为声明一个对动态数组类型进行操作的模板化函数? (所以你不必为此只使用自定义类型。) – 2011-04-22 14:26:56

+0

@Cyber​​Shadow:我想不出有什么办法,我很害怕。 – 2011-04-22 14:28:02

+0

类似功能似乎是可能的(请参阅下面的答案)。 – 2011-04-22 14:43:02

2
MyList.Add(myobject); 

IMO动态数组应该只用于在编译时你不知道数组的确切大小,但在运行时,你就知道了。



如果你需要不断地操作数组的大小,你不应该使用一个数组,而是一个TList或它的一个后代,正如其他人所说的:TObjectList,TInterfaceList,TStringList.Objects []都可以被使用(并且被滥用),并且还有一些新的,以及用于原始类型的TList。在泛型被引入到Delphi之前,TList曾经是一种痛苦 - 你必须使用指针 - 但是使用泛型:TList <T>这很容易。

此外,使用您创建的任何列表的容量特性 - 它会预先分配的内存一定量让你的代码不会导致大量的内存让每一个你的清单上执行操作时左右颠倒。 (如果你超越你分配的容量,内存管理器会给你runtime-你wan't失败更多的内存 - 见Delphi帮助)