Golang Slice Datenstrukturen

GolangGolangBeginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

Im vorherigen Abschnitt haben wir Arrays in Go besprochen. Allerdings haben Arrays Einschränkungen: Sobald sie deklariert und initialisiert sind, kann ihre Länge nicht geändert werden. Daher werden Arrays in der täglichen Programmierung nicht weit verbreitet eingesetzt. Im Gegensatz dazu werden Slices (Schnitte) häufiger verwendet und bieten eine flexiblere Datenstruktur.

Wissenspunkte:

  • Definition eines Slices
  • Initialisierung eines Slices
  • Operationen auf Slices, d. h. Hinzufügen, Löschen, Ändern und Suchen
  • Erweiterung eines Slices
  • Kürzung eines Slices
  • Mehrdimensionale Slices
Dies ist ein Guided Lab, das schrittweise Anweisungen bietet, um Ihnen beim Lernen und Üben zu helfen. Befolgen Sie die Anweisungen sorgfältig, um jeden Schritt abzuschließen und praktische Erfahrungen zu sammeln. Historische Daten zeigen, dass dies ein Labor der Stufe Anfänger mit einer Abschlussquote von 100% ist. Es hat eine positive Bewertungsrate von 100% von den Lernenden erhalten.

Was ist ein Slice?

Slices (Schnitte) sind ähnlich wie Arrays; sie sind Container, die Elemente desselben Datentyps enthalten. Allerdings haben Arrays Einschränkungen: Sobald sie deklariert und initialisiert sind, kann ihre Länge nicht geändert werden. Obwohl Arrays ihre Anwendungsfälle haben, sind sie nicht so flexibel. Daher werden Slices in der täglichen Programmierung häufiger verwendet.

In Go werden Slices mithilfe von Arrays implementiert. Ein Slice ist im Wesentlichen ein dynamisches Array, dessen Länge sich ändern kann. Wir können Operationen wie das Hinzufügen, Löschen, Ändern und Suchen von Elementen in einem Slice durchführen, was mit einem Array nicht möglich ist.

Definition eines Slices

Die Initialisierungssyntax für ein Slice ist der eines Arrays sehr ähnlich. Der Hauptunterschied besteht darin, dass die Länge der Elemente nicht angegeben werden muss. Schauen wir uns den folgenden Code an:

// Declare an array with a length of 5
var a1 [5]byte
// Declare a slice
var s1 []byte

Ein Slice ist eine Referenz auf ein Array

Wenn wir ein Array vom Typ int deklarieren, ist der Nullwert für jedes Element des Arrays 0.

Allerdings ist der Nullwert für ein Slice, wenn wir es deklarieren, nil. Lassen Sie uns eine Datei namens slice.go erstellen, um dies zu überprüfen:

touch ~/project/slice.go

Geben Sie den folgenden Code ein:

package main

import "fmt"

func main() {
    var a [3]int
    var s []int

    fmt.Println(a[0] == 0) // true
    fmt.Println(s == nil)  // true
}
go run slice.go

Nachdem Sie den Code ausgeführt haben, wird die folgende Ausgabe angezeigt:

true
true

Wir haben ein Array a und ein Slice s erstellt. Wir vergleichen das erste Element des Arrays a mit Null und prüfen, ob das Slice s nil ist.

Wie wir sehen können, ist der Nullwert eines Slices nil, wenn wir es deklarieren. Dies liegt daran, dass Slices keine Daten speichern; sie verweisen nur auf Arrays. Das Slice zeigt auf die zugrunde liegende Array-Struktur.

Datenstruktur eines Slices

Ein Slice ist ein zusammengesetzter Datentyp, auch als Struktur (struct) bekannt. Es ist ein zusammengesetzter Typ, der aus Feldern unterschiedlicher Typen besteht. Die interne Struktur eines Slices besteht aus drei Elementen: einem Zeiger, einer Länge und einer Kapazität.

type slice struct {
    elem *type
    len  int
    cap  int
}

Wie bereits erwähnt, verweist die Struktur auf das zugrunde liegende Array. Der elem-Zeiger zeigt auf das erste Element des Arrays, und type ist der Typ des referenzierten Array-Elements.

