Les structures et les méthodes dans le langage de programmation Go

Ce chapitre vous explique les structures et les méthodes en GoLang. Vous allez comprendre leurs intérêts et apprendre à déclarer, accéder et modifier des structures dans le langage de programmation Go.

C'est quoi une structure

Jusqu’ici pour définir une variable pouvant contenir plusieurs données on utilisait les tableaux. Le problème avec les tableaux c’est qu'ils vous obligent à utiliser le même type dans tout le tableau , d'ailleurs il nous le fait bien savoir si on tente de rentrer un autre type.

package main

import "fmt"

func main() {
    var tableau [2]int
    fmt.Println(tableau)

    tableau[0] = 5 // fonctionne bien car la valeur est du même type que notre tableau
    tableau[1] = "text"
}

Erreur :

cannot use "text" (type string) as type int in assignment

Pour résoudre ce problème on peut entre autres utiliser les structures. Une structure est tout simplement un type de données disponibles sur Go qui est défini par l'utilisateur et qui vous permet de combiner des éléments de données de différents types.

Déclarer une structure

Pour déclarer une structure, vous devez utiliser les mots-clés type et struct.

type Nom_de_ma_structure struct {
    nom_var_1 type;
    nom_var_2 type;
    ...
    nom_var_3 type;
}
  • Le mot-clé struct indique à votre compilateur qu'il s'agit d'une structure
  • Le mot-clé type permet d'associer un nom à votre structure
  • Entre les accolades vous placez vos attributs (vos variables)

Vous pouvez ensuite utiliser votre structure comme un type variable, exemple :

package main

import "fmt"

func main() {

    // déclaration de la structure nommée Personnage
    type Personnage struct {
        nom string
        age int
    }

    var perso Personnage // initialisation d'une variable de type Personnage
    fmt.Println(perso)
}

Résultat :

{ 0}

Vous pouvez surcharger les valeurs par défaut comme suit :

package main

import "fmt"

func main() {

    // déclaration de la structure nommée Personnage
    type Personnage struct {
        nom string
        age int
    }

    perso := Personnage{"Hatim", 20} // surchargement des valeurs par défaut 
    fmt.Println(perso)
}

Résultat :

{Hatim 20}

Comme résultat nous avons les valeurs par défaut des attributs contenues dans la structure Personnage

Accéder et Modifier les attributs de votre structure

Pour accéder et modifier votre attribut de votre structure, nous utiliserons un opérateur d’accès . (un point).

package main

import "fmt"

func main() {
    type Personnage struct {
        nom string
        age int
    }

    perso := Personnage{"Hatim", 20}
    fmt.Println(perso)

    // accès juste à l'attribut age de notre structure Personnage
    fmt.Println("je ne veux afficher que l'age du personnage => ", perso.age)

    perso.age = 23 // modification de l'age
    fmt.Println("3 ans plus tard ...", perso.age)
}

Résultat :

{Hatim 20]
je ne veux afficher que l'age du personnage =>  20
3 ans plus tard ... 23

L'intérêt des structures

L'intérêt d'une structure comme son nom l'indique est de mieux structurer vos variables et de les regrouper dans un seul type pour ainsi leur donner un sens.

Grâce aux structures il est possible de représenter une chose matérielle ou immatérielle qu'elle soit réelle ou fictive avec des caractéristiques (les attributs) et de lui associer des actions sous forme de code Go. Un exemple sera plus parlant :

Imaginons le scénario suivant :

Vous êtes développeur de jeu vidéo et vous avez décidé de créer votre jeu avec le langage de programmation go. Vous commencez alors par créer votre personnage basique et pour ça vous avez besoin de déclarer :

  • la vie du personnage
  • la puissance du personnage
  • le nom du personnage
  • Savoir si le personnage est mort ou pas
  • L'inventaire du personnage

Pour assouvir votre besoin on va stocker ces variables dans une structures nommée Personnage

package main

import "fmt"

// création de notre structure Personnage
type Personnage struct {
    nom        string
    vie        int
    puissance  int
    mort       bool
    inventaire [3]string
}

