Erfahren Sie mehr über Go Slices

Dieser Artikel ist eine Übersetzung und Ergänzung von Arrays, Slices (und Strings): Die Mechanik von'append '.

Introduction

Eines der häufigsten Merkmale prozeduraler Programmiersprachen ist das Konzept von Arrays.

Obwohl Arrays einfach aussehen, gibt es noch viele Fragen zum Mechanismus zum Hinzufügen von Arrays zur Sprache.

Die Antworten auf diese Fragen wirken sich sogar darauf aus, ob das Array nur eine Funktion der Sprache oder ein zentraler Bestandteil seines Designs ist.

In der frühen Entwicklung von Go dauerte es ungefähr ein Jahr, um das richtige Design mit den Antworten auf diese Fragen zu finden.

Der schwierigste Teil war die Einführung von Scheiben.

Slices basieren auf Arrays mit fester Größe und bieten eine flexible und erweiterbare Datenstruktur.

Erstmalige Go-Programmierer stolpern jedoch häufig, wenn es um das Slicing-Verhalten geht. Dies ist wahrscheinlich auf Erfahrungen in anderen Sprachen zurückzuführen.

In diesem Beitrag werde ich versuchen, die Verwirrung loszuwerden.

Dazu gehen wir alle Komponenten durch, die erforderlich sind, um zu erklären, wie die integrierte Funktion "Anhängen" funktioniert und warum sie so funktioniert.

Arrays

Arrays sind eine wichtige Komponente von Go, aber eine wichtige Komponente, die sich hinter Slices verbirgt.

Bevor wir zu den interessanten, kraftvollen und herausragenden Ideen von Slices übergehen, müssen wir die Sequenzen kurz erläutern.

Da das Array eine feste Länge hat, wird es in Go-Programmen selten gesehen.

Bei der Erklärung

var buffer [256]byte

Diese Deklaration deklariert eine Variable namens "buffer", die 256 Bytes enthält.

Der Typ von "Puffer" ist "[256] Byte" und der Typ enthält die Größe. Wenn Sie 512 Bytes behalten möchten, ist dies "[512] Byte".

Die dem Array zugeordneten Daten sind buchstäblich ein Array von Elementen. Im Allgemeinen sieht "Puffer" im Speicher folgendermaßen aus:

buffer: byte byte byte ... 256 times ... byte byte byte

Diese Variable hat also nur 256 Datenbytes.

Auf jedes Element kann über einen vertrauten Index zugegriffen werden. (Puffer \ [0 ], Puffer \ [1 ], ..., Puffer \ [255 ])

Das Programm stürzt ab, wenn Sie versuchen, auf den Index außerhalb des Arrays zuzugreifen.

Arrays, Slices und einige andere Datentypen verfügen über eine integrierte Funktion namens "len", die die Anzahl der Elemente zurückgibt.

Für Arrays ist klar, was len zurückgibt. In diesem Beispiel gibt "len (buffer)" einen festen Wert von 256 zurück.

Eine der Verwendungsmöglichkeiten von Arrays ist die korrekte Darstellung von Transformationsmatrizen. Der häufigste Zweck in Go besteht jedoch darin, den Speicher für Slices beizubehalten.

Slices: Über Slice-Header

Slices sind eine häufig verwendete Datenstruktur, aber um sie gut zu verwenden, müssen Sie genau verstehen, was sie sind und was sie tun.

Ein Slice ist eine Datenstruktur, die einen kontinuierlichen Abschnitt eines Arrays in sich beschreibt, und das Slice selbst ist kein Array. Slices repräsentieren einen Teil des Arrays.

Durch Schneiden dieses Arrays unter der Annahme der Variablen "buffer", die das Array im vorherigen Abschnitt darstellt, können Sie ein Slice erstellen, das die Elemente 100-150 (genauer 100-149) beschreibt.

var slice []byte = buffer[100:150]

Im obigen Beispiel haben wir eine explizite Variablendeklaration verwendet.

Die Variable "Slice" ist vom Typ "[] Byte", ausgesprochen "Byte Type Slice", und wird aus dem Array "Buffer" durch Schneiden der Elemente 100 bis 150 initialisiert.

In einer idiomatischeren Syntax können Sie die Beschreibung des einzustellenden Typs weglassen.

var slice = buffer[100:150]