len und cap stellen die Länge und die Kapazität des Slices dar. Sie können die Funktionen len() und cap() verwenden, um die Länge und die Kapazität des Slices zu erhalten.

Das folgende Bild zeigt, dass das Slice auf ein zugrunde liegendes Array vom Typ int verweist und eine Länge von 8 und eine Kapazität von 10 hat:

slice referencing int array

Wenn Sie ein neues Slice definieren, wird der elem-Zeiger auf den Nullwert (d. h. nil) initialisiert. Das Konzept von Zeigern wird in späteren Labs eingeführt. Beachten Sie vorerst, dass ein Zeiger auf die Speicheradresse eines Werts zeigt. Im obigen Bild zeigt der elem-Zeiger auf die Adresse des ersten Elements des zugrunde liegenden Arrays.

Operationen auf Slices: Hinzufügen, Löschen, Ändern und Suchen

Kürzen von Arrays oder Slices

Da die zugrunde liegende Struktur eines Slices ein Array ist, können wir eine bestimmte Länge des Arrays als Referenz für das Slice extrahieren. Das folgende Code-Segment zeigt dies:

package main

import "fmt"

func main() {
    // Define an integer array with a length of 10
    a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    // Declare an empty slice
    var s1 []int

    fmt.Println("Slice s1 is empty:", s1 == nil)

    // Use array truncation to obtain the slice
    s1 = a[1:5]
    s2 := a[2:5]
    s3 := a[:]

    fmt.Println("Slice s1, s2, and s3 are empty:", s1 == nil, s2 == nil, s3 == nil)
    fmt.Println("Elements in array a:", a)
    fmt.Println("Elements in slice s1:", s1)
    fmt.Println("Elements in slice s2:", s2)
    fmt.Println("Elements in slice s3:", s3)
}

Die Ausgabe ist wie folgt:

Slice s1 is empty: true
Slice s1, s2, and s3 are empty: false false false
Elements in array a: [0 1 2 3 4 5 6 7 8 9]
Elements in slice s1: [1 2 3 4]
Elements in slice s2: [2 3 4]
Elements in slice s3: [0 1 2 3 4 5 6 7 8 9]

In diesem Programm deklarieren und initialisieren wir zunächst das Array a und verwenden dann die Array-Kürzung, um einen Teil des Arrays dem leeren Slice s1 zuzuweisen. Dadurch erstellen wir ein neues Slice.

s1[1:5] repräsentiert die Erstellung eines Slices aus dem Array. Der Bereich des Slices geht von Index 1 des Arrays a bis Index 5, wobei das fünfte Element ausgeschlossen ist.

Hinweis: In Programmiersprachen beginnt der Index des ersten Elements mit 0, nicht mit 1. Entsprechend hat das zweite Element im Array den Index 1.

Wir verwenden den :=-Operator, um das gekürzte Array direkt dem Slice s2 zuzuweisen. Das Gleiche gilt für s3, aber hier wird kein Bereich angegeben, sodass alle Elemente des Arrays gekürzt werden.

Das folgende Bild zeigt die Kürzung. Beachten Sie, dass der grüne Teil des Slices auf das blaue Array verweist. Mit anderen Worten, sie teilen sich dasselbe zugrunde liegende Array, nämlich a.

slice truncation visualization

Die Syntax für die Kürzung von Slices lautet wie folgt:

[start:end]

Sowohl start als auch end sind optionale Argumente. Wenn wir alle Elemente des Arrays erhalten möchten, können wir sowohl das start- als auch das end-Argument weglassen. Dies wird in s3 := a[:] im vorherigen Programm gezeigt.

Wenn wir alle Elemente nach einem bestimmten Index abrufen möchten, können wir das end-Argument weglassen. Beispielsweise wird a1[3:] alle Elemente ab Index 3 abrufen.

Um alle Elemente vor einem bestimmten Index abzurufen, können wir das start-Argument weglassen. Beispielsweise wird a1[:4] alle Elemente von Index 0 bis Index 4 extrahieren, wobei das Element an Index 4 ausgeschlossen ist.

