5

レコードの一般的なリストがあります。これらのレコードには、次のような動的配列が含まれています

Type
  TMyRec=record
MyArr:Array of Integer;
    Name: string;
    Completed: Boolean;
  end;

var
  MyList:TList<TMyRec>;
  MyRec:TMyRec;

次に、リストを作成し、次のように配列の長さを設定します

MyList:=TList<TMyRec>.Create;
SetLength(MyRec.MyArr,5);
MyRec.MyArr[0]:=8;  // just for demonstration
MyRec.Name:='Record 1';
MyRec.Completed:=true;
MyList.Add(MyRec);

次に、データを変更し、リストに別の項目をMyArr変更MyRec.Nameして追加します

MyRec.MyArr[0]:=5;  // just for demonstration
MyRec.Name:='Record 2';
MyRec.Completed:=false;
MyList.Add(MyRec);

MyRec.MyArr最初のアイテムをリストに追加した後に変更すると、リストにMyArr保存される変更も変更されます。ただし、他のレコード フィールドにはありません。

私の質問はMyRec.MyArr、リスト項目に既に格納されている配列に変更が反映されないようにする方法です。

複数のレコードを宣言する必要がありますか。

4

3 に答える 3

4

この例は、ジェネリックへのすべての参照を削除して、次のように単純化できます。

{$APPTYPE CONSOLE}

var
  x, y: array of Integer;

begin
  SetLength(x, 1);
  x[0] := 42;
  y := x;
  Writeln(x[0]);
  y[0] := 666;
  Writeln(x[0]);
end.

出力は次のとおりです。

42
666

これは、動的配列が参照型であるためです。動的配列型の変数に代入すると、別の参照が取得され、コピーは作成されません。

これを解決するには、参照を一意にする (つまり、単純な参照のみにする) ことを強制します。これを達成する方法はいくつかあります。たとえば、SetLength一意にしたい配列を呼び出すことができます。

{$APPTYPE CONSOLE}

var
  x, y: array of Integer;

begin
  SetLength(x, 1);
  x[0] := 42;
  y := x;
  SetLength(y, Length(y));
  Writeln(x[0]);
  y[0] := 666;
  Writeln(x[0]);
end.

出力:

42
42

したがって、コードでは次のように記述できます。

MyList:=TList<TMyRec>.Create;

SetLength(MyRec.MyArr,5);
MyRec.MyArr[0]:=8;  // just for demonstration
MyRec.Name:='Record 1';
MyRec.Completed:=true;
MyList.Add(MyRec);

SetLength(MyRec.MyArr,5); // <-- make the array unique
MyRec.MyArr[0]:=5;  // just for demonstration
MyRec.Name:='Record 2';
MyRec.Completed:=false;
MyList.Add(MyRec);

Finalize、割り当てnilCopyなどを含む、一意性を強制するための他のさまざまな方法を使用できます。

この問題については、ドキュメントで詳細に説明されています。関連する抜粋を次に示します。

X と Y が同じ動的配列型の変数である場合、X := Y は X を Y と同じ配列にポイントします (この操作を実行する前に X にメモリを割り当てる必要はありません)。文字列や静的配列とは異なり、 -on-write は動的配列には使用されないため、書き込まれる前に自動的にコピーされません。たとえば、次のコードが実行された後:

 var
   A, B: array of Integer;
   begin
     SetLength(A, 1);
     A[0] := 1;
     B := A;
     B[0] := 2;
   end;

A[0] の値は 2 です (A と B が静的配列の場合、A[0] は 1 のままです)。動的配列インデックス (たとえば、MyFlexibleArray[2] := 7) への代入は、配列を再割り当てします。範囲外のインデックスはコンパイル時に報告されません。対照的に、動的配列の独立したコピーを作成するには、グローバル Copy 関数を使用する必要があります。

 var
   A, B: array of Integer;
 begin
   SetLength(A, 1);
   A[0] := 1;
   B := Copy(A);
   B[0] := 2; { B[0] <> A[0] }
 end;
于 2014-01-31T15:30:17.543 に答える