r17-06.doc

(289 KB) Pobierz
Szablon dla tlumaczy

Rozdział 17.
Strumienie

Do tej pory używaliśmy cout do wypisywania tekstu na ekranie, zaś cin do odczytywania klawiatury, wykorzystywaliśmy je bez pełnego zrozumienia ich działania.

Z tego rozdziału dowiesz się:

·         czym są strumienie i jak się ich używa,

·         jak zarządzać wejściem i wyjściem, wykorzystując strumienie,

·         jak odczytywać i zapisywać pliki za pomocą strumieni.

 

Przegląd strumieni

C++ nie określa, w jaki sposób dane są wypisywane na ekranie lub zapisywane do pliku, ani w jaki sposób są one odczytywane przez program. Operacje te stanowią jednak podstawową część pracy z C++, więc biblioteka standardowa C++ zawiera bibliotekę iostream, która obsługuje wejście i wyjście (I/O, input-output).

Zaletą oddzielenia funkcji wejścia-wyjścia od języka i obsługiwania ich w bibliotekach jest możliwość łatwiejszego przenoszenia programów pomiędzy różnymi platformami. Dzięki temu można napisać program na komputerze PC, a następnie przekompilować go i uruchomić na stacji roboczej Sun. Producent kompilatora dostarcza odpowiednią bibliotekę i wszystko działa. Przynajmniej w teorii.

 

UWAGA              Biblioteka jest zbiorem plików .obj, które mogą być połączone z programem w celu zapewnienia dodatkowej funkcjonalności. Jest to najbardziej podstawowa forma ponownegowielokrotnego wykorzystywania kodu, i używana od czasów pierwszych programistów, ryjących zera i jedynki na ścianach jaskiń.

Kapsułkowanie

Klasy biblioteki iostream postrzegają przepływ danych z programu na ekran jako strumień, płynący bajt po bajcie. Jeśli punktem docelowym strumienia jest plik lub ekran, wtedy jego źródłem jest zwykle jakaś część programu. Gdy strumień jest odwrócony, dane mogą pochodzić z klawiatury lub z dysku i mogą „wypełniać” zmienne w programie.

Jednym z podstawowych zadań strumieni jest kapsułkowanie problemu pobierania danych z wejścia (na przykład dysku) i wysyłania ich do wyjścia (na przykład na ekran). Po stworzeniu strumienia program działa z tym strumieniem, któryprzy czym strumień zajmuje się wszystkimi szczegółami. Tę podstawową ideę ilustruje rysunek 17.1.

 

Rys. 17.1. Kapsułkowanie poprzez strumienie

 

Buforowanie

Zapis na dysk (i w mniejszym stopniu także na ekran) jest bardzo „kosztowny.” Zapis danych na dysk lub odczyt ich z dysku zajmuje (stosunkowo) dużo czasu, a podczas operacji zapisu i odczytu działanie programu zwykle jest zablokowane. Aby rozwiązać ten problem, strumienie oferują „buforowanie”. Dane są zapisywane do strumienia, ale nie są natychmiast zapisywane na dysk. Zamiast tego bufor strumienia wypełnia się danymi; gdy się całkowicie wypełni, dane są zapisywane na dysk w ramach pojedynczej operacji.

 

Wyobraźmy sobie wodę wpływającą do zbiornika przez górny zawór i wypełniającą go, lecz nie wypływającą z niego poprzez dolny zawór. Ilustruje to rysunek 17.2.

 

Rys. 17.2. Wypełnianie bufora

 

Gdy woda (dane) całkowicie wypełni zbiornik, zawór się otwiera i cała zawartość gwałtownie wypływa. Pokazuje to rysunek 17.3.

 

Rys. 17.3. Opróżnianie bufora

 

Gdy bufor się opróżni, dolny zawór zostaje zamknięty, otwiera się górny zawór i do zbiornika-bufora zaczyna napływać woda. Przedstawia to rysunek 17.4.

 

Rys. 17.4. Ponowne napełnianie bufora

 

Często zdarza się, że chcemy wypuścić wodę ze zbiornika jeszcze zanim całkowicie się wypełni. Nazywa się to „zrzucaniem bufora”. Ilustruje to rysunek 17.5.

 

Rys. 17.5. Zrzucanie bufora

 

Strumienie i bufory

Jak można było oczekiwać, C++ implementuje strumienie i bufory w sposób obiektowy.

·         Klasa streambuf zarządza buforem, zaś jej funkcje składowe oferują możliwość wypełniania, opróżniania, zrzucania i innych sposobów manipulowania buforem.

·         Klasa ios jest bazową klasą dla klas wejścia-wyjścia z użyciem strumieni. Jedną ze zmiennych składowych tej klasy jest obiekt klasy streambuf.

·         Klasy istream i ostream są wyprowadzone z klasy ios i specjalizują działanie strumieni wejściowych i wyjściowych.

