
Slices (création, `len`, `cap`, `append`)
Maîtrisez les slices en Go : création via littéraux ou `make`, compréhension de `len` (longueur) et `cap` (capacité), et ajout dynamique d'éléments avec `append`.
Les séquences dynamiques par excellence : Introduction aux Slices
En Go, la structure de données la plus couramment utilisée pour gérer des séquences ou des listes d'éléments de même type est la slice. Contrairement aux tableaux (`array`) dont la taille est fixe et fait partie de leur type, les slices offrent une vue dynamique et flexible sur les éléments d'un tableau sous-jacent. Elles peuvent grandir et rétrécir, ce qui les rend beaucoup plus pratiques dans la plupart des cas d'utilisation.
Une slice ne stocke pas directement les données. En interne, elle est représentée par une petite structure contenant trois informations : un pointeur vers le premier élément du tableau sous-jacent concerné par la slice, la longueur (`length` ou `len`) de la slice (le nombre d'éléments qu'elle contient actuellement), et la capacité (`capacity` ou `cap`) de la slice (le nombre d'éléments disponibles dans le tableau sous-jacent à partir du début de la slice).
Comprendre cette structure interne (pointeur, longueur, capacité) est la clé pour maîtriser le fonctionnement des slices, notamment leur création, leur redimensionnement avec `append`, et la distinction entre longueur et capacité.
Créer des Slices : Littéraux, `make` et découpage
Il existe plusieurs façons de créer une slice en Go :
1. Avec un littéral de slice : Similaire à un littéral de tableau, mais sans spécifier la taille entre les crochets `[]`. Cela crée un tableau sous-jacent et retourne une slice qui le référence.
nombres := []int{2, 3, 5, 7, 11} // Crée un tableau [5]int et retourne une slice le référençant
fruits := []string{"pomme", "banane", "orange"}
fmt.Printf("Nombres: %v, type: %T\n", nombres, nombres) // Affiche: Nombres: [2 3 5 7 11], type: []int
fmt.Printf("Fruits: %v, type: %T\n", fruits, fruits) // Affiche: Fruits: [pomme banane orange], type: []string2. Avec la fonction intégrée `make` : C'est la méthode privilégiée lorsque vous connaissez la taille initiale nécessaire mais pas encore les valeurs, ou lorsque vous voulez spécifier une capacité initiale différente de la longueur. `make([]T, longueur, capacité)` crée un tableau sous-jacent de type `T`, initialisé avec les valeurs zéro du type, et retourne une slice référençant ce tableau.
// Crée une slice d'entiers de longueur 5 et capacité 5
// Tableau sous-jacent : [0 0 0 0 0]
s1 := make([]int, 5)
fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1)) // s1: [0 0 0 0 0], len=5, cap=5
// Crée une slice d'entiers de longueur 0 mais avec une capacité initiale de 10
// Tableau sous-jacent : [0 0 0 0 0 0 0 0 0 0]
s2 := make([]int, 0, 10)
fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2)) // s2: [], len=0, cap=10
// Si la capacité est omise, elle est égale à la longueur
s3 := make([]string, 3) // len=3, cap=3
fmt.Printf("s3: %q, len=%d, cap=%d\n", s3, len(s3), cap(s3)) // s3: ["" "" ""], len=3, cap=33. En découpant ("slicing") un tableau existant ou une autre slice : L'opérateur de découpage `[low:high]` crée une nouvelle slice qui partage le même tableau sous-jacent que l'original. La nouvelle slice référencera les éléments de l'indice `low` (inclus) jusqu'à l'indice `high` (exclus).
tableau := [6]string{"a", "b", "c", "d", "e", "f"}
sliceComplete := tableau[:] // Référence tout le tableau. len=6, cap=6
sliceMilieu := tableau[1:4] // Référence les éléments aux indices 1, 2, 3 ("b", "c", "d"). len=3, cap=5 (de l'indice 1 à la fin du tableau)
sliceDebut := tableau[:3] // Référence les 3 premiers (indices 0, 1, 2). len=3, cap=6
sliceFin := tableau[3:] // Référence de l'indice 3 à la fin. len=3, cap=3
fmt.Printf("Milieu: %v, len=%d, cap=%d\n", sliceMilieu, len(sliceMilieu), cap(sliceMilieu))
// Milieu: [b c d], len=3, cap=5
// Modifier un élément via une slice modifie le tableau sous-jacent
sliceMilieu[0] = "X"
fmt.Println("Tableau modifié:", tableau) // Tableau modifié: [a X c d e f]
fmt.Println("Slice complète:", sliceComplete) // Slice complète: [a X c d e f]Important : Etant donné que les slices créées par découpage partagent le même tableau sous-jacent, modifier un élément via une slice affectera les autres slices qui référencent le même élément.Une slice nil est une slice dont le pointeur interne est `nil`. Elle a une longueur et une capacité de 0. Une slice vide peut être non-nil mais avoir une longueur de 0. Les deux se comportent de manière similaire dans de nombreuses opérations (comme `len` ou `append`).
var sliceNil []int // Valeur zéro d'une slice, pointeur nil
sliceVideLiteral := []int{} // Slice vide, pointeur non-nil vers un tableau vide
sliceVideMake := make([]int, 0)
fmt.Printf("sliceNil: %v, len=%d, cap=%d, nil? %t\n", sliceNil, len(sliceNil), cap(sliceNil), sliceNil == nil) // len=0, cap=0, nil? true
fmt.Printf("sliceVideLiteral: %v, len=%d, cap=%d, nil? %t\n", sliceVideLiteral, len(sliceVideLiteral), cap(sliceVideLiteral), sliceVideLiteral == nil) // len=0, cap=0, nil? false
fmt.Printf("sliceVideMake: %v, len=%d, cap=%d, nil? %t\n", sliceVideMake, len(sliceVideMake), cap(sliceVideMake), sliceVideMake == nil) // len=0, cap=0, nil? falseLongueur (`len`) : Compter les éléments
La fonction intégrée `len()` appliquée à une slice retourne le nombre d'éléments actuellement présents dans cette slice. C'est la valeur du champ `length` interne de la slice.
maSlice := []string{"Go", "est", "simple"}
fmt.Println("Longueur:", len(maSlice)) // Affiche: Longueur: 3
sliceVide := make([]int, 0, 5)
fmt.Println("Longueur slice vide:", len(sliceVide)) // Affiche: Longueur slice vide: 0
var sliceNil []float64
fmt.Println("Longueur slice nil:", len(sliceNil)) // Affiche: Longueur slice nil: 0La longueur est utilisée pour accéder aux éléments par indice (de `0` à `len(slice)-1`) et détermine le nombre d'itérations dans une boucle `for i := 0; i < len(slice); i++` ou `for range slice`.
Capacité (`cap`) : Comprendre l'espace disponible
La fonction intégrée `cap()` appliquée à une slice retourne la capacité de la slice. C'est le nombre d'éléments disponibles dans le tableau sous-jacent, en comptant à partir du premier élément de la slice jusqu'à la fin du tableau sous-jacent.
La capacité détermine jusqu'où une slice peut s'étendre (en augmentant sa longueur par re-slicing ou `append`) sans nécessiter l'allocation d'un nouveau tableau sous-jacent.
nombres := []int{2, 3, 5, 7, 11, 13} // len=6, cap=6
fmt.Printf("nombres: len=%d, cap=%d\n", len(nombres), cap(nombres))
s1 := nombres[1:3] // Eléments: {3, 5}. Indices du tableau: 1, 2
// len = 3 - 1 = 2
// cap = 6 (taille tableau) - 1 (indice début slice) = 5
fmt.Printf("s1: %v, len=%d, cap=%d\n", s1, len(s1), cap(s1)) // s1: [3 5], len=2, cap=5
s2 := nombres[:4] // Eléments: {2, 3, 5, 7}. Indices du tableau: 0, 1, 2, 3
// len = 4 - 0 = 4
// cap = 6 (taille tableau) - 0 (indice début slice) = 6
fmt.Printf("s2: %v, len=%d, cap=%d\n", s2, len(s2), cap(s2)) // s2: [2 3 5 7], len=4, cap=6
s3 := make([]int, 3, 10) // len=3, cap=10 explicitement défini
fmt.Printf("s3: %v, len=%d, cap=%d\n", s3, len(s3), cap(s3)) // s3: [0 0 0], len=3, cap=10Comprendre la capacité est essentiel lorsqu'on utilise `append`, car si l'ajout d'éléments dépasse la capacité, un nouveau tableau plus grand sera alloué, et la slice sera mise à jour pour pointer vers ce nouveau tableau. Cela peut avoir des implications sur les performances et sur le partage de données entre slices.
Ajouter des éléments : La fonction `append`
La fonction intégrée `append` est utilisée pour ajouter un ou plusieurs éléments à la fin d'une slice. C'est le moyen idiomatique de faire grandir une slice dynamiquement. `append` prend en premier argument la slice à laquelle ajouter des éléments, suivi des éléments à ajouter.
Elle retourne une nouvelle slice contenant les éléments originaux plus les éléments ajoutés. Il est crucial d'assigner le résultat de `append` à la variable de slice d'origine, car `append` peut potentiellement retourner une slice pointant vers un nouveau tableau sous-jacent si la capacité initiale était insuffisante.
Syntaxe: `slice = append(slice, elem1, elem2, ...)`
Cas 1 : Capacité suffisante. Si la capacité de la slice originale est suffisante pour accueillir les nouveaux éléments, `append` place les éléments dans le tableau sous-jacent existant et retourne une nouvelle slice avec une longueur mise à jour, mais pointant toujours vers le même tableau sous-jacent.s := make([]int, 0, 5) // len=0, cap=5
fmt.Printf("Avant append: s=%v, len=%d, cap=%d, addr=%p\n", s, len(s), cap(s), s)
s = append(s, 10) // Ajoute 10. Capacité suffisante.
fmt.Printf("Après append(10): s=%v, len=%d, cap=%d, addr=%p\n", s, len(s), cap(s), s) // s=[10], len=1, cap=5, même adresse
s = append(s, 20, 30) // Ajoute 20, 30. Capacité suffisante.
fmt.Printf("Après append(20, 30): s=%v, len=%d, cap=%d, addr=%p\n", s, len(s), cap(s), s) // s=[10 20 30], len=3, cap=5, même adresseCas 2 : Capacité insuffisante. Si l'ajout des éléments dépasse la capacité de la slice originale, `append` alloue un nouveau tableau sous-jacent plus grand (la stratégie d'augmentation de taille peut varier, souvent un doublement), copie les éléments de l'ancienne slice vers la nouvelle, ajoute les nouveaux éléments, et retourne une nouvelle slice pointant vers ce nouveau tableau.s := make([]int, 3, 3) // len=3, cap=3. s=[0 0 0]
copy(s, []int{1, 2, 3}) // Met quelques valeurs : s=[1 2 3]
fmt.Printf("Avant append: s=%v, len=%d, cap=%d, addr=%p\n", s, len(s), cap(s), s)
s = append(s, 4) // Ajoute 4. Dépasse la capacité (3).
// Un nouveau tableau est alloué (capacité souvent doublée, ici probablement 6).
fmt.Printf("Après append(4): s=%v, len=%d, cap=%d, addr=%p\n", s, len(s), cap(s), s) // s=[1 2 3 4], len=4, cap=6, nouvelle adresse !C'est pourquoi l'assignation `slice = append(slice, ...)` est obligatoire : si un nouveau tableau est alloué, la variable `slice` doit être mise à jour pour pointer vers la nouvelle structure.On peut aussi ajouter tous les éléments d'une autre slice en utilisant la syntaxe `...` :
slice1 := []int{1, 2}
slice2 := []int{3, 4, 5}
slice1 = append(slice1, slice2...) // Ajoute tous les éléments de slice2 à slice1
fmt.Println("Slice fusionnée:", slice1) // Slice fusionnée: [1 2 3 4 5]Les Slices en résumé
Les slices sont l'outil de prédilection en Go pour manipuler des séquences dynamiques d'éléments. Elles offrent une abstraction flexible au-dessus des tableaux.
Retenez les points clés :
- Une slice est une vue sur un tableau sous-jacent (pointeur, longueur, capacité).
- Créez-les avec des littéraux `[]T{...}`, `make([]T, len, cap)`, ou en découpant `[low:high]`.
- `len()` donne le nombre d'éléments actuels.
- `cap()` donne l'espace disponible dans le tableau sous-jacent à partir du début de la slice.
- `append(slice, elems...)` ajoute des éléments, retourne une nouvelle slice (potentiellement avec un nouveau tableau sous-jacent). Assignez toujours le résultat : `slice = append(slice, ...)`.
Leur flexibilité et leur intégration étroite avec les autres fonctionnalités du langage (comme `for range`) en font une pierre angulaire du développement en Go.