func main() {
    var p1 Personnage // initialisation de ma structure Personnage

    /* 
        Initialisation des attributs de mon personnage p1
    */
    p1.nom = "magix"
    p1.vie = 100
    p1.puissance = 20
    p1.mort = false
    p1.inventaire = [3]string{"potion", "bâton", "poison"}
    
    fmt.Println("Vie du personnage", p1.nom, ":", p1.vie)
    fmt.Println("Puissance du personnage", p1.nom, ":", p1.puissance)

    if p1.mort {
        fmt.Println("Vie du personnage", p1.nom, "est mort")
    } else {
        fmt.Println("Vie du personnage", p1.nom, "est vivant")
    }

    fmt.Println("\nLe personnage", p1.nom, "possède dans son inventaire :", p1.vie)

    for _, item := range p1.inventaire {
        fmt.Println("-", item)
    }

}

Résultat :

Vie du personnage magix : 100
Puissance du personnage magix : 20
Vie du personnage magix est vivant

Le personnage magix possède dans son inventaire : 100
- potion
- bâton
- poison

Structures et pointeurs

Il est possible d'utiliser les pointeurs sur des structures pour modifier directement les valeurs des attributs de notre structure dans une fonction autre que la fonction main().

On va reprendre l'exemple précédent et déclarer une fonction qui permet de surcharger les valeurs par défaut de votre personnage en combinant structures et pointeurs.

package main

import "fmt"

type Personnage struct {
    nom        string
    vie        int
    puissance  int
    mort       bool
    inventaire [3]string
}

func main() {
    // initialisation de ma structure Personnage
    var p1 Personnage

    valeurParDefaut(&p1)

    fmt.Println("Vie du personnage", p1.nom, ":", p1.vie)
    fmt.Println("Puissance du personnage", p1.nom, ":", p1.puissance)

    if p1.mort {
        fmt.Println("Vie du personnage", p1.nom, "est mort")
    } else {
        fmt.Println("Vie du personnage", p1.nom, "est vivant")
    }

    fmt.Println("\nLe personnage", p1.nom, "possède dans son inventaire :", p1.vie)

    for _, item := range p1.inventaire {
        fmt.Println("-", item)
    }

}

/*
    Déclaration d'une fonction utilisant comme paramètre
    un pointeur de structure
*/
func valeurParDefaut(p1 *Personnage) {
    p1.nom = "inconnu"
    p1.vie = 50
    p1.puissance = 10
    p1.mort = false
    p1.inventaire = [3]string{"vide", "vide", "vide"}
}

Résultat :

Vie du personnage inconnu : 50
Puissance du personnage inconnu : 10
Vie du personnage inconnu est vivant

Le personnage inconnu possède dans son inventaire : 50
- vide
- vide
- vide

Information

Pas besoin d'utiliser l'astérisque * pour accéder ou modifier une valeur de la structure pointée.

Les méthodes

Une méthode n'est rien d'autre que le nom qu'on donne à une fonction avec un récepteur défini (ici le récepteur est notre structure). Pour faire simple les méthodes sont littéralement des fonctions liées à votre structure c'est-à-dire que ce sont des fonctions qui ne peuvent être appelées que par votre structure.

Je vais dans cet exemple déclarer une méthode nommée affichage() liée à la structure Personnage :

package main

import (
    "fmt"
)

type Personnage struct {
    nom        string
    vie        int
    puissance  int
    mort       bool
    inventaire [3]string
}

func main() {
    // initialisation de ma structure Personnage
    var p1 Personnage
    var p2 Personnage

    valeurParDefaut(&p1)
    valeurParDefaut(&p2)

    p1.nom = "barbare"
    p2.nom = "magicien"

    p1.affichage()
    p2.affichage()
}

// déclaration de ma méthode affichage() lié à ma structure Personnage
func (p Personnage) affichage() { 
    fmt.Println("--------------------------------------------------")
    fmt.Println("Vie du personnage", p.nom, ":", p.vie)
    fmt.Println("Puissance du personnage", p.nom, ":", p.puissance)

    if p.mort {
        fmt.Println("Vie du personnage", p.nom, "est mort")
    } else {
        fmt.Println("Vie du personnage", p.nom, "est vivant")
    }

    fmt.Println("\nLe personnage", p.nom, "possède dans son inventaire :", p.vie)

    for _, item := range p.inventaire {
        fmt.Println("-", item)
    }
}

/*
@description : Initialise des valeurs par défaut pour un personnage

@return: void
*/
func valeurParDefaut(p1 *Personnage) {
    p1.nom = "inconnu"
    p1.vie = 50
    p1.puissance = 10
    p1.mort = false
    p1.inventaire = [3]string{"vide", "vide", "vide"}
}

Résultat :

--------------------------------------------------
Vie du personnage personnage 1 : 50
Puissance du personnage personnage 1 : 10
Vie du personnage personnage 1 est vivant

