Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C Discussion :

scanf non fonctionelle


Sujet :

C

  1. #1
    Nouveau Candidat au Club
    scanf non fonctionelle
    Bonjour je ne trouve pas mon erreur :'(

    sylvainc@sylvain--pc:~/programme/Langage_C_Partie_4$ vim lire_donnees_au_clavier.c
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
     
    #include <stdio.h>
     
    int main(void)
    {
            //Nous avons vus dans le cours précèdant
     
            int monage = 29;
     
            printf("Mon âge est de %d ans \n", monage);
     
            /*Ce que l'on voudrais faire c'est apprendre à demmander à l'utilisateur 
             * de saisir les données lui même
            */
     
            int ageutilisateur = 0;
     
            printf("Quel âge avez vous ? ");
            scanf("%d", &ageutilisateur);
            printf("Vous avez donc %d ans \n", ageutilisateur);
     
            /*Le & est très important pour le scanf
             * mavariable  : contenu de la variable (example mavariable = A)
             * &mavariable : C'est l'adresse ou est stocker la variable
            */
     
            signed char lettre = 'A';
            printf("Choisissez une lettre :> ");
            scanf("%c", &lettre);
            printf("La lettre choisis est :> %c ", lettre);
     
            return 0;
    }


    sylvainc@sylvain--pc:]~/programme/Langage_C_Partie_4$ gcc lire_donnees_au_clavier.c -o liredonneesauclavier
    sylvainc@sylvain--pc:~/programme/Langage_C_Partie_4$ ./liredonneesauclavier
    Mon âge est de 29 ans
    Quel âge avez vous ? 30
    Vous avez donc 30 ans
    Choisissez une lettre :> La lettre choisis est :>
    sylvainc@sylvain--pc:~/programme/Langage_C_Partie_4$


    Pourquoi mon deuxième scanf est ignoré ?

  2. #2
    Expert éminent sénior
    Bonjour
    Citation Envoyé par sylvain666 Voir le message
    Pourquoi mon deuxième scanf est ignoré ?
    C'est un problème archi-connu.

    Le souci de scanf(), c'est qu'il attend une entrée formatée avec exactitude. Et que ce que tape l'utilisateur est tout sauf formaté avec exactitude. Même avec la meilleure volonté du monde, il ne peut pas donner à scanf() ce qu'elle attend. Et si ce qui est donné n'est pas comme il faut, alors la fonction l'ignore et ce quelque chose reste jusqu'à ce qu'il puisse être traité. On ne fait pas saisir quelque chose via scanf() à un humain. Malheureusemernt quand on apprend le C, on est obligé d'arriver assez vite à la saisie et les méthodes de saisies viables étant assez complexes, on passe alors, pour simplifier, par scanf(). Ce qui est un bon pis-aller mais seulement au début. Ensuite il faudra l'abandonner.

    Donc l'erreur: à ta première saisie, tu tapes "30" ok. Mais tu valides cette saisie par l'appui sur la touche <return>, qui est un caractère réel ('\n' exactement), lequel caractère vient s'ajouter à ta saisie. Le clavier contient donc '3', '0', '\n'. Le scanf() attend un nombre, il ne récupère que le nombre (le '3' et '0') et laisse tout ce qui n'est pas nombre (donc le '\n') dans le clavier.
    A la saisie suivante tu demandes à scanf() de récupérer un caractère. Ok, il a à sa disposition le caractère '\n' déjà présent, il ne s'embête pas à attendre que tu tapes quelque chose, il le récupère comme demandé.

    Pour corriger: chaque fois que tu feras saisir un nombre ou un simple caractère, il te faudra ajouter l'instruction fgetc(stdin) juste après, pour virer ce '\n'. Plus tard tu apprendras à faire des saisies plus robustes (parce que cette solution simpliste ne fonctionne que si tu joues le jeu et si par exemple tu tapes "toto" là où on te demande ton âge, elle ne fonctionnera pas).

    Et attention à ne pas formater ton cerveau sur des idées figées qui contiennent une certaine dose d'erreur car le "&" n'est pas systématique pour scanf() (cf ton commentaire). Parce que pour faire saisir une chaine, on ne le met pas.
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  3. #3
    Membre chevronné
    Bonjour,

    Attention, l'erreur n'est pas simplement due au fait que scanf doit avoir une entrée formatée avec exactitude/ s'assurer que la saisie effectuée au clavier par l'utilisateur devra être la même que celle spécifiée dans le format. Non ; c'est juste que la fonction scanf n'est absolument pas adaptée pour des entiers et où des nombres à virgule flottante à partir d'une d'entrée car, l'entrée en question peut contenir des nombres non représentables par le type d'argument voulu ou format attendu et c'est là où est réellement le problème et la difficulté de scanf.

    Une des solutions, c'est l'utilisation d'une fonction de saisie sécuriser et ensuite procéder à une conversion de la saisie avec les fonctions telles que strtol(). Ce qui garantie et vérifie bien que l'on a un entier ou nombre à la virgule flottante valide et représentable du type. À noter également que cette solution traite tous les caractères de fin y compris les caractères d'espacement, comme une condition d'erreur.



    Code C :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
     
    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
     
     
    /*
        Attention, le code source ci-dessous 
        est susceptible de comporter des erreurs.
    */
     
     
    bool f_get_numeric( long *dt ){
     
        char *p = NULL;
        char buffer[BUFSIZ];
     
        /* no crash test snip memset return */
        (void)memset(buffer, 0x0, BUFSIZ); 
     
        errno = 0x0;
        if( NULL == fgets(buffer, BUFSIZ, stdin) ){
            (void)fprintf(stderr, "Error(%d)\t:%s\n",
                errno, strerror(errno) );
            return false;
        }
     
        *dt = strtol(buffer, &p, 0xA);
        if( errno == ERANGE ){
            (void)fprintf(stderr, 
                "Error(%d)\t: valeur hors plage\n\t:%s\n",
                errno, strerror(errno) );
            return false;
        }else if( buffer == p ){
            (void)fprintf(stderr, "Erreur\t: Saisie no valid\n");
            return false;
        }else if('\n' != *p && '\0' != *p){
            (void)fprintf(stderr, 
                "Erreur\t: detection de caractere\n");
            return false; 
        }
        return true;
    }
     
     
    int main( void ){
        long dt = 0x0;
        if( false == f_get_numeric(&dt) )
            return EXIT_FAILURE;
        (void)fprintf(stderr, "(ret)\t:%ld\n", dt );
        return EXIT_SUCCESS;
    }



    à bientôt.
    Celui qui peut, agit. Celui qui ne peut pas, enseigne.
    Il y a deux sortes de savants: les spécialistes, qui connaissent tout sur rien,
    et les philosophes, qui ne connaissent rien sur tout.
    George Bernard Shaw

  4. #4
    Expert confirmé
    Bonjour,

    Dans les solutions simples pour résoudre le Retour Chariot qui reste dans le buffer de réception, il y la possibilité d'ajouter une espace à la fin de la chaîne de récupération.
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     printf("Quel âge avez vous ? ");
            scanf("%d ", &ageutilisateur);  // note l'espace juste après le %d, il permet d'enlever le '\n' qui resterait
            printf("Vous avez donc %d ans \n", ageutilisateur);
            char lettre;
            printf("Choisissez une lettre :> ");
            scanf("%c ", &lettre); // note l'espace juste après le %c, ici aussi on enlève le '\n' de trop
            printf("La lettre choisie est :> %c", lettre );
    Attention, ça ne fonctionne que si l'opérateur tape exactement ce que l'on attend de lui (par exemple une simple entier suivi de CR si on attend un entier, ...) Pour les cas plus complexes, tu verras plus tard.

  5. #5
    Expert éminent sénior
    Citation Envoyé par sambia39 Voir le message
    Attention, l'erreur n'est pas simplement due au fait que scanf doit avoir une entrée formatée avec exactitude/ s'assurer que la saisie effectuée au clavier par l'utilisateur devra être la même que celle spécifiée dans le format. Non ; c'est juste que la fonction scanf n'est absolument pas adaptée pour des entiers et où des nombres à virgule flottante à partir d'une d'entrée car, l'entrée en question peut contenir des nombres non représentables par le type d'argument voulu ou format attendu et c'est là où est réellement le problème et la difficulté de scanf.
    Pas ici. Ici il attend un entier, il tape "30" tout est ok. Il n'y a absolument aucun souci de nombre "non représentable".

    PS: Tu devrais écrire encore plus gros qu'on te voit mieux...

    Citation Envoyé par dalfab Voir le message
    Dans les solutions simples pour résoudre le Retour Chariot qui reste dans le buffer de réception, il y la possibilité d'ajouter une espace à la fin de la chaîne de récupération.
    Joli !!!
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  6. #6
    Membre chevronné
    Citation Envoyé par Sve@r Voir le message
    Pas ici. Ici il attend un entier, il tape "30" tout est ok. Il n'y a absolument aucun souci de nombre "non représentable".

    PS: Tu devrais écrire encore plus gros qu'on te voit mieux...
    Joli !!!
    Non. Tout n'est pas Ok. Tu ne vois que le développeur qui apprend à utiliser la fonction scanf et non l'utilisateur qui utilise le programme et qui est susceptible d'effectuer une saisie erronée et l'astuce d'un espace après le "%d " est une fausse bonne idée.


    L'espace après "%d " dit à la fonction scanf de faire correspondre l'espace (ou le vide pour la compréhension) à zéro ou plusieurs caractères d'espacement, jusqu'à ce que la correspondance n'aboutisse pas/ échoue. Pour être plus précis scanf va tenter de faire correspondre les caractères d'espacement jusqu'à ce qu'il n'y ait plus de correspondance et en faisant cela, scanf ne retournera que si l'ensemble de la correspondance recherchée est réellement manquante ou que l'on atteint la fin du fichier en d'autres termes (scanf devient bloquante), a force d'attendre, vous allez devoir vider le buffer par vous-même selon votre système d'exploitation soit CTRL+D (GNU/ Linux ou Unix) ou CTRL+C (Windows) donc pas joli.


    PS: pour ce qui est de l'écriture, il doit avoir une erreur de police de caractères.
    Celui qui peut, agit. Celui qui ne peut pas, enseigne.
    Il y a deux sortes de savants: les spécialistes, qui connaissent tout sur rien,
    et les philosophes, qui ne connaissent rien sur tout.
    George Bernard Shaw

  7. #7
    Expert confirmé
    L'espace n'est pas bloquante dans un format de scanf.
    Elle va extraire tous les caractères non affichables (espace,tab, CR, ...) consécutifs quel qu'en soit le nombre, y compris le cas où il y a zéro caractères.

  8. #8
    Expert éminent sénior
    Citation Envoyé par sambia39 Voir le message
    Non. Tout n'est pas Ok. Tu ne vois que le développeur qui apprend à utiliser la fonction scanf et non l'utilisateur qui utilise le programme et qui est susceptible d'effectuer une saisie erronée et l'astuce d'un espace après le "%d " est une fausse bonne idée.
    Mais on n'en n'est pas encore là!!! Là on a un débutant qui balbutie à peine du C depuis 3 jours, qui est tellement stressé d'oublier l'esperluette qu'il écrit dans un commentaire qu''il ne faut pas l'oublier et qui veut juste avoir le plaisir de saisir (correctement, cela va sans dire mais cela va encore mieux en le disant) un truc lui-même dans son propre programme et le voir réapparaitre sur la ligne du dessous. C'est ce que j'ai écrit en disant "ok pour commencer scanf() ça va mais plus tard il faudra l'abandonner". Il est encore super loin du code destiné à gérer un réacteur nucléaire et qui doit répondre à toute une gamme de saisie des plus exotiques tapée par des utilisateurs absorbés par un travail stressant !!!
    Et toi tu lui envoies du *dt = strtol(buffer, &p, 0xA) et du if('\n' != *p && '\0' != *p) (suis certain qu'il ne comprend même pas le pourquoi du '\0') dans un code qui commence par "Attention, le code source ci-dessous est susceptible de comporter des erreurs"
    Super quoi. C'est un truc à le dégoûter définitivement du C voire même de l'informatique !!!

    Citation Envoyé par sambia39 Voir le message
    PS: pour ce qui est de l'écriture, il doit avoir une erreur de police de caractères.
    Ouais enfin on cite ton code on voit de partout "size=+3"...
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  9. #9
    Membre chevronné
    Citation Envoyé par dalfab Voir le message
    L'espace n'est pas bloquante dans un format de scanf.
    Elle va extraire tous les caractères non affichables (espace,tab, CR, ...) consécutifs quel qu'en soit le nombre, y compris le cas où il y a zéro caractères.
    Je pense que vous ne m'avez pas compris et aussi compris pourquoi je parle de scanf bloquante.

    Ici dans le cas actuel la fonction ou du moins votre exemple avec scanf va générer une boucle infinie parce qu'une donnée non-représentable/ou tout simplement caractère non-numérique est présente lors de la recherche d'entier.

    Dans le cas de votre exemple et comme vous le dites, l'espace va extraire tous les caractères non-affichables (espace, tab, CR, ...) consécutifs quel qu'en soit le nombre, y compris le cas où il y a zéro caractère. Sauf que cela va suspendre ou générer une boucle infinie par scanf() qui va attendre que plus de données soient saisies par l'utilisateur(s) et donc à chaque fois que la touche entrée sera appuyée, une nouvelle ligne est envoyée et scanf() va à nouveau boucler en attente d'une correspondance ou la fin du fichier. Le seul moyen de l'arrêter dans le pire des cas va être soit Ctrl-D (sous GNU/Linux ou Unix) ou soit Ctrl-C (sous Windows). Donc, concrètement, ce n'est absolument pas une bonne idée et pas du tout joli.

    Citation Envoyé par Sve@r Voir le message
    Mais on n'en n'est pas encore là!!! Là on a un débutant qui balbutie à peine du C depuis 3 jours, qui est tellement stressé d'oublier l'esperluette qu'il écrit dans un commentaire qu''il ne faut pas l'oublier et qui veut juste avoir le plaisir de saisir (correctement, cela va sans dire mais cela va encore mieux en le disant) un truc lui-même dans son propre programme et le voir réapparaitre sur la ligne du dessous. C'est ce que j'ai écrit en disant "ok pour commencer scanf() ça va mais plus tard il faudra l'abandonner". Il est encore super loin du code destiné à gérer un réacteur nucléaire et qui doit répondre à toute une gamme de saisie des plus exotiques tapée par des utilisateurs absorbés par un travail stressant !!!
    Et toi tu lui envoies du *dt = strtol(buffer, &p, 0xA) et du if('\n' != *p && '\0' != *p) dans un code qui commence par "Attention, le code source ci-dessous est susceptible de comporter des erreurs"
    Super quoi. C'est un truc à le dégoûter définitivement du C voire même de l'informatique !!!
    Je ne suis pas d'accord avec ton raisonnement et tu le prends comme tu veux. Que ça soit un débutant ou programmeur expérimenter tout le monde est concerné à commencer par moi. Le but n'est pas de dégoûter un débutant, mais de comprendre que l'on n'est pas à l'abri d'une erreur ou d'un mauvais emploi d'une fonction dans un contexte précis. Ainsi donc, lors de la phase d'apprentissage le débutant utilisera la fonction en connaissance de cause (lire la documentation et comprendre pourquoi une telle fonction est à éviter ou à utiliser dans un contexte donné).


    La phrase type " Il est encore super loin du code destiné à gérer un réacteur nucléaire" n'est pas une raison valable de dire que ce n'est pas le bon moment car, il n'y a jamais de bon moment.
    Celui qui peut, agit. Celui qui ne peut pas, enseigne.
    Il y a deux sortes de savants: les spécialistes, qui connaissent tout sur rien,
    et les philosophes, qui ne connaissent rien sur tout.
    George Bernard Shaw

  10. #10
    Expert éminent sénior
    Citation Envoyé par sambia39 Voir le message
    La phrase type " Il est encore super loin du code destiné à gérer un réacteur nucléaire" n'est pas une raison valable de dire que ce n'est pas le bon moment car, il n'y a jamais de bon moment.
    Ouais, avec de tels raisonnements on se demande pourquoi on apprend les formules de surfaces des cercles, triangles, carrés et trapèzes aux enfants de 6° On n'a qu'à leur apprendre directement à calculer des intégrales. Et pourquoi apprendre Pythagore qui ne fonctionne que dans les triangles rectangles puisque Al-Kashi fonctionne pour tous les triangles. Et pourquoi apprendre les lois de Newton qui sont fausses au voisinnage du soleil alors que les formules de la relativité générale sont bien plus précises et bien plus exactes même près du soleil...
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  11. #11
    Modérateur

    La phrase type " Il est encore super loin du code destiné à gérer un réacteur nucléaire" n'est pas une raison valable de dire que ce n'est pas le bon moment car, il n'y a jamais de bon moment.
    "You aren't gonna need it" ?