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^^.


Anmelden zum Antworten