Pourquoi ça ne marche pas?
package main
import "fmt"
type name struct {
X string
}
func main() {
var a [3]name
a[0] = name{"Abbed"}
a[1] = name{"Ahmad"}
a[2] = name{"Ghassan"}
nameReader(a)
}
func nameReader(array []name) {
for i := 0; i < len(array); i++ {
fmt.Println(array[i].X)
}
}
Erreur:
.\structtest.go:15: cannot use a (type [3]name) as type []name in function argument
Vous avez défini votre fonction pour accepter une tranche en tant qu'argument pendant que vous essayez de transmettre un tableau lors de l'appel de cette fonction. Vous pouvez résoudre ce problème de deux manières:
Créez une tranche hors du tableau lors de l'appel de la fonction. Changer l'appel comme ça devrait suffire:
nameReader(a[:])
Modifiez la signature de la fonction pour prendre un tableau au lieu d’une tranche. Par exemple:
func nameReader(array [3]name) {
...
}
Les inconvénients de cette solution sont que la fonction ne peut désormais accepter qu'un tableau de longueur 3 et qu'une copie du tableau sera créée lors de son appel.
Vous pouvez trouver plus de détails sur les tableaux et les tranches, ainsi que les pièges courants lors de leur utilisation ici: http://openmymind.net/The-Minimum-You-Need-To-Know-About-Arrays-And-Slices-In -Aller/
Puisque la réponse de @ james-henstridge couvrait déjà la façon dont vous pourriez le faire fonctionner, je ne dupliquerai pas ce qu'il a dit, mais je vais expliquer pourquoi sa réponse fonctionne.
Dans Go, les tableaux fonctionnent un peu différemment que dans la plupart des autres langues (oui, il y a des tableaux et des tranches. Je discuterai des tranches plus tard). Dans Go, les tableaux ont une taille fixe, comme vous utilisez dans votre code (donc, [3]int
est d'un type différent de [4]int
). De plus, les tableaux sont values . Cela signifie que si je copie un tableau d'un endroit à un autre, je copie en fait tous les éléments du tableau (au lieu, comme dans la plupart des autres langues, de simplement faire une autre référence au même tableau). Par exemple:
a := [3]int{1, 2, 3} // Array literal
b := a // Copy the contents of a into b
a[0] = 0
fmt.Println(a) // Prints "[0 2 3]"
fmt.Println(b) // Prints "[1 2 3]"
Cependant, comme vous l'avez remarqué, Go a également des tranches. Les tranches ressemblent aux tableaux, sauf de deux manières. D'abord, ils ont une longueur variable (donc []int
est le type d'une tranche d'un nombre entier quelconque). Deuxièmement, les tranches sont references. Cela signifie que lorsque je crée une tranche, un morceau de mémoire est alloué pour représenter le contenu de la tranche, et la variable de tranche elle-même n'est en réalité qu'un pointeur sur cette mémoire. Ensuite, lorsque je copie cette tranche, je ne fais que copier le pointeur. Cela signifie que si je copie la tranche et que je modifie ensuite l'une des valeurs, je modifie cette valeur pour tout le monde. Par exemple:
a := []int{1, 2, 3} // Slice literal
b := a // a and b now point to the same memory
a[0] = 0
fmt.Println(a) // Prints "[0 2 3]"
fmt.Println(b) // Prints "[0 2 3]"
La mise en oeuvre
Si cette explication était assez facile à comprendre, vous pourriez aussi être curieux de savoir comment cela est mis en œuvre (si vous aviez du mal à comprendre cela, je cesserais de lire ici parce que les détails seront probablement source de confusion).
Sous le capot, les tranches Go sont en réalité des structures. Comme je l'ai mentionné, ils ont un pointeur sur la mémoire allouée, mais ils ont également d'autres composants clés: la longueur et la capacité. Si cela était décrit en termes de Go, cela ressemblerait à ceci:
type int-slice struct {
data *int
len int
cap int
}
La longueur est la longueur de la tranche, et elle est là pour que vous puissiez demander len(mySlice)
, ainsi que pour que Go puisse vérifier que vous n'accédez pas à un élément qui n'est pas réellement dans la tranche. La capacité, cependant, est un peu plus déroutante. Alors plongons un peu plus loin.
Lorsque vous créez une tranche pour la première fois, vous indiquez un certain nombre d’éléments que vous souhaitez lui attribuer. Par exemple, appeler make([]int, 3)
vous donnerait une tranche de 3 pouces. Cela permet d'allouer de l'espace en mémoire pendant 3 ints, puis de vous redonner une structure avec un pointeur sur les données, la longueur de 3 et la capacité de 3.
Cependant, dans Go, vous pouvez effectuer ce que vous appelez slicing. En gros, vous créez une nouvelle tranche à partir d’une ancienne tranche qui ne représente qu’une partie de l’ancienne tranche. Vous utilisez la syntaxe slc[a:b]
pour faire référence à la sous-tranche de slc
commençant à index a
et se terminant juste avant index b
. Donc, par exemple:
a := [5]int{1, 2, 3, 4, 5}
b := a[1:4]
fmt.Println(b) // Prints "[2 3 4]"
Cette opération de découpage sous le capot consiste à créer une copie de la structure qui correspond à a
, et à modifier le pointeur vers le nombre entier 1 en mémoire (car la nouvelle section commence à l’index 1) et à modifier la longueur 2 plus courte qu'avant (car l'ancienne tranche avait une longueur de 5, alors que la nouvelle avait une longueur de 3). Alors, à quoi cela ressemble-t-il en mémoire maintenant? Eh bien, si nous pouvions visualiser les entiers disposés, cela ressemblerait à ceci:
begin end // a
v v
[ 1 2 3 4 5 ]
^ ^
begin end // b
Remarquez comment il y a encore un autre int après la fin de b
? Eh bien c'est la capacité. Vous voyez, tant que la mémoire restera pour que nous puissions l’utiliser, nous pourrions aussi bien l’utiliser. Ainsi, même si vous ne disposez que d’une tranche dont la longueur est petite, vous vous souviendrez qu’il ya plus de capacité au cas où vous voudriez la récupérer. Donc, par exemple:
a := []int{1, 2, 3}
b := a[0:1]
fmt.Println(b) // Prints "[1]"
b = b[0:3]
fmt.Println(b) // Prints "[1 2 3]"
Vous voyez comment nous faisons b[0:3]
à la fin? La longueur de b
est en fait de moins que 3 à ce stade. La seule raison pour laquelle nous pouvons le faire est que Go a gardé en mémoire le fait que, dans la mémoire sous-jacente, nous avons en réalité plus capacité réservée. De cette façon, lorsque nous demanderons une partie de cette somme, elle peut obliger avec joie.
Une approche alternative
On pourrait invoquer un fonction variadic sur une tranche pour entrer la liste des noms en tant qu'arguments individuels de la fonction nameReader, par exemple:
package main
import "fmt"
type name struct {
X string
}
func main() {
a := [3]name{{"Abbed"}, {"Ahmed"}, {"Ghassan"}}
nameReader(a[:]...)
}
func nameReader(a ...name) {
for _, n := range a {
fmt.Println(n.X)
}
}
Au lieu de déclarer un tableau de taille, déclarez une tranche. Allouez-y la mémoire et remplissez-la. Une fois que vous l'avez, la référence d'origine est toujours la tranche, qui peut être transmise à une fonction. Considérez cet exemple simple
package main
import "fmt"
func main() {
// declare a slice
var i []int
i = make([]int, 2)
i[0] = 3
i[1] = 4
// check the value - should be 3
fmt.Printf("Val - %d\n", i[0])
// call the function
a(i)
// check the value again = should be 33
fmt.Printf("Val - %d\n", i[0])
}
func a(i []int) {
// check the value - should be 3
fmt.Printf("Val - %d\n", i[0])
// change the value
i[0] = 33
// check the value again = should be 33
fmt.Printf("Val - %d\n", i[0])
}
Comme vous pouvez le voir, le tableau a été passé (comme référence) et peut être modifié par la fonction correspondante.
La sortie ressemble à ceci:
Val - 3
Val - 3
Val - 33
Val - 33
Le programme complet est également disponible sur: http://play.golang.org/p/UBU56eWXhJ
Les valeurs des tableaux sont traitées comme une seule unité. Une variable de tableau n'est pas un pointeur sur un emplacement en mémoire, mais représente l'ensemble du "bloc de mémoire" contenant les éléments du tableau . Cela a pour conséquence de créer une nouvelle copie d'une valeur de tableau lorsque la variable de tableau est réaffectée ou transmis en tant que paramètre de fonction .Learning Go Programming, Par Vladimir Vivien
Cela pourrait avoir des effets secondaires indésirables sur la consommation de mémoire d'un programme. Vous pouvez résoudre ce problème en utilisant des "types de pointeur" pour référencer les valeurs d'un tableau. Par exemple:
faites plutôt ceci:
var numbers [1024*1024]int
tu dois faire:
type numbers [1024*1024]int
var nums *numbers = new(numbers)
Rappelez-vous que:
https://golang.org/pkg/builtin/#new
La nouvelle fonction intégrée alloue de la mémoire. Le premier argument est un type, pas une valeur, et la valeur renvoyée est un pointeur sur un élément nouvellement ajouté attribué une valeur zéro de ce type.
Vous pouvez maintenant passer le pointeur de tableau sur la fonction sans l’effet secondaire de la consommation de mémoire et l’utiliser à votre guise.
nums[0] = 10
doSomething(nums)
func doSomething(nums *numbers){
temp := nums[0]
...
}
Il est important de garder à l'esprit que ce type de matrice est une construction de stockage de bas niveau dans Go. Il est utilisé comme base pour les primitives de stockage, où il existe des exigences strictes en matière d'allocation de mémoire pour minimiser la consommation d'espace. Dans les cas où vos exigences dépendent des performances, vous devez choisir de travailler avec des tableaux (comme dans l'exemple précédent) au lieu de tranches.
Nous pouvons simplement utiliser des tranches. Exécutez le code ici: http://play.golang.org/p/WtcOvlQm01
N'oubliez pas que [3]name
est un tableau. []name
est une tranche.
package main
import "fmt"
type name struct {
X string
}
func main() {
a := []name{name{"Abbed"}, name{"Ahmad"}, name{"Ghassan"}}
nameReader(a)
}
func nameReader(array []name) {
for i := 0; i < len(array); i++ {
fmt.Println(array[i].X)
}
}
Pour en savoir plus: 50 Shades of Go: pièges, pièges et erreurs courantes pour les nouveaux développeurs de Golang