Skip to content

Tranches

En Go, les tableaux et les tranches semblent presque identiques, mais leurs fonctionnalités diffèrent considérablement. Les tableaux sont des structures de données de longueur fixe qui ne peuvent pas être modifiées une fois spécifiées, tandis que les tranches sont de longueur variable et peuvent s'agrandir automatiquement lorsque la capacité est insuffisante.

Tableaux

Si vous connaissez à l'avance la longueur des données à stocker et qu'il n'y a pas de besoin d'extension ultérieure, vous pouvez envisager d'utiliser un tableau. Les tableaux en Go sont des types de valeur, et non des références. Ils ne sont pas des pointeurs vers le premier élément.

TIP

Lorsqu'un tableau est passé comme paramètre à une fonction, comme Go utilise le passage de valeur, le tableau entier sera copié.

Initialisation

La longueur d'un tableau ne peut être qu'une expression constante lors de la déclaration. Elle ne peut pas être une variable. Vous ne pouvez pas déclarer une variable puis l'utiliser comme longueur du tableau.

go
// Exemple correct
var a [5]int

// Exemple incorrect
l := 1
var b [l]int

Initialisons d'abord un tableau d'entiers de longueur 5 :

go
var nums [5]int

Vous pouvez également initialiser avec des éléments :

go
nums := [5]int{1, 2, 3}

Vous pouvez laisser le compilateur déduire automatiquement la longueur :

go
nums := [...]int{1, 2, 3, 4, 5} // Équivalent à nums := [5]int{1, 2, 3, 4, 5}, les points de suspension doivent être présents, sinon une tranche est générée, pas un tableau

Vous pouvez également obtenir un pointeur via la fonction new :

go
nums := new([5]int)

Les méthodes ci-dessus alloueront toutes une mémoire de taille fixe à nums. La seule différence est que la dernière méthode obtient une valeur qui est un pointeur.

Lors de l'initialisation d'un tableau, il faut noter que la longueur doit être une expression constante, sinon la compilation échouera. Une expression constante signifie que le résultat final de l'expression est une constante. Voici un exemple incorrect :

go
length := 5 // Ceci est une variable
var nums [length]int

length est une variable, donc elle ne peut pas être utilisée pour initialiser la longueur du tableau. Voici un exemple correct :

go
const length = 5
var nums [length]int // Constante
var nums2 [length + 1]int // Expression constante
var nums3 [(1 + 2 + 3) * 5]int // Expression constante
var nums4 [5]int // Le plus couramment utilisé

Utilisation

Vous pouvez accéder aux éléments du tableau en utilisant le nom du tableau et l'index.

go
fmt.Println(nums[0])

Vous pouvez également modifier les éléments du tableau :

go
nums[0] = 1

Vous pouvez également utiliser la fonction intégrée len pour accéder au nombre d'éléments du tableau :

go
len(nums)

Utilisez la fonction intégrée cap pour accéder à la capacité du tableau. La capacité d'un tableau est égale à sa longueur. La capacité n'a de sens que pour les tranches.

go
cap(nums)

Découpage

Le format de découpage d'un tableau est arr[startIndex:endIndex]. L'intervalle de découpage est fermé à gauche et ouvert à droite. Après le découpage, un tableau devient un type de tranche. Voici des exemples :

go
nums := [5]int{1, 2, 3, 4, 5}

nums[:] // Sous-tranche [0,5) -> [1 2 3 4 5]
nums[1:] // Sous-tranche [1,5) -> [2 3 4 5]
nums[:5] // Sous-tranche [0,5) -> [1 2 3 4 5]
nums[2:3] // Sous-tranche [2,3) -> [3]
nums[1:3] // Sous-tranche [1,3) -> [2 3]
go
func main() {
  arr := [5]int{1, 2, 3, 4, 5}
  fmt.Printf("%T\n", arr)
  fmt.Printf("%T\n", arr[1:2])
}

Sortie :

[5]int
[]int

Pour convertir un tableau en tranche, il suffit de découper sans paramètres. La tranche convertie et le tableau d'origine pointent vers la même mémoire. Modifier la tranche entraînera des changements dans le tableau d'origine.

go
func main() {
  arr := [5]int{1, 2, 3, 4, 5}
  slice := arr[:]
  slice[0] = 0
  fmt.Printf("array: %v\n", arr)
  fmt.Printf("slice: %v\n", slice)
}

Sortie :

array: [0 2 3 4 5]
slice: [0 2 3 4 5]

Si vous souhaitez modifier la tranche convertie, il est recommandé d'utiliser la méthode suivante pour la conversion :

go
func main() {
  arr := [5]int{1, 2, 3, 4, 5}
  slice := slices.Clone(arr[:])
  slice[0] = 0
  fmt.Printf("array: %v\n", arr)
  fmt.Printf("slice: %v\n", slice)
}

Sortie :

array: [1 2 3 4 5]
slice: [0 2 3 4 5]

Tranches

Les tranches sont beaucoup plus largement utilisées en Go que les tableaux. Elles sont utilisées pour stocker des données de longueur inconnue et peuvent être fréquemment insérées et supprimées lors d'une utilisation ultérieure.