Sie können innerhalb der Funktion auch die folgende kurze Deklaration abgeben:

slice := buffer[100:150]

Was genau hat diese Slice-Variable?

Ich werde hier nicht über alles sprechen, aber ich stelle mir Slices vorerst als kleine Datenstrukturen mit zwei Elementen vor: Länge und Zeiger auf die Elemente des Arrays.

Sie können sich also vorstellen, dass die Slices hinter den Kulissen so aufgebaut sind:

type sliceHeader struct {
    Length        int
    ZerothElement *byte
}

slice := sliceHeader{
    Length:        50,
    ZerothElement: &buffer[100],
}

Dies ist natürlich nur ein Beispiel, nicht die tatsächliche Struktur.

In diesem Codebeispiel ist die SliceHeader-Struktur für den Programmierer nicht sichtbar, und die Art des Zeigers auf das Array hängt vom Typ des Elements im Array ab, das eine allgemeine Vorstellung vom Mechanismus vermittelt.

Bisher haben Sie die Slice-Operation für ein Array verwendet, aber Sie können das Slice auch wie folgt schneiden:

slice2 := slice[5:10]

Nach wie vor wird durch diese Operation ein neues Slice erstellt.

Verwenden Sie in diesem Fall die Elemente 5-9 (\ [5,9 ]) des ursprünglichen Slice.

Es repräsentiert die Elemente 105-109 des ursprünglichen Arrays.

Die sliceHeader Struktur, auf der die Variableslice2 basiert, sieht folgendermaßen aus:

slice2 := sliceHeader{
    Length:        5,
    ZerothElement: &buffer[105],
}

Beachten Sie, dass sich dieser Header auf denselben "Puffer" wie das ursprüngliche "Slice" als Referenz auf das Array bezieht.

Sie können es auch wieder in Scheiben schneiden. Schneiden Sie das Slice aus und speichern Sie das Ergebnis wie folgt in der ursprünglichen Slice-Struktur:

slice = slice[5:10]

Die Struktur "SliceHeader" der Variablen "Slice" sieht genauso aus wie für die Variable "Slice2".

Dies ist häufig der Fall, wenn Sie einen Teil eines Slice abschneiden möchten. Im folgenden Beispiel werden das erste und das letzte Element des Slice abgeschnitten.

slice = slice[1:len(slice)-1]

Wir hören oft erfahrene Go-Programmierer über "Slice-Header" sprechen.

Weil der Slice-Header tatsächlich das ist, was in der Slice-Variablen gespeichert ist.

Wenn Sie beispielsweise eine Funktion aufrufen, die ein Slice als Argument verwendet, z. B. "bytes.IndexRune", wird der Slice-Header an die Funktion übergeben.

slashPos := bytes.IndexRune(slice, '/')

In diesem Funktionsaufruf wird beispielsweise der Slice-Header tatsächlich an die Funktion übergeben.

Der Slice-Header enthält ein weiteres Datenelement. Wir werden später darüber sprechen, aber lassen Sie uns zuerst sehen, was das Vorhandensein eines Slice-Headers bedeutet, wenn Sie ein Programm schreiben, das Slices verwendet.

Wenn Slice als Argument verwendet wird

Es ist wichtig zu verstehen, dass das Slice selbst ein Wert ist, auch wenn das Slice Zeiger enthält.

Die Struktur des Slice ist ein ** Strukturwert **, der den Zeiger und die Länge enthält. Es ist kein Zeiger auf eine Struktur.

Das ist wichtig. Beim Aufrufen von "IndexRune" im vorherigen Beispiel wurde eine Kopie des Slice-Headers übergeben. Sein Verhalten hat wichtige Auswirkungen.

Betrachten Sie diese einfache Funktion.

func AddOneToEachElement(slice []byte) {
    for i := range slice {
        slice[i]++
    }
}

Wie der Name schon sagt, handelt es sich um eine Funktion, die Slices durchläuft und die Elemente des Slice bei jeder Iteration inkrementiert.

Versuchen Sie das folgende Programm.

func main() {
    slice := buffer[10:20]
    for i := 0; i < len(slice); i++ {
        slice[i] = byte(i)
    }
    fmt.Println("before", slice)
    AddOneToEachElement(slice)
    fmt.Println("after", slice)
}
before [0 1 2 3 4 5 6 7 8 9]
after [1 2 3 4 5 6 7 8 9 10]

