Wann und warum muss ich cin.ignore () in C ++ verwenden?

73

Ich habe ein sehr einfaches Programm in C ++ geschrieben, das den Benutzer aufforderte, eine Zahl und dann eine Zeichenfolge einzugeben. Zu meiner Überraschung hörte es beim Ausführen des Programms nie auf, nach der Zeichenfolge zu fragen. Es wurde einfach übersprungen. Nachdem ich etwas über StackOverflow gelesen hatte, stellte ich fest, dass ich eine Zeile hinzufügen musste, in der stand:

cin.ignore(256, '\n');

vor der Zeile, die die Zeichenfolgeneingabe erhält. Das Hinzufügen behebt das Problem und lässt das Programm funktionieren. Meine Frage ist, warum C ++ diese cin.ignore()Zeile benötigt und wie ich vorhersagen kann, wann ich sie verwenden muss cin.ignore().

Hier ist das Programm, das ich geschrieben habe:

#include <iostream>
#include <string>

using namespace std;

int main()
{
    double num;
    string mystr;

    cout << "Please enter a number: " << "\n";
    cin >> num;
    cout << "Your number is: " << num << "\n";
    cin.ignore(256, '\n'); // Why do I need this line?
    cout << "Please enter your name: \n";
    getline (cin, mystr);
    cout << "So your name is " << mystr << "?\n";
    cout << "Have a nice day. \n";

}
Raddicus
quelle
Dies wurde in der Antwort, die Sie gelesen haben, nicht erklärt? Warum nicht in einem Kommentar um Klarstellung bitten? Jetzt haben wir ein weiteres Duplikat zu diesem Thema. Seufzer.
Leichtigkeitsrennen im Orbit
3
@LightnessRacesinOrbit Abstimmung, um dann als Duplikat zu schließen?
MM
@LightnessRacesinOrbit Entschuldigung, dass Sie die Frage dupliziert haben.
Raddicus
@ MattMcNabb: Ja, aber cba, um es zu finden. Wäre schön gewesen, wenn ich nicht müsste.
Leichtigkeitsrennen im Orbit
Okay, wenn ich Ihren Code ohne Ignorieren ausführe, überspringt er direkt das zweite Kino >>, vermutlich weil sich das Endl noch im Stream befindet. Aber wenn ich Ihren Code mit Ignorieren ausführe, überspringt er immer noch den zweiten Cin >>, wenn ich etwas anderes als numerische Zeichen eingebe. Und wenn ich zwei Ignorierungen eingebe, wartet es auf eine dritte Eingabe vor dem zweiten Film >>. Kann jemand das erklären?
Kyle Delaney

Antworten:

73

Ignorieren ist genau das, was der Name impliziert.

Es "wirft" nicht etwas weg, das Sie nicht benötigen, sondern ignoriert die Anzahl der Zeichen, die Sie beim Aufrufen angeben, bis zu dem Zeichen, das Sie als Haltepunkt angeben.

Es funktioniert sowohl mit Eingabe- als auch mit Ausgabepuffern.

Im Wesentlichen std::cinignorieren Sie Anweisungen, die Sie ignorieren, bevor Sie einen getlineAufruf ausführen, denn wenn ein Benutzer etwas mit eingibt std::cin, drückt er die Eingabetaste und ein '\n'Zeichen gelangt in den cinPuffer. Wenn Sie dann verwenden getline, wird anstelle der gewünschten Zeichenfolge das Zeilenumbruchzeichen angezeigt. Sie tun also a std::cin.ignore(1000,'\n')und das sollte den Puffer bis zu der gewünschten Zeichenfolge löschen. (Die 1000 wird dort abgelegt, um eine bestimmte Anzahl von Zeichen vor dem angegebenen Haltepunkt zu überspringen, in diesem Fall das Zeichen \ n Zeilenumbruch.)

