Perché dovremmo chiamare cin.clear () e cin.ignore () dopo aver letto l'input?


86

Il tutorial C ++ di Google Code University aveva questo codice:

// Description: Illustrate the use of cin to get input
// and how to recover from errors.

#include <iostream>
using namespace std;

int main()
{
  int input_var = 0;
  // Enter the do while loop and stay there until either
  // a non-numeric is entered, or -1 is entered.  Note that
  // cin will accept any integer, 4, 40, 400, etc.
  do {
    cout << "Enter a number (-1 = quit): ";
    // The following line accepts input from the keyboard into
    // variable input_var.
    // cin returns false if an input operation fails, that is, if
    // something other than an int (the type of input_var) is entered.
    if (!(cin >> input_var)) {
      cout << "Please enter numbers only." << endl;
      cin.clear();
      cin.ignore(10000,'\n');
    }
    if (input_var != -1) {
      cout << "You entered " << input_var << endl;
    }
  }
  while (input_var != -1);
  cout << "All done." << endl;

  return 0;
}

Qual è il significato di cin.clear()e cin.ignore()? Perché sono necessari i parametri 10000e \n?


2
Questo è di gran lunga il mio post più votato di tutti i tempi. Devo aver raggiunto il picco al liceo.
JacKeown

Risposte:


104

Il cin.clear()cancella il flag di errore su cin(in modo che il futuro operazioni di I / O funziona correttamente), e poi cin.ignore(10000, '\n')salta alla riga successiva (ad ignorare qualsiasi altra cosa sulla stessa linea come il non-numero in modo che non provoca un altro fallimento parse) . Salterà solo fino a 10000 caratteri, quindi il codice presume che l'utente non inserirà una riga molto lunga e non valida.


59
+1. Voglio aggiungere che invece di ignorare fino a 10000 caratteri, sarebbe meglio usare cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');.
Dennis

31
Se vuoi usare std::numeric_limits, assicurati di farlo #include <limits>.
gbmhunter

1
Qualcuno può spiegare la sintassi std::numeric_limits<std::streamsize>::max()?
Minh Tran

4
@Minh Tran: std :: streamsize è un integrale con segno che fornisce il numero di caratteri i / o trasferiti o la dimensione del buffer I / O. Qui, usando la classe template "numeric_limits" volevamo conoscere il limite massimo del buffer I / O o del carattere trasferito.
Pankaj

1
@ Minh Tran: solo una piccola correzione "streamsize" non è una classe, è solo un tipo integrale. Possiamo ottenere anche il limite di altri, ad es. int, char ecc. controllalo su [link] ( en.cppreference.com/w/cpp/types/numeric_limits )
Pankaj

47

Entra nel file

if (!(cin >> input_var))

se si verifica un errore quando si prende l'input da cin. Se si verifica un errore, viene impostato un flag di errore e i futuri tentativi di ottenere input falliranno. Ecco perché hai bisogno

cin.clear();

per sbarazzarsi del flag di errore. Inoltre, l'input che non è riuscito si troverà in quello che presumo sia una sorta di buffer. Quando si tenta di ottenere nuovamente l'input, leggerà lo stesso input nel buffer e fallirà di nuovo. Ecco perché hai bisogno

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

Prende 10000 caratteri dal buffer ma si ferma se incontra una nuova riga (\ n). 10000 è solo un valore generico di grandi dimensioni.


11
Aggiungendo solo che l'impostazione predefinita per ignorare è saltare un singolo carattere, quindi è necessario un numero maggiore per saltare un'intera riga.
Bo Persson,

24

Perché usiamo:

1) cin.ignore

2) cin.clear

?

Semplicemente:

1) Ignorare (estrarre e scartare) valori che non vogliamo nello stream

2) Per cancellare lo stato interno del flusso. Dopo aver utilizzato cin.clear, lo stato interno viene nuovamente impostato su goodbit, il che significa che non ci sono "errori".

Versione lunga:

Se qualcosa viene messo in "stream" (cin), deve essere preso da lì. Con "preso" si intende "usato", "rimosso", "estratto" dallo stream. Il flusso ha un flusso. I dati scorrono su cin come l'acqua nel flusso. Semplicemente non puoi fermare il flusso dell'acqua;)

Guarda l'esempio:

string name; //line 1
cout << "Give me your name and surname:"<<endl;//line 2
cin >> name;//line 3
int age;//line 4
cout << "Give me your age:" <<endl;//line 5
cin >> age;//line 6

Cosa succede se l'utente risponde: "Arkadiusz Wlodarczyk" alla prima domanda?

Esegui il programma per vedere di persona.

Sulla console vedrai "Arkadiusz" ma il programma non ti chiederà l '"età". Finirà subito dopo la stampa di "Arkadiusz".

E "Wlodarczyk" non è mostrato. Sembra che se ne fosse andato (?) *

