Nous allons voir comment détecter si une saisie à échoué, comment savoir quelle est la séquence qui n'est pas bonne et comment y remédier.
Le retour de scanf()
Souvenez vous dans le premier chapitre du prototype de la fonction scanf() :
Code : C1 | int scanf (const char *fmt, ...);
|
La valeur de retour est de type
int, en vrai, scanf() retourne le nombre de saisies qui ont eu lieu avec succès.
Par exemple :
Code : C1
2
3
4
5
6 | int n1, n2;
int ret;
printf ("> ");
ret = scanf ("%d-%d", &n1, &n2);
printf ("%d\n", ret);
|
Donnera ceci :
Code : Console
Dans le premier cas, les deux saisies ont eu lieu avec succès, donc scanf() retourne 2, dans le deuxième cas, une a échoué (on demande un nombre, il nous écrit une lettre

), scanf() retourne donc 1. Et si aucune saisie ne fonctionne, scanf() renvoie tout simplement 0.
Le joker * supprimant l'assignement du format n'est pas compté dans la valeur de retour.
Cette valeur nous dit donc si les données reçues sont potables, on peut donc en cas d'erreur vider le buffer et mettre fin au programme proprement.
Voici comment on procède :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | int nombre;
int ret;
ret = scanf ("%d", &nombre);
/* ne pas oublier de vider le buffer après la saisie */
scanf ("%*[^\n]");
getchar ();
/* vérification de la saisie */
if (ret != 1)
{
printf ("erreur de saisie\n");
exit (EXIT_FAILURE); /*!< la fonction exit() et la constante EXIT_FAILURE sont définies dans stdlib.h */
}
printf ("%d\n", nombre);
|
Pourquoi ne pas avoir fait : scanf ("%d%*[^\n]", &nombre); ?
Si la saisie échoue au niveau du %d, scanf s'arrêtera là et retournera la valeur de retour (qui sera ici 0), et le reste de la saisie ne sera pas effectué (donc le buffer ne sera pas vidé). Ici cela permet de vider le buffer en cas d'erreur et également en cas de réussite.
Le format spécial %n
Le formatteur %n retourne le nombre de caractère lu par scanf() dans une variable de type
int, celui ci n'est pas compté non plus dans la valeur de retour de scanf().
Elle permet donc une meilleure précision sur la gestion d'erreur :
Exemple :
L'utilisateur tape ceci :
" 123azerty456uiop789 "
On ne veut récupérer uniquement les premiers et derniers chiffres (123 et 789)
La syntaxe est donc la suivante :
[nombre]~[lettres]~[nombre]~[lettres]~[nombre]
Logiquement nous ferions cela :
Code : C1
2 | int a, b;
scanf ("%d%*[a-z]%*[0-9]%*[a-z]%d", &a, &b);
|
- On récupère le premier nombre
- On vérifie la présence de lettres minuscules (sans assignement)
- On vérifie la présence de chiffres (sans assignement)
- On vérifie la présence de lettres minuscules (sans assignement)
- On récupère le dernier nombre
On a vu que si scanf réussi, on aura comme valeur de retour 2 (puisqu'on assigne deux éléments).
Mais en cas d'échec, la valeur ne pourra être que 0 (rien d'assigné) ou 1 (premier entier).
Si l'on veut faire une gestion fine des erreurs, voire reprendre sur ces mêmes erreurs, on ne dispose pas assez d'informations pour savoir où exactement scanf a échoué.
C'est ici que le format %n nous aide :
Code : C 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | int a, b;
int seq0, seq1, seq2, seq3, seq4;
int ret;
ret = scanf ("%d%n%*[a-z]%n%*[0-9]%n%*[a-z]%n%d%n", &a, &seq0, &seq1, &seq2, &seq3, &b, &seq4);
/* vidage du buffer */
scanf ("%*[^\n");
getchar ();
/* vérification */
if (ret != 2)
{
if (!seq0) /* premier nombre qui a échoué */
if (!seq1) /* première séquence de lettre qui a échoué */
if (!seq2) /* la séquence de chiffre a échoué */
if (!seq3) /* la dernière séquence de lettre a échoué */
if (!seq4) /* dernier nombre qui à échoué */
}
|
Les chaines de format de scanf() commencent à devenir complexes

.