Strumienie wejścia i wyjścia w C++
Wyjście
Biblioteka strumieni wejścia i wyjścia definiuje wyjście dla każdego typu wbudowanego. Ponadto można w łatwy sposób zdefiniować wyjście dla typu zdefiniowanego przez użytkownika. Operator << (wyślij do) służy jako operator wyjściowy na obiektach typu ostream; cout to standardowy strumień wyjściowy, a cerr to standardowy strumień do raportowania błędów. Domyślnie wartości wysyłane do cout są konwertowane na sekwencje znaków. Aby na przykład wysłać na wyjście liczbę 10, należy napisać:
void f()
{
cout << 10;
}
Instrukcja ta spowoduje wysłanie do standardowego strumienia wyjściowego znaków 1 i 0. Równie dobrze można by było to napisać tak:
void g()
{
int i {10};
cout << i;
}
Można też wysyłać dane różnych typów:
void h(int i)
{
cout << "Wartość i wynosi ";
cout << i;
cout << ' ';
}
Dla wywołania h(10) wynik byłby następujący:
Wpisywanie nazwy strumienia wyjściowego, gdy trzeba go użyć kilka razy po kolei, szybko się nudzi. Na szczęście wyniku wyrażenia wyjściowego można użyć do dalszego wysyłania danych, np.:
{
cout << "Wartość i wynosi " << i << ' ';
}
Efekt działania tej funkcji h2() jest taki sam jak wynik działania funkcji h().
Stała znakowa to znak napisany w pojedynczym cudzysłowie. Należy podkreślić, że znaki są wysyłane na wyjście właśnie jako znaki, a nie wartości liczbowe. Na przykład:
{
int b = 'b'; // uwaga: char niejawnie przekonwertowany na int
char c = 'c';
cout << 'a' << b << c;
}
Wartość liczbowa znaku b to 98 (w kodowaniu ASCII zastosowanym w użytej przeze mnie implementacji języka C++), a więc wynikiem działania tej funkcji będzie napis a98c.
Wejście
Obsługa wejścia w bibliotece standardowej jest realizowana przez bibliotekę istream. Podobnie jak ostream, także istream operuje na znakowych łańcuchowych reprezentacjach typów wbudowanych i może zostać rozszerzona o obsługę typów zdefiniowanych przez użytkownika. Operatorem wejścia jest >> (pobierz z), a standardowy strumień wejściowy to cin. Typ prawego argumentu operatora >> określa, jakie dane są przyjmowane i gdzie mają zostać zapisane.
Na przykład:
{
int i;
cin >> i; // wczytuje liczbę typu int do i
double d;
cin >> d; // wczytuje liczbę typu double do d
}
Powyższa funkcja wczytuje liczby w rodzaju 1234 do zmiennej całkowitoliczbowej i oraz liczby zmiennoprzecinkowe w rodzaju 12.34e5 do zmiennej zmiennoprzecinkowej o podwójnej precyzji d. Często trzeba wczytać szereg znaków. Dobrym sposobem na to jest wczytywanie ich do łańcucha string, np.:
{
cout << "Napisz, jak się nazywasz ";
string str;
cin >> str;
cout << "Cześć, " << str << "! ";
}
Jeśli wpiszesz Stefan, to program wyświetli napis:
Domyślnie biały znak (7.3.2), np. spacja, oznacza koniec wczytywania. Jeśli więc wpiszesz Stefan Batory, chcąc podszyć się pod jednego z polskich królów, to i tak otrzymasz odpowiedź:
Aby wczytać całą linijkę tekstu (z białymi znakami włącznie), należy użyć funkcji getline(), np.:
{
cout << "Napisz, jak się nazywasz ";
string str;
getline(cin,str);
cout << "Cześć, " << str << "! ";
}
Ta funkcja wyświetli to, co chcemy:
Znak nowego wiersza znajdujący się na końcu wiersza kodu został odrzucony, więc strumień cin jest gotowy na przyjęcie kolejnej linijki. Standardowe łańcuchy mają tę bardzo pożyteczną cechę, że rozszerzają się, aby zmieściło się w nich to, co chcemy w nich zapisać. Nie trzeba z góry obliczać ich maksymalnego rozmiaru. Jeśli więc ktoś wpisze kilka megabajtów średników, program je wyświetli.
Wejście i wyjście typów zdefiniowanych przez użytkownika
Oprócz wejścia i wyjścia typów wbudowanych oraz standardowych łańcuchów przy użyciu biblioteki iostream można także definiować operacje wejścia i wyjścia dla typów zdefiniowanych przez użytkownika. Weźmy jako przykład prosty typ o nawie Entry, który mógłby służyć do przechowywania pozycji książki telefonicznej:
string name;
int number;
};
Można zdefiniować prosty operator wyjściowy do drukowania pozycji książki w formacie {"nazwisko",numer} podobnym do tego, którego używamy w kodzie do inicjacji:
{
return os << "{"" << e.name << "", " << e.number << "}";
}
Operator wyjściowy zdefiniowany przez użytkownika pobiera swój strumień wyjściowy (przez referencję) jako pierwszy argument i zwraca go w wyniku. Odpowiedni operator wejściowy jest bardziej skomplikowany, bo musi sprawdzać, czy formatowanie jest prawidłowe, oraz obsługiwać błędy:
// wczytuje parę { "nazwisko" , numer }. Uwaga: formatowanie przy użyciu { " " i }
{
char c, c2;
if (is>>c && c=='{' && is>>c2 && c2=='"') { // czy na początku jest {"
string name; // domyślna wartość łańcucha to pusty łańcuch: ""
while (is.get(c) && c!='"') // wszystko przed " jest częścią nazwiska
name+=c;
if (is>>c && c==',') {
int number = 0;
if (is>>number>>c && c=='}') { // wczytuje numer i }
e = {name,number}; // przypisanie do pozycji
return is;
}
}
}
is.setf(ios_base::failbit); // rejestracja niepowodzenia w strumieniu
return is;
}
Operacja wejściowa zwraca referencję do swojego strumienia istream, którą można wykorzystać w celu sprawdzenia, czy operacja się powiodła. Przykładowo w warunku instrukcja is>>c oznacza: „Czy udało się wczytać z is do c?”.
Instrukcja is>>c domyślnie pomija białe znaki, a is.get(c) nie, a więc ten operator wejściowy typu Entry ignoruje białe znaki poza łańcuchem nazwiska, ale nie wewnątrz niego. Na przykład:
{"Michael Edward Palin",987654}
Taką parę wartości można wczytać z wejścia do Entry w następujący sposób:
cout << ee << ' '; // wydruk ee w cout
Wynik będzie następujący:
{"Michael Edward Palin", 987654}
Język C++. Kompendium wiedzy. Wydanie IV, Autor: Bjarne Stroustrup, Wydawnictwo: Helion