Programmation C source/entrées sorties
Apparence
#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;
}