web-dev-qa-db-de.com

Golang hängt ein Element an eine Scheibe an

Warum bleibt das Slice a gleich? Erzeugt append() ein neues Slice?

package main

import (
    "fmt"
)

var a = make([]int, 7, 8)

func Test(slice []int) {
    slice = append(slice, 100)
    fmt.Println(slice)
}

func main() {
    for i := 0; i < 7; i++ {
        a[i] = i
    }

    Test(a)
    fmt.Println(a)
}

Ausgabe:

[0 1 2 3 4 5 6 100]
[0 1 2 3 4 5 6]
52
Pole_Zhang

In Ihrem Beispiel erhält das Argument slice der Funktion Testeine Kopie der Variablen a im Geltungsbereich des Aufrufers.

Da eine Slice-Variable einen "Slice-Descriptor" enthält, der lediglich ein untergeordnetes Array referenzen enthält, ändern Sie in Ihrer Test-Funktion den Slice-Descriptor, der in der Variable slice enthalten ist, mehrmals hintereinander. Dies wirkt sich jedoch nicht auf den Aufrufer aus und seine Variable a.

Innerhalb der Test-Funktion ordnet das erste append das Backing-Array unter der Variablen slice neu zu, kopiert seinen ursprünglichen Inhalt und fügt ihm 100 hinzu. Dies ist das, was Sie beobachten. Beim Beenden von Test verlässt die Variable slice den Gültigkeitsbereich und das (neue) zugrunde liegende Array, auf das die Slice verweist.

Wenn Sie möchten, dass Test sich wie append verhält, Sie müssen das neue Slice daraus zurückgeben - so wie append dies tut - und die Aufrufer von Test so verwenden, wie sie append verwenden würden:

func Test(slice []int) []int {
    slice = append(slice, 100)

    fmt.Println(slice)

    return slice
}

a = Test(a)

Bitte lesen Sie diesen Artikel gründlich, da er Ihnen im Grunde zeigt, wie Sie append von Hand implementieren, nachdem Sie erklärt haben, wie Slices intern arbeiten. Dann lesen Sie das .

40
kostix

Typische append-Verwendung ist

a = append(a, x)

weil append sein Argument entweder vor Ort ändern kann oder eine Kopie des Arguments mit einem zusätzlichen Eintrag zurückgibt, abhängig von der Größe und Kapazität der Eingabe. Die Verwendung eines zuvor angefügten Slice kann zu unerwarteten Ergebnissen führen, z.

a := []int{1,2,3}
a = append(a, 4)
fmt.Println(a)
append(a[:3], 5)
fmt.Println(a)

kann drucken

[1 2 3 4]
[1 2 3 5]
22
Fred Foo

Versuchen Sie dies, was meiner Meinung nach klar ist. Das zugrunde liegende Array wird geändert, aber unser Slice ist nicht, print druckt nur len() Zeichen, und ein anderes Slice für cap().

