Un guide complet des tableaux et des tranches de Golang

La traduction de l'article a été préparée spécialement pour les étudiants du cours de développeur de Golang , dont les cours commencent aujourd'hui!




Au début, il est facile de percevoir les tableaux et les tranches comme une seule et même chose, mais avec des noms différents: les deux sont une structure de données pour représenter les collections. Cependant, en réalité, ils sont très différents les uns des autres.

Dans cet article, nous examinerons leurs différences et leurs implémentations dans Go.

Nous allons nous tourner vers des exemples afin que vous puissiez prendre une décision plus éclairée sur l'endroit où les appliquer.

Tableaux


Un tableau est une collection de taille fixe. L'accent est mis ici sur une taille fixe, car dès que vous définissez la longueur du tableau, vous ne pourrez plus la modifier.

Regardons un exemple. Nous allons créer un tableau de quatre valeurs entières:

arr := [4]int{3, 2, 5, 4} 


Longueur et type


Dans l'exemple ci-dessus, la variable arr définie comme un tableau de type [4]int , ce qui signifie que le tableau se compose de quatre éléments. Il est important de noter que la taille 4 incluse dans la définition de type.

Il en résulte que, en fait, des tableaux de longueurs différentes sont des tableaux de types différents. Vous ne pouvez pas assimiler des tableaux de longueurs différentes et vous ne pouvez pas attribuer la valeur d'un tableau à un autre dans ce cas:

 longerArr := [5]int{5, 7, 1, 2, 0} longerArr = arr // This gives a compilation error longerArr == arr // This gives a compilation error 


J'ai trouvé que les tableaux sont faciles à parler en termes de structures. Si vous essayez de créer une structure similaire à un tableau, vous obtiendrez probablement ce qui suit:

 // Struct equivalent for an array of length 4 type int4 struct { e0 int e1 int e2 int e3 int } // Struct equivalent for an array of length 5 type int5 struct { e0 int e1 int e2 int e3 int e5 int } arr := int4{3, 2, 5, 4} longerArr := int5{5, 7, 1, 2, 0} 

En fait, ce n'est pas recommandé, mais c'est un bon moyen de comprendre pourquoi les tableaux de différentes longueurs sont des tableaux de différents types.


Représentation de la mémoire


Le tableau est stocké comme une séquence de n blocs d'un certain type:



Cette mémoire est allouée lorsque vous initialisez une variable de tableau.

Passer par lien


Go ne signifie pas passer par référence, mais tout est passé par valeur. Si vous affectez la valeur du tableau à une autre variable, la valeur affectée sera simplement copiée.



Si vous souhaitez transmettre uniquement une «référence» au tableau, utilisez des pointeurs:



Lors de l'allocation de mémoire et dans une fonction, un tableau est en fait un type de données simple et fonctionne de la même manière que les structures.

Tranches


Les tranches peuvent être considérées comme une implémentation étendue des tableaux.
Des tranches ont été implémentées dans Go pour couvrir certains des cas d'utilisation extrêmement courants rencontrés par les développeurs lorsqu'ils travaillent avec des collections, comme le redimensionnement dynamique des collections.

Une déclaration de tranche est très similaire à une déclaration de tableau, sauf que le spécificateur de longueur est omis:

 slice := []int{4, 5, 3} 


Si vous regardez simplement le code, il semble que les tranches et les tableaux soient assez similaires, mais leur principale différence réside dans la mise en œuvre et les conditions d'utilisation.

Représentation de la mémoire


Une tranche est allouée différemment d'un tableau et est essentiellement un pointeur modifié. Chaque tranche contient trois blocs d'informations:

  1. Pointeur vers une séquence de données.
  2. La longueur, qui détermine le nombre d'éléments actuellement contenus dans la tranche.
  3. Capacité (capacité), qui détermine le nombre total de cellules de mémoire fournies.




Il s'ensuit que des tranches de longueurs différentes peuvent être affectées les unes aux autres. Ils sont du même type et le pointeur, la longueur et le volume peuvent varier:

 slice1 := []int{6, 1, 2} slice2 := []int{9, 3} // slices of any length can be assigned to other slice types slice1 = slice2 


