Partage
  • Partager sur Facebook
  • Partager sur Twitter

Fermeture de programme console

    18 juillet 2021 à 2:04:49

    Bonsoir tout le monde. Bon voilà, en fait j'ai installé codeblocks depuis un moment déjà. Et aujourd'hui j'ai voulu écrire un programme dans lequel l'utilisateur doit entrer son nom et son âge. Mais arrivé au moment x, lorsque j'essaie de saisir le nom, la fenêtre se ferme. Comment puis-je faire ?

    PS : c'est un programme console

    • Partager sur Facebook
    • Partager sur Twitter
      18 juillet 2021 à 7:13:53

      Un programme console? Tu fais des std::cin  ?
      Tu es certain de ne pas avoir d'erreur de compilation?
      Un conseil, affiche un message avant la saisie.
      std::cout << "Veuillez entrer votre nom: ";
      • Partager sur Facebook
      • Partager sur Twitter

      Le Tout est souvent plus grand que la somme de ses parties.

        18 juillet 2021 à 12:24:46

        Bonjour,

        Il doit y avoir un problème dans ton code. Ce type de problème est souvent indiqué sous formes de warnings au moment de la compilation.
        Pense à activer l'indication des warnings, ça n'est pas le cas par défaut avec gcc.
        Tu peux aussi poster ton code ici, on pourra t'aider.

        • Partager sur Facebook
        • Partager sur Twitter

        En recherche d'emploi.

          18 juillet 2021 à 13:27:25

          Salut,

          Pourrais tu nous montrer ton code, pour que l'on puisse déjà se faire une idée?

          En attendant, il faut savoir que, si tu as utilisé std::cin pour l'introduction du nom de l'utilisateur, tu vas avoir un problème majeur: "l'extraction" des information va s'arrêter juste avant le premier caractère qui ne soit ni un chiffre (0...9), ni une lettre (a...z / A... Z) ni un signe de ponctuation.

          Ou, plutôt, l'extraction avec l'opérateur >> de std::cin va s'arrêter juste avant le premier caractère qui sera soit un espace ' ', soit une tabulation '\t', soit un retour à la ligne '\n'.

          (pour être tout à  fait précis, l'opérateur >> de std::cin va s'arrêter au premier caractère qui ne correspond pas au type de donnée qu'il essaye d'extraire: dans le cas d'un entier, il s'arrêtera donc juste avant le premier caractère qui ne sera pas un chiffre, par exemple)

          Mais cela veut donc dire que ce caractère particulier restera dans le "buffer" de saisie de ton entrée standard (de ton clavier), et qu'il sera prêt à être extrait "séparément".

          Le truc, c'est que le retour à la ligne '\n' symbolise l'appui sur la touche "enter" du clavier et qu'il est -- justement -- interprété par std:: cin comme un signal signifiant "prêt pour extraction" ou, si tu préfère: "il y a des données dans le buffer, tu peux essayer de les extraire".

          Commences tu à voir où se situe le problème?  Allez, je vais te l'expliquer:

          Quand l'utilisateur va introduire son nom, il va l'écrire au clavier puis ... appuyer sur la touche <enter> pour valider, ce qui va placer quelque chose comme "koala01\n" dans le buffer de std::cin.

          De ton coté, quand tu vas demander l'extraction d'une chaine de caractères sous la forme de std::cin>>sonNom; le dernier caractère qui sera extrait avec cette instruction sera, selon mon exemple, le 1 de "koala01" et, devine ce qui restera dans le buffer de std::cin?

          Bien vu... il restera le '\n' correspondant à l'appui sur la touche <enter>.

          Seulement, je présume que ton code doit ressembler à  quelque chose comme

          #include <iostream>
          #include <string>
          
          int main(){
              std::string sonNom;
              std::cout << "introduisez votre nom :";
              std::cin >> sonNom;
              int sonAge;
              std::cout << "introduisez votre age :";
              std::cin >> sonAge;
              /* d'autres choses à faire par la suite */
          }

          Et du coup, que va-t-il se passer par la suite à ton avis,  sachant que l'instruction std::cin>>sonNom a réussi ?

          Bien vu: l'instruction std::cout<< "introduisez votre age :"; s'exécute "normalement" et l'instruction std::cin>> sonAge; est sensée mettre le programme "en attente d'une nouvelle introduction de la part de l'utilisateur".

          Oui, mais ... il y a toujours ce fameux '\n' qui correspond à  l'appui sur la touche <enter> qui traine dans le buffer de std::cin. Et je te rappelle que c'est ce caractère particulier qui donne le signal de "Prêt pour extraction" à std::cin.

          Et, du coup, std::cin va ... faire ce qu'elle est sensée faire à  ce moment bien précis, à savoir: tenter d'extraire sonAge du buffer. Et, comme il n'y a aucun caractère dans le buffer avant le '\n' qui puisse servir à l'extraction d'une valeur numérique (parce que std::cin s'attend, à ce moment particulier, à devoir extraire des chiffres compris entre 0 et 9 inclus), std::cin va se mettre dans un état invalide qui signifiera "j'ai pas su effectuer la dernière extraction demandée".

          Et, à  partir de là, si tu essayes d'extraire encore d'autres données dans les instructions qui prennent la place du commentaire "/* d'autres choses à faire par la suite */", ben, tu es foutu parce que std::cin n'essayera plus de s'arrêter ni d'extraire quoi que ce soit tant que nous ne l'aurons pas remis dans un état "valide", dans un état qui pourrait signifier "en attente d'extraction".

          De plus, il ne servira à  rien d'essayer de remettre cin dans cet état valide dans qu'il restera "des crasses" (comme, je sais pas, moi, ce '\n' qui a déjà foutu la m...e) dans le buffer de cin, parce que, autrement, la prochaine fois que l'on va essayer d'extraire des informations de cin, ce seront ces crasses qui vont passer en premier et l'extraction suivante a donc de fortes chances de ne pas bien se passer.  Ce qu'il faut faire à ce moment là, avant d'essayer de remettre cin dans un état "valide", c'est "purger" le buffer de toutes les crasses qui peuvent y rester.

          Bon, maintenant que tu as -- je l'espère -- une vague idée de ce qui se passe avec ton programme, on peut essayer de résoudre ce genre de problème.

          Alors, pour commencer, je vais te donner un conseil qui devrait s'appliquer de manière systématique, à tous tes programmes, quel que soit leur but: méfie toi comme de la peste de toutes les données que tu n'a pas pu falsifier par toi-même au niveau du code du programme.

          Ou, si tu préfères: considère systématiquement TOUT ce qui vient de l'extérieur du programme (parce que c'est récupéré par internet, lu dans un fichier ou, plus encore parce que ca a été introduit par l'utilisateur) comme suspect.

          Tu ne peux en AUCUN CAS faire confiance à une information qui vient "de l'extérieur". Et c'est d'autant plus vrai si l'information doit être fournie par l'utilisateur, parce que, par principe, l'utilisateur est un imbécile distrait qui attend la moindre occasion de faire une connerie.

          Ainsi, si tu demandes à l'utilisateur d'introduire une chaine de caractères puis que tu lui demande d'introduire un nombre, il n'y a absolument rien d'étonnant à  ce qu'il essaye d'introduire "salut" à la place du nombre demandé.

          Et, du coup, il va falloir rajouter des étapes pour s'assurer que les informations que l'on obtient sont "cohérentes" par rapport à ce que l'on attend.  Il faut en effet:

          1. extraire TOUT ce qui a été introduit (en profiter pour supprimer ce '\n' qui nous fait ch..er)
          2. valider les informations reçues pour s'assurer qu'elles correspondent à ce que l'on attend
          3. engueuler l'utilisateur et lui demander de recommencer (ou lancer une exception si l'information vient "d'ailleurs") si cela ne correspond pas
          4. passer à  la suite si on a bel et bien reçu des informations "cohérentes" avec ce à quoi on s'attendait

          Si tu respecte systématiquement ces quatre étapes, tu devrais être en mesure d'éviter la très grosse majorité des problèmes ;)

          Voilà pour la "théorie", il est temps de passer à  la pratique.

          Alors, le seul type de donnée qui puisse effectivement se "satisfaire" de n'importe quelle connerie introduite par l'utilisateur, c'est ... la chaine de caractères. Donc, l'extraction "de base", de la première étape devra systématiquement se faire à l'aide ... d'une chaine de caractères.

          Et, pour être sur de récupérer TOUT ce qui a été introduit, on utilisera l'instruction std::getline(flux, chaine temporaire) sous une forme proche de

          std::string temp;
          
          std::cout<<"introduisez votre nom :";
          std::getline(cin, temp);
          /* temp est sensé contenir un nom ici */
          
          std::cout<<"introduisez votre age :";
          std::getline(cin, temp);
          /* temp est sensé contenir un age ici, mais il ne faudra pas
           * s'étonner si ce n'est pas le cas
           */

          L'énorme avantage de std::getline, c'est qu'il va, justement, supprimer ce '\n' qui nous a fait chier plus haut et que, de plus, si le nom est "de la Poupée qui Tousse", nous pourrons récupérer tous les mot dans une seule chaine de caractères, ce qui est très intéressant dans certains cas, tu ne crois pas ?

          Voilà donc la première étape franchie.  Pour le nom, il n'y a pas vraiment moyen de vérifier la validité (à part, peut être, s'assurer qu'il n'y a pas de chiffres??? Et encore), donc, on peut copier le résultat obtenu directement dans la variable qui servira à  cet effet, et passer directement à  l'étape numéro quatre.

          Dans le cas de l'âge, la validation consistera en

          1. s'assurer que l'utilisateur a introduit des chiffres et
          2. s'assurer qu'il n'y a rien d'autre que des chiffres dans la réponse

          "A l'époque", avant l'arrivée de C++11 (qui est arrivé en ... 2011, si tu te poses la question :D ), il fallait jouer avec des std::stringstream pour s'assurer que l'information que l'on avait obtenu correspondait au type de donnée que l'on attendait.

          Cette manière de faire est d'ailleurs toujours possible, mais bon, depuis 2011, nous disposons de moyens beaucoup plus efficace au travers de fonctions comme std::stoi (pour les entiers signés) et toute la série (pour  les autres types).

          L'énorme avantage de cette fonction (en fait, de toutes les fonctions de la série), c'est que l'on peut lui transmettre, comme deuxième paramètre, un pointeur optionnel (on n'est pas obligé de le lui transmettre si on n'en a pas envie) sur une variable de type "entier non signé" dont la valeur (s'il est fourni) correspondra après l'appel de la fonction, au nombre de caractères qui ont effectivement été convertis.

          De plus, cette fonction va lancer une exception si les premiers caractères qu'elle rencontrent ne savent pas être convertis.

          Nous pourrons donc "valider" l'introduction de l'utilisateur à l'aide d'un code proche de

          int sonAge;
          size_t pos;
          try{
              sonAge = std::stoi(temp, & pos);
          }catch(std::exception & e){
              /* on arrive ici si l'utilisateur a introduit "salut" */
          }

          Et ca, ca va nous permettre de valider la réponse de l'utilisateur. Car l'utilisateur risque, en fait, de nous mettre dans trois situations "de base"

          • soit, il répond à la question qui lui est posée en fournissant la réponse que l'on attend (ex : "49") et "tout va bien"
          • soit, il répond à la question qui lui est posée, mais on va dire gentiment que son doigt a glissé, et on se retrouve avec des caractères supplémentaires après la réponse (ex: "49 ans"), et ce n'est pas ce que l'on veut (*)
          • soit, enfin, il nous répond carrément à coté de la plaque, à moins qu'il croie vraiment parler à  un humain (ex: "salut, j'ai 49 ans")  (*) et ce n'est clairement pas ce que l'on veut non plus...

          Tu l'auras compris, ce que nous on souhaite, c'est qu'il réponde à notre question sans rien y ajouter ( "49" ), les deux autres situations méritant qu'on engueule l'utilisateur ou, à  tout le moins, qu'on lui précise ce que l'on attend de lui (uniquement des chiffres) et, bien sur, de faire "un nouvel essai".

          La deuxième situation va extraire "tout ce qui est possible" dans sonAge et donner une valeur à pos qui ne correspond pas à la taille de la chaine de caractères.

          Quant à  la troisième situation, elle lancera une exception de type std::invalid_argument, ce qui justifie la présence des blocs try ... catch.

          (*) Après, bien sur, tout  cela va sans doute dépendre de l'empathie que l'on éprouve pour l'utilisateur, car certains pourraient tenir "à leur six mois"  ( "18.5") ou même écrire leur age en toute lettres "vingt-trois".  Faut il vraiment considérer ces réponses comme invalides, ou doit on prévoir "un système" qui nous permette de les accepter? Ca reste un sujet à  débat :D

          Nous sommes donc désormais prêts à prendre la troisième étape en compte, à savoir: engueuler l'utilisateur qui n'a pas répondu correctement et lui demander de recommencer.

          Du coup, nous modifierions sans doute intelligemment notre code de base pour lui donner une forme qui ressemble à

          std::string temp;  /* la chaine temporaire de récupération
                                                       * (si elle n'a pas été déclarée plus haut)
                                                       */
          int sonAge;        // la donnée que l'on recherche
          bool again{false}; // faut-il recommencer? (espérons que non)
          do{
              std::cout<<"Veuillez introduire votre age :";
              std::getline(std::cin, temp);
              try{
          
                  size_t pos;        // la taille de l'extraction
                  sonAge = std::stoi(temp, & pos);
                  if(pos != temp.size()){ // si la position d'extraction
                                         // ne correspond pas à  la
                                         // taille de la chaine
                      again = true;      // il faut recommencer
                  }
                  /* on pourrait prévoir d'autres vérification */
              }catch(std::exception & e){
                  /* il a répondu n'importe quoi, il faut donc recommencer
                   */
                  again = true;
              }
              if(again-{   // s'il faut recommencer
                  /* on "engueule" l'utilisateur en lui demandant
                   * de se concentrer sur ce qu'il fait
                   */
                  std::cout<<"veuillez n'introduire que des chiffres\n";
              }
          } while(again);
          /* arrivés ici, nous pouvons utiliser sonAge pour la suite
           * des événements
           */

          Bon, tout ce qui précède, c'est pour le cas (très vraisemblable) où tu aurais un problème avec ton code.

          On ne peut cependant pas ignorer la possibilité qu'il y ait "simplement" un problème de configuration de code::blocks.

          En effet, lorsque tu clique sur "run" (ou sur "build and run"), Code::Blocks va -- comme n'importe quel autre EDI, d'ailleurs -- lancer une console "bien à lui", sur laquelle il a "un peu plus la main" que la console "normalement utilisée" sous windows.

          De cette manière il pourra -- selon le choix de l'utilisateur -- décider d'imposer une "pause finale" entre la fin de l'exécution du programme (autrement dit, lorsque l'exécution arrive au "return 0;" de la fonction main) et la fermeture de la console.

          Il est important de comprendre que cette pause ne fait pas partie du programme, que c'est juste une "facilité" offerte par le fait que l'on exécute le programme au travers de l'EDI.

          Si bien que, une fois que tu as un programme fonctionnel et que tu as quitté Code::Blocks (ou n'importe quel autre EDI), ce sera windows qui va prendre le relais lorsque tu double cliqueras sur le programme et qui va travailler en trois phases:

          • lancer une console à lui
          • exécuter le programme (et garder la console ouverte tant que le programme est en cours d'exécution)
          • fermer la console lorsque le programme lui aura signalé la fin de son exécution

          Si tu veux qu'une pause soit exécutée entre la dernière instruction "intéressante" de ton programme et la feremeture de la console, tu devras donc mettre en place "quelque chose" pour attendre "un signe de la part de l'utilisateur" avant de mettre effectivement fin à l'exécution.

          Cependant, ce n'est que très rarement une bonne idée de le faire, car, en dehors des quelques programmes "de tests" que tu vas écrire lors de ton apprentissage, tu te rendras rapidement compte que, ce que tu attends de la part de ton programme une fois qu'il a fini son taf, ben, c'est qu'il s'arrête (et que la console se referme) ;)

          En attendant, revenons en donc à la pause que l'on peut demander à Code::Blocks d'imposer entre la fin du programme et la fermeture de la console.

          Comme c'est un choix que Code::Blocks laisse à l'utilisateur, il y a donc "quelque part" une option qui permet d'activer (ou au contraire de désactiver) cette facilité.

          Encore une fois, c'est une possibilité que tous les EDI vont donner à l'utilisateur.  Simplement, ce ne sera peut-etre (très vraisemblablement, plutôt) pas au même endroit sous QtCreator, sous VisualStudio ou sous Eclipse ;).

          Sous Code::blocks, donc, quand tu as un projet ouvert, tu peux aller dans le menu "Projects -> Properties", et choisir l'onglet "Build Targets".

          C'est dans cet onglet que se trouvent les deux cibles de compilation "classiques" que sont "Debug" et "Release" que tu vois d'ailleurs (et que tu peux sélectionner séparément) à l'extrême gauche de la fenêtre.

          Sur la droite de cette fenêtre, tu as un rappel des principales caractéristiques de chaque cible, dont une case à cocher dont le texte est "Pause when execution ends" (que l'on pourrait traduire par "faire un pause à la fin du programme").

          Si la case est cochée (c'est normalement le cas par défaut), une pause sera exécutée.  Et, bien sur, si la case n'est pas cochée, la console se fermera directement à  la fin du programme ;)

          • Partager sur Facebook
          • Partager sur Twitter
          Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
            27 septembre 2021 à 2:19:08

            koala01 merci pour la réponse.

            Pourrait tu m'éclairer sur std ?? Qu'est ce que c'est exactement. Je vois certains en mettre avant cin ou cout, mais je ne vois pas ce que ça représente exactement.

            -
            Edité par DaveNabia 27 septembre 2021 à 2:22:03

            • Partager sur Facebook
            • Partager sur Twitter
              27 septembre 2021 à 2:48:09

              std est l'espace de noms ("namespace") dans lequel se trouvent toutes les fonctionnalités (classes, fonctions libres, algorithmes et autres) fournis par la bilblithèque standard.

              Si certains se contentent d'écrire cin, cout ou encore string, c'est sans doute parce qu'ils ont placé une directive using namespace std; "quelque part" dans leur code, avant de faire appel à ces fonctionnalités.

              Le truc, c'est que cette directive a été ajoutée au tout début de la normalisation du langage, dans les années nonente, lorsqu'il a -- justement -- été décidé de placer l'ensemble des fonctionnalités de la bibliothèque standard dans l'espace de noms std.

              Cette décision a été prise pour une raison bien simple: la bibliothèque standard existait déjà et il existait également une grande base de code qui utilisait la bibliothèque standard.  Il était donc "hors de question" d'apporter une modification dans ce qui était utilisé qui aurait obligé des milliers de développeurs à reparcourir des milliers -- voir des millions -- de lignes de code pour corriger un problème idiot: le fait qu'il ne fallait plus se contenter d'écrire cin, mais qu'il fallait désormais écrire std::cin. (et, bien sur, il en va de même avec tout le reste ;) )

              Il a donc été décidé de fournir une mécanique qui contourne le problème au travers de cette fameuse directive using namespace std; car il "suffisait" de la rajouter dans un des fichiers d'en-têtes qui étaient systématiquement inclus (au pire, de manière indirecte) partout -- comme le fameux config.h -- pour que la directive soit appliquée ... partout.

              Seulement, ce qu'il faut comprendre, c'est que cette diretive a été prévue pour éviter aux développeurs d'avoir à modifier "en profondeur" du code existant au moment où l'implémentation de la bibliothèque standard utilisée commençait effectivement à implémenter les nouvelles règles.

              On peut donc -- typiquement -- considérer que cette directive ne devrait -- en théorie du moins -- n'être utilisée que... dans du code qui date d'avant la période où les fonctionnalités de la bibliothèque standard se sont retrouvées dans l'espace de noms std.  Et ca, ca remonte à il y a près de trente ans :p

              On ne peut donc décemment pas estimer que du code que tu vas écrire aujourd'hui date de cette période "qui date de la préhistoire" ;).  Et par conséquent, on peut décemment estimer qu'il n'est "pas correct" d'utiliser cette directive dans du code qui sera écrit aujourd'hui.

              Et c'est d'autant plus vrai que, même si cette directive a permis à du code qui compilait avant la normalisation de continuer à compiler après le passage dans l'espace de noms std, il n'en demeure pas moins que son utilisation présente énormément de problème, car, elle a -- justement -- pour effet de "sortir" les fonctionnalités de la bibliothèque standard de la "boite de rangement" dans laquelle on les a si correctement rangées; de "renverser la boite sur la table", en quelque sorte ;)

              • Partager sur Facebook
              • Partager sur Twitter
              Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait

              Fermeture de programme console

              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
              • Editeur
              • Markdown