Zeiger (pointer)

Alle Daten (Zeichenketten, Funktionen, Structs, …​) liegen im Hauptspeicher des Computers, damit das Programm darauf zugreifen kann. Go kümmert sich um die Organisation aller Daten, ohne, dass der Benutzer das merkt oder eingreifen muss. Weist man einer Variablen str eine Zeichenkette zu, dann kann man sofort damit arbeiten, ohne sich um den Speicherzugriff zu kümmern. Manchmal möchte man jedoch die Adresse der Daten haben, um diese direkt manipulieren zu können. Im Gegensatz zu Programmiersprachen wie C kann man jedoch keine Zeigerarithmetik durchführen:

package main

import (
	"fmt"
)

func main() {
	a := 3
	// pa ist Zeiger auf a und enthält
	// die Adresse von a im Speicher
	pa := &a
	fmt.Println(pa)
	// geht nicht:
	pa++
}

Mit Zeigern kann man mit mehreren Variablen auf denselben Speicherbereich zeigen. Damit kann man den Speicher von mehreren Stellen aus verändern und mit allen Variablen kann man auf die veränderten Werte zugreifen:

package main

import "fmt"

type beispiel struct {
	x string
	y string
}

func main() {
	a := beispiel{"A", "A"}
	b := a
	b.x = "B"
	fmt.Printf("%v\n", a)
	fmt.Printf("%v\n", b)

	// nun mit Zeigern
	pa := &beispiel{"A", "A"}
	pb := pa
	pb.x = "B"
	fmt.Printf("%v\n", pa)
	fmt.Printf("%v\n", pb)
}
Lässt man das Programm laufen, sieht man den Unterschied zwischen Zugriffen mit und ohne Zeigern. In der zweiten Zeile der main() Funktion wird ein Kopie der Struktur erzeugt und an b zugewiesen. In der Zuweisung pb := pa wird eine Kopie des Zeigers erzeugt, beide Variablen zeigen auf dieselbe Stelle.

Eine Erklärung der Zeichen: &x bedeutet »Speicheradresse von x«. &beispiel{...} bedeutet dasselbe wie &(beispiel{..}) und ist im Prinzip eine Kurzform für

tmp : = beispiel{...}
b := &tmp

nur ohne eine temporäre Variable. Es wird also die Speicheradresse einer Instanz der Struktur beispiel zurückgegeben.

Zeiger können auch den Wert nil haben, dann darf man sie nicht benutzen. Die Speicheradresse gibt es nicht und damit keinen Wert, den man dort abfragen kann. Daher ist es oft wichtig, auf nil zu prüfen.

package main

import "fmt"

type beispiel struct {
	x string
	y string
}

func main() {
	var pb *beispiel
	fmt.Println(pb)
	pb.x = "test"
}
pb ist noch nicht initialisiert, daher hat der Zeiger den Wert nil. Greift man nun auf ein Feld innerhalb der Struktur zu, gibt es einen Laufzeitfehler.

Die Ausgabe aus dem Programm (go run main.go) ist:

<nil>
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a6ef8]

goroutine 1 [running]:
main.main()
        /home/gobuch/main.go:13 +0x78
exit status 2

Auf nil zu prüfen ist leicht:

package main

import (
	"log"
)

type beispiel struct {
	x string
	y string
}

func main() {
	var pb *beispiel
	// zum Testen die nächste Zeile mit // auskommentieren
	pb = new(beispiel)

	if pb == nil {
		// beendet das Programm mit einem Fehler
		log.Fatal("pb wurde nicht initialisiert")
	}
	pb.x = "test"
}