Programmation C source/entrées sorties
int main(void)
 /*
 Entrées/sorties
 1. Manipulation de fichiers
 2. Sorties formatées
 3. Entrées formatées
 4. Entrées non formatées
 5. Entrées/sorties brutes
 6. Gestion des erreurs
 printf("----------------------------------------\n");
 printf(" 1. Manipulation de fichiers\n");
 printf("----------------------------------------\n");
 à savoir :
 La variable qui représente un fichier sera de type FILE.
 C'est la bibliothèque standard qui est responsable
 de fournir le type FILE.
 stdio.h définit les variables suivantes :
 stdin : l'entrée standard, le clavier
 stdout : la sortie standard, l'écran
 stderr : la sortie standard des erreurs, l'écran
 printf(" 1.1 Ouverture\n");
 #if 0
 FILE * fopen(const char * restrict chemin, const char * restrict mode)
 #endif
 le fichier à ouvrir est désigné par chemin
 L'argument mode est une chaîne de caractères
 désignant la manière dont on veut ouvrir le fichier
 fopen() crée un nouveau flux de données
 qui permet d'accéder au contenu du fichier
 et renvoie l'adresse mémoire sur ce flux (FILE *)
 mode :
 lecture écriture crée_le_fichier vide_le_fichier position_du_flux
 r X début
 r+ X X début
 w X X X début
 w+ X X X X début
 a X X fin
 a+ X X X fin
 Lorsqu'un fichier est ouvert en écriture, les données qui sont envoyées
 dans le flux ne sont pas directement écrites sur le disque.
 Elles sont stockées dans un tampon, une zone mémoire de taille finie.
 Lorsque le tampon est plein, les données sont purgées (flush),
 elles sont écrites dans le fichier.
 Ce mécanisme permet de limiter les accès au système de fichiers et donc
 d'accélerer les opérations sur les fichiers.
 À noter une particularité des systèmes Microsoft Windows,
 est de traiter différemment les fichiers textes, des fichiers binaires.
 Sur ces systèmes, le caractère de saut de ligne est en fait composé de
 deux caractères (CR, puis LF, de code ASCII respectif 13 et 10,
 ou '\r' et '\n' écrit sous forme de caractère C).
 Lorsqu'un fichier est ouvert en mode texte (mode par défaut),
 toute séquence CRLF lue depuis le fichier sera convertie en LF,
 et tout caractère LF écrit sera en fait précédé d'un caractère CR supplémentaire.
 Si le fichier est ouvert en mode binaire, aucune conversion n'aura lieu.
 #if 0
 fopen("fichier.txt", "rb"); /* Ouverture sans conversion */
 fopen("fichier.txt", "wb"); /* L'écriture d'un '\n' n'entrainera pas
 l'ajout d'un '\r' */
 #endif
 quand on utilise pas un nom de fichier mais par exemple stdin ou stdout,
 avec Microsoft Windows, il existe une fonction spécifique
 pour utiliser le mode binaire : setmode()
 Ce code qui suit n'est pas portable
 #if 0
 #include <fcntl.h>
 setmode(descripteur, O_BINARY);
 #endif
 setmode prend en paramètre un descripteur
 et pas un pointeur vers un flux (FILE *)
 on utilise fileno() pour la conversion
 #if 0
 int fileno(FILE *);
 #endif
 /* ce qui donne */
 #if 0
 setmode(fileno(stdout), O_BINARY);
 #endif
 printf(" 1.2 Fermeture\n");
 #if 0
 int fclose(FILE * flux);
 #endif
 Si le fichier était ouvert en écriture, le tampon est vidé.
 Cette fonction renvoie 0 si la fermeture s'est bien passée
 (notamment la purge des zones en écriture), ou EOF en cas d'erreur.
 printf(" 1.3 Suppression\n");
 #if 0
 int remove(const char * path);
 #endif
 Supprime le fichier ou le répertoire indiqué par path.
 La fonction renvoie 0 en cas de réussite
 et une valeur non nulle en cas d'erreur, ce qui peut inclure :
 * un répertoire n'est pas vide.
 * vous n'avez pas les permissions pour effacer le fichier
 (média en lecture seule).
 * le fichier est ouvert.
 * etc.
 printf(" 1.4 Renommage (ou déplacement)\n");
 #if 0
 int rename(const char * ancien_nom, const char * nouveau_nom);
 #endif
 Cette fonction permet de renommer l'ancien fichier ou répertoire nommé
 'ancien_nom' par 'nouveau_nom'.
 Elle peut aussi servir à déplacer un fichier, en mettant
 le chemin absolu ou relatif du nouvel emplacement dans 'nouveau_nom'.
 La fonction renvoie 0 si elle réussit et une valeur non nulle en cas d'erreur.
 Les causes d'erreur dépendent de l'implémentation, et peuvent être:
 * vous tentez d'écraser un répertoire par un fichier.
 * vous voulez écraser un répertoire non vide.
 * vous n'avez pas les permissions suffisantes.
 * les deux noms ne sont pas sur la même partition.
 * etc.
 printf(" 1.5 Déplacement dans le flux\n");
 #if 0
 int fseek( FILE * flux, long deplacement, int methode );
 long ftell( FILE * flux );
 #endif
 fseek permet de se déplacer à une position arbitraire dans un flux.
 Cette fonction renvoie 0 en cas de réussite.
 deplacement indique le nombre d'octet à avancer (ou reculer si ce nombre
 est négatif) à partir du point de référence (methode) :
 * SEEK_SET : le point de référence sera le début du fichier.
 * SEEK_CUR : le point de référence sera la position courante dans le fichier.
 * SEEK_END : le point de référence sera la fin du fichier.
 ftell permet de savoir à quelle position se trouve le curseur
 (ce depuis le début).
 En cas d'erreur, ces deux fonctions renvoient -1.
 Avec une machine 32 bits pouvant gérer des fichiers d'une taille de plus de 4 Go
 donc avec taille codé sur 64 bits,
 on n'utilisera pas fseek et ftell qui utilisent le type long codé sur 32 bits.
 on utilisera fseeko() et ftello() qui utilisent le type off_t.
 off_t est codé sur 64bits sur les architectures le supportant et 32bits sinon.
 La disponibilité de ces fonctions est en général limités aux systèmes Unix,
 puisque dépendantes de la spécification Single Unix (SUS).
 autre remarque :
 Il faut bien sûr que le périphérique où se trouve le fichier supporte
 une telle opération (fseek ftell).
 Dans la terminologie Unix, on appelle cela un périphérique en mode bloc.
 À la différence des périphériques en mode caractère
 (stdin, stdout, tube de communication, connexion réseau, etc ...)
 pour lesquels ces appels échoueront.
 printf(" 1.6 Synchronisation\n");
 #if 0
 int fflush ( FILE *flux );
 #endif
 Cette fonction purge la ou les zones mémoires en attente d'écriture
 et renvoie 0 si tout c'est bien passé, ou EOF en cas d'erreur.
 Si NULL est passé comme argument,
 tous les flux ouverts en écriture seront purgés.
 À noter que cette fonction ne permet pas de purger les flux ouverts en lecture.
 Une instruction de ce genre sera au mieux ignorée,
 et au pire provoquera un comportement indéterminé.
 #if 0
 fflush( stdin ); // Ce code contient une erreur volontaire !
 #endif
 Pour effectuer une purge des flux ouverts en lecture,
 il faut passer par des appels systèmes normalisés POSIX,
 mais dont la disponibilité est en général dépendante du système d'exploitation.
 printf(" exemples\n");
 long position;
 FILE * mon_fichier = fopen("rapport.txt", "r+");
 if (mon_fichier != NULL) // mon_fichier != NULL
 printf("[OK] : rapport.txt ouvert avec succès.\n");
 else
 printf("[ERREUR] : impossible d'ouvrir rapport.txt.\n");
 if (mon_fichier != NULL)
 if ((position = ftell(mon_fichier)) != -1)
 printf("[OK] : position dans rapport.txt : %ld.\n", position);
 else
 printf("[ERREUR] : rapport.txt, ftell(mon_fichier) a échoué.\n");
 if (!fseek(mon_fichier, 0, SEEK_END)) // fseek == 0 et donc != -1
 printf("[OK] : rapport.txt, fseek(mon_fichier, 0, SEEK_END) "
 "a réussi.\n");
 else
 printf("[ERREUR] : rapport.txt, fseek(mon_fichier, 0, SEEK_END) "
 "a échoué.\n");
 if ((position = ftell(mon_fichier)) != -1)
 printf("[OK] : position dans rapport.txt : %ld.\n", position);
 else
 printf("[ERREUR] : rapport.txt, ftell(mon_fichier) a échoué.\n");
 if (!fflush(mon_fichier)) // fflush == 0 et donc != EOF
 printf("[OK] : écriture de toutes les données se trouvant "
 "dans les tampons pour rapport.txt.\n");
 else
 printf("[ERREUR] : impossible de purger les tampons pour "
 "rapport.txt.\n");
 if (!fclose(mon_fichier)) // fclose() == 0 et donc != EOF
 printf("[OK] : rapport.txt a été fermé avec succès.\n");
 else
 printf("[ERREUR] : impossible de fermer rapport.txt.\n");
 if (!rename("rapport.txt", "rapport2.txt")) // rename == 0 et donc != -1
 printf("[OK] : rapport.txt a été renommé en rapport2.txt.\n");
 else
 printf("[ERREUR] : impossible de renommer rapport.txt.\n");
 if (!remove("rapport2.txt")) // remove == 0 et donc != -1
 printf("[OK] : rapport2.txt supprimé avec succès.\n");
 else
 printf("[ERREUR] : impossible de supprimer rapport2.txt.\n");
 printf("\n----------------------------------------\n");
 printf(" 2. Sorties formatées\n");
 printf("----------------------------------------\n");
 #if 0
 int printf(const char * restrict format, ...);
 int fprintf(FILE * restrict flux, const char * restrict format, ...);
 int sprintf(char * restrict chaine, const char * restrict format, ...);
 int snprintf(char * restrict chaine, size_t taille,
 const char * restrict format, ...);
 #endif
 printf écrit des données formatées dans la sortie standard
 fprintf écrit des données formatées dans un flux
 sprintf écrit des données formatées dans une chaîne de caractères
 printf renvoit le nombre de caractères qui a été écrit à l'écran
 fprintf renvoit le nombre de caractères qui a été écrit dans le flux
 sprintf renvoit le nombre de caractères qui a été écrit
 dans la zone mémoire, caractère nul non compris !
 sprintf il faut s'assurer qu'il n'y aura pas de débordements
 il vaut mieux utiliser la fonction snprintf()
 snprintf renvoit la taille de la chaine à écrire,
 indépendamment de la limite fixée par le paramètre taille
 ou
 le nombre de caractères écrit
 char test[16];
 printf("il faut %d caractères pour afficher INT_MAX\n",
 sprintf(test, "%d", INT_MAX));
 printf("il faut %d caractères pour afficher INT_MIN\n",
 sprintf(test, "%d", INT_MIN));
 printf("il faut %zu caractères au maximum\n",
 strlen(test) + strlen("entier = \n") + 1);
 char buf[22];
 int entier = 3210;
 sprintf(buf, "entier = %d\n", entier);
 printf("%s", buf);
 snprintf(buf, 22, "entier = %d\n", entier);
 printf("\n 2.1 Type de conversion\n");
 printf expliquée en détails
 L'argument format est une chaîne de caractères qui détermine ce qui sera affiché
 Dans cette chaîne, on peut mettre :
 des séquences de contrôle qui commencent par le caractère % suivi
 d'un caractère parmi
 d ou i pour afficher un entier signé au format décimal (int)
 u pour un entier non signé au format décimal
 x ou X pour afficher un entier au format hexadécimal
 (avec les lettres "abcdef" pour le format 'x'
 et "ABCDEF" avec le format 'X')
 f pour afficher un réel (double) avec une précision fixe
 e pour afficher un réel (double) en notation scientifique
 g effectue un mixe de 'f' et de 'e'
 suivant le format le plus approprié
 c pour afficher en tant que caractère
 s pour afficher une chaine de caractère C standard
 p pour afficher la valeur d'un pointeur,
 généralement sous forme hexadécimale.
 Suivant le compilateur,
 c'est l'équivalent soit à "%08x",
 ou alors à "0x%08x"
 n ce n'est pas un format d'affichage
 et l'argument associé doit être de type int * et être une référence valide.
 La fonction stockera dans l'entier pointé par l'argument
 le nombre de caractères écrit jusqu'à maintenant.
 % pour afficher le caractère '%'
 printf(" 2.2 Contraindre la largeur des champs\n");
 [-]<nombre>[.<nombre>]
 printf("...%10s...\n", "Salut"); /* " Salut" */
 // 10 caractères alignés à droite
 printf("...%-10s...\n", "Salut"); /* "Salut " */
 // 10 caractères alignés à gauche
 printf("...%10s...\n", "Salut tout le monde"); /* "Salut tout le monde" */
 printf("...%-10s...\n", "Salut tout le monde"); /* "Salut tout le monde" */
 // + de 10 caractères dans la chaine, la chaine est affichée
 printf("...%10.10s...\n", "Salut tout le monde"); /* "Salut tout" */
 printf("...%-10.12s...\n", "Salut tout le monde"); /* "Salut tout l" */
 printf("...%-10.8s...\n", "Salut tout le monde"); /* "Salut to " */
 // .10, .12 et .8, la chaine est tronquée car + de 10 caractères
 printf("\n 2.3 Contraindre la largeur des champs numériques\n");
 printf("...%-*d...\n", 10, 1234); /* "1234 " */
 // 10 caractères alignés à gauche
 printf("...%*d...\n", 10, 1234); /* " 1234" */
 // 10 caractères alignés à droite
 printf("...%-*d...\n", 2, 1234); /* "1234" */
 /* pour un entier la limite du spécificateur de format est sans effet,
 pour éviter les erreurs d'interprétation de la valeur */
 printf("...%*d...\n", 2, 1234); /* "1234" */
 // idem
 printf("...%10d...\n", 1234); /* " 1234" */
 // autre notation
 printf("...%010d...\n", 1234); /* "0000001234" */
 // affiche des 0 à la place des espaces
 printf("...%+10d...\n", 1234); /* " +1234" */
 // + affiche le signe de l'entier x (-x, +0, +x)
 printf("...% d...\n", 1234); /* " 1234" */
 printf("...% d...\n", -1234); /* "-1234" */
 /* Si le nombre est positif, un blanc sera mis avant,
 pour l'aligner avec les nombres négatifs */
 printf("%08x\n", 543); /* "0000021f" */
 // affiche des 0 à la place des espaces 
 // 8 caractères au moins
 // x format hexadécimal "abcdef"
 printf("...%10.8d...\n", 15000); /* " 00015000" */
 // 10 caractères sinon des espaces, .8 au moins 8 caractères sinon des 0
 printf("\n 2.4 Contraindre la largeur des champs réels\n");
 printf("%f\n", 3.1415926535); /* "3.141593" */
 printf("%010f\n", 3.1415926535); /* "003.141593" */
 printf("%.8f\n", 3.1415926535); /* "3.14159265" */
 // .8 sert en fait à indiquer la précision voulue après la virgule
 printf("%010.3f\n", 3.1415926535); /* "000003.142" */
 printf("%.0f\n", 3.1415926535); /* "3" */
 printf("\n 2.5 Spécifier la taille de l'objet\n");
 Par défaut, les entiers sont présuposés être de type int,
 les réels de type double et les chaines de caractères de type char *.
 Il arrive toutefois que les types soient plus grands
 et non plus petits à cause de la promotion des types.
 le spécificateur de format permet d'indiquer
 la taille de l'objet en ajoutant les attributs suivants
 avant le caractère de conversion :
