ProgrammierungMiddle/Senior Go Backend-Entwickler

Wie funktionieren for-range Schleifen über Slices und Maps in Go und welche Besonderheiten beim Kopieren von Werten treten beim Einsatz auf?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort.

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:

  • In jeder Iteration der Schleife werden neue Kopien der Variablen key/value erstellt.
  • Das Nehmen der Adresse von value innerhalb von for-range gibt nicht die Adresse des Elements in der Sammlung zurück, sondern die Adresse einer temporären Variable.
  • Bei Maps erfolgt die Iteration in willkürlicher Reihenfolge, es gibt keine Garantie für eine Sequenz.

Fragen mit Tücken.

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.

Typische Fehler und Anti-Pattern

  • Verwendung von &value in range, um Verweise auf verschiedene Elemente zu speichern
  • Änderung der Variablen value anstelle des Zugriffs über den Index
  • Erwartung einer sequenziellen Reihenfolge bei der Durchquerung von Maps

Lebensbeispiel

Negativer Fall

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:

  • Knappheit der Schleife

Nachteile:

  • Alle Verweise sind identisch, bei Änderung eines Elements ändern sich „alle“

Positiver Fall

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:

  • Jeder Zeiger verweist auf ein einzelnes Element des ursprünglichen Slices

Nachteile:

  • Die Syntax ist länger, aber das Ergebnis ist vorhersehbar