EMZETT.
Login

Kurz: Go hat keine Klassen. Stattdessen gibt es Structs (Datenverbunde) mit Methoden.

Teil des Kurses Go

Kapitel 6 von 11 im Kurs Go (Abschnitt „Eigene Typen“). Mit Fortschritt, Quiz und Zertifikat auf der Lernseite.

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
true

Wert- 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äfer

Sichtbarkeit 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+Sprache

Merke

  • 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
  • &x Adresse, *p Wert; 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

Weiter im Kurs

Zurück: Arrays, Slices und Maps

Weiter: Interfaces und Generics

Alle Kapitel: Go im Überblick