Warum passiert das?
Dies hat wenig mit der Eingabe zu tun, die Sie selbst bereitgestellt haben, sondern mit dem Standardverhalten std::getline()
. Bei der Eingabe für den Namen ( std::cin >> name
) haben Sie nicht nur die folgenden Zeichen übermittelt, sondern auch eine implizite neue Zeile an den Stream angehängt:
"John\n"
Eine neue Zeile wird immer an Ihre Eingabe angehängt, wenn Sie auswählen Enteroder Returnvon einem Terminal aus senden . Es wird auch in Dateien verwendet, um zur nächsten Zeile zu gelangen. Die neue Zeile bleibt nach dem Extrahieren im Puffer name
bis zur nächsten E / A-Operation, wo sie entweder verworfen oder verbraucht wird. Wenn der Kontrollfluss erreicht ist std::getline()
, wird die neue Zeile verworfen, aber die Eingabe wird sofort beendet. Der Grund dafür ist, dass die Standardfunktionalität dieser Funktion dies vorschreibt (sie versucht, eine Zeile zu lesen und stoppt, wenn sie eine neue Zeile findet).
Da dieser führende Zeilenumbruch die erwartete Funktionalität Ihres Programms beeinträchtigt, muss er übersprungen und irgendwie ignoriert werden. Eine Möglichkeit besteht darin, std::cin.ignore()
nach der ersten Extraktion aufzurufen . Das nächste verfügbare Zeichen wird verworfen, sodass die neue Zeile nicht mehr im Weg ist.
std::getline(std::cin.ignore(), state)
Ausführliche Erklärung:
Dies ist die Überlastung dessen std::getline()
, was Sie angerufen haben:
template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
std::basic_string<charT>& str )
Eine weitere Überladung dieser Funktion erfordert ein Trennzeichen vom Typ charT
. Ein Trennzeichen ist ein Zeichen, das die Grenze zwischen Eingabesequenzen darstellt. Diese bestimmte Überladung setzt das Trennzeichen input.widen('\n')
standardmäßig auf das Zeilenumbruchzeichen, da keines angegeben wurde.
Dies sind einige der Bedingungen, unter denen std::getline()
die Eingabe beendet wird:
- Wenn der Stream die maximale Anzahl von Zeichen extrahiert hat, die a enthalten
std::basic_string<charT>
kann
- Wenn das Zeichen für das Dateiende (EOF) gefunden wurde
- Wenn das Trennzeichen gefunden wurde
Die dritte Bedingung ist die, mit der wir es zu tun haben. Ihre Eingabe in state
wird folgendermaßen dargestellt:
"John\nNew Hampshire"
^
|
next_pointer
Wo next_pointer
ist das nächste zu analysierende Zeichen? Da das an der nächsten Position in der Eingabesequenz gespeicherte Zeichen das Trennzeichen ist, std::getline()
wird dieses Zeichen stillschweigend verworfen, next_pointer
zum nächsten verfügbaren Zeichen erhöht und die Eingabe gestoppt. Dies bedeutet, dass die restlichen Zeichen, die Sie angegeben haben, für die nächste E / A-Operation noch im Puffer verbleiben. Sie werden feststellen, dass state
Ihre Extraktion beim letzten Aufrufen std::getline()
des Trennzeichens das richtige Ergebnis liefert , wenn Sie einen weiteren Lesevorgang von der Zeile in ausführen .
Möglicherweise haben Sie bemerkt, dass dieses Problem beim Extrahieren mit dem formatierten Eingabeoperator ( operator>>()
) normalerweise nicht auftritt . Dies liegt daran, dass Eingabestreams Leerzeichen als Trennzeichen für die Eingabe verwenden und standardmäßig der Manipulator std::skipws
1 aktiviert ist. Streams verwerfen das führende Leerzeichen aus dem Stream, wenn formatierte Eingaben ausgeführt werden. 2
Im Gegensatz zu den formatierten Eingabeoperatoren std::getline()
handelt es sich um eine unformatierte Eingabefunktion. Und alle unformatierten Eingabefunktionen haben den folgenden Code etwas gemeinsam:
typename std::basic_istream<charT>::sentry ok(istream_object, true);
Das Obige ist ein Sentry-Objekt, das in allen formatierten / unformatierten E / A-Funktionen in einer Standard-C ++ - Implementierung instanziiert wird. Sentry-Objekte werden verwendet, um den Stream für E / A vorzubereiten und festzustellen, ob er sich in einem Fehlerzustand befindet oder nicht. Sie werden nur feststellen, dass in den unformatierten Eingabefunktionen das zweite Argument für den Sentry-Konstruktor lautet true
. Dieses Argument bedeutet, dass führende Leerzeichen nicht vom Beginn der Eingabesequenz an verworfen werden. Hier ist das relevante Zitat aus dem Standard [§27.7.2.1.3 / 2]:
explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);
[...] Wenn noskipws
Null ist und is.flags() & ios_base::skipws
ungleich Null ist, extrahiert und verwirft die Funktion jedes Zeichen, solange das nächste verfügbare Eingabezeichen c
ein Leerzeichen ist. [...]
Da die obige Bedingung falsch ist, verwirft das Wachobjekt das Leerzeichen nicht. Der Grund noskipws
, auf true
den diese Funktion eingestellt ist, std::getline()
liegt darin, dass rohe, unformatierte Zeichen in ein std::basic_string<charT>
Objekt eingelesen werden sollen .
Die Lösung:
Es gibt keine Möglichkeit, dieses Verhalten von zu stoppen std::getline()
. Was Sie tun müssen, ist, die neue Zeile vor dem Ausführen selbst zu verwerfen std::getline()
(aber nach der formatierten Extraktion). Dies kann erreicht werden, indem ignore()
der Rest der Eingabe verworfen wird, bis eine neue Zeile erreicht ist:
if (std::cin >> name &&
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
std::getline(std::cin, state))
{ ... }
Sie müssen einschließen <limits>
, um zu verwenden std::numeric_limits
. std::basic_istream<...>::ignore()
ist eine Funktion, die eine bestimmte Anzahl von Zeichen verwirft, bis sie entweder ein Trennzeichen findet oder das Ende des Streams erreicht ( ignore()
verwirft auch das Trennzeichen, wenn es es findet). Die max()
Funktion gibt die größte Anzahl von Zeichen zurück, die ein Stream akzeptieren kann.
Eine andere Möglichkeit, das Leerzeichen zu verwerfen, besteht darin, die std::ws
Funktion zu verwenden, die ein Manipulator ist, mit dem führende Leerzeichen vom Anfang eines Eingabestreams extrahiert und verworfen werden können:
if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }
Was ist der Unterschied?
Der Unterschied besteht darin, dass ignore(std::streamsize count = 1, int_type delim = Traits::eof())
3 Zeichen wahllos verwirft, bis sie entweder count
Zeichen verwirft , das Trennzeichen (angegeben durch das zweite Argument delim
) findet oder das Ende des Streams erreicht. std::ws
wird nur zum Verwerfen von Leerzeichen vom Anfang des Streams verwendet.
Wenn Sie formatierte Eingaben mit unformatierten Eingaben mischen und verbleibende Leerzeichen verwerfen müssen, verwenden Sie std::ws
. Andernfalls verwenden Sie, wenn Sie ungültige Eingaben löschen müssen, unabhängig davon, um was es sich handelt ignore()
. In unserem Beispiel müssen wir nur Leerzeichen löschen, da der Stream Ihre Eingabe "John"
für die name
Variable verbraucht hat . Alles, was übrig blieb, war das Zeilenumbruchzeichen.
1: std::skipws
ist ein Manipulator, der den Eingabestream anweist, führende Leerzeichen zu verwerfen, wenn formatierte Eingaben ausgeführt werden. Dies kann mit dem std::noskipws
Manipulator ausgeschaltet werden.
2: Eingabestreams betrachten bestimmte Zeichen standardmäßig als Leerzeichen, z. B. Leerzeichen, Zeilenumbruchzeichen, Formularvorschub, Wagenrücklauf usw.
3: Dies ist die Unterschrift von std::basic_istream<...>::ignore()
. Sie können es mit null Argumenten aufrufen, um ein einzelnes Zeichen aus dem Stream zu verwerfen, einem Argument, um eine bestimmte Anzahl von Zeichen zu verwerfen, oder zwei Argumenten, um count
Zeichen zu verwerfen , oder bis es erreicht ist delim
, je nachdem, welches zuerst eintritt. Normalerweise verwenden Sie std::numeric_limits<std::streamsize>::max()
als Wert, count
wenn Sie nicht wissen, wie viele Zeichen sich vor dem Trennzeichen befinden, diese aber trotzdem verwerfen möchten.
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
sollte auch wie erwartet funktionieren. (Zusätzlich zu den Antworten unten).