Gestion des Entrées/Sorties Formatées avec le Package `fmt` de Go

Le package standard fmt de Go fournit des fonctions d'entrée/sortie formatées, s'inspirant des fonctionnalités de printf et scanf du langage C. Il est essentiel pour interagir avec l'utilisateur ou d'autres flux de données. Ce package se divise principalement en deux catégories : l'émission de données (sortie) et la lecture de données (entrée).

Fonctions de Sortie du Package fmt

La bibliothèque fmt offre plusieurs fonctions pour afficher des informations, dont les plus courantes sont Print, Fprint, Sprint et Errorf.

Les fonctions Print, Printf et Println

Ces fonctions dirigent leur sortie vers le flux de sortie standard du système. Elles se ditsinguent par leur capacité de formatage et l'ajout de retours à la ligne :

  • Print affiche des valeurs sans formatage spécifique.
  • Printf permet un formatage précis des chaînes de caractères via des spécificateurs.
  • Println ajoute automatiquement un saut de ligne après les valeurs affichées.
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)

La puissance de Printf réside dans ses spécificateurs de formatage.

Spécificateurs de Formatage

Les fonctions de la série *printf utilisent un argument format pour structurer la sortie. Voici une ventilation des spécificateurs par type de variable :

Spécificateurs Génériques

  • %v : Représentation par défaut de la valeur.
  • %+v : Similaire à %v, mais ajoute les noms de champs pour les structures.
  • %#v : Représentation de la valeur en syntaxe Go.
  • %T : Affiche le type de la valeur.
  • %% : Imprime un signe de pourcentage littéral.
package main

import "fmt"

func main() {
    nombre := 123
    estVrai := true
    produit := struct { nom string }{nom: "Ordinateur Portable"}

    fmt.Printf("Valeur par défaut : %v\n", nombre)      // 123
    fmt.Printf("Valeur par défaut : %v\n", estVrai)     // true
    fmt.Printf("Structure avec noms de champs : %+v\n", produit) // {nom:Ordinateur Portable}
    fmt.Printf("Syntaxe Go de la structure : %#v\n", produit)    // struct { nom string }{nom:"Ordinateur Portable"}
    fmt.Printf("Type de la valeur : %T\n", produit)    // struct { nom string }
    fmt.Printf("Pourcentage littéral : 50%%\n")         // 50%
}

Spécificateurs pour Booléens

  • %t : Affiche true ou false.
package main

import "fmt"

func main() {
    estActif := true
    fmt.Printf("État : %t\n", estActif) // true
}

Spécificateurs pour Entiers

  • %b : Représentation binaire.
  • %c : Caractère Unicode correspondant à la valeur.
  • %d : Représentation décimale.
  • %o : Représentation octale.
  • %x : Représentation hexadécimale (minuscules).
  • %X : Représentation hexadécimale (majuscules).
  • %U : Format Unicode : U+XXXX.
  • %q : Littéral de caractère Go entre guillemets simples (échappement si nécessaire).
package main

import "fmt"

func main() {
    valeurASCII := 70 // 'F'
    fmt.Printf("Binaire : %b\n", valeurASCII) // 1000110
    fmt.Printf("Caractère : %c\n", valeurASCII) // F
    fmt.Printf("Décimal : %d\n", valeurASCII) // 70
    fmt.Printf("Octal : %o\n", valeurASCII) // 106
    fmt.Printf("Hex (minuscules) : %x\n", valeurASCII) // 46
    fmt.Printf("Hex (majuscules) : %X\n", valeurASCII) // 46
    fmt.Printf("Unicode : %U\n", valeurASCII) // U+0046
    fmt.Printf("Littéral de caractère : %q\n", '€') // '€'
}