Le personnage personnage 1 possède dans son inventaire : 50
- vide
- vide
- vide
--------------------------------------------------
Vie du personnage personnage 2 : 50
Puissance du personnage personnage 2 : 10
Vie du personnage personnage 2 est vivant

Le personnage personnage 2 possède dans son inventaire : 50
- vide
- vide
- vide

Méthodes et pointeurs

On va combiner les pointeurs avec les méthodes 💥.

homme choqué

Le but principal de cette manipulation ?

Ça va nous permettre de modifier directement les valeurs des attributs de notre structure depuis une méthode. Pour le même exemple je vais créer une méthode Init() qui va initialiser les valeurs des attributs de ma structure Personnage.

package main

import (
    "fmt"
)

type Personnage struct {
    nom        string
    vie        int
    puissance  int
    mort       bool
    inventaire [3]string
}

func main() {
    // initialisation de ma structure Personnage
    var p1 Personnage
    var p2 Personnage

    p1.Init("barbare", 200, 20, false, [3]string{"épée", "bouclier", "armure"})
    p2.Init("magicien", 100, 40, false, [3]string{"potions", "poisons", "bâton"})
    p1.affichage()
    p2.affichage()
}

/*
@description : Surcharge des valeurs par defaut

@return: void
*/
func (p *Personnage) Init(nom string, vie int, puissance int, mort bool, inventaire [3]string) {
    p.nom = nom
    p.vie = vie
    p.puissance = puissance
    p.mort = mort
    p.inventaire = inventaire
}

/*
@description : Affiche des informations sur un personnage

@return: void
*/
func (p Personnage) affichage() {
    fmt.Println("--------------------------------------------------")
    fmt.Println("Vie du personnage", p.nom, ":", p.vie)
    fmt.Println("Puissance du personnage", p.nom, ":", p.puissance)

    if p.mort {
        fmt.Println("Vie du personnage", p.nom, "est mort")
    } else {
        fmt.Println("Vie du personnage", p.nom, "est vivant")
    }

    fmt.Println("\nLe personnage", p.nom, "possède dans son inventaire :", p.vie)

    for _, item := range p.inventaire {
        fmt.Println("-", item)
    }
}

Résultat :

--------------------------------------------------
Vie du personnage barbare : 200
Puissance du personnage barbare : 20
Vie du personnage barbare est vivant

Le personnage barbare possède dans son inventaire : 200
- épée
- bouclier
- armure
--------------------------------------------------
Vie du personnage magicien : 100
Puissance du personnage magicien : 40
Vie du personnage magicien est vivant

Le personnage magicien possède dans son inventaire : 100
- potions
- poisons
- bâton

Conclusion

Go n'est pas un langage de programmation purement orienté objet. Les structures et les méthodes sont concepts qui peuvent être implémentés à l'aide de Go et qui se rapprochent de la POO (programmation orientée objet).

Quand nous parlerons de packages personnalisés, on commencera à s'approcher de plus en plus des classes qu'on peut retrouver dans d'autres langages de programmation (C++, Java, Python ...).

Mon but dans ce chapitre est de vous montrer que les structures comme son nom l'indique permettent de mieux structurer notre code.

Par exemple on a réussi à transformer un scénario sous forme de texte en programme écrit Go.

Je vous conseille de reprendre mon exemple sur les personnages et de rajouter des attributs et des méthodes (Attaquer(), Soigner(), etc …) sur votre structure Personnage et pourquoi pas recréer le tp précédent à base de structures.

Espace commentaire

Écrire un commentaire

Rejoignez la discussion

Vous devez être connecté pour poster un message.

24 commentaires

ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Cool. Essaie maintenant d'ajouter une méthode Attaquer(cible *Personnage), c'est le meilleur exercice pour manipuler les pointeurs entre deux structures.

03/04/2019 à 04:20
druiz
Membre Actif
Avatar de druiz
druiz
Membre Actif

J'ai testé la surcharge de valeurs par défaut, ça marche nickel. Merci pour l'exemple.

03/04/2019 à 00:05
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Tu gardes la première lettre en minuscule. Ça les restreint au package courant. Encapsulation basique mais efficace.

02/04/2019 à 19:43
couturier-marcelle
Membre Actif
Avatar de couturier-marcelle
couturier-marcelle
Membre Actif

Ça marche. Et si je veux cacher des champs ?