Neben der Extraktion eines Slices aus einem Array können wir auch ein neues Slice aus einem bestehenden Slice extrahieren. Die Operation ist dieselbe wie bei Arrays. Das folgende ist ein einfaches Beispiel:

package main

import "fmt"

func main() {
    // Define an integer array with a length of 10
    a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    // Create the initial slice s1
    var s1 []int
    s1 = a[1:7]
    fmt.Printf("Slice s1: %d\tLength: %d\tCapacity: %d\n", s1, len(s1), cap(s1))

    // Extract a new slice s2 from the initial slice s1
    s2 := s1[2:4]
    fmt.Printf("Slice s1: %d\tLength: %d\tCapacity: %d\n", s2, len(s2), cap(s2))
}

Die Ausgabe ist wie folgt:

Slice s1: [1 2 3 4 5 6]	Length: 6	Capacity: 9
Slice s1: [3 4]	Length: 2	Capacity: 7

In diesem Programm haben wir das Slice s1 durch Kürzung des Arrays a erhalten. Der Bereich von s1 geht von Index 1 bis Index 7. Mit := haben wir ein neues Slice s2 aus s1 extrahiert. Da die Kürzung von s1 zusammenhängend ist, wird auch das neue Slice s2 zusammenhängend sein.

Wir bemerken, dass die Kapazität eines Slices sich ändert, wenn wir es kürzen. Die Regeln lauten wie folgt:

Wenn wir ein Slice mit einer Kapazität von c kürzen, beträgt die Länge von s[i:j] j - i, und die Kapazität beträgt c - i.

Für s1 ist das zugrunde liegende Array a, und die Kapazität von a[1:7] beträgt 9 (d. h. 10 - 1).

Für s2 ist das zugrunde liegende Array dasselbe wie für s1. Da der gekürzte Teil in der vorherigen Stufe eine Kapazität von 9 hat, beträgt die Kapazität von s1[2:4] 7 (d. h. 9 - 2).

Änderungen an Slice-Werten beeinflussen gleichzeitig die Werte der zugrunde liegenden Array-Elemente

Da das Slice keine Daten speichert, sondern nur auf ein Array verweist, wird die Änderung des Werts eines Slices auch gleichzeitig den Wert des zugrunde liegenden Arrays ändern. Lassen Sie uns dies demonstrieren:

package main

import "fmt"

func main() {
    // Define an integer array with a length of 10
    a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    s1 := a[2:5]
    s2 := a[:]

    fmt.Println("Before Modification: ")
    fmt.Println("Elements in array a: ", a)
    fmt.Println("Elements in array a: ", s1)
    fmt.Println("Elements in array a: ", s2)

    // Modify the value at index 2 of the slice s1 to 23
    s1[2] = 23

    fmt.Println("After Modification: ")
    fmt.Println("Elements in array a: ", a)
    fmt.Println("Elements in array a: ", s1)
    fmt.Println("Elements in array a: ", s2)
}

Die Ausgabe ist wie folgt:

Before Modification:
Elements in array a:  [0 1 2 3 4 5 6 7 8 9]
Elements in array a:  [2 3 4]
Elements in array a:  [0 1 2 3 4 5 6 7 8 9]
After Modification:
Elements in array a:  [0 1 23 3 4 5 6 7 8 9]
Elements in array a:  [2 3 23]
Elements in array a:  [0 1 23 3 4 5 6 7 8 9]

In diesem Programm verweisen sowohl das Slice s1 als auch s2 auf das Array a. Wenn das Slice s1 den Wert an Index 2 auf 23 ändert, werden auch die Werte des Arrays a und des Slices s2 aktualisiert.

Wir können sehen, dass der Wert an Index 2 des Arrays auf 23 geändert wird, was dazu führt, dass der Wert an Index 4 des Slices s2 geändert wird.

Dies kann in der Programmentwicklung schwer zu debuggende Fehler verursachen. Daher sollten wir in der täglichen Programmierung versuchen, es so weit wie möglich zu vermeiden, dass mehrere Slices auf dasselbe zugrunde liegende Array verweisen.

Anhängen von Elementen an ein Slice

