2013-09-02 16 views
12

Czy w iteracji na dynamicznej tablicy przy użyciu for ... in ... do utworzono kopię elementu w tablicy? Na przykład:Delphi dynamiczna iteracja i kopiowanie rekordów

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; 

Czy pozycja w pętli będzie kopią elementu w tablicy lub odniesieniem do niego?

Jeśli jest to kopia, możliwe jest iterowanie po tablicy bez tworzenia kopii?

dzięki,

AJ

+0

W każdym razie zaimplementowałem wskaźniki rekordów dla podobnych scenariuszy. Za każdym razem, gdy chcesz przekazać rekordy i zmodyfikować ich zawartość w różnych miejscach, rekordowy wskaźnik 'PSomeRecord' działa o wiele wydajniej i nie musisz się martwić, że zostanie skopiowany. Zwłaszcza jeśli zawiera dużo treści (dużo pamięci). –

+1

@Jerry Ale teraz musisz zarządzać życiem jawnie –

+0

@David True, to duża zmiana w sposobie, w jaki wszystko działa, ale tak jak powiedziałem, jeśli pojedynczy rekord pochłania dużą ilość pamięci, najlepiej zachować je w wskaźnikach. –

Odpowiedz

11

zmienna pętli for/w pętli kopię wartości utrzymywanej w pojemniku, na której koło się iteracyjnie.

Ponieważ nie można zastąpić domyślnego modułu wyliczającego dla tablic dynamicznych, nie ma możliwości utworzenia modułu wyliczającego, który zwraca odwołania zamiast kopii. Jeśli chcesz zawrzeć tablicę wewnątrz rekordu, możesz utworzyć moduł wyliczający dla rekordu, który będzie zwracał odwołania.

Oczywiście możesz użyć tradycyjnej indeksowanej pętli for, jeśli chcesz uniknąć wykonywania kopii.


Ktoś mógłby zapytać, ponieważ wydaje się, że brak dokumentacji powyższego zestawienia, dlaczego kompilator nie zdecydować się na wprowadzenie takich do/z pętli przy użyciu odwołań zamiast kopii. Tylko projektanci mogą na to odpowiedzieć, ale mogę przedstawić uzasadnienie.

Należy rozważyć niestandardowy moduł wyliczający. documentation opisuje mechanizm następująco:

Aby użyć do pętli w skonstruować na klasę lub interfejsu, klasa lub interfejs musi wdrażać ustaloną wzór zbierania. typ, który implementuje wzorzec zbiórki musi mieć następujące atrybuty:

  • Klasa lub interfejs musi zawierać publiczną metodę instancji o nazwie GetEnumerator(). Metoda GetEnumerator() musi zwracać interfejs klasy, lub typ rekordu.
  • Klasa, interfejs lub rekord zwrócony przez GetEnumerator() musi zawierać publiczną instancję o nazwie MoveNext(). Metoda MoveNext() musi zwrócić wartość Boolean. Pętla for-in wywołuje tę metodę najpierw, aby upewnić się, że pojemnik nie jest pusty.
  • Klasa, interfejs lub rekord zwrócony przez GetEnumerator() musi zawierać instancję publiczną, właściwość tylko do odczytu o nazwie Current. Typ właściwości Current musi być typem z kolekcji .

Zwyczaj wyliczający zwraca każdą wartość z kolekcji poprzez właściwość Current. A to oznacza, że ​​wartość jest kopiowana.

Oznacza to, że niestandardowe moduły wyliczające zawsze używają kopii.Wyobraź sobie, że wbudowany moduł wyliczający tablice może używać referencji. Oznaczałoby to znaczną różnicę semantyczną między dwoma typami enumeratorów. Na pewno jest prawdopodobne, że projektanci zdecydowali się na spójność między semantyką typów różnicowych: enumeratorów:

+0

Jeśli kopia jest wykonana, dlaczego nie można zmodyfikować kopii? Na przykład item.SomeField1: = 'Test' daje błąd kompilacji. –

+1

Po prostu definicja języka określa, że ​​zmienne pętli nie mogą być modyfikowane. –

+1

Prawdopodobnie dlatego, że jest to kopia, a zmiany do skopiowania nie zostaną zapisane w tablicy. – VitaliyG

5

@ David odpowiedział, że moduł wyliczający jest kopią rekordu.

Powiedział także, że zawijanie tablicy dynamicznej wewnątrz rekordu umożliwiłoby użycie modułu wyliczającego.

Oto przykład robi że:

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. 

Edit

Wystarczy być kompletny i po komentarzach, tutaj jest ogólnym przykładem pojemnik dla każdej tablicy dynamicznej rekordu, z niestandardowy moduł wyliczający.

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. 
+0

+1 Niektóre komentarze. Cała płyta może być ogólna, a więc użyteczna dla każdego typu. Mówię też, że użycie metody Utwórz dla instancji jest mylące. Wygląda za bardzo jak konstruktor. Chciałbym mieć albo funkcję klasy o nazwie New, która zwróciła nowy moduł wyliczający, albo metodę instancji o nazwie Initialize. –

+0

@DavidHeffernan, co czyni go ogólnym, moduł wyliczający zwróci typ 'PT =^T;', ale element pętli zostanie zadeklarowany jako 'PSomeRecord'. Jak uniknąć błędu "niekompatybilnych typów"? –

+0

Zgłaszanie typu "GetCurrent" jako 'Pointer' rozwiązuje problem" niekompatybilnych typów ". Nie jestem pewien, czy to najlepsze rozwiązanie. –