savageWays
quelle
1
Vielen Dank. Wäre es zu viel zu fragen, warum C ++ das Problem hat, ein '\ n' Zeichen im Cin-Puffer zu erhalten, wie Sie beschrieben haben? Ist das ein Sprachfehler? Oder wurde es absichtlich so gemacht, weil es einem Zweck dient?
Raddicus
6
Nicht gerade ein Fehler, eher Präzision. Technisch gesehen liest das Zeilenumbruchzeichen nicht alles, wenn es nicht eingelesen wird. Eingabepuffer ignorieren Leerzeichen. Wenn Sie also Informationen aus std :: cin einlesen, spielt die Newline keine Rolle. Wenn Sie jedoch über getline sprechen, wird die gesamte Zeile bis zum Newline-Zeichen angezeigt, und wenn das Newline-Zeichen lautet Das erste, was die Funktion getline sieht, ist alles, was sie bekommt.
SavageWays
19
Sie müssen verwenden numeric_limits<streamsize>::max(), um alle Zeichen zu ignorieren, nicht 1000.
Neil Kirk
1
Gemäß dem Standard .ignore () "Zeichen extrahieren und verwerfen", denke ich, dass dies ziemlich nahe daran ist, cplusplus.com/reference/istream/istream/ignore
csguy wegzuwerfen
30

Du denkst falsch darüber nach. Sie denken jedes Mal in logischen Schritten cinoder getlinewerden verwendet. Ex. Fragen Sie zuerst nach einer Nummer und dann nach einem Namen. Das ist der falsche Weg, um darüber nachzudenken cin. Sie geraten also in eine Rennbedingung, weil Sie davon ausgehen, dass der Stream jedes Mal klar ist, wenn Sie nach einer Eingabe fragen.

Wenn Sie Ihr Programm nur zur Eingabe schreiben, finden Sie das Problem:

void main(void)
{
    double num;
    string mystr;

    cin >> num;
    getline(cin, mystr);

    cout << "num=" << num << ",mystr=\'" << mystr << "\'" << endl;
}

Oben denken Sie: "Holen Sie sich zuerst eine Nummer." Geben Sie also die 123Eingabetaste ein und Ihre Ausgabe wird angezeigtnum=123,mystr='' . Warum ist das so? Dies liegt daran, dass in dem Stream, den Sie haben, 123\nder 123in die numVariable analysiert wird, während er \nsich noch im Stream befindet. Wenn Sie das Dokument getlinestandardmäßig auf Funktion lesen, wird es istreambis a angezeigt\n wird. In diesem Beispiel seit\n sieht es so aus, als ob es im Stream "übersprungen" wurde, aber es hat ordnungsgemäß funktioniert.

Damit das oben genannte funktioniert, müssen Sie eingeben, 123Hello Worldwelches ordnungsgemäß ausgegeben wird num=123,mystr='Hello World'. Das, oder Sie setzen eine cin.ignorezwischen diecin undgetline damit es in logische Schritte zerfällt, die Sie erwarten.

Deshalb brauchen Sie die ignore Befehl. Weil Sie in logischen Schritten und nicht in Stream-Form darüber nachdenken, geraten Sie in eine Rennbedingung.

Nehmen Sie ein anderes Codebeispiel, das in Schulen häufig vorkommt:

void main(void)
{
    int age;
    string firstName;
    string lastName;

    cout << "First name: ";
    cin >> firstName;

    cout << "Last name: ";
    cin >> lastName;

    cout << "Age: ";
    cin >> age;

    cout << "Hello " << firstName << " " << lastName << "! You are " << age << " years old!" << endl;
}

Das Obige scheint in logischen Schritten zu sein. Fragen Sie zuerst nach Vorname, Nachname und dann nach Alter. Wenn Sie also Johneingetreten sind, dannDoe eingeben, dann 19eingeben, arbeitet die Anwendung jeden logischen Schritt. Wenn Sie in "Streams" daran denken, können Sie einfach John Doe 19die Frage "Vorname:" eingeben , und es würde auch funktionieren und die verbleibenden Fragen überspringen. Damit die oben genannten Schritte in logischen Schritten ausgeführt werden können, müssen Sie ignorefür jede logische Unterbrechung der Fragen den verbleibenden Stream verwenden.