Spécificateurs pour Flottants et Nombres Complexes

  • %b : Notation scientifique binaire sans décimales, ex: -123456p-78.
  • %e : Notation scientifique (minuscules), ex: -1.234e+78.
  • %E : Notation scientifique (majuscules), ex: -1.234E+78.
  • %f : Point fixe décimal sans exposant, ex: 123.456.
  • %F : Équivalent à %f.
  • %g : Utilise %e ou %f selon ce qui est le plus concis.
  • %G : Utilise %E ou %F selon ce qui est le plus concis.
package main

import "fmt"

func main() {
    temperature := 28.5678
    fmt.Printf("Flottant binaire : %b\n", temperature) // 6432326759160576p-48
    fmt.Printf("Scientifique (e) : %e\n", temperature) // 2.856780e+01
    fmt.Printf("Scientifique (E) : %E\n", temperature) // 2.856780E+01
    fmt.Printf("Fixe : %f\n", temperature)            // 28.567800
    fmt.Printf("Générique (g) : %g\n", temperature)   // 28.5678
    fmt.Printf("Générique (G) : %G\n", temperature)   // 28.5678
}

Spécificateurs pour Chaînes de Caractères et Slices de Bytes

  • %s : Affiche directement la chaîne ou le slice de bytes.
  • %q : Littéral de chaîne Go entre guillemets doubles (échappement si nécessaire).
  • %x : Chaque octet en hexadécimal (minuscules).
  • %X : Chaque octet en hexadécimal (majuscules).
package main

import "fmt"

func main() {
    phrase := "Salut Go"
    fmt.Printf("Chaîne simple : %s\n", phrase)    // Salut Go
    fmt.Printf("Littéral de chaîne : %q\n", phrase) // "Salut Go"
    fmt.Printf("Hexadécimal (octets) : %x\n", phrase) // 53616c757420476f
    fmt.Printf("Hexadécimal (majuscules) : %X\n", phrase) // 53616C757420476F
}

Spécificateurs pour Pointeur

  • %p : Représentation hexadécimale avec le préfixe 0x.
  • %#p : Représentation hexadécimale sans le préfixe 0x.
package main

import "fmt"

func main() {
    compteur := 50
    fmt.Printf("Adresse avec 0x : %p\n", &compteur)  // 0xc000...
    fmt.Printf("Adresse sans 0x : %#p\n", &compteur) // c000...
}

Contrôle de la Largeur et de la Précision

La largeur est définie par un nombre décimal suivant le signe pourcent. Si elle est omise, aucune information de remplissage n'est ajoutée au-delà du nécessaire. La précision est spécifiée par un point suivi d'un nombre décimal après la largeur (facultatif). Si la précision est omise, une précision par défaut est utilisée ; un point sans nombre indique une précision de zéro.

Spécificateur Description
%f Largeur et précision par défaut.
%10f Largeur de 10 caractères, précision par défaut.
%.3f Largeur par défaut, 3 chiffres après la virgule.
%10.3f Largeur de 10 caractères, 3 chiffres après la virgule.
%10.f Largeur de 10 caractères, précision de 0 (nombre entier).
package main

import "fmt"

func main() {
    valeurDecimale := 3.14159265
    fmt.Printf("Défaut : %f\n", valeurDecimale)       // 3.141593
    fmt.Printf("Largeur 10 : %10f\n", valeurDecimale)  //   3.141593
    fmt.Printf("Précision 2 : %.2f\n", valeurDecimale)  // 3.14
    fmt.Printf("Largeur 10, Précision 2 : %10.2f\n", valeurDecimale) //       3.14
    fmt.Printf("Largeur 10, Précision 0 : %10.f\n", valeurDecimale)  //          3
}

