Hintergrund der Frage:
Die for-range Konstruktion wurde in Go als Möglichkeit zur Iteration über Sammlungen (Slices, Arrays, Maps, Strings) eingeführt. Die Go-Entwickler implementierten eine Optimierung: In jeder Iteration der Schleife wird der Wert kopiert, anstatt ihn direkt über einen Verweis zu verwenden, was zu unvorhersehbaren Fehlern führen kann, insbesondere bei Schleifenvariablen.
Problem:
Viele machen einen Fehler, wenn sie versuchen, die Adresse einer Variable innerhalb von range zu nehmen (zum Beispiel &v), und glauben, dass sie die Adresse eines Elements der Sammlung erhalten, tatsächlich jedoch die Adresse einer lokalen Variable.
Lösung:
In der for-range Schleife werden in jeder Iteration neue Kopien der Iteratorvariablen (key, value) erstellt. Bei einfachen Typen ist das unproblematisch, aber bei Strukturen kann es zu unerwarteten Ergebnissen führen, wenn man einen Zeiger auf ein Element speichert – er verweist immer auf dieselbe Variable und nicht auf verschiedene Elemente des Slices.
Beispielcode:
people := []Person{{Name: "Ivan"}, {Name: "Oleg"}} ptrs := make([]*Person, 0) for _, p := range people { ptrs = append(ptrs, &p) // alle ptrs verweisen auf dasselbe p }
Schlüsselmerkmale:
Was passiert, wenn man Adressen der Variable value innerhalb von range speichert?
Alle Adressen weisen auf denselben Speicher hin, da value eine temporäre Variable ist.
for _, v := range someSlice { ptrs = append(ptrs, &v) } // Alle ptrs enthalten einen Verweis auf dieselbe Variable!
Kann man ein Element der Sammlung über einen Verweis auf value in range ändern?
Nein, eine Änderung von value betrifft nicht das ursprüngliche Element der Sammlung. Um Änderungen vorzunehmen, muss über den Index zugegriffen werden.
for _, v := range arr { v.Field = 10 // arr ändert sich nicht } for i := range arr { arr[i].Field = 10 // korrekt }
Garantiert for-range über Maps eine sequenzielle Reihenfolge der Durchquerung?
Nein, die Reihenfolge der Iteration über Maps in Go ist nicht festgelegt und kann bei jedem Start der Anwendung unterschiedlich sein.
Ein Entwickler versucht, eine Liste von Verweisen auf Elemente einer Struktur über range zu serialisieren und speichert &value in einem separaten Slice. Es ergibt sich ein Slice mit identischen Adressen.
Vorteile:
Nachteile:
Iterieren über den Index und einen Zeiger auf das gewünschte Element des Arrays speichern:
for i := range arr { ptrs = append(ptrs, &arr[i]) }
Vorteile:
Nachteile: