Wertübergabe vs. Referenzübergabe
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 21 von 107.
Go ist eine Pass-by-Value-Sprache. Wenn Sie eine Variable an eine Funktion übergeben, erstellt Go eine Kopie dieses Wertes. Das bedeutet, dass Änderungen innerhalb der Funktion die ursprüngliche Variable nicht beeinflussen.
func double(n int) {
n = n * 2
}
func main() {
x := 10
double(x)
fmt.Println(x) // 10 (unverändert)
}Das Gleiche gilt für Structs. Das Übergeben eines Structs an eine Funktion kopiert alle seine Felder, sodass Änderungen innerhalb der Funktion verloren gehen.
type Person struct {
Name string
Age int
}
func birthday(p Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(alice)
fmt.Println(alice.Age) // 30 (unverändert)
}Um den ursprünglichen Wert zu ändern, übergeben Sie stattdessen einen Pointer. Der Pointer selbst wird kopiert, aber er zeigt immer noch auf dieselbe Speicheradresse, was es Ihnen ermöglicht, die ursprünglichen Daten zu ändern.
func birthday(p *Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(&alice)
fmt.Println(alice.Age) // 31 (geändert!)
}Verwenden Sie Pointer, wenn Sie den ursprünglichen Wert ändern müssen oder wenn Sie große Structs übergeben, um teures Kopieren zu vermeiden. Verwenden Sie Werte, wenn Sie die ursprünglichen Daten vor unbeabsichtigten Änderungen schützen möchten.
Aufgabe
EinfachLassen Sie uns ein Warenkorbsystem erstellen, das den Unterschied zwischen der Übergabe von Structs als Wert (by value) gegenüber der Übergabe als Zeiger (by pointer) demonstriert. Sie werden aus erster Hand sehen, wie nur Zeiger-Parameter die ursprünglichen Daten ändern können.
Sie werden Ihren Code auf zwei Dateien aufteilen:
cart.go: Definieren Sie einCartItemStruct mit den FeldernName(string),Price(float64) undQuantity(int). Erstellen Sie zwei Funktionen, die versuchen, die Menge zu aktualisieren:UpdateQuantityByValuenimmt einCartItem(als Wert) und eine neue Menge entgegen und setzt dann die Menge des Artikels auf den neuen Wert.UpdateQuantityByPointernimmt ein*CartItem(Zeiger) und eine neue Menge entgegen und setzt dann die Menge des Artikels auf den neuen Wert.
TotalMethode fürCartItem, die den Preis multipliziert mit der Menge zurückgibt.main.go: Lesen Sie die Artikeldetails aus der Eingabe ein, erstellen Sie ein CartItem und demonstrieren Sie dann beide Aktualisierungsansätze. Versuchen Sie zuerst die Aktualisierung mit der Wert-Funktion und zeigen Sie das Ergebnis, dann aktualisieren Sie mit der Zeiger-Funktion und zeigen Sie das Ergebnis erneut.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Artikelname
- Zeile 2: Preis (float)
- Zeile 3: Anfängliche Menge (integer)
- Zeile 4: Neue zu setzende Menge (integer)
Ausgabeformat:
Initial: [Name] x[Quantity] = $[Total]
After value update: [Name] x[Quantity] = $[Total]
After pointer update: [Name] x[Quantity] = $[Total]Zum Beispiel, gegeben Laptop, 999.99, 1 und 3, sollte Ihre Ausgabe wie folgt aussehen:
Initial: Laptop x1 = $999.99
After value update: Laptop x1 = $999.99
After pointer update: Laptop x3 = $2999.97Beachten Sie, wie die Menge nach der Wert-Aktualisierung bei 1 bleibt (die Funktion hat eine Kopie geändert), sich aber nach der Zeiger-Aktualisierung auf 3 ändert (die Funktion hat das Original geändert). Formatieren Sie die Gesamtsummen mit zwei Dezimalstellen.
Spickzettel
Go ist eine Pass-by-Value-Sprache. Wenn Sie eine Variable an eine Funktion übergeben, erstellt Go eine Kopie dieses Wertes, sodass Änderungen innerhalb der Funktion die ursprüngliche Variable nicht beeinflussen.
func double(n int) {
n = n * 2
}
func main() {
x := 10
double(x)
fmt.Println(x) // 10 (unverändert)
}Dasselbe gilt für Structs. Die Übergabe eines Structs an eine Funktion kopiert alle seine Felder:
type Person struct {
Name string
Age int
}
func birthday(p Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(alice)
fmt.Println(alice.Age) // 30 (unverändert)
}Um den ursprünglichen Wert zu ändern, übergeben Sie einen Pointer. Der Pointer selbst wird kopiert, zeigt aber immer noch auf dieselbe Speicheradresse:
func birthday(p *Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(&alice)
fmt.Println(alice.Age) // 31 (geändert!)
}Verwenden Sie Pointer, wenn Sie den ursprünglichen Wert ändern müssen oder wenn Sie große Structs übergeben, um teures Kopieren zu vermeiden. Verwenden Sie Werte, wenn Sie die ursprünglichen Daten vor unbeabsichtigten Änderungen schützen möchten.
Probier es selbst
package main
import (
"fmt"
)
func main() {
// Eingabe lesen
var name string
var price float64
var initialQty, newQty int
fmt.Scanln(&name)
fmt.Scanln(&price)
fmt.Scanln(&initialQty)
fmt.Scanln(&newQty)
// TODO: Erstelle ein CartItem mit den Eingabewerten
// TODO: Gib den Anfangszustand mit folgendem Format aus:
// fmt.Printf("Initial: %s x%d = $%.2f\n", ...)
// TODO: Versuche, die Menge mit UpdateQuantityByValue zu aktualisieren
// Gib dann das Ergebnis aus (beachte, dass sich die Menge nicht ändern wird)
// TODO: Aktualisiere die Menge mit UpdateQuantityByPointer
// Gib dann das Ergebnis aus (beachte, dass sich die Menge ändern wird)
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool