Programmation C source/entrées sorties

Programmation C source
Programmation C++
Programmation C++
Sommaire
Modifier ce modèle
#include <stdio.h>
#include <string.h>
#include <limits.h>

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