Übung: Untersuchen von Slices
Im vorherigen Abschnitt haben Sie mehr über Arrays erfahren, und es wurde deutlich, dass Arrays die Grundlage für Slices und Zuordnungen sind. Den Grund dafür erfahren Sie in Kürze. Wie ein Array ist auch ein Slice ein Datentyp in Go, der eine Sequenz von Elementen desselben Typs darstellt. Der signifikantere Unterschied zu Arrays besteht jedoch darin, dass die Größe eines Slice dynamisch und nicht fest ist.
Ein Slice ist eine Datenstruktur, die auf einem Array oder einem anderen Slice aufbaut. Wir bezeichnen das ursprüngliche Array bzw. Slice als das zugrunde liegende Array. Bei einem Slice können Sie entweder Zugriff auf das gesamte zugrunde liegende Array oder nur auf eine Untersequenz von Elementen haben.
Ein Slice weist nur drei Komponenten auf:
- Zeiger auf das erste erreichbare Element des zugrunde liegenden Arrays. Dieses Element ist nicht unbedingt das erste Element des Arrays,
array[0]
. - Länge des Slice. Die Anzahl der Elemente im Slice.
- Kapazität des Slice. Die Anzahl der Elemente zwischen dem Anfang eines Slice und dem Ende des zugrunde liegenden Arrays.
In der folgenden Abbildung wird dargestellt, was ein Slice ist:
Beachten Sie, dass es sich bei dem Slice nur um eine Teilmenge des zugrunde liegenden Arrays handelt. Nun wird erläutert, wie Sie die vorherige Abbildung im Code darstellen können.
Deklarieren und Initialisieren eines Slice
Sie deklarieren einen Slice auf die gleiche Weise wie ein Array. Der folgende Code stellt beispielsweise dar, was Sie auf der Abbildung des Slice gesehen haben:
package main
import "fmt"
func main() {
months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
fmt.Println(months)
fmt.Println("Length:", len(months))
fmt.Println("Capacity:", cap(months))
}
Wenn Sie den Code ausführen, erhalten Sie die folgende Ausgabe:
[January February March April May June July August September October November December]
Length: 12
Capacity: 12
Beachten Sie, dass sich ein Slice momentan nicht zu stark von einem Array unterscheidet. Sie deklarieren sie auf die gleiche Weise. Sie können die integrierten Funktionen len()
und cap()
verwenden, um die Informationen aus einem Slice abzurufen. Sie verwenden diese Funktionen weiterhin, um zu bestätigen, dass ein Slice eine Folge von Elementen aus einem zugrunde liegenden Array aufweisen kann.
Sliceelemente
Go bietet Unterstützung für den Sliceoperator s[i:p]
, wobei:
s
das Array darstellt.i
stellt den Zeiger auf das erste Element des zugrunde liegenden Arrays (oder eines anderen Slice) dar, das dem neuen Slice hinzugefügt werden soll. Die Variablei
entspricht dem Element an der Indexpositioni
im Arrayarray[i]
. Denken Sie daran, dass dieses Element nicht unbedingt das erste Element des zugrunde liegenden Arrays,array[0]
, ist.p
stellt die Anzahl der Elemente im zugrunde liegenden Array dar, die beim Erstellen des neuen Slice und der Elementposition verwendet werden sollen. Die Variablep
entspricht dem letzten Element im zugrunde liegenden Array, das im neuen Slice verwendet werden kann. Das Element an der Positionp
im zugrunde liegenden Array befindet sich an der Positionarray[i+1]
. Beachten Sie, dass dieses Element nicht unbedingt das letzte Element des zugrunde liegenden Arrays,array[len(array)-1]
, ist.
Dies bedeutet, dass ein Slice nur auf eine Teilmenge der Elemente verweisen kann.
Angenommen, Sie möchten vier Variablen für jedes Quartal des Jahres darstellen, und Sie verfügen über einen Slice von months
mit 12 Elementen. In der folgenden Abbildung wird veranschaulicht, wie months
in vier neue quarter
-Slices aufgeteilt wird:
Sie können den folgenden Code verwenden, um im Code das darzustellen, was Sie auf der vorherigen Abbildung gesehen haben:
package main
import "fmt"
func main() {
months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
quarter1 := months[0:3]
quarter2 := months[3:6]
quarter3 := months[6:9]
quarter4 := months[9:12]
fmt.Println(quarter1, len(quarter1), cap(quarter1))
fmt.Println(quarter2, len(quarter2), cap(quarter2))
fmt.Println(quarter3, len(quarter3), cap(quarter3))
fmt.Println(quarter4, len(quarter4), cap(quarter4))
}
Wenn Sie den Code ausführen, erhalten Sie die folgende Ausgabe:
[January February March] 3 12
[April May June] 3 9
[July August September] 3 6
[October November December] 3 3
Beachten Sie, dass die Länge der Slices identisch ist, sich die Kapazität jedoch unterscheidet. Sehen Sie sich nun den quarter2
-Slilce an. Wenn Sie diesen Slice deklarieren, soll der Slice an der Positionsnummer 3 beginnen, und das letzte Element befindet sich an der Positionsnummer 6. Die Länge des Slice beträgt drei Elemente, die Kapazität jedoch neun, da das zugrunde liegende Array über mehr Elemente oder Positionen verfügt, die aber für den Slice nicht sichtbar sind. Wenn Sie beispielsweise versuchen, etwas wie fmt.Println(quarter2[3])
auszugeben, erhalten Sie den folgenden Fehler: panic: runtime error: index out of range [3] with length 3
.
Die Kapazität eines Slice gibt Aufschluss darüber, inwieweit Sie einen Slice erweitern können. Aus diesem Grund können Sie wie in diesem Beispiel einen erweiterten Slice aus quarter2
erstellen:
package main
import "fmt"
func main() {
months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
quarter2 := months[3:6]
quarter2Extended := quarter2[:4]
fmt.Println(quarter2, len(quarter2), cap(quarter2))
fmt.Println(quarter2Extended, len(quarter2Extended), cap(quarter2Extended))
}
Wenn Sie den vorherigen Code ausführen, wird die folgende Ausgabe angezeigt:
[April May June] 3 9
[April May June July] 4 9
Beachten Sie, dass Sie beim Deklarieren der quarter2Extended
-Variable nicht die ursprüngliche Position ([:4]
) angeben müssen. Wenn Sie das tun, nimmt Go an, dass Sie die erste Position des Slice verwenden möchten. Denselben Vorgang können Sie auch für die letzte Position ([1:]
) durchführen. Go geht davon aus, dass Sie auf alle Elemente bis zur letzten Position eines Slice (len()-1
) verweisen möchten.
Anfügen von Elementen
Wir haben untersucht, wie Slices funktionieren und wie sie Arrays ähneln. Nun erfahren Sie, wie sie sich von Arrays unterscheiden. Der erste Unterschied besteht darin, dass die Größe eines Slice nicht fest sondern dynamisch ist. Nachdem Sie einen Slice erstellt haben, können Sie ihm weitere Elemente hinzufügen, sodass er erweitert wird. Sie erfahren in Kürze, was mit dem zugrunde liegenden Array passiert.
Go bietet die integrierte Funktion append(slice, element)
, um ein Element zu einem Slice hinzuzufügen. Sie übergeben den zu ändernden Slice und das anzufügende Element als Werte an die Funktion. Die append
-Funktion gibt dann einen neuen Slice zurück, den Sie in einer Variablen speichern. Dies kann die gleiche Variable für den Slice sein, den Sie ändern.
Hier sehen Sie, wie der Anfügeprozess im Code aussieht:
package main
import "fmt"
func main() {
var numbers []int
for i := 0; i < 10; i++ {
numbers = append(numbers, i)
fmt.Printf("%d\tcap=%d\t%v\n", i, cap(numbers), numbers)
}
}
Wenn Sie den vorherigen Code ausführen, sollte die folgende Ausgabe angezeigt werden:
0 cap=1 [0]
1 cap=2 [0 1]
2 cap=4 [0 1 2]
3 cap=4 [0 1 2 3]
4 cap=8 [0 1 2 3 4]
5 cap=8 [0 1 2 3 4 5]
6 cap=8 [0 1 2 3 4 5 6]
7 cap=8 [0 1 2 3 4 5 6 7]
8 cap=16 [0 1 2 3 4 5 6 7 8]
9 cap=16 [0 1 2 3 4 5 6 7 8 9]
Diese Ausgabe ist interessant. Dies gilt besonders für die Rückgabe der cap()
-Funktion für den Aufruf. Bis zur dritten Iteration sieht alles normal aus, da hier die Kapazität in 4 geändert wird und nur drei Elemente im Slice vorhanden sind. In der fünften Iterationen wird die Kapazität in 8 und in der neunten Iteration in 16 geändert.
Erkennen Sie ein Muster in Bezug auf die Kapazitätsausgabe? Wenn ein Slice nicht über genügend Kapazität verfügt, um mehr Elemente aufzunehmen, verdoppelt Go die Kapazität. Es wird ein neues zugrunde liegendes Array mit der neuen Kapazität erstellt. Sie müssen nichts tun, um die Kapazität zu erhöhen. Go führt diesen Schritt automatisch aus. Sie müssen vorsichtig sein. Ein Slice kann zu einem bestimmten Zeitpunkt mehr Kapazität aufweisen, als er benötigt, wodurch Sie möglicherweise Speicherplatz verschwenden.
Entfernen von Elementen
Fragen Sie sich, wie Sie Elemente entfernen können? Go bietet keine integrierte Funktionen zum Entfernen von Elementen aus einem Slice. Sie können den zuvor erwähnten Sliceoperator s[i:p]
verwenden, um einen neuen Slice mit nur den benötigten Elementen zu erstellen.
Der folgende Code entfernt beispielsweise ein Element aus einem Slice:
package main
import "fmt"
func main() {
letters := []string{"A", "B", "C", "D", "E"}
remove := 2
if remove < len(letters) {
fmt.Println("Before", letters, "Remove ", letters[remove])
letters = append(letters[:remove], letters[remove+1:]...)
fmt.Println("After", letters)
}
}
Wenn Sie den vorherigen Code ausführen, wird die folgende Ausgabe angezeigt:
Before [A B C D E] Remove C
After [A B D E]
Mit diesem Code wird ein Element aus einem Slice entfernt. Er ersetzt das zu entfernende Element durch das nächste Element im Slice oder keines, wenn Sie das letzte Element entfernen.
Ein weiterer Ansatz ist das Erstellen einer neuen Kopie des Slice. Im nächsten Abschnitt erfahren Sie, wie Sie Kopien von Slices erstellen.
Erstellen von Kopien von Slices
Go verfügt über die integrierte Funktion copy(dst, src []Type)
zum Erstellen von Kopien eines Slice. Sie senden den Zielslice und den Quellslice. Sie können beispielsweise wie in diesem Beispiel gezeigt eine Kopie eines Slice erstellen:
slice2 := make([]string, 3)
copy(slice2, letters[1:4])
Warum könnte es wichtig sein, Kopien zu erstellen? Wenn Sie ein Element aus einem Slice ändern, ändern Sie auch das zugrunde liegende Array. Dies wirkt sich auf alle anderen Slices aus, die auf dasselbe zugrunde liegende Array verweisen. Sehen Sie sich diesen Prozess im Code an, und beheben Sie das Problem, indem Sie eine Kopie eines Slice erstellen.
Verwenden Sie den folgenden Code, um zu bestätigen, dass ein Slice auf ein Array zeigt. Jede von Ihnen in einem Slice vorgenommene Änderung wirkt sich auf das zugrunde liegende Array aus.
package main
import "fmt"
func main() {
letters := []string{"A", "B", "C", "D", "E"}
fmt.Println("Before", letters)
slice1 := letters[0:2]
slice2 := letters[1:4]
slice1[1] = "Z"
fmt.Println("After", letters)
fmt.Println("Slice2", slice2)
}
Wenn Sie den vorherigen Code ausführen, wird die folgende Ausgabe angezeigt:
Before [A B C D E]
After [A Z C D E]
Slice2 [Z C D]
Beachten Sie, dass sich die Änderung, die Sie bei slice1
vorgenommen haben, auf das Array letters
und slice2
ausgewirkt hat. In der Ausgabe sehen Sie, dass der Buchstabe „B“ durch „Z“ ersetzt wurde und sich dies auf alle Elemente auswirkt, die auf das letters
-Array zeigen.
Sie müssen zum Beheben des Problems eine Slicekopie erstellen, die im Prinzip ein neues zugrunde liegendes Array darstellt. Sie können dazu den folgenden Code verwenden:
package main
import "fmt"
func main() {
letters := []string{"A", "B", "C", "D", "E"}
fmt.Println("Before", letters)
slice1 := letters[0:2]
slice2 := make([]string, 3)
copy(slice2, letters[1:4])
slice1[1] = "Z"
fmt.Println("After", letters)
fmt.Println("Slice2", slice2)
}
Wenn Sie den vorherigen Code ausführen, wird die folgende Ausgabe angezeigt:
Before [A B C D E]
After [A Z C D E]
Slice2 [B C D]
Beachten Sie, dass sich die Änderung in slice1
auf das zugrunde liegende Array ausgewirkt hat, sich aber nicht auf den neuen slice2
ausgewirkt hat.