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 :
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!