Program exited.

Der Slice-Header selbst wurde als Wert übergeben (dh kopiert und übergeben). Da der Header jedoch Zeiger auf die Elemente des Arrays enthält, werden der ursprüngliche Slice-Header und eine Kopie des Headers an die Funktion übergeben Beide beziehen sich auf dasselbe Array.

Wenn die Funktion zurückkehrt, kann das geänderte Element durch die ursprüngliche Slice-Variable angezeigt werden.

Wie dieses Beispiel zeigt, sind die Funktionsargumente tatsächlich Kopien.

func SubtractOneFromLength(slice []byte) []byte {
    slice = slice[0 : len(slice)-1]
    return slice
}

func main() {
    fmt.Println("Before: len(slice) =", len(slice))
    newSlice := SubtractOneFromLength(slice)
    fmt.Println("After:  len(slice) =", len(slice))
    fmt.Println("After:  len(newSlice) =", len(newSlice))
}
Before: len(slice) = 50
After:  len(slice) = 50
After:  len(newSlice) = 49

Program exited.

In diesem Beispiel sehen Sie, dass der Inhalt des Slice-Arguments mit der Funktion geändert werden kann, der Header selbst jedoch nicht.

Der Funktion wird anstelle des ursprünglichen Headers eine Kopie des Slice-Headers übergeben, sodass die in der Slice-Variablen (der Length-Eigenschaft des ursprünglichen Headers) gespeicherte Länge durch Aufrufen der Funktion nicht geändert wird.

Wenn Sie eine Funktion erstellen, die den Header ändert, müssen Sie daher das geänderte Slice wie in diesem Beispiel als Rückgabewert zurückgeben.

Die ursprüngliche Slice-Variable hat sich nicht geändert, aber das in der Rückgabe zurückgegebene Slice hat eine neue Länge und wird in newSlice gespeichert.

Slice-Zeiger als Methodenempfänger

Eine andere Möglichkeit, wie die Funktion den Slice-Header stört, besteht darin, einen Zeiger auf das Slice zu übergeben

func PtrSubtractOneFromLength(slicePtr *[]byte) {
    slice := *slicePtr
    *slicePtr = slice[0 : len(slice)-1]
}

func main() {
    fmt.Println("Before: len(slice) =", len(slice))
    PtrSubtractOneFromLength(&slice)
    fmt.Println("After:  len(slice) =", len(slice))
}
Before: len(slice) = 50
After:  len(slice) = 49

Program exited.

Das obige Beispiel sieht nicht sehr schlau aus.

Es gibt einen häufigen Fall, in dem ein Zeiger auf ein Slice angezeigt wird.

Beispielsweise ist es üblich, einen Zeiger als Empfänger für Methoden zu verwenden, die Slice-Änderungen vornehmen.

Angenommen, Sie benötigen eine Methode, um Slices am letzten / aus dem Dateipfad abzuschneiden.

Zum Beispiel sollte "dir1 / dir2 / dir3" "dir1 / dir2" sein.

Eine Methode, die dies erfüllt, kann wie folgt geschrieben werden:

type path []byte

func (p *path) TruncateAtFinalSlash() {
    i := bytes.LastIndex(*p, []byte("/"))
    if i >= 0 {
        *p = (*p)[0:i]
    }
}

func main() {
    pathName := path("/usr/bin/tso") // Conversion from string to path.
    pathName.TruncateAtFinalSlash()
    fmt.Printf("%s\n", pathName)
}

Wenn Sie es ausführen, werden Sie sehen, dass es funktioniert. Dieses Mal wurde das Slice in der aufrufenden Methode geändert.

Wenn Sie andererseits eine Methode schreiben möchten, die ASCII-Zeichen im Pfad großschreibt (andere als Englisch ignoriert), kann der Empfänger der Methode ein Wert sein, da das Referenzziel des Arrays gleich bleibt und nur der Inhalt geändert wird.

type path []byte

func (p path) ToUpper() {
    for i, b := range p {
        if 'a' <= b && b <= 'z' {
            p[i] = b + 'A' - 'a'
        }
    }
}

func main() {
    pathName := path("/usr/bin/tso")
    pathName.ToUpper()
    fmt.Printf("%s\n", pathName)
}

