0% ont trouvé ce document utile (0 vote)
206 vues276 pages

Apprendre le langage GoLang facilement

Transféré par

Hassam
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
206 vues276 pages

Apprendre le langage GoLang facilement

Transféré par

Hassam
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats DOCX, PDF, TXT ou lisez en ligne sur Scribd

Apprendre GoLang

Dans ce cours complet et gratuit vous apprendrez à


développer des applications avec le langage de
programmation GoLang.
Introduction du cours pour
apprendre le langage
programmation GO
Vous aimeriez apprendre à programmer sur le langage de
programmation GO, mais vous ne savez pas par où
commencer ? Ce cours complet de programmation sur
GoLang pour débutants est fait pour vous !

Qu'est-ce que le langage go


Histoire

Go (prononcé en anglais "Gow") aussi appelé Golang est un langage de


programmation open source relativement jeune, développé 2007 par Robert
Griesemer, Rob Pike et Ken Thompson, travaillant aujourd'hui chez Google.

Déclaration des experts à l’origine de Go lors de son lancement

« Chez Google, nous pensons que la programmation devrait être rapide, productive et
surtout, fun. C’est pourquoi nous sommes ravis de proposer ce nouveau langage de
programmation expérimental. Les opérations de compilation sont presque
instantanées, et le code compilé propose une vitesse de fonctionnement proche de
celle du C ».

les responsables du projet Go.

Pour résumé leur message : Le langage de programmation Go rime


avec efficacité et simplicité.
Le langage de programmation Go a été lancé en novembre 2009 et est utilisé dans
certains systèmes de production de Google, c’est celui la même qui a été utilisé pour
développer le logiciel de conteneurisation Docker (c'est d'ailleurs avec le projet Docker
que j'ai connu pour la première fois ce langage).

Comparaison avec d'autres langages

Go est syntaxiquement similaire au langage C mais contrairement au C il possède


une sécurité de la mémoire avec un Garbage Collector et c'est un langage à typage
statique (les types des variables sont connus lors de la compilation et doivent être
spécifiés expressément par le programmeur) comme dans d'autres langages de
programmation à savoir le Java, le C ou le C++.

Go est souvent comparé au langage Python car tous les 2 se veulent très simples
syntaxiquement. Personnellement je trouve python est plus simple syntaxiquement
mais Python reste tout de même un langage interprété, contrairement à Go qui est un
langage compilé.

Disclaimer : pas de guéguerre de langage ici :p, chaque langage de programmation a


ses avantages et ses inconvénients, ça ne m’empêche pas d'utiliser Python sur
plusieurs projets voir même de combiner les deux langages, tout dépend simplement
des besoins 😉.

Rappel sur les langages interprétés et


langages compilés

Certains d'entre vous ont peut être utilisé que des langages de programmation
interprétés, si c'est le cas alors une petite explication s'impose pour ainsi commencer
avec des bonnes bases :

 Dans un langage interprété, le code source est interprété, par un autre logiciel nommé
l’interpréteur, celui-ci traduit au fur et à mesure les instructions de votre programme.
 Dans un langage compilé, le code source est tout d'abord compilé en langage binaire
c’est une suite de 0 et de 1 uniquement compréhensible par votre machine par un
autre logiciel qu'on appelle le compilateur.
Avantages et inconvénients des langages
interprétés et langages compilés

Un programme écrit dans un langage compilé a l'avantage de ne plus avoir besoin une
fois compilé de programme annexe pour s'exécuter (un langage interprété aura
toujours besoin de son interpréteur), de plus comme votre code est exécuté
directement par votre machine alors le temps d’exécution de votre programme sera en
général plus rapide pour le même programme dans un langage interprété.

Cependant pour chaque modification de votre code source il faudra recompiler le


programme pour que les modifications prennent effet, ensuite votre programme
compilé n’est pas multi-plateforme il faudra donc créer un exécutable pour chaque
système d’exploitation à l’inverse d’un l'inverse d’un langage interprété qui lui reste en
général multi-plateforme.

Revenons à nos moutons !

Donc pour revenir à notre langage Go, c'est un langage qui se veut accessible et
rapide pour une programmation à grande échelle, il est donc concevable de l'utiliser
aussi bien pour écrire des applications, des scripts ou sur d'autres types des gros
projets.

Les avantages du langage GO


Voici une courte liste d'avantages du langage GO :

 Une meilleure protection de la mémoire grâce à son ramasse-miettes (Garbage


Collector) qui permet une gestion automatique de la mémoire.
 Profite de la puissance de calcul des processeurs les plus robustes du marché
(processeurs multi-cœurs).
 Possibilité de faire du typage dynamique et intègre de nombreux types avancés tels
que les mappages clé-valeur.
 Possède une riche bibliothèque standard, qu’il est même tout à fait possible de
concevoir des programmes écrit avec le langage Go sans aucune dépendance
externe.
 Une base de code propre nécessaire aussi pour assurer la maintenance et l'évolution
des programmes sur plusieurs générations développeurs.
 Possède un temps de compilation rapide et intègre aussi un système de build
beaucoup moins compliqué que celui de la plupart des langages de compilés.
 Au niveau de la portabilité il est possible de compiler votre code pour une large
gamme de systèmes d'exploitation et de plateformes matérielles (Windows, Linux,
MAC OS, Android, IOS).

Utilisation du langage GO
On retrouve le langage Go dans les domaines suivants (liste non exhaustive) :

 Serveurs
 Web
 Systèmes embarqués
 IOT (Internet Of Things)
 Android
 IOS
 Jeux-vidéos
 etc ...

Liste non exhaustive des entreprises utilisant Go :

 CloudFlare
 CoreOS
 DropBox
 Docker
 Nokia
 Ovh
 YouTube
 SoundCloud
 Splice
 etc ...

Public visé
Ce tutoriel est conçu pour des programmeurs ou des curieux ayant besoin de
comprendre le langage de programmation Go à partir de zéro. Ce tutoriel vous
donnera une compréhension suffisante du langage de programmation Go, qui vous
permettra d’atteindre des niveaux d’expertise plus élevés.
Guide pour configurer de
votre environnement GoLang
Ce guide de démarrage explique comment configurer un
environnement GO sur Linux et Windows et d'exécuter votre
premier programme GO.

Prérequis
Je vais commencer par vous apprendre à lancer votre tout premier programme Go !
Mais avant de d'exécuter votre programme Go il vous faut :

 Un éditeur de texte
 Un compilateur GO

Éditeur de Texte
Pour ma part j’utilise un éditeur de texte gratuit et open source à savoir Visual studio
Code avec l’extension Go, c'est une extension qui permet entres autre de :

 Faire de l'auto complétions


 Obtenir des informations quand vous passez la souris sur votre code
 Mettre en forme votre code
 Déboguer votre code
 Importer des paquets automatiquement
 etc ...

Libre à vous d’utiliser autre chose, tant que votre éditeur de texte vous permet d'écrire
du texte et de le sauvegarder alors c'est suffisant et vous pouvez passer à l'étape
suivante !
Téléchargez le compilateur Go
Pour transformer votre code source en langage machine afin que votre CPU puisse
exécuter votre programme il faut installer le compilateur GO. Le compilateur Go est
disponible sur différents OS (Linux, Mac OS X et Windows)

Téléchargez la dernière version du compilateur en cliquant ici .

Installation du compilateur Go sous Linux

Téléchargez l’archive et extrayez l'archive dans le dossier /usr/local/ avec la


commande suivante

sudo tar -C /usr/local -xzf [NOM_ARCHIVE].[Link]

Copier

Ensuite il faut ajouter le binaire de votre compilateur /usr/local/go/bin/ à votre variable


d'environnement PATH en tapant la commande suivante :

echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.bashrc

source ~/.bashrc

Copier

Installation du compilateur Go sous Windows

Exécuter le fichier MSI et suivez les instructions de votre fenêtre pour installer les
outils Go.
L'architecture de Go
Avant de lancer notre programme, il serait intéressant de comprendre l'architecture de
go.

Déja lors de votre installation des outils, go vous a créé plusieurs variables
d'environnement dont deux variables d'environnements importantes nommées
respectivement :

 GOROOT : contient comme valeur un dossier destiné à votre compilateur


 GOPATH : contient comme valeur un répertoire d’espace de travail, c'est ici qu'il cherche
les packages que vous importez

Si vous ne connaissez pas la valeur de variable d'environnement GOPATH alors vous


pouvez utiliser la commande suivante :

go envCopier

Si vous vous placez sur votre variable d'environnement GOPATH, vous observerez
l'arborescence suivante :

$GOPATH

├──── bin

├──── pkg

└──── src

Voici la description de chaque dossier :

 src : contient les sources de votre projet et des librairies utilisées


 pkg : contient des fichiers avec l'extension .a ( a pour archivé ), ces fichiers sont une
version compilée de votre code source original,
 bin : contient des commandes exécutables.
Tester votre compilateur GO
Maintenant que vous avez compris le fonctionnement de l'architecture de GO, placez
vous sur le répertoire $GOPATH/src/.

A fin de tester votre compilateur, il faut créer un programme GO et l'exécuter.

Créez un fichier et nommez le [Link] et mettez le code suivant puis sauvegardez :

package main

import "fmt"