Denken Sie daran, Ihre Programmeingabe beim Lesen aus einem "Stream" und nicht in logischen Schritten zu betrachten. Jedes Mal, wenn Sie es aufrufen cin, wird es aus einem Stream gelesen. Dies erzeugt eine ziemlich fehlerhafte Anwendung, wenn der Benutzer die falsche Eingabe eingibt. Wenn Sie beispielsweise ein Zeichen eingegeben haben, bei dem a cin >> doubleerwartet wird, erzeugt die Anwendung eine scheinbar bizarre Ausgabe.

Dan
quelle
15

Kurze Antwort

Warum? Weil im Eingabestream noch Leerzeichen (Zeilenumbrüche, Tabulatoren, Leerzeichen, Zeilenumbrüche) vorhanden sind.

Wann? Wenn Sie eine Funktion verwenden, die nicht alleine die führenden Leerzeichen ignoriert. Cin ignoriert und entfernt standardmäßig das führende Leerzeichen, aber getline ignoriert das führende Leerzeichen nicht alleine.

Nun eine ausführliche Antwort.

Alles, was Sie in die Konsole eingeben, wird aus dem Standard-Stream stdin gelesen. Wenn Sie etwas eingeben, sagen wir 256 in Ihrem Fall und drücken Sie die Eingabetaste, wird der Inhalt des Streams 256\n. Jetzt nimmt cin 256 auf und entfernt sie aus dem Stream und \nbleibt immer noch im Stream. Wenn Sie nun Ihren Namen eingeben, sagen wir Raddicus, der neue Inhalt des Streams ist \nRaddicus.

Jetzt kommt hier der Haken. Wenn Sie versuchen, eine Zeile mit getline zu lesen, liest getline standardmäßig bis zum Zeilenumbruchzeichen und entfernt das Zeilenumbruchzeichen aus dem Stream, wenn kein Trennzeichen als drittes Argument angegeben ist. Beim Aufrufen einer neuen Leitung liest und verwirft getline\n aus dem Stream. dass eine leere Zeichenfolge in mystr gelesen wird, die so aussieht, als würde getline übersprungen (aber nicht), da bereits eine neue Zeile im Stream vorhanden war. Getline fordert Sie nicht zur Eingabe als auf es hat bereits gelesen, was es lesen sollte.

Wie hilft cin.ignore hier?

Laut der Ignorierdokumentation Auszug aus cplusplus.com -

istream & ignore (Streamsize n = 1, int delim = EOF);

Extrahiert Zeichen aus der Eingabesequenz und verwirft sie, bis entweder n Zeichen extrahiert wurden oder eines mit delim verglichen wird.

Die Funktion beendet auch das Extrahieren von Zeichen, wenn das Dateiende erreicht ist. Wenn dies vorzeitig erreicht wird (bevor entweder n Zeichen extrahiert oder Delim gefunden werden), setzt die Funktion das Eofbit-Flag.

Damit, cin.ignore(256, '\n'); ignoriert ersten 256 Zeichen oder alle Zeichen bis es trifft delimeter (hier \ n in Ihrem Fall), je nachdem , was zuerst eintritt (hier \ n ist das erste Zeichen, so dass es ignoriert , bis \ n angetroffen wird).

Nur als Referenz: Wenn Sie nicht genau wissen, wie viele Zeichen übersprungen werden sollen, und Ihr einziger Zweck darin besteht, den Stream zu löschen, um das Lesen einer Zeichenfolge mit getline oder cin vorzubereiten, sollten Sie diese verwenden cin.ignore(numeric_limits<streamsize>::max(),'\n').

Kurze Erklärung: Es werden die Zeichen ignoriert, die der maximalen Größe des Streams entsprechen oder bis ein '\ n' auftritt, je nachdem, welcher Fall zuerst eintritt.

CodeTalker
quelle
6

