Delphi:如何从类中引用数组

use*_*191 2 arrays delphi class

我使用数组并在类的上下文中测试了功能,例如:

Ttest   = class
 values : array of integer;
 procedure doStuff;
end;
Run Code Online (Sandbox Code Playgroud)

doStuff所有方法一样,这些方法都对值数组进行操作,而无需将数组作为参数传递。这适合我,而且速度很快。现在我想使用这个类来处理外部数组,比如Ttest.create(myValues)在构造函数中我可以复制myValues到内部,values但这会很浪费,而且,最后必须反转复制以将更新的值传回. 我的问题是如何扩展这个类,以便它可以有效地处理外部数组。在这样的伪代码中:

constructor create(var myValues : array of integer);
begin
  address of values := address of myValues;
  doSTuff;
end;
Run Code Online (Sandbox Code Playgroud)

And*_*and 9

第 1 课

在 Delphi 中,动态数组是引用类型。动态数组类型的变量只包含一个指向实际动态数组堆对象的指针,并且在赋值中,

A := B
Run Code Online (Sandbox Code Playgroud)

其中AB是相同类型的动态数组,动态数组堆对象不会被复制。唯一发生的事情是AB将指向同一个动态数组堆对象(即,将 32 位或 64 位B指针复制到A)并且堆对象的引用计数增加 1。

第 2 课

当你写

constructor Create(var AValues: array of Integer);
Run Code Online (Sandbox Code Playgroud)

您需要意识到,尽管外观如此,这并不是一个动态数组参数,而是一个开放的数组参数

如果您明确需要动态数组参数,则需要明确使用这样的类型:

constructor Create(AValues: TArray<Integer>);
Run Code Online (Sandbox Code Playgroud)

根据定义,TArray<Integer> = array of IntegerIntegers的动态数组。

请注意,该语言只有两种类型的数组——静态和动态;开放数组概念仅与函数参数有关。

如果您想使用动态数组,利用它们作为引用类型的性质,我建议您使用动态数组参数。然后唯一传递给函数(在这种情况下是构造函数)的是指向堆对象的指针。当然,堆对象的引用计数也会增加。

第 3 课 – 示例

A := B
Run Code Online (Sandbox Code Playgroud)

After SetLength,Arr指向引用计数为 1 的动态数组堆对象。您可以在 RAM 中看到它(按Ctrl+ Alt+ E,然后按Ctrl+G和 goto Arr[0])。当你进入Test,引用计数增加到2,因为这两个ArrA参考。当你离开时Test,引用计数减少回 1 因为A超出范围:现在再次只Arr引用它。

第 4 课

现在,一个“问题”:如果您更改动态数组的元素数量,则需要重新分配 (*)。因此,创建一个新的动态数组堆对象,引用计数为 1,旧对象的引用计数减 1(如果它变为零,则将其删除)。

因此,虽然前面的示例按预期工作,但以下示例不会:

constructor Create(var AValues: array of Integer);
Run Code Online (Sandbox Code Playgroud)

SetLength会创建引用计数1中的新动态数组堆对象和复制旧阵列的一半到这一点,把新的地址在当地的A参数,Test将改变这一新的数组,不触及旧其中一个全局Arr变量指向。原始堆对象的引用计数减一。

但是,如果您使用var参数,

constructor Create(AValues: TArray<Integer>);
Run Code Online (Sandbox Code Playgroud)

它会像以前一样工作。这有效地与Arr. 如果我们增加了元素的数量,数组可能会被重新分配,并且全局Arr变量会被更新为新地址。

结论

只要你不需要重新分配内存(改变元素的数量),Delphi 已经给了你你想要的,因为动态数组是引用类型。如果您确实需要重新分配,至少现在您已了解足够的技术细节以进行推理。

更新:因此,建议是这样做

var
  Arr: TArray<Integer>;

procedure Test(A: TArray<Integer>);
var
  i: Integer;
begin
  for i := Low(A) to High(A) do
    A[i] := 2*A[i];
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
begin

  SetLength(Arr, 10);
  for i := 0 to High(Arr) do
    Arr[i] := i;

  Test(Arr);

  for i := 0 to High(Arr) do
    ShowMessage(Arr[i].ToString);

end;
Run Code Online (Sandbox Code Playgroud)

要测试它:

var
  Arr: TArray<Integer>;

procedure Test(A: TArray<Integer>);
var
  i: Integer;
begin
  SetLength(A, 5);
  for i := Low(A) to High(A) do
    A[i] := 2*A[i];
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
begin

  SetLength(Arr, 10);
  for i := 0 to High(Arr) do
    Arr[i] := i;

  Test(Arr);

  for i := 0 to High(Arr) do
    ShowMessage(Arr[i].ToString);

end;
Run Code Online (Sandbox Code Playgroud)

脚注

  • 如果您 (1) 减少元素数量并且 (2) 引用计数为 1,则通常数据不会在内存中移动。如果引用计数 > 1,数据总是被移动,因为SetLength保证它的参数的引用计数1在它返回时。

  • 抱歉,我不小心写了一本小书。:/ (3认同)