Dlaczego mielibyśmy wywoływać cin.clear () i cin.ignore () po przeczytaniu wejścia?

86

Samouczek C ++ Google Code University używał tego kodu:

// 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;
}

Jakie jest znaczenie cin.clear()i cin.ignore()? Dlaczego parametry 10000i są \nkonieczne?

JacKeown
źródło
1
To jest mój najbardziej pozytywny post wszechczasów. Musiałem osiągnąć szczyt w liceum.
JacKeown

Odpowiedzi:

102

cin.clear()Czyści flagę błędu na cin(tak, aby przyszłe operacje We / Wy będzie działać poprawnie), a następnie cin.ignore(10000, '\n')przechodzi do następnego wiersza (zignorować cokolwiek innego na tej samej linii, co braku numeru, dzięki czemu nie powoduje innej awarii parse) . Pominie tylko do 10000 znaków, więc kod zakłada, że ​​użytkownik nie umieści bardzo długiej, nieprawidłowej linii.

Jeremiah Willcock
źródło
57
+1. Chcę dodać, że zamiast ignorować do 10000 znaków, lepiej byłoby użyć cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');.
Dennis,
30
Jeśli chcesz skorzystać std::numeric_limits, upewnij się, że tak #include <limits>.
gbmhunter
1
Czy ktoś może wyjaśnić składnię std::numeric_limits<std::streamsize>::max()?
Minh Tran,
4
@Minh Tran: std :: streamsize jest całką ze znakiem, która podaje liczbę przesłanych znaków we / wy lub rozmiar bufora we / wy. Tutaj, używając klasy szablonu "numeric_limits", chcieliśmy poznać maksymalny limit bufora I / O lub przesyłanych znaków.
Pankaj
1
@Minh Tran: tylko jedna drobna korekta „streamsize” nie jest klasą, to tylko typ całkowy. Możemy również uzyskać limit innych, tj. int, char itp. sprawdź to pod adresem [link] ( en.cppreference.com/w/cpp/types/numeric_limits )
Pankaj
46

Wchodzisz do

if (!(cin >> input_var))

instrukcja, jeśli wystąpi błąd podczas pobierania danych wejściowych z cin. Jeśli wystąpi błąd, ustawiana jest flaga błędu i przyszłe próby uzyskania danych wejściowych zakończą się niepowodzeniem. Dlatego potrzebujesz

cin.clear();

aby pozbyć się flagi błędu. Ponadto wejście, które się nie powiodło, będzie znajdować się w tym, co zakładam, jest jakimś rodzajem bufora. Gdy spróbujesz ponownie uzyskać dane wejściowe, odczyta to samo wejście w buforze i ponownie się nie powiedzie. Dlatego potrzebujesz

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

Pobiera 10000 znaków z bufora, ale zatrzymuje się, jeśli napotka znak nowej linii (\ n). 10000 to tylko ogólna duża wartość.

lot
źródło
11
Po prostu dodam, że domyślną wartością ignorowania jest pomijanie pojedynczego znaku, więc potrzebujesz większej liczby, aby pominąć cały wiersz.
Bo Persson
22

Dlaczego używamy:

1) cin.ignore

2) cin. Clear

?

Po prostu:

1) Aby zignorować (wyodrębnić i odrzucić) wartości, których nie chcemy w strumieniu

2) Aby wyczyścić wewnętrzny stan strumienia. Po użyciu cin.clear stan wewnętrzny jest ponownie ustawiany na goodbit, co oznacza, że ​​nie ma żadnych „błędów”.

Długa wersja:

Jeśli coś jest umieszczane w „strumieniu” (cin), to musi być stamtąd zabrane. Przez „wzięty” rozumiemy „używany”, „usunięty”, „wyodrębniony” ze strumienia. Strumień ma przepływ. Dane płyną na cin jak woda w strumieniu. Po prostu nie można zatrzymać przepływu wody;)

Spójrz na przykład:

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

Co się stanie, jeśli użytkownik odpowie: „Arkadiusz Włodarczyk” na pierwsze pytanie?

Uruchom program i przekonaj się sam.

Na konsoli zobaczysz „Arkadiusza”, ale program nie zapyta Cię o „wiek”. Po prostu skończy się zaraz po wydrukowaniu "Arkadiusza".

A "Włodarczyk" nie jest pokazywany. Wygląda na to, że zniknął (?) *

Co się stało? ;-)

Bo między "Arkadiuszem" a "Włodarczykiem" jest przestrzeń.