Wenn Sie eine bestimmte Anzahl von Zeichen manuell aus dem Eingabestream entfernen möchten.

Ein sehr häufiger Anwendungsfall ist die Verwendung dieser Option, um Zeilenumbrüche sicher zu ignorieren, da cin manchmal Zeilenumbrüche hinterlässt, die Sie durchgehen müssen, um zur nächsten Eingabezeile zu gelangen.

Kurz gesagt, es gibt Ihnen Flexibilität bei der Verarbeitung von Stream-Eingaben.

shafeen
quelle
Danke für die Antwort. Gibt es eine effizientere Möglichkeit, getline () nach cin zu verwenden? Sollte ich einfach vorhaben, eine Ignorierzeile zwischen jede Zeile mit einem Cin gefolgt von einer getline () zu werfen? Das kommt mir ziemlich albern vor. Vielleicht ist es sinnvoller, wenn Sie mehr Erfahrung mit C ++ haben als ich.
Raddicus
1
@Raddicus Es ist wirklich albern, zumal wir wissen, dass das '\n'in IOStreams als Leerzeichen analysiert wird und IOStreams den std::wsManipulator bereitstellen, der führende Leerzeichen verwirft. Sie müssen std::getline(std::cin >> ws, mystr)lediglich alle Leerzeichen und Zeilenumbrüche ignorieren.
0x499602D2
1
@Raddicus: Ich würde vorschlagen, dass Sie nicht mischen operator>>und getlineauf dem gleichen Stream. Zum Beispiel mit cinnur verwenden getline. Wenn Sie Daten ohne Zeichenfolge benötigen, analysieren Sie die Zeichenfolge, die Sie getlinemit istringstreamoderstoi/stod/etc...
Benjamin Lindley
Beschreiben Sie statt "manchmal" genau, wann und wo
MM
@ 0x499602D2 eine gute Idee in diesem Fall, aber es wird andere Fälle geben, in denen Leerzeilen von Bedeutung sind (z. B. Eingabe mit Standardwerten), so dass das Problem noch verstanden werden muss
MM
1

Die Ignorierfunktion wird verwendet, um Zeichen im Eingabestream zu überspringen (zu verwerfen / wegzuwerfen). Datei ignorieren ist der Datei istream zugeordnet. Betrachten Sie die folgende Funktion, z. B.: Cin.ignore (120, '/ n'); Die jeweilige Funktion überspringt die nächsten 120 Eingabezeichen oder überspringt die Zeichen, bis ein Zeilenumbruchzeichen gelesen wird.

Vinod
quelle
0

Wie von vielen anderen Benutzern richtig angegeben. Dies liegt daran, dass möglicherweise Leerzeichen oder Zeilenumbrüche vorhanden sind.

Betrachten Sie den folgenden Code, der alle doppelten Zeichen aus einer bestimmten Zeichenfolge entfernt.

#include <bits/stdc++.h>
using namespace std;

int main() {
    int t;
    cin>>t;
    cin.ignore(); //Notice that this cin.ignore() is really crucial for any extra whitespace or newline character
    while(t--){
        vector<int> v(256,0);
        string s;
        getline(cin,s);
        string s2;
        for(int i=0;i<s.size();i++){
            if (v[s[i]]) continue;
            else{
                s2.push_back(s[i]);
                v[s[i]]++;
            }
        }
        cout<<s2<<endl;
    }
    return 0;
}

Sie erhalten also den Punkt, dass diese unerwünschten Eingaben ignoriert werden und die Arbeit erledigt wird.

Pikachu
quelle
-3

Es ist besser, scanf ("% [^ \ n]", str) in c ++ als cin.ignore () nach der Anweisung cin >> zu verwenden. Dazu müssen Sie zuerst den Header <cstdio> einfügen.

Mahmud_28
quelle
1
Nicht wahr. scanfist ein Teil des <cstdio>Headers und nicht die idiomatische C ++ - Methode, um Eingaben vom Benutzer zu erhalten.
Daniel