Hier verwendet die ToUpper-Methode den Index des Arrays und zwei Variablen mit den entsprechenden Elementen in der Syntax für den Bereich.

Auf diese Weise können Sie die Häufigkeit reduzieren, mit der Sie "p [i]" nacheinander schreiben.

Kapazität: Schnittkapazität

Schauen wir uns die folgende Funktion an, die das als Argument übergebene Slice "Slice" vom Typ "int" durch Hinzufügen von "element" erweitert.

func Extend(slice []int, element int) []int {
    n := len(slice)
    slice = slice[0 : n+1]
    slice[n] = element
    return slice
}

Lass uns rennen

func main() {
    var iBuffer [10]int
    slice := iBuffer[0:0]
    for i := 0; i < 20; i++ {
        slice = Extend(slice, i)
        fmt.Println(slice)
    }
}
[0]
[0 1]
[0 1 2]
[0 1 2 3]
[0 1 2 3 4]
[0 1 2 3 4 5]
[0 1 2 3 4 5 6]
[0 1 2 3 4 5 6 7]
[0 1 2 3 4 5 6 7 8]
[0 1 2 3 4 5 6 7 8 9]
panic: runtime error: slice bounds out of range [:11] with capacity 10

goroutine 1 [running]:
main.Extend(...)
	/tmp/sandbox021597325/prog.go:16
main.main()
	/tmp/sandbox021597325/prog.go:25 +0x105

Program exited: status 2.

Die Scheiben dehnen sich immer mehr aus, aber sie hören in der Mitte auf.

Beschreibt die dritte Komponente des Slice-Headers "Kapazität".

Zusätzlich zum Array-Zeiger und zur Länge speichert der Slice-Header auch seine Kapazität ("Kapazität").

type sliceHeader struct {
    Length        int
    Capacity      int
    ZerothElement *byte
}

Das Feld "Kapazität" enthält den Speicherplatz, den das zugrunde liegende Array tatsächlich hat.

Dies ist der Maximalwert, den "Länge" erreichen kann. Wenn Sie versuchen, ein Slice über seine Kapazität hinaus zu vergrößern, geraten Sie in Panik, da Sie das Array-Limit überschreiten, dh Sie haben Zugriff außerhalb des Arrays.

Der Header des Slice, das von Slice: = iBuffer [0: 0] erstellt wurde

slice := sliceHeader{
    Length:        0,
    Capacity:      10,
    ZerothElement: &iBuffer[0],
}

Das Feld "Kapazität" entspricht der Länge des zugrunde liegenden Arrays abzüglich des Index des entsprechenden Arrays (in diesem Fall 0) für das erste Element des Slice.

Wenn Sie die Kapazität eines Slice überprüfen möchten, verwenden Sie die integrierte Funktion cap.

if cap(slice) == len(slice) {
    fmt.Println("slice is full!")
}

Make

Was ist, wenn Sie eine Scheibe über ihre Kapazität hinaus wachsen lassen möchten?

kann nicht! Per Definition ist "Kapazität" die Expansionsgrenze.

Sie können jedoch gleichwertige Ergebnisse erzielen, indem Sie ein neues Array zuweisen, die Daten kopieren, den Inhalt der Slices ändern und auf das neue Array verweisen.

Beginnen wir mit der Aufgabe. Sie können die neue integrierte Funktion verwenden, um ein größeres Array zuzuweisen und das Ergebnis zu schneiden. Es ist jedoch einfacher, stattdessen die integrierte Funktion "make" zu verwenden.

Ordnen Sie ein neues Array zu und erstellen Sie einen Slice-Header, der auf einen Schlag darauf verweist.

Die Funktion make akzeptiert drei Argumente: den Slice-Typ, seine Anfangslänge und seine Kapazität (die Länge des Arrays, das die Slice-Daten enthält).

Wie Sie anhand des folgenden Beispiels sehen können, erstellt dieser Aufruf einen Slice mit der Länge 10, und das Back-Array bietet Platz für 5 Größen (15-10).

    slice := make([]int, 10, 15)
    fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
len: 10, cap: 15

Program exited.
    slice := make([]int, 10, 15)
    fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
    newSlice := make([]int, len(slice), 2*cap(slice))
    for i := range slice {
        newSlice[i] = slice[i]
    }
    slice = newSlice
    fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
len: 10, cap: 15
len: 10, cap: 30

Program exited.

