12

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;

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

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

谢谢,

阿杰

4

2 回答 2

12

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

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

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


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

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

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

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

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

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

于 2013-09-02T15:12:29.543 回答
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.
于 2013-09-02T21:02:42.577 回答