Jak C++ rozmawia z konsolą
C++ obsługuje wejście i wyjście konsoli przez strumienie z nagłówka <iostream>. Piszesz do świata przez cout (wyjście znakowe), a czytasz od użytkownika przez cin (wejście znakowe). Używają dwóch operatorów, które znasz już jako przesunięcia bitowe, tutaj użytych w nowej roli:
<<to operator wstawiania: wysyła dane docout.>>to operator wyodrębniania: wyciąga dane zcindo zmiennej.
Łatwo zapamiętać kierunek: strzałki wskazują, w którą stronę płyną dane. Skoro umiesz już trzymać tekst w napisach, pora przenosić go do programu i z powrotem.
Wypisywanie przez cout
cout wysyła na standardowe wyjście wszystko, co do niego wstawisz. W jednej instrukcji możesz połączyć wiele <<, swobodnie mieszając tekst, liczby i zmienne.
Każde << dopisuje do tej samej linii, dopóki nie wstawisz znaku nowej linii. Masz na to dwa sposoby: '\n' wstawia znak nowej linii, a endl wstawia znak nowej linii i opróżnia bufor na ekran. Opróżnianie ma realny koszt, więc w pętli, która wypisuje tysiące linii, wybieraj '\n': strumień opróżnia się sam, gdy trzeba (i zawsze przy zakończeniu programu).
// Wystarczy przy jednorazowym komunikacie:
cout << "Done" << endl;
// W intensywnej pętli lepiej tak, bez wymuszonego opróżniania w każdej iteracji:
for (int i = 0; i < 1000000; ++i)
cout << i << '\n';
Wczytywanie przez cin i >>
cin >> variable wczytuje jeden token oddzielony białymi znakami i konwertuje go na typ zmiennej. Pomija początkowe spacje i znaki nowej linii, a zatrzymuje się na następnym białym znaku.
Ponieważ >> zatrzymuje się na białym znaku, świetnie nadaje się do pojedynczych liczb i słów, ale do całego zdania jest bezużyteczny: cin >> word dla danych hello world wczytuje tylko hello, a world zostawia w buforze na następny odczyt.
Wczytywanie całej linii przez getline
Aby złapać całą linię, razem ze spacjami, użyj getline(cin, line), które wczytuje wszystko aż do klawisza Enter do std::string.
To właściwe narzędzie zawsze, gdy dane mogą zawierać spacje: imiona i nazwiska, adresy, zdania. Czeka tu tylko jedna pułapka, która pojawia się w chwili, gdy połączysz getline z >>.
Strona o getline omawia tę funkcję osobno: własne separatory, wczytywanie do końca danych i czytanie pliku linia po linii przez std::ifstream.
Pułapka ze znakiem nowej linii przy cin + getline
To najczęstszy błąd wejścia i wyjścia w C++. Gdy wykonujesz cin >> n, wyodrębnianie wczytuje liczbę, ale zostawia znak nowej linii (wciśnięty Enter) w buforze wejścia. Następne getline od razu widzi ten pozostały znak nowej linii, uznaje linię za zakończoną i oddaje ci pusty napis, w ogóle nie czekając na dane.
int age;
string city;
cin >> age; // wpisujesz 30 i wciskasz Enter; '\n' zostaje w buforze
getline(cin, city); // czyta pozostałe '\n' -> city to "" (pusty!)
Rozwiązaniem jest odrzucenie tego pozostałego znaku nowej linii przez cin.ignore po >>, a przed getline:
cin.ignore(numeric_limits<streamsize>::max(), '\n') pomija znaki, dopóki nie zje znaku nowej linii (albo nie dojdzie do końca danych). To solidna wersja: krótsze cin.ignore() odrzuca tylko jeden znak, co się psuje, gdy użytkownik wpisze po liczbie dodatkowe spacje. Z przyzwyczajenia sięgaj po pełną formę.
Gdy wczytywanie się nie uda
Jeśli użytkownik wpisze litery tam, gdzie oczekujesz liczby, wyodrębnianie się nie udaje: cin przechodzi w stan błędu, a zmienna docelowa nie dostaje wczytanej wartości (od C++11 jest ustawiana na 0). Co gorsza, gdy cin jest w stanie błędu, każdy kolejny odczyt też jest po cichu pomijany, więc łatwo o nieskończoną pętlę.
Odzyskiwanie zawsze ma dwa kroki: cin.clear() czyści flagi błędu, żeby strumień znów nadawał się do użytku, a cin.ignore(...) wyrzuca problematyczne znaki, które nadal tkwią w buforze. Pomiń ignore, a błędne dane zostaną na miejscu, więc kolejne >> znowu się nie powiedzie: to klasyczna nieskończona pętla. Sprawdzanie cin >> n bezpośrednio w warunku działa, bo strumień w stanie błędu konwertuje się na false.
Częste błędy, których warto unikać
- Używanie
cin >> sdo zdania. Zatrzymuje się na pierwszej spacji. Do wszystkiego, co zawiera spacje, używajgetline. - Zapominanie o
cin.ignoremiędzy>>agetline. Pozostały znak nowej linii daje ci pustą linię. Najpierw wyczyść bufor. - Używanie
endlwszędzie. Każde wymusza opróżnienie bufora. Domyślnie pisz'\n', aendlzostaw na sytuacje, gdy wynik naprawdę musi pojawić się od razu. - Ignorowanie nieudanego
cin. Litery przy wczytywaniu liczby psującin; przed kolejnym odczytem zawsze wywołajclear(), a potemignore().
Najczęściej zadawane pytania
Dlaczego getline pomija dane zaraz po użyciu cin w C++?
cin >> x wczytuje liczbę, ale zostawia w buforze znak nowej linii z wciśniętego Entera. Następne getline czyta do tego pozostałego znaku nowej linii i od razu zwraca pusty napis. Najpierw go usuń: cin.ignore(numeric_limits<streamsize>::max(), '\n'); po >>, a przed getline.
Czym się różni endl od \n w C++?
Oba kończą linię, ale endl dodatkowo opróżnia bufor wyjścia na ekran, a '\n' wstawia tylko znak nowej linii. Opróżnianie kosztuje: w intensywnej pętli wybieraj '\n' i pozwól strumieniowi opróżniać się samodzielnie. Używaj endl tylko wtedy, gdy wynik naprawdę musi pojawić się natychmiast.
Jak wczytać całą linię tekstu ze spacjami w C++?
Użyj getline(cin, line), a nie cin >> line. Operator >> zatrzymuje się na pierwszym białym znaku, więc łapie tylko jedno słowo. getline wczytuje wszystko aż do klawisza Enter do std::string, razem ze spacjami.