Cet article est une traduction et un ajout de Tableaux, tranches (et chaînes): La mécanique de 'ajouter'.
Introduction
L'une des caractéristiques les plus courantes des langages de programmation procédurale est le concept de tableaux.
Bien que les tableaux semblent simples, il reste de nombreuses questions sur le mécanisme d'ajout de tableaux au langage.
Les réponses à ces questions déterminent même si le tableau est simplement une fonction du langage ou une partie essentielle de sa conception.
Au début du développement de Go, il a fallu environ un an pour trouver le bon design avec les réponses à ces questions.
Le plus dur a été l'introduction des tranches.
Les tranches sont construites sur des tableaux de taille fixe et fournissent une structure de données flexible et extensible.
Cependant, les programmeurs Go pour la première fois trébuchent souvent lorsqu'il s'agit de découper le comportement. Ceci est probablement dû à l'expérience dans d'autres langues.
Dans cet article, je vais essayer de me débarrasser de la confusion.
Pour ce faire, nous allons parcourir chacun des composants nécessaires pour expliquer comment la fonction intégrée append
fonctionne et pourquoi elle fonctionne de cette façon.
Les tableaux sont un composant important de Go, mais un composant important caché derrière les tranches.
Avant de passer aux idées intéressantes, puissantes et importantes des tranches, nous avons besoin d'une brève explication des séquences.
Comme le tableau a une longueur fixe, il est rarement vu dans les programmes Go.
Lors de la déclaration
var buffer [256]byte
Cette déclaration déclare une variable appelée «buffer» qui contient 256 octets.
Le type de «tampon» est «[256] octets» et le type inclut la taille. Si vous voulez conserver 512 octets, ce sera «[512] octets».
Les données associées au tableau sont littéralement un tableau d'éléments. En général, buffer
ressemble à ceci en mémoire:
buffer: byte byte byte ... 256 times ... byte byte byte
Donc, cette variable n'a que 256 octets de données.
Chaque élément est accessible à l'aide d'un index familier. (tampon \ [0 ], tampon \ [1 ], ..., tampon \ [255 ])
Le programme plantera si vous essayez d'accéder à l'index en dehors du tableau.
Les tableaux, les tranches et certains autres types de données ont une fonction intégrée appelée len
qui renvoie le nombre d'éléments.
Pour les tableaux, ce que renvoie len est clair. Dans cet exemple, len (buffer)
renvoie une valeur fixe de 256.
L'une des utilisations des tableaux est de représenter correctement les matrices de transformation, mais l'objectif le plus courant de Go est de préserver le stockage des tranches.
Les tranches sont une structure de données couramment utilisée, mais pour bien les utiliser, vous devez comprendre exactement ce qu'elles sont et ce qu'elles font.
Une tranche est une structure de données qui décrit une section continue d'un tableau à l'intérieur, et la tranche elle-même n'est pas un tableau. Les tranches représentent une partie du tableau.
En découpant ce tableau, étant donné la variable «buffer» qui représente le tableau dans la section précédente, vous pouvez créer une tranche qui décrit les éléments 100-150 (plus précisément, 100-149).
var slice []byte = buffer[100:150]
Dans l'exemple ci-dessus, nous avons utilisé une déclaration de variable explicite.
La variable «slice» est de type «[] byte», prononcée «byte type slice», et est initialisée à partir du tableau «buffer» en découpant les éléments de 100 à 150.
Dans une syntaxe plus idiomatique, vous pouvez omettre la description du type à définir.
var slice = buffer[100:150]
Vous pouvez également faire la courte déclaration suivante dans la fonction:
slice := buffer[100:150]
Qu'est-ce que cette variable de tranche a exactement?
Je ne parlerai pas de tout ici, mais pour l'instant, pensez aux tranches comme de petites structures de données avec deux éléments: la longueur et les pointeurs vers les éléments du tableau.
Vous pouvez donc penser aux tranches comme étant construites de cette façon dans les coulisses:
type sliceHeader struct {
Length int
ZerothElement *byte
}
slice := sliceHeader{
Length: 50,
ZerothElement: &buffer[100],
}
Bien sûr, ce n'est qu'un exemple, pas la structure réelle.
Dans cet exemple de code, la structure sliceHeader n'est pas visible pour le programmeur et le type de pointeur vers le tableau dépend du type de l'élément dans le tableau, ce qui donne une idée générale du mécanisme.
Jusqu'à présent, vous avez utilisé l'opération de tranche sur un tableau, mais vous pouvez également découper la tranche comme suit:
slice2 := slice[5:10]
Comme précédemment, cette opération crée une nouvelle tranche.
Dans ce cas, utilisez les éléments 5-9 (\ [5,9 ]) de la tranche d'origine.
Il représente les éléments 105 à 109 du tableau d'origine.
La structure sliceHeader
sur laquelle la variableslice2
est basée ressemble à ceci:
slice2 := sliceHeader{
Length: 5,
ZerothElement: &buffer[105],
}
Notez que cet en-tête fait référence au même «tampon» que la «tranche» d'origine comme référence au tableau.
Vous pouvez également le découper à nouveau. Autrement dit, coupez la tranche et enregistrez le résultat dans la structure de tranche d'origine comme suit:
slice = slice[5:10]
La structure sliceHeader
de la variableslice
a la même apparence que celle de la variable slice2
.
C'est souvent le cas lorsque vous souhaitez tronquer une partie d'une tranche. L'exemple suivant tronque le premier et le dernier éléments de la tranche.
slice = slice[1:len(slice)-1]
On entend souvent des programmeurs Go expérimentés parler de "slice headers".
Parce que l'en-tête de tranche est en fait ce qui est stocké dans la variable de tranche.
Par exemple, si vous appelez une fonction qui prend une tranche comme argument, telle que bytes.IndexRune
, l'en-tête de tranche sera passé à la fonction.
slashPos := bytes.IndexRune(slice, '/')
Par exemple, dans cet appel de fonction, ce qui est réellement passé à la fonction est l'en-tête de tranche.
Il existe un autre élément de données dans l'en-tête de tranche. Nous en reparlerons plus tard, mais voyons d'abord ce que signifie la présence d'un en-tête de tranche lors de l'écriture d'un programme qui utilise des tranches.
Il est important de comprendre le fait que la tranche elle-même est une valeur, même si la tranche contient des pointeurs.
La structure de la tranche est une ** valeur de structure ** qui contient le pointeur et la longueur. Ce n'est pas un pointeur vers une structure.
C'est important. Lors de l'appel de IndexRune
dans l'exemple précédent, une copie de l'en-tête de la tranche a été transmise. Son comportement a des implications importantes.
Considérez cette fonction simple.
func AddOneToEachElement(slice []byte) {
for i := range slice {
slice[i]++
}
}
Comme son nom l'indique, c'est une fonction qui parcourt les tranches et incrémente les éléments de la tranche à chaque itération.
Essayez le programme suivant.
func main() {
slice := buffer[10:20]
for i := 0; i < len(slice); i++ {
slice[i] = byte(i)
}
fmt.Println("before", slice)
AddOneToEachElement(slice)
fmt.Println("after", slice)
}
before [0 1 2 3 4 5 6 7 8 9]
after [1 2 3 4 5 6 7 8 9 10]
Program exited.
L'en-tête de tranche lui-même a été passé en tant que valeur (c'est-à-dire copié et passé), mais parce que l'en-tête contient des pointeurs vers les éléments du tableau, l'en-tête de tranche d'origine et une copie de l'en-tête transmis à la fonction Les deux font référence au même tableau.
Ainsi, lorsque la fonction revient, l'élément modifié peut être vu à travers la variable de tranche d'origine.
Comme le montre cet exemple, les arguments de la fonction sont en fait des copies.
func SubtractOneFromLength(slice []byte) []byte {
slice = slice[0 : len(slice)-1]
return slice
}
func main() {
fmt.Println("Before: len(slice) =", len(slice))
newSlice := SubtractOneFromLength(slice)
fmt.Println("After: len(slice) =", len(slice))
fmt.Println("After: len(newSlice) =", len(newSlice))
}
Before: len(slice) = 50
After: len(slice) = 50
After: len(newSlice) = 49
Program exited.
Dans cet exemple, vous pouvez voir que le contenu de l'argument de tranche peut être modifié avec la fonction, mais l'en-tête lui-même ne peut pas être modifié.
La fonction reçoit une copie de l'en-tête de tranche au lieu de l'en-tête d'origine, de sorte que la longueur stockée dans la variable de tranche (la propriété Length de l'en-tête d'origine) n'est pas modifiée par l'appel de fonction.
Par conséquent, si vous créez une fonction qui modifie l'en-tête, vous devez renvoyer la tranche modifiée comme valeur de retour, comme vous l'avez fait dans cet exemple.
La variable de tranche d'origine n'a pas changé, mais la tranche renvoyée dans le retour aura une nouvelle longueur et sera stockée dans newSlice.
Une autre façon dont la fonction interfère avec l'en-tête de la tranche est de passer un pointeur vers la tranche
func PtrSubtractOneFromLength(slicePtr *[]byte) {
slice := *slicePtr
*slicePtr = slice[0 : len(slice)-1]
}
func main() {
fmt.Println("Before: len(slice) =", len(slice))
PtrSubtractOneFromLength(&slice)
fmt.Println("After: len(slice) =", len(slice))
}
Before: len(slice) = 50
After: len(slice) = 49
Program exited.
L'exemple ci-dessus n'a pas l'air très intelligent.
Il existe un cas courant où un pointeur vers une tranche est affiché.
Par exemple, il est courant d'utiliser un pointeur comme récepteur pour les méthodes qui apportent des modifications de tranche.
Disons que vous avez besoin d'une méthode pour tronquer les tranches au dernier /
du chemin du fichier.
Par exemple, «dir1 / dir2 / dir3» doit être «dir1 / dir2».
Une méthode qui satisfait cela peut être écrite comme suit:
type path []byte
func (p *path) TruncateAtFinalSlash() {
i := bytes.LastIndex(*p, []byte("/"))
if i >= 0 {
*p = (*p)[0:i]
}
}
func main() {
pathName := path("/usr/bin/tso") // Conversion from string to path.
pathName.TruncateAtFinalSlash()
fmt.Printf("%s\n", pathName)
}
Si vous l'exécutez, vous verrez que cela fonctionne. Cette fois, la tranche a été modifiée dans la méthode d'appel.
En revanche, si vous souhaitez écrire une méthode qui met en majuscule les caractères ASCII dans le chemin (ignore les autres que l'anglais), le récepteur de la méthode peut être une valeur car la destination de référence du tableau reste la même et seul le contenu est modifié.
type path []byte
func (p path) ToUpper() {
for i, b := range p {
if 'a' <= b && b <= 'z' {
p[i] = b + 'A' - 'a'
}
}
}
func main() {
pathName := path("/usr/bin/tso")
pathName.ToUpper()
fmt.Printf("%s\n", pathName)
}
Ici, la méthode ToUpper
utilise l'index du tableau et deux variables avec les éléments correspondants dans la syntaxe pour la plage
.
En faisant cela, vous pouvez réduire le nombre de fois que vous écrivez p [i]
une par une.
Regardons la fonction suivante qui étend la tranche de type int slice
passée comme argument en ajoutant element
.
func Extend(slice []int, element int) []int {
n := len(slice)
slice = slice[0 : n+1]
slice[n] = element
return slice
}
Courons
func main() {
var iBuffer [10]int
slice := iBuffer[0:0]
for i := 0; i < 20; i++ {
slice = Extend(slice, i)
fmt.Println(slice)
}
}
[0]
[0 1]
[0 1 2]
[0 1 2 3]
[0 1 2 3 4]
[0 1 2 3 4 5]
[0 1 2 3 4 5 6]
[0 1 2 3 4 5 6 7]
[0 1 2 3 4 5 6 7 8]
[0 1 2 3 4 5 6 7 8 9]
panic: runtime error: slice bounds out of range [:11] with capacity 10
goroutine 1 [running]:
main.Extend(...)
/tmp/sandbox021597325/prog.go:16
main.main()
/tmp/sandbox021597325/prog.go:25 +0x105
Program exited: status 2.
Les tranches s'étendent de plus en plus, mais elles s'arrêtent au milieu.
Décrit le troisième composant de l'en-tête de tranche, «capacité».
En plus du pointeur et de la longueur du tableau, l'en-tête de tranche stocke également sa capacité («capacité»).
type sliceHeader struct {
Length int
Capacity int
ZerothElement *byte
}
Le champ capacity
contient la quantité d'espace dont dispose réellement le tableau sous-jacent.
Il s'agit de la valeur maximale que «Length» peut atteindre. Si vous essayez de faire croître une tranche au-delà de sa capacité, vous paniquerez car vous dépasserez la limite du tableau, c'est-à-dire que vous aurez un accès hors tableau.
L'en-tête de la tranche créée par slice: = iBuffer [0: 0]
slice := sliceHeader{
Length: 0,
Capacity: 10,
ZerothElement: &iBuffer[0],
}
Le champ «capacity» est égal à la longueur du tableau sous-jacent moins l'indice du tableau correspondant (0 dans ce cas) pour le premier élément de la tranche.
Si vous souhaitez vérifier la capacité d'une tranche, utilisez la fonction intégrée cap
.
if cap(slice) == len(slice) {
fmt.Println("slice is full!")
}
Make
Et si vous souhaitez faire pousser une tranche au-delà de sa capacité?
ne peux pas! Par définition, la «capacité» est la limite de l'expansion.
Cependant, vous pouvez obtenir des résultats équivalents en attribuant un nouveau tableau, en copiant les données, en modifiant le contenu des tranches et en référençant le nouveau tableau.
Commençons par la mission. Vous pouvez utiliser la nouvelle fonction intégrée pour allouer un tableau plus grand et découper le résultat, mais il est plus facile d'utiliser la fonction intégrée make
à la place.
Allouez un nouveau tableau et créez un en-tête de tranche qui le référence en un seul coup.
La fonction make
prend trois arguments: le type de tranche, sa longueur initiale et sa capacité
(la longueur du tableau qui alloue pour contenir les données de la tranche).
Comme vous pouvez le voir en exécutant l'exemple ci-dessous, cet appel crée une tranche de longueur 10 et le tableau arrière a de la place pour 5 tailles (15-10).
slice := make([]int, 10, 15)
fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
len: 10, cap: 15
Program exited.
slice := make([]int, 10, 15)
fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
newSlice := make([]int, len(slice), 2*cap(slice))
for i := range slice {
newSlice[i] = slice[i]
}
slice = newSlice
fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))
len: 10, cap: 15
len: 10, cap: 30
Program exited.
La tranche après l'exécution de ce code a beaucoup plus d'espace pour se développer (5-> 20)
Lors de la création de tranches, il est courant qu'elles aient la même longueur et la même capacité. La fonction intégrée «make» a une abréviation pour ce cas courant.
gophers := make([]Gopher, 10) // = make([]Gopher, 10, 10)
Si vous ne spécifiez pas de capacité, sa valeur par défaut est la même que la longueur, vous pouvez donc l'omettre comme ci-dessus et définir les deux sur la même valeur.
Copy
Lorsque j'ai doublé la capacité de la tranche dans la section précédente, j'ai créé une boucle qui copie les anciennes données dans la nouvelle tranche.
Go a une fonction intégrée «copier» pour rendre cela plus facile. Ses arguments sont deux tranches, copiant les données de l'argument droit vers l'argument gauche.
Voici un exemple de réécriture pour utiliser copy
.
newSlice := make([]int, len(slice), 2*cap(slice))
copy(newSlice, slice)
La fonction «copie» fait une copie intelligente. Copiez autant que vous le pouvez, en faisant attention à la longueur des deux arguments.
Autrement dit, le nombre d'éléments à copier est le minimum de la longueur des deux tranches.
Cela économise un peu d'écriture. De plus, la copie renvoie une valeur entière, qui est le nombre d'éléments copiés, mais cela ne vaut pas toujours la peine d'être vérifié.
La fonction «copie» est également gérée correctement si la source et la destination sont des doublons.
Autrement dit, il peut être utilisé pour déplacer le contenu dans une seule tranche. Voici comment utiliser une copie pour insérer une valeur au centre d'une tranche:
// Insert inserts the value into the slice at the specified index,
// which must be in range.
// The slice must have room for the new element.
func Insert(slice []int, index, value int) []int {
// Grow the slice by one element.
slice = slice[0 : len(slice)+1]
// Use copy to move the upper part of the slice out of the way and open a hole.
copy(slice[index+1:], slice[index:])
// Store the new value.
slice[index] = value
// Return the result.
return slice
}
Une chose à garder à l'esprit dans l'exemple ci-dessus est que la longueur a changé, vous devez donc toujours renvoyer la tranche modifiée comme valeur de retour.
Il doit également être capacité> longueur car il ajoute un élément.
Append
Il y a quelques sections, j'ai créé une fonction Extend
qui étend une tranche d'un seul élément.
Cependant, il y avait un bug car la fonction plantait si la taille de la tranche était trop petite. (L'exemple «Insert» présente le même problème.)
Maintenant que nous avons les éléments pour résoudre ce problème, créons une implémentation solide de Extend
qui étend la tranche entière.
func Extend(slice []int, element int) []int {
n := len(slice)
if n == cap(slice) {
//La tranche est pleine et doit être agrandie
//La nouvelle capacité est de 2x+1(+1 est x=Pour quand 0)
newSlice := make([]int, len(slice), 2*len(slice)+1)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0 : n+1]
slice[n] = element
return slice
}
Encore une fois, le tableau référencé change complètement, vous devez donc renvoyer la tranche comme valeur de retour.
Utilisons la fonction Extend
pour étendre et vérifier le comportement lorsque la tranche est pleine.
slice := make([]int, 0, 5)
for i := 0; i < 10; i++ {
slice = Extend(slice, i)
fmt.Printf("len=%d cap=%d slice=%v\n", len(slice), cap(slice), slice)
fmt.Println("address of 0th element:", &slice[0])
}
len=1 cap=5 slice=[0]
address of 0th element: 0xc000078030
len=2 cap=5 slice=[0 1]
address of 0th element: 0xc000078030
len=3 cap=5 slice=[0 1 2]
address of 0th element: 0xc000078030
len=4 cap=5 slice=[0 1 2 3]
address of 0th element: 0xc000078030
len=5 cap=5 slice=[0 1 2 3 4]
address of 0th element: 0xc000078030
len=6 cap=11 slice=[0 1 2 3 4 5]
address of 0th element: 0xc00005e060
len=7 cap=11 slice=[0 1 2 3 4 5 6]
address of 0th element: 0xc00005e060
len=8 cap=11 slice=[0 1 2 3 4 5 6 7]
address of 0th element: 0xc00005e060
len=9 cap=11 slice=[0 1 2 3 4 5 6 7 8]
address of 0th element: 0xc00005e060
len=10 cap=11 slice=[0 1 2 3 4 5 6 7 8 9]
address of 0th element: 0xc00005e060
Program exited.
Notez la réaffectation lorsque la matrice initiale de taille 5 est pleine.
Lorsqu'un nouveau tableau est attribué, la «capacité» et l'adresse du 0e élément changent.
Vous pouvez utiliser la fonction robuste Extend
comme guide pour créer des fonctions encore meilleures qui vous permettent d'étendre une tranche avec plusieurs éléments. Pour ce faire, profitez de la capacité de Go à traiter la liste des arguments de fonction comme une conversion en tranches lorsque la fonction est appelée, à savoir la fonction d'argument variable de Go.
Appelons la fonction «Append». Dans la première version, Extend peut être appelé à plusieurs reprises, ce qui clarifie le mécanisme des fonctions d'argument variable. La signature de la fonction Append
est:
func Append(slice []int, items ...int) []int
Cette signature indique qu'elle prend une tranche suivie de zéro ou plusieurs types int comme arguments.
// Append appends the items to the slice.
// First version: just loop calling Extend.
func Append(slice []int, items ...int) []int {
for _, item := range items {
slice = Extend(slice, item)
}
return slice
}
Il convient de noter que la boucle for range
gère les éléments de l'argument items
, qui sont traités comme [] int
, dans chaque boucle. Il est également nécessaire de noter que l'index des tranches inutiles est supprimé dans ce cas avec l'identifiant «_».
slice := []int{0, 1, 2, 3, 4}
fmt.Println(slice)
slice = Append(slice, 5, 6, 7, 8)
fmt.Println(slice)
Une nouvelle technique qui apparaît dans cet exemple consiste à initialiser une tranche en écrivant un littéral composite composé du type de tranche suivi des éléments entre crochets du milieu.
slice := []int{0, 1, 2, 3, 4}
Ce qui est encore plus intéressant à propos de la fonction Append
, c'est que vous pouvez non seulement ajouter des éléments, mais aussi ajouter la deuxième tranche elle-même en tant qu'argument en" décomposant "la tranche en un argument en utilisant la notation ...
côté appelant. C'est un point qui peut être fait.
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)
Dans le précédent Append
, il était deux fois plus long que la tranche d'origine, donc le nombre d'éléments à ajouter pouvait entraîner la réaffectation du tableau plusieurs fois, mais le
Appenddans l'exemple ci-dessous
Il est simplifié de vivre avec une seule allocation.
// Append appends the elements to the slice.
// Efficient version.
func Append(slice []int, elements ...int) []int {
n := len(slice)
total := len(slice) + len(elements)
if total > cap(slice) {
// Reallocate. Grow to 1.5 times the new size, so we can still grow.
newSize := total*3/2 + 1
newSlice := make([]int, total, newSize)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[:total]
copy(slice[n:], elements)
return slice
}
Notez que copy
est appelé deux fois, une fois lors de la copie des données de tranche dans la mémoire nouvellement allouée et lors de la copie de l'élément à ajouter à la fin du tableau.
Enfin, nous avons un élément pour expliquer la raison de la conception de la fonction intégrée append
.
La fonction intégrée append
s'exécute avec une efficacité égale, tout comme l'exemple Append
ci-dessus, mais fonctionne avec n'importe quel type de tranche.
La faiblesse de Go est qu'il doit fournir des opérations de type générique au moment de l'exécution.
Cela peut changer un jour, mais pour le moment, Go a une fonction générique intégrée append
pour faciliter le découpage.
Cela fonctionne de la même manière qu'une tranche de [] int
, mais cela fonctionne avec n'importe quel type de tranche.
Notez que l'en-tête de la tranche est constamment mis à jour avec un appel à append
, vous devez donc enregistrer la tranche renvoyée après l'appel.
En fait, le compilateur ne peut pas appeler append sans enregistrer le résultat.
Voici un exemple d'utilisation de append
. Il peut être intéressant de l'exécuter ou de le modifier.
// Create a couple of starter slices.
slice := []int{1, 2, 3}
slice2 := []int{55, 66, 77}
fmt.Println("Start slice: ", slice)
fmt.Println("Start slice2:", slice2)
// Add an item to a slice.
slice = append(slice, 4)
fmt.Println("Add one item:", slice)
// Add one slice to another.
slice = append(slice, slice2...)
fmt.Println("Add one slice:", slice)
// Make a copy of a slice (of int).
slice3 := append([]int(nil), slice...)
fmt.Println("Copy a slice:", slice3)
// Copy a slice to the end of itself.
fmt.Println("Before append to self:", slice)
slice = append(slice, slice...)
fmt.Println("After append to self:", slice)
Start slice: [1 2 3]
Start slice2: [55 66 77]
Add one item: [1 2 3 4]
Add one slice: [1 2 3 4 55 66 77]
Copy a slice: [1 2 3 4 55 66 77]
Before append to self: [1 2 3 4 55 66 77]
After append to self: [1 2 3 4 55 66 77 1 2 3 4 55 66 77]
Program exited.
En particulier, le dernier «append» dans l'exemple ci-dessus serait un bon exemple de la raison pour laquelle la conception des tranches est si simple.
La page Wiki "Slice Tricks" créée par une communauté de volontaires contient des exemples de diverses fonctions et processus liés à append
, 'copy' et d'autres tranches. Introduit.
Nil
En passant, les nouvelles connaissances que j'ai acquises cette fois me donneront une idée de la représentation réelle de la tranche «nulle».
Naturellement, il s'agit de la valeur zéro de l'en-tête de tranche.
sliceHeader{
Length: 0,
Capacity: 0,
ZerothElement: nil,
}
Ou
sliceHeader{}
La clé est que le pointeur d'élément est «nil».
array[0:0]
Un tableau créé de cette manière a une longueur et une capacité nulles, mais le pointeur n'est pas un "nil" et n'est pas traité comme une tranche "nil".
De toute évidence, les tranches vides peuvent être grandes (en supposant que la capacité est non nulle)
Cependant, la tranche «nil» n'a pas de tableau pour contenir les valeurs et ne peut pas être étendue pour contenir les éléments.
Cependant, une tranche «nil» est fonctionnellement équivalente à une tranche de longueur nulle, même si elle ne pointe vers rien, vous pouvez donc utiliser «append» pour affecter un tableau et ajouter des éléments. Je vais.
Strings
Ici, expliquons brièvement la chaîne Go du point de vue du découpage.
En réalité, le mécanisme des cordes est très simple. La chaîne est une tranche d'octets en lecture seule avec une prise en charge de la syntaxe supplémentaire du langage.
Ils sont en lecture seule, donc ils ne nécessitent pas d'espace (c'est-à-dire qu'ils ne peuvent pas être développés), mais à part cela, ils peuvent presque toujours être traités comme des tranches d'octets en lecture seule.
Pour commencer, vous pouvez indexer des octets individuels pour y accéder.
slash := "/usr/ken"[0] // yields the byte value '/'.
Il est également possible de découper la chaîne et d'extraire la sous-chaîne.
usr := "/usr/ken"[0:4] // yields the string "/usr"
Il devrait être évident pour tous ceux qui ont lu jusqu'ici ce qui se passe dans les coulisses lors du découpage d'une chaîne.
Il est également possible de générer une chaîne de caractères en convertissant une tranche de type octet comme suit.
str := string(slice)
L'inverse est également possible.
slice := []byte(usr)
Le tableau d'octets sur lequel la chaîne est basée n'apparaît pas dans le tableau. Autrement dit, il n'y a aucun moyen d'accéder à son contenu sauf via une chaîne.
Lorsque vous effectuez l'une de ces conversions, vous devez faire une copie du tableau d'octets.
Bien sûr, Go gère cela pour vous, vous n'avez donc pas à le faire vous-même. Après l'une de ces conversions, les modifications apportées au tableau sous-jacent de tranches d'octets n'affectent pas la chaîne correspondante. (Par exemple, changer slice
après str: = string (slice)
n'affecte pas str
)
La chose importante en raison de la conception de la chaîne comme une tranche est que la création de sous-chaînes est très efficace.
Tout ce que vous avez à faire est de créer un en-tête de chaîne de deux mots. La chaîne est en lecture seule, de sorte que la chaîne d'origine et les sous-chaînes résultant de l'opération de tranche peuvent partager en toute sécurité le même tableau.
Note historique: Dans les premières implémentations de chaînes, de nouveaux tableaux d'octets étaient toujours attribués lors de la création de sous-chaînes, mais lorsque des tranches étaient ajoutées au langage, elles étaient des caractères efficaces. Fournit un modèle pour le traitement des colonnes. En conséquence, certains benchmarks ont connu des accélérations significatives.
Bien sûr, il y a beaucoup plus de chaînes, et vous pouvez en savoir plus à leur sujet dans un autre article de blog (https://blog.golang.org/strings).
Pour comprendre le fonctionnement des tranches, il est utile de comprendre comment les tranches sont implémentées.
Il existe une petite structure de données appelée en-tête de tranche, qui est l'élément associé à la variable de tranche, qui fait référence à une partie du tableau attribué individuellement.
Si vous passez une valeur de tranche, l'en-tête sera copié, mais le tableau vers lequel il pointe sera toujours partagé.
Comprendre comment ils fonctionnent rend les tranches non seulement faciles à utiliser, mais aussi des structures de données puissantes et expressives, en particulier à l'aide des fonctions intégrées «copier» et «ajouter».
De plus, si vous souhaitez écrire d'autres articles sur Go, n'hésitez pas!
Recommended Posts