Inhalt

Pointer in Go

Ein Pointer in Go, oder auch Zeiger verweist auf die Speicheradresse an der ein Wert gesichert ist. Um die Speicheradresse für eine Variable auszugeben verwendet man das & Zeichen:

1
2
3
4
5
6
7
8
func main() {
	x := 10
	fmt.Println(x)
	fmt.Println(&x)
	
	fmt.Printf("%T\n", x)
	fmt.Printf("%T\n", &x)
}

Gibt man Typ der Speicheradresse ausgeben, so ist er *int. Da * ist der Operator für einen Pointer, dass heißt, die Speicheradresse ist ein Pointer zu einem int. Mit anderen Worten zeigt der Pointer zeigt auf eine Speicheradresse an der ein Wert vom Typ int gespeichert ist.

Man muss hier klarstellen, dass ein Pointer zu einem int ein eigener Typ ist und nicht dem Typ int gleichzusetzen ist.

1
2
3
4
5
6
7
func main() {
	x := 10
	
	y := &x
	fmt.Println(y)
	fmt.Println(*y)
}

In Zeile 6 wird der Operator * verwendet. Dieser dereferenziert die in y gespeicherte Speicheradressem d.h. er gibt den dort gespeicherten Wert aus.

1
2
3
4
5
6
7
func main() {
	x := 10
	
	y := &x
	*y = 88
	fmt.Println(x)
}

Hier wird x = 88 gesetzt. Das liegt daran, dass die Variable y die Speicheradresse von x enthält. Durch die Verwendung von *y = 88 passiert wieder eine Dereferenzierung, d.h. man sagt dem Programm im Grunde “Setze den Wert, der sich an der in y gespeicherten Adresse befindet gleich 88”.

Wie Pointer benutzt werden

Pointer sind dann besonders nützlich, wenn man große Datemengen übergeben möchte. Es ist wesentlich effizienter auf die Adresse zu verweisen an der die Daten gespeichert sind statt die Daten an sich zu übergeben.

Der zweite Anwendungsfall ist der, dass man manchmal Werte an einer Adresse ändern möchte.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func main() {
	var x int = 5
	fmt.Println("Adresse von x", &x)
	zahlen(&x)
}

func zahlen(x *int) {
	fmt.Println("x vorher ",*x)
	fmt.Println("Adresse von x", &x)
	*x = 48
	fmt.Println("x nachher ",*x)
	fmt.Println("Adresse von x", &x)
}

Die Funktion zahlen() konsumiert einen Pointer vom Typ int, d.h. wir übergeben in Zeile 3 die Speicheradresse von x indem wir &x übergeben. Die Speicheradresse bleibt im oberen Beispiel immer die gleiche, während wir den Wert an der Adresse ändern.

Die Funktion zahlen() hat keinen Zugriff auf. Selbst wenn x direkt an sie übergeben würde wäre es nicht möglich x zu ändern, da bei Übergabe an die Funktion eine Kopie von x erstellt wird.

Methodensatz

Eine Methodensatz sind die Methoden, die einem bestimmten Datentyp zugerordnet sind. Ein Methodensatz bestimmt die Schnittstellen (Interfaces), die der jeweilige Typ implementiert.

Bei den Methodensätzen gibt es einen wichtigen Unterschied beim Empfänger von Argumenten:

Ein Receiver, der nicht als Pointer definiert ist funktioniert mit mit Pointern und auch mit nicht-Pointern.

Ein Receiver, der als Pointer definiert ist funktioniert nur mit Pointern.

 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
package main

import (
	"fmt"
	"math"
)

type kreis struct {
	radius float64
}

type form interface {
	flaeche() float64
}

func (k kreis) flaeche() float64 {
	return math.Pi * k.radius * k.radius
}

func info(f form) {
	fmt.Println("Fläche", f.flaeche())
}

func main() {
	k := kreis{5}
	info(&k)  //Pointer Übergabe
	info(k)   //Nicht-Pointer Übergabe
}

Die Aufrufe in Zeile 26 und 27 führen beide zum selben Ergebnis. Würde man die Methode flaeche() anders definieren würde der Aufruf in Zeile 27 einen Fehler verursachen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
...
func (k kreis) flaeche() float64 {
	return math.Pi * k.radius * k.radius
}
...

func main() {
	k := kreis{5}
	info(&k)  //Pointer Übergabe
	info(k)   //Nicht-Pointer Übergabe // FEHLER
}

Der Grund warum info(k) fehlschlägt ist, dass die Funktion konkret nach einem Pointer vom Typ *kreis verlangt. Da wie oben schon beschrieben ein Pointer vom Typ kreis ein eigenständiger Typ ist und nicht das gleiche wie der Typ kreis muss hier auch ein Pointer übergeben werden.