Nicht erwarteter Datentyp bei der Eingabe, while Schleife (Konsolenanwendung)



  • Hallo.

    Das folgende Programm soll die Wurzel aus einer Zahl ziehen die der Benutzer eingibt. Alles funzt wunderbar solange man einen doubleWert eingibt.
    Sobald man aber etwas anderes als einen doubleWert eingibt (zB Buchstaben oder eine Zahl mit Beistrich statt Punkt) bleibt das Programm in der elseVerzweigung stecken ohne eine neue Eingabe abzufragen.

    Wie kann ich dem Benutzer die M├Âglichkeit geben in der Eingabe einen Fehler zu machen? If/else scheint iwie zu wenig.

    Ich habe xxxVariationen durch probiert, ohne Erfolg.
    Bin am verzweifeln ­čś×

    #include <iostream>
    #include <cmath>
    
    using namespace std;
    
    int main()
    {
    
    	while ( true )  
    	{
    
      		cout << "Von welcher Zahl soll die Wurzel gezogen werden? ";
    		double eingabe;
    		cin >> eingabe;
    
    		if (eingabe > 0)
    		{
    		double wurzel = sqrt(eingabe);
    
    		cout << "Die Wurzel von " << eingabe << " = " << wurzel << endl;
    		}
    
    		else
    		{
    		cout << "Negativen Zahlen, Buchstaben, Sonderzeichen und die Zahl ""0"" sind verboten!" << endl;
    		}
    
    	}
    
    	return 0;
    }
    


  • Mit

    std::cin.clear()
    

    solltest du die Fehleingabe l├Âschen k├Ânnen. Um auch den Rest (wie das Enter oder eventuelle Buchstaben nach der Zahl) aus dem Eingabepuffer zu l├Âschen, benutzt du am besten

    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n')
    

    Weiterhin kannst du mit

    if(std::cin.fail()) {...}
    

    oder

    if(!std::cin.good()) {...}
    

    Die Korrektheit der Eingabe ├╝berpr├╝fen.



  • Dankesch├Ân ­čÖé

    hab

    cin.clear();
    cin.ignore(10000,'\n');
    

    nach der cin >> eingabe; eingef├╝gt und es funzt.

    Jetzt muss ich nur noch herausfinden warum^^



  • Do7 schrieb:

    cin.clear();
    cin.ignore(10000,'\n');
    

    nach der cin >> eingabe; eingef├╝gt und es funzt.

    Nein, das ist ung├╝nstig. Wenn du Daten aus einem Stream extrahierst und bedingungslos alle weiteren Daten darin verwirfst, kann es sein, dass folgende Stream Operationen nicht das machen , was du erwartest.

    Skylac06 schrieb:

    Weiterhin kannst du mit

    if(std::cin.fail()) {...}
    

    oder

    if(!std::cin.good()) {...}
    

    Die Korrektheit der Eingabe ├╝berpr├╝fen.

    Besser ist es hier generall auf Fehler zu pr├╝fen:

    while ( !(std::cin >> x) )
    {
        // ... Fehlerfall
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
    

    Dort werden alle Fehlerf├Ąlle abgefangen, nicht nur das Fail Bit, sondern auch das Bad-Bit und das EOF-Bit.

    Du kannst dir einen folgenderma├čen 8vereinfacht) vorstellen:
    Das ist eine Liste von Bytes mit unbekannter L├Ąnge, bei usereingaben ist diese "Liste" meistens noch gar nicht vorhanden, wel noch nix eingegeben wurde. Dann blckiert der Stream bis er etwas hat, womit er arbeiten kann. Dort sagst du "Interpretier mal die n├Ąchsten Daten als double und gib mir die" und dann versucht der Stream aus den aktuellen Daten einen Double zu machen. Wenn das klappt wird ein interner Cursor weitergeschoben, n├Ąmlich auf die Position nach dem double. Die Daten dieses Doubles werden verworfen,weil sie ja nicht mehr interessant sind.
    Wenn der Versuch dieses Lesens allerdings fehlschl├Ągt (z.B. weil da Buchstaben statt Zahlen stehen), dann wandert der interne Cursor nicht weiter und eswird ein Fehlerstatus gesetzt, was dem User anzeigt, dass da etwas schiefgelaufen ist. Solange dieser Status gesetzt ist werden s├Ąmtliche nachfolgenden Operationen geblockt, d.h. auch mit diesem Fehler beantwortet. Setzt man diesen Fehlerstatus zur├╝ck kann man wieder wie gewohnt lesen, aber die Zeichen, die vorher zum Fehler gef├╝hrt haben stehen nat├╝rlich immer noch in dem Puffer/Liste in dem Stream, was dazu f├╝hrt, dass sie bei der n├Ąchsten Stream Operation nat├╝rlich nochmal gelesen werden. Bei solchen kleinen Programmen ist das nat├╝rlich nervig, aber bei gr├Â├čeren Dingen kann da coole Dinge mit machen. Z.B. finde ich, dass es mit C++ relativ easy ist einen parser zu schreiben, mit Java darf ich s├Ąmtliche parselogik immer komplett selbst neu schreiben, was ultra nervig und schwierig ist.



  • std::cin.clear() setzt den Fehlerstatus, in dem sich std::cin nach einer Fehleingabe befindet, wieder zur├╝ck.
    Und std::cin.ignore() ignoriert, wie der Name schon sagt, Zeichen. Der erste Parameter gibt an, wie viele Zeichen ├╝bersprungen werden sollen und der zweite gibt an, welches Zeichen daf├╝r sorgt, dass du wieder Eingaben durchf├╝hren kannst. Da nach einer Eingabe mit std::cin immer noch ein Enter im Eingabepuffer ist, l├Âscht das alle Zeichen, die nicht mehr in die Variable "passten", aus dem Puffer bis zu dem '\n'.
    Du kannst, wenn du willst, ja mal std::cin.ignore(20) ausprobieren. Dann musst du erst 20 Zeichen eingeben, bevor du wieder etwas eingeben kannst.
    Ansonsten kannst du auch mal std::cin.ignore(10000, 'x') ausprobieren. Dann musst du entweder 10000 Zeichen eingeben oder x dr├╝cken, abz├╝glich der Zeichen, die noch im Eingabepuffer waren. Wenn du in der Eingabe bereits x hattest, dann wird das auch ausgel├Âst und du kannst danach wieder normal deine Eingaben machen, au├čer danach kommen noch mehr Zeichen, dann hast du wieder das gleiche Problem wie zuvor.
    Bei std::cin hast du jedoch den Vorteil, dass das letzte Zeichen '\n' ist und dir das damit nicht passieren kann.
    Ich hoffe ich habe mich einigerma├čen verst├Ąndlich ausgedr├╝ckt. ­čÖé

    Edit: paar Sekunden langsamer. ­čÖä

    Skym0sh0 schrieb:

    Besser ist es hier generall auf Fehler zu pr├╝fen:

    while ( !(std::cin >> x) )
    {
        // ... Fehlerfall
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
    

    Das funktioniert dann aber nicht f├╝r Eingaben wie "12abc", oder?
    Weil das erste mal funktioniert es, die zweite Eingabe wird dadurch jedoch ├╝bersprungen. Danach kann man dann nat├╝rlich erneut eine Eingabe machen, aber dennoch bleibt es unsch├Ân.



  • Vielen vielen Dank f├╝r eure Beitr├Ąge!

    Hab es so gel├Âst. Statt ignore() verwende ich sync(). Dadurch entf├Ąllt das einbinde von <limits>.

    #include <iostream>
    #include <cmath>
    
    using namespace std;
    
    int main()
    {
        while ( true )
        {
            cout << "Von welcher Zahl soll die Wurzel gezogen werden? ";
    
            double eingabe;
            cin >> eingabe;
            cin.sync();
    
            if (cin.good() & eingabe >= 0)
            {
            double wurzel = sqrt(eingabe);
            cout << "Die Wurzel von " << eingabe << " = " << wurzel << endl;
            }
    
            if (cin.good() & eingabe < 0)
            {
    		cout << "Negativen Zahlen sind verboten!" << endl;
    		}
    
    		if (cin.fail())
            {
    		cin.clear();
    		cin.sync();
    
    		cout << "Buchstaben und Sonderzeichen sind verboten!" << endl;
            }
    
            if (!cin.good())
            {
    		cin.clear();
    		cin.sync();
    
    		cout << "fataler Fehler oder EOF" << endl;
            }
        }
    
        return 0;
    }
    

    Skym0sh0 schrieb:

    Nein, das ist ung├╝nstig. Wenn du Daten aus einem Stream extrahierst und bedingungslos alle weiteren Daten darin verwirfst, kann es sein, dass folgende Stream Operationen nicht das machen , was du erwartest.

    Das glaub ich erst wenn ichs seh :p
    Wenn good, dann macht ignore() wegen des 2ten Arguments gar nichts.
    und wenn fail, dann macht ignore() was es in diesem Fall machen soll.



  • Do7 schrieb:

    Skym0sh0 schrieb:

    Nein, das ist ung├╝nstig. Wenn du Daten aus einem Stream extrahierst und bedingungslos alle weiteren Daten darin verwirfst, kann es sein, dass folgende Stream Operationen nicht das machen , was du erwartest.

    Das glaub ich erst wenn ichs seh :p
    Wenn good, dann macht ignore() wegen des 2ten Arguments gar nichts.
    und wenn fail, dann macht ignore() was es in diesem Fall machen soll.

    Dann gib mal mehrere Zahlen in einer Zeile ein, und wenn dazwischen dann irgendein Buchstabe ist, dann wirst du sehen, was ich meine.



  • In den Zeilen 16 und 22 musst du den logischen Operator && verwenden, um zwei Bedingungen zu verkn├╝pfen.
    Und ich w├╝rde dir empfehlen nicht so viele einzelne if-Verzweigungen einzubauen und lieber auf else if umsteigen. Die allerletzte Bedingung kannst du hier auch einfach mit else schreiben.
    Also:

    #include <iostream>
    #include <cmath>
    
    using namespace std;
    
    int main()
    {
        while ( true )
        {
            cout << "Von welcher Zahl soll die Wurzel gezogen werden? ";
    
            double eingabe;
            cin >> eingabe;
            cin.sync();
    
            if (cin.good() && eingabe >= 0)							// Operator && statt &
            {
    	        double wurzel = sqrt(eingabe);
    	        cout << "Die Wurzel von " << eingabe << " = " << wurzel << endl;
            }
            else if (cin.good() && eingabe < 0)						// else if statt if
            {
         	   cout << "Negativen Zahlen sind verboten!" << endl;
            }
            else if (cin.fail())
            {
    	        cin.clear();
    	        cin.sync();
    
    	        cout << "Buchstaben und Sonderzeichen sind verboten!" << endl;
            }
            else                                                    // else statt noch ein if
            {
    	        cin.clear();
    	        cin.sync();
    
    	        cout << "fataler Fehler oder EOF" << endl;
            }
        }
    
        return 0;
    }
    

    Skym0sh0, ich wei├č ehrlich gesagt nicht, was genau du meinst. Ich konnte bisher noch nie irgendein Problem dabei feststellen.
    Wenn man besagtes Beispiel in das Beispiel oben einsetzt, dann wird die Zahl einfach mit dem ersten Buchstaben, der vorkommt, abgeschnitten und die Zahlen dahinter verfallen.
    Ich habe auch noch einmal alle std::cin.sync() Anweisungen durch std::cin.ignore(std::numeric_limitsstd::streamsize::max()) ersetzt und es kommt das gleiche Ergebnis dabei heraus: keine Probleme.
    Ich habe alle M├Âglichkeiten, die mir eingefallen sind, ausprobiert.

    Weiterhin h├Ątte ich auch noch die Frage, ob man tats├Ąchlich std::cin.sync() der dem std::cin.ignore() vorziehen sollte. Ich meine n├Ąmlich mal das Gegenteil gelesen zu haben.



  • @Skym0sh0
    Hast recht^^. Wenn man eine Zahl mit dahinter einem Buchstaben eingibt, dann wird die Eingabe nicht als fail-bit angesehen! Die Zahl wird gespeichert und der Buchstabe wird verworfen. Das f├╝hrt dazu das die Eingabe angenommen wird. Was ja eigentlich nicht sein kann da zB 123c kein double ist.

    Wenn ich dann ein if ( cin.fail()) erwarte bei der Eingabe zB 123c, kann ich lange warten^^.


Log in to reply