02/04/2019 à 13:17
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui, il suffit de mettre une majuscule au nom du type et aux champs (ex: Nom au lieu de nom) pour les rendre publics.

02/04/2019 à 08:42
ncarre
Membre Rédacteur
Avatar de ncarre
ncarre
Membre Rédacteur

Peut-on exporter ces structures vers d'autres packages ?

02/04/2019 à 04:19
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

De rien. N'oublie pas de tester tes méthodes avec des unit tests pour vérifier que les pointeurs ne sont pas nil avant d'écrire dedans.

01/04/2019 à 20:36
xcoste
Membre Actif
Avatar de xcoste
xcoste
Membre Actif

Merci pour le bloc sur les pointeurs, c'est ce qui m'a débloqué pour mon projet de jeu.

01/04/2019 à 14:34
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est le prix de la sécurité mémoire. Au moins, tu sais exactement ce que tu manipules, pas de surprise à l'exécution.

01/04/2019 à 09:18
henri14
Membre Actif
Avatar de henri14
henri14
Membre Actif

C'est pas un peu lourd de devoir toujours déclarer le type avant ?

01/04/2019 à 01:55
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui, mais il faut d'abord définir un type personnalisé : type MonInt int. Après, tu peux lui coller des méthodes sans souci.

31/03/2019 à 20:29
adrien-lacroix
Membre Actif
Avatar de adrien-lacroix
adrien-lacroix
Membre Actif

On peut mettre des méthodes sur d'autres types que les structures ? Genre sur un int ?

31/03/2019 à 15:55
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

T'as probablement pas initialisé tes valeurs. Soit tu fais une surcharge comme perso := Personnage{"Hatim", 20}, soit tu assignes manuellement chaque champ.

31/03/2019 à 08:29
anouk-deoliveira
Membre Actif
Avatar de anouk-deoliveira
anouk-deoliveira
Membre Actif

Je bloque sur l'affichage, mon code me renvoie {0} au lieu des valeurs. J'ai oublié quoi ?

30/03/2019 à 23:39
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Une méthode possède un récepteur. Elle est attachée à un type spécifique. Une fonction classique, elle, vit sa vie toute seule sans contexte lié à une structure.

30/03/2019 à 15:59
schevalier
Membre Actif
Avatar de schevalier
schevalier
Membre Actif

C'est quoi la différence concrète entre une méthode et une fonction classique en Go ?

30/03/2019 à 11:17
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est simple, lie la fonction à ton pointeur de structure pour modifier la vie :

func (p *Personnage) Soigner(points int) { p.vie += points }
30/03/2019 à 07:02
gilles-emmanuel
Membre Actif
Avatar de gilles-emmanuel
gilles-emmanuel
Membre Actif

J'essaie d'implémenter une méthode Soigner() mais je galère avec la syntaxe du récepteur. Quelqu'un peut me montrer un exemple simple ?

30/03/2019 à 00:02
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Carrément. C'est même recommandé pour la lisibilité. Voici comment tu peux structurer ça :

type Equipement struct { nom string }
type Personnage struct { sac []Equipement }
29/03/2019 à 18:38
paulette56
Membre Actif
Avatar de paulette56
paulette56
Membre Actif

Super clair le tuto. Par contre, est-ce qu'on peut imbriquer des structures ? Genre mettre une structure Inventaire dans Personnage ?

29/03/2019 à 13:38
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Si tu passes la structure sans pointeur, tu travailles sur une copie. Tes modifs ne seront pas répercutées sur l'objet original. Regarde bien l'exemple avec *Personnage, c'est indispensable pour modifier les attributs.

29/03/2019 à 07:34
constance24
Membre Actif
Avatar de constance24
constance24
Membre Actif

Question de débutant : pourquoi utiliser un pointeur dans la méthode Init ? On ne peut pas juste passer la structure directement ?

29/03/2019 à 02:36
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Bah oui, c'est la base du typage statique en Go. Comme expliqué dans l'intro, un tableau ne prend qu'un seul type. Si tu veux mélanger, utilise une structure ou une interface.

28/03/2019 à 18:49
bboulanger
Membre Actif
Avatar de bboulanger
bboulanger
Membre Actif

J'ai testé ton exemple sur les Personnage, mais j'ai une erreur bizarre quand je tente d'assigner un type différent dans mon tableau. C'est normal ?

28/03/2019 à 14:47

Rejoindre la communauté

Recevoir les derniers articles gratuitement en créant un compte !

S'inscrire