2017-02-28 4 views
1

Je suis assez nouveau en C, je travaillais en Python, et j'essaie de voir si quelque chose que j'ai lu est un nombre entier. Sinon, lire jusqu'à ce que je réussisse à entrer un numéro.Lecture jusqu'à ce que je parvienne à entrer un nombre entier

J'ai fait quelques recherches et j'ai découvert que la fonction scanf renvoie effectivement 1 si la lecture est effectuée correctement, et 0 sinon. Donc, j'ai écrit ce code, et je ne comprends pas pourquoi cela est une boucle infinie, l'écriture « Donnez un entier » sur la console

#include <stdio.h> 

int main() { 
    int a; 
    int b = 1; 
    do { 
     printf("Give an integer\n"); 
     b = scanf("%d", &a); 
    } while (b == 0); 
} 
+0

[Les intigers sont des choses merveilleuses!] (Https://www.youtube.com/watch?v=dJFyz73MRcg) – user4581301

+3

Le problème est que 'scanf()' ne consomme pas les données qui ne correspondent pas à votre descripteur de champ. Par conséquent, lorsque vous effectuez une nouvelle tentative pour réessayer, les mêmes données non correspondantes sont toujours en attente de traitement. –

+0

Ça marche! Mais je ne comprends pas vraiment pourquoi :)) –

Répondre

1

Je ne comprends pas pourquoi il en est une boucle infinie, écrire « Donner une intiger » sur la console

Le problème est que scanf() ne consomme pas les données qu'il ne peut pas correspondre contre le format spécifié. Il laisse ces caractères non lus dans le flux d'entrée. Par conséquent, si vous essayez de lire à nouveau à partir du même flux avec le même format, sans consommer au moins un caractère par un autre moyen, vous pouvez être certain que l'entrée ne correspondra pas à nouveau. Et encore. Et encore. Pour éviter votre boucle infinie, vous devez consommer au moins un caractère de l'entrée non correspondante après chaque échec de correspondance. Il y a plusieurs façons de le faire. voici une assez simple:

#include <stdio.h> 

int main() { 
    int a; 

    do { 
     printf("Give an intiger\n"); 
     if (scanf("%d", &a)) { 
      // breaks from the loop on a successful match or an error 
      break; 
     } 
     // consume the remainder of one line of input without storing it 
     if (scanf("%*[^\n]") == EOF) { 
      break; 
     } 
    } while (1); 
} 

qui consomme tout le reste de la ligne sur laquelle l'entrée non-appariement est rencontré, ce qui donnera un comportement interactif moins surprenant pour certaines entrées que de nombreuses solutions ne.

Si vous avez un penchant pour l'écriture de code laconique, ou si vous ne l'aimez pas break hors du milieu d'une boucle, vous pouvez écrire la même chose comme ceci:

#include <stdio.h> 

int main() { 
    int a; 

    do { 
     printf("Give an intiger\n"); 
    } while ((scanf("%d", &a) == 0) && (scanf("%*[^\n]") != EOF)); 
} 

Parce que les && courts-circuits de l'opérateur, le second appel scanf() sera exécuté uniquement si le premier renvoie zéro, et la boucle sortira après la première itération dans laquelle le premier scanf() appel renvoie non nul ou le second renvoie EOF (indiquant une erreur).

+0

ajoutant également getchar() après scanf fonctionne très bien. Mais pourquoi la fonction scanf ne consomme pas de données de formats spécifiques? C'est un bug ou une décision de langage? –

+0

@DANIROLST, en ajoutant 'getchar()' après l'échec 'scanf()' évitera une boucle * infinite *, mais cela produira néanmoins un comportement surprenant pour certaines entrées. Essayez, par exemple, avec "abc123". –

+3

@DANIROLST, la norme C spécifie clairement cet aspect du comportement de 'scanf()'. Et, cela a du sens: si 'scanf()' consommait les données non correspondantes, alors ces données seraient * perdues *, sans mécanisme pour le récupérer. De plus, si 'scanf()' veut consommer une entrée qui ne correspond pas, alors combien pensez-vous qu'il devrait consommer? Il n'y a pas de réponse qui conviendrait à toutes les circonstances. –

2

Le problème avec scanf() est qu'il arrête la lecture lorsque le premier l'espace blanc est rencontré pour la plupart des spécificateurs comme "%d", il est donc laissé dans l'entrée et c'est pourquoi relire causerait un problème si vous ne jetez pas cet espace blanc, car il reviendra immédiatement la prochaine fois que vous l'appelez. Un espace blanc obligatoire est introduit lorsque vous appuyez sur Entrez ou Renvoyez, le caractère nouvelle ligne '\n' (ou saut de ligne).

Si vous voulez lire un entier et assurez-vous que vous avez, vous pouvez essayer comme ça

#include <stdio.h> 
#include <stdlib.h> 
#include <ctype.h> 

int 
main(void) 
{ 
    long int value; // You can adapt this to use `int' 
        // but be careful with overflow 
    char line[100]; 
    while (fgets(line, sizeof(line), stdin) != NULL) { 
     char *endptr; 
     value = strtol(line, &endptr, 10); 
     if ((isspace(*endptr) != 0) && (endptr != line)) 
      break; 
    } 
    fprintf(stdout, "%ld\n", value); 
    return 0; 
} 

lire le manuel d » strtol() pour comprendre ce code

Vous pouvez comme suggéré dans les commentaires, retirer les caractères de l'espace blanc de l'entrée, mais à mon humble avis qui est plus difficile et vous auriez encore d'autres problèmes avec scanf(), comme la saisie d'entrée vide qui n'est pas simple.

+1

Il est au mieux trompeur de dire que 'scanf()' ignore les caractères blancs, et dans la mesure où c'est vrai de l'utilisation * OP * de 'scanf()' il n'explique pas le problème qu'il décrit. –

+0

@JohnBollinger Vous avez absolument raison! Je l'ai corrigé et je pense qu'il est maintenant plus facile de comprendre le problème avec le code d'OP. –

+0

'(isspace (* endptr)! = 0)' autorise l'entrée de '" 123 456 "' comme valide. Peut-être '(* endptr = '\ n' || * endptr = '\ 0')'? – chux