Tour de Go¶
Basé sur le tour officiel de Go.
Le point d'entrée de toute application Go est la fonction main
du package main
.
Cet exemple affiche "Hello, 世界" à la console (世界 signifie "monde" et se prononce "sékay" en japonais).
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Hello, 世界
On peut regrouper les imports entre parenthèses pour importer plusieurs packages.
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}
Now you have 2.6457513110645907 problems.
Variables¶
La capitale initiale des noms de fonctions ou de variables rend ces éléments exportés et donc accessibles depuis d'autres packages. Donc, ce n'est pas juste une convention.
Dans cet exemple Println
est accessible depuis le package fmt
car son nom commence par une majuscule.
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.Pi)
}
3.141592653589793
:=
permet de déclarer et d'initialiser une variable en une seule instruction sans spécifier son type explicitement.=
permet d'affecter une nouvelle valeur à une variable déjà déclarée.
package main
import "fmt"
func main() {
a := 10 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
b := "Hello"
fmt.Println(a, f, g)
fmt.Println(b)
b = "Monde"
fmt.Println(b)
}
10 3.142 (0.867+0.5i) Hello Monde
Alternativement, var
permet de déclarer des variables sans les initialiser (on peut toujours initialiser si on veut). Il est nécessaire de spécifier le type dans ce cas. Je recommande d'utiliser :=
autant que possible, et var
lorsque la valeur initiale ne peut être obtenue.
package main
import "fmt"
var c, python, java bool
var a, b float64 = 1.4, 2.999
func main() {
var i int
fmt.Println(i, c, python, java)
fmt.Println(a, b)
}
0 false false false 1.4 2.999
Les variables déclarées sans valeur initiale explicite reçoivent leur valeur zéro.
- 0 pour les types numériques,
- false pour le type booléen, et
- "" (la chaîne vide) pour les chaînes de caractères.
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
0 0 false ""
Les constantes sont déclarées avec le mot-clé const
. Leur valeur doit être connue à la compilation et ne peut pas changer. Je recommande d'utiliser des constantes par défaut et autant que possible pour éviter les erreurs.
package main
import "fmt"
const Pi = 3.14
func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
Hello 世界 Happy 3.14 Day Go rules? true
Les constantes en Go supportent les grands entiers.
package main
import "fmt"
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
// fmt.Println(needInt(Big)) // erreur car bigInt ne peut pas rentrer dans un int
}
21 0.2 1.2676506002282295e+29
Fonctions¶
Le type des variables, des arguments et des valeurs de retour est déclaré après le nom de la variable et du nom de la fonction. Si les types sont les mêmes, on peut les regrouper.
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func multiply(x, y int) int {
return x * y
}
func main() {
fmt.Println(add(42, 13))
fmt.Println(multiply(42, 13))
}
55 546
Les fonctions peuvent retourner plusieurs valeurs.
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
world hello
On peut nommer les valeurs de retour d'une fonction. Cela permet de documenter le code et de retourner les valeurs en utilisant simplement l'instruction return
sans spécifier les variables à retourner.
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
7 10
Fonctions comme des citoyens de première classe¶
On peut les assigner à des variables, les passer en arguments à d'autres fonctions et les retourner depuis des fonctions. On dit que les fonctions sont aussi des valeurs.
package main
import (
"fmt"
"math"
)
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
13 5 81
Les types de base¶
txt
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
fmt.Printf("Type: %T Value: %v\n", z, z)
}
Type: bool Value: false Type: uint64 Value: 18446744073709551615 Type: complex128 Value: (2+3i)
Typage fort¶
Go est un langage à typage fort. Les conversions de types doivent être explicites (contrairement au C et au JavaScript).
package main
import (
"fmt"
"math"
)
func main() {
var x, y int = 3, 4
var f float64 = math.Sqrt(float64(x*x + y*y))
var z uint = uint(f)
fmt.Println(x, y, z)
}
3 4 5
Structures de contrôle¶
Boucles avec une variable qui s'incrémente à chaque itération. Dans cette forme, i := 0;
s'appelle une instruction d'initialisation, i < 10;
est la condition de boucle, et i++
est l'instruction post qui s'exécute à la fin de chaque itération. Les instructions d'initialisation et post sont optionnelles.
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
45
Boucle sans variable d'itération où les instructions d'initialisation et post sont absentes.
package main
import "fmt"
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
1024
if
ne nécessite pas de parenthèses autour de la condition, mais nécessite des accolades autour du bloc de code.
package main
import (
"fmt"
"math"
)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func main() {
fmt.Println(sqrt(2), sqrt(-4))
}
1.4142135623730951 2i
if
supporte une instruction d'initialisation avant la condition assez utile pour intialiser des variables limitées au scope du if
et de son else
.
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// v n'est pas dispo ici
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
27 >= 20 9 20
switch
est un if
raccourci. Les break
sont implicites à la fin de chaque cas.
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("macOS.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
}
Go runs on macOS.
On peut omettre l'expression dans un switch
pour faire un switch
sur true
. Cela permet d'écrire des conditions plus complexes.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
Good afternoon.
defer
permet de différer l'exécution d'une fonction jusqu'à la fin de la fonction englobante. C'est particulièrement utile pour fermer des ressources (fichiers, connexions réseau, etc.) de manière sûre et lisible.
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
hello world
Pointeurs¶
Un pointeur est une variable qui stocke l'adresse mémoire d'une autre variable. Les pointeurs sont utiles pour partager des données entre différentes parties du code sans faire de copies.
Pour une variable x
de type T
:
&x
est un pointeur versx
, de type*T
*p
est la valeur pointée par le pointeurp
, de typeT
. On appelle cela la déréférenciation ou l'indirection.
Go ne propose pas d'arithmétique de pointeurs comme en C.
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // point to i
fmt.Println(*p) // read i through the pointer
*p = 21 // set i through the pointer
fmt.Println(i) // see the new value of i
p = &j // point to j
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j
}
42 21 73
struct¶
Collection de champs. Peuvent être compararés aux structs en C ou aux classes en Python/Java (sans les méthodes).
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
4
Si on a un pointeur vers une struct, on peut accéder à ses champs directement avec le point (.
) sans avoir à déréférencer explicitement le pointeur.
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9 // au lieu de (*p).X
fmt.Println(v)
}
{1000000000 2}
Différentes façons de création d'une struct via les littéraux de struct :
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
)
func main() {
fmt.Println(v1, p, v2, v3)
}
{1 2} &{1 2} {1 0} {0 0}
Tableaux et slices¶
Les tableaux ont une taille fixe en Go. Le type s'écrit [n]T
où n
est la taille et T
le type des éléments.
package main
import "fmt"
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
Hello World [Hello World] [2 3 5 7 11 13]
Un slice est une référence vers un tableau ou un sous-tableau existant. On peut récupérer un slice d'un tableau avec a[low : high]
où low
est l'indice de début (inclusif) et high
l'indice de fin (exclusif). low
et high
sont optionnels : si low
est omis, il vaut 0, et si high
est omis, il vaut la longueur du tableau. Les slices suivants sont équivalents :
a[0:10]
a[:10]
a[0:]
a[:]
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
[3 5 7]
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
[John Paul George Ringo] [John Paul] [Paul George] [John XXX] [XXX George] [John XXX George Ringo]
On peut creér un slice avec les littéraux de slice qui ressemblent aux littéaux de tableaux sans le type. Dans ce cas, un tableau sous-jacent est créé automatiquement et on n'a pas accès à ce tableau directement, mais on peut manipuler le slice comme n'importe quel autre tableau. La syntaxe []Type{valeurs}
package main
import "fmt"
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
[2 3 5 7 11 13] [true false true true false true] [{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
- La longueur d'un slice est le nombre d'éléments qu'il contient et se récupère avec la fonction intégrée
len
. - La capacité d'un slice est la taille du tableau sous-jacent à partir de l'indice de début du slice et se récupère avec la fonction intégrée
cap
.
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// Slice the slice to give it zero length.
s = s[:0]
printSlice(s)
// Extend its length.
s = s[:4]
printSlice(s)
// Drop its first two values.
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=6 cap=6 [2 3 5 7 11 13] len=0 cap=6 [] len=4 cap=6 [2 3 5 7] len=2 cap=4 [5 7]
La valeur zéro d'un slice est nil
. Un slice nil
n'a ni tableau sous-jacent, ni longueur, ni capacité.
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
[] 0 0 nil!
make
permet de créer des slices, des maps et des channels. Pour les slices, on peut spécifier la longueur et la capacité initiales.
a := make([]int, 5) // len(a)=5
b := make([]int, 1, 5) // len(b)=1, cap(b)=5
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
a len=5 cap=5 [0 0 0 0 0] b len=0 cap=5 [] c len=2 cap=5 [0 0] d len=3 cap=3 [0 0 0]
On peut utiliser l'opérateur append
pour ajouter des éléments à un slice. Si la capacité du slice est dépassée, un nouveau tableau sous-jacent est alloué automatiquement.
package main
import "fmt"
func main() {
var s []int
printSlice(s)
// append works on nil slices.
s = append(s, 0)
printSlice(s)
// The slice grows as needed.
s = append(s, 1)
printSlice(s)
// We can add more than one element at a time.
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=0 cap=0 [] len=1 cap=1 [0] len=2 cap=2 [0 1] len=5 cap=6 [0 1 2 3 4]
Exemples de slice multidimensionnel (un slice sont les éléments sont des slices eux-mêmes) :
package main
import (
"fmt"
"strings"
)
func main() {
// Create a tic-tac-toe board.
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// The players take turns.
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
}
X _ X O _ X _ _ O
Range¶
Permet d'itérer sur les éléments d'un tableau, d'un slice, d'une map ou d'une chaîne de caractères. Chaque itération retourne deux valeurs : l'index (ou la clé pour les maps) et la valeur de l'élément courant. On peut ignorer l'une des deux valeurs en utilisant le caractère _
.
package main
import "fmt"
func main() {
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
for i := range pow {
pow[i] = 1 << uint(i) // == 2**i
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}
2**0 = 1 2**1 = 2 2**2 = 4 2**3 = 8 2**4 = 16 2**5 = 32 2**6 = 64 2**7 = 128 1 2 4 8 16 32 64 128
Maps (dictionnaires)¶
Les maps sont des collections de paires clé-valeur. Les littéraux de map permettent de créer et d'initialiser une map en une seule instruction sans avoir à préciser son type.
package main
import "fmt"
func main() {
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
var m2 = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
// Forme la plus simple
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
fmt.Println(m2)
}
Cell[36]: Line 28 ./main.go:108:7: m redeclared in this block
Cell[36]: Line 10 ./main.go:90:7: other declaration of m
failed to run "/opt/homebrew/bin/go build -o /var/folders/sj/67508vy94d1346kyt0w_kcjw0000gn/T/gonb_9f099a67/gonb_9f099a67": exit status 1
Manipulation d'une map :
package main
import "fmt"
func main() {
m := make(map[string]int)
m["Answer"] = 42
fmt.Println("The value:", m["Answer"])
m["Answer"] = 48
fmt.Println("The value:", m["Answer"])
delete(m, "Answer")
fmt.Println("The value:", m["Answer"])
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
}
The value: 42 The value: 48 The value: 0 The value: 0 Present? false
Rappels sur quelques concepts¶
- Typage statique: le type des variables est connu à la compilation et ne change pas.
- Typage fort: les conversions de types doivent être explicites.
- Type explicite: le type des variables est déclaré explicitement.
- Type inféré: le type des variables est déduit par le compilateur à partir de la valeur initiale.