Quello che è successo? ;-)

Perché c'è uno spazio tra "Arkadiusz" e "Wlodarczyk".

Il carattere "spazio" tra il nome e il cognome è un segno per il computer che ci sono due variabili in attesa di essere estratte sul flusso di 'input'.

Il computer pensa che tu stia vincolando per inviare in input più di una variabile. Quel segno "spazio" è per lui un segno per interpretarlo in questo modo.

Quindi il computer assegna "Arkadiusz" a 'nome' (2) e poiché metti più di una stringa nello stream (input) il computer proverà ad assegnare il valore "Wlodarczyk" alla variabile 'age' (!). L'utente non avrà la possibilità di mettere nulla sul 'cin' nella riga 6 perché quell'istruzione è già stata eseguita (!). Perché? Perché c'era ancora qualcosa in streaming. E come ho detto prima il flusso è in un flusso, quindi tutto deve essere rimosso da esso il prima possibile. E la possibilità arrivò quando il computer vide le istruzioni cin >> età;

Il computer non sa che hai creato una variabile che memorizza l'età di qualcuno (riga 4). "età" è semplicemente un'etichetta. Per "età" dei computer si potrebbe anche chiamare: "afsfasgfsagasggas" e sarebbe lo stesso. Per lui è solo una variabile a cui tenterà di assegnare "Wlodarczyk" perché hai ordinato / istruito al computer di farlo in linea (6).

È sbagliato farlo, ma hey sei tu che l'hai fatto! È colpa tua! Beh, forse utente, ma comunque ...


Tutto bene tutto bene. Ma come risolverlo ?!

Proviamo a giocare un po 'con questo esempio prima di risolverlo correttamente per imparare alcune cose più interessanti :-)

Preferisco fare un approccio in cui capiamo le cose. Riparare qualcosa senza sapere come abbiamo fatto non dà soddisfazione, non credi? :)

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate(); //new line is here :-)

Dopo aver richiamato il codice sopra, noterai che lo stato del tuo stream (cin) è uguale a 4 (riga 7). Il che significa che il suo stato interno non è più uguale a goodbit. Qualcosa è incasinato. È abbastanza ovvio, no? Hai provato ad assegnare un valore di tipo stringa ("Wlodarczyk") alla variabile di tipo int 'age'. I tipi non corrispondono. È tempo di informare che qualcosa non va. E il computer lo fa modificando lo stato interno del flusso. È come: "Cazzo amico, aggiustami per favore. Ti informo 'gentilmente' ;-)"

Semplicemente non puoi più usare 'cin' (stream). È bloccato. Come se avessi messo grossi tronchi di legno nel corso d'acqua. Devi aggiustarlo prima di poterlo usare. I dati (acqua) non possono più essere ottenuti da quel flusso (cin) perché il registro del legno (stato interno) non ti consente di farlo.

Oh quindi se c'è un ostacolo (tronchi di legno) possiamo semplicemente rimuoverlo utilizzando strumenti che sono fatti per farlo?

Sì!

lo stato interno del cin impostato su 4 è come un allarme che ulula e fa rumore.

cin.clear riporta lo stato alla normalità (goodbit). È come se tu fossi venuto a silenziare l'allarme. Devi solo rimandare. Sai che è successo qualcosa, quindi dici: "Va bene smettere di fare rumore, so già che qualcosa non va, stai zitto (chiaro)".

Va bene, facciamolo! Usiamo cin.clear ().

Richiama il codice riportato di seguito utilizzando "Arkadiusz Wlodarczyk" come primo input:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl; 
cin.clear(); //new line is here :-)
cout << cin.rdstate()<< endl;  //new line is here :-)

Possiamo sicuramente vedere dopo aver eseguito il codice sopra che lo stato è uguale a goodbit.

Ottimo quindi il problema è risolto?

Richiama il codice riportato di seguito utilizzando "Arkadiusz Wlodarczyk" come primo input:

string name;
cout << "Give me your name and surname:"<<endl;
cin >> name;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << cin.rdstate() << endl;; 
cin.clear(); 
cout << cin.rdstate() << endl; 
cin >> age;//new line is here :-)

Anche se lo stato è impostato su goodbit dopo la riga 9, all'utente non viene richiesta "età". Il programma si interrompe.

PERCHÉ?!

Oh amico ... Hai appena tolto l'allarme, che ne dici del tronco di legno dentro l'acqua? * Torna al testo in cui abbiamo parlato di "Wlodarczyk" come si suppone fosse andato.

È necessario rimuovere "Wlodarczyk" quel pezzo di legno dal flusso. La disattivazione degli allarmi non risolve affatto il problema. L'hai appena messo a tacere e pensi che il problema sia sparito? ;)

Quindi è il momento per un altro strumento:

