Warum bleibt die Scheibe a
gleich? 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]
Antworten:
In Ihrem Beispiel erhält das
slice
Argument derTest
Funktion eine Kopie der Variablena
im 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 derslice
Variablen enthaltenen Slice-Deskriptor mehrmals hintereinander, dies hat jedoch keine Auswirkungen auf den Aufrufer und seinea
Variable.Innerhalb der
Test
Funktion ordnet der ersteappend
das Hintergrundarray unter derslice
Variablen neu zu, kopiert seinen ursprünglichen Inhalt, hängt100
ihn an und genau das beobachten Sie. Beim Verlassen von verlässtTest
dieslice
Variable den Gültigkeitsbereich und das (neue) zugrunde liegende Array, auf das Slice verweist.Wenn Sie möchten, dass Sie sich so
Test
verhaltenappend
, müssen Sie das neue Slice von ihm zurückgeben - genau wie dies derappend
Fall ist - und von den Anrufern verlangenTest
, dass sie es auf die gleiche Weise verwenden, wie sie es verwenden würdenappend
: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
append
von Hand implementieren , nachdem Sie erklärt haben, wie Slices intern funktionieren. Dann lies das .quelle
append
InTest
ordnet keinen Speicher neu zu, da die ursprüngliche Zuordnung des Array-Backing-Slicea
immer noch zu einem einzelnen zusätzlichen Element passen kann. Mit anderen Worten, während dieses Programm geschrieben wird, sind der Rückgabewert vonTest(a)
unda
verschiedene Slice-Header mit unterschiedlichen Längen, aber sie zeigen auf genau dasselbe zugrunde liegende Array. Das Druckenfmt.Println(a[:cap(a)]
als letzte Zeile dermain
Funktion macht dies deutlich.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/QKRnl5CTcM1func 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.append
Funktion 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 denappend
Stapelrahmen des Stapels und dann außerhalb davon). Wennappend
nun 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, wennappend
eine Neuzuweisung erforderlich ist, und nicht anders (das zugrunde liegende Array hatte nicht verwendeten Speicherplatz). Das ist der Kern des "Problems".func Test(slice []int)
erhält eine Kopie des Slice-Werts vona
. Und es zeigt auf dasselbe Array wie dasa
Zeigen. Ich kann meinen obigen Kommentar nicht bearbeiten, und wenn ich ihn entferne, wird diese Konversation verwirrt.Typische
append
Verwendung ista = append(a, x)
weil je nach Größe und Kapazität der Eingabe
append
entweder 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, za := []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]
quelle
_ = append(a[:3], 5)
jetzt kompiliert werdenDamit 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) }
quelle
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!
quelle
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.Versuchen Sie dies, was meiner Meinung nach deutlich macht. Das zugrunde liegende Array wird geändert, unser Slice jedoch nicht. Es werden
print
nurlen()
Zeichen gedruckt . Durch ein anderes Slicecap()
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] }
quelle
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 }
quelle
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]
quelle
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]
Weitere Details finden Sie unter https://allegro.tech/2017/07/golang-slices-gotcha.html
quelle
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:
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
slice
haben, keinen Einfluss auf das in dermain
Funktion 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 Sieappend(slice, 1000)
beim Ausführen beim Erweitern des zugrunde liegenden Arrays auch die Schnittinformationen von geändertslice
, die in IhrerTest()
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,
a
indem Siea[:cap(a)]
über das geänderte zugrunde liegende Array gesprochen haben, das durch dieTest()
Funktion geändert wurde . Wie hier angegeben:quelle
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 }
quelle