Delphi动态数组迭代和记录复制

时间:2013-09-02 14:41:00

标签: delphi

使用for ... in ... do迭代动态数组是否会在数组中创建项目的副本?例如:

type
  TSomeRecord =record
    SomeField1 :string;
    SomeField2 :string;
  end;

var
  list: array of TSomeRecord;
  item: TSomeRecord;

begin
  // Fill array here
  for item in list do
    begin
      // Is item here a copy of the item in the array or a reference to it?
    end;
end;

循环中的项目是数组中项目的副本还是对它的引用?

如果它是副本,是否可以在没有创建副本的情况下迭代数组?

谢谢,

AJ

2 个答案:

答案 0 :(得分:11)

for / in循环的循环变量是循环迭代的容器所持有的值的副本。

由于无法替换动态数组的默认枚举器,因此无法创建返回引用而非副本的枚举数。如果要将数组包装在记录中,可以为记录创建一个返回引用的枚举器。

如果您希望避免复制,显然可以使用传统的索引循环。


有人可能会问,因为似乎没有上述语句的文档,为什么编译器不选择使用引用而不是副本来实现这样的for / in循环。只有设计师才能肯定回答,但我可以提供理由。

考虑一个自定义枚举器。 documentation描述了如下机制:

  

要在类或接口上使用 for-in 循环结构,   类或接口必须实现规定的集合模式。一个   实现集合模式的类型必须具有以下内容   属性:

     
      
  • 类或接口必须包含名为GetEnumerator()的公共实例方法。 GetEnumerator()方法必须返回一个类,   接口或记录类型。
  •   
  • GetEnumerator()返回的类,接口或记录必须包含名为MoveNext()的公共实例方法。 MoveNext()   方法必须返回Boolean。 for-in循环调用此方法   首先要确保容器不是空的。
  •   
  • GetEnumerator()返回的类,接口或记录必须包含一个名为Current的公共实例,只读属性。该   Current属性的类型必须是包含在中的类型   集合。
  •   

自定义枚举器通过Current属性返回集合中的每个值。这意味着价值被复制了。

因此,这意味着自定义枚举器始终使用副本。想象一下,如果数组的内置枚举器可以使用引用。这将导致两种类型的枚举器之间存在显着的语义差异。设计师选择不同类型的普查员语义之间的一致性肯定是合理的

答案 1 :(得分:5)

@David回答说,枚举器是记录的副本。

他还说,在记录中包装动态数组将允许自定义枚举器。

这是一个这样做的例子:

program ProjectCustomEnumerator;

{$APPTYPE CONSOLE}

type
  PSomeRecord = ^TSomeRecord;
  TSomeRecord = record
    SomeField1 :string;
    SomeField2 :string;
  end;

  TSomeRecordArray = record
  private type
    TSomeRecordDynArray = array of TSomeRecord;
    // For x in .. enumerator
    TSomeRecordArrayEnumerator = record
        procedure Create( const AnArray : TSomeRecordDynArray);
      private
        FCurrent,FLast : Integer;
        FArray : TSomeRecordDynArray;
        function GetCurrent : PSomeRecord; inline;
      public
        function MoveNext : Boolean; inline;
        property Current : PSomeRecord read GetCurrent;
    end;
  public
    List : TSomeRecordDynArray;
    // Enumerator interface
    function GetEnumerator : TSomeRecordArrayEnumerator; inline;
  end;

procedure TSomeRecordArray.TSomeRecordArrayEnumerator.Create(
  const AnArray: TSomeRecordDynArray);
begin
  FCurrent := -1;
  FLast := Length(AnArray)-1;
  FArray := AnArray;
end;

function TSomeRecordArray.TSomeRecordArrayEnumerator.GetCurrent: PSomeRecord;
begin
  Result := @FArray[FCurrent];
end;

function TSomeRecordArray.TSomeRecordArrayEnumerator.MoveNext: Boolean;
begin
  Inc(FCurrent);
  Result := (FCurrent <= FLast);
end;

function TSomeRecordArray.GetEnumerator: TSomeRecordArrayEnumerator;
begin
  Result.Create(Self.List);
end;

var
  aList : TSomeRecordArray;
  item : PSomeRecord;
  i    : Integer;
begin
  // Fill array here
  SetLength(aList.List,2);
  aList.List[0].SomeField1 := 'Ix=0; Field1';
  aList.List[0].SomeField2 := 'Ix=0; Field2';
  aList.List[1].SomeField1 := 'Ix=1; Field1';
  aList.List[1].SomeField2 := 'Ix=1; Field2';
  i := -1;
  for item in aList do
  begin
    // Item here a pointer to the item in the array
    Inc(i);
    WriteLn('aList index:',i,' Field1:',item^.SomeField1,' Field2:',item^.SomeField2);
  end;
  ReadLn;
end.

修改

只需完成并跟进注释,这里是任何动态记录数组的通用容器示例,带有自定义枚举器。

program ProjectCustomEnumerator;

{$APPTYPE CONSOLE}

type
  PSomeRecord = ^TSomeRecord;
  TSomeRecord = record
    SomeField1 :string;
    SomeField2 :string;
  end;

  TRecordArray<T> = record
  private type
    TRecordDynArray = array of T;
    // For x in .. enumerator
    TRecordArrayEnumerator = record
        procedure Initialize( const AnArray : TRecordDynArray);
      private
        FCurrent,FLast : Integer;
        FArray : TRecordDynArray;
        function GetCurrent : Pointer; inline;
      public
        function MoveNext : Boolean; inline;
        property Current : Pointer read GetCurrent;
    end;
  public
    List : TRecordDynArray;
    // Enumerator interface
    function GetEnumerator : TRecordArrayEnumerator; inline;
  end;

procedure TRecordArray<T>.TRecordArrayEnumerator.Initialize(
  const AnArray: TRecordDynArray);
begin
  FCurrent := -1;
  FLast := Length(AnArray)-1;
  FArray := AnArray;
end;

function TRecordArray<T>.TRecordArrayEnumerator.GetCurrent: Pointer;
begin
  Result := @FArray[FCurrent];
end;

function TRecordArray<T>.TRecordArrayEnumerator.MoveNext: Boolean;
begin
  Inc(FCurrent);
  Result := (FCurrent <= FLast);
end;

function TRecordArray<T>.GetEnumerator: TRecordArrayEnumerator;
begin
  Result.Initialize(Self.List);
end;

var
  aList : TRecordArray<TSomeRecord>;
  item : PSomeRecord;
  i    : Integer;
begin
  // Fill array here
  SetLength(aList.List,2);
  aList.List[0].SomeField1 := 'Ix=0; Field1';
  aList.List[0].SomeField2 := 'Ix=0; Field2';
  aList.List[1].SomeField1 := 'Ix=1; Field1';
  aList.List[1].SomeField2 := 'Ix=1; Field2';
  i := -1;
  for item in aList do
  begin
    // Item here a pointer to the item in the array
    Inc(i);
    WriteLn('aList index:',i,' Field1:',item^.SomeField1,' Field2:',item^.SomeField2);
  end;
  ReadLn;
end.