gotour1.png

Le Gotour

par Romain Barraud· ·

Le Gotour leçon 1

Introduction

Go, comment ça marche ?

Tous les programmes Go sont composés de packages.

Les programmes se lancent dans un package nommé main .

Ce programme utilisent les packages en important les chemins fmt et math/rand

Par **convention, **le nom d’un package est celui du dernier élément du chemin d’import. Cela signifie que le package comprend les fichier qui commencent par l’instruction package rand.

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	fmt.Println("Mon numéro favori est", rand.Intn(10))
}

Si on s’intéresse vraiment

On voit que rand est un sous package de math

Et si on regarde ce que fait la fonction intn alors on voit qu’elle retourne un entier, un entier non négatif entre 0 et n. Et il panic (pose une erreur) si n est inférieur ou égal à zéro.

Donc si je lance le code suivant il devrait renvoyer un panic

func main() {
	fmt.Println("My favorite number is", rand.Intn(-10))
}

Ce à quoi il répond :

<span notion-color="red">panic: invalid argument to Intn

goroutine 1 [running]:
math/rand.(*Rand).Intn(0x100556c20?, 0xc0000061c0?)
	/usr/local/go-faketime/src/math/rand/rand.go:180 +0x4c
math/rand.Intn(0xfffffffffffffff6)
	/usr/local/go-faketime/src/math/rand/rand.go:453 +0x25
main.main()
	/tmp/sandbox3830465717/prog.go:9 +0x1a</span>

Les Imports

package main

package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Printf("Now you have %g problems.\n", math.Sqrt(7))
}

Ce code regroupe deux imports entre parenthèses, c’est une instruction d’import “factorisé”

Vous pouvez également écrire les différents imports unitairement comme ceci :

import "fmt"
import "math"

Mais c’est une bonne pratique d’utiliser l’instruction d’import factorisé.

Les Exported names

package main

import (
	"fmt"
	"math"
)

func main() {
	fmt.Println(math.Pi)
}

En Go un nom est exporté si il commence par une lettre capitale, par exemple Pizza est un nom exporté.

Tout comme la constante Pi qui existe et est exporté du package math.

Lorsque l’on importe un package, on peut uniquement utiliser les noms qui sont exportés. Tous les noms qui ne sont pas exportés ne sont pas accessibles en dehors du package. Par exemple si on avait mis l’instruction fmt.Println(math.pi) Alors on aurait eu l’erreur suivante :

<span notion-color="red">./prog.go:9:19: undefined: math.pi</span>

Go build failed.

Les fonctions

Les Functions :

package main

import "fmt"

func add(x int, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

Une fonction peu prendre 0 ou n arguments. On déclare une fonction en Go avec le mot clé func Dans cet exemple on peut voir que la fonction add prendre deux paramètres de type int.

Si vous souhaitez en savoir davantage sur les raisons pour lesquelles les types sont déclarés ainsi, consultez l'article sur le blog qui en traite. article on Go's declaration syntax

Les Functions continued

package main

import "fmt"

func add(x, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

Quand deux ou plusieurs paramètres nommés d’une fonction sont consécutifs et partagent un type, nous pouvons ne pas mettre le type sur jusqu’au dernier paramètre.

Dans cet exemple nous avons raccourci : x int, y int en x, y int.

Les résultats multiples

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)
}

Une fonction peut retourner un nombre quelconque de résultats.

Par exemple la fonction swap renvoie deux chaînes de caractères.

Les Named return values

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))
}

Les valeurs de retours peuvent être nommées en Go. Elles sont alors traitées en tant que variables définies en haut d’une fonction.

Ces noms doivent être explicites pour donner du sens aux valeurs retournées.

Une instruction return sans argument va retourner les named return values soit (x, y int) . On appelle ça un “naked” return. Les “naked return” doivent être utilisés que dans des fonctions courtes. Car elles ne sont pas simples à lire dans de longues fonctions

Un petit dernier pour tester

package main

import "fmt"

func addAndSayToto(x, y int, z string) (int, string) {
	return x + y, z
}

