使用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
答案 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.