Une tranche, contrairement à un tableau, n'alloue pas de mémoire lors de l'initialisation. En fait, les tranches sont initialisées avec une valeur nil .

Passer par lien


Lorsque vous affectez une tranche à une autre variable, vous transmettez toujours la valeur. Ici, la valeur se réfère uniquement au pointeur, à la longueur et au volume, et non à la mémoire occupée par les éléments eux-mêmes.



Ajout de nouveaux éléments


Pour ajouter de nouveaux éléments à la tranche, vous devez utiliser la fonction append .

 nums := []int{8, 0} nums = append(nums, 8) 


Sous le capot, cela ressemblera à l'attribution d'une valeur spécifiée pour un nouvel élément, et après cela - le retour d'une nouvelle tranche. La longueur de la nouvelle tranche sera une de plus.



Si lors de l'ajout d'un élément la longueur augmente de un et dépasse ainsi le volume déclaré, il est nécessaire de prévoir un nouveau volume (dans ce cas, le volume actuel double généralement).

C'est pourquoi il est le plus souvent recommandé de créer une tranche avec la longueur et le volume spécifiés à l'avance (surtout si vous avez clairement une idée de la taille de la tranche dont vous avez besoin):

 arr := make([]int, 0, 5) // This creates a slice with length 0 and capacity 5 


Que faut-il utiliser: des tableaux ou des tranches?


Les tableaux et les tranches sont des choses complètement différentes, et donc leurs cas d'utilisation varient également.

Regardons quelques exemples open source et la bibliothèque standard Go pour comprendre quoi utiliser et quand.

Cas 1: UUID


Les UUID sont des données de 128 bits qui sont souvent utilisées pour marquer un objet ou une entité. Ils sont généralement représentés sous forme de valeurs hexadécimales, séparées par des tirets:

 e39bdaf4-710d-42ea-a29b-58c368b0c53c 


Dans la bibliothèque Google UUID , l'UUID est représenté par un tableau de 16 octets:

 type UUID [16]byte 

Cela a du sens puisque nous savons que l'UUID se compose de 128 bits (16 octets). Nous n'allons pas ajouter ou supprimer d'octets de l'UUID, et donc l'utilisation du tableau pour le représenter le sera.

Cas 2: tri des valeurs entières


Dans cet exemple, nous utiliserons la fonction sort.Ints de la bibliothèque standard de tri :

 s := []int{5, 2, 6, 3, 1, 4} // unsorted sort.Ints(s) fmt.Println(s) // [1 2 3 4 5 6] 


La fonction sort.Ints prend une tranche d'entiers et les trie par ordre croissant de valeurs. Les tranches sont préférables à utiliser ici pour deux raisons:

  1. Le nombre d'entiers n'est pas spécifié (le nombre d'entiers pour le tri peut être quelconque);
  2. Les nombres doivent être triés par ordre croissant. L'utilisation d'un tableau garantit que la collection entière d'entiers est transmise en tant que valeur, de sorte que la fonction trie sa propre copie, et non la collection qui lui est transmise.


Conclusion


Maintenant que nous avons examiné les principales différences entre les tableaux et les tranches, ainsi que leurs cas d'utilisation, je veux donner quelques conseils pour vous aider à décider plus facilement de la conception à utiliser:

  1. Si une entité est décrite par un ensemble d'éléments non vides d'une longueur fixe, utilisez des tableaux.
  2. Lorsque vous décrivez une collection à laquelle vous souhaitez ajouter ou à partir de laquelle supprimer des éléments, utilisez des tranches.
  3. Si la collection peut contenir un certain nombre d'éléments, utilisez des tranches.
  4. Allez-vous changer la collection de quelque façon que ce soit? Si c'est le cas, des tranches doivent être utilisées.


Comme vous pouvez le voir, les tranches couvrent la plupart des scénarios de création d'applications Go. Cependant, les tableaux ont le droit d'exister et, en outre, ils sont incroyablement utiles, en particulier lorsqu'un cas d'utilisation approprié apparaît.

Source: https://habr.com/ru/post/fr465613/


All Articles