130 votes

scanf() laisse le caractère de nouvelle ligne dans le tampon

J'ai le programme suivant :

int main(int argc, char *argv[])
{
    int a, b;
    char c1, c2;
    printf("Enter something: ");
    scanf("%d", &a); // line 1
    printf("Enter other something: ");
    scanf("%d", &b); // line 2

    printf("Enter a char: ");
    scanf("%c", &c1); // line 3
    printf("Enter another char: ");
    scanf("%c", &c2); // line 4

    printf("Done"); // line 5

    system("PAUSE");

    return 0;
}

Comme je l'ai lu dans le livre C, l'auteur dit que scanf() a laissé un caractère de nouvelle ligne dans la mémoire tampon, par conséquent, le programme ne s'arrête pas à la ligne 4 pour que l'utilisateur entre les données, mais il stocke le caractère de nouvelle ligne dans la mémoire tampon. c2 et passe à la ligne 5.

C'est bien cela ?

Cependant, cela se produit-il uniquement avec char les types de données ? Parce que je n'ai pas vu ce problème avec int comme dans les lignes 1, 2, 3. Est-ce exact ?

123voto

Jeremiah Willcock Points 14674

Les scanf() saute automatiquement les espaces blancs avant d'essayer d'analyser les conversions autres que les caractères. Les formats de caractères (principalement %c ; également les jeux de balayage %[…] - et %n ) sont l'exception ; ils ne sautent pas les espaces blancs.

Utilice " %c" avec un blanc en tête pour sauter l'espace blanc facultatif. N'utilisez pas de blanc de fin dans un scanf() chaîne de format.

Notez que cela ne consomme toujours pas les espaces blancs restants dans le flux d'entrée, pas même jusqu'à la fin d'une ligne, donc faites attention à cela si vous utilisez également getchar() o fgets() sur le même flux d'entrée. Nous faisons simplement en sorte que scanf saute les espaces blancs avant comme c'est le cas pour les conversions %d et d'autres conversions sans caractère.


Notez que les "directives" ne comportant pas d'espace blanc (pour utiliser des Terminologie POSIX scanf ) autres que les conversions, comme le texte littéral en scanf("order = %d", &order); ne saute pas non plus les espaces blancs. Le littéral order doit correspondre au prochain caractère à lire.

Vous souhaitez donc probablement " order = %d" si vous souhaitez sauter une nouvelle ligne à partir de la ligne précédente tout en exigeant une correspondance littérale sur une chaîne de caractères fixe, comme cette question .

42voto

Shweta Points 972

Utilice scanf(" %c", &c2); . Cela résoudra votre problème.

13voto

brandizzi Points 11051