Das Slice nach dem Ausführen dieses Codes hat viel mehr Platz zum Erweitern (5-> 20).

Beim Erstellen von Slices ist es üblich, dass sie dieselbe Länge und Kapazität haben. Die eingebaute Funktion make hat eine Abkürzung für diesen allgemeinen Fall.

gophers := make([]Gopher, 10) // = make([]Gopher, 10, 10)

Wenn Sie keine Kapazität angeben, entspricht der Standardwert der Länge. Sie können ihn also wie oben weglassen und beide auf denselben Wert setzen.

Copy

Als ich im vorherigen Abschnitt die Kapazität des Slice verdoppelt habe, habe ich eine Schleife erstellt, die die alten Daten in das neue Slice kopiert.

Go hat eine eingebaute Funktion "Kopieren", um dies zu vereinfachen. Seine Argumente sind zwei Slices, die die Daten vom rechten zum linken Argument kopieren.

Das Folgende ist ein Beispiel für das Umschreiben, um "Kopie" zu verwenden.

    newSlice := make([]int, len(slice), 2*cap(slice))
    copy(newSlice, slice)

Die Funktion copy erstellt eine intelligente Kopie. Kopieren Sie so viel wie möglich und achten Sie dabei auf die Länge beider Argumente.

Das heißt, die Anzahl der zu kopierenden Elemente ist das Minimum der Länge der beiden Slices.

Das spart ein bisschen Schreiben. Außerdem gibt copy einen ganzzahligen Wert zurück, der die Anzahl der kopierten Elemente darstellt. Es lohnt sich jedoch nicht immer, ihn zu überprüfen.

Die Funktion "Kopieren" wird auch dann ordnungsgemäß ausgeführt, wenn Quelle und Ziel doppelt vorhanden sind.

Das heißt, es kann verwendet werden, um den Inhalt in einem einzelnen Slice zu verschieben. So verwenden Sie eine Kopie, um einen Wert in die Mitte eines Slice einzufügen:

// Insert inserts the value into the slice at the specified index,
// which must be in range.
// The slice must have room for the new element.
func Insert(slice []int, index, value int) []int {
    // Grow the slice by one element.
    slice = slice[0 : len(slice)+1]
    // Use copy to move the upper part of the slice out of the way and open a hole.
    copy(slice[index+1:], slice[index:])
    // Store the new value.
    slice[index] = value
    // Return the result.
    return slice
}

Im obigen Beispiel ist zu beachten, dass sich die Länge geändert hat. Sie müssen das geänderte Slice daher immer als Rückgabewert zurückgeben.

Es muss auch Kapazität> Länge sein, wenn ein Element hinzugefügt wird.

Append

Vor einigen Abschnitten habe ich eine Funktion "Erweitern" erstellt, die ein Slice nur um ein Element erweitert.

Es gab jedoch einen Fehler, da die Funktion abstürzen würde, wenn die Slice-Größe zu klein wäre. (Das Beispiel "Einfügen" hat das gleiche Problem.)

Nachdem wir nun die Elemente haben, um dies zu beheben, erstellen wir eine solide Implementierung von "Extend", die das Integer-Slice erweitert.

func Extend(slice []int, element int) []int {
    n := len(slice)
    if n == cap(slice) {
        //Das Slice ist voll und muss erweitert werden
        //Die neue Kapazität beträgt 2x+1(+1 ist x=Für wenn 0)
        newSlice := make([]int, len(slice), 2*len(slice)+1)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0 : n+1]
    slice[n] = element
    return slice
}

Auch hier ändert sich das referenzierte Array vollständig, sodass Sie das Slice als Rückgabewert zurückgeben müssen.

Verwenden Sie die Funktion "Erweitern", um das Verhalten zu erweitern und zu überprüfen, wenn das Slice voll ist.

    slice := make([]int, 0, 5)
    for i := 0; i < 10; i++ {
        slice = Extend(slice, i)
        fmt.Printf("len=%d cap=%d slice=%v\n", len(slice), cap(slice), slice)
        fmt.Println("address of 0th element:", &slice[0])
    }