·         Klasa iostream jest wyprowadzona zarówno z klasy istream, jak i ostream i dostarcza metod wejścia-wyjścia do wypisywania danych na ekranie i odczytywania ich z klawiatury.

·         Klasa fstream zapewnia wejście i wyjście z oraz do plików.

 

Standardowe obiekty wejścia-wyjścia

Gdy program C++, zawierający klasę iostream, rozpoczyna działanie, tworzy i inicjalizuje cztery obiekty:

 

UWAGA              Biblioteka klasy iostream jest dodawana przez kompilator do programu automatycznie. Aby użyć jej funkcji, musisz dołączyć do początku kodu swojego programu odpowiednią instrukcję #include.

 

·         cin (wymawiane jako „si-in”) obsługuje wprowadzanie danych ze standardowego wejścia, czyli klawiatury.

·         cout (wymawiane jako „si-aut”) obsługuje wyprowadzanie danych na standardowe wyjście, czyli ekran.

·         cerr (wymawiane jako „si-err”) obsługuje nie buforowane wyprowadzanie danych na standardowe urządzenie wyjściae dla wydruku błędów, czyli ekran. Ponieważ wyjście błędów nie jest buforowane, wszystko co zostanie wysłane do cerr, jest wypisywane na standardowym urządzeniu błędów natychmiast, bez oczekiwania na wypełnienie bufora lub nadejście polecenia zrzutu.

·         clog (wymawiane jako „si-log”) obsługuje buforowane wyprowadzanie danych na standardowe wyjście błędów, czyli ekran. Dość często to wyjście jest przekierowywane do pliku log dziennika, co zostanie opisane w następnym podrozdziale.

 

Przekierowywanie

Każde ze standardowych urządzeń, wejścia, wyjścia oraz błędów, może zostać przekierowane na inne urządzenie. Standardowe wyjście błędów często jest przekierowywane do pliku, zaś standardowe wejście i wyjście mogą zostać połączone potokowo z plikami danych wejściowych i wyjściowych. Można uzyskać ten efekt za pomocą poleceń systemu operacyjnego.

Przekierowanie oznacza powiązanie wyjścia (lub wejścia) z miejscem innym niż domyślne. Operatory przekierowania dla DOS-a i UNIKS-a to: < dla przekierowania wejścia oraz > dla przekierowania wyjścia.

Potok oznacza wykorzystanie danych wyjściowych jednego programu jako danych wejściowych drugiego.

DOS zapewnia jedynie podstawowe polecenia przekierowywania, takie jak przekierowane wyjście (>) i przekierowane wejście (<). UNIX posiada bardziej zaawansowane możliwości przekierowywania, ale rządząca nimi zasada jest ta sama: zapisz wyniki skierowane na ekran do pliku lub przekaż je potokiem do innego programu. Podobnie, dane wejściowe dla programu mogą być pobierane z pliku, a nie z domyślnej klawiatury.

Przekierowywanie jest raczej funkcją systemu operacyjnego niż bibliotek iostream. C++ zapewnia jedynie dostęp do czterech urządzeń standardowych; przekierowanie tych urządzeń w odpowiednie miejsca należy do użytkownika.

 

Wejście z użyciem cin

Globalny obiekt cin odpowiada za wejście i jest dostępny dla programu po dołączeniu biblioteki iostream. We wcześniejszych przykładach, w celu umieszczania danych w zmiennych programu, używaliśmy przeciążonego operatora ekstrakcji (>>). Jak to działa? Składnia, jak być może pamiętasz, jest następująca:

 

int someVariable;

cout << "Wpisz liczbe: ";

cin >> someVariable;

 

Globalny obiekt cout zostanie opisany w dalszej części rozdziału; na razie skupmy się na trzeciej linii, cin >> someVariable;. Czego możemy dowiedzieć się na temat cin?

Oczywiście, musi to być obiekt globalny, gdyż nie zdefiniowaliśmy go w naszym kodzie. Z doświadczenia wiemy, że cin posiada przeciążony operator ekstrakcji (>>) i że efektem tego jest wypełnienie naszej lokalnej zmiennej someVariable danymi z bufora cin.

Nie od razu możemy domyślić się, że cin przeciąża operator ekstrakcji dla bardzo różnych typów parametrów, między innymi int&, short&, long&, double&, float&, char* i tak dalej. Gdy piszemy cin >> someVariable;, analizowany jest typ zmiennej someVariable. W poprzednim przykładzie zmienna ta była typu int, więc została wywołana następująca funkcja:

 

istream & operator>> (int &)

 

Zauważ, że ponieważ parametr jest przekazywany poprzez referencję, operator ekstrakcji może działać na pierwotnej zmiennej. Listing 17.1 ilustruje użycie obiektu cin.

 