Flags Additionnels

  • '+' : Affiche toujours le signe numérique ; pour %q (%+q), produit une sortie entièrement ASCII (avec échappement).
  • ' ' (espace) : Pour les nombres positifs, ajoute un espace devant (au lieu de rien) ; pour les chaînes avec %x ou %X (% x ou % X), ajoute des espaces entre les octets imprimés.
  • '-' : Remplit les espaces sur la droite de la sortie au lieu de la gauche (alignement à gauche par défaut).
  • '#' : Ajoute 0 pour les octaux (%#o), 0x pour les hexadécimaux (%#x) ou 0X (%#X) ; retire le 0x pour les pointeurs (%#p) ; pour %q (%#q), imprime le littéral Go entre guillemets simples ; pour %U (%#U), affiche le littéral Go entre espaces et guillemets simples.
  • '0' : Utilise des zéros pour le remplissage au lieu d'espaces ; pour les types numériques, les zéros de remplissage sont placés après le signe.

Les fonctions Fprint, Fprintf et Fprintln

Ces fonctions dirigent leur sortie vers une varialbe implémentant l'interface io.Writer. Elles sont couramment utilisées pour écrire dans des fichiers ou d'autres flux personnalisés.

func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)

Un exemple typique est l'écriture dans un fichier :

package main

import (
	"fmt"
	"io"
	"os"
)

func main() {
	// Écrire dans la sortie standard (qui est un io.Writer)
	fmt.Fprintln(os.Stdout, "Ceci est envoyé à la console.")

	fichier, err := os.OpenFile("log.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("Erreur lors de l'ouverture du fichier :", err)
		return
	}
	defer func(closer io.Closer) {
		err := closer.Close()
		if err != nil {
			fmt.Println("Erreur lors de la fermeture du fichier :", err)
		}
	}(fichier)

	utilisateur := "Alice"
	// Écrire des données formatées dans le fichier
	_, err = fmt.Fprintf(fichier, "L'utilisateur %s s'est connecté à %s.\n", utilisateur, "192.168.1.1")
	if err != nil {
		fmt.Println("Erreur lors de l'écriture dans le fichier :", err)
	}
}

Les fonctions Sprint, Sprintf et Sprintln

Contrairement aux fonctions Print et Fprint, ces fonctions ne produisent pas de sortie directe. Elles formatent les données et renvoient le résultat sous forme de chaîne de caractères.

func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
func Sprintln(a ...interface{}) string

package main

import "fmt"

func main() {
    messageInitial := fmt.Sprint("Initialisation du système.")
    nom := "Bob"
    age := 30
    infoUtilisateur := fmt.Sprintf("Utilisateur: %s, Âge: %d ans.", nom, age)
    logEvenement := fmt.Sprintln("Événement enregistré.")

    fmt.Println(messageInitial) // Initialisation du système.
    fmt.Println(infoUtilisateur) // Utilisateur: Bob, Âge: 30 ans.
    fmt.Println(logEvenement)   // Événement enregistré.
}

La fonction Errorf

La fonction Errorf génère une chaîne formatée et la renvoie en tant qu'objet error. Elle est couramment utilisée pour créer des messages d'erreur personnalisés.

func Errorf(format string, a ...interface{}) error

Un cas d'utilisation moderne est l'encapsulation d'erreurs (Error Wrapping) grâce au spécificateur %w, introduit dans Go 1.13. Cela permet de chaîner des erreurs, facilitnat leur inspection ultérieure.

package main

import (
	"errors"
	"fmt"
)

func main() {
	errInterne := errors.New("échec de la connexion à la base de données")
	errExterne := fmt.Errorf("impossible de traiter la requête: %w", errInterne)

	fmt.Println(errExterne) // impossible de traiter la requête: échec de la connexion à la base de données
	if errors.Is(errExterne, errInterne) {
		fmt.Println("L'erreur externe enveloppe l'erreur interne.")
	}
}

Fonctions d'Entrée du Package fmt

Le package fmt propose également Scan, Scanf et Scanln pour lire les entrées de l'utilisateur depuis le flux standard.

Les fonctions Fscan, Fscanf et Fscanln

Ces fonctions sont l'équivalent des fonctions Scan mais lisent les données depuis un io.Reader spécifié, plutôt que l'entrée standard.

func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)

Les fonctions Sscan, Sscanf et Sscanln

Ces fonctions sont l'équivalent des fonctions Scan mais lisent les données depuis une chaîne de caractères fournie, au lieu de l'entrée standard.