len=1 cap=5 slice=[0]
address of 0th element: 0xc000078030
len=2 cap=5 slice=[0 1]
address of 0th element: 0xc000078030
len=3 cap=5 slice=[0 1 2]
address of 0th element: 0xc000078030
len=4 cap=5 slice=[0 1 2 3]
address of 0th element: 0xc000078030
len=5 cap=5 slice=[0 1 2 3 4]
address of 0th element: 0xc000078030
len=6 cap=11 slice=[0 1 2 3 4 5]
address of 0th element: 0xc00005e060
len=7 cap=11 slice=[0 1 2 3 4 5 6]
address of 0th element: 0xc00005e060
len=8 cap=11 slice=[0 1 2 3 4 5 6 7]
address of 0th element: 0xc00005e060
len=9 cap=11 slice=[0 1 2 3 4 5 6 7 8]
address of 0th element: 0xc00005e060
len=10 cap=11 slice=[0 1 2 3 4 5 6 7 8 9]
address of 0th element: 0xc00005e060

Program exited.

Beachten Sie die Neuzuweisung, wenn das Array der Anfangsgröße 5 voll ist.

Wenn ein neues Array zugewiesen wird, ändern sich sowohl die "Kapazität" als auch die Adresse des 0. Elements.

Sie können die robuste Funktion "Erweitern" als Leitfaden verwenden, um noch bessere Funktionen zu erstellen, mit denen Sie ein Slice mit mehreren Elementen erweitern können. Nutzen Sie dazu die Fähigkeit von Go, die Liste der Funktionsargumente beim Aufruf der Funktion als Konvertierung in Slices zu behandeln, nämlich die variable Argumentfunktion von Go.

Nennen wir die Funktion "Anhängen". In der ersten Version kann Extend wiederholt aufgerufen werden, wodurch der Mechanismus variabler Argumentfunktionen verdeutlicht wird. Die Signatur der Funktion "Anhängen" lautet:

func Append(slice []int, items ...int) []int

Diese Signatur gibt an, dass ein Slice gefolgt von null oder mehr int-Typen als Argumente verwendet wird.

// Append appends the items to the slice.
// First version: just loop calling Extend.
func Append(slice []int, items ...int) []int {
    for _, item := range items {
        slice = Extend(slice, item)
    }
    return slice
}

Es ist zu beachten, dass die Schleife "for range" die Elemente der Argumente "items" behandelt, die in jeder Schleife als "[] int" behandelt werden. Es ist auch zu beachten, dass der Index unnötiger Slices in diesem Fall mit der Kennung "_" verworfen wird.

    slice := []int{0, 1, 2, 3, 4}
    fmt.Println(slice)
    slice = Append(slice, 5, 6, 7, 8)
    fmt.Println(slice)

Eine neue Technik, die in diesem Beispiel angezeigt wird, besteht darin, ein Slice zu initialisieren, indem ein zusammengesetztes Literal geschrieben wird, das aus dem Slice-Typ gefolgt von den Elementen in den mittleren Klammern besteht.

    slice := []int{0, 1, 2, 3, 4}

Noch interessanter an der Funktion "Anhängen" ist, dass Sie nicht nur Elemente hinzufügen können, sondern auch das zweite Slice selbst als Argument hinzufügen können, indem Sie das Slice mit der Notation "..." auf dem Aufrufer "zerlegen". Es ist ein Punkt, der getan werden kann.

    slice1 := []int{0, 1, 2, 3, 4}
    slice2 := []int{55, 66, 77}
    fmt.Println(slice1)
    slice1 = Append(slice1, slice2...) // The '...' is essential!
    fmt.Println(slice1)