func main() {
	addition, name := addAndSayToto(42, 13, "toto")
	fmt.Println(addition)
	fmt.Println(name)
}

Je voulais vérifier que lors de la déclaration d’une continued function on puisse après deux paramètres consécutifs de même type enchaine sur un autre type.

Enfin je souhaitais récupérer les deux afin d’en différencier les affichages.

Les variables (on aurait dû commencer par là)

L’instruction var

L’instruction var déclare une liste de variables, comme dans les fonctions avec le type en dernier.

L’instruction var peut être au niveau du package ou au niveau d’une fonction. Comme on peut le voir dans cet exemple.

package main

import "fmt"

var c, python, java bool

func main() {
	var i int
	fmt.Println(i, c, python, java)
}

Les variables avec initializers

package main

import "fmt"

var i, j int = 1, 2

func main() {
	var c, python, java = true, false, "no!"
	fmt.Println(i, j, c, python, java)
}

Une déclaration avec l’instruction var peut contenir des initializers, un par variable. Si celui-ci est présent alors le type peut être omis. Go est assez intelligent pour déduire le type à partir de l’initializer.

Les Short variable déclarations

Dans une fonction l’instruction d’assignation rapide := peut être utilisée à la place de l’instruction var avec un type implicite.

Attention ce constructeur n’est pas valable en dehors d’une fonction. En dehors d’une fonction toutes les instructions commencent avec un mot-clé : var, func etc.

Les types basiques en Go

Voici les types basiques en Go

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias pour uint8

rune // alias pour int32
     // represente un code Unicode

float32 float64

complex64 complex128

L’exemple montre des variables de types différents, et aussi ces déclarations de variables peuvent être factorisés en bloc tout comme les instructions d’import.

Les types int, uint, uintptr sont des types de 32bits sur les systèmes 32bits et 64bits sur des systèmes 64bits. Quand vous avez besoin d’un entier, utilisez int tant que vous n’avez pas une raison spécifique d’utiliser les autres.

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)
}

Les Zero values

Les variables déclarées sans valeurs initiales vont donner leur zero value La zero value est :

  • 0 pour les types numeric
  • false pour le type boolean
  • “” (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)
}

Les conversions de type

L’expression T(v) convertit la valeur v vers le type T

Voici quelques conversions numériques :

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

Ou plus simplement

i := 42
f := float64(i)
u := uint(f)

Contrairement au C, l’assignation entre éléments de types différents nécessite une conversion explicite.

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)
}

Allons un peu plus loin , comment Go se comporte lors de conversions particulières ?

package main

import "fmt"

func main() {
	i := 123
	s := string(i) 
	fmt.Println("%s", s)
}

Cela m’affiche la valeur x mais pourquoi ? parce que 123 est la valeur ascii du caractère x Mais comment afficher “123” ?

Ne venant pas du C et en recherchant sur le web on trouve des réponses

https://stackoverflow.com/questions/11123865/format-a-go-string-without-printing https://stackoverflow.com/questions/10105935/how-to-convert-an-int-value-to-string-in-go

package main

import (
    "strconv"
    "fmt"
)

func main() {
    t := strconv.Itoa(123)
    fmt.Println(t)
}

La meilleure solution est celle-ci car l’utilisation de Sprintf est apparemment plus coûteuse.

Type inference

Quand on déclare une variable sans spécifier son type , elle est devinée à partir de la valeur de droite.

var i int
j := i // j est donc un int

Mais quand la partie de droite contient une constante numérique sans type, alors sa valeur peut prendre plusieurs types comme int, float64 ou complex128 selon la précision de la constante.

i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128
package main

import "fmt"

func main() {
	v := 42 // change me!
	fmt.Printf("v is of type %T\n", v)
}

Les constantes

Une constante peut être déclarée comme des variables, mais avec le mot clé const Les constantes peuvent être un caractères une chaine de caractères un boolean ou une valeur numérique.

Une constante ne peut être déclarée avec la syntaxe :=

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)
}

Les constantes numériques

Les constantes numériques sont des valeurs à haute précision. Une constante non typée prend le type nécessaire selon son contexte.

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))
}

Félicitations c’est la fin de la première leçon du gotour