web-dev-qa-db-fra.com

Golang ajoute un élément à une tranche

Pourquoi la tranche a reste-t-elle la même? append() génère-t-il une nouvelle tranche?

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

Sortie:

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

Dans votre exemple, l'argument slice de la fonction Test reçoit une copie de la variable a dans la portée de l'appelant.

Puisqu'une variable de tranche contient un "descripteur de tranche" qui ne fait que références un tableau sous-jacent, dans votre fonction Test, vous modifiez le descripteur de tranche conservé dans la variable slice plusieurs fois de suite, mais cela n'a aucune incidence sur l'appelant. a variable.

Dans la fonction Test, la première append réalloue le tableau de support sous la variable slice, copie son contenu d'origine, y ajoute 100 et c'est ce que vous observez. Lors de la sortie de Test, la variable slice sort de la portée, de même que le (nouveau) tableau sous-jacent qui coupe les références.

Si vous voulez que Test se comporte comme append, vous devez renvoyer la nouvelle tranche de celle-ci - comme le fait append - et obliger les appelants de Test à l'utiliser de la même manière qu'ils utiliseraient append:

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

    fmt.Println(slice)

    return slice
}

a = Test(a)

Veuillez lire cet article de manière approfondie car il vous montre en gros comment implémenter append manuellement, après avoir expliqué comment les tranches fonctionnent en interne. Puis lisez ceci .

40
kostix

L’utilisation typique de append est

a = append(a, x)

parce que append peut modifier son argument in-situ ou renvoyer une copie de son argument avec une entrée supplémentaire, selon la taille et la capacité de son entrée. L'utilisation d'une tranche précédemment ajoutée peut donner des résultats inattendus, par ex.

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

peut imprimer

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

Essayez ceci, ce qui, je pense, le rend clair. le tableau sous-jacent est modifié mais notre tranche n'est pas, print, imprime simplement len() caractères, par une autre tranche de la cap(), vous pouvez voir le tableau modifié:

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

AVIS que append génère une nouvelle tranche si cap n'est pas suffisant. La réponse de @ kostix est correcte, ou vous pouvez passer l'argument slice par pointeur!

5
Gizak

Go adopte une approche plus maigre et paresseuse. Il conserve modifier le même tableau sous-jacent jusqu'à ce que la capacité d'une tranche soit de atteint.

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

La sortie de l'exemple à partir du lien explique le comportement des tranches dans Go.

Créer une tranche a.

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

La tranche b désigne les 2, 3, 4 indices de la tranche a. La capacité est donc de 5 (= 7-2).

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

La modification de la tranche b modifie également a, puisqu'elles pointent sur le même tableau sous-jacent.

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]

Ajouter 1 à la tranche b. Écrase a.

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

Ajouter 2 à la tranche b. Écrase 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]

Ajouter 3 pour trancher b. Ici, une nouvelle copie est créée car la capacité est surchargée.

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]

La vérification des tranches a et b pointe vers différents tableaux sous-jacents après la surcharge de capacité de l'étape précédente.

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

Je pense que la réponse originale n'est pas tout à fait correcte. append() a modifié les tranches et le tableau sous-jacent, même si le tableau sous-jacent est modifié mais reste partagé par les deux tranches.

Comme spécifié par le Go Doc:

Une tranche ne stocke aucune donnée, elle décrit simplement une section d'un tableau sous-jacent. (Lien)

Les tranches ne sont que des valeurs d'encapsulation autour des tableaux, ce qui signifie qu'elles contiennent des informations sur la manière dont elles découpent un tableau sous-jacent qu'elles utilisent pour stocker un ensemble de données. Par conséquent, par défaut, une tranche, lorsqu'elle est transmise à une autre méthode, est réellement transmise par valeur, plutôt que par référence/pointeur, même si elle utilisera toujours le même tableau sous-jacent. Normalement, les tableaux sont également passés par valeur aussi, donc je suppose qu'une tranche pointe sur un tableau sous-jacent au lieu de le stocker en tant que valeur. En ce qui concerne votre question, lorsque vous exécutez passé votre tranche à la fonction suivante:

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

vous avez en fait passé une copie de votre tranche avec un pointeur sur le même tableau sous-jacent. Cela signifie que les modifications apportées à la variable slice n'ont pas eu d'incidence sur celle de la fonction main. C'est la tranche elle-même qui stocke les informations concernant la quantité de matrice découpée et exposée au public. Par conséquent, lorsque vous avez exécuté append(slice, 1000), tout en développant le tableau sous-jacent, vous avez également modifié les informations de découpage de slice également, qui sont restées privées dans votre fonction Test()

Toutefois, si vous avez modifié votre code comme suit, cela aurait peut-être fonctionné:

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

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

La raison en est que vous avez développé a en disant a[:cap(a)] sur son tableau sous-jacent modifié, modifié par la fonction Test(). Comme spécifié ici:

Vous pouvez augmenter la longueur d'une tranche en la découpant à nouveau, à condition que sa capacité soit suffisante. (Lien)

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

Plus de détails voir https://allegro.tech/2017/07/golang-slices-gotcha.html

1
wcc526

Pour que votre code fonctionne sans avoir à renvoyer la tranche de Test, vous pouvez passer un pointeur comme celui-ci:

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

Voici une belle implémentation de append pour les tranches. Je suppose que cela ressemble à ce qui se passe sous le capot:

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

Expliqué pour les Dummies/Beginners;


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
}

Sortie:

[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

Référence 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