Maison  >  Article  >  développement back-end  >  Quand la fonction append() de Go crée-t-elle une nouvelle tranche ?

Quand la fonction append() de Go crée-t-elle une nouvelle tranche ?

Barbara Streisand
Barbara Streisandoriginal
2024-10-30 12:31:50965parcourir

When Does Go's append() Function Create a New Slice?

Quand Append() de Go crée-t-il une nouvelle tranche ?

La fonction append() du langage Go est utilisée pour étendre les tranches existantes. Selon la documentation de l'API intégrée, append() peut créer une nouvelle tranche avec une capacité plus grande lorsque la capacité de la tranche d'origine est insuffisante.

Cependant, ce comportement soulève des questions lorsqu'il est considéré dans le contexte des algorithmes récursifs. En particulier, l'algorithme suivant génère des combinaisons d'un alphabet :

<code class="go">package main

import (
    "fmt"
)

func AddOption(c chan []bool, combo []bool, length int) {
    if length == 0 {
        fmt.Println(combo, "!")
        c <- combo
        return
    }
    var newCombo []bool
    for _, ch := range []bool{true, false} {
        newCombo = append(combo, ch)
        AddOption(c, newCombo, length-1)
    }
}

func main() {
    c := make(chan []bool)
    go func(c chan []bool) {
        defer close(c)
        AddOption(c, []bool{}, 4)
    }(c)
    for combination := range c {
        fmt.Println(combination)
    }
}</code>

Dans ce code, la fonction AddOption ajoute récursivement des membres d'un alphabet à une tranche, envoyant le résultat sur un canal. Cependant, les observations montrent que les tranches envoyées dans le canal sont modifiées après avoir été envoyées.

La contradiction survient car la documentation suggère que append() devrait renvoyer une nouvelle tranche, mais le comportement dans le code implique le contraire. Cet article examine le mécanisme sous-jacent de append() et clarifie quand il crée une nouvelle tranche.

Comprendre la représentation des tranches

Pour comprendre le comportement de append(), il est crucial pour comprendre la représentation interne d’une tranche. Une tranche, malgré son apparence autonome, n’est pas une structure de données autonome. Au lieu de cela, il s'agit d'un descripteur qui pointe vers un tableau sous-jacent des données réelles.

Le descripteur de tranche comprend trois composants :

  1. Longueur : le nombre d'éléments actuellement dans la tranche .
  2. Capacité : le nombre d'éléments que le tableau sous-jacent peut contenir.
  3. Pointeur de données : un pointeur vers le premier élément du tableau sous-jacent.

Valeur de retour d'Append()

Lorsque append() est utilisé, la fonction crée un nouveau descripteur de tranche avec sa propre longueur, sa capacité et son propre pointeur de données. Ceci est cohérent avec la documentation, qui indique que append() "réaffecte[s] et copie[s] dans un nouveau bloc de tableau."

Cependant, cela soulève une autre question : pourquoi les modifications apportées à la tranche Le descripteur après son envoi dans le canal persiste dans la tranche d'origine ?

Comprendre la référence partagée

La clé pour résoudre ce problème est de comprendre la nature du pointeur de données dans le descripteur de tranche. Ce pointeur ne crée pas de copie des données sous-jacentes ; il pointe vers les mêmes données que la tranche d'origine.

Par conséquent, lorsque append() est utilisé sur une tranche, bien qu'il crée un nouveau descripteur de tranche, le pointeur de données reste le même. Cela signifie que toute modification apportée aux éléments de l'un ou l'autre descripteur de tranche sera reflétée dans les deux tranches, quel que soit l'endroit où les modifications se produisent.

Démonstration

Pour illustrer ce concept , considérez l'extrait de code suivant :

<code class="go">package main

import "fmt"

func main() {
    s := make([]int, 0, 5)
    s = append(s, []int{1, 2, 3, 4}...)

    a := append(s, 5)
    fmt.Println(a)

    b := append(s, 6)
    fmt.Println(b)
    fmt.Println(a)
}</code>

Lorsque ce code est exécuté, il affiche :

<code class="go">package main

import (
    "fmt"
)

func AddOption(c chan []bool, combo []bool, length int) {
    if length == 0 {
        fmt.Println(combo, "!")
        c <- combo
        return
    }
    var newCombo []bool
    for _, ch := range []bool{true, false} {
        newCombo = append(combo, ch)
        AddOption(c, newCombo, length-1)
    }
}

func main() {
    c := make(chan []bool)
    go func(c chan []bool) {
        defer close(c)
        AddOption(c, []bool{}, 4)
    }(c)
    for combination := range c {
        fmt.Println(combination)
    }
}</code>

Dans cet exemple, les deux tranches a et b partagent initialement les mêmes données sous-jacentes. Cependant, lorsqu'une nouvelle valeur est attribuée à b, un nouveau tableau de données sous-jacent est créé et le pointeur de données de b est mis à jour pour pointer vers lui. Puisqu'a fait toujours référence au même pointeur de données, il continue d'accéder à l'ancien tableau de données.

En modifiant la capacité des tranches, il peut être démontré que les tranches partagent effectivement les données sous-jacentes lorsque la capacité est suffisante pour éviter la réallocation.

Conclusion

La fonction append() de Go alloue un nouveau descripteur de tranche mais conserve une référence au tableau de données d'origine. Cela signifie que les modifications apportées aux tranches au sein d'un algorithme récursif seront visibles dans toutes les tranches partageant la même référence de données. Comprendre ce comportement est crucial pour travailler efficacement avec des tranches dans Go.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn