Golang hängt einen Gegenstand an eine Scheibe an


77

Warum bleibt die Scheibe agleich? Hat append()eine neue Scheibe erzeugen?

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]

Dieser Code erklärt, was passiert: https://play.golang.org/p/eJYq65jeqwn . func Test (Slice [] int) erhält eine Kopie des Slice-Werts von a. Und es zeigt auf dasselbe Array wie ein Zeiger.
Gihanchanuka

Antworten:


59

In Ihrem Beispiel erhält das sliceArgument der TestFunktion eine Kopie der Variablen aim Bereich des Aufrufers.

Da eine Slice-Variable einen "Slice-Deskriptor" enthält, der lediglich auf ein zugrunde liegendes Array verweist , Teständern Sie in Ihrer Funktion den in der sliceVariablen enthaltenen Slice-Deskriptor mehrmals hintereinander, dies hat jedoch keine Auswirkungen auf den Aufrufer und seine aVariable.

Innerhalb der TestFunktion ordnet der erste appenddas Hintergrundarray unter der sliceVariablen neu zu, kopiert seinen ursprünglichen Inhalt, hängt 100ihn an und genau das beobachten Sie. Beim Verlassen von verlässt Testdie sliceVariable den Gültigkeitsbereich und das (neue) zugrunde liegende Array, auf das Slice verweist.

Wenn Sie möchten, dass Sie sich so Testverhalten append, müssen Sie das neue Slice von ihm zurückgeben - genau wie dies der appendFall ist - und von den Anrufern verlangen Test, dass sie es auf die gleiche Weise verwenden, wie sie es verwenden würden append:

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

    fmt.Println(slice)

    return slice
}

a = Test(a)

Bitte lesen Sie diesen Artikel sorgfältig durch, da er Ihnen im Grunde zeigt, wie Sie appendvon Hand implementieren , nachdem Sie erklärt haben, wie Slices intern funktionieren. Dann lies das .


6
Ich denke tatsächlich, dass diese Beschreibung auf subtile Weise falsch ist. Die Antwort von @ doun unten ist tatsächlich eine korrektere Darstellung dessen, was intern vor sich geht: Das appendIn Testordnet keinen Speicher neu zu, da die ursprüngliche Zuordnung des Array-Backing-Slice aimmer noch zu einem einzelnen zusätzlichen Element passen kann. Mit anderen Worten, während dieses Programm geschrieben wird, sind der Rückgabewert von Test(a)und averschiedene Slice-Header mit unterschiedlichen Längen, aber sie zeigen auf genau dasselbe zugrunde liegende Array. Das Drucken fmt.Println(a[:cap(a)]als letzte Zeile der mainFunktion macht dies deutlich.
Jeff Lee

Diese Aussage ist falsch; "In Ihrem Beispiel erhält das Slice-Argument der Testfunktion eine Kopie der Variablen a im Bereich des Aufrufers." Wie in der Verwendung von Go-Slices erwähnt , erhält func einen Zeiger. Versuchen Sie zu ändern slice = append(slice, 100)-> slice[1] = 13. Sie werden [0 13 2 3 4 5 6]zweimal gedruckt . @kostix kannst du das erklären?. Refer - play.golang.org/p/QKRnl5CTcM1
gihanchanuka

@gihanchanuka, im Fall von func Test(slice []int)erhält die Funktion keinen "Zeiger". In Go wird alles immer als Wert übergeben. Nur einige Typen haben zufällig eine Zeigerdarstellung oder enthalten Zeiger. Slices in Go sind von letzterer Art: Jeder Slice-Wert ist eine Struktur aus drei Feldern, von denen eines tatsächlich ein Zeiger auf den Speicherblock ist, der die Elemente des Slice enthält.
Kostix

@gihanchanuka, jetzt nimmt die eingebaute appendFunktion einen Slice-Wert und gibt einen Slice-Wert zurück. In beiden Fällen ist es diese Drei-Felder-Struktur, die bei der Eingabe und bei der Ausgabe kopiert wird (in den appendStapelrahmen des Stapels und dann außerhalb davon). Wenn appendnun der Speicher des Slice neu zugewiesen werden muss, um Platz für das Anhängen der Daten zu schaffen, enthält der zurückgegebene Slice-Wert einen Zeiger, der sich von dem im eingegebenen Slice-Wert unterscheidet. Dies geschieht nur, wenn appendeine Neuzuweisung erforderlich ist, und nicht anders (das zugrunde liegende Array hatte nicht verwendeten Speicherplatz). Das ist der Kern des "Problems".
Kostix

@kostix Ich habe es "+1", Danke! Dieser Code erklärt, was passiert: https://play.golang.org/p/zJT7CW-pfp8 . func Test(slice []int)erhält eine Kopie des Slice-Werts von a. Und es zeigt auf dasselbe Array wie das aZeigen. Ich kann meinen obigen Kommentar nicht bearbeiten, und wenn ich ihn entferne, wird diese Konversation verwirrt.
Gihanchanuka

34

Typische appendVerwendung ist

a = append(a, x)

weil je nach Größe und Kapazität der Eingabe appendentweder das Argument direkt geändert oder eine Kopie des Arguments mit einem zusätzlichen Eintrag zurückgegeben werden kann. Die Verwendung eines zuvor angehängten 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]

