Kurz: Go hat keine Klassen. Stattdessen gibt es Structs (Datenverbunde) mit Methoden.
Teil des Kurses Go
Go hat keine Klassen. Stattdessen gibt es Structs (Datenverbunde) mit Methoden.
package main
import "fmt"
type Person struct {
Name string
Alter int
}
// Methode mit Wert-Empfänger: arbeitet auf einer Kopie
func (p Person) Vorstellen() string {
return fmt.Sprintf("%s (%d)", p.Name, p.Alter)
}
// Methode mit Zeiger-Empfänger: kann das Original ändern
func (p *Person) Geburtstag() {
p.Alter++
}
func main() {
mia := Person{Name: "Mia", Alter: 17}
tom := Person{"Tom", 19}
var leer Person // Nullwerte: "" und 0
mia.Geburtstag()
fmt.Println(mia.Vorstellen(), tom, leer)
fmt.Printf("%+v\n", mia)
p := &tom // Zeiger auf Person
p.Alter = 30 // Go dereferenziert automatisch
fmt.Println(tom.Alter, (*p).Alter)
n := new(Person) // Zeiger auf Nullwert
n.Name = "Neu"
fmt.Println(*n)
}Ausgabe:
Mia (18) {Tom 19} { 0}
{Name:Mia Alter:18}
30 30
{Neu 0}Zeiger
Ein Zeiger (*T) enthält die Adresse einer Variable. &x liefert die Adresse, *p den Wert dahinter. Anders als in C gibt es keine Zeigerarithmetik, und der Garbage Collector verwaltet den Speicher.
package main
import "fmt"
func falsch(x int) { x = 99 }
func richtig(x *int) { *x = 99 }
type Zaehler struct{ n int }
func (z Zaehler) FalschErhoehen() { z.n++ } // Kopie: wirkungslos
func (z *Zaehler) Erhoehen() { z.n++ } // Original
func main() {
n := 1
falsch(n)
fmt.Println(n)
richtig(&n)
fmt.Println(n)
z := Zaehler{}
z.FalschErhoehen()
z.Erhoehen()
z.Erhoehen()
fmt.Println(z.n)
var p *int
fmt.Println(p == nil)
// fmt.Println(*p) // Panic: nil pointer dereference
}Ausgabe:
1
99
2
trueWert- oder Zeiger-Empfänger? Nimm *T, wenn die Methode den Zustand ändert oder das Struct groß ist. Mische beides nicht für denselben Typ.
Konstruktoren und Einbettung
Go hat keine Konstruktoren. Üblich sind Funktionen mit dem Namen NewXyz:
package main
import (
"errors"
"fmt"
)
type Konto struct {
inhaber string // klein geschrieben = nur im Paket sichtbar
stand float64
}
func NewKonto(inhaber string, start float64) (*Konto, error) {
if start < 0 {
return nil, errors.New("Startwert darf nicht negativ sein")
}
return &Konto{inhaber: inhaber, stand: start}, nil
}
func (k *Konto) Einzahlen(b float64) { k.stand += b }
func (k *Konto) Stand() float64 { return k.stand }
// Einbettung statt Vererbung
type Tier struct{ Name string }
func (t Tier) Vorstellen() string { return "Ich bin " + t.Name }
type Hund struct {
Tier // eingebettet: Felder und Methoden werden "hochgereicht"
Rasse string
}
func main() {
k, err := NewKonto("Mia", 100)
if err != nil {
fmt.Println(err)
return
}
k.Einzahlen(50)
fmt.Println(k.Stand())
_, err = NewKonto("X", -1)
fmt.Println(err)
h := Hund{Tier: Tier{Name: "Rex"}, Rasse: "Schäfer"}
fmt.Println(h.Name, h.Vorstellen(), h.Rasse)
}Ausgabe:
150
Startwert darf nicht negativ sein
Rex Ich bin Rex SchäferSichtbarkeit regelt die Groß-/Kleinschreibung des ersten Buchstabens: Name ist exportiert (von anderen Paketen nutzbar), name nicht.
Struct-Tags, Vergleich und Kopie
package main
import (
"encoding/json"
"fmt"
)
type Artikel struct {
Name string `json:"name"`
Preis float64 `json:"preis"`
Notiz string `json:"notiz,omitempty"`
}
func main() {
a := Artikel{"Stift", 1.5, ""}
b := a // Kopie
b.Preis = 9
fmt.Println(a == Artikel{"Stift", 1.5, ""}, a.Preis, b.Preis) // Structs sind vergleichbar
daten, _ := json.Marshal(a)
fmt.Println(string(daten))
var c Artikel
json.Unmarshal([]byte(`{"name":"Heft","preis":2.5}`), &c)
fmt.Printf("%+v\n", c)
}Ausgabe:
true 1.5 9
{"name":"Stift","preis":1.5}
{Name:Heft Preis:2.5 Notiz:}Methoden auf anderen Typen
Methoden kannst du auf jedem eigenen Typ definieren, nicht nur auf Structs:
package main
import (
"fmt"
"strings"
)
type Celsius float64
func (c Celsius) Fahrenheit() float64 { return float64(c)*9/5 + 32 }
type Woerter []string
func (w Woerter) Laengste() string {
l := ""
for _, x := range w {
if len(x) > len(l) {
l = x
}
}
return l
}
func (w Woerter) String() string { return strings.Join(w, "+") } // fmt nutzt String() automatisch
func main() {
fmt.Println(Celsius(100).Fahrenheit())
w := Woerter{"Go", "Gopher", "Sprache"}
fmt.Println(w.Laengste(), w)
}Ausgabe:
212
Sprache Go+Gopher+SpracheMerke
type Name struct { ... }bündelt Daten; Methoden stehen außerhalb:func (p Person) Methode()- Zeiger-Empfänger
(p *Person)ändern das Original, Wert-Empfänger arbeiten auf einer Kopie &xAdresse,*pWert; keine Zeigerarithmetik; automatisches Dereferenzieren bei Feldzugriff- Konstruktoren sind
NewXyz-Funktionen; Einbettung ersetzt Vererbung - Großbuchstabe = exportiert; Struct-Tags steuern z. B. JSON
Übungsaufgabe
Modelliere einen Warenkorb mit Artikeln (Struct), Methoden Hinzufuegen und Summe und lass ihn als JSON ausgeben.
Quiz zur Selbstkontrolle
Wann braucht eine Methode einen Zeiger-Empfänger?
- Wenn sie das Original ändern soll oder das Struct groß ist (richtig)
- Immer
- Nie
- Nur bei Maps
Wie macht man ein Feld "privat"?
- Mit kleinem Anfangsbuchstaben (richtig)
- Mit dem Schlüsselwort private
- Mit einem Unterstrich am Ende
- Gar nicht möglich
Was ersetzt in Go die Vererbung?
- Einbettung (Embedding) und Interfaces (richtig)
- extends
- Mehrfachvererbung
- Templates
Weiter im Kurs
Zurück: Arrays, Slices und Maps
Weiter: Interfaces und Generics
Alle Kapitel: Go im Überblick