func main() {

  for i := 0; i < 7; i++ {
      a[i] = i
  }

  Test(a)

  fmt.Println(a) // prints [0..6]
  fmt.Println(a[:cap(a)] // prints [0..6,100]
}
5
doun

Beachten Sie, dass append ein neues Slice erzeugt, wenn cap nicht ausreicht. @kostix's Antwort ist korrekt, oder Sie können slice argument per Zeiger übergeben!

5
Gizak

Go geht dabei schlanker und fauler vor. Es hält Ändern des gleichen zugrunde liegenden Arrays, bis die Kapazität eines Slice .__ ist. erreicht.

Ref: http://criticalindirection.com/2016/02/17/slice-with-a-pinch-of-salt/

Die Ausgabe des Beispiels aus dem Link erklärt das Verhalten von Slices in Go.

Slice erstellen a.

Slice a len=7 cap=7 [0 0 0 0 0 0 0]

Slice b bezieht sich auf die 2, 3, 4-Indizes in Slice a. Daher beträgt die Kapazität 5 (= 7-2).

b := a[2:5]
Slice b len=3 cap=5 [0 0 0]

Beim Ändern von Slice b wird auch a geändert, da sie auf dasselbe darunterliegende Array zeigen.

b[0] = 9
Slice a len=7 cap=7 [0 0 9 0 0 0 0]
Slice b len=3 cap=5 [9 0 0]

1 an Scheibe anhängen b. Überschreibt a.

Slice a len=7 cap=7 [0 0 9 0 0 1 0]
Slice b len=4 cap=5 [9 0 0 1]

Anhängen von 2 an Scheibe b. Überschreibt a.

Slice a len=7 cap=7 [0 0 9 0 0 1 2]
Slice b len=5 cap=5 [9 0 0 1 2]

Anhängen von 3 an Scheibe b. Hier wird eine neue Kopie erstellt, da die Kapazität überlastet ist.

Slice a len=7 cap=7 [0 0 9 0 0 1 2]
Slice b len=6 cap=12 [9 0 0 1 2 3]

Verifizierung der Slices a und b zeigen nach der Kapazitätsüberlastung im vorherigen Schritt auf untergeordnete Arrays.

b[1] = 8
Slice a len=7 cap=7 [0 0 9 0 0 1 2]
Slice b len=6 cap=12 [9 8 0 1 2 3]
2
user31986

Ich finde die ursprüngliche Antwort nicht genau richtig. append() hat sowohl die Slices als auch das zugrunde liegende Array geändert, obwohl das zugrunde liegende Array geändert, aber von beiden Slices gemeinsam genutzt wird.

Wie vom Go-Doc festgelegt:

Ein Slice speichert keine Daten, es beschreibt lediglich einen Abschnitt eines zugrunde liegenden Arrays. (Verknüpfung)

Slices sind nur Wrapper-Werte für Arrays. Das heißt, sie enthalten Informationen darüber, wie sie ein darunter liegendes Array schneiden, mit dem sie einen Datensatz speichern. Daher wird ein Slice standardmäßig bei der Übergabe an eine andere Methode tatsächlich als Wert statt Referenz/Zeiger übergeben, obwohl sie immer noch dasselbe zugrunde liegende Array verwenden. Normalerweise werden Arrays auch als Wert übergeben. Ich gehe also von einem Slice-Punkt in einem darunter liegenden Array aus, anstatt ihn als Wert zu speichern. Bezüglich Ihrer Frage, wenn Sie das Slice ausführen, übergeben Sie es an die folgende Funktion:

func Test(slice []int) {
    slice = append(slice, 100)
    fmt.Println(slice)
}

sie haben tatsächlich eine Kopie Ihres Slice zusammen mit einem Zeiger auf dasselbe zugrunde liegende Array übergeben. Das heißt, die Änderungen, die Sie an der Variablen slice vorgenommen haben, hatten keine Auswirkungen auf die Änderung der Funktion main. Es ist das Slice selbst, das die Informationen darüber speichert, wie viel eines Arrays es schneidet und der Öffentlichkeit zugänglich macht. Wenn Sie append(slice, 1000) ausgeführt haben, während Sie das zugrunde liegende Array erweitert haben, haben Sie auch die Slicing-Informationen von slice geändert, die in Ihrer Test()-Funktion geheim gehalten wurden. 

Wenn Sie Ihren Code jedoch wie folgt geändert haben, hat es möglicherweise funktioniert:

func main() {
    for i := 0; i < 7; i++ {
        a[i] = i
    }

    Test(a)
    fmt.Println(a[:cap(a)])
}

Der Grund ist, dass Sie a erweitert haben, indem Sie a[:cap(a)] über das geänderte zugrunde liegende Array sagten, geändert durch Test()-Funktion. Wie hier angegeben:

Sie können die Länge eines Slices verlängern, indem Sie es erneut schneiden, vorausgesetzt, es verfügt über ausreichende Kapazität. (Verknüpfung)

1
Tarik
package main

import (
    "fmt"
)

func a() {
    x := []int{}
    x = append(x, 0)
    x = append(x, 1)  // commonTags := labelsToTags(app.Labels)
    y := append(x, 2) // Tags: append(commonTags, labelsToTags(d.Labels)...)
    z := append(x, 3) // Tags: append(commonTags, labelsToTags(d.Labels)...)
    fmt.Println(y, z)
}

func b() {
    x := []int{}
    x = append(x, 0)
    x = append(x, 1)
    x = append(x, 2)  // commonTags := labelsToTags(app.Labels)
    y := append(x, 3) // Tags: append(commonTags, labelsToTags(d.Labels)...)
    z := append(x, 4) // Tags: append(commonTags, labelsToTags(d.Labels)...)
    fmt.Println(y, z)
}

func main() {
    a()
    b()
}

First guess could be

[0, 1, 2] [0, 1, 3]
[0, 1, 2, 3] [0, 1, 2, 4]

but in fact it results in

[0, 1, 2] [0, 1, 3]
[0, 1, 2, 4] [0, 1, 2, 4]

 enter image description here

 enter image description here

Weitere Details siehe https://allegro.tech/2017/07/golang-slices-gotcha.html

1
wcc526

Damit Ihr Code funktioniert, ohne dass Sie das Segment von Test zurückgeben müssen, können Sie einen Zeiger wie folgt übergeben:

package main

import (
    "fmt"
)

var a = make([]int, 7, 8)

func Test(slice *[]int) {
    *slice = append(*slice, 100)

    fmt.Println(*slice)
}

func main() {

    for i := 0; i < 7; i++ {
        a[i] = i
    }

    Test(&a)

    fmt.Println(a)
}
1
David Brophy

Hier ist eine nette Implementierung von Append für Slices. Ich denke, es ähnelt dem, was sich unter der Haube abspielt:

package main

import "fmt"

func main() {
    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)
}