1
Danke, Larsmans, ich habe Code geändert. "make" würde eine ausreichende Kapazität geben. Das Ergebnis ist das gleiche und ich bin immer noch verwirrt.
Pole_Zhang

Dies muss _ = append(a[:3], 5)jetzt kompiliert werden
y3sh

6

Damit Ihr Code funktioniert, ohne dass Sie das Slice 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)
}

5

HINWEIS: Durch Anhängen wird ein neues Slice generiert, wenn die Obergrenze nicht ausreicht. Die Antwort von @ kostix ist richtig, oder Sie können das Slice- Argument per Zeiger übergeben!


1
Sie haben Recht mit Zeigern, aber ich habe sie entschieden nicht erwähnt, da die Slices hauptsächlich erfunden wurden, um die Programmierer vom Umgang mit Zeigern auf Arrays zu befreien. In einer Referenzimplementierung (von Go) enthält eine Slice-Variable einen Zeiger und zwei Ganzzahlen. Das Kopieren ist daher billig und daher slice = append(slice, a, b, c)idiomatisch. Es wird keine Slice-Variable per Zeiger übergeben und "an Ort und Stelle" geändert, sodass der Aufrufer die sieht Veränderung.
Kostix

1
@kostix Du hast recht, der Zweck der Codes sollte explizit sein. Aber ich denke, in der ganzen Geschichte geht es nur darum, einen Wert zu übergeben, der einen Zeiger speichert, und einen Zeiger zu übergeben, der auf einen Zeiger zeigt. Wenn wir die Referenz ändern, können beide funktionieren, aber wenn wir die Referenz ersetzen, verliert die erste die Wirkung. Der Programmierer sollte wissen, was er tut.
Gizak

5

Versuchen Sie dies, was meiner Meinung nach deutlich macht. Das zugrunde liegende Array wird geändert, unser Slice jedoch nicht. Es werden printnur len()Zeichen gedruckt . Durch ein anderes Slice cap()können Sie das geänderte Array sehen:

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]
}

Also sind 'a' und 'a [: cap (a)]' unterschiedliche Scheiben?
Pole_Zhang

2
Ja, wenn Sie den Code ausführen, werden Sie das herausfinden. weil Kappe (a) während des Test (a) Anrufs
geändert wird

3

Erläuterung (Inline-Kommentare lesen):


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 1")
    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 2")
}

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 1
[0 1 2 3 4 5 13 100 200] 9 16  << Test 2
[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
}

2

Go geht dabei schlanker und fauler vor. Das zugrunde liegende Array wird so lange geändert, bis die Kapazität eines Slice erreicht ist.

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

Die Ausgabe des Beispiels über den 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]

Durch Ändern von Slice b wird auch a geändert, da sie auf dasselbe zugrunde liegende Array verweisen.

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 Slice b. Hier wird eine neue Kopie erstellt, wenn 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]

Überprüfen der Slices a und b zeigen auf verschiedene zugrunde liegende Arrays nach der Kapazitätsüberlastung im vorherigen Schritt.

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
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]

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Weitere Details finden Sie unter https://allegro.tech/2017/07/golang-slices-gotcha.html


1

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

Wie im Go Doc angegeben:

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

Slices sind nur Wrapper-Werte um Arrays. Dies bedeutet, dass sie Informationen darüber enthalten, wie sie ein zugrunde liegendes Array aufteilen, in dem sie einen Datensatz speichern. Daher wird ein Slice standardmäßig, wenn es an eine andere Methode übergeben wird, tatsächlich als Wert anstelle von Referenz / Zeiger übergeben, obwohl immer noch dasselbe zugrunde liegende Array verwendet wird. Normalerweise werden Arrays auch als Wert übergeben, daher gehe ich davon aus, dass ein Slice auf ein zugrunde liegendes Array zeigt, anstatt es als Wert zu speichern. In Bezug auf Ihre Frage hat Ihr Slice beim Ausführen die folgende Funktion übergeben:

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. Dies bedeutet, dass die Änderungen, die Sie an dem vorgenommen slicehaben, keinen Einfluss auf das in der mainFunktion hatten. Es ist das Slice selbst, das die Informationen darüber speichert, wie viel von einem Array es schneidet und der Öffentlichkeit zugänglich macht. Daher haben Sie append(slice, 1000)beim Ausführen beim Erweitern des zugrunde liegenden Arrays auch die Schnittinformationen von geändert slice, die in Ihrer Test()Funktion privat gehalten wurden .

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

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

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

Der Grund dafür ist, dass Sie erweitert haben, aindem Sie a[:cap(a)]über das geänderte zugrunde liegende Array gesprochen haben, das durch die Test()Funktion geändert wurde . Wie hier angegeben:

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


0

Hier ist eine schöne Implementierung von Append für Slices. Ich denke, es ist ähnlich wie unter der Haube:

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
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.