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
}
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
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/AR8lBcin.sync()
nic nie robi i coś mi mówi, żelibstdc++
ma taką właśnie implementację - znalazłem nawet zgłoszenie buga, żesync
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
. Funkcjagetchar
wg standardu zwracaint
więcint
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, afflush(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.