Znak "spacji" między imieniem i nazwiskiem jest dla komputera znakiem, że istnieją dwie zmienne oczekujące na wyodrębnienie ze strumienia „wejściowego”.

Komputer myśli, że chcesz wysłać do wejścia więcej niż jedną zmienną. Ten znak „spacji” jest dla niego znakiem, aby interpretował to w ten sposób.

Czyli komputer przypisuje „Arkadiusz” do „imienia” (2) i ponieważ umieścisz więcej niż jeden ciąg na strumieniu (wejściu), komputer będzie próbował przypisać wartość „Włodarczyk” zmiennej „wiek” (!). Użytkownik nie będzie miał szansy wstawić niczego do 'cin' w linii 6, ponieważ ta instrukcja została już wykonana (!). Czemu? Ponieważ wciąż coś zostało w strumieniu. I jak powiedziałem wcześniej, strumień płynie, więc wszystko trzeba z niego jak najszybciej usunąć. I taka możliwość pojawiła się, gdy komputer zobaczył instrukcję cin >> wiek;

Komputer nie wie, że utworzyłeś zmienną przechowującą wiek kogoś (wiersz 4). „wiek” to tylko etykieta. Dla komputerów „wiek” można by równie dobrze nazwać: „afsfasgfsagasggas” i byłoby to to samo. Dla niego to tylko zmienna, do której będzie próbował przypisać "Włodarczyk", ponieważ w linii (6) kazałeś / nakazałeś komputerowi zrobić to.

To źle, ale hej, to ty to zrobiłeś! To Twoja wina! Cóż, może użytkownik, ale nadal ...


W porządku, w porządku. Ale jak to naprawić ?!

Spróbujmy trochę pobawić się tym przykładem, zanim naprawimy go poprawnie, aby dowiedzieć się kilku ciekawszych rzeczy :-)

Wolę przyjąć podejście, w którym rozumiemy rzeczy. Naprawianie czegoś bez wiedzy, jak to zrobiliśmy, nie daje satysfakcji, nie sądzisz? :)

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 :-)

Po wywołaniu powyższego kodu zauważysz, że stan twojego strumienia (cin) jest równy 4 (linia 7). Co oznacza, że ​​jego stan wewnętrzny nie jest już równy goodbit. Coś jest nie w porządku. To całkiem oczywiste, prawda? Próbowałeś przypisać wartość typu string („Wlodarczyk”) do zmiennej typu int „age”. Typy nie pasują. Czas poinformować, że coś jest nie tak. A komputer robi to poprzez zmianę wewnętrznego stanu strumienia. To jest tak: „Ty jebany człowieku, napraw mnie proszę. Informuję Cię 'uprzejmie' ;-)”

Po prostu nie możesz już używać „cin” (stream). Utknęło. Tak jakbyś umieścił duże kłody drewna na strumieniu wody. Musisz to naprawić, zanim będziesz mógł z niego korzystać. Dane (woda) nie mogą być już pobierane z tego strumienia (cin), ponieważ log drewna (stan wewnętrzny) nie pozwala na to.

Och, więc jeśli jest przeszkoda (kłody drewna), możemy ją po prostu usunąć za pomocą narzędzi, które są do tego przeznaczone?

Tak!

stan wewnętrzny cin ustawiony na 4 jest jak alarm, który wyje i hałasuje.

cin.clear czyści stan z powrotem do normalnego (goodbit). To tak, jakbyś przyszedł i uciszył alarm. Po prostu to odkładasz. Wiesz, że coś się wydarzyło, więc mówisz: „Można przestać hałasować, wiem, że już coś jest nie tak, zamknij się (wyczyść)”.

W porządku, zróbmy to! Użyjmy cin.clear ().

Wywołaj poniższy kod, używając "Arkadiusz Włodarczyk" jako pierwszego wejścia:

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 :-)

Z pewnością możemy zobaczyć po wykonaniu powyższego kodu, że stan jest równy goodbit.

Świetnie, więc problem został rozwiązany?

Wywołaj poniższy kod, używając "Arkadiusz Włodarczyk" jako pierwszego wejścia:

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 :-)

Nawet jeśli stan jest ustawiony na goodbit po linii 9, użytkownik nie jest pytany o „wiek”. Program się zatrzymuje.

CZEMU?!

O rany ... Właśnie odłożyłeś alarm, a co z kłodą w wodzie? * Wróć do tekstu, w którym rozmawialiśmy o "Włodarczyku", jak to podobno zniknęło.