In diesem Abschnitt werden wir die append-Funktion einführen, die zum Hinzufügen von Elementen zu einem Slice verwendet wird. Die Syntax lautet wie folgt:

func append(slice []Type, elems...Type) []Type

Das erste Argument ist das Slice slice, und die verbleibenden Argumente sind die Elemente, die dem Slice hinzugefügt werden sollen. Das []Type am Ende gibt an, dass die append-Funktion ein neues Slice mit demselben Datentyp wie slice zurückgibt.

Das elems nach ... bedeutet, dass es sich um einen variablen Parameter handelt, was bedeutet, dass ein oder mehrere Parameter eingegeben werden können.

Hier ist ein Beispiel für die Verwendung der append-Funktion:

package main

import "fmt"

func main() {
    // Define an integer array with a length of 10
    a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    s1 := a[1:7]
    fmt.Printf("Initial s1 value: %d\tLength: %d\tCapacity: %d\n", s1, len(s1), cap(s1))

    s1 = append(s1, 12)
    fmt.Printf("Modified s1 value: %d\tLength: %d\tCapacity: %d\n", s1, len(s1), cap(s1))

    s1 = append(s1, 14, 14)
    fmt.Printf("Modified s1 value: %d\tLength: %d\tCapacity: %d\n", s1, len(s1), cap(s1))
}

Die Ausgabe ist wie folgt:

Initial s1 value: [1 2 3 4 5 6]	Length: 6	Capacity: 9
Modified s1 value: [1 2 3 4 5 6 12]	Length: 7	Capacity: 9
Modified s1 value: [1 2 3 4 5 6 12 14 14]	Length: 9	Capacity: 9

In diesem Programm erstellen wir zunächst das Slice s1 durch Kürzung des Arrays a. Der Bereich von s1 geht von Index 1 bis Index 7. Wir verwenden den :=-Operator, um den Rückgabewert von append an s1 zuzuweisen. Wenn wir ein Element mit append hinzufügen, wird sich die Kapazität des Slices ändern. Die Kapazität von s1 wird verdoppelt, wenn die Anzahl der Elemente die Kapazität überschreitet.

Die Regeln für die Erweiterung von Slices lauten wie folgt:

  • Wenn das zugrunde liegende Array eines Slices neue Elemente aufnehmen kann, ändert sich die Kapazität des Slices nicht.
  • Wenn das zugrunde liegende Array eines Slices keine neuen Elemente aufnehmen kann, erstellt Go ein größeres Array, kopiert die Werte des ursprünglichen Slices in das neue Array und fügt dann den angehängten Wert dem neuen Array hinzu.

Hinweis: Die Erweiterung von Slices ist nicht immer eine Verdopplung; es hängt von der Größe der Elemente, der Anzahl der erweiterten Elemente und der Hardware des Computers u. a. Faktoren ab. Weitere Informationen finden Sie im fortgeschrittenen Abschnitt über Slices.

Löschen von Elementen in einem Slice

Go bietet keine Schlüsselwörter oder Funktionen zum Löschen von Elementen aus einem Slice, aber wir können die Array-Kürzung verwenden, um dieselbe Funktionalität oder eine noch leistungsfähigere Fähigkeit zu erreichen.

Der folgende Code löscht das Element an Index 5 im Slice s und weist es einem neuen Slice s1 zu:

package main

import "fmt"

func main() {
    s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    // Print the slice `s` before deleting
    fmt.Println(s)

    s1 := append(s[:5], s[6:]...)
    // Print the slices `s` and `s1` after deleting the element at index 5
    fmt.Printf("%d\n%d\n", s, s1)
}

Die Ausgabe ist wie folgt:

[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 6 7 8 9 9]
[0 1 2 3 4 6 7 8 9]

Der Schlüssel zum Löschen eines bestimmten Index liegt in der append-Anweisung. Sie hängt die Elemente nach Index 6 an die Elemente vor Index 5 an.

Diese Operation entspricht einer Vorüberschreibung. Beispielsweise wird Element 5 mit Element 6 überschrieben, Element 6 mit Element 7 usw., bis Element 9, das dem neuen Slice hinzugefügt wird. Daher ist der Wert des zugrunde liegenden Arrays an diesem Punkt [0 1 2 3 4 6 7 8 9 9].

