« Obsługa strumienia wejściowego, lekcja »
Rozdział 9. Omówienie obsługi standardowego wejścia za pomocą strumienia std::cin oraz przedstawienie sposobu walidacji wczytywanych danych. (lekcja)
Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Zarejestruj się!
Autor: Piotr Szawdyński
Kurs C++

Obsługa strumienia wejściowego

[lekcja] Rozdział 9. Omówienie obsługi standardowego wejścia za pomocą strumienia std::cin oraz przedstawienie sposobu walidacji wczytywanych danych.
Idąc małymi krokami do przodu nadszedł czas na obsługę strumienia wejściowego, czyli mówiąc prościej na wczytywanie danych. Zagadnienie to o ile jest stosunkowo proste do zrozumienia to wymaga ono sporej otoczki związanej z kontrolowaniem błędnie wprowadzonych danych. Zanim jednak dojdziemy do tych zagadnień zajmijmy się najpierw podstawami.

Wczytywanie danych

Wczytywanie danych do zmiennych odbywa się za pomocą strumienia std::cin. Aby móc wczytać dane znajdujące się na standardowym wejściu, należy wykorzystać do tego celu operator >>. Wczytanie danych wygląda więc następująco:
C/C++
std::cin >> nazwa_zmiennej;
W powyższy sposób możemy wczytywać wszystkie podstawowe typy danych - poczynając od liczb całkowitych, przechodząc przez liczby rzeczywiste oraz znaki, a kończąc na tekście. W niniejszym rozdziale skupimy się tylko i wyłącznie na wczytywaniu liczb całkowitych i rzeczywistych. Tekstem zajmiemy się w dalszej części kursu.
Rozpatrzmy teraz następujący przykład:
C/C++
#include <iostream>
int main()
{
    int a;
    float b;
    std::cout << "Podaj liczbe calkowita: ";
    std::cin >> a;
    std::cout << "Podaj liczbe rzeczywista: ";
    std::cin >> b;
    std::cout << "Liczba a = " << a << std::endl;
    std::cout << "Liczba b = " << b << std::endl;
    return 0;
}
Powyższy program zapyta się użytkownika o podanie dwóch liczb. Pierwszą z nich ma być liczba całkowita, a drugą liczba rzeczywista. Jeśli użytkownik będzie posłuszny, program wyświetli przykładowo następującą treść:
Podaj liczbe calkowita: 13
Podaj liczbe rzeczywista: 16.6
Liczba a = 13
Liczba b = 16.6
Problem zaczyna się pojawiać jednak wtedy, gdy mamy do czynienia z Yeti, czyli kimś kto niekoniecznie chce używać programu zgodnie z wolą programisty.

Sprawdzanie poprawności wprowadzonych danych

Wczujmy się przez chwilę w złośliwego użytkownika i zacznijmy działać tak jak on - wprowadźmy więc zamiast liczby literę. Ja wprowadziłem literę a i otrzymałem następujący wynik na ekranie:
Podaj liczbe calkowita: a
Podaj liczbe rzeczywista: Liczba a = 4273126
Liczba b = 3.21401e-039
Jak widać już na pierwszy rzut oka program zrobił dwie złe rzeczy. Pierwsza - nie wczytał liczby całkowitej; druga - pominął wczytywanie liczby rzeczywistej. Jako programiści powinniśmy zadać jednak pytanie: czy udało się wczytać dane?. Dopiszmy więc dwa nowe wiersze do programu po każdym wczytywaniu danych:
C/C++
std::cout << "Czy udalo sie wczytac? " << std::cin.good() << std::endl;
std::cout << "Czy cos nawalilo? " << std::cin.fail() << std::endl;
Po dokonaniu powyższej zmiany program będzie wyglądał tak:
C/C++
#include <iostream>
int main()
{
    int a;
    float b;
    std::cout << "Podaj liczbe calkowita: ";
    std::cin >> a;
    std::cout << "Czy udalo sie wczytac? " << std::cin.good() << std::endl;
    std::cout << "Czy cos nawalilo? " << std::cin.fail() << std::endl;
   
    std::cout << "Podaj liczbe rzeczywista: ";
    std::cin >> b;
    std::cout << "Czy udalo sie wczytac? " << std::cin.good() << std::endl;
    std::cout << "Czy cos nawalilo? " << std::cin.fail() << std::endl;
   
    std::cout << "Liczba a = " << a << std::endl;
    std::cout << "Liczba b = " << b << std::endl;
    return 0;
}
W programie pojawiły się dwa nowe zapisy: std::cin.good() oraz std::cin.fail(). good() i fail() są  metodami należącymi do strumienia std::cin. Za pomocą nich możemy odczytać stany strumienia. Są nimi odpowiednio: czy wczytywanie się powiodło (dla metody good()), oraz czy wystąpiły jakieś błędy (dla metody fail()).
Posiadając wiedzę na temat tego czy udało się wczytać poprawnie dane czy też nie - będziemy mogli podjąć w przyszłości odpowiednie działania. Na chwilę obecną nie potrafisz jednak sterować przebiegiem programu dlatego też powrócimy do tego zagadnienia ponownie, gdy będziesz miał odpowiednią wiedzę.

Opis działania strumienia wejściowego

