Version en ligne

Tutoriel : Utiliser les bonnes fonctions d'entrée

Table des matières

Utiliser les bonnes fonctions d'entrée
La fonction scanf()
Les alternatives à scanf() pour récupérer une chaîne
Les alternatives à scanf() pour récupérer un nombre

Utiliser les bonnes fonctions d'entrée

La fonction scanf()

Salut à tous.
Dans ce mini tuto je vais vous expliquer pourquoi il faut éviter d'utiliser la fonction scanf() en C pour récupérer les entrées du clavier. Je vous expliquerai aussi ce qu'il faut utiliser à la place. Vous verrez que ce n'est pas plus compliqué. Ces méthodes sont utilisées par les professionnels alors pourquoi pas par vous. ;)
Après la lecture de ce tuto vous serez normalement capable de:

Comme base pour lire et comprendre ce tuto, vous devez juste avoir lu le début du cours C/C++ de M@teo21. Il vaut mieux aussi connaître le principe des chaînes de caractères en C.

Voilà, bonne lecture.

La fonction scanf()

Les alternatives à scanf() pour récupérer une chaîne

Le problème principal de la fonctions scanf() est qu'elle est compliquée à utiliser correctement. Cela vient du fait qu'elle attend des données formatées. Voici son prototype:

int scanf(const char *format, ... );

La fonction retourne le nombre de valeurs lues. Le premier paramètre est une chaîne de formats (%d, %c, ...) et ensuite viennent les pointeurs vers les variables que l'on souhaite remplir.

Mais vous pauvre programmeur, vous ne savez pas ce que va rentrer l'utilisateur de votre programme. Prenez cet exemple:

double a = 12.0;
int b = 0;
printf("Entrez un double: ");
b = scanf("%lf", &a);
printf("Vous avez entre: %lf, valeurs lues: %d", a, b);

Bon très bien, vous entrez un double, pas de problème. Mais l'utilisateur de votre programme n'est pas très malin (aïe, pas taper :-° ), on lui demande de taper un double et lui il va entrer une chaîne de caractères.
Et là, votre scanf ne va pas aimer:

Entrez un double:

coucou

Vous avez entre: 12.000000, valeurs lues: 0

Appuyez sur une touche pour continuer...

Vous voyez bien que scanf() n'a pas lu ce qui a été entré. En effet lorsque scanf() rencontre quelque chose qui ne correspond pas au format, la fonction s'arrête. Et ce qui n'a pas été lu reste dans le tampon qu'il faudrait donc vider.

Un autre inconvénient de la fonction scanf() est qu'on ne peut pas saisir facilement une chaîne de caractère contenant des espaces.
Voilà pourquoi il vaut mieux ne pas utiliser scanf() pour les saisies clavier.


Les alternatives à scanf() pour récupérer une chaîne

Les alternatives à scanf() pour récupérer une chaîne

La fonction scanf() Les alternatives à scanf() pour récupérer un nombre

En fait dans cette partie, comme le titre l'indique, je vais vous montrer comment récupérer une chaîne de caractère sans utiliser scanf(). Et comme je suis partageur, je vous montrerais aussi comment récupérer un seul caractère (ça vous le savez peut-être déjà mais on ne sais jamais :) ).

Récupérer une chaîne

Nous allons d'abord voir les saisies de chaînes. Alors quelles fonctions va t'on utiliser ?
Et bien la première vous la connaissez, c'est la fonction fgets().

o_O Quoi ? Mais fgets() ne sert pas à lire une ligne d'un fichier ?

Si mais en C le flux d'entrée, stdin, est aussi considéré comme un fichier et il est ouvert automatiquement au début du programme. La fonction fgets() va permettre de lire ce flux et donc de récupérer les entrées clavier. Voici le prototype de cette fonction:

char* fgets(char *s, int n, FILE *stream);

