Perché succede questo?
Questo ha poco a che fare con l'input che hai fornito tu stesso, ma piuttosto con il comportamento predefinito std::getline()
esibito. Quando hai fornito il tuo input per il nome ( std::cin >> name
), non solo hai inviato i seguenti caratteri, ma anche una nuova riga implicita è stata aggiunta allo stream:
"John\n"
Una nuova riga viene sempre aggiunta al tuo input quando selezioni Entero Returnquando invii da un terminale. Viene anche utilizzato nei file per spostarsi verso la riga successiva. La nuova riga viene lasciata nel buffer dopo l'estrazione name
fino alla successiva operazione di I / O in cui viene eliminata o consumata. Quando il flusso di controllo raggiunge std::getline()
, la nuova riga verrà scartata, ma l'input cesserà immediatamente. Il motivo per cui ciò accade è perché la funzionalità predefinita di questa funzione impone che dovrebbe (tenta di leggere una riga e si ferma quando trova una nuova riga).
Poiché questa nuova riga iniziale inibisce la funzionalità prevista del programma, ne consegue che deve essere saltata o ignorata in qualche modo. Un'opzione è chiamare std::cin.ignore()
dopo la prima estrazione. Scarterà il prossimo personaggio disponibile in modo che la nuova riga non sia più d'intralcio.
std::getline(std::cin.ignore(), state)
Spiegazione approfondita:
Questo è il sovraccarico di std::getline()
quello che hai chiamato:
template<class charT>
std::basic_istream<charT>& getline( std::basic_istream<charT>& input,
std::basic_string<charT>& str )
Un altro sovraccarico di questa funzione accetta un delimitatore di tipo charT
. Un carattere delimitatore è un carattere che rappresenta il confine tra le sequenze di input. Questo particolare overload imposta il delimitatore sul carattere di nuova riga input.widen('\n')
per impostazione predefinita poiché uno non è stato fornito.
Ora, queste sono alcune delle condizioni in base alle quali std::getline()
termina l'input:
- Se il flusso ha estratto il numero massimo di caratteri che un
std::basic_string<charT>
può contenere
- Se è stato trovato il carattere di fine file (EOF)
- Se è stato trovato il delimitatore
La terza condizione è quella con cui abbiamo a che fare. Il tuo input state
è rappresentato così:
"John\nNew Hampshire"
^
|
next_pointer
dov'è next_pointer
il prossimo carattere da analizzare. Poiché il carattere memorizzato nella posizione successiva nella sequenza di input è il delimitatore, std::getline()
scarterà silenziosamente quel carattere, passerà next_pointer
al successivo carattere disponibile e interromperà l'immissione. Ciò significa che il resto dei caratteri forniti rimangono ancora nel buffer per la successiva operazione di I / O. Noterai che se esegui un'altra lettura dalla riga in state
, la tua estrazione produrrà il risultato corretto come l'ultima chiamata a std::getline()
scartare il delimitatore.
Potresti aver notato che in genere non incontri questo problema durante l'estrazione con l'operatore di input formattato ( operator>>()
). Questo perché i flussi di input usano spazi bianchi come delimitatori per l'input e hanno il manipolatore std::skipws
1 impostato per impostazione predefinita. Streams eliminerà lo spazio bianco iniziale dal flusso quando inizierà a eseguire l'input formattato. 2
A differenza degli operatori di input formattati, std::getline()
è una funzione di input non formattata . E tutte le funzioni di input non formattate hanno il seguente codice in qualche modo in comune:
typename std::basic_istream<charT>::sentry ok(istream_object, true);
Quanto sopra è un oggetto sentinella che viene istanziato in tutte le funzioni di I / O formattate / non formattate in un'implementazione C ++ standard. Gli oggetti Sentry vengono utilizzati per preparare il flusso per l'I / O e per determinare se si trova o meno in uno stato di errore. Troverai solo che nelle funzioni di input non formattate , il secondo argomento del costruttore di sentry è true
. Questo argomento significa che gli spazi bianchi iniziali non verranno eliminati dall'inizio della sequenza di input. Ecco la citazione pertinente dallo Standard [§27.7.2.1.3 / 2]:
explicit sentry(basic_istream<charT, traits>& is, bool noskipws = false);
[...] Se noskipws
è zero ed è diverso da is.flags() & ios_base::skipws
zero, la funzione estrae e scarta ogni carattere fintanto che il successivo carattere di input disponibile c
è uno spazio bianco. [...]
Poiché la condizione precedente è falsa, l'oggetto sentinella non eliminerà lo spazio vuoto. Il motivo noskipws
è impostato true
da questa funzione è perché il punto std::getline()
è quello di leggere caratteri grezzi e non formattati in un std::basic_string<charT>
oggetto.
La soluzione:
Non c'è modo di fermare questo comportamento di std::getline()
. Quello che dovrai fare è scartare tu stesso la nuova riga prima std::getline()
dell'esecuzione (ma fallo dopo l'estrazione formattata). Questo può essere fatto usando ignore()
per scartare il resto dell'input fino a raggiungere una nuova riga fresca:
if (std::cin >> name &&
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n') &&
std::getline(std::cin, state))
{ ... }
Dovrai includere <limits>
per utilizzare std::numeric_limits
. std::basic_istream<...>::ignore()
è una funzione che scarta una quantità specificata di caratteri finché non trova un delimitatore o raggiunge la fine del flusso ( ignore()
scarta anche il delimitatore se lo trova). La max()
funzione restituisce il maggior numero di caratteri che un flusso può accettare.
Un altro modo per eliminare gli spazi bianchi è usare la std::ws
funzione che è un manipolatore progettato per estrarre e scartare gli spazi bianchi iniziali dall'inizio di un flusso di input:
if (std::cin >> name && std::getline(std::cin >> std::ws, state))
{ ... }
Qual è la differenza?
La differenza è che ignore(std::streamsize count = 1, int_type delim = Traits::eof())
3 scarta indiscriminatamente i caratteri finché non scarta i count
caratteri, trova il delimitatore (specificato dal secondo argomento delim
) o raggiunge la fine del flusso. std::ws
viene utilizzato solo per eliminare i caratteri di spazio vuoto dall'inizio del flusso.
Se stai mescolando un input formattato con un input non formattato e devi eliminare gli spazi vuoti residui, usa std::ws
. Altrimenti, se è necessario cancellare l'input non valido indipendentemente da cosa sia, utilizzare ignore()
. Nel nostro esempio, abbiamo solo bisogno di cancellare gli spazi vuoti poiché il flusso ha consumato il tuo input "John"
per la name
variabile. Tutto ciò che era rimasto era il carattere di nuova riga.
1: std::skipws
è un manipolatore che indica al flusso di input di eliminare gli spazi bianchi iniziali durante l'esecuzione di input formattato. Questo può essere disattivato con il std::noskipws
manipolatore.
2: I flussi di input considerano alcuni caratteri come spazi bianchi per impostazione predefinita, come il carattere spazio, il carattere di nuova riga, l'avanzamento modulo, il ritorno a capo, ecc.
3: questa è la firma di std::basic_istream<...>::ignore()
. Puoi chiamarlo con zero argomenti per scartare un singolo carattere dal flusso, un argomento per scartare una certa quantità di caratteri o due argomenti per scartare count
caratteri o finché non raggiunge delim
, a seconda di quale si verifica per primo. Normalmente usi std::numeric_limits<std::streamsize>::max()
come valore di count
se non sai quanti caratteri ci sono prima del delimitatore, ma vuoi scartarli comunque.
std::cin >> name && std::cin >> std::skipws && std::getline(std::cin, state)
dovrebbe funzionare anche come previsto. (Oltre alle risposte di seguito).