Skoro nauczyliśmy się już korzystać ze strumienia wejściowego w podstawowym wymiarze - przyjrzyjmy się teraz jego działaniu. Wyobraźmy więc sobie, że początkowo strumień jest pusty. Wysyłamy następnie żądanie: "daj mi liczbę całkowitą" (czyli: std::cin>>liczba). Strumień jest pusty, więc nie można z niego pobrać danych, a więc użytkownik musi wprowadzić nowe dane do strumienia. Wprowadźmy teraz do strumienia następujące dane:
12345, 321. Czy 2+2 wynosi 4?
Po wciśnięciu klawisza ENTER dane te trafiają do bufora strumienia wejściowego, z którego następnie odczytywane są dane. Po wczytaniu liczby w buforze strumienia wejściowego zostaną następujące dane:
, 321. Czy 2+2 wynosi 4?
Co się teraz stanie, gdy zechcemy wczytać kolejną liczbę? Strumień stwierdzi, że pierwszym znakiem w strumieniu jest znak
,
, który nie jest liczbą, a więc nie zostanie wczytana liczba. Operacja wczytywania się nie powiedzie, a flaga błędu zostanie ustawiona.

Białe znaki

Białymi znakami nazywamy te, które nie mają swojej reprezentacji wizualnej, a istnieją w tekście. Białe znaki to: spacja, enter i tabulacja.

Strumień, a białe znaki

Gdy używamy strumienia std::cin>> białe znaki są pomijane. Tak więc gdyby w buforze strumienia nie znajdował się przecinek, tylko biały znak - strumień by go po prostu pominął i przeszedł do kolejnego znaku. W konsekwencji druga operacja wczytywania liczby powiodłaby się, a w strumieniu pozostałyby dane:
. Czy 2+2 wynosi 4?

Czyszczenie zawartości strumienia wejściowego

Jeśli chcemy mieć większą kontrolę nad strumieniem wejściowym to powinniśmy czyścić jego zawartość przed każdym wczytaniem danych. Aby to zrobić musimy wywołać dwie metody strumienia std::cin. Pierwszą z nich jest std::cin.clear(), która czyści flagi błędu. Drugą metodą jest std::cin.sync(), która czyści bufor strumienia. Kod, który wyczyści zawartość bufora będzie wyglądał następująco:
C/C++
std::cin.clear();
std::cin.sync();

Problemy z czyszczeniem strumienia pod Linuksem

Jeżeli jesteś szczęśliwym posiadaczem Linuksa i uczysz się programowania właśnie pod nim, niestety możesz doświadczyć nieprzyjemnej sytuacji, w której wiersz
std::cin.sync();
 po prostu nie zadziała. Rozwiązaniem powyższego problemu może być użycie metody ignore, dostępnej w strumieniu std::cin. Wspomnianą metodę należy użyć w następujący sposób:
C/C++
//tu wczytanie danych ze strumienia za pomocą std::cin>>
std::cin.clear();
std::cin.ignore( 1000, '\n' );
Powyższy zapis ignoruje do 1000 znaków znajdujących się w strumieniu wejściowym. Jeżeli w strumieniu wejściowym zostanie napotkany znak nowego wiersza '\n' proces ignorowania znaków zostanie zakończony. Proces ignorowania znaków nie zostanie jednak zakończony gdy w buforze nie będzie więcej znaków do odczytania. Jeżeli chcesz używać tej techniki to należy czyścić strumień wejściowy po każdym wczytaniu danych by uniknąć ewentualnych problemów technicznych związanych ze sposobem działania tej metody.

Jeżeli nie chcesz uzależniać się od konkretnej stałej liczbowej, która mówi ile maksymalnie znaków może zostać pominiętych, możesz użyć poniższego zapisu:
C/C++
#include <limits>
int liczba;
std::cin >> liczba;
bool bCzySukces = std::cin.good();
std::cin.clear();
std::cin.ignore( std::numeric_limits < std::streamsize >::max(), '\n' );
//(...) tu dalsza część programu
//INFO: zmienna bCzySukces zawiera informację czy udało się wczytać dane
Powyższy kod spowoduje wczytanie liczby, a następnie pominie wszystkie znaki znajdujące się w buforze aż do napotkania znaku przejścia do nowej linii '\n'.

Pamiętaj, że użycie std::numeric_limits wymaga dodatkowo dołączenia biblioteki limits na początku programu.

Pamiętaj, że:

Efekt działania zapisów
cin.clear();
 oraz
cin.sync();
 będzie możliwy do zaobserwowania tylko i wyłącznie po wprowadzeniu błędnych danych do programu. Przykładem błędnych danych jest wprowadzanie tekstu zamiast liczb.

Zadanie domowe

Napisz program, który wczyta trzy liczby rzeczywiste, a na końcu programu je wszystkie wypisze. Zadbaj o to, by bufor strumienia wejściowego był za każdym razem czyszczony. Wynik końcowy powinien również zawierać informacje czy wczytanie danej liczby się powiodło.

Przykładowe dane wejściowe

13.3
tak 123
33.22nie

Przykładowe dane wyjściowe

Liczba pierwsza to: 13.3. Wczytano? 1.
Liczba druga to: 0. Wczytano? 0.
Liczba trzecia to: 33.22. Wczytano? 1.

Wskazówka

Informacje o poprawnym (albo niepoprawnym) wczytaniu danych należy przechować w dodatkowych zmiennych typu bool.
Poprzedni dokumentNastępny dokument
Pojęcie zmiennej i podstawowe typy danychOperacje matematyczne