La fonction fgets() prends comme premier paramètre la chaîne dans laquelle elle va placer la saisie. Le deuxième paramètre est le nombre maximal de caractères (n-1 plus exactement) qui seront lus et le dernier paramètre est le flux à lire. Ici se sera stdin.

Mais comment connaît-on le nombre de bits à lire ? Tout simplement en utilisant sizeof qui permet de connaître le nombre de bytes d'une variable. Voilà un exemple d'utilisation de la fonction fgets pour récupérer une chaîne:

#include <stdio.h>
#include <stdlib.h>
 
int main ( int argc, char* argv[] )
{
    char chaine[20] = "";
 
    printf("Entrez une chaine:\n");
    fgets(chaine, sizeof(chaine), stdin);
    printf("Chaine: %s.\n", chaine);
 
    return 0;
}

Votre console affiche:

Entrez une chaine:

Je suis un zero

Chaine: Je suis un zero

.

 

Appuyez sur une touche pour continuer...

Ici il y a deux choses à remarquer. Premièrement, la fonction fgets permet la saisie de chaînes contenant des espaces, ce que scanf() ne permet pas.
Et deuxièmement, il y a un saut de ligne à la fin de la chaîne. En effet, vous avez appuyé sur entrée pour saisir votre chaîne. Le caractère est donc à la fin de la chaîne.
Il y a un autre problème, on dit à la fonction fgets() de lire 19 caractères mais rien n'empêche l'utilisateur de rentrer 50 caractères. À ce-moment là un deuxième appel à la fonction fgets() récupérera la suite de l'entrée.

Il faut donc corriger ce problème.
Pour cela il faut utiliser une fonction, la fonction clean(). Elle n'est pas de moi et je l'ai tellement vue sur le forum que je suis incapable de vous donner son auteur.

void clean(const char *buffer, FILE *fp)
{
    char *p = strchr(buffer,'\n');
    if (p != NULL)
        *p = 0;
    else
    {
        int c;
        while ((c = fgetc(fp)) != '\n' && c != EOF);
    }
}

Je suppose que je dois vous expliquer cette fonction ?
Oui, bon d'accord. Alors cette fonction prend en paramètre la chaîne que vous avez récupérée avec fgets() et le flux qui est comme précédemment stdin. Cette fonction ne retourne rien.

D'abord, on cherche le caractère dans la chaîne à l'aide de la fonction strchr() (donc il faudra penser à inclure string.h). Si on trouve le , on le supprime de la chaîne et si on ne le trouve pas on vide le buffer avec une boucle while. Cette boucle ne s'arrête pas tant qu'elle lit un caractère différent de et que celui ci n'est pas le caractère de fin de flux ("Ctrl" + "Z" sur Windows ou "Ctrl" + "D" sur GNU/Linux).

Donc voici comment récupérer correctement une chaîne de caractère en C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void clean(const char *buffer, FILE *fp);
 
int main ( int argc, char* argv[] )
{
    char chaine[20] = "";
 
    printf("Entrez une chaine:\n");
    fgets(chaine, sizeof(chaine), stdin);
    clean(chaine, stdin);
    printf("Chaine: %s.\n", chaine);
 
    return 0;
}
 
void clean(const char *buffer, FILE *fp)
{
    char *p = strchr(buffer,'\n');
    if (p != NULL)
        *p = 0;
    else
    {
        int c;
        while ((c = fgetc(fp)) != '\n' && c != EOF);
    }
}

Et voici le résultat:

Entrez une chaine:

Je suis un zero

Chaine: Je suis un zero.

 

Appuyez sur une touche pour continuer...

Vous pouvez enfin dire au revoir à scanf().

Récupérer un caractère

Alors pourquoi je n'utilise pas fgets() + clean() ? C'est simple, il existe une fonction permettant de ne récupérer qu'un seul caractère et non une chaîne. Cette fonction c'est getchar().
Bien sur ce n'est pas si simple. Si vous n'utilisez que getchar() vous pourrez rencontrer des problèmes. En effet, cette fonction prend le dernier caractère tapé par l'utilisateur. Donc voyez ce code:

