Go Structs verstehen: Definition, Verwendung, anonyme Felder und Verschachtelung
Grace Collins
Solutions Engineer · Leapcell

Das Typsystem von Go ist auf Einfachheit und Effizienz ausgelegt. Im Kern für die Datenaggregation liegt die struct
. Eine struct
ist ein zusammengesetzter Datentyp, der null oder mehr benannte Felder unterschiedlicher Typen zu einer einzigen, logischen Einheit zusammenfasst. Sie ist der primäre Weg, benutzerdefinierte Datentypen in Go zu definieren, vergleichbar mit Klassen in objektorientierten Sprachen (obwohl Go nicht rein OOP ist), jedoch ohne die Vererbungshierarchie. Dieser Artikel befasst sich mit der Definition, der praktischen Verwendung, dem einzigartigen Konzept anonymer Felder und der Leistungsfähigkeit von Struct-Verschachtelungen in Go.
Definition und Initialisierung von Structs
Eine Struct wird mit dem Schlüsselwort type
, gefolgt vom Namen der Struct und dem Schlüsselwort struct
, wobei ihre Felder in geschweiften Klammern eingeschlossen sind. Jedes Feld hat einen Namen und einen Typ.
Beginnen wir mit einem einfachen Beispiel: Definition einer Person
-Struct.
package main import "fmt" // Person definiert eine Struct, um persönliche Informationen zu speichern. type Person struct { Name string Age int IsAdult bool } func main() { // Initialisierung einer Struct auf verschiedene Arten: // 1. Nullwert-Initialisierung: // Alle Felder werden mit ihren jeweiligen Nullwerten initialisiert (z. B. "", 0, false). var p1 Person fmt.Println("p1:", p1) // Ausgabe: p1: { 0 false} // 2. Feldweise Zuweisung: p1.Name = "Alice" p1.Age = 30 p1.IsAdult = true fmt.Println("p1 aktualisiert:", p1) // Ausgabe: p1 aktualisiert: {Alice 30 true} // 3. Verwendung von Struct Literalen (geordnete Felder): // Dies erfordert, dass die Felder in der Reihenfolge deklariert sind, in der sie in der Struct-Definition stehen. p2 := Person{"Bob", 25, false} fmt.Println("p2:", p2) // Ausgabe: p2: {Bob 25 false} // 4. Verwendung von Struct Literalen (benannte Felder): // Dies ist der gebräuchlichste und empfohlene Weg, da er lesbarer ist und robust gegenüber Feldvertauschungen. p3 := Person{ Name: "Charlie", Age: 40, IsAdult: true, } fmt.Println("p3:", p3) // Ausgabe: p3: {Charlie 40 true} // 5. Erstellung eines Zeigers auf eine Struct: p4 := &Person{Name: "Diana", Age: 22, IsAdult: true} fmt.Println("p4 (Zeiger):", p4) // Ausgabe: p4 (Zeiger): &{Diana 22 true} fmt.Println("p4.Name:", p4.Name) // Go dereferenziert Zeiger für Struct-Felder automatisch fmt.Println("(*p4).Name:", (*p4).Name) // Explizite Dereferenzierung funktioniert ebenfalls }
Zugriff und Änderung von Feldern
Der Zugriff auf Felder einer Struct (oder eines Zeigers auf eine Struct) erfolgt über den Punktoperator .
. Änderungen sind einfache Zuweisungen.
package main import "fmt" type Car struct { Make string Model string Year int Color string } func main() { myCar := Car{ Make: "Toyota", Model: "Camry", Year: 2020, Color: "Silver", } fmt.Println("Mein Auto:", myCar) fmt.Println("Marke:", myCar.Make) fmt.Println("Jahr:", myCar.Year) // Ändern eines Feldes myCar.Color = "Blue" fmt.Println("Neue Farbe:", myCar.Color) // Structs sind Werttypen in Go. // Wenn Sie eine Struct einer anderen zuweisen, wird eine Kopie erstellt. anotherCar := myCar // `anotherCar` ist eine separate Kopie von `myCar` anotherCar.Year = 2023 fmt.Println("Mein Auto Jahr:", myCar.Year) // Ausgabe: Mein Auto Jahr: 2020 fmt.Println("Anderes Auto Jahr:", anotherCar.Year) // Ausgabe: Anderes Auto Jahr: 2023 // Wenn Sie die zugrunde liegenden Daten teilen möchten, verwenden Sie Zeiger. carPtr := &myCar carPtr.Year = 2024 // Dies ändert das ursprüngliche `myCar` fmt.Println("Mein Auto Jahr (nach Zeiger-Update):", myCar.Year) // Ausgabe: Mein Auto Jahr (nach Zeiger-Update): 2024 }
Anonyme Felder (eingebettete Felder)
Go bietet eine mächtige Funktion namens "anonyme Felder" oder "eingebettete Felder". Anstatt einem Feld einen Namen zu geben, geben Sie nur seinen Typ an. Dies bettet implizit die Felder des anonymen Typs in die enthaltende Struct ein und befördert sie auf die oberste Ebene. Dieser Mechanismus ist Gos Art, Komposition über Vererbung zu erreichen und bietet eine Form der "Typenbeförderung".
package main import "fmt" type Engine struct { Type string Horsepower int } type Wheels struct { Count int Size int } // Auto (mit anonymen Feldern) type ModernCar struct { Make string Model string Engine // Anonymes Feld vom Typ Engine Wheels // Anonymes Feld vom Typ Wheels Price float64 } func main() { myModernCar := ModernCar{ Make: "Tesla", Model: "Model 3", Engine: Engine{ // Initialisierung des eingebetteten Engine-Feldes Type: "Electric", Horsepower: 450, }, Wheels: Wheels{ // Initialisierung des eingebetteten Wheels-Feldes Count: 4, Size: 19, }, Price: 55000.00, } fmt.Println("Mein modernes Auto:", myModernCar) // Zugriff auf eingebettete Felder direkt: fmt.Println("Motortyp:", myModernCar.Type) // Zugriff auf Engine.Type direkt fmt.Println("Motorleistung:", myModernCar.Horsepower) // Zugriff auf Engine.Horsepower direkt fmt.Println("Radanzahl:", myModernCar.Count) // Zugriff auf Wheels.Count direkt fmt.Println("Radgröße:", myModernCar.Size) // Zugriff auf Wheels.Size direkt // Sie können immer noch über ihren ursprünglichen zusammengesetzten Typ darauf zugreifen, falls nötig: fmt.Println("Vollständiger Motor:", myModernCar.Engine) fmt.Println("Vollständige Räder:", myModernCar.Wheels) // Bei Feldnamenkollisionen zwischen eingebetteten Typen oder zwischen einem eingebetteten Typ und der enthaltenden Struct hat das Feld, das direkt in der einbettenden Struct deklariert ist, Vorrang. // Bei Kollisionen zwischen zwei eingebetteten Typen müssen Sie das Feld qualifizieren. type Dimensions struct { Width float64 Height float64 } type SpecificCar struct { Make string Dimensions // Eingebettete Struct Weight float64 } type Garage struct { Name string Dimensions // Eingebettete Struct, Kollision mit SpecificCar.Dimensions Location string } // Beispiel 1: Zugriff auf eingebettetes Feld sc := SpecificCar{ Make: "Limousine", Dimensions: Dimensions{ Width: 1.8, Height: 1.5, }, Weight: 1500, } fmt.Println("SpecificCar Breite:", sc.Width) // Greift auf Dimensions.Width zu // Beispiel 2: Namenskollision (in diesem einfachen Beispiel nicht direkt gezeigt, aber Go's Regeln gelten) // Wenn Sie `type Car struct { Color string; Vehicle }` und `Vehicle` auch `Color string` hätten, // dann würde `Car.Color` auf die in `Car` deklarierte `Color` verweisen, nicht auf `Vehicle.Color`. // Um auf `Vehicle.Color` zuzugreifen, würden Sie `Car.Vehicle.Color` verwenden. // Im `ModernCar`-Beispiel, wenn sowohl `Engine` als auch `Wheels` ein Feld namens `ID` hätten, // müssten Sie sie als `myModernCar.Engine.ID` und `myModernCar.Wheels.ID` aufrufen. }
Anonyme Felder sind besonders leistungsfähig für die Komposition von Interfaces (durch Einbettung von Interfaces) und für die Erstellung wiederverwendbarer Komponenten.
Struct-Verschachtelung
Struct-Verschachtelung bezieht sich auf die Praxis, eine Struct als benanntes Feld in einer anderen Struct einzuschließen. Im Gegensatz zu anonymen Feldern, bei denen die Felder des eingebetteten Typs befördert werden, greifen Sie bei benannter Verschachtelung explizit über das benannte innere Struct-Feld auf die Felder zu. Dies ist nützlich für die Organisation komplexer Daten und zur Vermeidung von Namenskollisionen.
package main import "fmt" type Manufacturer struct { Name string Country string } type EngineDetails struct { FuelType string Cylinders int Horsepower int } type Vehicle struct { ID string Manufacturer Manufacturer // Manufacturer ist eine verschachtelte Struct Engine EngineDetails // EngineDetails ist eine verschachtelte Struct Price float64 } func main() { // Erstellung einer Vehicle-Instanz mit verschachtelten Structs myVehicle := Vehicle{ ID: "V1001", Manufacturer: Manufacturer{ // Initialisierung der verschachtelten Manufacturer-Struct Name: "Honda", Country: "Japan", }, Engine: EngineDetails{ // Initialisierung der verschachtelten EngineDetails-Struct FuelType: "Gasoline", Cylinders: 4, Horsepower: 150, }, Price: 25000.00, } fmt.Println("Fahrzeug-ID:", myVehicle.ID) fmt.Println("Herstellername:", myVehicle.Manufacturer.Name) // Zugriff auf verschachteltes Feld fmt.Println("Herstellerland:", myVehicle.Manufacturer.Country) // Zugriff auf verschachteltes Feld fmt.Println("Motor Kraftstoffart:", myVehicle.Engine.FuelType) // Zugriff auf verschachteltes Feld fmt.Println("Motorleistung:", myVehicle.Engine.Horsepower) // Zugriff auf verschachteltes Feld // Ändern eines verschachtelten Feldes myVehicle.Engine.Horsepower = 160 fmt.Println("Neue Motorleistung:", myVehicle.Engine.Horsepower) // Ganze verschachtelte Structs können ebenfalls zugewiesen werden myVehicle.Manufacturer = Manufacturer{Name: "Toyota", Country: "Japan"} fmt.Println("Neuer Herstellername:", myVehicle.Manufacturer.Name) }
Auswahl zwischen anonymen Feldern und benannter Verschachtelung
Die Wahl zwischen anonymen Feldern und benannter Verschachtelung hängt von der semantischen Beziehung ab, die Sie ausdrücken möchten:
- Anonyme Felder (Einbettung): Verwenden Sie, wenn die äußere Struct "eine Art" oder "die Eigenschaften" der eingebetteten Struct "hat" und Sie ihre Felder auf die oberste Ebene heben möchten. Sie impliziert eine stärkere, integriertere Beziehung, die oft für Code-Wiederverwendung oder Interface-Erfüllung verwendet wird. Betrachten Sie es als das Einmischen von Fähigkeiten oder Attributen.
- Benannte Verschachtelung: Verwenden Sie, wenn die äußere Struct eine eindeutige Komponente oder einen Teil "hat", der selbst eine Struct ist. Sie impliziert eine klare Containment-Beziehung, und Komponenten werden explizit über ihre Namen angesprochen. Dies ist ideal für die Modellierung komplexer, hierarchischer Datenstrukturen.
Struct-Tags
Go-Structs können auch "Tags" haben, die ihren Feldern zugeordnet sind. Diese werden durch Reflexion für Metadatenzwecke verwendet, am häufigsten für die Serialisierung/Deserialisierung in Formate wie JSON oder XML oder für Validierungsbibliotheken.
package main import ( "encoding/json" "fmt" ) type User struct { ID int `json:"id"` // Gibt den Namen des JSON-Schlüssels an Username string `json:"username,omitempty"` // Gibt den JSON-Schlüssel an und lässt ihn weg, wenn er leer ist Email string `json:"email"` Password string `json:"-"` // Der "-"-Tag ignoriert dieses Feld während der JSON-Serialisierung CreatedAt string `json:"created_at,string"` // Als String für JSON behandeln } func main() { u := User{ ID: 1, Username: "gopher", Email: "gopher@example.com", Password: "supersecret", // Dieses Feld wird von JSON ignoriert CreatedAt: "2023-10-27T10:00:00Z", } // Die Struct in JSON umwandeln jsonData, err := json.MarshalIndent(u, "", " ") if err != nil { fmt.Println("Fehler bei der JSON-Umwandlung:", err) return } fmt.Println("JSON-Ausgabe:\n", string(jsonData)) // Ausgabe: // JSON-Ausgabe: // { // "id": 1, // "username": "gopher", // "email": "gopher@example.com", // "created_at": "2023-10-27T10:00:00Z" // } // JSON zurück in eine Struct umwandeln jsonString := `{"id":2, "username":"anon", "email":"anon@example.com", "password":"abcd", "created_at":"2023-10-28T11:00:00Z"}` var u2 User err = json.Unmarshal([]byte(jsonString), &u2) if err != nil { fmt.Println("Fehler bei der JSON-Umwandlung:", err) return } fmt.Println("\nUmwandlung User 2:", u2) fmt.Println("User 2 Passwort (sollte Nullwert sein, da ignoriert):",