func main() {

[Link]("Hello, World!")

Copier

Testez ensuite votre programme avec la commande suivante :

go run [Link]

Copier

Si tout se passe bien vous devriez avoir comme sortie :

Hello, World!

Bravo vous avez appris à lancer votre premier programme en GO "clap clap"👏 !
Dans le prochain chapitre je vais vous expliquer un peu plus en détail le code que
vous venez d'exécuter.
Explication pas à pas du
programme GO hello world
Ce chapitre vous explique pas à pas le code source du
programme "hello world" écrit sous le langage de
programmation GO et les commentaires en Go.

Explication du code
Reprenons le code du chapitre précédent et détaillons-le :

package main

import "fmt"

func main() {

[Link]("Hello, World!") // afficher du texte

Copier

Information

Nous verrons avec plus de détails certaines notions dans d'autres chapitres
Le package main

Commençons par la première ligne :

package main

Copier

Les programmes GO ne peuvent s’exécuter que dans des packages, c’est une
déclaration obligatoire pour chaque programme go. Il permet de définir le nom du
paquet dans lequel le programme devrait se trouver. le package main permet
d’informer votre compilateur Go que le paquet doit être compilé en tant que
programme exécutable au lieu d'une bibliothèque partagée (nous verrons cette partie
plus en détail dans un chapitre dédié aux packages).

Importation de la bibliothèque fmt

import "fmt"

Copier

Cette ligne permet d’informer votre compilateur qu’il est nécessaire d’importer la
bibliothèque fmt avant d'exécuter la suite de votre programme.

Information

fmt est une bibliothèque qui permet de formater votre texte

La fonction main

func main()

Copier

C'est une fonction nommée main() qui représente le point d’entrée du binaire c’est
donc la fonction principale du début de l'exécution de votre programme.
La fonction Println

[Link] (...)

Copier

Ici on utilise la fonction de la bibliothèque fmt à savoir Println() qui permet


d’afficher du texte avec un retour à la ligne sur votre écran.

Les commentaires
[Link]("Hello, World!") // afficher du texte

Copier

le texte situé après les deux slashs (//) est ce qu'on appelle un commentaire

Les commentaires sont des lignes de code qui sont ignorées par votre compilateur, ils
permettent à vous ou à une autre personne qui lit votre code de mieux comprendre
votre code source et inversement si vous lisez le code source de quelqu'un d'autre il
sera plus simple pour vous de mieux vous retrouver dans son code.

Information

Ça va aussi me permettre d'ajouter des explications au code source que je partagerai


avec vous sur les prochains chapitres.

Il existe deux types de commentaires :

Les commentaires sur une seule ligne ou à droite d'une instruction. Pour déclarer ce
type de commentaire il suffit de taper un double slash (// ) suivi de votre
commentaire.

Exemple :
// mon commentaire
[Link]("Hello") // cette fonction permet d'afficher le text Hello

Copier

Les commentaires sur plusieurs lignes. Pour déclarer ce type de commentaire il suffit
de taper un slash suivi d'une étoile ( /*) pour indiquer le début du commentaire suivi
de votre commentaire et enfin une étoile suivie d'une slash ( */) pour indiquer la fin du
commentaire.

Exemple :
/* Je suis un long commentaire

première ligne

deuxième ligne */
Les variables dans le
langage de programmation
Go
Ce chapitre vous explique les variables et les constantes en
GoLang. Vous allez voir les différents types de variables et
d'opérateurs mais aussi apprendre à déclarer, modifier et
calculer vos variables dans le langage de programmation GO.

Définition
Avant de vous montrer comment déclarer et utiliser une variable, il faut d'abord
comprendre ce qu'est une variable.

Rappel

"Go est un langage à typage statique (les types des variables sont connus lors de la
compilation et doivent être spécifiés expressément par le programmeur)"

Une variable est le nom donné à un emplacement mémoire pour stocker une valeur,
elle permet de stocker des données, ces données vont pouvoir être utilisées plus
tard.

Une variable est constituée d'un type. Le type d'une variable permet :

 De déterminer la quantité d'espace occupé dans la mémoire.


 D'avertir le compilateur Go de quelle manière il doit interpréter votre variable
Les différents types de variables
il existe une multitude de types de variables en go, nous allons voir les types les plus
utilisés.

Types entiers

Ce type de variable permet de stocker des nombres entiers : 1, 2, 3, 4…

Il existe deux catégories d'entiers :

 unsigned (non signé) : permet de stocker que des nombres positifs


 signed (signé): permet de stocker des nombres positifs et négatifs

Il y a plusieurs types d'entiers possibles, tout dépend de la valeur que vous voulez
stocker dans votre variable. Je vous présente ci-dessous un tableau qui liste les types
d'entiers qu'il est possible d'utiliser en GoLang ainsi que les nombres qu'ils peuvent
conserver.

Type Taille Valeurs possibles

uint8 8 bits 0 à 255

uint16 16 bits 0 à 65535

uint32 32 bits 0 à 4294967295

uint64 64 bits 0 à 18446744073709551615

int8 8 bits -128 à 127

int16 16 bits -32768 à 32767

int32 32 bits -2147483648 à 2147483647

int64 64 bits -9223372036854775808 à 9223372036854775807

Information

le bit est la plus petite valeur informatique : soit 0 soit 1.


Types flottants

Ce type permet de stocker des nombres décimaux (nombres à virgule) : 1.5, 1.6,
128.5

Type Taille Précision

float32 32 bits Environ 7 chiffres décimaux

float64 64 bits Environ 16 chiffres décimaux

Autres types numériques

Il existe également un ensemble de types numériques avec des tailles spécifiques :

Type description

byte Identique à uint8

rune Identique à int32

uint 32 ou 64 bits non signé

int Même taille que uint mais signé

Types booléens

Un booléen est un type de variables qui possède deux états :

 L'état « vrai » : true en anglais


 Ou bien l'état « faux » : false en anglais

C'est un type qui va nous permettre par exemple dans un jeu de savoir si est un joueur
est vivant ou pas.

On utilise le mot-clé bool pour déclarer une variable de type booléen

Type string

C'est un type qui nous permet de stocker des chaines de caractères (du texte).
On utilise le mot-clé string pour déclarer une variable de type chaines de caractères

Information

Je n'ai cité que les variables plus utilisées mais il faut savoir qu'il existe d'autres types
de variables que je ne vais pas détailler dans cette séance.

Déclaration des variables en GoLang


Comme la plupart des langages de programmation Il y'a certaines règles communes à
respecter pour le nommage de vos variables :

 Les espaces sont interdits


 Les accents sont interdits
 Le nom de votre variable ne peut pas débuter par un chiffre

Voici comment on déclare une variable dans le langage Go :

var [nom_de_la_variable] [type]

Copier

Exemple :
package main

import (

"fmt"

func main() {

var vie int // déclaration de la variable vie


[Link](vie) // affichage de la variable vie

Copier

Par défaut la valeur d'un type int sera égale à 0

Résultat :
0

Pour changer la valeur par défaut de la variable, il suffit de la surcharger comme ci-
dessous :

package main

import (

"fmt"

func main() {

var vie int = 12 // surchargement de la variable vie

[Link](vie)

Copier

Résultat :
12

Il est possible en Go de déclarer plusieurs variables dans la même ligne de code :

package main

import (

"fmt"

func main() {

var vie, argent, puissance int // déclaration de plusieurs


variables sur une seule ligne

[Link]("vie : ", vie)

[Link]("argent : ", argent)

[Link]("puissance : ", puissance)

Copier

Résultat :
vie : 0

argent : 0

puissance : 0
Et même de les surcharger !

package main

import (

"fmt"

func main() {

var vie, argent, puissance int = 10, 20, 30 // surchargement des


variables

[Link]("vie : ", vie)

[Link]("argent : ", argent)

[Link]("puissance : ", puissance)

Copier

Résultat :
vie : 10

argent : 20

puissance : 30

Il est possible en Go de déclarer plusieurs variables avec des types différents.


Soit avec la syntaxe classique :

package main

import (

"fmt"

func main() {

var vie int = 20

var nom string = "Default"

var vitesse float32 = 5.4

[Link]("vie : ", vie)

[Link]("nom : ", nom)

[Link]("vitesse : ", vitesse)

Copier

Résultat :
vie : 20

nom : Default
vitesse : 5.4

Soit avec cette nouvelle syntaxe :

package main

import (

"fmt"

func main() {

var (

vie int = 20

nom string = "Default"

vitesse float32 = 5.4

[Link]("vie : ", vie)

[Link]("nom : ", nom)

[Link]("vitesse : ", vitesse)

}
Copier

Résultat :
vie : 20

nom : Default

vitesse : 5.4

Typage dynamique en Go
Il est tout à fait possible en GoLang de déclarer des variables dynamiquement (pas
besoin de spécifier le type lors de la déclaration), dans ce cas on va laisser l'ordinateur
choisir pour nous le type de variables en fonction de la valeur qu'on lui a transmise.

Pour déclarer une variable dynamique, il suffit de supprimer le mot-clé var et de


remplacer le signe = par :=

Exemple :
package main

import "fmt"

func main() {

/*

Déclaration des variables dynamiques

*/

flt := 15.5 // sera automatiquement de type float


in := 5 // sera automatiquement de type int

st := "hello" // sera automatiquement de type string

bol := true // sera automatiquement de type boolean

[Link]("Le type de la varialbe flt est %T\n", flt)

[Link]("Le type de la varialbe in est %T\n", in)

[Link]("Le type de la varialbe st est %T\n", st)

[Link]("Le type de la varialbe bol est %T\n", bol)

Copier

Résultat :
Le type de la varialbe flt est float64

Le type de la varialbe in est int

Le type de la varialbe st est string

Le type de la varialbe bol est bool

Information

La fonction Printf() permet aussi d'afficher du texte et des variables mais


contrairement à la fonction Println() la fonction Printf() permet d'afficher plus
d'informations, il suffit de rajouter un symbole spécial à l'endroit où l'on veut afficher la
variable.

Voici la description de certains symboles :


 %T : affiche le type d'une valeur
 %d : affiche un entier
 %s : affiche une chaîne de caractères
 %f : affiche un nombre décimal
 %b : affiche une représentation binaire

Les constantes
Par définition une constante possède une valeur fixe, elle ne peut en aucun cas être
modifié. Voici comment on déclare une constante.

package main

import "fmt"

func main() {

const maConstante int = 50 // déclaration d'une constante

[Link]("ma Constante : ", maConstante)

Copier

Résultat :
ma Constante : 50

Tentons de modifier notre constante ⛔.

package main
import "fmt"

func main() {

const maConstante int = 50

maConstante = 50

[Link]("ma Constante : ", maConstante)

Copier

Résultat :
.\[Link]:14: cannot assign to maConstante

il nous envoie bouler. Il n'aime pas trop quand on tente de modifier ses constantes 😡.

Les calculs
Il est possible avec le langage Go de faire des opérations très simples, telles quelles :

 L'addition
 La soustraction
 La multiplication
 La division
 Le modulo (le reste d'une division)
Les opérateurs de calcul

Il existe plusieurs manières de modifier la valeur d'une variable mathématiquement,


l'une d'entre elles se fait par des opérateurs de calcul.

Opérateurs
de calcul Effet

+ Additionne deux valeurs

- Soustrait deux valeurs

* Multiplie deux valeurs

/ Divise deux valeurs

% Calcul le reste de la division de deux valeurs

Exemple :
package main

import "fmt"

func main() {

var a int = 4

var b int = 2

[Link]("a + b = ", a+b) // addition de la variable a et b

[Link]("a - b = ", a-b) // soustraction de la variable a et


b
[Link]("a * b = ", a*b) // multiplication de la variable a
et b

[Link]("a / b = ", a/b) // division de la variable a et b

[Link]("a % b = ", a%b) // modulo de la variable a et b

Copier

Résultat :
a + b = 6

a - b = 2

a * b = 8

a / b = 2

a % b = 0

Les opérateurs d'assignation

les opérateurs d'assignation permettent de modifier la valeur d'une variable


mathématiquement et de stocker le résultat dans la variable.

Opérateurs
d'assignation Effet

+= Additionne deux valeurs et stocke le résultat dans la variable

-= Soustrait deux valeurs et stocke le résultat dans la variable

*= Multiplie deux valeurs et stocke le résultat dans la variable

/= Divise deux valeurs et stocke le résultat dans la variable

%= Divise deux valeurs et stocke le reste dans la variable

Exemple :
package main

import "fmt"

func main() {

var a int = 4

var b int = 2

a += b

[Link]("a += b = ", a)

a -= b

[Link]("a -= b = ", a)

a *= b

[Link]("a *= b = ", a)

a /= b

[Link]("a /= b = ", a)
a %= 3

[Link]("a %= b = ", a)

Copier

Résultat :
a += b = 6

a -= b = 4

a *= b = 8

a /= b = 4

a %= b = 1

Les opérateurs d'incrémentation

Comme dans d'autres langages de programmation, il est possible sur Go de faire


une incrémentation (augmentation d'une unité) et une décrémentation (diminution
d'une unité) avec ce qu'on appelle des opérateurs d'incrémentation.

Opérateurs Résultat (x :=
d'incrémentation Syntaxe Effet 9)

++ augmentation d'une unité de la variable x++ 10


x

-- diminution d'une unité de la variable x x-- 8

Exemple :
package main
import "fmt"

func main() {

var a int = 4

a ++ // incrémentation

[Link]("incrémentation de 1 : ", a)

a -- // décrémentation

[Link]("décrémentation de 1 : ", a)

Copier

Résultat :
incrémentation de 1 : 5

décrémentation de 1 : 4

Ne touche pas à mon type !


J'ai évoqué au début de ce cours que le type d'une variable permet entre autres de
faire comprendre à notre compilateur Go de quelle manière il doit interpréter notre
variable

Voyons voir ceci plus en détails en additionnant deux variables de type int :
package main

import "fmt"

func main() {

var x int = 50

var y int = 30

[Link]("x + y = ", x+y) //addition de deux variables de type


int

Copier

Résultat :
x + y = %!(EXTRA int=80)

Information

Ne faites pas attention à au mot-clé EXTRA, l'important ici est le type de retour du calcul
(ici un type int) et la valeur (ici 80)

Jusqu'ici tout va bien on obtient le résultat attendu, tentons maintenant d'additionner


un type float32 avec int :

package main

import "fmt"
func main() {

var x int = 50

var y float32 = 30.5

[Link]("x + y = ", x+y) // addition d'une variable de type


int et et une autre de type float32

Copier

Erreur :
invalid operation: x + y (mismatched types int and float32)

Le compilateur Go nous avertit qu'il n'arrive pas à calculer le résultat avec des types
différents.

Pour éviter ce type erreur, il suffit soit de changer dés le début définitivement le type
de la variable, soit de faire un cast notre variable c'est à dire de convertir la variable
temporairement juste le temps de faire notre calcul pour pouvoir garder son type
original (ici x int) sur la suite de notre code.

Je vais ici choisir la 2eme solution :

package main

import "fmt"
func main() {

var x int = 50

var y float32 = 30.5

[Link]("x + y = ", float32(x )+ y) // convertir le type de la


variable x de int en float32

Copier

Résultat :
x + y = %!(EXTRA float32=80.5)

Comme le montre le résultat le type de retour de notre calcul est bien de type float32
car nous avons additionné un type float32 avec un type int convertit en float32.
Les conditions dans le
langage de programmation
Go
Ce chapitre vous explique les conditions en GoLang,. Ils
permettent de tester vos variables. On apprendra à utiliser les
mots-clés if, else if, else et switch et on fera le tour sur les
différents opérateurs existant dans le langage de
programmation Go.

Les prérequis
Maintenant que vous savez comment déclarer vos variables, il est temps de les tester
avec des conditions !

Entrée utilisateur

Avant de vous montrer comment créer des conditions, je vais d'abord vous indiquer
comment enregistrer une entrée utilisateur dans une variable (nous utiliserons les
entrées utilisateur plus tard dans la suite de ce chapitre et dans les prochains
chapitres)

Pour cela, on va utiliser la fonction NewScanner() de la bibliothèque bufio. Cette


fonction permet de créer un nouveau scanner qui nous permettra de capturer l'entrée
utilisateur.

Voici comment elle s'utilise :

package main
import (

"bufio"

"fmt"

"os"

func main() {

scanner := [Link]([Link]) // création du scanner


capturant une entrée utilisateur

[Link]("Entrez quelque chose : ")

[Link]() // lancement du scanner

entreeUtilisateur := [Link]() // stockage du résultat du


scanner dans une variable

[Link](entreeUtilisateur)

Copier

Résultat :
Entrez quelque chose : quelque chose

quelque chose
Convertir une string en entier

J'ai une autre chose à vous montrer avant de passer à la suite. Pour le moment nous
sommes capables de capturer que des chaines de caractères, dans certains cas vous
aurez besoin de capturer un type int à la place d'un type string pour faire par exemple
des calculs.

Pour ce faire, il suffit de convertir le résultat de votre scanner en int, une fonction
nommée Atoi() de la bibliothèque strconv est disponible pour répondre à ce besoin.

package main

import (

"bufio"

"fmt"

"os"

"strconv"

func main() {

scanner := [Link]([Link])

[Link]("Entrez un nombre entier : ")

[Link]()
nbr, _ := [Link]([Link]()) // conversion du type
string en int

[Link]("res : %d\n", (nbr + 6))

Copier

Résultat :
Entrez un nombre entier : 6

res : 12

Les conditions
Maintenant promis on s'attaque aux conditions 😅.

if, else

Imaginons le scénario suivant : "un videur de boîte de nuit fainéant décide


d'automatiser l'entrée des clients avec le langage de programmation Go."

Pour répondre à l'envie du videur, on va utiliser les conditions.

Pour créer une condition il suffit d'utiliser le mot-clé if (qui se traduit en français par
"si") suivit de votre condition. Ensuite vous ouvrez une accolade { et fermez-la un peu
plus loin }. Tout ce qui se trouve à l'intérieur de vos accolades sera exécuté
uniquement si la condition est bonne.

Pour revenir à notre scénario, on va d'abords commencer par respecter la loi en


refusant les mineurs.

package main
import (

"bufio"

"fmt"

"os"

"strconv"

func main() {

scanner := [Link]([Link])

[Link]("Entrez votre age : ")

[Link]()

age, err := [Link]([Link]())

if err != nil {

// Si la conversion n'a pas fonctionné alors on affiche un


message d'erreur et on quitte le programme

[Link]("On essaie de m'arnaquer ? allé Dehors ! Et la


prochaine entrez un entier !")

[Link](2) // on quitte le programmation

}
if age < 17 { // vérifier si l'utilisateur à au moins 18 ans

[Link]("Sortez !")

} else { // si ce n'est pas le cas alors on l'accepte pas

[Link]("Entrez :)")

Copier

Résultat :
Entrez votre age : 16

Sortez !

Entrez votre age : 25

Entrez :)

Information

nil est une valeur par défaut de plusieurs autres types de variables que nous
découvrirons dans d‘autres chapitres

La dernière partie avec le mot-clé else (qui se traduit en français par "autre") traite le
cas où aucune des conditions n'a été remplie (elle est optionnelle).

Les opérateurs de comparaison

Dans notre ancien exemple nous avons utilisé l'opérateur de comparaison <. En effet il
existe d'autres types d'opérateurs que je vais décrire à travers le tableau ci-dessous
Exempl
Opérateur Description e Résultat

== Compare deux valeurs et x==4 La condition est bonne si x


vérifie leur égalité est égal à 4

< Vérifie qu'une variable est x<4 La condition est bonne si x


strictement inférieure à une est strictement inférieure à
valeur 4

<= Vérifie qu'une variable est x<=4 La condition est bonne si x


inférieure ou égale à une est inférieure ou égale à 4
valeur

> Vérifie qu'une variable est x>4 La condition est bonne si x


strictement supérieure à une est strictement supérieure à
valeur 4

>= Vérifie qu'une variable est >=4 La condition est bonne si x


supérieure ou égale à une est supérieure ou égale à 4
valeur

!= Vérifie qu'une variable est >=4 La condition est bonne si x


différente à une valeur est différent à 4

Attention

Ne confondez pas l'opérateur d'égalité == avec l'opérateur d'affectation =, le premier


permet de comparer l'égalité de deux variables et le second d'affecter une valeur à
une variable !

Les opérateurs logiques

Les opérateurs logiques vont nous permettre de vérifier si plusieurs conditions sont
bonnes. Il existe trois types d'opérateurs logiques :

Opérateur Signification Description

|| OU Vrai si au moins une des comparaisons est vraie

&& ET Vrai si toutes les comparaisons sont vraies

! NON Retourne faux si la comparaison est vraie et vraie si la


comparaison est fausse
else if

Suite du scénario : "Le videur a laissé sa place à un nouveau videur très fainéant
aussi (on ne change pas une équipe qui gagne 😉) mais bon il est un peu spécial ..."

Tant mieux car au moins avec lui on va pouvoir utiliser les opérateurs logiques vu
précédemment !

Voici les caractéristiques du nouveau videur :

 Le nouveau videur n'aime pas le prénom Hatim ou hatim (je ne me sens pas visé 😢)
 Le nouveau videur a tendance à changer d'humeur de temps en temps et quand il n'est
pas content il décide alors de virer les personnes ayant 18 ans

Les opérateurs de comparaison ne vont pas nous suffire, il nous faut un moyen pour
tester d'autres conditions quand la première condition n'est pas vrai. Ce cas est
gérable avec le mot-clé else if (qui se traduit en français par "sinon si"). La syntaxe
ressemble à celle du mot-clé if.

Dans cet exemple je vais utiliser la bibliothèque math/rand pour apporter de l'aléatoire
à notre code

package main

import (

"bufio"

"fmt"

"math/rand"

"os"

"strconv"

"time"
)

func main() {

scanner := [Link]([Link])

[Link]("Entrez votre age : ")

[Link]()

age, err := [Link]([Link]())

if err != nil {

[Link]("On essaie de m'arnaquer ? allé Dehors ! Et la


prochaine entrez un entier !")

[Link](2)

[Link]("Entrez votre prenom : ")

[Link]()

prenom := [Link]()

/*

On a besoin de changer la graine (générateur de nombres


pseudo-aléatoires) à
chaque exécution de programmation sinon on obtiendra le même
nombre aléatoire

*/

[Link]([Link]().UnixNano())

radomInt := [Link](2) // retourne aléatoirement soit 1 soit 0

radomInt2 := [Link](2)

if age < 18 {

[Link]("Sortez !")

} else if prenom == "Hatim" || prenom == "hatim" {

[Link]("Ah c'est toi Hatim, dehors !")

} else if age == 18 && radomInt == 1 { // si le client est


chanceux et qu'il a 18 ans

[Link]("Hum vous avez de la chance je suis de bonne


humeur, entrez !")

} else if radomInt2 == 0 {

[Link]("Vous n'avez vraiment pas de chance sortez !")

} else {

[Link]("Entrez :)")

}
}

Copier

Résultat :
Entrez votre age : 16

Sortez !

Entrez votre age : 19

Entrez votre prenom : Hatim

Ah c'est toi Hatim, dehors !

Entrez votre age : 18

Entrez votre prenom : Fred

Hum vous avez de la chance je suis de bonne humeur, entrez !

Entrez votre age : 18

Entrez votre prenom : Alex

Vous n'avez vraiment pas de chance sortez !

switch, case

Une instruction switch permet de tester l'égalité d'une variable par rapport à une liste
de valeurs (idéal pour le choix dans un menu !).

package main

import (
"bufio"

"fmt"

"os"

"strconv"

func main() {

scanner := [Link]([Link])

[Link]("Votre choix : ")

[Link]()

choix, err := [Link]([Link]())

if err != nil {

[Link]("Entrez un entier !")

[Link](2)

switch choix { // la variable qu'on souhaite vérifier

case 0, 1: // 1 ou 0

println("George Boole !")


case 7:

println("William Van de Walle!")

case 23:

println("Pour certains, le nombre 23 est source de


nombreux mystères, qu'en penses-tu Jim Carrey?")

case 42:

println("la réponse à la question ultime du sens de la


vie!")

case 666:

println("Quand le diable veut une âme, le mal devient


séduisant.")

default:

println("Mauvais choix !") // Valeur par défaut

Copier

le mot-clé default correspond au choix par défaut comme le mot-clé else.

Résultat :
Votre choix : 666

Quand le diable veut une âme, le mal devient séduisant

Votre choix : 7
William Van de Walle!

Votre choix : 1

George Boole !

Votre choix : 0

George Boole !

Votre choix : 50

Mauvais choix !

Voici un autre exemple qui nous permet de savoir si on est en week-end ou pas, dans
cet exemple nous utiliserons les fonctions Now().Weekday() de la bibliothèque time :

package main

import (

"fmt"

"time"

func main() {

switch [Link]().Weekday() {

case [Link]:

[Link]("Il est samedi")


case [Link]:

[Link]("Il est dimanche")

default:

[Link]("au boulot ! Le week-end est fini")

Copier

Le switch dur à cuire

Sur d'autres langages de programmation il n'est pas toujours toléré d'utiliser des
opérateurs logiques ou des opérateurs de conditions dans votre instruction switch.
Sur Go s'est toléré, mais dans ce cas il ne faut pas rajouter votre variable après votre
mot-clé switch (d'ailleurs vous être libre de vérifier n'importe quoi vu que le switch ne
se base plus sur une variable).

package main

import (

"bufio"

"fmt"

"os"

"strconv"

"time"
)

func main() {

scanner := [Link]([Link])

[Link]("Votre choix : ")

[Link]()

choix, err := [Link]([Link]())

if err != nil {

[Link]("Entrez un entier !")

[Link](2)

switch {

case choix >= 2000:

println("Ah un 2000 !")

case choix >= 1939 && choix <= 1945:

println("Triste année")

case [Link]().Weekday() == [Link]:

println("Dimanche !")
default:

println("Mauvais choix !") // Valeur par défaut

Copier

Votre choix : 2000

Ah un 2000 !

Votre choix : 1942

Triste année

Votre choix : 0

Mauvais choix !
Les boucles dans le langage
de programmation Go
Ce chapitre vous explique les boucles en GoLang. Les
boucles permettent d'itérer un bloc de code plusieurs fois. On
apprendra à utiliser la boucle for avec les mots-clés break et
continue dans le langage de programmation Go.

Description
Une boucle est un moyen d'exécuter un bloc de code plusieurs fois, suivant le
résultat d’une condition ou selon un nombre d'itérations connu à l'avance.

Information

Une itération correspond à un tour de boucle

Boucle for
Pour déclarer une boucle il suffit d'utiliser le mot-clé for.

Il existe deux types de boucle for, soit vous connaissez au préalable le nombre
d'itérations (par exemple vous savez dès le début qu'un bloc de code va s'exécuter x
fois) ou inversement vous n'avez aucune idée du nombre d'itérations de votre bloc de
code.

Vous connaissez déjà le nombre d'itérations

Voici comment on déclare une boucle quand vous connaissez antérieurement le


nombre d'itérations :
for initialisation ; condition ; itération {

/* le code qui sera répété */

Copier

Où initialisation est une instruction exécutée avant le premier parcours de la


boucle for c'est là-bas qu'on initialise le compteur de la boucle (le compteur est une
variable temporaire qui est détruite à la sortie de la boucle) suivit d'une condition du
compteur dont la valeur déterminera la fin de la boucle, l'itération est l'opération qui
déterminera le nombre de répétitions de votre bloc de code.

Imaginez le scénario suivant : "Vous êtes jeune, fainéant et mauvais élève (pas bien !).
Votre professeur vous demande alors d'écrire 100 fois la phrase suivante "Je ne dois
frapper mes camarades de classe". ça tombe vous êtes en train de suivre un cours sur
les boucles 😉 !"

package main

import (

"fmt"

func main() {

for compteur := 0; compteur < 100; compteur++ {

[Link](compteur + 1, ") Je ne dois frapper mes camarades


de classe")
}

Copier

Résultat :
1 ) Je ne dois taper mes camarades de classe

2 ) Je ne dois taper mes camarades de classe

...

99 ) Je ne dois taper mes camarades de classe

100 ) Je ne dois taper mes camarades de classe

Vous ne connaissez pas le nombre d'itérations

Voici comment on déclare une boucle quand vous ne connaissez pas à l'avance le
nombre d'itérations :

for condition {

/* le code qui sera répété */

Copier

Si la condition est vraie alors on reste dans la boucle sinon on quitte la boucle.

Imaginez le scénario suivant : "Un videur de boîte de nuit refuse l'entrée des mineurs"

package main
import (

"bufio"

"fmt"

"os"

"strconv"

func main() {

scanner := [Link]([Link])

var age int

for age < 18 { // on quitte la boucle s'il est majeur

[Link]("Entrez votre age : ")

[Link]()

age, _ = [Link]([Link]())

[Link]("Bienvenue en boite de nuit !")

}
Copier

Résultat :
Entrez votre age : 17

Entrez votre age : 19

Bienvenue en boite de nuit

break et continue
Pour manipuler vos boucles infinies il peut être intéressant d'utiliser les deux mots-clés
suivants :

 break : Interrompre une boucle


 continue : Revenir au début de la boucle

Imaginons le scénario suivant : "Vous avez bien envie de jouer à un jeu hasard mais le
problème c'est que vous n'avez pas assez d'argent 😢 par contre vous avez votre
compilateur go 😉."

Vous n'avez pas d'argent ? Pas de panique les boucles infinies vont vous aider à sortir
de cette crise !

package main

import (

"bufio"

"fmt"

"math/rand"

"os"
"strconv"

"time"

func main() {

[Link]([Link]().UnixNano())

randomInt := [Link](10)

scanner := [Link]([Link])

max := 20

for true { // boucle infinie

[Link]("Entrez votre nombre : ")

[Link]()

nbr, err := [Link]([Link]())

if err != nil {
[Link]("Entrez un nombre !")

continue // on revient au début de la boucle

if nbr > max || nbr < 0 {

[Link]("Votre nombre doit être compris entre 0 et ",


max, " !")

continue

} else if nbr == randomInt {

[Link]("Bien joué !")

break // on quitte la boucle

} else {

[Link]("Dommage !")

Copier

Résultat :
Entrez votre nombre : 50

Votre nombre doit être compris entre 0 et 20 !

Entrez votre nombre : arnaque


Entrez un nombre !

Entrez votre nombre : 6

Dommage !

Entrez votre nombre : 15

Dommage !

Entrez votre nombre : 3

Bien joué !
Les fonctions dans le
langage de programmation
Go
Ce chapitre vous explique les fonctions en GoLang, On
apprendra aussi à déclarer et utiliser les différents types de
fonctions dans le langage programmation GO.

Explication
Les fonctions vont nous permettre de structurer nos programmes en un groupe
d'instructions qui effectueront un ensemble de tâches.

Elles permettent de simplifier notre code et de le rendre beaucoup plus lisible que ce
soit pour nous ou pour les autres, mais surtout (rappelez vous que vous êtes un bon
flemmard 😎) elles nous permettrons de ne pas retaper le même code plusieurs fois
d’affiler.

Information

La bibliothèque standard Go fournit de nombreuses fonctions intégrées que votre


programme peut appeler comme par exemple la fonction Println() de la
bibliothèque fmt.

Déclarer une fonction


Pour déclarer une fonction il nous faut :

 Le nom de la fonction (non obligatoire si votre fonction est anonyme, on verra les
fonctions anonymes à la fin de ce chapitre) : C'est le nom qui décrit votre fonction, il
faut juste penser à respecter les mêmes règles que pour les variables (pas d'accents,
pas d'espaces, etc.). GoLang vous recommande de nommée vos fonctions en Camel
case, c'est à dire que chaque mot commence par une majuscule à l’exception du
premier.
 Le type de retour de la fonction (non obligatoire si votre fonction ne retourne rien) :
comme les variables les fonctions ont un type, plus précisément c'est le type de la
valeur qu'elle retourne.
 Des paramètres (non obligatoire) : Ce sont des variables que la fonction va exploiter
dans son bloc de code.

func nomDeLaFonction( liste_de_paramètres ) type_de_retour

/* votre code */

Copier

Avertissement

Une fonction non anonyme doit être déclarée en dehors de la fonction main()

Fonction sans type de retour et sans


paramètres

Voici comment on déclare une fonction sans type de retour et sans aucun paramètre.

package main

import (

"fmt"

)
// déclaration de la fonction affichage()

func affichage() {

[Link]("#################################")

[Link]("\tBonjour")

[Link]("#################################")

func main() {

affichage() // appel de la fonction affichage()

Copier

Résultat :
#################################

Bonjour

#################################

Fonction sans type de retour mais avec des


paramètres

Une fonction est capable de prendre autant de paramètres que vous voulez et peu
importe leurs types.

package main
import (

"fmt"

// prend en paramètre un type string et un int

func affichage(nom string, age int) {

[Link]("Bonjour", nom, "vous avez", age, "ans")

func main() {

affichage("Hatim", 9)

affichage("Alex", 12)

Copier

Résultat :
Bonjour Hatim vous avez 9 ans

Bonjour Alex vous avez 12 ans


Fonction avec un type de retour

On utilisera le mot-clé return pour renvoyer une valeur depuis notre fonction. Vous
pouvez ensuite stocker la valeur retournée par votre fonction dans une variable.

package main

import "fmt"

// Fonction qui retourne un type int

func maxNbr(a int, b int) int {

if a > b {

return a // retourne le variable a de type int

return b // retourne le variable b de type int

func main() {

max := maxNbr(10, 30) // stockage du retour de la fonction dans


une variable

[Link](max)

// Utilisation directe du retour de la fonction sans la stocker


dans une variable
[Link]("Valeur : %d , Type : %T\n", maxNbr(50, 30),
maxNbr(50, 30))

Copier

Résultat :
30

Valeur : 50 , Type : int

Je veux plus de valeurs !


Sur l'exemple précédant notre fonction ne retournait qu'un seul type de retour, dès fois
nous aurons besoin de retourner plus de valeurs. GoLang nous propose cette
fonctionnalité pour renvoyer autant de valeurs souhaitées.

package main

import (

"fmt"

func main() {

a := 5

b := 8

[Link]("Avant fonction a =", a, " b =", b)


a, b = additionTrois(a, b) // stockage du retour de la fonction
dans deux variables

[Link]("Après fonction a =", a, " b =", b)

// retourne deux types int

func additionTrois(a int, b int) (int, int) {

return a + 3, b + 3

Copier

Résultat :
Avant fonction a = 5 b = 8

Après fonction a = 8 b = 11

Je veux plus de paramètres !


Il est possible en Go de déclarer une fonction qui peut prendre en compte des
paramètres infinis aussi appelés paramètres variadiques .

Pour ce faire il faut rajouter des trois points ... collés avant le type du paramètre.

Exemple :
package main
import (

"fmt"

func main() {

[Link](addition(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13))

// déclaration d'une fonction avec des paramètres infinis

func addition(param ...int) int {

total := 0

for _, value := range param { //j'ai mis un underscore "_" car je


ne souhaite pas récupérer l'index de la range

total += value

return total

Copier

Résultat :
91
Une fonction anonyme
Les fonctions anonymes sont utilisées lorsque nous voulons définir une fonction sans
lui attribuer de nom , elles peuvent être déclarées et appelées directement depuis
n'importe bloc de votre code enfin elles peuvent aussi être utilisées en tant que
paramètres.

Je vais dans cet exemple déclarer et utiliser une fonction anonyme en tant que
paramètre.

package main

import (

"fmt"

"math"

// Déclaration d'une fonction qui prend en paramètres un float64 et


une fonction anonyme

func rajouterDix(a float64, fAnonyme func(float64) float64) /**/ {

operation := fAnonyme(a) // Appel à notre fonction anonyme

result := operation + 10

[Link](result)

}
func main() {

// stockage de notre fonction anonyme dans une variable

racineCarree := func(x float64) float64 { return [Link](x) }

rajouterDix(9, racineCarree)

/*

il est possible aussi d'utiliser directement une fonction


anonyme

dans une variable sans forcement la stocker dans une variable

*/

rajouterDix(5, func(x float64) float64 { return [Link](x, 2) })

Copier

Résultat :
13

35
La portée des variables dans
le langage de programmation
Go
Ce chapitre vous explique les portée des variables en GoLang.
Vous allez apprendre à différencier les différents types de
portée à savoir : les variables locales, variables globales et
paramètres formels.

Présentation
Quand on parle de portée des variables on parle d’endroits dans notre code où on
peut utiliser telle ou telle variable.

On peut résumer la portée des variables par la question suivante :

"Est-ce que ma variable est accessible dans tel ou tel bloc de mon code ?"

On peut différencier la portée de nos variables par des :

 Variables locales
 Variables globales
 Paramètres formels

Variables locales
Les variables déclarées à l'intérieur d'une fonction ou d'un bloc (un bloc est tout
simplement la partie de votre code dans des accolades) sont appelées variables
locales. Elles ne peuvent être accessibles qu’à l’intérieur de votre fonction ou d'un
bloc de code.

Exemple d'une variable accessible que depuis un bloc :

package main

import (

"fmt"

func test() {

for i := 1; i < 3; i++ {

a := 20

a *= i

[Link](a)

func main() {

test()

}
Copier

Résultat :
20

40

Jusqu’ici rien de choquant par contre si on tente de manipuler la variable a en dehors


de notre bloc …

package main

import (

"fmt"

func test() {

for i := 1; i < 3; i++ {

a := 20

a *= i

[Link](a)

[Link](a) // erreur ici

}
func main() {

test()

Copier

Erreur :
undefined: a

Si on souhaite exploiter notre variable à la fois dans notre fonction et à la fois dans
notre bloc alors il suffit de la déclarer au début de notre fonction, comme ceci

package main

import (

"fmt"

func test() {

a := 20 // déclaration de notre variable locale

for i := 1; i < 3; i++ {

a *= i

[Link]("dans ma boucle for :", a)


}

[Link]("en dehors de ma boucle for :", a)

func main() {

test()

Copier

Résultat :
dans ma boucle for : 20

dans ma boucle for : 40

en dehors de ma boucle for : 40

On va maintenant tenter d’utiliser une variable en dehors d'une fonction

package main

import (

"fmt"

func test() {
a := 10

a += 20

[Link](a)

func main() {

test()

[Link](a) // l'erreur vient d'ici

Copier

Erreur :
undefined: a

Hum ça ne fonctionne pas, il serait peut-être temps de voir comment ça se passe au


niveau des variables globales.

Variables globales
Les variables globales sont définies en dehors de vos fonctions (généralement au
début de votre programme). À l'inverse des variables locales elles conservent leur
valeur pendant toute la durée de vie du programme et sont accessibles à l’intérieur de
n’importe quelles fonctions définies dans votre programme.

Ça tombe bien car ça va résoudre notre problème !

package main
import (

"fmt"

var g int // déclaration de notre variable globale

func test() {

g += 20

[Link]("Pendant ma fonction test() : ", g)

func main() {

[Link]("Avant l'utilisation de la fonction test() :", g)

test()

[Link]("Pendant ma fonction main() : ", g)

g += 30

[Link]("Modifie moi encore : ", g)

Copier

Résultat :
Avant l'utilisation de la fonction test() : 0
Pendant ma fonction test() : 20

Pendant ma fonction main() : 20

Modifie moi encore : 50

Avertissement

Comme vous vous pouvez le constater la modification d'une variable globale


est permanente quel que soit l'endroit où elle est modifiée.

Paramètres formels
Les paramètres formels sont traités comme des variables locales dans une fonction
par contre ils auront toujours une priorité sur les variables globales.

package main

import (

"fmt"

var g int // déclaration de notre variable formel

func test(g int) { // déclaration de notre paramètre globale

g += 20 // prend le dessus sur notre variable globale

[Link]("Pendant ma fonction test() : ", g)


}

func main() {

[Link]("Avant l'utilisation de la fonction test() :", g)

test(20)

[Link]("Pendant ma fonction main() : ", g)

g += 30

[Link]("Modifie moi encore : ", g)

Copier

Résultat :
Avant l'utilisation de la fonction test() : 0

Pendant ma fonction test() : 40

Pendant ma fonction main() : 0

Modifie moi encore : 30


Les tableaux dans le langage
de programmation Go
Ce chapitre vous explique les tableaux en GoLang. Vous allez
apprendre à initialiser et modifier un tableau et d'accéder à des
éléments du tableau dans le langage de programmation GO.

Pourquoi les tableaux


Imaginez que vous avez besoin de déclarer 100 variables de type int, est-ce que vous
allez écrire 100 fois cette ligne ?

var var1 int

var var1 int

var var2 int

...

var var100 int

Copier

La réponse est NON. Ça sera long et fastidieux, rappelez vous aussi que vous êtes un
bon flemmard. Donc impossible pour vous de passer votre temps à déclarer plusieurs
variables !

La solution dans ce cas c'est de créer un tableau de type int. Un tableau (array en
anglais) est une structure de données qui permet de stocker une collection
séquentielle de taille fixe de variables du même type.
Dans ce chapitre nous allons voir que des tableaux statiques, c'est-à-dire des
tableaux dont la taille est connue pendant la déclaration, nous verrons les tableaux
dynamiques dans un chapitre dédié aux Slice().

Initialisation des tableaux


Voici comment on déclare un tableau statique

var nomTableau [taille] type

Copier

Où taille est un nombre entier qui correspond au nombre de variables que le tableau
est capable de stocker suivit du type de variable qu'il peut supporter.

Revenons à notre exemple où il fallait déclarer 100 variables de type int, en tableau
statique ça donnera quelque chose comme ça :

var tableauInt [100]int

Copier

On peut déclarer n'importe quel type de tableau (int, float64 etc ...) et les variables
dans votre tableau auront la même valeur par défaut que d'habitude.

Exemple :

package main

import (

"fmt"

)
func main() {

var tableauInt [10]int

var tableauFloat [10]float32

var tableauString [10]string

var tableauBool [10]bool

[Link]("Valeur par défaut de la variable tableauInt :",


tableauInt)

[Link]("Valeur par défaut de la variable tableauFloat :",


tableauFloat)

[Link]("Valeur par défaut de la variable tableauString :",


tableauString)

[Link]("Valeur par défaut de la variable tableauBool :",


tableauBool)

Copier

Résultat :

Valeur par défaut de la variable tableauInt : [0 0 0 0 0 0 0 0 0 0]

Valeur par défaut de la variable tableauFloat : [0 0 0 0 0 0 0 0 0 0]

Valeur par défaut de la variable tableauString : [ ]

Valeur par défaut de la variable tableauBool : [false false false false false false
false false false false]
Vous pouvez aussi surcharger les valeurs par défaut de votre tableau grâce à des
accolades.

package main

import (

"fmt"

func main() {

var tableau1 = [5]int{1, 2, 3, 4, 5}

[Link]("la taille de mon tableau1 :", len(tableau1))

[Link]("les valeurs de mon tableau1 :", tableau1)

Copier

Information

la fonction len() retourne la taille de votre tableau

Résultat :

la taille de mon tableau1 : 5

les valeurs de mon tableau1 : [1 2 3 4 5]


Accéder aux éléments du tableau
Depuis l'index du tableau

les variables dans votre tableau sont accessibles depuis leur index (numéro de la
case).

Attention

Le premier index commence toujours à la case 0 !

Voici un tableau de type string qui stocke les jours de la semaine :

package main

import (

"fmt"

func main() {

var jours = [7]string{"lundi", "mardi", "mercredi", "jeudi",


"vendredi", "samedi", "dimanche"}

[Link]("jours[0] =", jours[0])

[Link]("jours[1] =", jours[1])

[Link]("jours[2] =", jours[2])


[Link]("jours[3] =", jours[3])

[Link]("jours[4] =", jours[4])

[Link]("jours[5] =", jours[5])

[Link]("jours[6] =", jours[6])

Copier

Résultat :

jours[0] = lundi

jours[1] = mardi

jours[2] = mercredi

jours[3] = jeudi

jours[4] = vendredi

jours[5] = samedi

jours[6] = dimanche

On peut très vite se tromper en essayant d'accéder à la dernière valeur de notre


tableau comme ci-dessous :

package main

import (

"fmt"
)

func main() {

var jours = [7]string{"lundi", "mardi", "mercredi", "jeudi",


"vendredi", "samedi", "dimanche"}

[Link]("jours[7] =", jours[7]) // erreur

Copier

Erreur :

invalid array index 7 (out of bounds for 7-element array)

Sauf que ça ne fonctionne pas car votre compilateur vous explique que vous être hors
limites de votre tableau, l'index que vous avez rentré est non valide. Rappelez-vous
que le premier index est toujours égal à 0, il faut donc prendre en considération le 0
quand vous calculez la taille de votre tableau, ici la valeur du dernier index est égale à
6 et non à 7.

Voici un moyen plus simple pour récupérer automatiquement la dernière valeur de


votre tableau en utilisant la fonction len() :

package main

import (

"fmt"

)
func main() {

var jours = [7]string{"lundi", "mardi", "mercredi", "jeudi",


"vendredi", "samedi", "dimanche"}

[Link]("Dernier jour =", jours[len(jours)-1]) // taille du


tableau - 1 = dernier index

Copier

Résultat :

Dernier jour = dimanche

Boucle sur un tableau

Vous pouvez aussi parcourir la totalité de vos variables grâce à la boucle for en
utilisant le mot-clé range.

le mot-clé range retourne à la fois l'index et la valeur de l'element itéré du tableau

package main

import (

"fmt"

)
func main() {

var jours = [7]string{"lundi", "mardi", "mercredi", "jeudi",


"vendredi", "samedi", "dimanche"}

// récupération de l'index et de la valeur

for index, j := range jours {

[Link](j, "est le jour numéro", (index + 1), "de la


semaine")

Copier

Résultat :

lundi est le jour numéro 1 de la semaine

mardi est le jour numéro 2 de la semaine

mercredi est le jour numéro 3 de la semaine

jeudi est le jour numéro 4 de la semaine

vendredi est le jour numéro 5 de la semaine

samedi est le jour numéro 6 de la semaine

dimanche est le jour numéro 7 de la semaine


Boucle for len

Il est concevable aussi d'utiliser une boucle for classique en récupérant la taille du
tableau avec la fonction len()

package main

import (

"fmt"

func main() {

var jours = [7]string{"lundi", "mardi", "mercredi", "jeudi",


"vendredi", "samedi", "dimanche"}

for i := 0; i < len(jours); i++ {

[Link](jours[i], "est le jour numéro", (i + 1), "de


la semaine")

Copier

Résultat :

lundi est le jour numéro 1 de la semaine


mardi est le jour numéro 2 de la semaine

mercredi est le jour numéro 3 de la semaine

jeudi est le jour numéro 4 de la semaine

vendredi est le jour numéro 5 de la semaine

samedi est le jour numéro 6 de la semaine

dimanche est le jour numéro 7 de la semaine

Récupération rapide et condensée

Il est possible comme sur le langage de programmation python de récupérer des


valeurs de notre tableau grâce à l'opérateur :. Voici quelques cas d'utilisation :

- Récupérer tous les éléments de notre tableau :

package main

import (

"fmt"

func main() {

var jours = [7]string{"lundi", "mardi", "mercredi", "jeudi",


"vendredi", "samedi", "dimanche"}

[Link](jours[:]) // on récupère tous les éléments

}
Copier

Résultat :

[lundi mardi mercredi jeudi vendredi samedi dimanche]

- Récupérer les trois premiers éléments de notre tableau :

package main

import (

"fmt"

func main() {

var jours = [7]string{"lundi", "mardi", "mercredi", "jeudi",


"vendredi", "samedi", "dimanche"}

[Link](jours[:3]) // trois premiers éléments

Copier

Résultat :

[lundi mardi mercredi]

- Récupérer tous les résultats à partir du troisième index :

package main
import (

"fmt"

func main() {

var jours = [7]string{"lundi", "mardi", "mercredi", "jeudi",


"vendredi", "samedi", "dimanche"}

[Link](jours[3:])

Copier

Résultat :

[jeudi vendredi samedi dimanche]

- Récupérer les résultats du premier jusqu'au troisième index (exclus) :

package main

import (

"fmt"

func main() {
var jours = [7]string{"lundi", "mardi", "mercredi", "jeudi",
"vendredi", "samedi", "dimanche"}

[Link](jours[1:3])

Copier

Résultat :

[mardi mercredi]

Changer les valeurs des éléments du


tableau
Il est permis de modifier la valeur d'une variable dans un tableau en récupérant l'index
de l’élément en question

monTableau [indexDeLElement] = nouvelleValeur

Exemple :

package main

import "fmt"

func main() {

var jours = [5]string{"Hatim", "Robert", "Inconnu", "Ahmed",


"Inconnu"}
jours[0] = "Alex" // on remplace le premier element (ici Hatim) par
Alex

[Link](jours)

jours = replaceByHatim(jours)

[Link](jours)

/*

J'utilise une fonction pour vous montrer qu'il est

possible de prendre en paramètre un tableau

mais aussi de retourner un tableau dans une fonction

*/

func replaceByHatim(jours [5]string) [5]string {

for i, jour := range jours {

if jour == "Inconnu" {

jours[i] = "Hatim" // Remplacer "Inconnu" par "Hatim"

return jours
}

Copier

Résultat :

[Alex Robert Inconnu Ahmed Inconnu]

[Alex Robert Hatim Ahmed Hatim]

Tableau à deux dimensions


Initialisation des tableaux à deux dimensions

Un tableau à deux dimensions n'est rien d'autre qu'un tableau qui contient d'autres
tableaux (des tableaux dans un tableau).

Un tableau à deux dimensions est composé de :

 lignes : correspond au nombre des sous tableaux inclus dans le tableau principal.
 colonnes: correspond au nombre d'éléments dans les sous tableaux.

Un tableau à deux dimensions peut par exemple être utilisé pour la construction d'une
map dans un jeu 2D.
« Map d'un jeu construite depuis un tableau à double dimensions »

Exemple :

package main

import "fmt"

func main() {

const (

maxLigne int = 3 // 3 sous tableaux

maxColonne int = 4 // 4 éléments pour chaque sous tableau

)
var tableau [maxLigne][maxColonne]int // Création d'un tableau à double
dimension

[Link](tableau)

Copier

Résultat :

[[0 0 0 0] [0 0 0 0] [0 0 0 0]]

Modifier les valeurs des tableaux à deux


dimensions

Pour modifier la valeur d'un tableau à deux dimensions, il nous faut :

 index de la ligne : permet de récupérer un tableau bien spécifique dans le tableau


principal (ex : s'il est égal à 0 alors on récupère le premier tableau du tableau principal).
 index de la colonne : récupérer un élément depuis tableau spécifique.

package main

import "fmt"

func main() {

const (

maxLigne int = 3

maxColonne int = 3
)

var doubleTableau [maxLigne][maxColonne]int

[Link](doubleTableau)

[Link]("----------------------")

//modification de la ligne 3, colonne 2

doubleTableau[2][1] = 5 // modification du 2eme élément du 3eme


tableau

[Link](doubleTableau)

chaine := "Hello"

[Link](chaine)

Copier

Résultat :
[[0 0 0] [0 0 0] [0 0 0]]

----------------------

[[0 0 0] [0 0 0] [0 5 0]]
TP Le jeu du morpions dans
le langage de programmation
Go
Dans ce chapitre vous allez commencer votre premier TP sur
le langage de programmation GO en créant un jeu de
morpions en GoLang.

Présentation du TP
Il est temps de pratiquer un peu !

Je vous ai préparé un TP qui reprend la base de tout ce qu'on a pu étudier jusqu’ici.

Nous allons créer un jeu de morpion appelé aussi le tic tac toe avec le langage de
programmation Go.

Image du jeu de morpion


Avant de commencer la création du jeu voici déjà quelques règles à respecter :

 Le jeu se déroulera sur un damier de 3 cases par 3 cases


 Le jeu se joue à deux. Un premier joueur dessine son symbole sur une case. Puis c'est
au tour de l'autre joueur de dessiner son symbole sur une case libre.
 Pour gagner il faut aligner 3 symboles identiques horizontalement, verticalement ou en
diagonale avant le tour de l'adversaire
 Quand toutes les cases sont remplîtes alors il y a match nul.
 Vérifier toujours si l'utilisateur a rentré les valeurs attendues "Never trust user input" !
 Le jeu va avoir lieu sur l'invite de commande.

Libre à vous de choisir le design que vous souhaitez, en ce qui me concerne voici à
quoi ressemble mon jeu :

1 2 3

4 5 6

7 8 9

Joueur 1 entrez un nombre compris entre 1 à 9 : 1

X 2 3

4 5 6

7 8 9

Joueur2 entrez un nombre compris entre 1 à 9 : 2

X O 3

4 5 6

7 8 9

Joueur 1 entrez un nombre compris entre 1 à 9 : 5

X O 3

4 X 6
7 8 9

Joueur2 entrez un nombre compris entre 1 à 9 : 6

X O 3

4 X O

7 8 9

Joueur 1 entrez un nombre compris entre 1 à 9 : 9

Joueur 1 vous avez gagné !

Information

Ici les chiffres correspondent à des cases libres. Ça permet à l'utilisateur de savoir si
sa case est libre. Libre à vous bien sûr de mettre autre chose à la place.

Gestion des différents cas


Voici la sortie standard des différents cas d'usage :

- Si le joueur rentre autre chose que ce que je souhaite :

1 2 3

4 5 6

7 8 9

Joueur 1 entrez un nombre compris entre 1 à 9 : 15

Votre nombre doit être compris entre 0 à 9 !

Joueur 1 entrez un nombre compris entre 1 à 9 : dsdsds


Entrez un nombre et non autre chose !

Joueur 1 entrez un nombre compris entre 1 à 9 : 1

X 2 3

4 5 6

7 8 9

Joueur2 entrez un nombre compris entre 1 à 9 : 1

Cette case est déjà prise !

- Si il y a match nul :

1 2 3

4 5 6

7 8 9

Joueur 1 entrez un nombre compris entre 1 à 9 : 1

X 2 3

4 5 6

7 8 9

Joueur2 entrez un nombre compris entre 1 à 9 : 4

X 2 3

O 5 6

7 8 9
Joueur 1 entrez un nombre compris entre 1 à 9 : 7

X 2 3

O 5 6

X 8 9

Joueur2 entrez un nombre compris entre 1 à 9 : 5

X 2 3

O O 6

X 8 9

Joueur 1 entrez un nombre compris entre 1 à 9 : 2

X X 3

O O 6

X 8 9

Joueur2 entrez un nombre compris entre 1 à 9 : 3

X X O

O O 6

X 8 9

Joueur 1 entrez un nombre compris entre 1 à 9 : 6

X X O

O O X
X 8 9

Joueur2 entrez un nombre compris entre 1 à 9 : 9

X X O

O O X

X 8 O

Joueur 1 entrez un nombre compris entre 1 à 9 : 8

X X O

O O X

X X O

Partie nulle !

Sachez que vous possédez les connaissances nécessaires vues dans les chapitres
précédents, et donc vous êtes tout à fait capables de réaliser ce jeu.

Good Luck ! et à vous de jouer !

Solution
J'espère que vous avez réussi à réaliser ce tp ! Même si vous n'avez pas forcément
réussi à tout faire. L'essentiel c'est qu'au moins quelques fonctionnalités du jeu
fonctionnent, ça restera toujours mieux que de voir la solution directement sans même
essayer.

Je vous présente ici ma solution avec beaucoup de commentaires. J'ai essayé de


reprendre tout ce qu'on a pu découvrir jusqu'ici. Je ne détiens pas la meilleure solution
donc n'hésitez pas à améliorer le code en rajoutant ou modifiant les fonctionnalités
déjà présentes et de partager votre code dans les commentaires avec un lien de votre
repository github ou autres !

package main

import (

"bufio"

"fmt"

"os"

"strconv"

// constantes globales

const (

tailleDamier = 9

symboleJoueur1 = "X"

symboleJoueur2 = "O"

// Variables globales

var (
tableauMorpion = [tailleDamier]string{ // création d'un damier
sans aucune cases remplies

"1", "2", "3",

"4", "5", "6",

"7", "8", "9"}

joueur1 = true // c'est le joueur 1 qui commence en 1er

func main() {

jouer() // Lancement du jeu

/**

* description de la fonction : Permet de lancer le jeu

* @return rien

*/

func jouer() {

var numeroCase int


for true {

affichage()

numeroCase = gestionEntreeUtilisateur() // Récupération de


l'entrée utilisateur

remplirCase(numeroCase)

if gagner() { // Vérifier si le joueur a gagné

affichage()

[Link](nomJoueur(), "vous avez gagné !")

[Link](0) // on quitte la partie

} else if partieNulle() { // Vérifier si match nul

affichage()

[Link]("Partie nulle !")

[Link](0) // on quitte la partie

joueur1 = !joueur1 // on change de joueur

/**

* description de la fonction : Permet d'afficher le damier en prenant


en compte les cases remplies
*

* @return rien

*/

func affichage() {

for i := 0; i < len(tableauMorpion); i++ {

[Link](" ", tableauMorpion[i], " ")

if (i+1)%3 == 0 { // retour à la ligne après avoir affiché 3


éléments

[Link]()

/**

* description de la fonction : Retourne le nom des joueurs

* @return string

*/

func nomJoueur() string {

if joueur1 {
return "Joueur 1 "

} else {

return "Joueur2 "

/**

* description de la fonction : Permet de vérifier l'entrée


utilisateur

* @return int : retourne l'entrée utilisateur

*/

func gestionEntreeUtilisateur() int {

var (

bonneEntree = false // variable qui permet de vérifier si


l'utilisateur a rentré la valeur qu'on attend de lui

numeroCase = 0

err error

scanner = [Link]([Link])

)
for bonneEntree == false {

[Link](nomJoueur(), "entrez un nombre compris entre 1 à ",


tailleDamier, " : ")

[Link]()

numeroCase, err = [Link]([Link]())

if err != nil { //vérifier si l'utilisateur a rentré un


nombre

[Link]("Entrez un nombre et non autre chose !")

} else if numeroCase < 1 || numeroCase > tailleDamier {

[Link]("Votre nombre doit être compris entre 0 à",


tailleDamier, "!")

} else if tableauMorpion[numeroCase-1] == symboleJoueur1 ||


tableauMorpion[numeroCase-1] == symboleJoueur2 { // vérifier si la
case est libre

[Link]("Cette case est déjà prise !")

} else {

bonneEntree = true

return numeroCase - 1 // n'oubliez pas que la taille d'un tableau


commence toujours par 0 ;)

}
/**

* description de la fonction : Permet de remplir la case choisie par


le joueur

* @param numeroCase

* @return rien

*/

func remplirCase(numeroCase int) {

if joueur1 {

tableauMorpion[numeroCase] = symboleJoueur1

} else {

tableauMorpion[numeroCase] = symboleJoueur2

/**

* description de la fonction : Permet de savoir si le joueur a gagné

* @return bool
*/

func gagner() bool {

/*

tableauxdeGain est un tableau à double dimensions où j'ai


rajouté

les différents cas d'utilisation où il est possible de


gagner.

*/

tableauxdeGain := [][tailleDamier]bool{

true, true, true,

false, false, false,

false, false, false},

false, false, true,

false, false, true,

false, false, true},

false, false, false,


false, false, false,

true, true, true},

true, false, false,

true, false, false,

true, false, false},

true, false, false,

false, true, false,

false, false, true},

false, false, true,

false, true, false,

true, false, false},

false, true, false,

false, true, false,

false, true, false}}


// création d'un damier temporaire

var tableauMorpionBool [tailleDamier]bool

for index, valeur := range tableauMorpion {

if joueur1 && valeur == symboleJoueur1 { // si c'est le tour


du joueur 1 et que la case possède le bon symbole du joueur 1

tableauMorpionBool[index] = true

} else if !joueur1 && valeur == symboleJoueur2 {

tableauMorpionBool[index] = true

ressemblance := 0 // Nombre de true qui sont sur les mêmes cases


dans le tableau tableauMorpionBool et dans le tableau tableauxdeGain

for _, tableauGain := range tableauxdeGain {

for i := 0; i < len(tableauMorpionBool); i++ {

if tableauMorpionBool[i] == true && tableauMorpionBool[i]


== tableauGain[i] { // si c'est à true dans le même index de
tableauMorpionBool et tableauGain alors on incrémente la ressemblance

ressemblance++

if ressemblance == 3 { // si les cases du tableau


tableauMorpionBool sont 3 fois les mêmes que sur l'un des tableaux
tableauxdeGain alors ça veut dire que le joueur a gagné
return true

ressemblance = 0 // On remet le compteur à 0 pour vérifier un


autre tableau du tableauxdeGain

return false

/*

* description de la fonction : Permet de vérifier si la partie est


nulle

* @return bool

*/

func partieNulle() bool {

occurence := 0

for _, valeur := range tableauMorpion {

if valeur == symboleJoueur1 || valeur == symboleJoueur2 {


occurence++ // incrémenter de 1 si une case est remplite
par un symbole

/*

si toutes les cases sont remplies de symboles

et que le joueur n'a pas encore gagné alors la partie est


nulle

*/

return (occurence == len(tableauMorpion))

}
Les pointeurs dans le
langage de programmation
Go
Ce chapitre vous explique les pointeurs en GoLang. Vous allez
apprendre d'abord le fonctionnement de la mémoire de votre
ordinateur et ensuite découvrir comment déclarer, accéder et
modifier des pointeurs dans le langage de programmation GO.

Le fonctionnement de mémoire
Fonctionnement

Avant de vous présenter le concept des pointeurs, une petite explication à propos de
la mémoire s’exige.

Vous ne vous êtes jamais demandé comment votre machine manipule les valeurs de
vos variables ?

Et bah c’est grâce aux adresses de vos variables !

"Hein une adresse comme une adresse postale ?" 🤔

Oui tout à fait, toute variable manipulée dans un programme est stockée quelque part
dans la mémoire de votre machine. Cette mémoire est constituée d'octets qui sont
identifiés de manière univoque par un numéro qu'on appelle adresse Donc si votre
machine veut manipuler vos variables, elle aura besoin au préalable de connaître où
se situe votre variable grâce à son adresse mémoire qui lui indiquera la position de la
donnée dans la mémoire vive (la RAM de votre ordinateur).
Par exemple si on prend le fonctionnement de la poste, le facteur a besoin de
l'adresse de destination pour savoir chez qui il doit renvoyer la lettre. Ici le facteur c'est
votre ordinateur et l'adresse destination correspond à l'adresse de la variable que
l'ordinateur souhaite chercher.

Afficher l'adresse d'une variable

On utilisera la fonction Printf() avec le symbole %p et le signe & pour accéder et


afficher à l'adresse d'une variable.

Un exemple sera beaucoup plus parlant :

package main

import (

"fmt"

func main() {

var a int = 20

[Link]("Adresse de la variable a: %p\n", &a)

Copier

Résultat :
Adresse de la variable a: 0xc0000100a0
Schéma explicatif

Je vous partage ici un code simple qui affiche et modifie une variable et je vais ensuite
vous expliquer comment votre machine raisonne quand elle exécute votre code.

package main

import (

"fmt"

func main() {

var a int = 20

var b int = 40

[Link](a)

b = 50

Copier

Comme promis voici le schéma d'explication :


Agrandir l'image

Ça fonctionne comme à la poste, le facteur a besoin de connaître votre adresse pour


vous livrer votre colis. C'est la même chose pour votre machine, elle a besoin de
connaître l'adresse de votre variable pour la manipuler.
les pointeurs
Déclarer et afficher un pointeur

Maintenant que vous avez compris qu’est-ce une adresse mémoire et comment y
accéder, alors je peux passer à la suite en vous expliquant les pointeurs !

Les pointeurs sont des variables dont le contenu est une adresse mémoire d'une autre
variable, c'est-à-dire l'adresse directe de l'emplacement mémoire d'une variable.

Vous devez déclarer un pointeur avant de pouvoir l'utiliser pour stocker l'adresse
d'une variable. La forme générale d'une déclaration de variable de pointeur est :

var nom_de_votre_variable *type_de_votre_variable

Copier

Ici, type_de_votre_variable est le type de base du pointeur (int, float32, etc …)


et nom_de_votre_variable est le nom de la variable de pointeur.
L'astérisque * permet d’indiquer à votre compilateur que votre variable s’agit bien d’un
pointeur (ce signe est obligatoire).

Après la théorie, passant à un peu de pratique. Pour cet exemple je vais déclarer un
pointeur et une variable et accéder directement à la valeur de ma variable depuis son
adresse grâce à mon pointeur.

package main

import "fmt"

func main() {
var a int = 20

var ap *int // création d'un pointeur

ap = &a // stockage de l'adresse mémoire de la variable dans


notre pointeur

[Link]("Adresse de votre variable a : %p\n", &a)

[Link]("Valeur de votre variable (pointeur) ap : %p\n", ap)

[Link]("Valeur de l'adresse %p: %d\n", ap, *ap)

Copier

Résultat :
Adresse de votre variable: 0xc000056058

Valeur de votre variable (pointeur) ap : 0xc000056058

Valeur de l'adresse 0xc000056058: 20

Même si j'ai mis quelques commentaires dans le code, une petite explication du code
s’impose :

var a int = 20

Copier

Ici rien de compliqué, je déclare une variable nommée a de type int.


var ap *int

Copier

La aussi rien de compliqué, je déclare juste un pointeur nommé ap de type int.

ap = &a

Copier

Petit rappel "un pointeur ne peut stocker que des adresses mémoire". Ça tombe bien
car ici grâce au signe & on peut accéder à l'adresse mémoire de notre variable a

[Link]("Adresse de votre variable a : %p\n", &a)

Copier

Ici j’affiche tout simplement l’adresse mémoire de la variable a

[Link]("Valeur de votre variable (pointeur) ap : %p\n", ap)

Copier

Sur cette étape j'affiche la valeur du pointeur ap pour vous démontrer qu'il possède bel
et bien comme valeur l’adresse de votre variable a
[Link]("Valeur de l'adresse %p: %d\n", ap, *ap)

Copier

Pour cette étape j’accède directement à la valeur de la variable a depuis son adresse
grâce au pointeur ap.

L'astérisque * nous permet entre autres de déclarer un pointeur mais il permet aussi
d'accéder à la valeur de la variable pointée, sur cette ligne de code on l'utilise pour
accéder à la valeur contenue dans l'adresse mémoire de la variable a

Valeur par défaut d'un pointeur

La valeur par défaut d'un pointeur est nil. Ça peut vous aider pour savoir si votre
pointeur comporte une adresse mémoire ou pas, exemple :

package main

import "fmt"

func main() {

var ptr *int

if ptr == nil { // est ce que le pointeur est vide ?

[Link]("Aucune adresse mémoire")

} else {

[Link]("Votre adresse mémoire est %p\n", ptr)


}

var b int = 20

var pb *int

pb = &b

if pb == nil {

[Link]("Aucune adresse mémoire")

} else {

[Link]("Votre adresse mémoire est %p\n", pb)

Copier

Résultat :
Aucune adresse mémoire

Votre adresse mémoire est 0xc000056058


Modifier la valeur d'une variable depuis un
pointeur

Comme on peut accéder à l'adresse mémoire d'une variable depuis un pointeur alors il
est tout à fait réalisable de modifier la valeur qui se trouve dans cette adresse
mémoire. Voyons voir cela de plus près :

package main

import "fmt"

func main() {

var a int = 20

var ap *int

ap = &a

[Link]("Valeur de la variable a : %d\n", a)

*ap = 30 // modification de la valeur de la variable "a"


depuis un pointeur

[Link]("Valeur de la variable a : %d\n", a)


}

Copier

Résultat :
Valeur de la variable a : 20

Valeur de la variable a : 30

Voici l'explication du code :

*ap = 30

Copier

Sur cette ligne de code, j'accède directement à l'adresse mémoire de la


variable a grâce à mon pointeur ap , que je remplace par une nouvelle valeur (ici 30).

Les pointeurs dans les fonctions

Il est concevable de déclarer des pointeurs en tant que paramètres dans une fonction.

Attention

Il faut savoir qu'un changement de valeurs d'une variable actionné depuis un pointeur
est irréversible car le pointeur va directement modifier la valeur depuis votre adresse
mémoire.

Voici un exemple qui illustre bien ce que je viens de vous expliquer.

package main

import "fmt"
func main() {

var a int = 20

[Link]("Avant de modifier la variable de a : %d\n", a)

rajouterCinq(&a)

[Link]("Après modification par des pointeurs de la variable


de a : %d\n", a)

affichage(&a)

// fonction qui prend en paramètre un pointeur

func rajouterCinq(pa *int) {

*pa = *pa + 5 // modification de la valeur de la variable "a"


depuis un pointeur

// fonction qui prend en paramètre un pointeur

func affichage(pa *int) {


[Link]("affichage da valeur dans une fonction :", *pa)

Copier

Résultat :
Avant de modifier la variable de a : 20

Après modification par des pointeurs de la variable de a : 25

affichage da valeur dans une fonction : 25

J'ai modifié la valeur de ma variable a depuis le pointeur défini en tant que paramètre
sur la fonction rajouterCinq(), comme je l'ai expliqué plus haut, les changements
sont irréversibles peu importe l'endroit où je l'affiche (fonction main() ou autres).

Conclusion
Les pointeurs sont des fonctionnalités de bas niveau, il était donc nécessaire de vous
expliquer le fonctionnement de la mémoire d’un ordinateur pour que vous puissiez
ensuite bien comprendre comment les utiliser correctement.

Sur le chapitre qui traitait sur la portée des variables, je vous avais expliqué qu’il n'était
pas possible de modifier une variable locale hors de sa fonction ou de son bloc mais
avec les pointeurs il devient possible de modifier une variable locale même si cette
dernière est déclarée dans une autre fonction ou dans un autre fichier (package) et
c’est vraiment là toute la puissance des pointeurs. On peut déclarer notre variable
n’importe où dans notre code et la modifier sur n'importe quel l’endroit de notre code.

Vous aurez le plaisir d’utiliser les pointeurs quand vous travaillerez sur des plus gros
projets, pour le moment mon but était juste de vous faire comprendre la notion et
l’utilisation basique des pointeurs mais sachez qu'on les utilisera sur d'autres chapitres
comme le chapitre sur les structures.
Si vous avez des questions n’hésitez pas à me les poser sur mon LinkedIn (je suis très
actif dessus !) 😊.

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

[Link](tableau)

tableau[0] = 5 // fonctionne bien car la valeur est du même type que


notre tableau

tableau[1] = "text"

Copier

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;

Copier

 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

[Link](perso)
}

Copier

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

[Link](perso)

}
Copier

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}


[Link](perso)

// accès juste à l'attribut age de notre structure Personnage

[Link]("je ne veux afficher que l'age du personnage => ",


[Link])

[Link] = 23 // modification de l'age

[Link]("3 ans plus tard ...", [Link])

Copier

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

*/

[Link] = "magix"

[Link] = 100

[Link] = 20

[Link] = false

[Link] = [3]string{"potion", "bâton", "poison"}

[Link]("Vie du personnage", [Link], ":", [Link])

[Link]("Puissance du personnage", [Link], ":", [Link])

if [Link] {

[Link]("Vie du personnage", [Link], "est mort")

} else {

[Link]("Vie du personnage", [Link], "est vivant")

}
[Link]("\nLe personnage", [Link], "possède dans son inventaire
:", [Link])

for _, item := range [Link] {

[Link]("-", item)

Copier

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)
[Link]("Vie du personnage", [Link], ":", [Link])

[Link]("Puissance du personnage", [Link], ":", [Link])

if [Link] {

[Link]("Vie du personnage", [Link], "est mort")

} else {

[Link]("Vie du personnage", [Link], "est vivant")

[Link]("\nLe personnage", [Link], "possède dans son inventaire


:", [Link])

for _, item := range [Link] {

[Link]("-", item)

/*

Déclaration d'une fonction utilisant comme paramètre


un pointeur de structure

*/

func valeurParDefaut(p1 *Personnage) {

[Link] = "inconnu"

[Link] = 50

[Link] = 10

[Link] = false

[Link] = [3]string{"vide", "vide", "vide"}

Copier

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)

[Link] = "barbare"

[Link] = "magicien"

[Link]()

[Link]()

// déclaration de ma méthode affichage() lié à ma structure


Personnage

func (p Personnage) affichage() {


[Link]("--------------------------------------------------")

[Link]("Vie du personnage", [Link], ":", [Link])

[Link]("Puissance du personnage", [Link], ":", [Link])

if [Link] {

[Link]("Vie du personnage", [Link], "est mort")

} else {

[Link]("Vie du personnage", [Link], "est vivant")

[Link]("\nLe personnage", [Link], "possède dans son


inventaire :", [Link])

for _, item := range [Link] {

[Link]("-", item)

/*

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


@return: void

*/

func valeurParDefaut(p1 *Personnage) {

[Link] = "inconnu"

[Link] = 50

[Link] = 10

[Link] = false

[Link] = [3]string{"vide", "vide", "vide"}

Copier

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 💥.

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

[Link]("barbare", 200, 20, false, [3]string{"épée", "bouclier",


"armure"})

[Link]("magicien", 100, 40, false, [3]string{"potions", "poisons",


"bâton"})

[Link]()

[Link]()

/*

@description : Surcharge des valeurs par defaut

@return: void

*/

func (p *Personnage) Init(nom string, vie int, puissance int, mort bool,
inventaire [3]string) {

[Link] = nom

[Link] = vie

[Link] = puissance

[Link] = mort
[Link] = inventaire

/*

@description : Affiche des informations sur un personnage

@return: void

*/

func (p Personnage) affichage() {

[Link]("--------------------------------------------------")

[Link]("Vie du personnage", [Link], ":", [Link])

[Link]("Puissance du personnage", [Link], ":", [Link])

if [Link] {

[Link]("Vie du personnage", [Link], "est mort")

} else {

[Link]("Vie du personnage", [Link], "est vivant")

}
[Link]("\nLe personnage", [Link], "possède dans son
inventaire :", [Link])

for _, item := range [Link] {

[Link]("-", item)

Copier

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.
Les Slices (tableaux
dynamiques) dans le langage
de programmation Go
Ce chapitre vous explique les Slices en GoLang. Elles
permettent de créer des tableaux dynamiques. Vous allez
apprendre à déclarer et à copier une Slice mais aussi à
récupérer, rajouter, modifier et supprimer les éléments d'une
Slice dans le langage de programmation Go.

Pourquoi les Slices ?


Le problème avec un tableau c’est qu’il a une taille fixe, il faut donc absolument
connaître sa taille au moment de sa déclaration ce qui n’est pas vraiment évident car
on peut très vite s’apercevoir plus tard dans notre code que la taille allouée au départ à
notre tableau est insuffisante.

Et c’est là qu’interviennent les Slices dans Go, Ils vont nous permettre d’avoir
un tableau flexible et le dimensionner de façon dynamique sans se soucier de sa
taille pendant sa déclaration.

Déclarer une Slice


Il existe deux façons pour créer une Slice.
Soit à partir de la même syntaxe qu'un tableau sauf que cette fois-ci on ne spécifie pas
la taille du tableau :

package main

import (

"fmt"

func main() {

var nombres = []int{0, 0, 0, 0, 0} // création d'une slice avec 5


éléments

[Link](nombres)

Copier

Résultat :
[0 0 0 0 0]

Soit depuis la fonction make()

package main

import (

"fmt"
)

func main() {

var nombres = make([]int, 5) // création d'une slice avec 5


éléments

[Link](nombres)

Copier

Résultat :
[0 0 0 0 0]

Rajouter un élément dans une Slice


Pour rajouter un élément dans votre slice il faut utiliser la fonction append(), qui prend
comme paramètres d'abord votre slice et ensuite l'élément que vous voulez rajouter et
elle vous retournera une nouvelle Slice avec l'élément rajouté.

package main

import "fmt"

func main() {

var mois []string


mois = append(mois, "Janvier")

[Link](mois)

mois = append(mois, "Février")

[Link](mois)

Copier

Résultat :
[Janvier]

[Janvier Février]

Supprimer un élément dans une


Slice
Il n'existe pas spécialement de fonction dans Go qui permet de supprimer un élément,
mais il est possible de réutiliser la fonction append() pour supprimer un élément en
Go.

Avant de vous montrer comment faire pour supprimer n'importe quel élément d'une
slice, laissez-moi avant vous expliquer comment fonctionne la fonction append().

Voici à quoi ressemble le prototype de la fonction append() :

func append(slice []Type, elems ...Type) []Type

Copier
La fonction append() prend comme premier argument une Slice, le second paramètre
est quant à lui est un paramètre variadique c'est-à-dire qu'il prend en compte un
nombre indéterminé de paramètres.

Comme il est possible de rajouter un nombre indéterminé de paramètres sur le


deuxième paramètre de la fonction append() , alors pour supprimer un élément il suffit
de concaténer les éléments qui sont avant l'élément qu'on souhaite supprimer dans
notre slice avec les éléments qui sont après l'élément qu'on souhaite supprimer dans
notre slice.

Ce qui nous donne :

package main

import "fmt"

func main() {

mois := []string{"Janvier", "Février", "Mars", "Avril",


"Juin", "Juillet"}

[Link](mois)

// index à supprimer de notre slice

indexASupprimer := 1

// Suppression de l'index 1 du tableau soit "Février"


mois = append(mois[:indexASupprimer],
mois[(indexASupprimer+1):]...)

[Link](mois)

Copier

Résultat :
[Janvier Février Mars Avril Juin Juillet]

[Janvier Mars Avril Juin Juillet]

Copier le contenu d'une Slice


Il est possible de copier le contenu d'une slice source vers une slice cible grâce à la
fonction copy(). Cette fonction prend comme premier paramètre la slice cible et
comme deuxième paramètre la slice source.

Attention

La fonction copy() copie le contenu d’une tranche source vers une source cible, il
est donc important que la slice cible soit de même taille que la slice source afin de
copier le contenu total de la cible.

package main

import "fmt"

func main() {
animaux1 := []string{"Lion", "Cheval", "Ours"}

[Link]("Contenu du tableau animaux1 :", animaux1)

// création d'une slice cible avec la même taille que la slice


source

animaux2 := make([]string, len(animaux1))

// copie du contenu de la slice source vers la slice cible

copy(animaux2, animaux1)

[Link]("Contenu du tableau animaux2 :", animaux2)

Copier

Résultat :
Contenu du tableau animaux1 : [Lion Cheval Ours]

Contenu du tableau animaux2 : [Lion Cheval Ours]


Les Maps dans le langage de
programmation Go
Ce chapitre vous explique les Maps en GoLang. Elles
permettent d'associer des clés uniques à des valeurs. Vous
allez apprendre à déclarer une Map et à récupérer, rajouter,
modifier et supprimer les éléments d'une Map dans le langage
de programmation Go.

Pourquoi les Maps ?


Go fournit un autre type de données important nommé Map, qui associe des clés
uniques à des valeurs.

Avant pour récupérer un élément précis dans votre tableau vous utilisiez un index, les
Maps possèdent le même processus de récupération d’éléments qu’un tableau
cependant pour les Maps votre index est nommé clé et votre clé peut être de n’importe
quel type (int, string, float64 …) à l'instar des tableaux où l’index est obligatoirement de
type int.

Par exemple on peut créer une Map avec comme clé les noms des élèves et comme
valeur leurs notes et intercepter la note d’un élève précis (la valeur) en utilisant son
nom (la clé)
Déclarer une Map
Comme les Slices, il existe deux façons pour créer une Map.

Première méthode :

package main

import (

"fmt"

func main() {

var notes map[string]int

Copier

Deuxième méthode depuis la fonction make()

package main

import (

"fmt"

)
func main() {

var notes = make(map[string]int)

Copier

Comme dans d'autres types de variables il est aussi possible de surcharger les valeurs
par défaut de votre Map.

package main

import "fmt"

func main() {

notes := map[string]int{"Hatim": 20, "Alex": 18}

[Link](notes)

Copier

Résultat :
map[Alex:18 Hatim:20]
Rajouter un élément dans une Map
Voici comment on rajoute un élément dans une Map.

package main

import "fmt"

func main() {

var notes = make(map[string]int)

notes["Hatim"] = 20

notes["Alex"] = 18

notes["Kevin"] = 15

[Link](notes)

Copier

Résultat :
map[Alex:18 Hatim:20 Kevin:15]
Récupérer les éléments d'une Map
Récupérer un élément précis

Comme expliqué sur l'introduction de ce chapitre, on récupère un élément d'une Map


grâce à sa clé, donc on utilisera la clé de l'élément de notre Map pour récupérer sa
valeur.

package main

import "fmt"

func main() {

notes := map[string]int{"Hatim": 20, "Alex": 18}

[Link]("La note de Hatim est :", notes["Hatim"])

[Link]("La note de Alex est :", notes["Alex"])

Copier

Résultat :
La note de Hatim est : 20

La note de Alex est : 18


Boucle for dans une map

On peut utiliser la boucle for avec le mot-clé range pour récupérer la clé de tous les
éléments de votre Map.

package main

import "fmt"

func main() {

notes := map[string]int{"Hatim": 20, "Alex": 18, "Kevin": 15,


"Robert": 17}

for eleve := range notes {

[Link]("La note de ", eleve, "est", notes[eleve])

Copier

Résultat :
La note de Hatim est 20

La note de Alex est 18

La note de Kevin est 15


La note de Robert est 17

Supprimer un élément dans une Map


Pour supprimer un élément de votre Map il faut utiliser la fonction delete(), qui prend
comme paramètres d'abord votre Map et ensuite la clé de l'élément que vous voulez
supprimer.

package main

import "fmt"

func main() {

notes := map[string]int{"Hatim": 20, "Alex": 18, "Kevin": 15,


"Robert": 17}

[Link](notes)

delete(notes, "Hatim")

[Link](notes)

Copier

Résultat :
map[Alex:18 Hatim:20 Kevin:15 Robert:17]

map[Alex:18 Kevin:15 Robert:17]


Les Interfaces dans le
langage de programmation
Go
Ce chapitre vous explique les interfaces en GoLang. Ils
permettent de créer un ensemble de signatures de méthodes
qu'une structure peut implémenter. Vous allez comprendre
leurs intérêts et apprendre à déclarer, accéder et modifier des
interfaces dans le langage de programmation Go.

Définition
Une interface est un ensemble de signatures de méthodes qu'une structure
peut implémenter. Par conséquent, l'interface définit le comportement d’une structure.
Le but principal de l'interface consiste à ne fournir que des signatures de
méthodes composées :

 Du nom de la méthode
 Des arguments d'entrée
 Des types de retour

Il appartient à la structure de déclarer les méthodes signées dans l’interface et de les


implémenter

Démonstration step by step


Déclarer une interface

Pour déclarer une interface nous avons besoin du mot-clé type qui permet d'associer
un nom à votre interface et du du mo-clé interface qui indique à votre compilateur
qu'il s'agit d'une interface.

package main

import (

"fmt"

type Forme interface { // création de L'interface Forme

Air() float64 // signature de la méthode Air()

Perimetre() float64 // signature de la méthode Perimetre()

}
func main() {

var f Forme // Initialisation de l'interface Forme

[Link](f)

Copier

Comme vous pouvez le voir dans une interface on ne met que des signatures de
méthodes sans aucun attributs. Pour le moment ces méthodes ne sont pas encore
utilisées car pour l'instant je vous montre juste comment déclarer une interface.

Résultat :
nil

D'après le résultat ci-dessus, nous pouvons voir que la valeur de notre interface
est nil car il n'existe aucune structure qui implémente l'interface Forme.

Utilisation d'une interface

Notre interface Forme possède deux signatures :

 Air() qui retourne un type float64


 Perimetre() qui retourne un type float64

La structure qui va implémenter l'interface Forme doit obligatoirement implémenter les


méthodes Air() et Perimetre() signées dans l'interface Forme.

package main

import (
"fmt"

type Forme interface {

Air() float64

Perimetre() float64

type Rectangle struct {

largeur float64

longueur float64

/*

Pour implémenter une interface dans Go, il suffit

d'implémente toutes les méthodes de l'interface. Ici on

implémente la méthode Air() de l'interface Forme.

*/

func (r Rectangle) Air() float64 {


return [Link] * [Link]

/*

On implémente la méthode Perimetre() de l'interface Forme

*/

func (r Rectangle) Perimetre() float64 {

return 2 * ([Link] * [Link])

func main() {

var f Forme

f = Rectangle{5.0, 4.0} // affectation de la structure Rectangle


à l'interface Forme

r := Rectangle{5.0, 4.0}

[Link]("Type de f :", f)

[Link]("Valeur de f : %v\n", f)

[Link]("Air du rectangle r :", [Link]())

[Link]("f == r ? ", f == r)
}

Copier

Il est tout à fait possible d'affecter à notre interface Forme la


structure Rectangle puisque la structure Rectangle implémente l'interface Forme.

Résultat :
Type de f : {5 4}

Valeur de f : {5 4}

Air du rectangle r : 20

f == r ? true

Sur le résultat on peut remarquer que f == r ? est à true car dans mon
exemple f et r ont le même type et la même valeur.

Je vais intentionnellement supprimer la méthode Air() de la


structure Rectangle pour ainsi vous prouvez qu'il est
vraiment obligatoire d'implémenter toutes les méthodes de l'interface Forme.

package main

import (

"fmt"

)
type Forme interface {

Air() float64

Perimetre() float64

type Rectangle struct {

largeur float64

longueur float64

/* Pas de méthode Air */

func (r Rectangle) Perimetre() float64 {

return 2 * ([Link] * [Link])

func main() {

var f Forme

f = Rectangle{5.0, 4.0}
r := Rectangle{5.0, 4.0}

[Link]("Type de f :", f)

[Link]("Valeur de f : %v\n", f)

[Link]("Air du rectangle r :", [Link]())

[Link]("f == r ? ", f == r)

Copier

Erreur :
.cannot use Rectangle literal (type Rectangle) as type Forme in
assignment:

Rectangle does not implement Forme (missing Air method)

invalid operation: f == r (mismatched types Forme and Rectangle)

L'erreur ci-dessus indique clairement que, pour implémenter une interface avec
succès, vous devez implémenter toutes les méthodes déclarées par l'interface et notre
compilateur nous le fait bien savoir on nous marquons qu'il manque la
méthode Air() "missing Air method".

On va dans cet exemple créer deux structures Cercle et Rectangle qui implémente
l'interface Forme :

package main

import (

"fmt"
"math"

type Forme interface {

Air() float64

Perimetre() float64

/*----------------Début Structure Rectangle----------------*/

type Rectangle struct {

largeur float64

longueur float64

func (r Rectangle) Air() float64 {

return [Link] * [Link]

func (r Rectangle) Perimetre() float64 {


return 2 * ([Link] * [Link])

/*----------------Fin Structure Rectangle----------------*/

/*----------------Début Structure Cercle-----------------*/

type Cercle struct {

rayon float64

func (c Cercle) Air() float64 {

return [Link] * [Link] * [Link]

func (c Cercle) Perimetre() float64 {

return 2 * [Link] * [Link]

/*----------------Fin Structure Cercle-------------------*/


//Fonction qui prend compte un paramètre de type Cerle ou Rectangle

func AirPerimetrePresentation(f Forme) {

[Link]("- Air :", [Link]())

[Link]("- Perimetre :", [Link]())

func main() {

var r Forme = Rectangle{5.0, 4.0}

var c Forme = Cercle{5.0}

[Link]("Cercle :")

AirPerimetrePresentation(r)

[Link]("\nrectangle :")

AirPerimetrePresentation(c)

Copier

Résultat :
Cercle :

- Air : 20
- Perimetre : 40

rectangle :

- Air : 78.53981633974483

- Perimetre : 31.41592653589793

Le résultat ci-dessus nous montre que l'interface Forme peut être déclaré en tant
que Cercle ou Rectangle

Conclusion
Les interfaces sont une sorte de contrat, ils permettent de créer des comportements
génériques: si plusieurs structures doivent obéir à des comportements particuliers,
alors on créé une interface décrivant ces comportements. Ces structures devront ainsi
obéir strictement aux signatures de l'interface, sans quoi la compilation ne se fera pas.

Grâce à eux on peut obtenir un code beaucoup plus claire et facile à maintenir.
Gestion des erreurs dans le
langage de programmation
Go
Ce chapitre vous montre comment gérer vos erreurs en
GoLang, vous allez apprendre à détecter, créer et gérer vos
erreurs dans le langage de programmation GO.

Présentation
Il est possible avec le langage de programmation go de gérer les erreurs plus
précisément Go nous permet de détecter ou de créer une erreur pour ensuite la
manier comme bon nous semble.

La première chose à réaliser pour gérer d’éventuelles erreurs lors de votre


compilation, c'est avant tout de les repérer 👀.
Détection d'erreurs
Je vais volontairement sur cet exemple provoquer une erreur lors de l'exécution d'une
fonction, et le compilateur va nous prévenir d’une manière ou d’une autre qu’une
erreur a été levée.

package main

import (

"bufio"

"fmt"

"os"

"strconv"

func division() {

scanner := [Link]([Link])

[Link]("Entrez un chiffre : ")

[Link]()

nbr, _ := [Link]([Link]())

[Link]("Résultat :", 1000/nbr)

}
func main() {

division()

[Link]("Fin")

Copier

Erreur :
Entrez un chiffre : nope

panic: runtime error: integer divide by zero

Sans aucune surprise nous obtenons une erreur qui nous explique qu'il est impossible
de diviser par 0.

Pour le bon déroulement de notre programme, il faut gérer cette erreur en expliquant à
l'utilisateur qu'il n'est pas possible de diviser par 0 et qu'il doit rentrer un nombre
supérieur à 0.

package main

import (

"bufio"

"fmt"

"os"

"strconv"
)

func division() {

for true {

scanner := [Link]([Link])

[Link]("Entrez un chiffre : ")

[Link]()

nbr, _ := [Link]([Link]())

if nbr <= 0 {

[Link]("[division par zéro impossible] Votre valeur


doit être supérieur ou égal à 0")

} else {

[Link]("Résultat :", 1000/nbr)

break

func main() {
division()

[Link]("Fin")

Copier

Résultat :
Entrez un chiffre : dsfsdf

[division par zéro impossible] Votre valeur doit être supérieur ou


égal à 0

Entrez un chiffre : dssd

[division par zéro impossible] Votre valeur doit être supérieur ou


égal à 0

Entrez un chiffre : 78

Résultat : 12

Fin

C'est cool car on a résolu le problème de la division par zéro mais par contre lorsque
l'utilisateur rentre des caractères on lui affiche toujours le même message comme quoi
son entrée doit être supérieur à 0. Il serait plus judicieux de l'informer qu'il doit rentrer
un nombre et non des caractères.

Pour afficher cette information on va analyser un peu plus en détail la fonction Atoi().

Déjà une chose est sûr l'erreur est levée lors de la fonction Atoi(), voyons voir un
peu plus en détail le prototype de cette fonction :

func Atoi(s string) (int, error)

Copier
On peut apprendre grâce au prototype que la fonction Atoi() retourne deux types de
valeurs :

 Un type int qui correspond à la chaîne de caractères s converti en entier


 un type error qui est l'erreur retourné par la fonction Atoi() qu'on peut gérer

Donc pour gérer cette erreur de conversion il faut vérifier la valeur de retour
de error de la fonction Atoi() dans une condition.

package main

import (

"bufio"

"fmt"

"os"

"strconv"

func division() {

for true {

scanner := [Link]([Link])

[Link]("Entre un chiffre : ")

[Link]()

nbr, err := [Link]([Link]())


if err != nil { // Gestion de l'erreur de la fonction Atoi()

[Link]("Vous devenez rentrer un nombre et non une


chaîne de caractères !")

} else if nbr <= 0 {

[Link]("[division par zéro impossible] Votre valeur


doit être supérieur ou égal à 0")

} else {

[Link]("Résultat :", 1000/nbr)

break

func main() {

division()

[Link]("Fin")

Copier

Résultat :
Entre un chiffre : -5
[division par zéro impossible] Votre valeur doit être supérieur ou
égal à 0

Entre un chiffre : nope

Vous devenez rentrer un nombre et non une chaîne de caractères !

Entre un chiffre : 5

Résultat : 200

Fin

Créer une erreur


L'interface error

Il est possible de construire un message d'erreur grâce à la fonction New() de la


structure errors.

package main

import (

"errors"

"fmt"

"os"

)
func verificationDivision(nbr float64) (float64, error) {

if nbr <= 0 {

return 0, [Link]("Erreur: Il est impossible de diviser


par 0 !") //création de l'erreur

} else {

return nbr, nil // on retourne nil si aucune erreur est


détectée

func main() {

nbr := 0.0

nbr, err := verificationDivision(nbr)

if err != nil {

panic(err)

} else {

[Link]("Aucune erreur")

}
Copier

Résultat :
panic: Erreur: Il est impossible de diviser par 0 !

goroutine 1 [running]:

exit status 2

Information

Si votre fonction retourne une interface error alors vous ne pouvez retourner que le
type nil ou le type errors

Ici j'utilise la fonction panic() pour quitter mon programme avec un code
retour différent de 0. Un code d'erreur égale à zéro indique que l'exécution de votre
programme s'est bien déroulée si il est différent de zéro cela indique un échec de
votre programme. C'est une valeur qui est retournée lors de la fin d'exécution de votre
programme.

Sur Linux il est possible d'afficher le dernier code d'erreur d'un programme/commande
en utilisant la commande suivante :

echo $?

Copier

Il est intéressant de retourner le bon code d'erreur lors de l'exécution de votre


programme, car c'est possible que cette valeur soit récupérée et exploitée par une
autre personne qui utilise votre programme en tant que script pour vérifier si votre
programme s'est bien exécuté.
Recommandation

la fonction [Link]() ne fait que renvoyer un string, donc il est totalement


possible de créer votre propre système de gestion d'erreur comme sur l'exemple
ci-dessous :

package main

import (

"fmt"

func verificationDivision(nbr float64) (float64, bool) {

if nbr <= 0 {

return 0, false

} else {

return nbr, true

func main() {

nbr := 0.0
nbr, err := verificationDivision(nbr)

if err == false {

panic("Erreur: Il est impossible de diviser par 0 !")

} else {

[Link]("Aucune erreur")

Copier

Mais il reste préférable de gérer vos erreurs en utilisant la fonction [Link]() afin
de rendre votre code plus lisible et clair. Cela aide à la compréhension, à la relecture,
au contrôle visuel et à la maintenance de votre code.
Les packages dans le
langage de programmation
Go
Ce chapitre vous explique les packages en GoLang. Ils
permettent de déplacer des portions de votre code dans
d’autres fichiers afin de mieux organiser et maintenir votre
programme. Vous allez apprendre à créer et gérer des
packages dans le langage de programmation Go.

Définition
Jusqu'ici nous n'avons écrit notre code que dans un seul fichier, pour l'instant c'était
tolérable car la taille de note code était petite, mais plus votre code va grandir plus
vous allez vite vous rendre compte qu'il est nécessaire de déplacer des portions de
votre code dans d'autres fichiers afin de mieux organiser et maintenir votre
programme, et c'est là qu'interviennent les packages !

Un package n'est rien d'autre qu'un répertoire contenant des fichiers de code go, qui
peut être exécuté par d'autres fichiers go.

Comme nous en avons discuté au début de nos chapitres, il existe deux types de
packages :

 Package exécutable
 Package utilitaire

Dans un package exécutable on retrouve le fameux package main qui permet


d'informer votre compilateur Go que le paquet doit être compilé en tant que
programme exécutable au lieu d'un package utilitaire.
Un package utilitaire n'est pas auto-exécutable, il améliore plutôt les fonctionnalités
d'un package exécutable en lui fournissant des fonctionnalités.

Création de packages
Où créer mon package ?

Pour importer un paquet, il faut utiliser le mot-clé import suivi du nom du paquet.

Lorsque vous créez un paquet et que vous l'importez sur votre package principal, votre
compilateur va d'abord chercher le répertoire du paquet dans le $GOROOT/src/ et si
répertoire n'existe pas il va le cherchera dans le dossier $GOPATH/src/.

Information

$GOROOT et $GOPATH sont deux variables d'environnement créez lors de l'installation de


GO.

Il est possible d'afficher toutes vos variables d'environnement Go comme suit :

go env

Copier

Résultat :

set GOCACHE=C:\Users\hatim\AppData\Local\go-build

...

set GOPATH=C:\Users\hatim\go

set GOROOT=C:\Go

...
Je vais volontairement importer un package qui n'existe pas, pour vous prouver qu'il
cherche bel et bien vos packages dans ces deux chemins :

package main

import (

"aucunpackage"

"fmt"

func main() {

[Link]([Link]())

Copier

Erreur :

cannot find package "aucunpackage" in any of:

C:\Go\src\aucunpackage (from $GOROOT)

C:\Users\hatim\go\src\aucunpackage (from $GOPATH)

Le compilateur nous avertis qu'il ne retrouve pas le package dans mon


dossier $GOROOT et $GOPATH. Il est donc nécessaire de déposer vos packages dans un
des deux chemins.
Arborescence

Dans cet nouvel exemple, nous allons créer un simple package qui nous permet
d'afficher un nom et un sexe, mais avant de toucher à du code on va d'abord réfléchir
à notre arborescence

Déjà premièrement on va déposer nos packages comme go nous le demande à savoir


dans le dossier $GOPATH/src/, qui est l'équivalent sur mon ordinateur au chemin C:\
Users\hatim\go\src\ (tapez la commande go env pour connaître le chemin
de $GOPATH/src/).

Ensuite pour créer un package il faut créer un dossier avec le nom de notre package
suivi d'un fichier (même nom que le package de préférence) ou on déposera le code
Go de notre package.

Dans l'exemple qui suit notre package se nommera affichage et notre package
exécutable se nommera [Link]. Ce qui nous donnera l'arborescence suivante :

$GOPATH/src

├── affichage

| ├── [Link]

[Link]

Création du package

Voici à quoi va ressembler notre fichier [Link]

package affichage

var Nom string = "Hatim"

var sexe string = "masculin"


func AfficheSexe() string {

return Nom + " est de sexe " + sexe

Copier

Attention

Go exporte une variable dans un autre package si et seulement si le nom de la


variable commence par une majuscule. Toutes les autres variables ne commençant
pas par une lettre majuscule sont privées du paquet.

La ligne de code package affichage indique à notre compilateur qu'il s'agit bien d'un
package utilitaire nommé "affichage". Ensuite pour le reste du code je déclare deux
variables et une fonction qui peuvent être réutilisées depuis le fichier [Link] une fois
qu'il aura importé le package.

Il ne reste plus maintenant qu'à importer le package affichage dans le fichier [Link]
et par la suite il sera possible depuis le fichier [Link] d'utiliser les fonctions et les
variables du package affichage.

Voici à quoi va ressembler [Link] :

package main

import (

"affichage"

"fmt"

)
func main() {

[Link]("Je m'appelle", [Link])

[Link]([Link]())

Copier

Résultat :

Je m'appelle Hatim

Hatim est de sexe masculin.

Imbrication de paquets
Nous pouvons imbriquer un paquet dans un paquet. Tout ce que nous avons à faire
c'est de fournir le chemin relatif du paquet imbriqué.

Nous allons rajouter dans notre exemple précèdent un sous-package


nommé passion avec une fonction qui retourne un string. Notre nouvelle arborescence
ressemblera à ceci :

$GOPATH/src

├── affichage

| ├── [Link]

| ├── passion

| ├── [Link]

[Link]
Voici à quoi va ressembler notre fichier [Link] :

package passion

func AffichagePassion() string {

return "j'aime programmé en Go !"

Copier

Et voici à quoi va ressembler notre fichier [Link] :

package main

import (

"affichage"

"affichage/passion" // chemin relatif

"fmt"

func main() {

[Link]("Je m'appelle", [Link])

[Link]([Link]())
[Link]([Link]())

Copier

ici on utilise le chemin relatif pour importer notre package passion imbriqué dans notre
package affichage.

Résultat :

Je m'appelle Hatim

Hatim est de sexe masculin

j'aime programmé en Go !.
La programmation orientée
objet dans le langage de
programmation Go
Ce chapitre vous explique la programmation orientée objet en
GoLang. Même si GO n'est pas un langage purement orienté
objet il est possible en réussissant à combiner les structures et
les packages de se rapprocher le plus possible de la POO
dans le langage de programmation GO.

Go est-il un langage orienté objet ?


Promesse faite, promesse tenue, je vais vous parler de la POO (programmation
orientée objet) en Go.

Voici d'abord ce qu'on peut retrouver dans la faq de go à propos de la POO.

Is Go an object-oriented language?

Yes and no. Although Go has types and methods and allows an object-oriented style
of programming, there is no type hierarchy. The concept of “interface” in Go provides a
different approach that we believe is easy to use and in some ways more general.
There are also ways to embed types in other types to provide something analogous—
but not identical—to subclassing. Moreover, methods in Go are more general than in
C++ or Java: they can be defined for any sort of data, even built-in types such as plain,
“unboxed” integers. They are not restricted to structs (classes).

faq de go

En gros il nous explique qu'il n'existe pas de hiérarchie des types en Go (par exemple
la notion d'héritage). Mais qu'il existe des approches différentes de hiérarchisation de
nos classes en utilisant par exemple les packages imbriqués où le répertoire racine
sera la classe mère et les sous dossier des classes filles qui qui héritent de la classe
mere.

Pour résumé Go n'est pas un langage de programmation purement orienté objet . Mais
en combinant les structures et les packages, il est réalisable de se rapprocher le plus
possible de la POO.

Création de nos classes


On va reprendre le même exemple vu dans le chapitre des structures, à savoir une
structure Personnage avec les attributs suivants :

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

Et avec les méthodes suivantes :

 Presentation()
 ViePerdu()
 EstMort()

Nous allons créer notre structure Personnage dans un package personnage, ce qui
nous donnera l'arborescence suivante :

$GOPATH/src

├── personnage

| ├── [Link]

[Link]

Voici à quoi va ressembler notre classe Personnage dans le fichier [Link] :

package personnage
import (

"fmt"

type Personnage struct {

Nom string

Vie int

Puissance int

Mort bool

Inventaire [3]string

/*

Affiche des informations sur un personnage

@return: void

*/

func (p Personnage) Affichage() { // déclaration de ma méthode


Affichage() liée à ma structure Personnage
[Link]("--------------------------------------------------")

[Link]("Vie du personnage", [Link], ":", [Link])

[Link]("Puissance du personnage", [Link], ":", [Link])

if [Link] {

[Link]("Vie du personnage", [Link], "est mort")

} else {

[Link]("Vie du personnage", [Link], "est vivant")

[Link]("\nLe personnage", [Link], "possède dans son


inventaire :", [Link])

for _, item := range [Link] {

[Link]("-", item)

Copier

Rappel
Go exporte une variable/fonctions dans un autre package si et seulement si le nom
commence par une lettre majuscule. Toutes les autres attributs/fonctions ne
commençant pas par une lettre majuscule elles sont considérées comme variables
privées ou fonctions privées par le paquet, c'est-à-dire qu'elles ne sont pas
exploitables en dehors du package où elles ont été initialisées.

Ensuite voici à quoi va ressembler notre fichier [Link]

package main

import (

"personnage"

func main() {

magicien := [Link]{ // Instanciation de la


classe Personnage

Nom: "magix",

Vie: 100,

Puissance: 20,

Mort: false,

Inventaire: [3]string{"potions", "poisons", "bâton"},

}
[Link]()

Copier

Ici on importe le package personnage et sa structure Personnage qui pour nous


représente une classe.

Une fois notre package importé, on crée ensuite une instance de notre classe qu'on
nomme magicien et on lui affecte des valeurs par défaut.

À la fin de notre programme on invoque la méthode de notre classe Personnage, ce


qui nous donne comme 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

- potions

- poisons

- bâton
Les constructeurs
Le rôle du constructeur est de déclarer et d'initialiser les attributs d'une classe. Go ne
supporte pas par défaut les constructeurs, mais il est possible de les créer en
bidouillant avec les méthodes.

Avant de vous montrer comment on créée une sorte de constructeur en Go, voici
d'abord une comparaison avec le le langage Java.

Voilà à quoi va ressembler par exemple notre structure Personnage en java:

public class Personnage{

public String Nom;

public int Vie;

public int Puissance;

public boolean Mort;

String tableauChaine[] = {"rien", "rien", "rien"};

Copier

Voici comment on instancie une classe en java :

public class Main

public static void main(String[] args)

{
// Instanciation de la classe Personnage

Personnage magicien = new Personnage("magix", 100, 20, false, new


String[] {"potions", "poisons", "bâton"} );

Copier

C'est grâce à l'opérateur new, qu'on arrive à faire appel à un constructeur d'une classe
en java. Sur Go on va bidouiller un peu dans notre package personnage pour recréer
l'opérateur new utilisé dans java (mais aussi dans beaucoup d'autres langages de
programmations).

Pour ça on va créer une méthode nommée New dans package personnage, comme
suit :

package personnage

import (

"fmt"

type Personnage struct {

Nom string

Vie int

Puissance int
Mort bool

Inventaire [3]string

/*

Créer une instance de la classe Personnage

@return: struct Personnage

*/

func New(Nom string, Vie int, Puissance int, Mort bool, Inventaire
[3]string) Personnage {

personnage := Personnage{Nom, Vie, Puissance, Mort, Inventaire}

return personnage

/*

Affiche des informations sur un personnage

@return: void

*/
func (p Personnage) Affichage() { // déclaration de ma méthode
Affichage() liée à ma structure Personnage

[Link]("--------------------------------------------------")

[Link]("Vie du personnage", [Link], ":", [Link])

[Link]("Puissance du personnage", [Link], ":", [Link])

if [Link] {

[Link]("Vie du personnage", [Link], "est mort")

} else {

[Link]("Vie du personnage", [Link], "est vivant")

[Link]("\nLe personnage", [Link], "possède dans son


inventaire :", [Link])

for _, item := range [Link] {

[Link]("-", item)

Copier
Notre fichier [Link] va ressembler à ça :

package main

import (

"personnage"

func main() {

magicien := [Link]("magix", 100, 20, false,


[3]string{"potions", "poisons", "bâton"}) //Instanciation de la
classe Personnage

[Link]()

Copier

Conclusion
Vous l'aurez sans doute compris même si Go ne prend pas en charge les classes, il
reste tout de même possible d'utiliser les structures à la place des classes. Grâce à
cette fusion entre structures et packages il est possible d'encore mieux organiser
son code en s'orientant plus vers la POO. Mon but sur ce chapitre était de vous
introduire à la POO. Il est bien sûr tout à fait possible d'aller encore plus loin en créant
par exemple une notion d'héritage grâce aux interfaces, je créerai sûrement un article
à propos de ce sujet.
Suggestion d'exercice

Avant de nous quitter sur ce chapitre je vous conseil de recréer le TP sur les morpions
avec des classes ! Vous avez largement les ressources nécessaires pour réussir à
bien cet exercice.
Les goroutines dans le langage
de programmation Go
Ce chapitre vous explique les goroutines en GoLang. Elles
permettent de créer des programmes multi-threads simplement.
Vous allez d'abord comprendre la différence entre concurrence et
parallélisme et ensuite apprendre à créer et gérer vos différentes
goroutines sur le langage de programmation Go.

Concurrence et parallélisme
Avant d'aborder des goroutines il est essentiel de comprendre la différence entre concurrence
et parallélisme.

Concurrence

La concurrence est la capacité de traiter plusieurs de choses à la fois, par exemple :

Un humain normal doit d'abord finir sa bouchée avant de pouvoir parler, une fois qu'il aura
fini sa bouchée il pourra parler ensuite une fois qu'il aura fini de parler il pourra encore une
fois reprendre une autre bouchée et reparler juste après.

Dans cet exemple la personne est capable capable de gérer plusieurs de choses (manger et
parler) dans un intervalle de temps différent.

Parallélisme

Le parallélisme permet de traiter beaucoup de choses en même temps, je m'explique :

L'humain normal de l'exemple précèdent devient un humain mutant avec un bras et une
bouche en plus. Cette fois-ci il est capable de manger et de parler en même temps.
Le fonctionnement d'un point de vue informatique

Maintenant que vous avez compris ce qu'est la concurrence et comment elle diffère du
parallélisme en utilisant des exemples de la vie réelle, il est temps maintenant de comprendre
qu'est ce ça donne d'un point de vue technique.

Imaginons que vous codez votre propre navigateur Web et qu'un utilisateur utilise votre
navigateur pour visiter une page web afin de télécharger une vidéo. Il clique alors sur le
bouton de téléchargement de la page et en attendant la fin de son téléchargement il visite
d'autres pages du site.

Votre navigateur doit dans ce cas gérer deux taches de manière indépendante, une tâche pour
le téléchargement et une autre tache pour le rendu des pages web que l'utilisateur va visiter.
Cette tâche indépendante est ce qu'on appelle un thread.

Lorsque votre navigateur est exécuté dans un processeur avec un seul cœur (ou processeur
multicœur), le processeur bascule entre les deux tâches (chaque tâche va s'exécuter un petit
moment puis une autre, puis une autre, puis on revient à la première, etc) ceci est connu sous
le nom de la concurrence. Dans ce cas, le téléchargement et le rendu commencent à
différents moments et leurs exécutions se chevauchent.

Disons que le même navigateur est exécuté sur une autre machine avec un processeur
multicœur et que la tâche de téléchargement et la tâche de rendu HTML s'exécute
simultanément sans chevauchement dans des cœurs différents alors ceci est plus connu sous le
nom de parallélisme.
Schéma sur le fonctionnement de la concurrence et le parallélisme sur un Processeur avec un
seul cœur et deux cœurs.
Vos tâches peuvent parfois avoir besoin de communiquer entre eux. Par exemple dans votre
navigateur dès que l'utilisateur aura fini son téléchargement une popup indiquant à l'utilisateur
que le téléchargement c'est bien déroulé apparaîtra au-dessus du rendu si et seulement si la
page courante n'est pas en plein écran. Dans ce cas on parle de concurrence et l'avantage de
cette dernière c'est que les différentes tâches peuvent potentiellement accéder à des données
partagées cependant il peut y avoir un risque de décohérence entre les deux.

Les goroutines
Pourquoi les goroutines ?

L'un des aspects les plus intéressants dans Go est son modèle de concurrence, il rend la
création de programmes multi-threads simples.

Go est capable d'effectuer plusieurs opérations simultanément. C'est particulièrement


important sur les processeurs multicœurs actuels. Les programmes n'utilisant qu'un seul cœur
laisse une grande partie de la puissance de traitement perdue, coup de chance car Go nous
permet d'utiliser pleinement les cœurs de notre processeur grâce aux goroutines.

Pratiquons un peu !

Pour mieux comprendre les gains de performances avec les goroutines, laissez moi vous
présenter un programme standard sans goroutines qui attend 3 secondes pour chaque appel de
la fonction run().

package main

import (

"fmt"

"time"
)

func run(name string) {

for i := 0; i < 3; i++ {

[Link](1 * [Link]) // attendre 1 seconde

[Link](name, " : ", i)

func main() {

debut := [Link]()

run("Hatim")

run("Robert")

run("Alex")

fin := [Link]()

[Link]([Link](debut))

Copier
Résultat :
Hatim : 0

Hatim : 1

Hatim : 2

Robert : 0

Robert : 1

Robert : 2

Alex : 0

Alex : 1

Alex : 2

9.0095154s

Sans grande surprise le temps d'exécution est d'un peu après 9 secondes. Maintenant essayons
d'améliorer les performances de notre programme en utilisant les goroutines.

Pour créer une goroutine il faut placer le mot clé go avant un appel de fonction, exemple :

package main

import (

"fmt"

"time"

)
func run(name string) {

for i := 0; i < 3; i++ {

[Link](1 * [Link])

[Link](name, " : ", i)

func main() {

debut := [Link]()

go run("Hatim")

go run("Robert")

run("Alex")

fin := [Link]()

[Link]([Link](debut))

Copier

Avertissement
J'ai volontairement pas placé le mot clé go avant la ligne run("Alex"), vous allez
comprendre pourquoi plus tard.

Résultat :
Robert : 0

Hatim : 0

Alex : 0

Hatim : 1

Robert : 1

Alex : 1

Robert : 2

Hatim : 2

Alex : 2

3.0022266s

Les problèmes des goroutines

Hé hé, vous voyez c'est un sacré gain de temps d'exécution 😵. Il ne faut pas se réjouir trop vite
car maintenant je vais rajouter une goroutine à la ligne run("Alex").

package main

import (

"fmt"
"time"

func run(name string) {

for i := 0; i < 3; i++ {

[Link](1 * [Link])

[Link](name, " : ", i)

func main() {

debut := [Link]()

go run("Hatim")

go run("Robert")

go run("Alex")

fin := [Link]()

[Link]([Link](debut))

}
Copier

Résultat :
0s

Oulala 😨, 0 seconde et aucune fonction run() qui s'exécute ?! Mais que s'est-il passé ?

Pour répondre à ces questions répondrez d'abord à celle la : "Selon vous combien de
goroutines se sont exécutés ?"

La réponse est 4 !

Il y a certes les 3 goroutines de la fonction run() mais aussi une goroutine principale, je
m'explique. Lorsqu'un programme Go démarre, une goroutine commence à s'exécuter
immédiatement, c'est la goroutine principale de votre programme, c'est celle qui est exécutée
lorsque votre programme commence à s'exécuter. C'est la goroutine à partir duquel
d'autres goroutines enfants seront générées (ici les goroutines enfants sont ceux de la
fonction run()).

Lorsque l'exécution de la goroutine principale est terminée, les goroutines enfants sont
abandonnées aussi

Ce qui s'est passé c'est que le thread principal s'est alors terminé après l'exécution de la
fonction [Link]([Link](debut)) et c'est qui a tué les goroutines enfants.

La Solution

Pour éviter ce type problème il est possible d'utiliser la fonction [Link](temps


d'exécution de vos goroutines) avant la fin d'exécution de votre goroutine
principale, mais le problème avec cette technique c'est qu'il faut connaître à l'avance connaître
le temps d'exécution de la totalité de toutes vos goroutines.

Il existe une façon beaucoup plus simple pour synchroniser nos threads en utilisant la
structure WaitGroup de bibliothèque sync. Je vous dévoile d'abord le code et ensuite je vous
l'explique.

package main
import (

"fmt"

"sync"

"time"

var wg [Link] // instanciation de notre structure WaitGroup

func run(name string) {

defer [Link]()

for i := 0; i < 3; i++ {

[Link](1 * [Link])

[Link](name, " : ", i)

func main() {

debut := [Link]()
[Link](1)

go run("Hatim")

[Link](1)

go run("Robert")

[Link](1)

go run("Alex")

[Link]()

fin := [Link]()

[Link]([Link](debut))

Copier

Résultat :
Hatim : 0

Alex : 0

Robert : 0

Alex : 1

Robert : 1

Hatim : 1
Alex : 2

Robert : 2

Hatim : 2

3.0108647s

La structure WaitGroup vous permet d'attendre la fin d'exécution d'une collection de


goroutines. La méthode Add() permet de définir le nombre de goroutines à attendre (on
l'incrémente de 1 à chaque création de goroutine). Puis chacune des goroutines s'exécute et
appelle la méthode Done() lorsque la goroutine a terminé de s'exécuter. Dans le même
temps, la méthode Wait() est utilisée pour empêcher l'exécution d'autres lignes de code
jusqu'à ce que toutes les goroutines soient terminées.

Ici le mot-clé defer est extrêmement important ! La fonction qui est placée après le mot-
clé defer s'exécutera à chaque fois qu'on quittera notre fonction même en cas de panique
(plantage) de la fonction ! Le mot-clé defer nous garantit alors l'exécution de la
méthode Done(). Si vous supprimez le mot-clé et que votre programme panique (plante)
alors la méthode Done() ne sera jamais exécutée et votre programme tournera en boucle.

Information

Pour information vous pouvez utilisez la méthode Wait() autant de fois que vous voulez.
Lire et écrire dans un fichier
dans le langage de
programmation Go
Ce chapitre va vous apprendre comment vous pouvez
efficacement lire et écrire sur des fichiers à l'aide du langage
de programmation go.

Introduction
Dans ce chapitre, nous allons voir comment vous pouvez efficacement lire et écrire sur
des fichiers à l'aide du langage de programmation go.

Il existe de multiples façons pour lire et écrire dans un fichier. Dans les exemples qui
suivent je vais vous montrer les techniques que j'utilise selon les différents cas
d'utilisation que je juge le plus simple.

Je vais manipuler un fichier nommé [Link] avec le contenu suivant :

je suis un fichier de test

Lire seulement un fichier


Dans le cas ou vous ne faites que lire un fichier, le mieux reste d'utiliser la
fonction ReadFile() de la bibliothèque io/ioutil. Cette fonction retourne un
type byte (bit), il faut donc penser à caster (convertir le type) le résultat obtenu
en string

package main
import (

"fmt"

"io/ioutil"

func main() {

data, err := [Link]("[Link]") // lire le fichier


[Link]

if err != nil {

[Link](err)

[Link](string(data)) // conversion de byte en string

Copier

Résultat :
je suis un fichier de test

Dans le cas ou le fichier n'existe pas, vous aurez l'erreur suivante :

Le fichier spécifié est introuvable.


Écrire sur un fichier
il y a deux manières pour écrire dans un fichier. Soit vous décidez d'écraser un fichier
après écriture, soit d'écrire à la suite du contenu du fichier.

Pour écrire dans un fichier on va utiliser la bibliothèque os.

package main

import (

"fmt"

"io/ioutil"

"os"

func main() {

file, err := [Link]("[Link]", os.O_CREATE|os.O_WRONLY|


os.O_APPEND, 0600)

defer [Link]() // on ferme automatiquement à la fin de notre


programme

if err != nil {

panic(err)

}
_, err = [Link]("test\n") // écrire dans le fichier

if err != nil {

panic(err)

_, err = [Link]("i love test\n")

if err != nil {

panic(err)

data, err := [Link]("[Link]") // lire le fichier

if err != nil {

[Link](err)

[Link](string(data))

Copier
Information

J'utilise le "\n" pour faire un saut de la ligne dans mon fichier.

Résultat :
je suis un fichier de test

test

i love test

Je reviens ici sur ces lignes de code :

file, err := [Link]("[Link]", os.O_CREATE|os.O_WRONLY|


os.O_APPEND, 0600)

defer [Link]() // on ferme automatiquement le fichier après


l'avoir manipulé

Copier

La fonction [Link](), propose vraiment beaucoup d'options, que je vous


explique ci-dessous :

 Premier paramètre : correspond au nom du fichier à ouvrir


 Deuxième paramètre : ici ce sont des options spécialement dédiées au fichier que vous
allez manipuler :
o os.O_CREATE : Permet de créer le fichier si il n'existe pas.
o os.O_WRONLY : Permet de rendre le fichier (dans votre programme)
accessible en écriture seulement.
o os.O_APPEND : Permet de ne pas écraser le fichier quand vous écrivez
dessus (supprimez cette option si vous souhaitez écraser le fichier).
 Troisième paramètre : les droits d'accès de votre fichier (Plus d'information sur les
permissions ici ici)

Il est absolument important de fermer le fichier à la fin de votre programme. D'où


l'utilisation de la fonction close(), j'ai rajouté le mot clé defer, ce mot-clé permet
d'exécuter la ligne de code en question jusqu'à la fin d'exécution d'une fonction.
Bonus

On se retrouve avec beaucoup de lignes de code répétables, il serait temps d'utiliser


les super pouvoirs des fonctions pour mieux structurer notre code :

package main

import (

"fmt"

"io/ioutil"

"os"

func check(e error) {

if e != nil {

panic(e)

func write(text string, file *[Link]) {

if _, err := [Link](text); err != nil {


panic(err)

func read(filename string) string {

data, err := [Link](filename)

check(err)

return string(data)

func main() {

file, err := [Link]("[Link]", os.O_CREATE|os.O_WRONLY|


os.O_APPEND, 0600)

defer [Link]()

check(err)

write("Test\n", file)

data := read([Link]())

[Link](data)
}

Résultat :
je suis un fichier de test

test

i love test

Test

Exercice.
Voilà maintenant vous savez comment lire et écrire dans un fichier. Si vous souhaitez
aller encore plus loin, vous pouvez reprendre le tp du morpions pour rajouter un
système qui vous permet de sauvegarder la partie en cours (tour du joueur et le
damier).
Les channels dans le
langage de programmation
Go
Ce chapitre vous explique les channels en GoLang. Ils
permettent de créer, connecter, synchroniser et communiquer
les différentes goroutines. Vous allez apprendre à créer,
envoyer, recevoir vos channels et à gérer les différents cas
d'erreurs des channels dans le langage de programmation
GO.

C'est quoi les channels ?


Dans ce tutoriel nous allons parler des channels. Les channels sont utilisés avec les
goroutines pour envoyer des données (int, string, struct…) d'une goroutine et les
recevoir dans une autre goroutine. C'est un moyen de connecter les différentes
goroutines, c'est un moyen de communication et de synchronisation entre les
goroutines. La transmission des channels se fait qu'avec des goroutines

Les channels
Déclarer un channel

Pour déclarer votre channel, vous utiliserez le mot-clé make avec le mot-clé chan suivit
du type de donnée que vous souhaitez transiter.
ch := make(chan typeDeValeur)Copier

Envoyer et récupérer un channel

Pour envoyer ou recevoir une valeur dans un channel, il faut utiliser l'opérateur <-.

Exemple :
package main

import "fmt"

func run(c chan string, name string) {

c <- name // envoyer une valeur d'un channel

func main() {

canal := make(chan string)

go run(canal, "Hatim")

[Link](<-canal) // récupérer une valeur d'un channel

Copier

Résultat :
Hatim

Les channels sont bloquants

Les envois et les réceptions sur un channel est bloquant par défaut. Qu'est-ce que ça
veut dire ? Lorsqu'une donnée est envoyée à un channel, le contrôle est bloqué dans
l'instruction d'envoi jusqu'à ce qu'une autre goroutine lise depuis ce channel. De la
même manière, lorsque des données sont lues sur un channel, la lecture est bloquée
jusqu'à ce qu'une certaine goroutine écrit des données sur ce channel.

C'est cette propriété des channels qui permet aux goroutines de communiquer
efficacement sans l'utilisation de verrous explicites ou de variables conditionnelles.

package main

import (

"fmt"

"time"

func run(ch chan string, name string) {

[Link]([Link] * 2)

[Link]("fonction run() :", name)

ch <- name

}
func main() {

now := [Link]()

ch := make(chan string)

go run(ch, "channel 1")

[Link]("fonction main() :", <-ch)

go run(ch, "channel 2")

[Link]("fonction main() :", <-ch)

[Link]([Link]().Sub(now))

Copier

Résultat :
fonction run() : channel 1

fonction main() : channel 1

fonction run() : channel 2


fonction main() : channel 2

4.00097503s

Nous avons lancé 2 fois la goroutine de la fonction run() avec une lecture sur le
channel ch, nous pouvons remarquer que le temps d’exécution est de 4 secondes
avec un intervalle de 2 secondes entre chaque goroutine, ce qui prouve que les
channels sont bien bloquants.

Par contre ici on ne profite pas forcément de la puissance des goroutines car elles ne
sont pas lancées en simultanée vu que le channel est placé entre chaque goroutine.
Pour régler ce problème je vais changer l'ordre de lecture de nos channels.

package main

import (

"fmt"

"time"

func run(ch chan string, name string) {

[Link]([Link] * 2)

[Link]("fonction run() :", name)

ch <- name

}
func main() {

now := [Link]()

ch := make(chan string)

go run(ch, "channel 1")

go run(ch, "channel 2")

// changement d'ordre de lecture de nos channels

[Link]("fonction main() :", <-ch)

[Link]("fonction main() :", <-ch)

[Link]([Link]().Sub(now))

Copier

Résultat :
fonction run() : channel 1

fonction main() : channel 1

fonction run() : channel 2


fonction main() : channel 2

2.000341006s

Information

La première goroutine qui aura écrit sur un channel sera la première à être lu dans
notre programme.

deadlock

Par défaut, les channels sont dit unbuffered, ce qui signifie qu'ils n'accepteront pas
de récepteur ( chan<-) que s'il existe un expéditeur ( <- chan) correspondant prêt à
recevoir la valeur envoyée, l'inverse est aussi vrai.

Voila ce que j'entend par expéditeur et récepteur :

 récepteur : c'est le moment où on entre une valeur dans notre channel


 expéditeur : c'est le moment où on lit une valeur depuis notre channel

Dans cet exemple je vais créer un channel avec 5 récepteurs et 5 expéditeurs :

package main

import (

"fmt"

"sync"

"time"

var wg = [Link]{}
func main() {

now := [Link]()

ch := make(chan int)

// 5 expéditeurs

for j := 0; j < 5; j++ {

[Link](1)

go func() {

[Link]([Link] * 2)

i := <-ch

[Link](i)

[Link]()

}()

// 5 récepteurs

for j := 0; j < 5; j++ {


[Link](1)

go func() {

[Link]([Link] * 2)

ch <- 50

[Link]()

}()

[Link]()

[Link]([Link]().Sub(now))

Copier

Résultat :
50

50

50

50

50
2.000827906s

Ici nous avons autant de récepteurs que d'expéditeurs, donc aucune erreur dans notre
programme, mais maintenant je vais rajouter un récepteur en plus.

package main

import (

"fmt"

"sync"

"time"

var wg = [Link]{}

func main() {

now := [Link]()

ch := make(chan int)

// 5 expéditeur

for j := 0; j < 5; j++ {


[Link](1)

go func() {

[Link]([Link] * 2)

i := <-ch

[Link](i)

[Link]()

}()

// 6 récepteurs

for j := 0; j < 6; j++ {

[Link](1)

go func() {

[Link]([Link] * 2)

ch <- 50

[Link]()

}()

}
[Link]()

[Link]([Link]().Sub(now))

Copier

Erreur :
50

50

50

50

50

fatal error: all goroutines are asleep - deadlock!

D'après le résultat nous pouvons remarquer que les 5 premières goroutines se sont
bien exécutées mais ce n'est pas le cas pour la dernière goroutine. Car cette dernière
n'a aucun expéditeur d'où l'erreur deadlock.

buffered channels

Pour éviter le problème d'avant il est possible de rendre nos channels buffered, c'est-
à-dire de posséder autant de récepteurs que la taille du buffer de notre channel, par
exemple un channel avec une taille de buffer de 30 peut posséder 30 récepteurs.

Pour rendre notre channel buffered, il suffit d'indiquer la longueur de notre buffer
comme second argument de notre canal.
ch := make(chan type-de-valeur, taille_du_buffer)

package main

import (

"fmt"

"sync"

"time"

var wg = [Link]{}

func main() {

now := [Link]()

const size int = 10

ch := make(chan int, size) // channel avec un buffer de taille 10

// 5 expéditeurs

for j := 0; j < 5; j++ {


[Link](1)

go func() {

[Link]([Link] * 2)

i := <-ch

[Link](i)

[Link]()

}()

// 10 récepteurs

for j := 0; j < size; j++ {

[Link](1)

go func() {

[Link]([Link] * 2)

ch <- 50

[Link]()

}()

}
[Link]()

[Link]([Link]().Sub(now))

Copier

Résultat :
50

50

50

50

50

2.00064941s

Itération dans un channel

Il est possible d'itérer sur un channel buffered en utilisant le mot clé range. Je vais
volontairement provoquer une erreur en itérant sur une channel buffered qui possède
moins de récepteurs que la taille du buffer.

package main

import (

"fmt"
"time"

func main() {

ch := make(chan string, 2) // buffer de taille 2

go func() {

ch <- "test" // 1 seul récepteur

}()

for elem := range ch {

[Link](elem)

Copier

Erreur :
test

fatal error: all goroutines are asleep - deadlock!


Alors, déjà on peut remarquer qu'on arrive à lire notre première valeur de notre
channel, mais juste après nous avons un deadlock. L'erreur vient du fait qu'on est en
train de lire sur un channel avec un buffer de taille 2, sauf que ne nous n'avons rentré
qu'une seule valeur dans notre channel d'où l'erreur.

Pour nous prémunir de cette erreur, il faut indiquer à notre compilateur qu'il n'est pas
nécessaire de lire la suite du buffer de notre channel, cela est possible avec la
fonction close().

package main

import (

"fmt"

"time"

func main() {

ch := make(chan string, 2) // buffer de taille 2

go func() {

defer close(ch) // on indique à notre compilateur qu'on


a finit d'écrire sur le channel

ch <- "test"
}()

for elem := range ch {

[Link](elem)

Copier

Résultat :
test

Il existe aussi un autre moyen d'itérer sur un channel, mais avant de vous montrez le
code il faut savoir qu'un channel renvoie à la fois sa valeur mais aussi un
booléen qui vous indique si la valeur a été envoyée sur le channel.

package main

import (

"fmt"

func main() {

ch := make(chan string, 2) // buffer de taille 2


go func() {

defer close(ch) // on indique à notre compilateur qu'on


a finit d'écrire sur le channel

ch <- "test"

}()

for true {

if elem, ok := <-ch; ok == true { // est ce que le


chanel possède encore un récepteur ?

[Link](elem, ok)

} else {

break

Copier

Résultat :
test
TP créer une application de
chat dans le langage de
programmation Go
Vous avez pu voir beaucoup de notions en GoLang, il est
temps alors de réaliser un Tp en créant une application de
chat dans le langage de programmation GO et d'utiliser les
éléments que vous avez pu voir dans les chapitres
précédents.

Présentation du TP
Nous avons vu beaucoup de notions et je conçois qu'il soit difficile dans certains cas
de comprendre l'intérêt de certaines notions à travers des exemples basiques, et donc
c'est pour ces raisons que j'ai décidé de vous concocter un Tp pour utiliser les
éléments que nous avons pu voir dans les chapitres précédents.

Le but de ce tp, est de créer une application de discussion, permettant de


communiquer avec la personne que l'on souhaite et peu importe l'endroit où on se
trouve.

Les exigences

Voici une liste d'exigences pour ce tp :

 Le serveur accepte plusieurs utilisateurs


 Le serveur possède un fichier de logs avec les connexions entrantes et sortantes.
 L'utilisateur doit définir un pseudo avant de pouvoir se connecter et le pseudo ne doit
dépasser 20 caractères.
 L'utilisateur ne peut pas prendre un pseudo déjà utilisé
 L'utilisateur doit envoyer le message au serveur et le message sera diffusé à tous les
autres utilisateurs.
 Utilisez la notion de classe avec des structures (une structure pour le serveur et une
autre pour le client)
 Prévenir les autres utilisateurs qu'un utilisateur s'est connecté/déconnecté
 Le client et le serveur communiquent via le protocole TCP

Quelques conseils pour bien


démarrer
Je ne vais pas vous laissez sans aucunes informations (sauf si vous le souhaitez,
dans ce cas lancez vous sur le tp directement), ci-dessous je vais vous présenter les
étapes à suivre pour créer un serveur et plusieurs clients en utilisant la
bibliothèque net

Le serveur

Je vous ai mis des commentaires sur toutes les lignes de code, mais je vous tout de
même vous fournir plus tard quelques explications en plus. Commencez d'abords par
créer un fichier et nommez le [Link].

Voici à quoi il doit ressembler :

package main

import (

"fmt"

"net"

)
func gestionErreur(err error) {

if err != nil {

panic(err)

const (

IP = "[Link]" // IP local

PORT = "3569" // Port utilisé

func main() {

[Link]("Lancement du serveur ...")

// on écoute sur le port 3569

ln, err := [Link]("tcp", [Link]("%s:%s", IP, PORT))

gestionErreur(err)
// On accepte les connexions entrantes sur le port 3569

conn, err := [Link]()

if err != nil {

panic(err)

// Information sur les clients qui se connectent

[Link]("Un client est connecté depuis", [Link]())

gestionErreur(err)

// boucle pour toujours écouter les connexions entrantes (ctrl-c


pour quitter)

for {

// On écoute les messages émis par les clients

buffer := make([]byte, 4096) // taille maximum du


message qui sera envoyé par le client

length, err := [Link](buffer) // lire le message envoyé


par client

message := string(buffer[:length]) // supprimer les bits qui


servent à rien et convertir les bytes en string
if err != nil {

[Link]("Le client s'est déconnecté")

// on affiche le message du client en le convertissant de


byte à string

[Link]("Client:", message)

// On envoie le message au client pour qu'il l'affiche

[Link]([]byte(message + "\n"))

Copier

on a commencé par importer la bibliothèque net, c'est cette bibliothèque qui va nous
aider, à créer notre client et serveur

ln, err := [Link]("tcp", [Link]("%s:%s", IP, PORT))Copier

La fonction Listen() permet de créer un serveur, elle prend comme premier


paramètre le protocole à utiliser ici on utilise le protocole TCP/IP sans rentrer dans les
détails c'est un protocole qui va gérer pour nous les règles de communication. Ensuite
comme deuxième paramètre elle prend une valeur de type string sur laquelle on
associera notre IP (ici c'est l'ip local de notre machine) et notre port (ici 3569)

conn, err := [Link]()Copier


La fonction Listen() nous a permis de créer notre serveur sauf que pour le moment il
n'accepte aucun client encore. De ce fait on utilise la fonction Accept() pour accepter
les connexions entrantes.

Un bon serveur se doit d'être disponible 24h/24h 7j/7j "Pas de serveur, pas de clients
🍫" c'est pour cela qu'on utilise la boucle for pour être toujours à l'écoute des
connexions entrantes.

buffer := make([]byte, 4096)Copier

la taille du buffer est de de 4096, donc le message qu'on recevra du client ne peut
dépasser les 4096 bits, le surplus ne sera pas reçu par le client (il est possible d'éviter
ce problème avec la fonction NewReader() de la bibliothèque bufio, je vais utiliser
cette méthode sur le client)

>message := string(buffer[:length])Copier

Si notre message est de taille 16 et notre buffer de taille 4096, il serait plus approprié
d'adapter le buffer selon la taille du message en supprimant le surplus. Cette étape est
très importante si vous souhaitez faire des comparaisons de votre message avec une
autre string (je pense notamment à la vérification du pseudo par le serveur).

Enfin on ne peut envoyer et ne recevoir que des bits (des 0 ou 1) via le protocole TCP
(tous les protocoles d'ailleurs), il est donc important de convertir les messages avant
de les envoyer de string en byte et inversement quand on souhaite les afficher sur
notre écran.

Le client

Côté client ça reste un peu près le même code avec quelques petites modifications.
Créez un fichier et nommez le [Link] avec le code suivant :

package main

import (
"bufio"

"fmt"

"net"

"os"

func gestionErreur(err error) {

if err != nil {

panic(err)

const (

IP = "[Link]" // IP local

PORT = "3569" // Port utilisé

func main() {
// Connexion au serveur

conn, err := [Link]("tcp", [Link]("%s:%s", IP, PORT))

gestionErreur(err)

for {

// entrée utilisateur

reader := [Link]([Link])

[Link]("client: ")

text, err := [Link]('\n')

gestionErreur(err)

// On envoie le message au serveur

[Link]([]byte(text))

// On écoute tous les messages émis par le serveur et


on rajouter un retour à la ligne

message, err := [Link](conn).ReadString('\n')

gestionErreur(err)

// on affiche le message utilisateur


[Link]("serveur : " + message)

Copier

conn, err := [Link]("tcp", [Link]("%s:%s", IP, PORT))Copier

Ici on n'utilise pas la fonction Listen() mais la fonction Dial() qui nous permet créer
un client et de le connecter à notre serveur, le reste du code ressemble beaucoup au
code du serveur.

Résultat :

Côté serveur :

> go run [Link]

Lancement du serveur ...

Un client est connecté depuis [Link]:448

Client:salut

Client:ça va ?

Client:je parle seul :'(

Le client s'est déconnecté

Côté client :

> go run [Link]

client: salut

serveur : salut
client: ça va ?

serveur : ça va ?

client: je parle seul :'(

serveur : je parle seul :'(

client: ^Csignal: interrupt

Gérer plusieurs clients

Vous pensez que c'est fini ? Je vous assure que non car si vous souhaitez rajouter un
deuxième client voila ce qui se passe :

> go run [Link]

client: test

On touche à la limite de ce type de serveur, il n'est pas capable de


gérer simultanément plusieurs clients pour pallier ce problème il suffit de créer dans
notre serveur une goroutine par client connecté. Voici à quoi va ressembler notre
serveur en rajoutant cette fonctionnalité :

package main

import (

"bufio"

"fmt"

"net"

)
func gestionErreur(err error) {

if err != nil {

panic(err)

const (

IP = "[Link]"

PORT = "3569"

func read(conn [Link]) {

message, err := [Link](conn).ReadString('\n')

gestionErreur(err)

[Link]("Client:", string(message))

}
func main() {

[Link]("Lancement du serveur ...")

ln, err := [Link]("tcp", [Link]("%s:%s", IP, PORT))

gestionErreur(err)

var clients [][Link] // tableau de clients

for {

conn, err := [Link]()

if err == nil {

clients = append(clients, conn) //quand un client se


connecte on le rajoute à notre tableau

gestionErreur(err)

[Link]("Un client est connecté depuis",


[Link]())
go func() { // création de notre goroutine quand un client
est connecté

buf := [Link](conn)

for {

name, err := [Link]('\n')

if err != nil {

[Link]("Client disconnected.\n")

break

for _, c := range clients {

[Link]([]byte(name)) // on envoie un message à


chaque client

}()

Copier
Il faut savoir que par défaut la fonction Accept() est bloquante, elle ne s'exécutera
seulement si le serveur reçoit une nouvelle connexion. Une goroutine est alors créée
propre à chaque nouveau client connecté connexion.

Je vais créer deux clients et voici le résultat obtenu :

Côté serveur :

> go run [Link]

Lancement du serveur ...

Un client est connecté depuis [Link]:44976

Un client est connecté depuis [Link]:44980

Client 1 :

> go run [Link]

client: slt

serveur : slt

client: toto

serveur : ah

Client 2 :

> go run [Link]

client: ah

serveur slt

client: tata

serveur ah
Le serveur gère bien plusieurs clients mais par contre côté clients c'est du n'importe au
niveau de l'affichage des messages 🤔.

Le serveur envoie bel et bien les données aux clients, mais les clients ne semblent pas
bonnement synchroniser l'affichage des messages. Pour remédier ce problème il suffit
de créer deux goroutines dans notre client, une goroutine pour gérer la réception des
données et une goroutine pour gérer l'envoi des données.

Voici à quoi va ressembler notre nouveau client :

package main

import (

"bufio"

"fmt"

"net"

"os"

"sync"

func gestionErreur(err error) {

if err != nil {

panic(err)
}

const (

IP = "[Link]" // IP local

PORT = "3569" // Port utilisé

func main() {

var wg [Link]

// Connexion au serveur

conn, err := [Link]("tcp", [Link]("%s:%s", IP, PORT))

gestionErreur(err)

[Link](2)

go func() { // goroutine dédiée à l'entrée utilisateur


defer [Link]()

for {

reader := [Link]([Link])

text, err := [Link]('\n')

gestionErreur(err)

[Link]([]byte(text))

}()

go func() { // goroutine dédiée à la reception des messages du


serveur

defer [Link]()

for {

message, err := [Link](conn).ReadString('\n')

gestionErreur(err)

[Link]("serveur : " + message)

}()
[Link]()

Copier

Résultat :

Client 1 :

> go run [Link]

slt

serveur : slt

serveur : hoho

serveur : haha

serveur : bla bla

coco

serveur : coco

Client 2 :

> go run [Link]

hoho

serveur : hoho

haha
serveur : haha

bla bla

serveur : bla bla

serveur : coco

à vous de jouer

Vous ne vous êtes peut être pas rendu compte mais mais on vient de créer une
application de chat ! Par contre ça ne respecte pas encore nos exigences ;). Je vous ai
passé les informations nécessaires pour mener à bien ce TP. Vous n'allez pas être
noté ! Donc prenez le temps d'utiliser au maximum les notions vues sur les chapitres
précédents et bien sûr amusez-vous :).

Solution
Mon petit message

Avant de vous partager le code, je tiens à préciser plusieurs choses.

Tout d'abord mon code sera en anglais, la raison est que j'ai l'habitude de coder et
d'écrire mes documentations en anglais, je vous conseille de faire pareil car ça va
vous permettre d'apprendre la langue anglaise avec de nouveaux termes techniques
anglais, cette langue est très importante car la majorité des réponses de vos
recherches sur un moteur de recherche (Google ou autres 😉) seront en anglais.

Deuxièmement, je ne vais pas vous expliquer comme j'ai l'habitude de faire ligne par
ligne mon code. vous avez largement les connaissances nécessaires pour
comprendre mon code mais Je vous ai tout de même mis des commentaires sur
certaines lignes de code et sur toutes les méthodes de mes structures.

Troisièmement, il existe différentes façons de faire ce type d'application, donc n'hésitez


pas à le modifier selon votre guise.
Voici le lien pour télécharger mon code ici
Lancer le programme

Avant de lancer mon code, voici à quoi doit ressembler votre arborescence :

$GOPATH/src/

├── chat-application

│ ├── client

│ │ └── [Link]

│ └── server

│ └── [Link]

└── [Link]

Lancer le serveur :

go run [Link] --mode serverCopier

Lancer le client :

go run [Link] --mode clientCopier


Screenshot
Agrandir l'image
Compiler votre programme pour le
Partager !
Pour compiler votre programme afin de le partager avec les autres utilisateurs il suffit
de lancer la commande suivante :

go build [Link]

Si vous êtes sur windows ça vous créera un fichier [Link] et un fichier main sur
linux. Une fois compilé les utilisateurs qui utiliseront votre programme n'auront pas
besoin d'installer le compilateur go vu que sera un fichier binaire.
Conclusion du cours
d'initiation à GoLang
Clap de fin ! Vous connaissez à présent la plupart des
fonctionnalités de base du langage de programmation Go. J'ai
publié cet article pour vous présenter mon message de
conclusion, mes futurs projets/articles sur ce langage et
quelques conseils pour mieux progresser.

Conclusion
Vous voici arrivé(e) au terme de ce cours et je tiens vraiment à vous remercier d'avoir
pris le temps de suivre ce cours et à vous féliciter pour avoir tenu jusqu'au bout 🥇.

En ce qui me concerne, j'ai choisi de vous apprendre le langage Go, car c'est l'un des
langages que j'apprécie le plus, autant vous dire que je me suis vraiment bien régalé à
faire ce cours !

J'espère que la lecture de ce cours vous aura été utile et agréable et que ça vous aura
permis d'y voir un peu plus clair et que ces bases vous auront, je l'espère, donné envie
d'aller encore plus loin.

Vous connaissez à présent la plupart des fonctionnalités de base en Golang et vous


êtes donc d'ores et déjà capable de vous lancer sur des projets beaucoup plus
avancés.

Si j'ai un seul conseil à vous donner, ça sera de prendre le temps de pratiquer


régulièrement sur des projets (peu importe la taille) pour vous entraîner et vous
assurer d'avoir bien compris. C'est comme ça que vous allez réussir à évoluer et
pourquoi pas créer la futur technologie de demain 😉.
La suite ?
Pour les personnes qui se demanderaient si c'est vraiment le dernier tutoriel sur ce
langage ? La réponse est NON.

Je continuerai à faire d'autres tutoriels concernant ce langage. Il peut s'agir d'un article
ou je partage avec vous un simple projet ou script réutilisable. mais

je compte aussi prochainement prévoir un cours concernant l'utilisation de Golang


dans le monde du WEB.

Vous vous rappelez quand je vous ai évoqué au début de ce cours que le langage Go
est utilisé dans plusieurs domaines ? Et bah je ne vous ai pas menti (heureusement
😅), car je viens de découvrir un repository Github vachement sympa qui répertorie
plusieurs frameworks et bibliothèques Go selon le domaine qui vous intéresse.

Lien : [Link]

Sans oublier qu'il existe aussi les bibliothèques officielles de Golang que vous pouvez
retrouver ici.

Sur ce, bonne continuation !


« This is the end, Drop the Mic »

Vous aimerez peut-être aussi