func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)

La fonction Scan

fmt.Scan analyse les données lues depuis l'entrée standard. Elle stocke les données séparées par des espaces blancs dans les arguments fournis. Les retours à la ligne sont traités comme des espaces blancs.

  • La fonction renvoie le nombre d'éléments scannés avec succès et toute erreur rencontrée.
  • Une erreur est retournée si le nombre d'éléments lus est inférieur au nombre d'arguments fournis.
func Scan(a ...interface{}) (n int, err error)

package main

import "fmt"

func main() {
	var (
		prenom   string
		age      int
		ville    string
	)
	fmt.Print("Entrez votre prénom, âge et ville (séparés par des espaces) : ")
	_, err := fmt.Scan(&prenom, &age, &ville)
	if err != nil {
		fmt.Println("Erreur de lecture :", err)
		return
	}
	fmt.Printf("Données lues : Prénom: %s, Âge: %d, Ville: %s\n", prenom, age, ville)
}

Exemple d'interaction :

Entrez votre prénom, âge et ville (séparés par des espaces) : Paul 35 Paris
Données lues : Prénom: Paul, Âge: 35, Ville: Paris

La fonction Scanf

fmt.Scanf lit le texte de l'entrée standard en se basant sur un format spécifique fourni via l'argument format. Elle stocke les valeurs lues dans les arguments passés.

func Scanf(format string, a ...interface{}) (n int, err error)

Scanf exige que l'entrée corresponde précisément au format spécifié, y compris les caractères littéraux.

package main

import "fmt"

func main() {
	var (
		nomArticle string
		quantite   int
		prixUnite  float32
	)
	fmt.Print("Entrez l'article au format 'Article: [nom] Quantité: [qty] Prix: [prix]' : ")
	_, err := fmt.Scanf("Article: %s Quantité: %d Prix: %f", &nomArticle, &quantite, &prixUnite)
	if err != nil {
		fmt.Println("Erreur de lecture formatée :", err)
		return
	}
	fmt.Printf("Détails de l'article : %s, Quantité: %d, Prix unitaire: %.2f\n", nomArticle, quantite, prixUnite)
}

Exemple d'interaction :

Entrez l'article au format 'Article: [nom] Quantité: [qty] Prix: [prix]' : Article: Clavier Quantité: 2 Prix: 75.50
Détails de l'article : Clavier, Quantité: 2, Prix unitaire: 75.50

Si l'entrée ne respecte pas le format :

Entrez l'article au format 'Article: [nom] Quantité: [qty] Prix: [prix]' : Clavier 2 75.50
Erreur de lecture formatée : expected 'Article:', got 'Clavier'

La fonction Scanln

fmt.Scanln est similaire à Scan, mais elle arrête de scanner dès qu'un caractère de nouvelle ligne est rencontré. La dernière donnée lue doit être suivie d'un retour à la ligne ou de la fin du flux.

func Scanln(a ...interface{}) (n int, err error)

Cette fonction est souvent préférée pour lire des lignes complètes de données.

Utilisation de bufio.NewReader pour des lignes avec des espaces

Pour lire une ligne entière d'entrée qui peut contenir des espaces (comme une phrase), les fonctions Scan ne sont pas toujours idéales car elles séparent les entrées par des espaces. Le package bufio offre une solution robuste.

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin) // Crée un lecteur depuis l'entrée standard
	fmt.Print("Veuillez saisir une phrase : ")
	saisie, err := reader.ReadString('\n') // Lit jusqu'au caractère de nouvelle ligne
	if err != nil {
		fmt.Println("Erreur de lecture :", err)
		return
	}
	saisie = strings.TrimSpace(saisie) // Supprime les espaces blancs et le retour à la ligne
	fmt.Printf("Vous avez saisi : %#v\n", saisie)
}

Étiquettes: Go fmt printf scanf gestion d'erreurs

Publié le 25 juin à 05h27