Manipulations basiques des entrées/sorties

Durant l’exécution d’un programme, le processeur a besoin de communiquer avec le reste du matériel. Ces échanges d’informations sont les entrées et les sorties (ou input et output pour les anglophones), souvent abrégées E/S (ou I/O par les anglophones).

Les entrées permettent de recevoir une donnée en provenance de certains périphériques. Les données fournies par ces entrées peuvent être une information envoyée par le disque dur, la carte réseau, le clavier, la souris, un CD, un écran tactile, bref par n’importe quel périphérique. Par exemple, notre clavier va transmettre des informations sur les touches enfoncées au processeur : notre clavier est donc une entrée.

À l’inverse, les sorties vont transmettre des données vers ces périphériques. On pourrait citer l’exemple de l’écran : notre ordinateur lui envoie des informations pour qu’elles soient affichées.

Dans ce chapitre, nous allons apprendre différentes fonctions fournies par le langage C qui vont nous permettre d’envoyer des informations vers nos sorties et d’en recevoir depuis nos entrées. Vous saurez ainsi comment demander à un utilisateur de fournir une information au clavier et comment afficher quelque chose sur la console.

Les sorties

Intéressons-nous dans un premier temps aux sorties. Afin d’afficher du texte, nous avons besoin d’une fonction.

Une fonction est un morceau de code qui a un but, une fonction particulière et qui peut être appelée à l’aide d’une référence, le plus souvent le nom de cette fonction (comme pour une variable, finalement). En l’occurrence, nous allons utiliser une fonction qui a pour objectif d’afficher du texte dans la console : la fonction printf().

Première approche

Un exemple valant mieux qu’un long discours, voici un premier exemple.

#include <stdio.h>


int main(void)
{
    printf("Bonjour tout le monde !\n");
    return 0;
}
Résultat
Bonjour tout le monde !

Deux remarques au sujet de ce code.

#include <stdio.h>

Il s’agit d’une directive du préprocesseur, facilement reconnaissable car elles commencent toutes par le symbole #. Celle-ci sert à inclure un fichier (« stdio.h ») qui contient les références de différentes fonctions d’entrée et sortie (« stdio » est une abréviation pour « Standard input output », soit « Entrée-sortie standard »).

Un fichier se terminant par l’extension « .h » est appelé un fichier d’en-tête (header en anglais) et fait partie avec d’autres d’un ensemble plus large appelée la bibliothèque standard (« standard » car elle est prévue par la norme1).

printf("Bonjour tout le monde !\n");

Ici, nous appelons la fonction printf() (un appel de fonction est toujours suivi d’un groupe de parenthèses) avec comme argument (ce qu’il y a entre les parenthèses de l’appel) un texte (il s’agit plus précisément d’une chaîne de caractères, qui est toujours comprise entre deux guillemets double). Le \n est un caractère spécial qui représente un retour à la ligne, cela est plus commode pour l’affichage.

Le reste devrait vous être familier.

Les formats

Bien, nous savons maintenant afficher une phrase, mais ce serait quand même mieux de pouvoir voir les valeurs de nos variables. Comment faire ? bien, pour y parvenir, la fonction printf() met à notre disposition des formats. Ceux-ci sont en fait des sortes de repères au sein d’un texte qui indiquent à printf() que la valeur d’une variable est attendue à cet endroit. Voici un exemple pour une variable de type int.

#include <stdio.h>


int main(void)
{
    int variable = 20;

    printf("%d\n", variable);
    return 0;
}
Résultat
20

Nous pouvons voir que le texte de l’exemple précédent a été remplacé par %d, seul le \n a été conservé. Un format commence toujours par le symbole % et est suivi par une ou plusieurs lettres qui indiquent le type de données que nous souhaitons afficher. Cette suite de lettres est appelée un indicateur de conversion. En voici une liste non exhaustive.

Type Indicateur(s) de conversion
_Bool d (ou i)
char c
signed char d (ou i)
short d (ou i)
int d (ou i)
long ld (ou li)
long long lld (ou lli)
unsigned char u, x (ou X) ou o
unsigned short u, x (ou X) ou o
unsigned int u, x (ou X) ou o
unsigned long lu, lx (ou lX) ou lo
unsigned long long llu, llx (ou llX) ou llo
float f, e (ou E) ou g (ou G)
double f, e (ou E) ou g (ou G)
long double Lf, Le (ou LE) ou Lg (ou LG)

Notez que les indicateurs de conversions sont identiques pour les types _Bool, char (s’il stocke un entier), short et int (pareil pour leurs équivalents non signés) ainsi que pour les types float et double.

Les indicateurs x, X et o permettent d’afficher un nombre en représentation hexadécimale ou octale (l’indicateur x affiche les lettres en minuscules alors que l’indicateur X les affiche en majuscules).