Initialisation

Il existe plusieurs méthodes pour initialiser une tranche :

go
var nums []int // Valeur
nums := []int{1, 2, 3} // Valeur
nums := make([]int, 0, 0) // Valeur
nums := new([]int) // Pointeur

On peut voir que la différence entre une tranche et un tableau réside uniquement dans l'absence de longueur d'initialisation. En général, il est recommandé d'utiliser make pour créer une tranche vide. Pour les tranches, la fonction make accepte trois paramètres : le type, la longueur et la capacité. Prenons un exemple pour expliquer la différence entre longueur et capacité. Supposons qu'il y ait un seau d'eau, l'eau n'est pas pleine. La hauteur du seau représente sa capacité, c'est-à-dire la quantité totale d'eau qu'il peut contenir. La hauteur de l'eau dans le seau représente la longueur. La hauteur de l'eau doit être inférieure ou égale à la hauteur du seau, sinon l'eau déborderait. Ainsi, la longueur d'une tranche représente le nombre d'éléments dans la tranche, et la capacité représente le nombre total d'éléments que la tranche peut contenir. La principale différence entre une tranche et un tableau est que la capacité de la tranche s'étend automatiquement, tandis que celle du tableau ne le fait pas. Pour plus de détails, consultez Manuel de référence - Longueur et capacité.

TIP

L'implémentation sous-jacente d'une tranche est toujours un tableau. C'est un type de référence, qui peut être simplement compris comme un pointeur vers le tableau sous-jacent (en essence, une tranche en Go est une structure contenant un pointeur vers le tableau sous-jacent, une valeur de longueur et une valeur de capacité). Par conséquent, lorsqu'une tranche est passée comme paramètre de fonction, le tableau sous-jacent n'est pas copié, et les modifications apportées à la tranche dans la fonction se refléteront dans la tranche d'origine.

Lorsqu'une tranche est déclarée avec var nums []int, sa valeur par défaut est nil, donc aucune mémoire ne lui est allouée. Lors de l'initialisation avec make, il est recommandé de pré-allouer une capacité suffisante pour réduire efficacement la consommation de mémoire lors des extensions ultérieures.

Utilisation

L'utilisation de base d'une tranche est exactement la même que celle d'un tableau, sauf que la longueur d'une tranche peut varier dynamiquement. Voici quelques exemples.

Les tranches peuvent effectuer de nombreuses opérations via la fonction append. La signature de la fonction est la suivante : slice est la tranche cible à laquelle ajouter des éléments, elems sont les éléments à ajouter, et la valeur de retour est la tranche après ajout.

go
func append(slice []Type, elems ...Type) []Type

Créez d'abord une tranche vide de longueur 0 et de capacité 0, puis insérez des éléments à la fin, et enfin affichez la longueur et la capacité.

go
nums := make([]int, 0, 0)
nums = append(nums, 1, 2, 3, 4, 5, 6, 7)
fmt.Println(len(nums), cap(nums)) // 7 8 On peut voir que la longueur et la capacité ne sont pas cohérentes.

La taille de la capacité du tampon réservé pour la nouvelle tranche suit certaines règles. Avant la mise à jour de Go 1.18, la plupart des articles en ligne décrivaient la stratégie d'extension des tranches comme suit : Lorsque la capacité de la tranche d'origine est inférieure à 1024, la nouvelle capacité devient 2 fois l'ancienne ; lorsque la capacité de la tranche d'origine dépasse 1024, la nouvelle capacité devient 1,25 fois l'ancienne. Après la mise à jour de la version 1.18, la stratégie d'extension des tranches est devenue : Lorsque la capacité de la tranche d'origine (oldcap) est inférieure à 256, la nouvelle capacité (newcap) est 2 fois l'ancienne ; lorsque la capacité de la tranche d'origine dépasse 256, la nouvelle capacité newcap = oldcap+(oldcap+3*256)/4

Insertion d'éléments

L'insertion d'éléments dans une tranche doit également être combinée avec la fonction append. Voici une tranche existante :

go
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Insérer des éléments depuis le début :

go
nums = append([]int{-1, 0}, nums...)
fmt.Println(nums) // [-1 0 1 2 3 4 5 6 7 8 9 10]

Insérer des éléments à l'index i du milieu :

go
nums = append(nums[:i+1], append([]int{999, 999}, nums[i+1:]...)...)
fmt.Println(nums) // i=3, [1 2 3 4 999 999 5 6 7 8 9 10]

Insérer des éléments à la fin est l'utilisation la plus originale de append :

go
nums = append(nums, 99, 100)
fmt.Println(nums) // [1 2 3 4 5 6 7 8 9 10 99 100]

Suppression d'éléments

La suppression d'éléments d'une tranche doit être combinée avec la fonction append. Voici une tranche existante :

go
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Supprimer n éléments depuis le début :

go
nums = nums[n:]
fmt.Println(nums) //n=3 [4 5 6 7 8 9 10]

Supprimer n éléments depuis la fin :

go
nums = nums[:len(nums)-n]
fmt.Println(nums) //n=3 [1 2 3 4 5 6 7]