Im vorherigen "Anhängen" war es doppelt so lang wie das ursprüngliche Slice, sodass die Anzahl der hinzuzufügenden "Elemente" dazu führen konnte, dass das Array mehrmals neu zugewiesen wurde, aber das "Anhängen" im folgenden Beispiel `Es ist rationalisiert, mit nur einer Zuordnung zu leben.

// Append appends the elements to the slice.
// Efficient version.
func Append(slice []int, elements ...int) []int {
    n := len(slice)
    total := len(slice) + len(elements)
    if total > cap(slice) {
        // Reallocate. Grow to 1.5 times the new size, so we can still grow.
        newSize := total*3/2 + 1
        newSlice := make([]int, total, newSize)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[:total]
    copy(slice[n:], elements)
    return slice
}

Beachten Sie, dass copy zweimal aufgerufen wird, einmal beim Kopieren der Slice-Daten in den neu zugewiesenen Speicher und beim Kopieren des Elements, das am Ende des Arrays hinzugefügt werden soll.

Als integrierte Funktion anhängen

Schließlich haben wir ein Stück, um den Grund für das Design der eingebauten Funktion "Anhängen" zu erläutern.

Die eingebaute Funktion "Anhängen" wird genauso effizient ausgeführt wie das obige Beispiel "Anhängen", funktioniert jedoch mit jedem Slice-Typ.

Die Schwäche von Go besteht darin, dass zur Laufzeit generische Typoperationen bereitgestellt werden müssen.

Es kann sich eines Tages ändern, aber für den Moment verfügt Go über eine integrierte generische Funktion zum Anhängen, um das Schneiden zu vereinfachen.

Es funktioniert genauso wie ein Slice von [] int, aber es funktioniert mit jedem Slice-Typ.

Beachten Sie, dass der Slice-Header ständig mit einem Aufruf zum Anhängen aktualisiert wird. Sie müssen daher das nach dem Aufruf zurückgegebene Slice speichern.

Tatsächlich kann der Compiler append nicht aufrufen, ohne das Ergebnis zu speichern.

Unten finden Sie ein Beispiel für die Verwendung von "Anhängen". Es kann interessant sein, es tatsächlich auszuführen oder zu bearbeiten.

    // Create a couple of starter slices.
    slice := []int{1, 2, 3}
    slice2 := []int{55, 66, 77}
    fmt.Println("Start slice: ", slice)
    fmt.Println("Start slice2:", slice2)

    // Add an item to a slice.
    slice = append(slice, 4)
    fmt.Println("Add one item:", slice)

    // Add one slice to another.
    slice = append(slice, slice2...)
    fmt.Println("Add one slice:", slice)

    // Make a copy of a slice (of int).
    slice3 := append([]int(nil), slice...)
    fmt.Println("Copy a slice:", slice3)

    // Copy a slice to the end of itself.
    fmt.Println("Before append to self:", slice)
    slice = append(slice, slice...)
    fmt.Println("After append to self:", slice)
Start slice:  [1 2 3]
Start slice2: [55 66 77]
Add one item: [1 2 3 4]
Add one slice: [1 2 3 4 55 66 77]
Copy a slice: [1 2 3 4 55 66 77]
Before append to self: [1 2 3 4 55 66 77]
After append to self: [1 2 3 4 55 66 77 1 2 3 4 55 66 77]

Program exited.

Insbesondere das endgültige Anhängen im obigen Beispiel wäre ein gutes Beispiel dafür, warum das Slice-Design so einfach ist.

Die "Slice Tricks" -Wiki-Seite, die von einer Community von Freiwilligen erstellt wurde, enthält Beispiele für verschiedene Funktionen und Prozesse im Zusammenhang mit "Anhängen", "Kopieren" und anderen Slices. Eingeführt.

Nil

Abgesehen davon gibt mir das neue Wissen, das ich dieses Mal gelernt habe, eine Vorstellung davon, wie das Null-Slice tatsächlich dargestellt wird.

Dies ist natürlich der Nullwert des Slice-Headers.

sliceHeader{
    Length:        0,
    Capacity:      0,
    ZerothElement: nil,
}

Oder

sliceHeader{}

Der Schlüssel ist, dass der Elementzeiger "Null" ist.

array[0:0]

Ein auf diese Weise erstelltes Array hat eine Länge und Kapazität von Null, aber der Zeiger ist kein Nullpunkt und wird nicht als Null-Slice behandelt.

Offensichtlich können leere Scheiben groß sein (vorausgesetzt, die Kapazität ist nicht Null).

Das Slice "nil" verfügt jedoch nicht über ein Array, in dem die Werte gespeichert werden können, und kann nicht erweitert werden, um die Elemente aufzunehmen.

Ein "Null" -Slice entspricht jedoch funktional einem Slice mit der Länge Null, auch wenn es auf nichts zeigt. Sie können also "Anhängen" verwenden, um ein Array zuzuweisen und Elemente hinzuzufügen. Ich werde.

Strings

Lassen Sie uns hier kurz die Go-Zeichenfolge aus der Perspektive des Schneidens erklären.

In Wirklichkeit ist der String-Mechanismus sehr einfach. Die Zeichenfolge ist ein schreibgeschütztes Byte-Slice mit zusätzlicher Syntaxunterstützung aus der Sprache.

Sie sind schreibgeschützt, benötigen also keinen Speicherplatz (dh sie können nicht erweitert werden), aber ansonsten können sie fast immer wie schreibgeschützte Byte-Slices behandelt werden.

Zu Beginn können Sie einzelne Bytes indizieren, um darauf zuzugreifen.

slash := "/usr/ken"[0] // yields the byte value '/'.

Es ist auch möglich, den String zu schneiden und den Teilstring zu extrahieren.

usr := "/usr/ken"[0:4] // yields the string "/usr"

Es sollte jedem klar sein, der bisher gelesen hat, was beim Schneiden einer Schnur hinter den Kulissen passiert.

Es ist auch möglich, eine Zeichenfolge zu generieren, indem ein Slice vom Bytetyp wie folgt konvertiert wird.

str := string(slice)

Das Gegenteil ist ebenfalls möglich.

slice := []byte(usr)

Das Byte-Array, auf dem die Zeichenfolge basiert, wird nicht in der Tabelle angezeigt. Das heißt, es gibt keine Möglichkeit, auf den Inhalt zuzugreifen, außer über eine Zeichenfolge.

Wenn Sie eine dieser Konvertierungen durchführen, müssen Sie eine Kopie des Byte-Arrays erstellen.

Natürlich erledigt Go dies für Sie, sodass Sie es nicht selbst tun müssen. Nach einer dieser Konvertierungen wirken sich Änderungen am zugrunde liegenden Array von Byte-Slices nicht auf die entsprechende Zeichenfolge aus. (Zum Beispiel wirkt sich das Ändern von "Slice" nach "str: = string (Slice)" nicht auf "str" aus.)

Das wichtige Ergebnis beim Entwerfen des Strings wie ein Slice ist, dass die Erstellung von Teilzeichenfolgen sehr effizient ist.

Sie müssen lediglich einen String-Header mit zwei Wörtern erstellen. Die Zeichenfolge ist schreibgeschützt, sodass die ursprüngliche Zeichenfolge und die Teilzeichenfolgen, die sich aus der Slice-Operation ergeben, sicher dasselbe Array gemeinsam nutzen können.

Historischer Hinweis: In den frühesten Implementierungen von Zeichenfolgen wurden beim Erstellen von Teilzeichenfolgen immer neue Byte-Arrays zugewiesen. Wenn jedoch Slices zur Sprache hinzugefügt wurden, handelte es sich um effiziente Zeichen. Bereitstellung eines Modells für die Spaltenverarbeitung. Infolgedessen haben einige Benchmarks erhebliche Beschleunigungen erfahren.

Natürlich gibt es noch viel mehr Zeichenfolgen, und Sie können mehr darüber in einem anderen Blog-Beitrag (https://blog.golang.org/strings) lesen.

Zusammenfassung

Um zu verstehen, wie Slices funktionieren, ist es hilfreich zu verstehen, wie Slices implementiert werden.

Es gibt eine kleine Datenstruktur, die als Slice-Header bezeichnet wird. Dies ist das Element, das der Slice-Variablen zugeordnet ist. Dieser Header bezieht sich auf einen Teil des individuell zugewiesenen Arrays.

Wenn Sie einen Slice-Wert übergeben, wird der Header kopiert, aber das Array, auf das er zeigt, wird immer gemeinsam genutzt.

Wenn Sie verstehen, wie sie funktionieren, sind Slices nicht nur einfach zu verwenden, sondern auch leistungsfähige und ausdrucksstarke Datenstrukturen, insbesondere mithilfe der integrierten Funktionen "Kopieren" und "Anhängen".

Wenn Sie weitere Artikel über Go schreiben möchten, bitte!

Referenz

Recommended Posts

Erfahren Sie mehr über Go Slices
Über Python-Slices
Erfahren Sie mehr über das Programmieren
Über Go-Funktionen
Über Go Interface
Hinweis zu Zeigern (Go)
Informationen zur Go-Steuerungssyntax
Über Go-Module (vgo)
[Golang] Über den Sprachkanal Go
Über den Grundtyp von Go
Erfahren Sie mehr über die Protokollierung mit dem Python-Protokollierungsmodul ①
[Golang] Über Go-Sprache Produzent und Konsument
Weitere Informationen zu AWS-Webanwendungen ohne Server