Inhalt

Datenstrukturen in Go

Ein Struct ist eine Datenstruktur, die es uns erlaubt Werte verschiedener Datentypen zu Feldern zusammenzufassen. Jedes Feld hat eine Bezeichnung und einen Typ.

Im Prinzip erstellt man sich mit einem Struct einen eigenen Typ.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type person struct {
	vorname  string
	nachname string
	alter int
}

func main() {
	person1 := person{
		vorname:  "Peter",
		nachname: "Müller",
		alter: 28,
	}
	person2 := person{
		vorname:  "Maria",
		nachname: "Maier",
		alter: 21,
	}
	fmt.Println(person1)
	fmt.Println(person2)

	fmt.Println(person1.vorname, person1.nachname, person1.alter)
	fmt.Println(person2.vorname, person2.nachname, person2.alter)
}

Wie man sieht kann man sowohl die kompletten Daten ausgeben, als auch die verschiedenen Felder ganz konkret abrufen.

Eingebettete Structs

Man kann Structs auch ineinander einbetten, d.h. genauso wie ein Struct ein Feld des Typs Integer enthalten kann, könnte es auch ein Feld enthalten, dass in einem anderen Struct definiert wurde.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type person struct {
	vorname  string
	nachname string
	alter    int
}

type superHeld struct {
	person
	cape bool
}

func main() {
	person1 := person{
		vorname:  "Peter",
		nachname: "Müller",
		alter:    28,
	}
	person2 := superHeld{
		person: person{
			vorname:  "Maria",
			nachname: "Maier",
			alter:    21,
		},
		cape: true,
	}
	fmt.Println(person1)
	fmt.Println(person2)

	fmt.Println(person1.vorname, person1.nachname, person1.alter)
	fmt.Println(person2.vorname, person2.nachname, person2.alter, person2.cape)
}

Wie man sieht enthält das Struct superHeld ein Feld vom Typ person, dass wir zuvor schon definiert haben und ein zusätzliches Feld vom Typ boolean.

Namenskollisionen

Es kann Vorkommen, dass eingebettete Structs eventuell kollidierende Felder haben. In diesem Beispiel wäre das das Feld vorname

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type person struct {
	vorname  string
	nachname string
	alter    int
}

type superHeld struct {
	person
	vorname string
	cape bool
}

func main() {
	person2 := superHeld{
		person: person{
			vorname:  "Maria",
			nachname: "Maier",
			alter:    21,
		},
		vorname: "Batgirl",
		cape: true,
	}
	fmt.Println(person2)

	fmt.Println(person2.vorname, person2.person.vorname, person2.nachname, person2.alter, person2.cape)
}

Ruft man den Wert für person2.vorname ab so erhält man das Feld Vorname des Typs superHeld. Um an den Vornamen des Typs person zu kommen muss man in diesem Fall ganz konkret das Feld des eingebetteten Structs abrufen: person2.person.vorname

Wenn, wie beim Beispiel zuvor ein Feld des eingebetteten Structs nicht im übergeordneten Struct vorkommt, wird dieses automatisch eine Ebene höher “befördert” und ist wie in dem Beispiel über person2.vorname abrufbar.

Anonyme Structs

Anonyme Structs sind Datenstrukturen ohne Namen. Die Verwendung von anonymen Structs macht z.B. Sinn wenn man ein Struct wirklich nur an einer bestimmten Stelle benötigt und es ansonsten nicht nutzbar sein muss:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
person1 := struct {
		vorname  string
		nachname string
		alter    int
	}{
		vorname:  "Peter",
		nachname: "Müller",
		alter:    28,
	}
	fmt.Println(person1)

Im Grunde passieren hier mehrere Dinge auf einmal. Es wird eine Variable person1 mit einem eigenen Typ definiert (struct {…}). Anschließend werden dieser Variable Werte zugewiesen. Hier sieht man auch warum man von anonymen Structs spricht.
In zeile 1 wird zwar über struct {} ein eigener Typ definiert, dieser hat aber keinen Namen im Gegensatz zu den Beispielen zuvor.

Über ein Struct iterieren

Im Gegensatz zu Maps kann man über die Felder eines Structs nicht iterieren. Abhilfe schafft hier das Paket reflect:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package main

import (
	"fmt"
	"reflect"
)

type person struct {
	Vorname  string `json:"fname"`
	Nachname string
	Alter    int
}

type superHeld struct {
	person
	Vorname string `json:"fname"`
	Cape    bool
}

func main() {
	person2 := superHeld{
		person: person{
			Vorname:  "Maria",
			Nachname: "Maier",
			Alter:    21,
		},
		Vorname: "Batgirl",
		Cape:    true,
	}

	v := reflect.TypeOf(person2)
	feld1 := v.Field(1)

	fmt.Println("Feldname: ", feld1.Name)
	fmt.Println("Feldtyp: ", feld1.Type)
	fmt.Println("Feldindex: ", feld1.Index)
	fmt.Println("Feldtag: ", feld1.Tag)

	fmt.Println("Anzahl Felder: ", v.NumField())

	for i := 0; i < v.NumField(); i++ {
		f := v.Field(i)
		fmt.Println("Feldname: ", f.Name)
		fmt.Println("Feldtyp: ", f.Type)
		fmt.Println("Feldindex: ", f.Index)
		fmt.Println("Feldtag: ", f.Tag)
	}

}

Die Funktion TypeOf() in Zeile 31 liefert uns den Typ vom Typ Type (ja jetzt wirds abstrakt) unseres Structs zurück. Der Typ Type stellt uns neben anderen Methoden die Methode Field() zur Verfügung. Wir übergeben hier eine 1 für den Index 1, also das Feld Vorname. Zurück erhalten wir einen Wert vom Typ StructField. Dieser ist selbst ein Struct und besitzt unter anderem die Felder Name, Type, Index und Tag welche wir in Zeile 34+ für die Ausgabe nutzen.

Damit wir über die Felder unseres Staructs iterieren können benötigen wir die Anzahl der Felder. Die Funktion len() scheidet hier leider aus, da sie keine Structs verarbeiten kann. Glücklicherweise besitzt der Typ Type eine Methode NumField() deren Aufgabe es ist die Anzahl der Felder eines Structs zurückzuliefern. Dadurch wird es möglich, so wie in Zeile 41+ über die Felder unseres Structs zu iterieren.