Listing 17.1. Obiekt cin obsługuje różne typy danych

  0:  //Listing 17.1 - użycie obiektu cin

  1: 

  2:  #include <iostream>

  3:  using namespace std;

  4: 

  5:  int main()

  6:  {

  7:     int myInt;

  8:     long myLong;

  9:     double myDouble;

10:     float myFloat;

11:     unsigned int myUnsigned;

12: 

13:     cout << "int: ";

14:     cin >> myInt;

15:     cout << "long: ";

16:     cin >> myLong;

17:     cout << "double: ";

18:     cin >> myDouble;

19:     cout << "float: ";

20:     cin >> myFloat;

21:     cout << "unsigned: ";

22:     cin >> myUnsigned;

23: 

24:     cout << "\n\nint:\t" << myInt << endl;

25:     cout << "long:\t" << myLong << endl;

26:     cout << "double:\t" << myDouble << endl;

27:     cout << "float:\t" << myFloat << endl;

28:     cout << "unsigned:\t" << myUnsigned << endl;

29:     return 0;

30:  }

 

Wynik

int: 2

long: 70000

double: 987654321

float: 3.33

unsigned: 25

 

 

int:    2

long:   70000

double: 9.87654e+008

float:  3.33

unsigned:       25

Analiza

W liniach od 7. do 11. są deklarowane zmienne różnych typów. W liniach od 13. do 22. użytkownik jest proszony o wprowadzenie wartości dla tych zmiennych, po czym w liniach od 24. do 28. wypisywane są (z użyciem cout) wyniki.

Wynik odzwierciedla fakt, że dane zostały umieszczone w zmiennych odpowiedniego „rodzaju” i że program działa tak, jak mogliśmy tego oczekiwać.

 

Łańcuchy

Obiekt cin może także obsługiwać argumenty w postaci łańcuchów do znaków (char*); tak więc możemy stworzyć bufor znaków i użyć cin do jego wypełnienia. Na przykład, możemy napisać:

 

char YourName[50];

cout << "Wpisz swoje imie: ";

cin >> YourName;

 

Gdy wpiszesz Jesse, zmienna YourName zostanie wypełniona znakami J, e, s, s, \0. Ostatni znak to null; cin automatycznie kończy nim łańcuch, dlatego w buforze musi być wystarczająca ilość miejsca na pomieszczenie całego łańcucha oraz kończącego go znaku null. Dla funkcji biblioteki standardowej znak null oznacza koniec łańcucha. Funkcje biblioteki standardowej zostaną omówione w rozdziale 21., „Co dalej”.

 

Problemy z łańcuchami

Pamiętając o wszystkich zaletach obiektu cin, możesz być zaskoczony, próbując wpisać do łańcucha swoje pełne imię i nazwisko. Obiekt cin traktuje białe spacje jako separatory. Gdy natrafia na spację lub znak nowej linii, zakłada, że dane wejściowe dla parametru są kompletne i, w przypadku łańcuchów, dodaje na ich końcu znak null. Problem ten ilustruje listing 17.2.

 

Listing 17.2. Próba wpisania do cin więcej niż jednego słowa

  0:  //Listing 17.2 - cin i łańcuchy znaków

  1: 

  2:  #include <iostream>

  3: 

  4:  int main()

  5:  {

  6:     char YourName[50];

  7:     std::cout << "Podaj imie: ";

  8:     std::cin >> YourName;

  9:     std::cout << "Masz na imie: " << YourName << std::endl;

10:     std::cout << "Podaj imie i nazwisko: ";

11:     std::cin >> YourName;

12:     std::cout << "Nazywasz sie: " << YourName << std::endl;

13:     return 0;

14:  }

 

Wynik

Podaj imie: Jesse

Masz na imie: Jesse

Podaj imie i nazwisko: Jesse Liberty

Nazywasz sie: Jesse

 

Analiza

W linii 6. została stworzona tablica znaków (w celu przechowania danych wprowadzanych przez użytkownika). W linii 7. użytkownik jest proszony o wpisanie jedynie imienia; imię to jest przechowywane prawidłowo, co odzwierciedla wynik.

W linii 10. użytkownik jest proszony o podanie całego nazwiska. Obiekt cin odczytuje dane wejściowe i gdy natrafia na spację pomiędzy wyrazami, umieszcza po pierwszym słowie znak null i kończy odczytywanie danych. Nie tego oczekiwaliśmy.

Aby zrozumieć, dlaczego działa to w ten sposób, przeanalizuj listing 17.3. Wyświetla on dane wprowadzone do kilku pól.

 

Listing 17.3 Wejście wielokrotne

  0:  //Listing 17.3 - działanie cin

  1: 

  2:  #include <iostream>

  3:  using namespace std;

  4: 

  5:  int main()

  6:  {

  7:     int myInt;

  8:     long myLong;

  9:     double myDouble;

10:     float myFloat;

11:     unsigned int myUnsigned;

12:     char myWord[50];

13: 

14:     cout << "int: ";

15:     cin >> myInt;

16:     cout << "long: ";

17:     cin >> myLong;

18:     cout << "double: ";

...
Zgłoś jeśli naruszono regulamin