Les indicateurs f, e et g permettent quant à eux d’afficher un nombre flottant. L’indicateur f affiche un nombre en notation simple avec, par défaut, six décimales ; l’indicateur e affiche un nombre flottant en notation scientifique (l’indicateur e utilise la lettre « e » avant l’exposant alors que l’indicateur « E » emploie la lettre « E ») et l’indicateur g choisit quant à lui entre les deux notations précédentes suivant le nombre fourni et supprime la partie fractionnaire si elle est nulle de sorte que l’écriture soit concise (la différence entre les indicateurs g et G est identique à celle entre les indicateurs e et E).

Allez, un petit exemple pour reprendre tout cela et retravailler le chapitre précédent par la même occasion.

#include <stdio.h>


int main(void)
{
    char z = 'z';
    char a = 10;
    unsigned short b = 20;
    int c = 30;
    long d = 40;
    float e = 50.;
    double f = 60.0;
    long double g = 70.0;

    printf("%c\n", z);
    printf("%d\n", a);
    printf("%u\n", b);
    printf("%o\n", b);
    printf("%x\n", b);
    printf("%d\n", c);
    printf("%li\n", d);
    printf("%f\n", e);
    printf("%e\n", f);
    g = 80.0;
    printf("%Lg\n", g);
    return 0;
}
Résultat
z
10
20
24
14
30
40
50.000000
6.000000e+01
80

Si vous souhaitez afficher le caractère % vous devez le doubler : %%.

Précision des nombres flottants

Vous avez peut-être remarqué que lorsqu’un flottant est affiché avec le format f, il y a un certain nombre de zéros qui suivent (par défaut six) et ce, peu importe qu’ils soient utiles ou non. Afin d’en supprimer certains, vous pouvez spécifier une précision. Celle-ci correspond au nombre de chiffres suivant la virgule qui seront affichés. Elle prend la forme d’un point suivi par un nombre : la quantité de chiffres qu’il y aura derrière la virgule.

double x = 42.42734;

printf("%.2f\n", x);
Résultat
42.43

Notez que pour respecter la précision demandée, la valeur est arrondie.

Les caractères spéciaux

Dans certains cas, nous souhaitons obtenir un résultat à l’affichage (saut de ligne, une tabulation, un retour chariot, etc.). Cependant, ils ne sont pas particulièrement pratiques à insérer dans une chaîne de caractères. Aussi, le C nous permet de le faire en utilisant une séquence d’échappement. Il s’agit en fait d’une suite de caractères commençant par le symbole \ et suivie d’une lettre. En voici une liste non exhaustive.

Séquence d’échappement Signification
\a Caractère d’appel
\b Espacement arrière
\f Saut de page
\n Saut de ligne
\r Retour chariot
\t Tabulation horizontale
\v Tabulation verticale
\" Le symbole « " »
\\ Le symbole « \ » lui-même

En général, vous n’utiliserez que le saut de ligne, la tabulation horizontale et de temps à autre le retour chariot, les autres n’ont quasiment plus d’intérêt. Un petit exemple pour illustrer leurs effets.

#include <stdio.h>


int main(void)
{
    printf("Quelques sauts de ligne\n\n\n");
    printf("\tIl y a une tabulation avant moi !\n");
    printf("Je voulais dire que...\r");
    printf("Hey ! Vous pourriez me laisser parler !\n");
    return 0;
}
Résultat
Quelques sauts de ligne


        Il y a une tabulation avant moi !
Hey ! Vous pourriez me laisser parler !

Le retour chariot provoque un retour au début de la ligne courante. Ainsi, il est possible d’écrire par-dessus un texte affiché.

Sur plusieurs lignes

Notez qu’il est possible d’écrire un long texte sans appeler plusieurs fois la fonction printf(). Pour ce faire, il suffit de le diviser en plusieurs chaînes de caractères.

#include <stdio.h>

int main(void)
{
    printf("Texte écrit sur plusieurs "
           "lignes dans le code source "
           "mais sur une seule dans la console.\n");
    return 0;
}
Résultat
Texte écrit sur plusieurs lignes dans le code source mais sur une seule dans la console.

  1. ISO/IEC 9899:201x, doc. N1570, § 7, Library, p. 180.

Interagir avec l'utilisateur

Maintenant que nous savons déclarer, utiliser et même afficher des variables, nous sommes fin prêts pour interagir avec l’utilisateur. En effet, jusqu’à maintenant, nous nous sommes contentés d’afficher des informations. Nous allons à présent voir comment en récupérer grâce à la fonction scanf(), dont l’utilisation est assez semblable à printf().

#include <stdio.h>


int main(void)
{
    int age;

    printf("Quel âge avez-vous ? ");
    scanf("%d", &age);
    printf("Vous avez %d an(s)\n", age);
    return 0;
}
Résultat
Quel âge avez-vous ? 15
Vous avez 15 an(s).

Il est possible que la question « Quel âge avez-vous ? » s’affiche après que vous avez entré des données. Ce comportement peut se produire dans le cas où la ligne affichée n’est pas terminée par un retour à la ligne (\n) et dépend de la bibliothèque C présente sur votre système. Dans un tel cas, pour afficher la ligne, il vous suffit d’ajouter un appel à la fonction fflush() après l’appel à la fonction printf(), comme suit.

