Zabezpieczenie przed wpisywaniem liter

Bardzo często na forum pojawia się pytanie: jak zabezpieczyć program przed wpisaniem liter, gdy program oczekuje na liczby.
Najprostsze rozwiązanie w języku C++ wygląda tak:

int zmienna;
while(!(cin>>zmienna)) //dopóki strumień jest w stanie błędu -> dopóki podawane są błędne dane
{
  //ew komunikat błędu
  cin.clear(); //kasowanie flagi błędu strumienia
  cin.sync(); //kasowanie zbędnych znaków z bufora
}
//tutaj na pewno wczytano poprawne dane do zmienna

To jest dosyć bezpieczne rozwiązanie (Polecane przez B.Stroustrupa w FAQ)

template<typename T>
T io_read(const char *msg, const char *errmsg, std::istream &is = std::cin, std::ostream &os = std::cout)
{
    T result {};
    while((os << msg) && !(is >> result))
    {
        os << errmsg;
        is.clear();
        is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
    return result;
}

W języku C nie istnieje równie proste rozwiązanie tego problemu. Można spróbować użyć:

int zmienna;
while(scanf("%d", &zmienna) != 1) //dopóki nie uda się wczytać
{
  //ew. komunikat błędu
  fflush(stdin);
}

Ale nie jest to dobre rozwiązanie, ponieważ jest zależne od implementacji. fflush() nie zawsze powoduje wyczyszczenie bufora!
Rozwiązanie działające w każdej sytuacji (i implementacji) polega na żmudnym wyciągnięciu z stdin zalegających tam, zbędnych znaków (dodatkowa pętla w miejscu fflush())

int zmienna;
while(scanf("%d", &zmienna) != 1) //dopóki nie uda się wczytać
{
  //ew. komunikat błędu
  int c;
  while((c = getchar()) != '\n' && c != EOF); //pętla wyciągająca znaki z bufora
}
FAQ

14 komentarzy

fflush na stdin nie można użyć - pomijam, że w wielu miejscach to nie zadziała.

Zdaje się, że oba rozwiązania z C++ nie obsługują sytuacji z EOF.

W przypadku pierwszego rozwiązania, żywcem je skopiowałem do siebie i mam pętle nieskończoną wyświetlającą komunikat błędu:
przy czym g jest typem double ale przy int jest to samo

while (!(cin >> g))
				{
					cout << "blad ";
					cin.clear(); 
					cin.sync(); 
				}

Btw: rozwiązanie w C++ też jest zależne od implementacji. Jedyne pewne to zdaje się cin.ignore. :-) Ktoś już o tym niżej wspominał i miał rację. Ponieważ standard jest w tych kwestiach niezrozumiały polecam http://en.cppreference.com/w/cpp/io/basic_istream/sync (też niezrozumiałe, ale trochę mniej; nie lubię tego strasznego skomplikowania strumieni) Np. za SO: http://ideone.com/AR8lB cin.sync() nic nie robi i coś mi mówi, że libstdc++ ma taką właśnie implementację - znalazłem nawet zgłoszenie buga, że sync nie działa: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=46624 (oczywiście "invalid")

Drugie rozwiązanie przy kompilowaniu zawsze reaguje dopiero za drugim razem. Jak np trzeba coś wprowadzić z klawiatury. Załóżmy printf("wprowadź wiek\n") pojawia się ten komunikat i jeśli się wpisze jakąkolwiek cyfrę czy liczbę, to za pierwszym razem nic to nie daje, dopiero za drugim odbiera wprowadzone dane. Natomiast jeśli się wpisuje co innego niż cyfry, to działa natychmiast. To tak musi być?

Drugie rozwiązanie, nawet jeśli jest teoretycznie niepoprawne, to w praktyce wszędzie działa i jest łatwiejsze w napisaniu ;)

Poprawiłem błędy w drugim kodzie C, mam nadzieję, że jest dobrze.
Nie ma co się spierać o ten EOF. Funkcja getchar wg standardu zwraca int więc int należy od niej odebrać i to na pewno wtedy zadziała jak powinno.

Sugeruję też, żeby drugie (niepoprawne) rozwiązanie usunąć w ogóle.

Zmienna 'c' powinna być typu 'int'. W przeciwnym razie, jeśli 'char' jest domyślnie 'signed', znak 0xFF będzie rozpoznany jako 'EOF' (-1), bo '(signed char)0xFF == EOF'. Jeśli 'char' jest domyślnie 'unsigned' to jeszcze gorzej, 'EOF' będzie puszczone jako zwykły znak, bo '(unsigned char)EOF != EOF'.

Właśnie dlatego 'getchar' zwraca 'int' a nie 'char', aby można było rozróżnić znak (0-255) od EOF (-1). W zasadzie standard nie precyzuje ile wynosi 'EOF', nie koniecznie -1. W każdym bądź razie ile by to nie były to rzutowane na 'char' wygeneruje jeden z kodów ASCII. Standard gwarantuje, że można porównać wynik 'getchar' do EOF. Jeśli ten wynik rzutujemy na 'char' to tracimy precyzję i porównanie nie musi się powieść.

@Azrael_Valedhel nie do końca się zgodzę. Dokumentacja twierdzi:
"This effectively means that the unread characters in the buffer are discarded"
Ba, nawet jako przykład użycia pokazane jest jak czyścić strumień cin.
A co do działania fflush to jest wyraźnie napisane że nie należy go używać.

Oj do poprawy ten wpis, istream::sync() różnie działa zależnie od implementacji i od tego na jakim strumieniu jest wywołane, a fflush(stdin) to niezdefiniowane zachowanie.

mogłbyś dodać kod w delphi :P

Hm. A niedawno czytałam, że to błąd. Dzięki za wyprostowanie.

"In the C Standard Library, character read functions such as getchar return a value equal to the symbolic value (macro) EOF to indicate that an end-of-file condition has occurred"
Parafrazując: EOF w powyższym kodzie to nie jest wartość zmiennej char, a pewne makro, które ma "specjalną wartość". Funkcja getchar() sprytnie nie zwraca nam "znaku eof" (bo takowego w ascii nie ma) a jedynie ową "specjalną wartość" kiedy natrafi na prawdziwego eof'a. Stąd też kod jest poprawny ;)

Rozwiązanie c chyba nie jest zbyt dobre. char nie przechowa EOF.