Une autre option (que j'ai obtenue grâce à aquí ) est de lire et d'écarter la nouvelle ligne en utilisant l'option option d'affectation-supression . Pour ce faire, il suffit de mettre un format pour lire un caractère avec un astérisque entre % y c :

scanf("%d%*c",&a); // line 1
scanf("%c%*c",&c1); // line 3

scanf lira alors le prochain caractère (c'est-à-dire la nouvelle ligne) mais ne l'assignera à aucun pointeur.

En fin de compte, cependant, j'appuierais la dernière option de la FAQ :

Ou, selon vos besoins, vous pouvez également oublier scanf()/getchar(), utiliser fgets() pour obtenir une ligne de texte de l'utilisateur et l'analyser vous-même.

10voto

ilkkachu Points 3349

Pour faire écho à ce que j'ai posté dans une autre réponse sur le C++ : Je suggère de jeter scanf() de ne jamais l'utiliser, et d'utiliser à la place fgets() y sscanf() .

La raison en est qu'au moins dans les systèmes de type Unix, le terminal sur lequel votre programme CLI s'exécute effectue par défaut un traitement des données de l'utilisateur avant que votre programme ne les voie. Il met en mémoire tampon les entrées jusqu'à ce qu'une nouvelle ligne soit saisie, et permet une édition rudimentaire des lignes, comme le retour à la ligne.

Vous ne pouvez donc jamais obtenir un seul caractère à la fois, ou quelques caractères isolés, mais seulement une ligne complète. Mais ce n'est pas ce que fait l'ex. scanf("%d") il ne traite que les chiffres, et s'arrête là et laisse le reste en mémoire tampon dans la bibliothèque C, en vue d'une prochaine utilisation. stdio à utiliser. Si votre programme a par exemple

printf("Enter a number: ");
scanf("%d", &a);

printf("Enter a word: ");
scanf("%s", word);

et vous entrez dans la ligne 123 abcd il complète à la fois scanf() en une seule fois, mais seulement après un retour à la ligne. Le premier scanf() ne revient pas lorsque l'utilisateur a appuyé sur la touche espace, même si c'est là que le numéro se termine (parce qu'à ce moment-là, la ligne est encore dans le tampon de ligne du terminal) ; et la deuxième commande scanf() n'attend pas que vous saisissiez une autre ligne (parce que le tampon d'entrée contient déjà suffisamment de données pour remplir la fonction %s conversion).

Ce n'est pas ce à quoi les utilisateurs s'attendent habituellement !

Au lieu de cela, ils s'attendent à ce que le fait d'appuyer sur la touche Entrée complète la saisie, et si vous appuyez sur la touche Entrée, vous obtenez soit une valeur par défaut, soit une erreur, avec éventuellement une suggestion de ne donner que la réponse, s'il vous plaît.

On ne peut pas vraiment faire cela avec scanf("%d") . Si l'utilisateur se contente d'appuyer sur la touche "Entrée", rien ne se passe. Parce que scanf() attend toujours le numéro. Le terminal envoie la ligne plus loin, mais votre programme ne la voit pas, car scanf() le mange. Vous n'avez pas la possibilité de réagir à l'erreur de l'utilisateur.

Ce n'est pas non plus très utile.

C'est pourquoi je suggère d'utiliser fgets() o getline() pour lire une ligne complète d'entrée à la fois. Cela correspond exactement à ce que le terminal donne, et donne toujours à votre programme le contrôle après que l'utilisateur a entré une ligne. Ce que vous faites avec la ligne d'entrée dépend de vous, si vous voulez un nombre, vous pouvez utiliser atoi() , strtol() ou même sscanf(buf, "%d", &a) pour analyser le nombre. sscanf() n'a pas le même décalage que scanf() La fonction ne peut pas attendre davantage, car le tampon qu'elle lit est limité en taille et, lorsqu'il se termine, il se termine -- la fonction ne peut pas attendre davantage.

( fscanf() sur un fichier normal peut également convenir si le format du fichier prend en charge la façon dont il passe sur les nouvelles lignes comme sur n'importe quel espace blanc. Pour les données orientées ligne, j'utiliserais toujours fgets() y sscanf() .)


Donc, au lieu de ce que j'ai fait ci-dessus, utilisez quelque chose comme ceci :

printf("Enter a number: ");

fgets(buf, bufsize, stdin);
sscanf(buf, "%d", &a);

ou, en fait, vérifier la valeur de retour de sscanf() afin de détecter les lignes vides et les autres données non valides :

#include <stdio.h>

int main(void)
{
    const int bufsize = 100;
    char buf[bufsize];
    int a;
    int ret;
    char word[bufsize];

    printf("Enter a number: ");
    fgets(buf, bufsize, stdin);

    ret = sscanf(buf, "%d", &a);

    if (ret != 1) {
        fprintf(stderr, "Ok, you don't have to.\n");
        return 1;
    }

    printf("Enter a word: ");
    fgets(buf, bufsize, stdin);

    ret = sscanf(buf, "%s", word);
    if (ret != 1) {
        fprintf(stderr, "You make me sad.\n");
        return 1;
    }

    printf("You entered %d and %s\n", a, word);
}

Bien sûr, si vous voulez que le programme insiste, vous pouvez créer une fonction simple pour passer en boucle sur le fichier fgets() y sscanf() jusqu'à ce que l'utilisateur décide de faire ce qu'on lui demande, ou de sortir immédiatement avec une erreur. Cela dépend de ce que vous pensez que votre programme devrait faire si l'utilisateur ne veut pas jouer le jeu.


Vous pourriez faire quelque chose de similaire, par exemple en faisant une boucle sur getchar() pour lire les caractères jusqu'à une nouvelle ligne après scanf("%d") est renvoyée, ce qui permet d'éliminer les déchets laissés dans le tampon, mais cela ne résout pas le cas où l'utilisateur appuie simplement sur la touche Entrée sur une ligne vide. Quoi qu'il en soit, fgets() lirait jusqu'à une nouvelle ligne, de sorte que vous n'ayez pas à le faire vous-même.

8voto

Jiwon Points 547

Utilice getchar() avant d'appeler le deuxième scanf() .

scanf("%c", &c1);
getchar();  // <== remove newline
scanf("%c", &c2);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X