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

AJ.*_*AJ. 12 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;
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢,

AJ

Dav*_*nan 11

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

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

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


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

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

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

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

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

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

  • @AJ.当然可以.`list [i]`**是原始记录,在这种情况下! (3认同)
  • 简单地说,语言定义规定循环变量不能被修改。 (2认同)

LU *_* RD 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.
Run Code Online (Sandbox Code Playgroud)

编辑

为了完整和跟进注释,这里是任何动态记录数组的通用容器示例,带有自定义枚举器.

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.
Run Code Online (Sandbox Code Playgroud)