Supprimer n éléments à partir de l'index i du milieu :

go
nums = append(nums[:i], nums[i+n:]...)
fmt.Println(nums)// i=2, n=3, [1 2 6 7 8 9 10]

Supprimer tous les éléments :

go
nums = nums[:0]
fmt.Println(nums) // []

Copie

Lors de la copie d'une tranche, il faut s'assurer que la tranche cible a une longueur suffisante. Par exemple :

go
func main() {
  dest := make([]int, 0)
  src := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
  fmt.Println(src, dest)
  fmt.Println(copy(dest, src))
  fmt.Println(src, dest)
}
[1 2 3 4 5 6 7 8 9] []
0
[1 2 3 4 5 6 7 8 9] []

En modifiant la longueur à 10, la sortie est la suivante :

[1 2 3 4 5 6 7 8 9] [0 0 0 0 0 0 0 0 0 0]
9
[1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9 0]

Parcours

Le parcours d'une tranche est exactement le même que celui d'un tableau. Boucle for :

go
func main() {
   slice := []int{1, 2, 3, 4, 5, 7, 8, 9}
   for i := 0; i < len(slice); i++ {
      fmt.Println(slice[i])
   }
}

Boucle for range :

go
func main() {
  slice := []int{1, 2, 3, 4, 5, 7, 8, 9}
  for index, val := range slice {
    fmt.Println(index, val)
  }
}

Tranches multidimensionnelles

Commençons par l'exemple ci-dessous. La documentation officielle fournit également une explication : Effective Go - Tranches bidimensionnelles

go
var nums [5][5]int
for _, num := range nums {
   fmt.Println(num)
}
fmt.Println()
slices := make([][]int, 5)
for _, slice := range slices {
   fmt.Println(slice)
}

Le résultat de la sortie est :

[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]

[]
[]
[]
[]
[]

On peut voir que la structure interne des tableaux et des tranches bidimensionnels est différente. Lors de l'initialisation d'un tableau, ses dimensions un et deux sont déjà fixes, tandis que la longueur d'une tranche n'est pas fixe. Chaque tranche dans une tranche peut avoir une longueur différente, elle doit donc être initialisée séparément. La partie d'initialisation de la tranche doit être modifiée comme suit :

go
slices := make([][]int, 5)
for i := 0; i < len(slices); i++ {
   slices[i] = make([]int, 5)
}

Le résultat final de la sortie est :

[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]

[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]
[0 0 0 0 0]

Expression étendue

TIP

Seules les tranches peuvent utiliser l'expression étendue.

Les tranches et les tableaux peuvent tous deux utiliser une expression simple pour le découpage, mais seule l'expression étendue peut être utilisée avec les tranches. Cette fonctionnalité a été ajoutée dans la version Go 1.2, principalement pour résoudre le problème de lecture/écriture partagée du tableau sous-jacent par les tranches. Le format principal est le suivant, la relation low<= high <= max <= cap doit être satisfaite. La capacité de la tranche découpée avec l'expression étendue est max-low :

go
slice[low:high:max]

low et high conservent leur signification d'origine, et max fait référence à la capacité maximale. Dans l'exemple ci-dessous, max est omis, donc la capacité de s2 est cap(s1)-low :

go
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4] // cap = 9 - 3 = 6

Cela pose un problème évident : s1 et s2 partagent le même tableau sous-jacent. Lors de la lecture/écriture de s2, il est possible d'affecter les données de s1. Le code suivant appartient à ce cas :

go
s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
s2 := s1[3:4]                          // cap = 9 - 3 = 6
s2 = append(s2, 1)   // Ajout d'un nouvel élément, comme la capacité est de 6, il n'y a pas de redimensionnement, modification directe du tableau sous-jacent
fmt.Println(s2)
fmt.Println(s1)

La sortie finale est :

[4 1]
[1 2 3 4 1 6 7 8 9]

On peut voir que l'ajout d'éléments à s2 a également modifié s1. L'expression étendue est conçue pour résoudre ce type de problème. Il suffit de modifier légèrement pour résoudre ce problème :

go
func main() {
   s1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} // cap = 9
   s2 := s1[3:4:4]                        // cap = 4 - 3 = 1
   s2 = append(s2, 1)    // Capacité insuffisante, allocation d'un nouveau tableau sous-jacent
   fmt.Println(s2)
   fmt.Println(s1)
}

Le résultat obtenu est maintenant normal :

[4 1]
[1 2 3 4 5 6 7 8 9]

clear

Go 1.21 a ajouté la fonction intégrée clear. clear mettra toutes les valeurs de la tranche à leur valeur zéro :

go
package main

import (
    "fmt"
)

func main() {
    s := []int{1, 2, 3, 4}
    clear(s)
    fmt.Println(s)
}

Sortie :

[0 0 0 0]

Si vous souhaitez vider une tranche, vous pouvez :

go
func main() {
  s := []int{1, 2, 3, 4}
    s = s[:0:0]
  fmt.Println(s)
}

La capacité après découpage est limitée, ce qui permet d'éviter d'écraser les éléments suivants de la tranche d'origine.

Golang by www.golangdev.cn edit