An diesem Punkt können wir, wenn wir die Länge und die Kapazität von s1 überprüfen, sehen, dass die Länge 9 beträgt, aber die Kapazität 10. Dies liegt daran, dass s1 eine Referenz auf die Kürzung von s ist. Sie teilen sich dasselbe zugrunde liegende Array.

package main

import "fmt"

func main() {
    s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println(s)

    s1 := append(s[:5], s[6:]...)
    // Check the length and capacity of s1
    fmt.Println(len(s1), cap(s1))
    fmt.Printf("\n%d\n%d\n\n", s, s1)

    // Modify the slice s
    s[3] = 22
    fmt.Printf("%d\n%d\n", s, s1)
}

Die Ausgabe ist wie folgt:

[0 1 2 3 4 5 6 7 8 9]
9 10

[0 1 2 3 4 6 7 8 9 9]
[0 1 2 3 4 6 7 8 9]

[0 1 2 22 4 6 7 8 9 9]
[0 1 2 22 4 6 7 8 9]

Übung

Neben dem Löschen von Elementen an bestimmten Positionen können wir auch einen bestimmten Bereich von Elementen aus einem Slice mithilfe der Kürzung löschen. Die Operation ist dieselbe wie beim Löschen eines Elements an einem bestimmten Index. Lassen Sie uns eine kleine Übung machen.

Erstellen Sie eine Datei slice1.go. Erstellen Sie ein Slice a und initialisieren Sie es wie folgt. Verwenden Sie dann die Kürzung, um ein weiteres Slice s zu erstellen, das keine Elemente größer als 3 oder kleiner als 7 enthält. Geben Sie schließlich das neue Slice aus.

a := []int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}

Ausgabe:

[9 8 7 3 2 1 0]
  • Hinweis: Achten Sie auf den Startindex des Slices (denken Sie daran, dass der Index bei 0 beginnt).
  • Anforderungen: Die Datei slice1.go muss im Verzeichnis ~/project platziert werden.
✨ Lösung prüfen und üben

Erweitern von Slices

Ein Slice hat zwei Attribute: len und cap. len repräsentiert die Anzahl der aktuell im Slice enthaltenen Elemente, während cap die maximale Anzahl der Elemente angibt, die das Slice aufnehmen kann.

Was passiert, wenn die Anzahl der dem Slice hinzugefügten Elemente seine Kapazität überschreitet? Lassen Sie uns es gemeinsam herausfinden:

package main

import "fmt"

func main() {
    s1 := make([]int, 3)
    s2 := make([]int, 3, 5)
    fmt.Println("Before Append in s1:", s1, "Length:", len(s1), "Capacity:", cap(s1))
    fmt.Println("Before Append in s2:", s2, "Length:", len(s2), "Capacity:", cap(s2))

    s1 = append(s1, 12)
    s2 = append(s2, 22)
    fmt.Println("After Append in s1:", s1, "Length:", len(s1), "Capacity:", cap(s1))
    fmt.Println("After Append in s2:", s2, "Length:", len(s2), "Capacity:", cap(s2))
}

Um das Programm auszuführen, führen Sie den folgenden Befehl aus:

go run slice.go

Die Ausgabe ist wie folgt:

Before Append in s1: [0 0 0] Length: 3 Capacity: 3
Before Append in s2: [0 0 0] Length: 3 Capacity: 5
After Append in s1: [0 0 0 12] Length: 4 Capacity: 6
After Append in s2: [0 0 0 22] Length: 4 Capacity: 5

Wie wir in diesem Programm sehen können, wird die Kapazität eines Slices automatisch erhöht, wenn wir Elemente hinzufügen und die Anzahl der Elemente die ursprüngliche Kapazität überschreitet.

Die Regeln für die Erweiterung eines Slices lauten wie folgt:

  • Wenn das zugrunde liegende Array eines Slices neue Elemente aufnehmen kann, ändert sich die Kapazität des Slices nicht.
  • Wenn das zugrunde liegende Array eines Slices keine neuen Elemente aufnehmen kann, erstellt Go ein größeres Array, kopiert die Werte aus dem ursprünglichen Slice in das neue Array und fügt dann die angehängten Werte dem neuen Array hinzu.

