1.8. Les fonctions d'entrée / sortie de base

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 à afficherCaractère de formatage
NumériquesEntier 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 doublef, e, g, E ou G
CaractèresCaractère isoléc
 Chaîne de caractèress
PointeursPointeurp

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

OptionType utilisableTaille du type
FPointeurPointeur FAR (DOS uniquement)
NPointeurPointeur NEAR (DOS uniquement)
hEntiershort int
lEntier, caractère ou chaîne de caractèreslong int ou wchar_t
LFlottantlong 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 :

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.