a = getchar();
printf("Vous avez tape la lettre %c\n", a);
b = getchar();
printf("Vous avez tape la lettre %c\n", b);

Avec ce code la console affiche:

d

Vous avez tape la lettre d

Vous avez tape la lettre

 

Appuyez sur une touche pour continuer...

Vous voyez que votre programme ne vous demande pas de rentrer le deuxième caractère. C'est parce que lors du deuxième appel à la fonction getchar() celle ci récupère le caractère qui correspond à l'appui que vous avez fait sur la touche entrée après avoir tapé la lettre d. Cela se produit parce que le est toujours dans le "buffer".
Il faut donc enlever ce du buffer. Pour cela on fait juste une boucle comme ceci:

while (getchar() != '\n');

Cela permet de vider le flux entrant. En effet, la boucle lit les caractères jusqu'à ce qu'elle ait lu le . Celui ci n'est donc plus dans le buffer. Essayez le code suivant:

a = getchar();
while (getchar() != '\n');
printf("Vous avez tape la lettre %c\n", a);
b = getchar();
while (getchar() != '\n');
printf("Vous avez tape la lettre %c\n", b);

Vous pouvez voir que cette fois ci le programme vous demande bien les deux caractères:

d

Vous avez tape la lettre d

s

Vous avez tape la lettre s

 

Appuyez sur une touche pour continuer...

Vous savez maintenant récupérer un caractère en C.


La fonction scanf() Les alternatives à scanf() pour récupérer un nombre

Les alternatives à scanf() pour récupérer un nombre

Les alternatives à scanf() pour récupérer une chaîne

Alors vous allez me dire, oui c'est bien ton truc, on peut récupérer des chaînes de caractères mais pour les nombres comment on fait ? o_O

Et bien il suffit d'utiliser des fonctions qui convertissent les chaînes en nombres. Cela tombe bien parce qu'en C il y en a plusieurs, je vais vous présenter celles ci:
sscanf(), strtol(), strtod, strtoul()

strtol()

Tout d'abord le prototype de la fonction:

long int strtol(const char *str, char **endptr, int base);

La fonction strtol() permet de convertir une chaîne en un nombre de type long int.
Le premier paramètre de la fonction est la chaîne de caractère que l'on souhaite convertir, le second paramètre contiendra l'adresse où la conversion s'est arrêtée. Enfin le dernier paramètre est la base dans laquelle on souhaite convertir la chaîne. En général on utilise la base 10 (celle de la vie de tous les jours). Voici un exemple d'utilisation de cette fonction:

#include <stdio.h>
#include <stdlib.h>
 
int main ( int argc, char* argv[] )
{
    char chaine[10] = "255";
    long int nombre = 0;
    char* fin = NULL;
 
    nombre = strtol(chaine, &fin, 10);
    printf("Chaine: %s, nombre: %ld\n", chaine, nombre);
 
    return 0;
}

strtod()

Le prototype de la fonction:

double strtod(const char *str, char **endptr);

La fonction strtod() permet de convertir une chaîne en double.
Comme pour la fonction précédente, le premier paramètre de la fonction est la chaîne de caractère que l'on souhaite convertir, le second paramètre contiendra l'adresse où la conversion s'est arrêtée. Un exemple d'utilisation:

#include <stdio.h>
#include <stdlib.h>
 
int main ( int argc, char* argv[] )
{
    char chaine[10] = "345.632";
    double nombre = 0;
    char* fin = NULL;
 
    nombre = strtod(chaine, &fin);
    printf("Chaine: %s, nombre: %lf\n", chaine, nombre);
 
    return 0;
}

strtoul()

Le prototype de la fonction:

unsigned long int strtoul(const char *str, char **endptr, int base);

