Go a un système de type très intéressant. Il évite les classes et l'héritage au profit des interfaces et de la composition, mais d'un autre côté, il n'a pas de modèles ni de génériques. La façon dont il gère les collections est également unique.
Dans ce didacticiel, vous en apprendrez plus sur les tenants et les aboutissants du système de type Go et sur son utilisation efficace pour l'écriture de code Go clair et idiomatique..
Le système de type Go prend en charge les paradigmes procéduraux, orientés objet et fonctionnels. Son support pour la programmation générique est très limité. Bien que Go soit un langage résolument statique, il offre suffisamment de flexibilité pour les techniques dynamiques via des interfaces, des fonctions de premier ordre et la réflexion. Le système de types de Go manque des fonctionnalités communes à la plupart des langues modernes:
Ces omissions sont toutes intentionnelles afin de rendre Go aussi simple que possible..
Vous pouvez créer des alias dans Go et créer des types distincts. Vous ne pouvez pas affecter une valeur du type sous-jacent à un type avec alias sans conversion. Par exemple, l'affectation var b int = a
dans le programme suivant provoque une erreur de compilation parce que le type Âge
est un alias d'int, mais c'est ne pas un int:
paquet principal type age int func main () var a Age = 5 var b int = a Sortie: tmp / sandbox547268737 / main.go: 8: impossible d'utiliser un (type Age) comme type int dans l'affectation
Vous pouvez grouper les déclarations de type ou utiliser une déclaration par ligne:
type IntIntMap map [int] int StringSlice [] type de chaîne (Taille uint64 Chaîne de texte CoolFunc func (a int, b bool) (int, erreur))
Tous les suspects habituels sont présents: booléen, chaîne de caractères, entiers et entiers non signés avec tailles de bits explicites, nombres à virgule flottante (32 et 64 bits) et nombres complexes (64 et 128 bits)..
bool string int int8 int16 int32 int64 uint8 uint16 uint32 uint32 uint64 uintptr octet // alias pour uint8 rune // alias pour int32, représente un point de code Unicode float32 float64 complex64 complex128
Les chaînes de Go sont codées en UTF8 et peuvent donc représenter n’importe quel caractère Unicode. Le paquet de chaînes fournit une multitude d'opérations de chaîne. Voici un exemple de sélection d'un tableau de mots, de leur conversion en casse appropriée et de leur association à une phrase.
package principal d'importation ("fmt" "chaînes") func main () mots: = [] chaîne "i", "J'aime", "les couleurs:", "ROUGE", "bLuE,", "et" , "GrEEn" properCase: = [] chaîne pour i, w: = mots de plage si i == 0 properCase = append (properCase, strings.Title (w)) else properCase = append (properCase, strings.ToLower (w)) phrase: = strings.Join (properCase, "") + "." fmt.Println (phrase)
Allez a des pointeurs. Le pointeur null (voir les valeurs zéro plus tard) est nil. Vous pouvez obtenir un pointeur sur une valeur en utilisant le Et
opérateur et revenir en utilisant le *
opérateur. Vous pouvez aussi avoir des pointeurs sur des pointeurs.
package principal import ("fmt") type S struct chaîne float64 b func main () x: = 5 px: = & x * px = 6 fmt.Println (x) ppx: = & px ** ppx = 7 fmt .Println (x)
Go prend en charge la programmation orientée objet via des interfaces et des structures. Il n'y a pas de classes ni de hiérarchie de classes, bien que vous puissiez incorporer des structures anonymes dans des structures, qui fournissent une sorte d'héritage unique..
Pour une exploration détaillée de la programmation orientée objet dans Go, consultez Let's Go: Programmation orientée objet dans Golang..
Les interfaces sont la pierre angulaire du système de type Go. Une interface est simplement une collection de signatures de méthodes. Chaque type qui implémente toutes les méthodes est compatible avec l'interface. Voici un exemple rapide. le Forme
interface définit deux méthodes: GetPerimeter ()
et GetArea ()
. le Carré
objet implémente l'interface.
type Interface de forme GetPerimeter () uint GetArea () uint type Square struct side uint func (s * carré) GetPerimeter () uint return s.side * 4 func (s * Square) GetArea () uint retour s.side * s.side
L'interface vide interface
est compatible avec n'importe quel type car aucune méthode n'est requise. L’interface vide peut alors pointer sur n’importe quel objet (similaire à un objet Java ou un pointeur vide C / C ++) et est souvent utilisée pour le typage dynamique. Les interfaces sont toujours des pointeurs et pointent toujours sur un objet concret.
Pour un article complet sur les interfaces Go, consultez: Comment définir et implémenter une interface Go.
Les structures sont les types définis par l'utilisateur de Go. Une structure contient des champs nommés, qui peuvent être des types de base, des types de pointeur ou d'autres types de structure. Vous pouvez également intégrer des structures de manière anonyme dans d'autres structures sous forme d'héritage d'implémentation..
Dans l’exemple suivant, les structures S1 et S2 sont intégrées à la structure S3, qui possède également sa propre int
champ et un pointeur sur son propre type:
package principal d'importation ("fmt") type S1 struct f1 int type S2 struct f2 int type S3 struct S1 S2 f3 int f4 * S3 func main () s: = et S3 S1 5, S2 6, 7, nil fmt.Println (s) Sortie: & 5 6 7
Les assertions de type vous permettent de convertir une interface en un type concret. Si vous connaissez déjà le type sous-jacent, vous pouvez simplement l'affirmer. Si vous n'êtes pas sûr, vous pouvez essayer plusieurs assertions de type jusqu'à ce que vous découvriez le bon type..
Dans l'exemple suivant, il existe une liste d'éléments contenant des chaînes et des valeurs autres que des chaînes, représentées par une tranche d'interfaces vides. Le code parcourt toutes les choses, essayant de convertir chaque élément en chaîne et de stocker toutes les chaînes dans une tranche distincte qu'il imprimera éventuellement..
package main import "fmt" func main () objets: = [] interface "hi", 5, 3.8, "ici", nil, "!" chaînes: = [] chaîne pour _, t : = range choses s, ok: = t. (chaîne) si ok chaînes = append (chaînes, s) fmt.Println (chaînes) Sortie: [hi there!]
Le feu vert réfléchir
package vous permet de vérifier directement le type d'une interface sans assertions de type. Vous pouvez également extraire la valeur d'une interface et la convertir en une interface si vous le souhaitez (inutile).
Voici un exemple similaire à l'exemple précédent, mais au lieu d'imprimer les chaînes, il les compte, il n'est donc pas nécessaire de convertir interface
à chaîne
. La clé appelle reflect.Type ()
pour obtenir un objet de type, qui a un Gentil()
méthode qui nous permet de détecter si nous avons affaire à une chaîne ou non.
package main import ("fmt" "reflect") func main () objets: = [] interface "hi", 5, 3.8, "il", nil, "!" stringCount: = 0 pour _, t: = range choses tt: = reflect.TypeOf (t) si tt! = nil && tt.Kind () == reflect.String stringCount ++ fmt.Println ("Nombre de chaînes:", stringCount)
Les fonctions sont des citoyens de première classe dans Go. Cela signifie que vous pouvez affecter des fonctions à des variables, transmettre des fonctions en tant qu'arguments à d'autres fonctions ou les renvoyer en tant que résultats. Cela vous permet d'utiliser le style de programmation fonctionnel avec Go.
L'exemple suivant montre quelques fonctions, GetUnaryOp ()
et GetBinaryOp ()
, qui renvoient des fonctions anonymes sélectionnées au hasard. Le programme principal décide s’il a besoin d’une opération unaire ou d’une opération binaire en fonction du nombre d’arguments. Il stocke la fonction sélectionnée dans une variable locale appelée "op", puis l'invoque avec le nombre correct d'arguments..
paquet principal import ("fmt" "math / rand") type UnaryOp func (un entier) int type BinaryOp un func (a, b int) entier fun GetBinaryOp () BinaryOp si rand.Intn (2) == 0 return func (a, b int) int return a + b else return func (a, b int) int return a - b func GetUnaryOp () UnaryOp if rand.Intn (2) == 0 return func (a int) int return -a else return func (a int) int return a * a func main () arguments: = [] [] int 4,5, 6, 9, 7,18, 33 var result int pour _, a: = arguments de plage if len (a) == 1 op: = GetUnaryOp () result = op (a [0]) else op: = GetBinaryOp () result = op (a [0], a [1]) fmt.Println (result)
Les canaux sont un type de données inhabituel. Vous pouvez les considérer comme des files de messages utilisées pour transmettre des messages entre plusieurs goroutines. Les canaux sont fortement typés. Ils sont synchronisés et prennent en charge la syntaxe dédiée pour l’envoi et la réception de messages. Chaque canal peut être uniquement réception, envoi uniquement ou bidirectionnel.
Les canaux peuvent également être éventuellement tamponnés. Vous pouvez parcourir les messages d’un canal à l’aide d’une plage, et les routines aller peuvent bloquer simultanément plusieurs canaux à l’aide de l’opération de sélection polyvalente..
Voici un exemple typique où la somme des carrés d'une liste d'entiers est calculée en parallèle par deux routines aller, chacune étant responsable de la moitié de la liste. La fonction principale attend les résultats des deux routines suivies, puis additionne les sommes partielles pour le total. Notez comment le canal c
est créé en utilisant le faire()
fonction intégrée et comment le code lit et écrit sur le canal via la fonction spéciale <-
opérateur.
package principal d'importation "fmt" fonction sum_of_squares (s [] int, c chan int) somme: = 0 pour _, v: = plage s somme + = v * v c <- sum // send sum to c func main() s := []int11, 32, 81, -9, -14 c := make(chan int) go sum_of_squares(s[:len(s)/2], c) go sum_of_squares(s[len(s)/2:], c) sum1, sum2 := <-c, <-c // receive from c total := sum1 + sum2 fmt.Println(sum1, sum2, total)
Ceci ne fait que gratter la surface. Pour un examen détaillé des chaînes, consultez:
Go a plusieurs collections génériques intégrées pouvant stocker n'importe quel type. Ces collections sont spéciales et vous ne pouvez pas définir vos propres collections génériques. Les collections sont des tableaux, des tranches et des cartes. Les canaux sont aussi génériques et peuvent aussi être considérés comme des collections, mais ils ont des propriétés assez uniques, je préfère donc en discuter séparément.
Les tableaux sont des collections d'éléments du même type de taille fixe. Voici quelques tableaux:
package main import "fmt" func main () a1: = [3] int 1, 2, 3 var a2 [3] int a2 = a1 fmt.Println (a1) fmt.Println (a2) a1 [1] = 7 fmt.Println (a1) fmt.Println (a2) a3: = [2] interface 3, "hello" fmt.Println (a3)
La taille du tableau fait partie de son type. Vous pouvez copier des tableaux du même type et de la même taille. La copie est par valeur. Si vous souhaitez stocker des éléments de types différents, vous pouvez utiliser la trappe d'échappement d'un tableau d'interfaces vides..
Les tableaux sont assez limités en raison de leur taille fixe. Les tranches sont beaucoup plus intéressantes. Vous pouvez considérer les tranches comme des tableaux dynamiques. Sous les couvertures, les tranches utilisent un tableau pour stocker leurs éléments. Vous pouvez vérifier la longueur d'une tranche, ajouter des éléments et d'autres tranches, et le plus amusant est d'extraire des sous-tranches similaires au découpage Python:
package main import "fmt" func main () s1: = [] int 1, 2, 3 var s2 [] int s2 = s1 fmt.Println (s1) fmt.Println (s2) // Modifier s1 s1 [ 1] = 7 // s1 et s2 désignent tous deux le même tableau sous-jacent fmt.Println (s1) fmt.Println (s2) fmt.Println (len (s1)) // Slice s1 s3: = s1 [1: len ( s1)] fmt.Println (s3)
Lorsque vous copiez des tranches, vous copiez simplement la référence dans le même tableau sous-jacent. Lorsque vous découpez, la sous-coupe pointe toujours sur le même tableau. Mais lorsque vous ajoutez, vous obtenez une tranche qui pointe vers un nouveau tableau.
Vous pouvez parcourir des tableaux ou des tranches à l'aide d'une boucle régulière avec des index ou à l'aide de plages. Vous pouvez également créer des tranches d’une capacité donnée qui seront initialisées avec la valeur zéro de leur type de données à l’aide de la touche faire()
une fonction:
package main import "fmt" func main () // Crée une tranche de 5 booléens initialisés à faux s1: = make ([] bool, 5) fmt.Println (s1) s1 [3] = true s1 [4] = true fmt.Println ("Itérer avec standard pour une boucle avec index") pour i: = 0; je < len(s1); i++ fmt.Println(i, s1[i]) fmt.Println("Iterate using range") for i, x := range(s1) fmt.Println(i, x) Output: [false false false false false] Iterate using standard for loop with index 0 false 1 false 2 false 3 true 4 true Iterate using range 0 false 1 false 2 false 3 true 4 true
Les cartes sont des collections de paires clé-valeur. Vous pouvez leur attribuer des littéraux de carte ou d'autres cartes. Vous pouvez également créer des cartes vides à l'aide de la faire
fonction intégrée. Vous accédez aux éléments à l'aide de crochets. Les cartes supportent l'itération à l'aide de intervalle
, et vous pouvez tester l'existence d'une clé en essayant d'y accéder et en vérifiant la deuxième valeur de retour booléenne facultative.
package main import ("fmt") func main () // Création d'une carte à l'aide d'un littéral de carte m: = map [int] chaîne 1: "un", 2: "deux", 3: "trois" // Affecter un élément à une clé m [5] = "cinq" // Un élément à accéder à une clé fmt.Println (m [2]) v, ok: = m [4] si ok fmt.Println (v) else fmt .Println ("Clé manquante: 4") pour k, v: = plage m fmt.Println (k, ":", v) Sortie: deux Clé manquante: 4 5: cinq 1: un 2: deux 3: trois
Notez que l'itération n'est pas dans l'ordre de création ou d'insertion.
Il n'y a pas de types non initialisés dans Go. Chaque type a une valeur zéro prédéfinie. Si une variable d'un type est déclarée sans lui attribuer de valeur, elle contient alors sa valeur zéro. Ceci est une caractéristique de sécurité importante.
Pour tout type T
, *triton)
renverra un zéro de T
.
Pour les types booléens, la valeur zéro est "false". Pour les types numériques, la valeur zéro est… zéro. Pour les tranches, les cartes et les pointeurs, c'est nul. Pour les structures, c'est une structure où tous les champs sont initialisés à leur valeur zéro.
package principal import ("fmt") type S struct une chaîne float64 b func main () fmt.Println (* new (bool)) fmt.Println (* new (int)) fmt.Println (* new ([ ] string)) fmt.Println (* new (map [int] string)) x: = * new (chaîne]) si x == nil fmt.Println ("Les tranches non initialisées sont nil") y: = * new (map [int] string) si y == nil fmt.Println ("Les cartes non initialisées sont également nil") fmt.Println (* new (S))
Go a aucun. C’est probablement la plainte la plus courante à propos du système de types de Go. Les concepteurs de Go sont ouverts à l’idée, mais ne savent pas encore comment la mettre en œuvre sans violer les autres principes de conception sous-jacents au langage. Que pouvez-vous faire si vous avez cruellement besoin de types de données génériques? Voici quelques suggestions:
Go a un système de types intéressant. Les concepteurs de Go ont pris la décision explicite de rester du côté simple du spectre. Si vous êtes sérieux au sujet de la programmation de Go, vous devriez investir du temps et en apprendre davantage sur son système de types et ses particularités. Cela vaudra bien votre temps.