Hinweis: Die Kapazität eines Slices wird nicht immer verdoppelt; es hängt von der Größe der Elemente, der Anzahl der Elemente und der Hardware des Computers ab. Weitere Informationen finden Sie im fortgeschrittenen Abschnitt über Slices.

Kopieren von Slices

Wir können die copy-Funktion verwenden, um ein Slice in ein anderes zu kopieren. Die Syntax lautet wie folgt:

func copy(dst, src []Type) int

dst ist das Ziel-Slice, src ist das Quell-Slice, und das abschließende int gibt die Anzahl der kopierten Elemente an, die das Minimum von len(dst) und len(src) ist.

Hinweis: Die copy-Funktion fügt keine Elemente hinzu.

Hier ist ein Beispiel:

package main

import "fmt"

func main() {
    s1 := []int{0, 1, 2, 3}
    s2 := []int{8, 9}

    s3 := []int{0, 1, 2, 3}
    s4 := []int{8, 9}

    // Copy s1 to s2
    n1 := copy(s2, s1)
    // Copy s4 to s3
    n2 := copy(s3, s4)

    fmt.Println(n1, s1, s2)
    fmt.Println(n2, s3, s4)
}

Um das Programm auszuführen, führen Sie den folgenden Befehl aus:

go run slice.go

Die Ausgabe ist wie folgt:

2 [0 1 2 3] [0 1]
2 [8 9 2 3] [8 9]

In diesem Programm haben wir die Werte des Slices s1 in das Slice s2 und die Werte des Slices s4 in das Slice s3 kopiert. Die copy-Funktion gibt die Anzahl der kopierten Elemente zurück.

Wir bemerken, dass die Werte von s1 und s2 sowie die Werte von s3 und s4 übereinstimmen. Die erste copy-Funktion kopiert s1[0, 1, 2, 3] in s2[8, 9]. Da die minimale Länge von s1 und s2 2 ist, werden 2 Werte kopiert. Das Ziel-Slice s2 wird zu [0, 1] geändert.

Die zweite copy-Funktion kopiert s4[8, 9] in s3[0, 1, 2, 3]. Da die minimale Länge von s3 und s4 2 ist, werden 2 Werte kopiert. Dadurch wird s3 zu [8, 9, 2, 3] geändert.

Durchlaufen von Slices

Das Durchlaufen eines Slices ähnelt dem Durchlaufen eines Arrays. Alle Methoden zum Durchlaufen von Arrays können auch für Slices verwendet werden.

Übung

In dieser Übung werden wir unser Verständnis des Durchlaufens von Slices und Arrays testen und festigen.

Erstellen Sie eine Datei slice2.go. Deklarieren Sie ein Array a1 und ein Slice s1 und initialisieren Sie sie wie folgt. Iterieren Sie dann über die Elemente im Array a1 und im Slice s1 und geben Sie ihre Indizes und Werte aus.

a1 := [5]int{1, 2, 3, 9, 7}
s1 := []int{1, 8, 12, 1, 3}

Ausgabe:

Element a1 at index 0 is 1
Element a1 at index 1 is 2
Element a1 at index 2 is 3
Element a1 at index 3 is 9
Element a1 at index 4 is 7
Element s1 at index 0 is 1
Element s1 at index 1 is 8
Element s1 at index 2 is 12
Element s1 at index 3 is 1
Element s1 at index 4 is 3
  • Anforderungen: Die Datei slice2.go muss im Verzeichnis ~/project platziert werden.
  • Hinweis: Sie können das 'range'-Format oder das Index-Format verwenden, um durch die Elemente zu iterieren.
✨ Lösung prüfen und üben

Zusammenfassung

In diesem Abschnitt haben wir uns mit Slices und ihrer Verwendung beschäftigt. Im Vergleich zu Arrays sind Slices flexibler und vielseitiger. Wenn wir mit mehreren Slices arbeiten, müssen wir vorsichtig sein, um unerwartete Slice-Operationen zu vermeiden.