// Append ...
func Append(slice []int, items ...int) []int {
    for _, item := range items {
        slice = Extend(slice, item)
    }
    return slice
}

// Extend ...
func Extend(slice []int, element int) []int {
    n := len(slice)
    if n == cap(slice) {
        // Slice is full; must grow.
        // We double its size and add 1, so if the size is zero we still grow.
        newSlice := make([]int, len(slice), 2*len(slice)+1)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0 : n+1]
    slice[n] = element
    return slice
}
0
Nicky Feller

Erklärt für die Dummies/Anfänger;


package main

import (
    "fmt"
)

var a = make([]int, 7, 8)
// A slice is a descriptor of an array segment. 
// It consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment).
// The length is the number of elements referred to by the slice.
// The capacity is the number of elements in the underlying array (beginning at the element referred to by the slice pointer).
// |-> Refer to: https://blog.golang.org/go-slices-usage-and-internals -> "Slice internals" section

func Test(slice []int) {
    // slice receives a copy of slice `a` which point to the same array as slice `a`
    slice[6] = 10
    slice = append(slice, 100)
    // since `slice` capacity is 8 & length is 7, it can add 100 and make the length 8
    fmt.Println(slice, len(slice), cap(slice), " << Test")
    slice = append(slice, 200)
    // since `slice` capacity is 8 & length also 8, slice has to make a new slice 
    // - with double of size with point to new array (see Reference 1 below).
    // (I'm also confused, why not (n+1)*2)=20. But make a new slice of 16 capacity).
    slice[6] = 13 // make sure, it's a new slice :)
    fmt.Println(slice, len(slice), cap(slice), " << Test")
}

func main() {
    for i := 0; i < 7; i++ {
        a[i] = i
    }

    fmt.Println(a, len(a), cap(a))
    Test(a)
    fmt.Println(a, len(a), cap(a))
    fmt.Println(a[:cap(a)], len(a), cap(a))
    // fmt.Println(a[:cap(a)+1], len(a), cap(a)) -> this'll not work
}

Ausgabe:

[0 1 2 3 4 5 6] 7 8
[0 1 2 3 4 5 10 100] 8 8  << Test
[0 1 2 3 4 5 13 100 200] 9 16  << Test
[0 1 2 3 4 5 10] 7 8
[0 1 2 3 4 5 10 100] 7 8

Referenz 1: https://blog.golang.org/go-slices-usage-and-internals

func AppendByte(slice []byte, data ...byte) []byte {
    m := len(slice)
    n := m + len(data)
    if n > cap(slice) { // if necessary, reallocate
        // allocate double what's needed, for future growth.
        newSlice := make([]byte, (n+1)*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:n]
    copy(slice[m:n], data)
    return slice
}
0
gihanchanuka