printf("Quel âge avez-vous ? ");
fflush(stdout);

Nous reviendrons sur cette fonction et son argument dans la seconde partie du cours. Pour l’heure, retenez simplement que cet appel est nécessaire si vous rencontrez le comportement décrit. :)

Comme vous le voyez, l’appel à scanf() ressemble très fort à celui de printf() mise à part l’absence du caractère spécial \n (qui n’a pas d’intérêt puisque nous récupérons des informations) et le symbole &.

À son sujet, souvenez-vous de la brève explication sur la mémoire au début du chapitre précédent. Celle-ci fonctionne comme une armoire avec des tiroirs (les adresses mémoires) et des objets dans ces tiroirs (nos variables). La fonction scanf() a besoin de connaître l’emplacement en mémoire de nos variables afin de les modifier. Afin d’effectuer cette opération, nous utilisons le symbole & (qui est en fait un opérateur que nous verrons en détail plus tard). Ce concept de transmission d’adresses mémoires est un petit peu difficile à comprendre au début, mais ne vous inquiétez pas, vous aurez l’occasion de bien revoir tout cela en profondeur dans le chapitre traitant des pointeurs.

Ici, scanf() attend patiemment que l’utilisateur saisisse un nombre au clavier afin de modifier la valeur de la variable age. On dit que c’est une fonction bloquante, car elle suspend l’exécution du programme tant que l’utilisateur n’a rien entré.

Pour ce qui est des indicateurs de conversions, ils sont un peu différents de ceux de printf().

Type Indicateur(s) de conversion
char c
signed char hhd (ou hhi)
short hd ou hi
int d ou i
long ld ou li
long long lld ou lli
unsigned char hhu, hhx ou hho
unsigned short hu, hx ou ho
unsigned int u, x ou o
unsigned long lu, lx ou lo
unsigned long long llu, llx, ou llo
float f
double lf
long double Lf

Faites bien attention aux différences ! Si vous utilisez le mauvais format, le résultat ne sera pas celui que vous attendez. Les changements concernent les types char (s’il stocke un entier), short et double.

Notez que :

  • l’indicateur c ne peut être employé que pour récupérer un caractère et non un nombre ;
  • il n’y a plus qu’un seul indicateur pour récupérer un nombre hexadécimal : x (l’utilisation de lettres majuscules ou minuscules n’a pas d’importance) ;
  • il n’y a pas d’indicateur pour le type _Bool.

En passant, sachez qu’il est possible de lire plusieurs entrées en même temps, par exemple comme ceci.

int x, y;

scanf("%d %d", &x, &y);
printf("x = %d | y = %d\n", x, y);

L’utilisateur a deux possibilités, soit insérer un (ou plusieurs) espace(s) entre les valeurs, soit insérer un (ou plusieurs) retour(s) à la ligne entre les valeurs.

Résultat (1)
14
6
x = 14 | y = 6
Résultat (2)
14 6
x = 14 | y = 6

Voici un exemple exploitant et illustrant plusieurs indicateurs de scanf().

#include <stdio.h>

int
main(void)
{
    char c, hhd;

    scanf("%c %hhd", &c, &hhd);
    printf("c = %c, n = %d\n", c, hhd);

    int i, o, x;

    scanf("%i %o %x", &i, &o, &x);
    printf("i = %i, o = %o, x = %x\n", i, o, x);

    float f;
    double lf;

    scanf("%f %lf", &f, &lf);
    printf("f = %f, lf = %f\n", f, lf);
    return 0;
}
Résultat
c 5
c = c, n = 5
10 777 C7
i = 10, o = 777, x = c7
56.7 2e-3
f = 56.700001, lf = 0.002000

La fonction scanf() est en apparence simple (oui, en apparence), mais son utilisation peut devenir très complexe lorsqu’il est nécessaire de vérifier les entrées de l’utilisateur (entre autres). Cependant, à votre niveau, vous ne pouvez pas encore effectuer de telles vérifications. Ce n’est toutefois pas très grave, nous verrons cela en temps voulu. ;)


Maintenant, vous êtes capable de communiquer avec l’utilisateur. Cependant, nos actions sont encore un peu limitées. Nous verrons dans les prochains chapitres comment mieux interagir avec l’utilisateur.

En résumé
  1. Les fonctions de la bibliothèque standard sont utilisables en incluant des fichiers d’en-tête ;
  2. Il est possible d’afficher du texte à l’aide de la fonction printf() ;
  3. La fonction printf() emploie différents indicateurs de conversion suivant le type de ses arguments ;
  4. Il existe un certain nombre de caractères spéciaux permettant de produire différents effets à l’affichage ;
  5. La fonction scanf() permet de récupérer une entrée ;
  6. Les indicateurs de conversions ne sont pas identiques entre printf() et scanf() ;
  7. L’opérateur & permet de transmettre l’adresse d’une variable à scanf().