Ich habe den folgenden Code, der den Benutzer zur Eingabe seines Namens und Status auffordert:
#include <iostream>
#include <string>
int main()
{
std::string name;
std::string state;
if (std::cin >> name && std::getline(std::cin, state))
{
std::cout << "Your name is " << name << " and you live in " << state;
}
}
Was ich finde ist, dass der Name erfolgreich extrahiert wurde, aber nicht der Status. Hier ist die Eingabe und die resultierende Ausgabe:
Input: "John" "New Hampshire" Output: "Your name is John and you live in "
Warum wurde der Name des Staates in der Ausgabe weggelassen? Ich habe die richtige Eingabe gegeben, aber der Code ignoriert sie irgendwie. Warum passiert das?
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
sollte auch wie erwartet funktionieren. (Zusätzlich zu den Antworten unten).Antworten:
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: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 iststd::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.Ausführliche Erklärung:
Dies ist die Überlastung dessen
std::getline()
, was Sie angerufen haben: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 Trennzeicheninput.widen('\n')
standardmäßig auf das Zeilenumbruchzeichen, da keines angegeben wurde.Dies sind einige der Bedingungen, unter denen
std::getline()
die Eingabe beendet wird:std::basic_string<charT>
kannDie dritte Bedingung ist die, mit der wir es zu tun haben. Ihre Eingabe in
state
wird folgendermaßen dargestellt: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, dassstate
Ihre Extraktion beim letzten Aufrufenstd::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 Manipulatorstd::skipws
1 aktiviert ist. Streams verwerfen das führende Leerzeichen aus dem Stream, wenn formatierte Eingaben ausgeführt werden. 2Im Gegensatz zu den formatierten Eingabeoperatoren
std::getline()
handelt es sich um eine unformatierte Eingabefunktion. Und alle unformatierten Eingabefunktionen haben den folgenden Code etwas gemeinsam: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]:Da die obige Bedingung falsch ist, verwirft das Wachobjekt das Leerzeichen nicht. Der Grund
noskipws
, auftrue
den diese Funktion eingestellt ist,std::getline()
liegt darin, dass rohe, unformatierte Zeichen in einstd::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 verwerfenstd::getline()
(aber nach der formatierten Extraktion). Dies kann erreicht werden, indemignore()
der Rest der Eingabe verworfen wird, bis eine neue Zeile erreicht ist:Sie müssen einschließen
<limits>
, um zu verwendenstd::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). Diemax()
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: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 entwedercount
Zeichen verwirft , das Trennzeichen (angegeben durch das zweite Argumentdelim
) 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 handeltignore()
. In unserem Beispiel müssen wir nur Leerzeichen löschen, da der Stream Ihre Eingabe"John"
für diename
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 demstd::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, umcount
Zeichen zu verwerfen , oder bis es erreicht istdelim
, je nachdem, welches zuerst eintritt. Normalerweise verwenden Siestd::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.quelle
if (getline(std::cin, name) && getline(std::cin, state))
?std::stoi()
, aber dann ist es nicht so klar, dass es einen Vorteil gibt. Aber ich bevorzuge es, nurstd::getline()
für zeilenorientierte Eingaben zu verwenden und dann die Zeile auf eine sinnvolle Weise zu analysieren. Ich denke, es ist weniger fehleranfällig.std::getline()
ist, wenn Sie alle Zeichen bis zu einem bestimmten Trennzeichen erfassen und in eine Zeichenfolge eingeben möchten. Dies ist standardmäßig die neue Zeile. Wenn dieseX
Anzahl von Zeichenfolgen nur einzelne Wörter / Token sind, kann diese Aufgabe leicht ausgeführt werden>>
. Andernfalls würden Sie die erste Nummer in eine Ganzzahl mit>>
eingeben,cin.ignore()
in der nächsten Zeile aufrufen und dann eine Schleife ausführen, die Sie verwendengetline()
.Alles ist in Ordnung, wenn Sie Ihren ursprünglichen Code folgendermaßen ändern:
quelle
get()
das nächste Zeichen verbraucht wird. Es gibt auch das,(std::cin >> name).ignore()
was ich früher in meiner Antwort vorgeschlagen habe.if (getline(std::cin, name) && getline(std::cin, state))
?Dies liegt daran, dass ein impliziter Zeilenvorschub, der auch als Zeilenumbruchzeichen bezeichnet
\n
wird, an alle Benutzereingaben eines Terminals angehängt wird, da der Stream angewiesen wird, eine neue Zeile zu beginnen. Sie können dies sicher berücksichtigen, indem Siestd::getline
bei der Suche nach mehreren Zeilen von Benutzereingaben verwenden. Das Standardverhalten vonstd::getline
liest alles bis einschließlich des Zeilenumbruchzeichens\n
aus dem Eingabestreamobjekt, dasstd::cin
in diesem Fall vorhanden ist.quelle