La fonction strtoul() permet encore de convertir une chaîne en un nombre de type long int mais cette fois il est unsigned c'est à dire qu'il doit être positif.
Le premier paramètre de la fonction est la chaîne de caractère que l'on souhaite convertir, le second paramètre contiendra l'adresse où la conversion s'est arrêtée. Comme pour strtol() le dernier paramètre est la base dans laquelle on souhaite convertir la chaîne. Voici un exemple d'utilisation de cette fonction:

#include <stdio.h>
#include <stdlib.h>
 
int main ( int argc, char* argv[] )
{
    char chaine[10] = "255";
    unsigned long int nombre = 0;
    char* fin = NULL;
 
    nombre = strtol(chaine, &fin, 10);
    printf("Chaine: %s, nombre: %u\n", chaine, nombre);
 
    return 0;
}

Vous remarquerez que l'on utilise %u pour afficher un unsigned long int et non %ld comme pour un long int. D'ailleurs M@teo21 ne donnant pas tous les codes de format dans son cours, je vous les donne ici:

Code de format

Variable associée

c

char

d

int

u

unsigned int

hd

short int

hu

unsigned short

ld

long int

lu

unsigned long

f

float (notation décimale)

e

float (notation exponentielle)

lf

double (notation décimale)

le

double (notation exponentielle)

s

chaîne de caractère

sscanf()

La fonction sscanf() est très intéressante puisqu'elle peut remplacer les trois autres(ah j'aurais dû vous le dire avant ? :p ), voici son prototype:

int sscanf(const char *s, const char *format, ... );

Comme son nom l'indique un peu cette fonction a une utilisation proche de celle de scanf(). Le premier paramètre est la chaîne de caractère à convertir, le deuxième est le format (%ld, %lf, ...). Et enfin il y a un ou plusieurs paramètres représentant les nombres que l'on veut "extraire" de la chaîne.

Alors là je vous vois venir, vous allez me dire mais pourquoi on utilise sscanf() alors que tu nous a dit de ne pas utiliser scanf() ?
Mais là c'est très différent, en effet ici c'est vous qui décidez du contenu de la chaîne (contrairement aux entrées issues du clavier). Donc en principe vous savez ce que vous faites.

Donc comme je suppose que vous savez vous servir de scanf() (sinon je vous invite fortement à relire le cours de M@teo21), je ne vais prendre qu'un exemple puis vous testerez toutes les possibilités de sscanf():

#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char* argv[])
{
    char chaine[20] = "9999 45.56";
    int nombre1 = 0;
    double nombre2 = 0.0;
 
    sscanf(chaine, "%d %lf", &nombre1, &nombre2);
    printf("Chaine: %s, nombre1: %d, nombre2: %lf\n", chaine, nombre1, nombre2);
 
    return 0;
}

On obtient le résultat suivant:

Chaine: 9999 45.56, nombre1: 9999, nombre2: 45.56000

Citation : moi

sscanf(chaine, "%d %lf", &nombre1, &nombre2);

En effet, pareil que pour scanf(), c'est un pointeur sur la variable que l'on passe à la fonction sscanf(). En effet pour pouvoir modifier la valeur de la variable, il faut donner à la fonction l'adresse de la variable. Si on ne donne que la variable elle-même, celle-ci sera dupliquée mais sa valeur ne sera pas modifiée.

Et voilà c'est fini.
J'espère vous avoir appris quelques choses et que vous n'utiliserez plus l'horrible scanf(). :)
Si vous êtes bilingues (ou un peu moins ;) ) et que vous voulez en savoir plus sur les fonctions que je vous ai présentées (ou d'autres), vous pouvez aller voir ce lien ou sont présentées les fonctions systèmes d'Unix. Beaucoup de fonctions C sont équivalentes.

Si vous avez des questions ou des suggestions (voire des corrections) vous pouvez me contacter par MP ou laisser un commentaire.
Ce tuto est sous licence GNU/FDL. Vous pouvez donc le modifier ou le redistribuer.

Seb13.


Les alternatives à scanf() pour récupérer une chaîne