Format Attributs de taille
 aucun hh h l (elle) ll (elle-elle)
n int * signed char * short * long * long long *
d, i,
o, x,
X int signed char short long long long
u unsigned int unsigned char unsigned short unsigned long unsigned long long
s char * wchar_t *
c int wint_t
p void *
Format Attributs de taille
 aucun L
a, A,
e, E,
f, F,
g, G double long double 
Format Autres attributs(rarement utilisés)
 j z t
n intmax_t * size_t * ptrdiff_t *
d, i,
o, x,
X intmax_t size_t ptrdiff_t
u uintmax_t size_t ptrdiff_t
 hh et ll sont des nouveautés de C99
 On notera qu'avec l'attribut hh et les formats n, d, i, o, x ou X,
 le type est signed char et non char
 le type char peut être signé ou non, suivant l'implémentation.
 Ici, on est sûr de manipuler le type caractère signé.
 signed char nb; 
 printf("%d%hhn\n", 12345, &nb); /* Affichage de "12345" et nb vaudra 5 */
 printf("nb = %hhd\n", nb);
 printf("%ls\n", L"Hello world!"); /* "Hello world!" */
 printf("\n 2.6 Arguments positionnels\n");
 char * s = "Bla bla";
 printf("La chaine '%s' a %zu caractères\n", s, strlen(s) );
 Une traduction en allemand du message précédant, pourrait donner :
 "%zu Zeichen lang ist die Zeichenkette '%s'"
 On remarque que les spécificateurs de format sont inversés
 par rapport à la chaine originale.
 C'est là que nous avons besoin des arguments positionnels.
 On ajoute 1$, 2$, etc. Exemple : %zu devient %1$zu.
 printf("%2$zu Zeichen lang ist die Zeichenkette '%1$s'\n", s, strlen(s));
 /* À noter que si un des arguments utilise la référence positionnelle,
 tous les autres arguments devront faire de même,
 sous peine d'avoir un comportement imprévisible.
 Les arguments positionnels sont très utiles lorsque qu'on utilise gettext
 gettext est un ensemble de fonctions
 permettant de manipuler des catalogues de langues.
 La principale fonction de cette bibliothèque est justement gettext(),
 qui en fonction d'une chaine de caractère
 retourne la chaine traduite selon la locale en cours.
 printf( gettext("La chaine '%1$s' a %2$zu caractères\n"), s, strlen(s) );
 on utilisera les arguments positionnels dans toutes les langues
 et empêcher toute erreur.
 gettext pourra être étudié dans un autre cours !
 printf("\n 2.7 Écriture par bloc ou par ligne\n");
 flux et tampons
 Les trois types de tampons disponibles sont les suivants :
 pas de tampons,
 tampons de blocs,
 et tampons de lignes.
 Quand un flux de sortie n’a pas de tampon,
 les données apparaissent dans le fichier destination,
 ou sur le terminal, dès qu’elles sont écrites.
 Avec les tampons par blocs,
 une certaine quantité de données est conservée
 avant d’être écrite en tant que bloc.
 Avec les tampons de lignes,
 les caractères sont conservés jusqu’à ce qu’un saut de ligne soit transmis,
 ou que
 l’on réclame une lecture sur un flux attaché au terminal
 (typiquement stdin).
 La fonction fflush() peut être utilisée pour forcer l’écriture
 à n’importe quel moment.
 Normalement, tous les fichiers utilisent des tampons de blocs.
 Quand une première opération d’entrée-sortie se déroule sur un fichier,
 malloc() est appelée, et un tampon est créé.
 Si le flux se rapporte à un terminal (comme stdout habituellement)
 il s’agit d’un tampon de ligne.
 Le flux standard de sortie d’erreur stderr n’a jamais de tampon par défaut.
 C'est ce qui fait qu'un programme affichant
 des messages avec retour à la ligne à intervalle régulier (genre une seconde),
 affichent ces lignes une à une sur un terminal,
 et par bloc de plusieurs lignes
 lorsqu'on redirige sa sortie vers un programme de mise en page (comme more),
 avec une latence qui peut s'avérer génante.
 C'est ce qui fait aussi qu'une instruction comme printf("Salut tout le monde");
 n'affichera en général rien, car il n'y a pas de retour à la ligne.
 En fait ce comportement peut être explicitement réglé, avec cette fonction :
 #if 0
 int setvbuf(FILE * restrict flux, char * restrict mem, int mode, size_t taille);
 #endif
 Cette fonction doit être appelée juste après l'ouverture du flux
 et avant la première écriture. Les arguments ont la signification suivante :
 * flux : Le flux stdio pour lequel vous voulez changer la méthode d'écriture.
 * mem : Vous pouvez transmettre une zone mémoire
 qui sera utilisée pour stocker les données
 avant d'être écrites dans le fichier.
 Vous pouvez aussi passer la valeur NULL,
 dans ce cas, la fonction malloc() est appelée
 lors de la première écriture.
 * mode : indique comment se feront les écritures :
 _IONBF : écriture immédiate, pas de stockage temporaire.
 _IOLBF : écriture par ligne.
 _IOFBF : par bloc.
 * taille : La taille de la zone mémoire transmise ou à allouer.
 La fonction setvbuf() renvoie 0 si elle réussit,
 et une valeur différente de zéro dans le cas contraire
 (en général le paramètre mode est invalide).
 Cette fonctionnalité peut être intéressante pour les programmes
 générant des messages sporadiques.
 Il peut effectivement s'écouler un temps arbitrairement long avant
 que le bloc mémoire soit plein,
 si cette commande est redirigée vers un autre programme,
 ce qui peut s'avérer assez dramatique pour
 des messages signalant une avarie grave.
 Dans ce cas, il est préférable de forcer l'écriture par ligne (ou immédiate),
 plutôt que de faire suivre systématiquement chaque écriture de ligne
 par un appel à fflush(), avec tous les risques d'oubli que cela comporte.
 Remarque :
 Il faut toujours s’assurer que le contenu de buf existe encore au
 moment de la fermeture du flux stream (qui se produit automatiquement
 à la fin du programme).
 Par exemple, ceci n’est pas valable :
 #if 0
 #include <stdio.h>
 int main(void)
 char buf[BUFSIZ];
 setbuf(stdin, buf);
 printf("Hello, world!\n");
 return 0;
 #endif
 il faut ajouter fclose(stdin); avant return 0;
 printf("\n 2.8 Quelques remarques pour finir\n");
 Il faut aussi faire attention au fait que certaines implémentations de
 printf() tiennent compte de la localisation pour les conversions des nombres
 réels (virgule ou point comme séparateur décimal, espace ou point comme
 séparateurs des milliers, etc.). Ceci peut être gènant lorsqu'on veut
 retraiter la sortie de la commande. Pour désactiver la localisation,
 on peut utiliser la fonction setlocale():
 #if 0
 #include <locale.h>
 setlocale( LC_ALL, "C" );
 printf( ... );
 setlocale( LC_ALL, "" );
 #endif
 printf("\n----------------------------------------\n");
 printf(" 3. Entrées formatées\n");
 printf("----------------------------------------\n");
 #if 0
 int scanf(const char * restrict format, ...);
 int fscanf(FILE * restrict flux, const char * restrict format, ...);
 int sscanf(const char * restrict chaine, const char * restrict format, ...);
 #endif
 scanf lit des données formatées depuis l'entrée standard
 fscanf lit des données formatées depuis un flux
 sscanf lit des données formatées depuis une chaîne de caractères
 L'argument format ressemble aux règles d'écriture de la famille de fonctions type
 printf, cependant les arguments qui suivent ne sont plus des variables d'entrée
 mais des variables de sortie (ie : l'appel à scanf va modifier leur valeur,
 il faut donc passer une référence).
 Ces fonctions retournent le nombre d'arguments correctement lus depuis le
 format, qui peut être inférieur ou égal au nombre de spécificateurs de format,
 et même nul.
 printf("\n 3.1 Format de conversion\n");
 Les fonctions scanf() analysent le spécificateur de format et les données
 d'entrée, en les comparant caractère à caractère et s'arrêtant lorsqu'il y en a
 un qui ne correspond pas. À noter que les blancs (espaces, tabulations et
 retour à la ligne) dans le spécificateur de format ont une signification
 spéciale : à un blanc de la chaine format peut correspondre un nombre
 quelconque de blanc dans les données d'entrée, y compris aucun. D'autres part,
 il est possible d'insérer des séquences spéciales, commençant par le caractère
 '%' et à l'image de printf(), pour indiquer qu'on aimerait récupérer la valeur
 sous la forme décrite par le caractère suivant le '%' :
 s : extrait la chaîne de caractères, en ignorant les blancs initiaux et ce
 jusqu'au prochain blanc. L'argument correspondant doit être de type char *
 et pointer vers un bloc mémoire suffisamment grand pour contenir la chaîne
 et son caractère terminal.
 d : extrait un nombre décimal signé de type int, ignorant les espaces se
 trouvant éventuellement avant le nombre.
 i : extrait un nombre (de type int) hexadécimal, si la chaine commence par
 "0x", octal si la chaine commence par "0" et décimal sinon. Les éventuels
 espaces initiaux seront ignorés.
 f : extrait un nombre réel, en sautant les blancs, de type float.
 u : lit un nombre décimal non-signé, sans les blancs, de type int.
 c : lit un caractère (de type char), y compris un blanc.
 [] : lit une chaîne de caractères qui doit faire partie de l'ensemble entre
 crochets. Cet ensemble est une énumération de caractère. On peut utiliser
 le tiret ('-') pour grouper les déclarations (comme "0-9" ou "a-z"). Pour
 utiliser le caractère spécial ']' dans l'ensemble, il doit être placé en
 première position et, pour utiliser le tiret comme un caractère normal, il
 doit être mis à la fin. Pour indiquer que l'on veut tous les caractères
 sauf ceux de l'ensemble, on peut utiliser le caractère '^' en première
 position. À noter que scanf terminera toujours la chaîne par 0 et que,
 contrairement au spécificateur %s, les blancs ne seront pas ignorés.
 n : Comme pour la fonction printf(), ce spécificateur de format permet de
 stocker dans l'entier correspondand de type int, le nombre de caractères
 lus jusqu'à présent.
 printf("\n 3.2 Contraindre la largeur\n");
 Comme pour la fonction printf(), il est possible de contraindre le nombre de
 caractères à lire, en ajoutant ce nombre juste avant le caractère de conversion.
 Dans le cas des chaînes, c'est même une obligation, dans la mesure où scanf() ne
 pourra pas ajuster l'espace à la volée.
 #if 0
 /* Lit une chaîne de caractères entre guillemets d'au plus 127 caractères */
 char tmp[128];
 if (fscanf(fichier, "Config = \"%127[^\"]\"", tmp ) == 1)
 printf("L'argument associé au mot clé 'Config' est '%s'\n", tmp);
 #endif
 Cet exemple est plus subtil qu'il ne paraît. Il montre comment analyser une
 structure relativement classique de ce qui pourrait être un fichier de
 configuration de type "MotClé=Valeur". Ce format spécifie donc qu'on s'attend à
 trouver le mot clé "Config", en ignorant éventuellement les blancs initiaux, puis
 le caratère '=', entouré d'un nombre quelconque de blancs, eventuellement aucun.
 À la suite de cela, on doit avoir un guillemet ('"'), puis au plus 127 caractères
 autres qu'un guillemet, qui seront stockés dans la zone mémoire tmp (qui
 sera terminée par 0, d'où l'allocation d'un caractère supplémentaire). Le
 guillemet final est là pour s'assurer, d'une part, que la longueur de la chaîne
 est bien inférieure à 127 caractère et, d'autre part, que le guillemet n'a pas
 été oublié dans le fichier.
 En cas d'erreur, on peut par exemple ignorer tous les caractères jusqu'à la ligne
 suivante.
 printf("\n 3.3 Ajuster le type des arguments\n");
 /* On peut aussi ajuster le type des arguments en fonction des attributs de taille :
Format Attributs de taille
 aucun hh h l (elle) ll (elle-elle)
d, int * char * short * long * long long *
i,
n
u unsigned int * unsigned char * unsigned short * unsigned long * unsigned long long *
s, char *
c,
f float * double * long double *
 Ainsi pour lire la valeur d'un entier sur l'entrée standard, on utilisera un
 code tel que celui ci :
 #if 0
 #include <stdio.h>
 int main(void)
 int i;
 printf("Entrez un entier : ");
 scanf("%d", &i);
 printf("la variable i vaut maintenant %d\n", i);
 return 0;
 #endif
 Les appels à printf ne sont pas indispensables à l'exécution du scanf, mais
 permettent à l'utilisateur de comprendre ce qu'attend le programme (et ce qu'il
 fait aussi).
 printf("\n 3.4 Conversions muettes\n");
 La fonction scanf reconnait encore un autre attribut qui permet d'effectuer la
 conversion, mais sans retourner la valeur dans une variable. Il s'agit du
 caractère étoile '*', qui remplace l'éventuel attribut de taille.
 Exemple:
 #if 0
 int i, j;
 sscanf("1 2.434e-308 2", "%d %*f %d", &i, &j); /* i vaut 1 et j vaut 2 */
 #endif
 printf("\n 3.5 Quelques remarques pour finir\n");
 Deux opérations en apparence simples, mais impossible à
 réaliser avec les fonctions de type scanf :
 1. Purger les données en attente de lecture, pour éviter les réponses
 « automatiques ».
 2. Saisir des caractères sans demande de confirmation.
 Ces fonctionnalités sont hélas le domaine de la gestion des terminaux POSIX et
 spécifiées dans la norme du même nom.
 On l'aura compris, cette famille de fonction est plus à l'aise pour traiter des
 fichiers, ou tout objet nécessitant le moins d'interaction possible avec
 l'utilisateur.
 À noter, que contrairement à la famille de fonction printf(), scanf() n'est pas
 sensible à la localisation pour la saisie de nombres réels.
 printf("----------------------------------------\n");
 printf(" 4. Entrées non formatées\n");
 printf("----------------------------------------\n");
 Pour saisir des chaines de caractères indépendemment de leur contenu, on peut
 utiliser les fonctions suivantes :
 #if 0
 char * fgets(char * restrict chaine, int taille_max, FILE * restrict flux);
 int fgetc( FILE * restrict flux);
 int ungetc( int octet, FILE * flux );
 #endif
 fgets()
 Si la ligne peut être contenue dans chaine, chaine contiendra le caractère de
 saut de ligne ('\n'), en plus du caractère nul.
 Sinon la ligne est tronquée.
 Appeler une fois de plus fgets() pour obtenir la suite de la ligne.
 Si la fonction a pu lire au moins un caractère, elle retournera chaine, ou NULL
 s'il n'y a plus rien à lire.
 La fonction fgetc() permet de ne saisir qu'un caractère depuis le flux spécifié.
 À noter que la fonction renvoie bien un entier de type int et non de type char,
 car en cas d'erreur (y compris dans le cas où on se trouve en fin de fichier),
 cette fonction renvoie EOF (défini à un entier -1 en général).
 printf("----------------------------------------\n");
 printf(" 5. Entrées/sorties brutes\n");
 printf("----------------------------------------\n");
 Les fonctions suivantes permettent d'écrire ou de lire des quantités arbitraires
 de données depuis un flux. Il faut faire attention à la portabilité de ces
 opérations, notamment lorsque le flux est un fichier. Dans la mesure où lire et
 écrire des structures binaires depuis un fichier nécessite de gérer l'alignement,
 le bourrage, l'ordre des octets pour les entiers (big endian, little endian) et
 le format pour les réels, il est souvent infiniment plus simple de passer par un
 format texte.
 printf("\n 5.1 Sortie\n");
 #if 0
 size_t fwrite(const void * buffer, size_t taille, size_t nombre, FILE * flux);
 #endif
 printf("\n 5.1 Entrée\n");
 #if 0
 size_t fread(void * buffer, size_t taille, size_t nombre, FILE * flux);
 #endif
 Lit nombre éléments de taille taille, à partir du flux et stocke le
 résultat dans le buffer. Renvoie le nombre d'éléments correctement lus.
 printf("----------------------------------------\n");
 printf(" 6. Gestion des erreurs\n");
 printf("----------------------------------------\n");
 #if 0
 int ferror( FILE * );
 Si ferror() différent de 0, on sait que le flux a rencontré une erreur.
 Sans plus de précision.
 #endif
 pour connaitre l'erreur, la variable globale errno qui sera expliquée dans le
 chapitre sur la gestion d'erreur est necessaire.
 return 0;