Nous avons distingué au début de ce chapitre les programmes graphiques,
qui traitent les événements qu'ils reçoivent du système sous la forme de messages, des autres programmes,
qui reçoivent les données à traiter et écrivent leurs résultats sur les flux d'entrée / sortie standards.
Les notions de flux d'entrée / sortie standards n'ont pas été définies plus en détail à ce moment, et il
est temps à présent de pallier cette lacune.
1.8.1. Généralités sur les flux d'entrée / sortie en C
Un flux est une notion informatique qui permet
de représenter un flot de données séquentielles en provenance d'une source de données ou à destination
d'une autre partie du système. Les flux sont utilisés pour uniformiser la manière dont les programmes
travaillent avec les données, et donc pour simplifier leur programmation. Les fichiers constituent
un bon exemple de flux, mais ce n'est pas le seul type de flux existant : on peut traiter un flux
de données provenant d'un réseau, d'un tampon mémoire ou de toute autre source de données ou partie
du système permettant de traiter les données séquentiellement.
Sur quasiment tous les systèmes d'exploitation, les programmes disposent
dès leur lancement de trois flux d'entrée / sortie standards. Généralement, le flux d'entrée standard
est associé au flux de données provenant d'un terminal, et le flux de sortie standard à la console
de ce terminal. Ainsi, les données que l'utilisateur saisit au clavier peuvent être lues par les programmes
sur leur flux d'entrée standard, et ils peuvent afficher leurs résultats à l'écran en écrivant simplement
sur leur flux de sortie standard. Le troisième flux standard est le flux d'erreur standard qui, par
défaut, est également associé à l'écran, et sur lequel le programme peut écrire tous les messages d'erreur
qu'il désire.
Note : La plupart des systèmes permettent de rediriger les flux standards
des programmes afin de les faire travailler sur des données provenant d'une autre source de données
que le clavier, ou, par exemple, de leur faire enregistrer leurs résultats dans un fichier. Il est même
courant de réaliser des « pipelines » de programmes, où les résultats de l'un sont envoyés
dans le flux d'entrée standard de l'autre, et ainsi de suite. Ces suites de programmes sont également
appelés des tubes en français.
La manière de réaliser les redirections des flux standards dépend
des systèmes d'exploitation et de leurs interfaces utilisateurs. De plus, les programmes doivent
être capables de travailler avec leurs flux d'entrée / sortie standards de manière générique, que
ceux-ci soient redirigés ou non. Les techniques de redirection ne seront donc pas décrites plus
en détail ici.
Vous remarquerez l'intérêt d'avoir deux flux distincts pour
les résultats des programmes et leurs messages d'erreur. Si, lors d'une utilisation normale,
ces deux flux se mélangent à l'écran, ce n'est pas le cas lorsque l'on redirige le flux de sortie
standard. Seul le flux d'erreur standard est affiché à l'écran dans ce cas, et les messages d'erreur
ne se mélangent donc pas aux résultats du programme.
On pourrait penser que les programmes graphiques ne disposent
pas de flux d'entrée / sortie standards. Pourtant, c'est généralement le cas. Les événements traités
par les programmes graphiques dans leur boucle de messages ne proviennent généralement pas du flux
d'entrée standard, mais d'une autre source de données spécifique à chaque système. En conséquence,
les programmes graphiques peuvent toujours utiliser les flux d'entrée / sortie standard si cela
s'avère nécessaire.
Afin de permettre aux programmes d'écrire sur leurs flux d'entrée / sortie
standards, la bibliothèque C définit plusieurs fonctions extrêmement utiles. Les deux principales fonctions
sont sans doute les fonctions printf et scanf.
La fonction printf (« print formatted » en anglais) permet d'afficher
des données à l'écran, et scanf (« scan formatted ») permet de les lire
à partir du clavier.
En réalité, ces fonctions ne font rien d'autre que d'appeler
deux autres fonctions permettant d'écrire et de lire des données sur un fichier : les fonctions
fprintf et fscanf. Ces fonctions s'utilisent exactement
de la même manière que les fonctions printf et scanf, à ceci
près qu'elles prennent en premier paramètre une structure décrivant le fichier sur lequel elles
travaillent. Pour les flux d'entrée / sortie standards, la bibliothèque C définit les pseudo-fichiers
stdin, stdout et stderr, qui correspondent
respectivement aux flux d'entrée, au flux de sortie et au flux d'erreur standards. Ainsi, tout appel
à scanf se traduit par un appel à fscanf sur le pseudo-fichier
stdin, et tout appel à printf par un appel
à fprintf sur le pseudo-fichier stdout.
Note : Il n'existe pas de fonction permettant d'écrire directement sur
le flux d'erreur standard. Par conséquent, pour effectuer de telles écritures, il faut impérativement
passer par la fonction fprintf, en lui fournissant en paramètre le pseudo-fichier
stderr.
La description des fonctions de la bibliothèque C standard dépasse
de loin le cadre de ce cours. Aussi les fonctions de lecture et d'écriture sur les fichiers
ne seront-elles pas décrites plus en détail ici. Seules les fonctions printf
et scanf seront présentées, car elles sont réellement indispensable pour l'écriture
d'un programme C. Consultez la bibliographie si vous désirez obtenir plus de détails sur la bibliothèque C
et sur toutes les fonctions qu'elle contient.
Le C++ dispose également de mécanismes de gestion des flux d'entrée / sortie qui lui
sont propres. Ces mécanismes permettent de contrôler plus finement les types des données écrites et lues
de et à partir des flux d'entrée / sortie standards. De plus, ils permettent de réaliser les opérations
d'écriture et de lecture des données formatées de manière beaucoup plus simple. Cependant, ces mécanismes
requièrent des notions objets avancées et ne seront décrits que dans les chapitres dédiés au C++. Comme
il est également possible d'utiliser les fonctions printf et scanf
en C++ d'une part, et que, d'autre part, ces fonctions sont essentielles en C, la suite de cette section
s'attachera à leur description. Un chapitre complet est dédié aux mécanismes de gestion des flux du C++
dans la deuxième partie de ce document.
Les fonctions printf et scanf
sont toutes deux des fonctions à nombre de paramètres variables. Elles peuvent donc être utilisées pour
effectuer des écritures et des lectures multiples en un seul appel. Afin de leur permettre de déterminer
la nature des données passées dans les arguments variables, elles attendent toutes les deux en premier
paramètre une chaîne de caractères descriptive des arguments suivants. Cette chaîne est appelée
chaîne de format, et elle permet de spécifier avec précision le type, la position
et les options de format (précision, etc.) des données à traiter. Les deux sections suivantes décrivent
la manière d'utiliser ces chaînes de format pour chacune des deux fonctions printf
et scanf.
1.8.2. La fonction printf
La fonction printf s'emploie comme suit :
printf(chaîne de format [, valeur [, valeur [...]]])
On peut passer autant de valeurs que l'on veut, pour peu qu'elles
soient toutes référencées dans la chaîne de format. Elle renvoie le nombre de caractères affichés.
La chaîne de format peut contenir du texte, mais surtout elle doit
contenir autant de formateurs que de variables à afficher. Si ce n'est pas le cas,
le programme plantera. Les formateurs sont placés dans le texte là où les valeurs des variables doivent
être affichées.
La syntaxe des formateurs est la suivante :
%[[indicateur]...][largeur][.précision][taille] type
Un formateur commence donc toujours par le caractère %. Pour afficher
ce caractère sans faire un formateur, il faut le dédoubler (%%).
Le type de la variable à afficher est obligatoire
lui aussi. Les types utilisables sont les suivants :
Tableau 1-1. Types pour les chaînes de format de printf
Type de donnée à afficher
Caractère de formatage
Numériques
Entier décimal signé
d
Entier décimal non signé
u ou i
Entier octal non signé
o
Entier hexadécimal non signé
x (avec les caractères 'a' à 'f')
ou X (avec les caractères 'A' à 'F')
Flottants de type double
f, e, g, E ou G
Caractères
Caractère isolé
c
Chaîne de caractères
s
Pointeurs
Pointeur
p
Note : Voir le Chapitre 4 pour plus de détails
sur les pointeurs. Le format des pointeurs dépend de la machine.
Les valeurs flottantes infinies sont remplacées par les mentions
+INF et -INF. Un non-nombre IEEE (Not-A-Number) donne
+NAN ou -NAN. Notez que le standard C ne permet de formater que
des valeurs de type double. Les valeurs flottantes de type float devront
donc être convertie en double avant affichage.
Les autres paramètres sont facultatifs.
Les valeurs disponibles pour le paramètre de taille
sont les caractères suivants :
Tableau 1-2. Options pour les types des chaînes de format
Option
Type utilisable
Taille du type
F
Pointeur
Pointeur FAR (DOS uniquement)
N
Pointeur
Pointeur NEAR (DOS uniquement)
h
Entier
short int
l
Entier, caractère ou chaîne de caractères
long int ou wchar_t
L
Flottant
long double
Exemple 1-22. Utilisation de printf et fprintf
#include <stdio.h> /* Ne pas chercher à comprendre cette ligne
pour l'instant. Elle est nécessaire pour utiliser
les fonctions printf et scanf. */
int main(void)
{
int i = 2;
printf("Voici la valeur de i : %d.\n", i);
/* Exemple d'écriture sur la sortie d'erreur standard : */
fprintf(stderr, "Pas d'erreur jusqu'ici...\n");
return 0;
}
Vous remarquerez dans cet exemple la présence d'une ligne
#include <stdio.h>. Cette ligne est nécessaire pour permettre l'utilisation
des fonctions printf et fprintf. Nous décrirons sa signification
précise ultérieurement dans le chapitre sur le préprocesseur.
Sans entrer dans les détails, disons simplement que cette ligne permet d'inclure un fichier contenant
les déclarations de toutes les fonctions d'entrée / sortie de base.
Les paramètres indicateurs,
largeur et précision sont moins utilisés. Il peut y avoir
plusieurs paramètres indicateurs, ils permettent de modifier l'apparence de la sortie. Les principales
options sont :
'-' : justification à gauche de la sortie,
avec remplissage à droite par des 0 ou des espaces ;
'+' : affichage du signe pour les nombres positifs ;
espace : les nombres positifs commencent tous
par un espace.
Le paramètre largeur permet de spécifier la largeur
minimale du champ de sortie, si la sortie est trop petite, on complète avec des 0 ou des espaces.
Notez qu'il s'agit bien d'une largeur minimale ici et non d'une largeur maximale. Le résultat
du formatage de la donnée à écrire peut donc dépasser la valeur indiquée pour la largeur du champ.
Enfin, le paramètre précision spécifie la précision maximale
de la sortie (nombre de chiffres à afficher).
1.8.3. La fonction scanf
La fonction scanf permet de faire une ou plusieurs
entrées. Comme la fonction printf, elle attend une chaîne de format en premier
paramètre. Il faut ensuite passer les variables devant contenir les entrées dans les paramètres
qui suivent. Sa syntaxe est la suivante :
scanf(chaîne de format, &variable [, &variable [...]]);
Elle renvoie le nombre de variables lues.
Ne cherchez pas à comprendre pour l'instant la signification du symbole
& se trouvant devant chacune des variables. Sachez seulement que s'il est oublié,
le programme plantera.
La chaîne de format peut contenir des chaînes de caractères. Toutefois,
si elle contient autre chose que des formateurs, le texte saisi par l'utilisateur devra correspondre
impérativement avec les chaînes de caractères indiquées dans la chaîne de format.
scanf cherchera à reconnaître ces chaînes, et arrêtera l'analyse à la première erreur.
La syntaxe des formateurs pour scanf diffère un peu
de celle de ceux de printf :
%[*][largeur][taille]type
Seul le paramètre largeur change par rapport à
printf. Il permet de spécifier le nombre maximal de caractères à prendre en compte
lors de l'analyse du paramètre. Le paramètre '*' est facultatif, il indique seulement de passer la donnée
entrée et de ne pas la stocker dans la variable destination. Cette variable doit quand même être présente
dans la liste des paramètres de scanf.
Note : Tout comme pour les fonctions printf et
fprintf, il est nécessaire d'ajouter la ligne
#include <stdio.h> en début de fichier pour pouvoir utiliser la fonction
scanf. La signification de cette ligne sera donnée dans le chapitre traitant
du préprocesseur.
En pratique, la fonction scanf n'analyse les caractères
provenant du flux d'entrée que lorsqu'une ligne complète a été saisie. Toutefois, elle ne supprime
pas du tampon de flux d'entrée le caractère de saut de ligne, si bien qu'il s'y trouvera toujours
lors de l'entrée suivante. Cela n'est pas gênant si l'on n'utilise que la fonction
scanf pour réaliser les entrées de données dans le programme, car cette fonction
ignore tout simplement ces caractères de saut de ligne. En revanche, si l'on utilise une autre
fonction après un appel à scanf, il faut s'attendre à trouver ce caractère
de saut de ligne dans le flux d'entrée.
La fonction scanf n'est pas très adaptée à la lecture
des chaînes de caractères, car il n'est pas facile de contrôler la taille maximale que l'utilisateur
peut saisir. C'est pour cette raison que l'on a généralement recours à la fonction
fgets, qui permet de lire une ligne sur le flux d'entrée standard et de stocker
le résultat dans une chaîne de caractères fournie en premier paramètre et dont la longueur maximale
est spécifiée en deuxième paramètre. Le troisième paramètre de la fonction fgets
est le flux à partir duquel la lecture de la ligne doit être réalisée, c'est à dire généralement
stdin. L'analyse de la chaîne de caractères ainsi lue peut alors être faite
avec une fonction similaire à la fonction scanf, mais qui lit les caractères
à analyser dans une chaîne de caractères au lieu de les lire directement depuis le flux d'entrée
standard : la fonction sscanf. Cette fonction s'utilise exactement comme
la fonction scanf, à ceci près qu'il faut lui fournir en premier paramètre
la chaîne de caractères dans laquelle se trouvent les données à interpréter. La description de
ces deux fonctions dépasse le cadre de ce document et ne sera donc pas faite ici. Veuillez
vous référer à la documentation de votre environnement de développement ou à la bibliographie
pour plus de détails à leur sujet.