Programmabsturz bei Buchstabeneingabe in Integerfeld



  • Deine Zeilen 1 und 2: Ich glaube, du hast das Prinzip nicht verstanden. Der operator>> ist ein ganz normaler Funktionsaufruf. Der hat einen Rückgabewert. Diesen kann man prüfen. Das ist es, was mit dem if(cin >> wert) gemeint ist. Der operator>> wird mit den Argumenten cin und wert aufgerufen* und liefert einen Wert zurück, der dann vom if ausgewertet wird.

    Das bedeutet, du hast hier in Zeile 1 einen Aufruf der Einlesefunktion, deren Rückgabewert verworfen wird (aber choice wird natürlich trotzdem der gelesene Wert zugewiesen. Der Rückgabewert ist nicht der gelesene Wert, sondern eine Referenz auf den Eingabestream selbst) und in der nächsten Zeile noch eine Einlesefunktion, wobei dieses Mal die Rückgabe ausgewertet wird. Dies ist der Grund, wieso du derzeit die Eingabe zwei mal machen musst.

    heißt das es reicht

    if (cin >> choice){....}
    

    zu schreiben und der wert wird eingelesen und bei choice gespeichert? prüft der dann wenn die eingabe eine int-zahl ist dann ist sie wahr?
    Wenn ja wie macht die Funktion dann bei char?
    Mit dem fettgedrucktem meinst du verworfen weil in der nächsten zeile einfach "darüber geschrieben" wird?
    Habe das Problem bei meinen Aufgaben auch immer weil ich davon ausgegangen bin wenn ich prüfe ob die eingabe von z.b. ein int wert von 1-9 ist dann gehe ich beim rest davon aus das es fehlerhaft ist aber so funktioniert das halt nicht 😞



  • Ja, ich weiß, dass Goto im Spaghetticode enden kann, deshalb sind das bei mir auch wenige Ausnahmen wo ich's verwende und nur winzige Schritte (ein paar Zeilen nach oben). Nicht "die feine englische..." =I

    Also, wenn die Abfrage nur ins if packe, dann funktioniert das!

    if (cin >> choice){....}
    

    reicht also.

    Das freut mich! Noch eine Frage - und zwar: wie kann ich ihm klar machen, dass er maximal ein Zeichen akzeptieren darf und nicht bei mehreren Zeichen alle nacheinander abarbeitet?

    Bin gerade etwas in Eile, ich beschäftige mich später näher mit deinen Hinweisen, SeppJ!

    Danke, soweit! 🙂



  • Vermutlich würde man `std::cin.ignore(numeric_limits<streamsize>::max());[c] nach jedem Lesen aufrufen. D.h man liest ein Zeichen und schmeisst den Rest weg.

    Zumindestens, wenn du den Rest, den der User eingegeben hat wirklich nicht akzeptieren und verarbeiten willst.

    Ich weiß aber gerade gar nicht, ob operator>> failtbit setzt, wenn man sozusagen "zuviel" eingibt. Hört sich aber unlogisch. Kanns leider gerade nicht prüfen, weil Javazwang.

    [c]goto` ist für dich auch wenns "nur ein paar Zeilen weiter hoch" springt trotzdem einfach nicht akzeptabel, weil du anscheinend die Alernative schlicht weg nicht kennst.

    Während du C++ lernst solltest du einfach vergessen, dass goto existiert und eine Lösung, die es verwendet, nicht als solche akzeptieren.



  • Man, ich habs echt drauf mit den Tags.


  • Mod

    Mvstylez schrieb:

    heißt das es reicht

    if (cin >> choice){....}
    

    zu schreiben und der wert wird eingelesen und bei choice gespeichert? prüft der dann wenn die eingabe eine int-zahl ist dann ist sie wahr?
    Wenn ja wie macht die Funktion dann bei char?
    Mit dem fettgedrucktem meinst du verworfen weil in der nächsten zeile einfach "darüber geschrieben" wird?
    Habe das Problem bei meinen Aufgaben auch immer weil ich davon ausgegangen bin wenn ich prüfe ob die eingabe von z.b. ein int wert von 1-9 ist dann gehe ich beim rest davon aus das es fehlerhaft ist aber so funktioniert das halt nicht 😞

    Der Rückgabewert der (allermeisten) Lesefunktionen ist der Stream selbst (bzw. eine Referenz darauf, da Streams nicht kopiert werden können). Das heißt cin >> irgendwas gibt cin zurück. Nur deshalb ist es möglich, solche Aktionen aneinander zu hängen:

    cin >> irgendwas >> irgendwas_anderes
    

    Zuerst wird cin >> irgendwas ausgewertet, das gibt cin zurück. Es bleibt also cin >> irgendwas_anderes übrig und das ist wieder ein gültiger Aufruf einer der Lesefunktionen und kann durchgeführt werden. Diese gibt dann wieder cin zurück.
    Aber was bringt das einem, wenn man den Stream selbst als Rückgabewert hat? Nun, man könnte natürlich die Funktionen aufrufen, die testen, ob ein Stream in einem Fehlerzustand ist, zum Beispiel:

    if((cin >> irgendwas).fail()) // ...
    

    Wieder wird zuerst cin >> irgendwas ausgewertet, das gibt wieder cin zurück, auf dem dann die fail -Funktion aufgerufen wird, die zurück gibt, ob der Stream im fail-Zustand ist. Aber die Streams haben noch mehr Funktionen, unter anderem eine automatische Konvertierung in bool'schen Ausdrücken*. Diese ergibt true, wenn kein Fehlerwert gesetzt ist und false, wenn eines der fail-, bad- oder eof-Flags gesetzt ist. Diese Flags werden gesetzt, wenn eine Leseaktion fehl schlägt. fail, weil das was man einlesen wollte nicht zum Format passt, beispielsweise ein Buchstabe wo eine Zahl erwartet wird (Und es ist kein Fehler, wenn noch irgendwas folgt. Wieso sollte das ein Fehler sein? Das ist einfach die nächste Eingabe!). bad, wenn irgendwas total kaputt ist. eof, wenn über das Ende des Streams hinaus gelesen wurde (Und nicht, wie von vielen schlechten Lehrern angenommen, wenn das Ende erreicht ist. Wieso sollte das ein Fehler sein? Es ging doch bis dahin alles gut und wer weiß, was die Zukunft bringt?).
    Somit erhält man auch das Grundkonstrukt der typischen C++-Leseschleife:

    while(eingabestream >> daten)
    {
      verarbeite(daten);
    }
    

    "Lese Daten, so lange es gut geht."

    Sollte eigentlich in jedem guten Buch erklärt sein. Ist übrigens ein einfacher Indikator für die ganz, ganz schlechten Bücher/Lehrer, wenn sie einem stattdessen

    while(!stream.eof())
    {
      stream >> daten;
      verarbeite(daten);
    }
    

    andrehen wollen. Das ist, nach obiger Erklärung, offensichtlich totaler Unsinn+. Wenn man so etwas in einem Buch sieht, dann kann man damit gleich zum Altpapiercontainer gehen.

    *: Genau genommen ist es eine Konvertierung zu void* . Warum das so ist, kann in einem anderen Thread erklärt werden. Es ist eher eine Behelfskrücke mit unerwünschten Nebenwirkungen, da so etwas wie cout << cin kein Fehler ist, sondern einfach einen Pointer ausgibt.

    +: Sicherheitshalber noch eine Erklärung: Der Code ist doppelt falsch. Erstens wird nur auf eof geprüft. Passiert irgendein anderer Fehler, hat man eine Endlosschleife, denn alle Aktionen auf dem Stream schlagen fehl und man kann nie die Schleife verlassen. Zweitens erfolgt die Prüfung erst nachdem die Daten verarbeitet wurden. Das heißt, bei einem Lesefehler verarbeitet man irgendwelche Mülldaten, bevor man den Fehler bemerkt.



  • Starke Ansage. Ich glaub ich habs soweit kapiert. Der untere Stern gilt deinem falschen Code, richtig? Da ich mit

    !stream.eof()
    

    eben nur auf eof-flags prüfe und bad-, fail-flags außer Acht lasse.

    Ich war mal so frei zu beweisen, dass ich mich auch fähig fühle das Programm ohne Goto zum Laufen zu bringen:

    //************Menü************    
        while (sprache == 'd' || sprache == 'e'){
    
    	    meldung (sprache, id = 17); //Meldung 17 Menü
    	    meldung (sprache, id = 13); //Meldung 13 Einlesen
    	    meldung (sprache, id = 14); //Meldung 14 Iteration
    	    meldung (sprache, id = 15); //Meldung 15 Ausgabe
    	    meldung (sprache, id = 16); //Meldung 16 Exit
    	    meldung (sprache, id = 3); //Meldung 3 Eingabe
    
    		if(cin >> choice){ //Wenn Rückgabewert ok, dann...
    
    		    switch (choice){
    			    case 1: einlesen(spielfeld, sprache, id);
    			    		break;
    
    			    case 2: lebensregeln(spielfeld);
    			       		ausgeben(spielfeld, sprache, id);
    			       		break;
    
    			    case 3: ausgeben(spielfeld, sprache, id);
    			    		break;
    
    			    case 4: meldung (sprache, id = 18); //Meldung 18 Byebye
    			    		getch();
    			    		exit(0);
    						break;
    			}
    
    		} //if cin choice
    		else if (cin.fail()){ //Wenn Rückgabewert -> Fail-Flag (Buchstabeneingabe), dann
    		cin.clear();
    		cin.ignore();
    		}
    		else{
    		exit(1);
    		}
    
    	} //while Menü
    

    SeppJ, deine Antworten sind schon der Hammer. Ich lese da zweimal, bis ich dir folgen kann 🙂

    Sehe ich das richtig, dass ich mit dem

    else if (cin.fail())
    

    den Rückgabewert auf fail-flags überprüfe? Das heißt, das ist dann eigentlich meine Überprüfung auf falsche Eingaben (/Formate - in dem Fall Buchstaben oder Sonderzeichen), oder? Nun, da ich meine Eingaben sowieso nur mit der Tastatur mache, kann ich doch fast das

    else if
    

    weg lassen, einfach nur

    else
    

    verwenden und habe damit alle Flags abgedeckt, oder? Ist es notwendig da in meinem Fall noch mal zu unterscheiden?

    Nehmen wir mal an ich würde eine eof-Flag als Rückgabewert bekommen (kann ich aber nicht, oder?), könnte ich dann auch mit cin.clear() und cin.ignore() das Programm vor der Endlosschleife retten und einfach wieder ins Menü zurückkehren?

    @cvcv: Ich habe das mit den Mehrfacheingaben ausprobiert, komme aber noch nicht so richtig klar damit. Muss ich anstelle von streamsize dann was eingeben? Ich probiere weiter.

    Super Forum, einfach klasse!

    Danke!


  • Mod

    Der untere Stern gilt deinem falschen Code, richtig?

    Ja.

    Ich war mal so frei zu beweisen, dass ich mich auch fähig fühle das Programm ohne Goto zum Laufen zu bringen:

    Habe gerade keine Zeit, es anzugucken. Vielleicht jemand anderes. Oder ich, in ein paar Stunden.

    Sehe ich das richtig, dass ich mit dem

    else if (cin.fail())
    

    den Rückgabewert auf fail-flags überprüfe?

    Nicht direkt einen Rückgabewert. Du prüfst hier den Stream cin direkt. Wenn eine vorherige Leseaktion fehlschlug, behält der aber seine Fehlerflags, bis clear benutzt wird.

    Das heißt, das ist dann eigentlich meine Überprüfung auf falsche Eingaben (/Formate - in dem Fall Buchstaben oder Sonderzeichen), oder?

    Jain. Die Überprüfung erfolgte im Prinzip schon in Zeile 11. Da hast du festgestellt, ob alles richtig lief. Hier prüfst du, wenn etwas fehl schlug, warum genau es fehl schlug. Ich wollte verdeutlichen, dass es verschiedene Arten von Fehlern geben kann, die man einzeln prüfen kann. Aber ehrlich gesagt prüfe ich selber meistens nur wie in Zeile 11 ob alles in Ordnung war. Wenn etwas falsch lief, dann lief es eben falsch und es ist relativ egal, was genau der Fehler war. Hier, bei einer Benutzerinteraktion, kann es aber ganz nützlich sein, genauere Meldungen zu geben. Es ist eben schon wichtig, ob der Nutzer etwas falsch eingab oder ob er explizit die Eingabe abgeschlossen hat (siehe nächster Absatz).

    snirg0r schrieb:

    Nehmen wir mal an ich würde eine eof-Flag als Rückgabewert bekommen (kann ich aber nicht, oder?),

    Doch. Unter Windowskonsolen CTRL+Z drücken (muss glaube ich noch mit Enter bestätigt werden), unter anderen Konsolen CTRL+D drücken (wirkt normalerweise sofort). Das schließt sämtlich Eingaben ab.

    könnte ich dann auch mit cin.clear() und cin.ignore() das Programm vor der Endlosschleife retten und einfach wieder ins Menü zurückkehren?

    Nicht wirklich. Mit clear kannst du den Fehlerstatus aufheben. Aber was willst du an der Stelle noch ignorieren? Da kommst nichts mehr. ignore ist auch eine Leseaktion (eben eine ohne Ergebnis), ignore am Dateiende verursacht daher auch bloß wieder ein eof.

    Wenn der stream Hin- und Herspringen unterstützt (seekg, seekp, tellg, tellp), dann kannst du nach einem clear an eine vorherige Stelle springen. Das funktioniert aber nicht bei jeder Art von Stream, sondern nur bei solchen, wo das auch Sinn macht. fstreams zum Beispiel oder stringstreams.


  • Mod

    Der Code sieht in Ordnung aus. Ich würde aber nie mit exit aus einem Programm aussteigen. Das räumt den Stack nicht ab. Da würde ich eher ein return aus der Funktion machen (sofern es Sinn macht) oder zur Not eine Exception schmeißen (wenn ein return nicht in Frage kommt).

    Oder wir ändern einfach ein bisschen den Programmfluss (ungetestet):

    menüausgabe();
    bool exitflag = false;
    while(!exitflag && !(cin >> choice).eof())
    {
      if(cin.fail())
      {
         cin.clear(); cin.ignore(); continue;
      }
      switch (choice)
      {
        // ...
        case 4: exitflag = true; break;
      }
      menüausgabe();
    }
    

    Ja, ein continue ist auch nichts anderes als ein verherrlichtes goto. Aber da der Effekt lokal begrenzt ist, wird es normalerweise toleriert 🙂 .

    Ein exitflag ist auch nicht gerade die tollste Art. Denn die übliche Art, solch ein Flag zu vermeiden, ist tatsächlich ein vorsichtig eingesetztes goto (oder return/throw, wie oben erklärt). Aber wie schon gesagt, solltest du goto vorerst vermeiden und ich wollte mal eine Alternative vormachen und zeigen, dass diese nicht unbedingt umständlich sein braucht.



  • Habe das exit durch return ersetzt, damit auch der Stack abgeräumt wird 🙂

    Okay. Mein Programm funktioniert soweit. Ich bin äußerst froh so kompetent beraten worden zu sein.

    Die eine Frage bleibt noch offen...

    Wie kann ich das mit den mehreren Eingaben beeinflussen? Gebe ich z.B. "kk" ein, so wird zwei mal das Menü aufgerufen, dabei ist nur einmal gewollt. Er arbeitet also jedes "k" nacheinander ab. Ich habe schon versucht stattdessen choice als "ein-elementiges" (eindimensionales) int-Feld zu verwenden, aber da müsste es doch noch eine andere Möglichkeit geben, oder? Die Anweisung von unten kann ich so nicht verwenden, da dann im Menü nach Durchlaufen einer Schleife gar keine Eingaben mehr gemacht werden können. 😕 Habt ihr da noch was passendes für mich im Repertoire?

    Vielen Dank!


  • Mod

    snirg0r schrieb:

    Wie kann ich das mit den mehreren Eingaben beeinflussen? Gebe ich z.B. "kk" ein, so wird zwei mal das Menü aufgerufen, dabei ist nur einmal gewollt.

    Hast du mein Grundgerüst benutzt? Da sollte das eigentlich nicht passieren. Zeig mal Code.


Anmelden zum Antworten