Problem mit cin und cin.getline()
-
Hallo Leute,
seit ein paar Wochen versuche ich mich nun an C++. Jetzt habe ich hier ein Problem bei dem ich nicht so recht weiter komme, bzw. was ich nicht so ganz verstehe.
Folgender Beispielcode soll mein Problem schildern:
/*************************************************************************** nummereingabe ***************************************************************************/ #include <iostream> using namespace std; int nummereingabe(int index){ int nummer,ausgabe; char a[20]; cout<<"Nummer des Eintrag: "; cin>>nummer; // cin.ignore(1); if (nummer>=0 && nummer<=index){ ausgabe=nummer; cin.getline(a,20); } else ausgabe=-1; return (nummer); }
Ist die If-Anweisung erfüllt so kann ich dennoch keinen String mit cin.getline(a,20); einlesen. Dies gelingt nur wenn ich die auskommentierte Zeile mit in meinen Code aufnehme.
Da ich diese Lösung aber eher durch langes ausprobieren von wahrlos gewählten Befehlen hinbekommen habe bin ich mir nicht der Nebenwirkungen bewusst.Muss ich hinter jedem cin>>xy; jetzt ein cin.ignore(1); setzen??? oder kann man das eleganter lösen?
Gruß
Matthias
-
mtepe schrieb:
Muss ich hinter jedem cin>>xy; jetzt ein cin.ignore(1); setzen??? oder kann man das eleganter lösen?
Das Problem ist, dass nach der Eingabe der Zahl du auf Enter/Return drückst. Dadurch wird dem Stream noch ein \n angefügt, also ein Line Feed (neue Zeile). Gelesen wird aber nur die Zahl und der Line Feed bleibt im Stream. Wenn du nun eine ganze Zeile auslesen willst, kommt nichts, da die Zeile gleich beendet ist, wegen dem Line Feed.
Du musst also den Stream leeren, bevor du mit ihm wieder weiterarbeiten kannst, zumindest in diesem Fall. Wenn du nur eine weitere Zahl einlesen würdest, wäre das kein Problem, da Leerzeichen automatisch übersprungen werden.#include <string> #include <iostream> int main() { int number1; std::cin >> number1; int number2; std::cin >> number2; // Kein Problem char line[20]; // std::cin.getline(a, 20); // Wird ein Problem, also zuerst leeren. std::cin.clear(); // Alle Fehlerflags löschen. std::cin.ignore(std::cin.rdbuf()->in_avail()); // Alles was noch verfügbar ist, ignorieren. std::cin.getline(a, 20); // Zeile lesen. // Übrigens, eine bessere Art mit strings umzugehen: std::string myLine; std::getline(std::cin, myLine); return 0; }
C++ Referenz: http://www.cplusplus.com/reference/
Grüssli
-
Wenn ich an Stelle von cin.ignore(1); den Befehl cin.ignore(cin.rdbuf()->in_avail()); nutze kommt es weiterhin zu Fehler.
Hier mein kompletter Code:
/*************************************************************************** Einsendeaufgabe 4.1 ***************************************************************************/ #include <iostream> #include <cstdlib> using namespace std; #include "warten.h" #include "programmKopf.h" #include "nummereingabe.h" // Festlegen der globalen Strukturen struct verzeichnis { char vorname[80]; char name[80]; char telefon[20]; }; // Festlegen der globalen Variablen verzeichnis tbuch[100]; // Weist bei jedem Eintrag dem Namen einen leeren String zu. void initialisierung(verzeichnis *vrz){ for (int i=0; i<100; i++){ vrz[i].vorname[0]='\0'; vrz[i].name[0]='\0'; vrz[i].telefon[0]='\0'; } } //Eintrag anlegen int eintragAnlegen(int index){ programmKopf(index); cout<<"Anlegen eines neuen Datensatz:"<<endl; if(index>=100){ cout<<"\n\n"<<"Das Telefonbuch ist voll, es können keine weiteren"; cout<<" Einträge mehr angelegt werden!"<<"\n\n"; } else { cout<<"Vorname\t\t: "; cin.getline(tbuch[index].vorname, 80); cout<<"Nachname\t: "; cin.getline(tbuch[index].name, 80); cout<<"Telefonnummer\t: "; cin.getline(tbuch[index].telefon, 80); cout<<'\n'<<'\n'<<"Eintrag erfolgreich in gespeichert."<<endl; index++; } warten(); return index; } // Eintrag anzeigen void eintragAnzeigen(int index){ int auswahl; programmKopf(index); cout<<"Anzeigen eines Eintrag:"<<endl; auswahl=nummereingabe(index); if(auswahl<=0 || auswahl>index){ cout<<'\n'<<'\n'; cout<<"Fehler: \"Datensatz nicht vorhanden!\""<<endl; } else { cout<<'\n'; cout<<"Vorname\t\t: "; cout<<tbuch[auswahl-1].vorname<<endl; cout<<"Nachname\t: "; cout<<tbuch[auswahl-1].name<<endl; cout<<"Telefonnummer\t: "; cout<<tbuch[auswahl-1].telefon<<endl; } warten(); } // Alle Einträge anzeigen void alleAnzeigen(int index){ programmKopf(index); cout<<'\n'; for(int i=0;i<index;i++){ cout<<(i+1)<<". "; cout<<tbuch[i].vorname<<", "; cout<<tbuch[i].name<<": "; cout<<tbuch[i].telefon<<endl; } warten(); } //Eintrag löschen int eintragLoeschen(int index){ int auswahl; programmKopf(index); cout<<"Löschen eines Eintrag:"<<endl; auswahl=nummereingabe(index); if(auswahl<=0 || auswahl>index){ cout<<'\n'<<'\n'; cout<<"Fehler: \"Datensatz nicht vorhanden!\""<<endl; } else { for (int i=auswahl; i<index; i++){ strcpy(tbuch[i-1].vorname,tbuch[i].vorname); strcpy(tbuch[i-1].name,tbuch[i].name); strcpy(tbuch[i-1].telefon,tbuch[i].telefon); } strcpy(tbuch[index].vorname,""); strcpy(tbuch[index].name,""); strcpy(tbuch[index].telefon,""); index--; cout<<'\n'<<'\n'<<"Eintrag erfolgreich gelöscht."<<endl; } warten(); return index; } // Hauptprogramm int main() { int menue,index=0; initialisierung(tbuch); //Hauptmenue: while (menue!=6){ programmKopf(index); cout<<"Folgende Optionen stehen zur Auswahl: "<<endl; cout<<"[1] Neuen Eintrag anlegen"<<endl; cout<<"[2] Eintrag anzeigen"<<endl; cout<<"[3] Alle Einträge anzeigen"<<endl; cout<<"[4] Eintrag löschen"<<endl; cout<<"[5] Alle Einträge löschen"<<endl; cout<<"[6] Programm beenden"<<endl; cout<<'\n'; cout<<"Ihre Auswahl\t: "; cin>>menue; cin.clear(); //Eingabefehler von cin aufheben cin.ignore(cin.rdbuf()->in_avail()); //Eingabefehler von cin aufheben switch(menue) { case 1: index=eintragAnlegen(index); break; case 2: eintragAnzeigen(index); break; case 3: alleAnzeigen(index); break; case 4: index=eintragLoeschen(index); break; case 5: initialisierung(tbuch); index=0; cout<<'\n'<<'\n'<<"Alle Einträge wurden gelöscht!"<<endl; warten(); break; case 6: cout<<"Das Programm wird beendet."<<endl; break; default: cout<<"Ungültige Auswahl"<<endl; } } return 0; }
EDIT: Falls ich hier was falsch gemacht habe oder es bessere Umsetztungsmöglichkeiten gibt: Lasst hören, ich will schließlich was dazu lernen
-
vor das ignore muss das cin.clear();
dann sollte es klappen
-
mtepe schrieb:
struct verzeichnis { char vorname[80]; char name[80]; char telefon[20]; // <--- hier 20 }; // ... cout<<"Telefonnummer\t: "; cin.getline(tbuch[index].telefon, 80); // <-- hier 80
ein schönes Beispiel warum man eben keine char-arrays benutzen soll. Falls man bei der Telefonnummer mehr als 20 Zeichen eingibt, wird undefiniert Speicher überschrieben. Solche Fehler sind bei größeren Programmen recht schwer zu finden.
Aber dafür gibt's ja C++. Meine erste Empfehlung daher ist, std::string zu nutzen.struct verzeichnis { std::string vorname; // erfordert #include <string> std::string name; std::string telefon; }; // ... verzeichnis eintrag; cout<<"Telefonnummer\t: "; getline( cin, eintrag.telefon );
mtepe schrieb:
// Festlegen der globalen Variablen verzeichnis tbuch[100];
Beschäftige Dich mit den Standard Containern. Und mache die Variable nicht global. Hier besser - z.B. im main:
std::vector< verzeichnis > tbuch; // erfordert #include <vector>
Die Funktion initialisierung() ist danach schlicht überflüssig, dass erledigen std::string und std::vector für Dich.
Den 'Index' brauchst Du dann auch nicht mehr. Lass Dir von der Funktion eintragAnlegen() ein Objekt von Typ verzechnis zurückgeben.verzeichnis eintragAnlegen() { verzeichnis eintrag; // Füllen s.o. return eintrag; }
und danach einfach mit tbuch.push_back( .. ) an den vector tbuch anhängen.
Die Variable 'menue' in Zeile 121 wird nicht initialisiert, aber in Zeile 125 bereits abgefragt.
:xmas2: Werner
-
Skym0sh0 schrieb:
vor das ignore muss das cin.clear();
dann sollte es klappen
Aber vor dem ignore ist doch ein cin.clear();
cin>>menue; cin.clear(); //Eingabefehler von cin aufheben cin.ignore(cin.rdbuf()->in_avail()); //Eingabefehler von cin aufheben switch(menue) { case 1:
-
mtepe schrieb:
Wenn ich an Stelle von cin.ignore(1); den Befehl cin.ignore(cin.rdbuf()->in_avail()); nutze kommt es weiterhin zu Fehler.
Definiere Fehler. Wo? Was? Wie? Wann?
Dein kompletter Code hat schliesslich nichts mit dem ersten gezeigten zu tun. Wenn du meine Lösung im ersten Code einfügst, dann funktioniert dieser.
Grüssli
-
Dravere schrieb:
Definiere Fehler. Wo? Was? Wie? Wann?
Wenn ich den zweiten Code ausführe, in dem ich nach einem cin ein cin.clear(); und ein cin.ignore(cin.rdbuf()->in_avail()); kommt es zu folgende Verhalten im Programm:
Wähle ich z.B die 1 zum anlegen eines neuen Eintrag, so wird die Eingabe des Vornamen übersprungen.
Wähle ich hingegen z.B ein e so fängt das ganze Programm an zu blinken und lasst sich nicht mehr unterbrechen.Dravere schrieb:
Dein kompletter Code hat schliesslich nichts mit dem ersten gezeigten zu tun
Sorry, der erste Code ist auch ein Teil des Programm. Er ist am Anfang vom zweiten Code eingebunden.
#include "nummereingabe.h"
Hab ich wohl vergessen zu erwähnen
-
Hab den Code übernommen und getestet.
Alle FunktionenprogrammKopf
undwarten
rausgestrichen, da ich diese nicht kenne, und habe den Fehler korrigiert, welcher Werner aufgezeigt hat.
Bei mir lief es einwandfrei und konnte die Probleme nicht reproduzieren, welche du aufgezeigt hast.Hast du auch alles richtig umgesetzt? Was passiert in
programmKopf
undwarten
? Welchen Kompiler und welche Standardbibliothek benutzt du?Grüssli
-
Dravere schrieb:
Gelesen wird aber nur die Zahl und der Line Feed bleibt im Stream. Wenn du nun eine ganze Zeile auslesen willst, kommt nichts, da die Zeile gleich beendet ist, wegen dem Line Feed.
Das gilt für get, aber nicht für getline.
-
KasF schrieb:
Dravere schrieb:
Gelesen wird aber nur die Zahl und der Line Feed bleibt im Stream. Wenn du nun eine ganze Zeile auslesen willst, kommt nichts, da die Zeile gleich beendet ist, wegen dem Line Feed.
Das gilt für get, aber nicht für getline.
Das gilt auch für den
operator >>
und darauf hatte ich mich bezogen, nicht aufgetline
getline
holt eine leere Zeile, weil nicht gesäubert wurde nach demoperator >>
.Grüssli
-
Ich muss sagen, dass ich dieses Problem auch habe(wenn ich die Beschreibungen nicht alle vollständig missverstanden habe).
Folgender Code pausiert am Ende nämlich nicht:#include <iostream> using namespace std; int main() { int var; cin >> var; cin.clear(); cin.ignore(cin.rdbuf()->in_avail()); cin.get(); }
Ich habe stattdessen immer die etwas schlechtere Variante verwendet:
#include <iostream> #include <limits> using namespace std; int main() { int var; cin >> var; cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n'); cin.get(); }
-
Dravere schrieb:
Das gilt auch für den
operator >>
und darauf hatte ich mich bezogen, nicht aufgetline
Ohh, sorry.
-
programmKopf.cpp
/*************************************************************************** Versionsanzeige ***************************************************************************/ #include <iostream> #include <cstdlib> using namespace std; void programmKopf(int index) { system ("clear"); cout<<"Telefonbuch v0.1"<<endl; cout<<index<<" von 100 Einträge belegt."<<endl; cout<<'\n'; }
warten.cpp
/*************************************************************************** Warten auf Bestätigung ***************************************************************************/ #include <iostream> #include <cstdlib> using namespace std; void warten(){ cout<<'\n'<<"Weiter mit Eingabetaste."<<endl; char enter[1]; cin.getline(enter,1); }
Wie bekomme ich den Compiler und die Standardbibliothek heraus?
Ich glaub mit Compiler ist der GCC gemeint?
Dann wäre es folgender:matthias@laptop:~$ gcc --version gcc (GCC) 4.2.4 (Ubuntu 4.2.4-1ubuntu3) Copyright (C) 2007 Free Software Foundation, Inc. Dies ist freie Software; die Kopierbedingungen stehen in den Quellen. Es gibt KEINE Garantie; auch nicht für MARKTGÄNGIGKEIT oder FÜR SPEZIELLE ZWECKE.
-
Dann muss ich sagen, dass ich keine Ahnung habe, wieso es nicht geht, sofern du alles richtig umgesetzt hast, was wir dir gesagt haben.
Oder ich sehe den Fehler nicht. Jedenfalls läuft es bei mir einwandfrei.Probier vielleicht noch die folgenden Zwei Dinge aus. Statt der Linie:
std::cin.ignore(std::cin.rdbuf()->in_avail());
Schreib entweder diese hin:
std::cin.sync();
Oder diese hier:
#include <limits> // <- für std::numeric_limits nötig. // ... std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
Grüssli
-
JustAnotherNoob schrieb:
Ich habe stattdessen immer die etwas schlechtere Variante verwendet:
Wieso schlechtere ?
Ich hatte schonmal die Problematik mit dem in_avail() angesprochen: http://c-plusplus.net/forum/viewtopic-var-p-is-1300629.html
-
cin>>menue; cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n');
Funktioniert wunderbar
DANKE !!!
Aber was genau passiert denn da jetzt?
Das Zeichen \n wird ignoriert. Ok macht Sinn.
streamsize??? wird ignoriert wenn > numeric_limits bzw <::max()Was sind streamsize, numeric_limits und was ist ::max() ???
-
mtepe schrieb:
cin>>menue; cin.clear(); cin.ignore(numeric_limits<streamsize>::max(), '\n');
Funktioniert wunderbar
DANKE !!!
Aber was genau passiert denn da jetzt?
Das Zeichen \n wird ignoriert. Ok macht Sinn.
streamsize??? wird ignoriert wenn > numeric_limits bzw <::max()Was sind streamsize, numeric_limits und was ist ::max() ???
Es werden alle Zeichen im weggeworfen, die entwerder bis zu nächsten '\n' gehen (also bis zum nächsten Return), oder, falls kein '\n' im Stream ist, wird alles bis zur maximalen Streamgröße weggeworfen.
Wenn der Stream allerdings leer ist (in Deinem Fall unwahrscheinlich), dann blockiert der Aufruf von ignore allerdings, bis irgendwas in den Stream geschrieben wird.
Das Leerräumen von Input-Streams ist unter C++ leider alles andere als einfach. Im Grunde genommen ist das sogar ziemlich scheisse, weil es nur wenige Lösungen gibt, die wirklich portabel sind.Eine recht gute Zusammenfassung zu dem Thema gibt es hier.