Slices

Slices sind sogenannte Felder, also eine Aneinanderreihung von Datentypen, die man über einen fortlaufenden Zähler (Index) auswählen kann. Was sich kompliziert anhört, ist vom Aufbau her ganz einfach. Beispielsweise sind die Tage in einem Monat über einen Index auswählbar (»der 13. Tag im Oktober«). Oder Häuser in einer Straße (»Haus Nr. 117«). Auch werden Dateien von der Festplatte Byte für Byte eingelesen und als Byte-Slices gespeichert. Es kann aber jeder beliebige Datentyp in einem Feld gespeichert werden.

Im Unterschied zu unserer gewohnten Zählweise beginnt Go mit der Zahl 0 an zu zählen. D.h. das erste Element in einer Reihe wird über den Index 0 angesprochen:

package main

import "fmt"

func main() {
    jahreszeiten := []string{"Frühling", "Sommer", "Herbst", "Winter"}
    // Schreibt "Sommer" auf die Konsole, nicht "Frühling!"
    fmt.Println(jahreszeiten[1])
}

Die Länge eines slice kann man mit der eingebauten Funktion len() feststellen. Das Feld jahreszeiten aus dem Beispiel hat erwartungsgemäß die Länge 4. Vorsicht: Wenn du versuchst auf einen Index zuzugreifen, der nicht erlaubt ist (im Beispiel wäre das -1 oder kleiner bzw. 4 oder größer), dann wird ein Laufzeitfehler erzeugt (eine sogenannte panic, siehe Fehlerbehandlung).

Für Felder reserviert Go eine bestimmte Menge an zusammenhängenden Speicher, damit man effizient auf die einzelnen Werte zugreifen kann. Jedes Slice hat eine Länge und eine Kapazität. Die Länge bestimmt das, was der Anwender sieht (also die Felder, auf die man zugreifen kann) und die Kapazität die Menge der Einträge, die man mit append() hinzufügen kann, bevor Go neuen Speicher reservieren muss.

package main

import "fmt"

func main() {
    feld := make([]byte, 50, 100)
    fmt.Println("Länge:", len(feld))
    fmt.Println("Kapazität:", cap(feld))
    feld[49] = 100
    // geht nicht, da feld nur eine Länge von 50
    // hat und die Indices von 0-49 gehen
    // feld[50] = 100
    feld = append(feld, 100)
    fmt.Println(feld[50])
    fmt.Println("Länge nach append:", len(feld))
    fmt.Println("Kapazität nach append:", cap(feld))
}

Die Ausgabe ist:

$ go run main.go
Länge: 50
Kapazität: 100
100
Länge nach append: 51
Kapazität nach append: 100

fügt man noch weitere Einträge mit append() hinzu, erhöht sich die Kapazität des Slices entsprechend. Beachte aber, dass das Erweitern der Kapazität »teuer« ist. Es muss neuer Speicher reserviert werde, und alle Einträge aus dem aktuellen Slice müssen in den neuen Speicher kopiert werden. Bei zeitkritischen Routinen sollten man also ein wenig planen.