Trzeba usunąć "Włodarczyk" ten kawałek drewna ze strumienia. Wyłączenie alarmów w ogóle nie rozwiązuje problemu. Właśnie to uciszyłeś i myślisz, że problem zniknął? ;)

Czas więc na kolejne narzędzie:

cin.ignore można porównać do specjalnej ciężarówki z linami, które przyjeżdżają i usuwają kłody drewna, które utknęły w strumieniu. Rozwiązuje problem utworzony przez użytkownika programu.

Więc czy możemy go użyć jeszcze przed uruchomieniem alarmu?

Tak:

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;

"Włodarczyk" zostanie usunięty przed hałasem w linii 7.

Co to jest 10000 i „\ n”?

Mówi, że usuń 10000 znaków (na wszelki wypadek), dopóki '\ n' nie zostanie spełniony (ENTER). BTW Można to zrobić lepiej, używając numeric_limits, ale nie jest to tematem tej odpowiedzi.


Więc główna przyczyna problemu zniknęła, zanim powstał hałas ...

Dlaczego więc potrzebujemy „jasności”?

A co by było, gdyby ktoś zapytał w linii 6 o „podaj mi swój wiek”, na przykład: „dwadzieścia lat” zamiast pisać 20?

Typy nie pasują ponownie. Komputer próbuje przypisać ciąg do int. I zaczyna się alarm. Nie masz nawet szansy zareagować na taką sytuację. cin.ignore nie pomoże Ci w takim przypadku.

Musimy więc użyć clear w takim przypadku:

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

Ale czy powinieneś wyczyścić stan „na wszelki wypadek”?

Oczywiście nie.

Jeśli coś pójdzie nie tak (cin >> wiek;) instrukcja poinformuje Cię o tym zwracając false.

Możemy więc użyć instrukcji warunkowej, aby sprawdzić, czy użytkownik umieścił w strumieniu niewłaściwy typ

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";

W porządku, więc możemy rozwiązać nasz początkowy problem, na przykład ten:

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;
}

Oczywiście można to poprawić, na przykład robiąc to, co robiłeś, używając pętli while.

PREMIA:

Możesz się zastanawiać. A co jeśli chciałbym otrzymać imię i nazwisko w tej samej linii od użytkownika? Czy jest w ogóle możliwe użycie cin, jeśli cin interpretuje każdą wartość oddzieloną „spacją” jako inną zmienną?

Jasne, możesz to zrobić na dwa sposoby:

1)

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

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

2) lub używając funkcji getline.

getline(cin, nameOfStringVariable);

i tak to się robi:

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

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

Druga opcja może przynieść odwrotny skutek, jeśli użyjesz jej po użyciu „cin” przed getline.

Sprawdźmy to:

za)

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;

Jeśli jako wiek wpiszesz „20”, nie zostaniesz poproszony o podanie imienia i nazwiska.

Ale jeśli zrobisz to w ten sposób:

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

wszystko w porządku.

CO?!

Za każdym razem, gdy umieszczasz coś na wejściu (strumieniu), zostawiasz na końcu biały znak, którym jest ENTER ('\ n'). Musisz jakoś wprowadzić wartości do konsoli. Musi się więc zdarzyć, jeśli dane pochodzą od użytkownika.

b) cechą cin jest to, że ignoruje białe znaki, więc kiedy czytasz informacje z cin, znak nowej linii „\ n” nie ma znaczenia. Jest ignorowany.

a) Funkcja getline pobiera całą linię aż do znaku nowej linii ('\ n'), a kiedy znak nowej linii jest pierwszą rzeczą, którą funkcja getline otrzymuje '\ n', i to wszystko do uzyskania. Wydobywasz znak nowej linii, który pozostawił w strumieniu przez użytkownika, który umieścił „20” w strumieniu w linii 3.

Aby to naprawić, należy zawsze wywoływać cin.ignore (); za każdym razem, gdy używasz cin do uzyskania jakiejkolwiek wartości, jeśli kiedykolwiek zamierzasz używać getline () w swoim programie.

Zatem właściwy kod to:

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;

Mam nadzieję, że strumienie są bardziej przejrzyste, wiesz.

Hah, ucisz mnie, proszę! :-)

Morfidon
źródło
1

użyj, cin.ignore(1000,'\n')aby wyczyścić wszystkie poprzednie znaki cin.get()w buforze i zdecyduje się zatrzymać, gdy napotka '\ n' lub 1000 charspierwszy.

Phy Lieng
źródło