cin.ignore può essere paragonato a un camion speciale con funi che arriva e rimuove i tronchi di legno che hanno bloccato il torrente. Elimina il problema creato dall'utente del programma.

Quindi potremmo usarlo anche prima di far suonare l'allarme?

Sì:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;

Il "Wlodarczyk" verrà rimosso prima di emettere il rumore nella linea 7.

Cos'è 10000 e "\ n"?

Dice di rimuovere 10000 caratteri (per ogni evenienza) fino a quando non viene raggiunto "\ n" (INVIO). BTW Può essere fatto meglio usando numeric_limits ma non è l'argomento di questa risposta.


Quindi la causa principale del problema è scomparsa prima che si producesse il rumore ...

Perché allora abbiamo bisogno di "clear"?

E se qualcuno avesse chiesto "dammi la tua età" alla riga 6, ad esempio: "vent'anni" invece di scrivere 20?

I tipi non corrispondono di nuovo. Il computer tenta di assegnare una stringa a int. E parte l'allarme. Non hai nemmeno la possibilità di reagire a una situazione del genere. cin.ignore non ti aiuterà in questo caso.

Quindi dobbiamo usare clear in casi del genere:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
int age;
cout << "Give me your age:" << endl;
cin >> age;
cin.clear();
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

Ma dovresti cancellare lo stato "per ogni evenienza"?

Ovviamente no.

Se qualcosa va storto (cin >> age;) l'istruzione ti informerà restituendo false.

Quindi possiamo usare l'istruzione condizionale per verificare se l'utente ha inserito un tipo sbagliato nello stream

int age;
if (cin >> age) //it's gonna return false if types doesn't match
    cout << "You put integer";
else
    cout << "You bad boy! it was supposed to be int";

Va bene così possiamo risolvere il nostro problema iniziale come ad esempio quello:

string name;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow

int age;
cout << "Give me your age:" << endl;
if (cin >> age)
  cout << "Your age is equal to:" << endl;
else
{
 cin.clear();
 cin.ignore(10000, '\n'); //time to remove "Wlodarczyk" the wood log and make the stream flow
 cout << "Give me your age name as string I dare you";
 cin >> age;
}

Ovviamente questo può essere migliorato, ad esempio, facendo ciò che hai fatto in questione usando loop while.

BONUS:

Ti starai chiedendo. E se volessi ottenere nome e cognome nella stessa riga dall'utente? È anche possibile usare cin se cin interpreta ogni valore separato da "spazio" come variabile diversa?

Certo, puoi farlo in due modi:

1)

string name, surname;
cout << "Give me your name and surname:"<< endl;
cin >> name;
cin >> surname;

cout << "Hello, " << name << " " << surname << endl;

2) o utilizzando la funzione getline.

getline(cin, nameOfStringVariable);

ed è così che si fa:

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

La seconda opzione potrebbe ritorcersi contro se la usi dopo aver usato 'cin' prima della getline.

Controlliamolo:

un)

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endl;

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

Se inserisci "20" come età non ti verrà chiesto nome e cognome.

Ma se lo fai in questo modo:

b)

string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;
int age;
cout << "Give me your age:" <<endl;
cin >> age;
cout << "Your age is" << age << endll

va tutto bene.

CHE COSA?!

Ogni volta che metti qualcosa in input (stream) lasci alla fine il carattere bianco che è ENTER ('\ n') Devi in ​​qualche modo inserire dei valori nella console. Quindi deve accadere se i dati provengono dall'utente.

b) La caratteristica cin è che ignora gli spazi bianchi, quindi quando stai leggendo le informazioni da cin, il carattere di nuova riga '\ n' non ha importanza. Viene ignorato.

a) la funzione getline ottiene l'intera riga fino al carattere di nuova riga ('\ n'), e quando il carattere di nuova riga è la prima cosa la funzione getline ottiene '\ n', e questo è tutto da ottenere. Estrai il carattere di nuova riga che è stato lasciato in streaming dall'utente che ha inserito "20" nello stream nella riga 3.

Quindi per risolverlo è sempre invocare cin.ignore (); ogni volta che usi cin per ottenere un valore se hai intenzione di usare getline () all'interno del tuo programma.

Quindi il codice corretto sarebbe:

int age;
cout << "Give me your age:" <<endl;
cin >> age;
cin.ignore(); // it ignores just enter without arguments being sent. it's same as cin.ignore(1, '\n') 
cout << "Your age is" << age << endl;


string nameAndSurname;
cout << "Give me your name and surname:"<< endl;
getline(cin, nameAndSurname);

cout << "Hello, " << nameAndSurname << endl;

Spero che i flussi siano più chiari per te.

Hah zittimi per favore! :-)


1

usa cin.ignore(1000,'\n')per cancellare tutti i caratteri del precedente cin.get()nel buffer e sceglierà di fermarsi quando incontra '\ n' o il 1000 charsprimo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.