8001


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ę:

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 ponownego[Author ID1: at Thu Nov 8 11:14:00 2001 ]wielokrotnego[Author ID1: at Thu Nov 8 11:15:00 2001 ] wykorzysty[Author ID1: at Thu Nov 8 11:15:00 2001 ]w[Author ID1: at Thu Nov 8 11:15:00 2001 ]ania 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óry[Author ID1: at Thu Nov 8 11:16:00 2001 ]przy czym strumień [Author ID1: at Thu Nov 8 11:16:00 2001 ]zajmuje się wszystkimi szczegółami. Tę podstawową ideę ilustruje rysunek 17.1.

Rys. 17.1. Kapsułkowanie poprzez strumienie

0x01 graphic

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

0x01 graphic

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

0x01 graphic

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

0x01 graphic

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

0x01 graphic

Strumienie i bufory

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

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.

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: ";

19: cin >> myDouble;

20: cout << "float: ";

21: cin >> myFloat;

22: cout << "slowo: ";

23: cin >> myWord;

24: cout << "unsigned: ";

25: cin >> myUnsigned;

26:

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

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

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

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

31: cout << "slowo: \t" << myWord << endl;

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

33:

34: cout << "\n\nint, long, double, float, slowo, unsigned: ";

35: cin >> myInt >> myLong >> myDouble;

36: cin >> myFloat >> myWord >> myUnsigned;

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

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

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

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

41: cout << "slowo: \t" << myWord << endl;

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

43:

44:

45: return 0;

46: }

Wynik

int: 2

long: 30303

double: 393939397834

float: 3.33

slowo: Hello

unsigned: 85

int: 2

long: 30303

double: 3.93939e+011

float: 3.33

slowo: Hello

unsigned: 85

int, long, double, float, word, unsigned: 3 304938 393847473 6.66 bye -2

int: 3

long: 304938

double: 3.93847e+008

float: 6.66

slowo: bye

unsigned: 4294967294

Analiza

Także w tym przykładzie zostało stworzonych kilka zmiennych, tym razem obejmujących także tablicę znaków. Użytkownik jest proszony o wprowadzenie danych, które następnie zostają wypisane.

W linii 34. użytkownik jest proszony i wpisanie wszystkich danych jednocześnie, po czym każde „słowo” danych wejściowych jest przypisywane odpowiedniej zmiennej. W przypadku takiego wielokrotnego przypisania, obiekt cin musi potraktować każde słowo jako pełne dane dla każdej ze zmiennych. Gdyby obiekt cin potraktował wszystkie dane jako skierowane do jednej zmiennej, wtedy takie połączone wprowadzanie danych nie byłoby możliwe.

Zwróć uwagę, że w linii 42. ostatnim zażądanym obiektem była liczba całkowita bez znaku, lecz użytkownik wpisał wartość -2. Ponieważ cin wierzy, że odczytuje liczbę całkowitą bez znaku, wzorzec bitowy wartości -2 został odczytany jako liczba bez znaku. Wartość ta, wypisana przez cout, to 4294967294. Wartość całkowita bez znaku 4294967294 ma dokładnie ten sam wzór bitów, co wartość całkowita ze znakiem równa -2.

W dalszej części rozdziału zobaczymy, jak wpisać do bufora cały łańcuch z wieloma słowami. Na razie jednak pojawia się pytanie: w jaki sposób operator ekstrakcji daje sobie radę z łączeniem wartości?

operator>> zwraca referencję do obiektu istream

Wartością zwracaną przez cin jest referencja do obiektu istream. Ponieważ samo cin jest obiektem klasy istream, więc zwracana wartość jednej ekstrakcji może być wejściem dla następnej.

int varOne, varTwo, varThree;

cout << "Wpisz trzy liczby: ";

cin >> varOne >> varTwo >> varThree;

Gdy piszemy cin >> varOne >> varTwo >> varThree;, wtedy pierwsza ekstrakcja jest obliczana jako (cin >> varOne). Otrzymana wartość jest kolejnym obiektem istream i operator ekstrakcji tego obiektu otrzymuje zmienną varTwo. Wygląda to tak, jakbyśmy napisali:

((cin >> varOne) >> varTwo) >> varThree;

Do techniki tej wrócimy później, przy omawianiu działania obiektu cout.

Inne funkcje składowe klas[Author ID1: at Thu Nov 8 11:23:00 2001 ]y[Author ID1: at Thu Nov 8 11:23:00 2001 ]w[Author ID1: at Thu Nov 8 11:24:00 2001 ] dyspo[Author ID1: at Thu Nov 8 11:23:00 2001 ]zycji[Author ID1: at Thu Nov 8 11:24:00 2001 ] cin

Oprócz przeciążonego [Author ID1: at Thu Nov 8 11:25:00 2001 ]operatora>>[Author ID1: at Thu Nov 8 11:25:00 2001 ], obiekt [Author ID1: at Thu Nov 8 11:25:00 2001 ]cin[Author ID1: at Thu Nov 8 11:25:00 2001 ] posiada[Author ID1: at Thu Nov 8 11:25:00 2001 ]W dyspozycji obiektu [Author ID1: at Thu Nov 8 11:25:00 2001 ]cin[Author ID1: at Thu Nov 8 11:25:00 2001 ] pozostaje nie tylko[Author ID1: at Thu Nov 8 11:25:00 2001 ] operator [Author ID1: at Thu Nov 8 11:26:00 2001 ]>>[Author ID1: at Thu Nov 8 11:26:00 2001 ], ale[Author ID1: at Thu Nov 8 11:26:00 2001 ] także inne funkcje składowe. Są one używane wtedy, gdy jest wymagana bardziej precyzyjna kontrola wprowadzania danych.

Wprowadzanie pojedynczych znaków

operator>> przyjmujący referencję do znaku może być użyty do pobierania pojedynczego znaku ze standardowego wejścia. Do pobrania pojedynczego znaku można także użyć funkcji składowej get(), i to na dwa sposoby: funkcja get() może zostać wywołana bez parametrów(wtedy wykorzystywana jest wartość zwracana) lub z referencją do znaku.

Użycie get() bez parametrów

Pierwsza forma funkcji get() nie przyjmuje żadnych parametrów. Zwraca ona odczytany znak lub znak EOF (znak końca pliku, end of file) w chwili dojścia do końca pliku. Funkcja get() bez parametrów nie jest używana zbyt często. Nie ma możliwości łączenia tej funkcji z wielokrotnym wprowadzaniem, gdyż zwracaną wartością nie jest obiekt klasy iostream. Dlatego nie zadziała poniższa linia kodu:

cin.get() >> myVarOne >> myVarTwo; // niedozwolone

Wartością zwracaną przez cin.get() >> myVarOne jest wartość całkowita, a nie obiekt klasy iostream.

Najczęstsze zastosowanie funkcji get() bez parametrów przedstawia listing 17.4.

Listing 17.4. Użycie funkcji get() bez parametrów

0: // Listing 17.4 - Using get() with no parameters

1:

2: #include <iostream>

3:

4: int main()

5: {

6: char ch;

7: while ( (ch = std::cin.get()) != EOF)

8: {

9: std::cout << "ch: " << ch << std::endl;

10: }

11: std::cout << "\nGotowe!\n";

12: return 0;

13: }

Wynik

Hello

ch: H

ch: e

ch: l

ch: l

ch: o

ch:

World

ch: W

ch: o

ch: r

ch: l

ch: d

ch:

(ctrl+z)

Gotowe!

Analiza

W linii 6. została zadeklarowana lokalna zmienna znakowa ch. Pętla while przypisuje dane otrzymane od funkcji get.cin() do ch, i gdy nie jest to znak końca pliku, wypisywany jest łańcuch.

Wypisywane dane są buforowane aż do chwili osiągnięcia końca linii. Gdy zostanie napotkany znak EOF (wprowadzony w wyniku naciśnięcia kombinacji klawiszy Ctrl+Z w DOS-ie lub Ctrl+D w UNIKS-ie), następuje wyjście z pętli.

Pamiętaj, że nie każda implementacja klasy istream obsługuje tę wersję funkcji get(), mimo iż obecnie stanowi ona część standardu ANSI/ISO.

Użycie funkcji get() z parametrem w postaci referencji do znaku

Gdy jako parametr funkcji get() zostanie użyty znak, jest on wypełniany następnym znakiem ze strumienia wejściowego. Zwracaną wartością jest obiekt iostream, więc wywołanie tej funkcji get() może być łączone, co ilustruje listing 17.5.

Listing 17.5. Użycie funkcji get() z parametrem

0: // Listing 17.5 - Użycie funkcji get() z parametrem

1:

2: #include <iostream>

3:

4: int main()

5: {

6: char a, b, c;

7:

8: std::cout << "Wpisz trzy litery: ";

9:

10: std::cin.get(a).get(b).get(c);

11:

12: std::cout << "a: " << a << "\nb: ";

13: std::cout << b << "\nc: " << c << std::endl;

14: return 0;

15: }

Wynik

Wpisz trzy litery: raz

a: r

b: a

c: z

Analiza

W linii 6. zostały zadeklarowane trzy zmienne znakowe. W linii 10. zostaje trzykrotnie wywołana, w sposób połączony, funkcja cin.get(). Najpierw jest wywoływana cin.get(a). To powoduje umieszczenie pierwszej litery w zmiennej a i zwrócenie obiektu cin. W rezultacie, po powrocie z wywołania, zostaje wywołane cin.get(b), a w zmiennej b jest umieszczana następna litera. Ostatecznie wywołane zostaje cin.get(c), a w zmiennej c zostaje umieszczona trzecia litera.

Ponieważ cin.get(a) zwraca obiekt cin, moglibyśmy napisać:

cin.gat(a) >> b;

W tej formie cin.get(a) zwraca obiekt cin, więc drugą frazą jest cin >> b;.

TAK

NIE

Używaj operatora ekstrakcji (>>)wtedy, gdy chcesz pominąć białe spacje.

Używaj funkcji get() z parametrem wtedy, gdy chcesz sprawdzić każdy znak, włącznie z białymi spacjami.

Odczytywanie łańcuchów z wejścia standardowego

Operator ekstrakcji (>>) może być używany do wypełniania tablicy znaków, podobnie jak funkcje get() i getline().

Ostatnia forma funkcji get() przyjmuje trzy parametry. Pierwszym z nich jest wskaźnik do tablicy znaków, drugim parametrem jest maksymalna ilość znaków do odczytania plus jeden, zaś trzecim parametrem jest znak kończący.

Gdy jako drugi parametr podasz wartość 20, funkcja get() odczyta dziewiętnaście znaków, doda znak null, po czym umieści całość w buforze wskazywanym przez pierwszy parametr. Trzeci parametr, znak kończący, jest domyślnie znakiem nowej linii ('\n'). Gdy znak kończący zostanie napotkany przed odczytaniem maksymalnej ilości znaków, do łańcucha zostaje dodany znak null, a znak kończący pozostaje w buforze wejściowym.

Sposób użycia tej formy funkcji get() przedstawia listing 17.6.

Listing 17.6. Użycie funkcji get() z tablicą znaków

0: // Listing 17.6 - Użycie funkcji get() z tablicą znaków

1:

2: #include <iostream>

3: using namespace std;

4:

5: int main()

6: {

7: char stringOne[256];

8: char stringTwo[256];

9:

10: cout << "Wpisz pierwszy lancuch: ";

11: cin.get(stringOne,256);

12: cout << "Pierwszy lancuch: " << stringOne << endl;

13:

14: cout << "Wpisz drugi lancuch: ";

15: cin >> stringTwo;

16: cout << "Drugi lancuch: " << stringTwo << endl;

17: return 0;

18: }

Wynik

Wpisz pierwszy lancuch: Dobry zart

Pierwszy lancuch: Dobry zart

Wpisz drugi lancuch: tynfa wart

Drugi lancuch: tynfa

Analiza

W liniach 7. i 8. zostały zadeklarowane dwie tablice znaków. W linii 10. użytkownik jest proszony o wprowadze[Author ID1: at Thu Nov 8 11:27:00 2001 ]a[Author ID1: at Thu Nov 8 11:27:00 2001 ]nie łańcucha, po czym w linii 11. zostaje wywołana funkcja cin.get(). Pierwszym parametrem jest bufor do wypełnienia, zaś drugim jest zwiększona o jeden maksymalna ilość znaków, jaką funkcja get() może przyjąć (dodatkowa pozycja jest zarezerwowana dla znaku null, '\0'). Domyślnym, trzecim parametrem jest znak nowej linii.

Użytkownik wpisuje „Dobry żart”. Ponieważ fraza ta kończy się znakiem nowej linii, jest ona umieszczana wraz z kończącym znakiem null w buforze stringOne.

W linii 14. użytkownik jest proszony o wpisanie kolejnego łańcucha; tym razem do odczytania go został użyty operator ekstrakcji. Ponieważ operator ekstrakcji odczytuje znaki tylko do chwili napotkania białej spacji, w drugim buforze zostaje umieszczony wyraz „tynfa”, wraz z końcowym znakiem null. Oczywiście, nie tego chcieliśmy.

Innym sposobem rozwiązania tego problemu jest użycie funkcji getline(), co ilustruje listing 17.7.

Listing 17.7. Użycie funkcji getline()

0: // Listing 17.7 - Użycie funkcji getline()

1:

2: #include <iostream>

3: using namespace std;

4:

5: int main()

6: {

7: char stringOne[256];

8: char stringTwo[256];

9: char stringThree[256];

10:

11: cout << "Wpisz pierwszy lancuch: ";

12: cin.getline(stringOne,256);

13: cout << "Pierwszy lancuch: " << stringOne << endl;

14:

15: cout << "Wpisz drugi lancuch: ";

16: cin >> stringTwo;

17: cout << "Drugi lancuch: " << stringTwo << endl;

18:

19: cout << "Wpisz trzeci lancuch: ";

20: cin.getline(stringThree,256);

21: cout << "Trzeci lancuch: " << stringThree << endl;

22: return 0;

23: }

Wynik

Wpisz pierwszy lancuch: raz dwa trzy

Pierwszy lancuch: raz dwa trzy

Wpisz drugi lancuch: cztery piec szesc

Drugi lancuch: cztery

Wpisz trzeci lancuch: Trzeci lancuch: piec szesc

Analiza

Ten przykład wymaga dokładnego przeanalizowania, gdyż może sprawić kilka niespodzianek. W liniach od 7. do 9. zostały zadeklarowane trzy tablice znaków.

W linii 11. użytkownik jest proszony o wprowadzenie łańcucha; ten łańcuch jest odczytywany za pomocą funkcji getline(). Podobnie jak funkcja get(), funkcja getline() przyjmuje bufor i maksymalną liczbę znaków. Jednak w odróżnieniu od funkcji get(), końcowy znak nowej linii jest odczytywany i odrzucany. Funkcja get() nie odrzuca końcowego znaku nowej linii, ale pozostawia go w buforze wejściowym.

W linii 15. użytkownik jest ponownie proszony o wpisanie łańcucha; tym razem do jego odczytania zostaje użyty operator ekstrakcji. Użytkownik wpisuje cztery pięć sześć, a w tablicy [Author ID1: at Thu Nov 8 11:28:00 2001 ]buforze[Author ID1: at Thu Nov 8 11:28:00 2001 ] stringTwo umieszczane jest pierwsze słowo cztery. Następnie wyświetlany jest komunikat Wpisz trzeci łańcuch: i ponownie zostaje wywołana funkcja getline(). Ponieważ w buforze wejściowym nadal znajdują się słowa pięć sześć, zostają one natychmiast odczytane, aż do znaku nowej linii; dopiero wtedy funkcja getline() kończy działanie i łańcuch z bufora stringThree zostaje wypisany na ekranie (w 21. linii kodu).

Użytkownik nie ma możliwości wpisania trzeciego łańcucha, gdyż drugie wywołanie funkcji getline() zostaje spełnione[Author ID1: at Thu Nov 8 11:28:00 2001 ]zaspokojone[Author ID1: at Thu Nov 8 11:28:00 2001 ] przez dane pozostające jeszcze [Author ID1: at Thu Nov 8 11:28:00 2001 ]w buforze wejściowym po wywołaniu operatora ekstrakcji w linii 16.

Operator ekstrakcji (>>) odczytuje znaki aż do chwili napotkania pierwszego białego znaku, wtedy umieszcza odczytane słowo w tablicy znaków.

Funkcja składowa get() jest przeciążona. W pierwszej wersji nie przyjmuje żadnych parametrów i zwraca znak pobrany z bufora wejściowego. W drugiej wersji przyjmuje referencję do pojedynczego znaku i poprzez referencję zwraca obiekt klasy istream.

W trzeciej i ostatniej wersji funkcja get() przyjmuje tablicę znaków, ilość znaków do pobrania oraz znak kończący (którym domyślnie jest znak nowej linii). Ta wersja funkcji get() odczytuje znaki do tablicy aż do chwili odczytania maksymalnej ich ilości (mniejszej o jeden od[Author ID1: at Thu Nov 8 11:58:00 2001 ] wartości podanej jako jej parametr) lub do natrafienia na znak końcowyy[Author ID1: at Thu Nov 8 11:29:00 2001 ]. Gdy funkcja get() natrafi na znak końcowyy[Author ID1: at Thu Nov 8 11:29:00 2001 ], przestaje odczytywać dalsze znaki, a znak końcowy pozostawia w buforze.

Funkcja składowa getline() także przyjmuje trzy parametry: bufor do wypełnienia, zwiększoną o jeden maksymalną ilość znaków, jaką może odczytać,[Author ID1: at Thu Nov 8 11:59:00 2001 ] oraz znak końcowyy[Author ID1: at Thu Nov 8 11:29:00 2001 ]. Funkcja getline() działa tak samo, jak funkcja get() z takimi samymi parametrami, z wyjątkiem tego, że funkcja getline() odrzuca znak końcowy.

Użycie cin.ignore()

Czasem zdarza się, że chcemy pominąć znaki, które pozostały na końcu linii (do znaku EOL, end of line) lub do końca pliku (EOF, end of file). Służy do tego funkcja składowa ignore(). Funkcja ta przyjmuje dwa parametry: maksymalną ilość znaków do pominięcia oraz znak końcowy. Gdybyśmy napisali ignore(80,'\n'), zostałoby odrzuconych do osiemdziesięciu znaków. Znaleziony znak nowej linii zostałby odrzucony i funkcja zakończyłaby działanie. Użycie funkcji ignore() ilustruje listing 17.8.

Listing 17.8. Użycie funkcji ignore()

0: // Listing 17.8 - Użycie funkcji ignore()

1: #include <iostream>

2: using namespace std;

3:

4: int main()

5: {

6: char stringOne[255];

7: char stringTwo[255];

8:

9: cout << "Wpisz pierwszy lancuch: ";

10: cin.get(stringOne,255);

11: cout << "Pierwszy lancuch: " << stringOne << endl;

12:

13: cout << "Wpisz drugi lancuch: ";

14: cin.getline(stringTwo,255);

15: cout << "Drugi lancuch: " << stringTwo << endl;

16:

17: cout << "\n\nA teraz sprobuj ponownie...\n";

18:

19: cout << "Wpisz pierwszy lancuch: ";

20: cin.get(stringOne,255);

21: cout << "Pierwszy lancuch: " << stringOne<< endl;

22:

23: cin.ignore(255,'\n');

24:

25: cout << "Wpisz drugi lancuch: ";

26: cin.getline(stringTwo,255);

27: cout << "Drugi lancuch: " << stringTwo<< endl;

28: return 0;

29: }

Wynik

Wpisz pierwszy lancuch: dawno temu

Pierwszy lancuch: dawno temu

Wpisz drugi lancuch: Drugi lancuch:

A teraz sprobuj ponownie...

Wpisz pierwszy lancuch: dawno temu

Pierwszy lancuch: dawno temu

Wpisz drugi lancuch: byl sobie...

Drugi lancuch: byl sobie...

Analiza

W liniach 6. i 7. zostały stworzone dwie tablice znaków. W linii 9. użytkownik jest proszony o wprowadzenie łańcucha, więc wpisuje słowa dawno temu, po których naciska klawisz Enter. Do odczytania łańcucha zostaje w linii 10. wywołana funkcja get(). Funkcja get() wypełnia tablicę stringOne i kończy działanie na znaku nowej linii, ale nie odrzuca go, lecz pozostawia w buforze wejściowym.

W linii 13. użytkownik jest ponownie proszony o wpisanie łańcucha znaków, ale funkcja getline() w linii 14. odczytuje pozostały w buforze znak nowej linii i natychmiast po tym kończy działanie (zanim użytkownik może wpisać jakikolwiek znak).

W linii 19. użytkownik jest jeszcze raz proszony o dokonanie wpisu i wpisuje te same słowa, co na początku. Jednak tym razem, w linii 23., zostaje użyta funkcja ignore(), która „zjada” pozostający znak nowej linii. W związku z tym, gdy w linii 26. zostaje wywołana funkcja getline(), bufor wejściowy jest już pusty i użytkownik może wprowadzić następną linię historyjki.

peek() oraz putback()

Obiekt strumienia wejściowego cin posiada dwie dodatkowe metody, które mogą okazać się całkiem przydatne: funkcję peek(), która sprawdza, czy w buforze jest dostępny znak, lecz nie pobiera go, oraz funkcję putback(), która wstawia znak do strumienia wejściowego. Listing 17.9 pokazuje, w jaki sposób mogłyby zostać użyte te funkcje.

Listing 17.9. Użycie funkcji peek() i putback()

0: // Listing 17.9 - Użycie funkcji peek() i putback()

1: #include <iostream>

2: using namespace std;

3:

4: int main()

5: {

6: char ch;

7: cout << "Wpisz fraze: ";

8: while ( cin.get(ch) )

9: {

10: if (ch == '!')

11: cin.putback('$');

12: else

13: cout << ch;

14: while (cin.peek() == '#')

15: cin.ignore(1,'#');

16: }

17: return 0;

18: }

Wynik

Wpisz fraze: Nadszedl!czas#na!dobra#zabawe!

Nadszedl$czasna$dobrazabawe$

Analiza

W linii 6. została zadeklarowana zmienna znakowa ch, a w linii 7. użytkownik jest proszony o wpisanie frazy. Zadaniem tego programu jest zamiana każdego wykrzyknika (!) na znak dolara ($) oraz usunięcie każdego znaku hash (#).

Program działa w pętli aż do napotkania znaku końca pliku (Ctrl+C w Windows, Ctrl+Z[Author ID1: at Thu Nov 8 11:31:00 2001 ]C[Author ID1: at Thu Nov 8 11:31:00 2001 ] lub Ctrl+D w innych systemach operacyjnych). Pamiętajmy,[Author ID1: at Thu Nov 8 11:32:00 2001 ] że dla końca pliku [Author ID1: at Thu Nov 8 11:32:00 2001 ]funkcja cin.get() zwraca wartość 0 dla [Author ID1: at Thu Nov 8 11:32:00 2001 ]zasygnalizowania[Author ID1: at Thu Nov 8 11:33:00 2001 ] końca pliku[Author ID1: at Thu Nov 8 11:32:00 2001 ]. Jeśli bieżący znak jest wykrzyknikiem, zostaje odrzucony i do bufora wejściowego jest wstawiany znak dolara; zostanie on odczytany jako następny znak łańcucha. Jeśli bieżący znak nie jest wykrzyknikiem, zostaje wydrukowany. Każdy znak w buforze jest sprawdzany za pomocą funkcji peek() i jeśli jest to znak #, zostaje usunięty.

Nie jest to najbardziej efektywny sposób wykonania tego zadania (nie usunie znaku #, gdy będzie on pierwszym znakiem wprowadzonego łańcucha), ale ilustruje sposób działania tych metod. Nie są one wykorzystywane zbyt często i nie łam sobie głowy, próbując na siłę wymyślić jakieś ich zastosowanie. Potraktuj je jako swego rodzaju sztuczki[Author ID1: at Thu Nov 8 11:34:00 2001 ]ę[Author ID1: at Thu Nov 8 11:34:00 2001 ]; być może kiedyś do czegoś się przydadzą.

RADA Funkcje peek() i putback() są zwykle używane do przetwarzania łańcuchów i innych danych, na przykład wtedy, gdy chcemy stworzyć kompilator.

Wyjście poprzez cout

Obiektu cout, wraz z przeciążonym operatorem wstawiania (<<), używaliśmy do wypisywania na ekranie łańcuchów, liczb całkowitych i innych danych. Istnieje także możliwość formatowania wypisywanych danych, wyrównywania kolumn i wypisywania danych numerycznych w postaci dziesiętnej i szesnastkowej. Wszystkie te zagadnienia opiszemy w tym podrozdziale.

Zrzucanie zawartości bufora

Przekonaliśmy się, że użycie endl powoduje zrzucenie zawartości bufora. endl wywołuje funkcję składową flush() obiektu cout, która wypisuje wszystkie dane znajdujące się w buforze. Funkcję flush() można wywoływać bezpośrednio, albo poprzez wywołanie metody obiektu, albo poprzez napisanie:

cout << flush

Przydaje się to wtedy, gdy chcemy mieć pewność, że bufor wyjściowy jest pusty i że jego zawartość została wypisana na ekranie.

Powiązane funkcje

Działanie operatora ekstrakcji może być rozszerzone funkcjami get() i getline(); operator wstawiania także może być uzupełniony funkcjami put() oraz write().

Funkcja put() służy do wysyłania pojedynczego znaku do urządzenia wyjściowego. Ponieważ funkcja ta zwraca referencję do obiektu klasy ostream i ponieważ cout jest obiektem tej klasy, możemy łączyć wywołanie funkcji put() z wywołaniami operatora wstawiania. Ilustruje to listing 17.10.

Listing 17.10. Użycie funkcji put()

0: // Listing 17.10 - Użycie funkcji put()

1:

2: #include <iostream>

3:

4: int main()

5: {

6: std::cout.put('H').put('e').put('l').put('l').put('o').put('\n');

7: return 0;

8: }

Wynik

Hello

UWAGA Niektóre kompilatory mają problem z [Author ID1: at Thu Nov 8 11:34:00 2001 ]wypisywaniema[Author ID1: at Thu Nov 8 11:34:00 2001 ] znaków za pomocą powyższego kodu. Jeśli twój kompilator nie wypisze słowa Hello, możesz pominąć ten listing.

Analiza

Linia 6. jest przetwarzana w następujący sposób: std::cout.put('H') wypisuje na ekranie literę H i zwraca obiekt cout. To pozostawia nam:

cout.put('e').put('l').put('l').put('o').put('\n');

Wypisana zostaje litera e, pozostawiając cout.put('l'). Proces się powtarza: wypisane zostają litery i zwracany jest obiekt cout, aż do chwili wypisania końcowego znaku ('\n'), po czym funkcja kończy działanie.

Funkcja write() działa podobnie jak operator wstawiania (<<), ale przyjmuje parametr określający maksymalną ilość znaków, jaka może zostać wypisana. Jej użycie pokazuje listing 17.11.

Listing 17.11. Użycie funkcji write()

0: // Listing 17.11 - Użycie funkcji write()

1: #include <iostream>

2: #include <string.h>

3: using namespace std;

4:

5: int main()

6: {

7: char One[] = "O jeden most za daleko";

8:

9: int fullLength = strlen(One);

10: int tooShort = fullLength -4;

11: int tooLong = fullLength + 6;

12:

13: cout.write(One,fullLength) << "\n";

14: cout.write(One,tooShort) << "\n";

15: cout.write(One,tooLong) << "\n";

16: return 0;

17: }

Wynik

O jeden most za daleko

O jeden most za da

O jeden most za daleko ╠└ ↕

UWAGA W twoim komputerze wynik może wyglądać nieco inaczej.

Analiza

W linii 7. jest tworzona pojedyncza fraza. W linii 9. zmienna fullLength (pełna długość) zostaje ustawiona na długość frazy, zmienna tooShort (zbyt krótka) zostaje ustawiona na długość pomniejszoną o cztery, zaś zmienna tooLong (zbyt długa) zostaje ustawiona na wartość fullLength plus sześć.

W linii 13., za pomocą funkcji write(), zostaje wypisana cała fraza. Długość została ustawiona zgodnie z faktyczną długością frazy, więc wydruk jest poprawny.

W linii 14. fraza jest wypisywana ponownie, lecz tym razem jest o cztery znaki krótsza niż pełna wersja, co odzwierciedla kolejna linia wyniku.

W linii 15. fraza jest wypisywana jeszcze raz, ale tym razem funkcja write() ma wypisać o sześć znaków za dużo. Po wypisaniu frazy wypisane zostają znaki odpowiadające wartościom sześciu bajtów z pamięci położonej bezpośrednio za frazą.

Manipulatory, znaczniki oraz instrukcje formatowania

Strumień wyjściowy posiada kilka znaczników stanu, określających aktualnie używany system liczbowy (dziesiętny lub szesnastkowy), szerokość wypisywanych pól oraz znak używany do wypełniania pól. Znacznik stanu jest bajtem, którego poszczególne bity posiadają określone znaczenia. Sposób operowania bitami zostanie opisany w rozdziale 21. Każdy ze znaczników klasy ostream może być ustawiany za pomocą funkcji składowych i manipulatorów.

Użycie cout.width()

Domyślna szerokość wydruku [Author ID1: at Thu Nov 8 11:35:00 2001 ]niku[Author ID1: at Thu Nov 8 11:35:00 2001 ] umożliwia zmieszczenie w jej obrębie wypisywanej liczby, znaku lub łańcucha znajdującego się w buforze wyjściowym. Można ją zmienić, używając funkcji width(). Ponieważ width() jest funkcją składową, musi być wywoływana z użyciem obiektu cout. Zmienia ona szerokość jedynie następnego wypisywanego pola, po czym natychmiast przywraca ustawienia domyślne. Jej użycie ilustruje listing 17.12.

Listing 17.12. Dostosowywanie szerokości wydruku [Author ID1: at Thu Nov 8 11:35:00 2001 ]niku[Author ID1: at Thu Nov 8 11:35:00 2001 ]

0: // Listing 17.12 - Dostosowywanie szerokości wydruku

1: #include <iostream>

2: using namespace std;

3:

4: int main()

5: {

6: cout << "Start >";

7: cout.width(25);

8: cout << 123 << "< Koniec\n";

9:

10: cout << "Start >";

11: cout.width(25);

12: cout << 123<< "< Nastepny >";

13: cout << 456 << "< Koniec\n";

14:

15: cout << "Start >";

16: cout.width(4);

17: cout << 123456 << "< Koniec\n";

18:

19: return 0;

20: }

Wynik

Start > 123< Koniec

Start > 123< Nastepny >456< Koniec

Start >123456< Koniec

Analiza

Pierwsze wyjście (linie od 6. do 8. kodu) wypisuje liczbę 123 w polu, którego szerokość została ustawiona na 25 w linii 7.. Odzwierciedla to pierwsza linia wyniku.

Drugie wyjście najpierw wypisuje liczbę 123 w polu o szerokości ustawionej na 25 znaków, po czym wypisuje wartość 456. Zauważ, że wartość 456 jest wypisywana w polu o szerokości umożliwiającej precyzyjne zmieszczenie tej liczby; jak wspomniano, funkcja width() odnosi się wyłącznie do następnego wypisywanego pola.

Ostatnia linia wyniku pokazuje, iż ustawienie szerokości mniejszej niż wymagana oznacza ustawienie szerokości wystarczającej na zmieszczenie wypisywanego łańcucha.

Ustawianie znaków[Author ID1: at Thu Nov 8 11:35:00 2001 ]u[Author ID1: at Thu Nov 8 11:35:00 2001 ] wypełnie[Author ID1: at Thu Nov 8 11:35:00 2001 ]a[Author ID1: at Thu Nov 8 11:35:00 2001 ]nia

Zwykle obiekt cout wypełnia puste pola (powstałe w wyniku wywołania funkcji width()) spacjami, tak jak widzieliśmy w poprzednim przykładzie. Czasem zdarza się jednak, że chcemy wypełnić ten obszar innymi znakami, na przykład gwiazdkami. W tym celu możemy wywołać funkcję fill(), jako argument przekazując jej znak, którego chcemy użyć jako znaku wypełnie[Author ID1: at Thu Nov 8 11:35:00 2001 ]a[Author ID1: at Thu Nov 8 11:35:00 2001 ]nia. Pokazuje to listing 17.13.

Listing 17.13. Użycie funkcji fill()

0: // Listing 17.13 - Użycie funkcji fill()

1:

2: #include <iostream>

3: using namespace std;

4:

5: int main()

6: {

7: cout << "Start >";

8: cout.width(25);

9: cout << 123 << "< Koniec\n";

10:

11:

12: cout << "Start >";

13: cout.width(25);

14: cout.fill('*');

15: cout << 123 << "< Koniec\n";

16: return 0;

17: }

Wynik

Start > 123< Koniec

Start >**********************123< Koniec

Analiza

Linie od 7. do 9. stanowią powtórzenie poprzedniego przykładu. Linie od 12. do 15. także stanowią powtórzenie, ale tym razem, w linii 14., jako znak wypełniania zostaje wybrana gwiazdka, co odzwierciedla druga linia wyniku.

Funkcja setf()

Obiekt iostream pamięta swój stan dzięki przechowywanym znacznikom. Możemy je ustawiać, wywołując funkcję setf() i przekazując jej jedną z predefiniowanych stałych wyliczeniowych.

Obiekty posiadają stan wtedy, gdy któraś lub wszystkie z ich danych reprezentują warunki, które mogą ulegać zmianom podczas działania programu.

Na przykład, możemy określić, czy mają być wyświetlane zera końcowee (tak by 20.00 nie zostało obcięte do 20). Aby wyłączyć zera końcowe, wywołaj setf(ios::showpoint).

Stałe wyliczeniowe należą do zakresu klasy iostream (ios) i w związku z tym są stosowane z pełną kwalifikowaną nazwą w postaci ios::nazwa_znacznika, na przykład ios::showpoint.

Używając ios::showpos można włączyć wyświetlanie znaku plus (+) przed liczbami dodatnimi. Z kolei do wyrównywania wyniku służą stałe ios::left (do lewej strony), ios::right (do prawej) lub ios::internal (znak wypełnie[Author ID1: at Thu Nov 8 11:36:00 2001 ]a[Author ID1: at Thu Nov 8 11:36:00 2001 ]nia jest wstawiany między [Author ID1: at Thu Nov 8 11:36:00 2001 ]prze znakiem[Author ID1: at Thu Nov 8 11:36:00 2001 ] przedrostkiem oznaczającym system[Author ID1: at Thu Nov 8 11:37:00 2001 ] a liczbą).

Ustawiając znaczniki, możemy także wybrać[Author ID1: at Thu Nov 8 11:37:00 2001 ]ustawić[Author ID1: at Thu Nov 8 11:37:00 2001 ] system dla [Author ID1: at Thu Nov 8 11:37:00 2001 ]wypisywanych liczb jako dziesiętny (stała ios::dec), ósemkowy (o podstawie osiem, stała ios::oct) lub szesnastkowy (o podstawie szesnaście, stała ios::hex). Te znaczniki mogą być także łączone z operatorem wstawiania. Ustawienia te ilustruje listing 17.14. Dodatkowo, listing ten pokazuje także zastosowanie manipulatora setw, który ustawia szerokość pola, ale może być przy tym łączony z operatorem wstawiania.

Listing 17.14. Użycie funkcji setf()

0: // Listing 17.14 - Użycie funkcji setf()

1: #include <iostream>

2: #include <iomanip>

3: using namespace std;

4:

5: int main()

6: {

7: const int number = 185;

8: cout << "Liczba to " << number << endl;

9:

10: cout << "Liczba to " << hex << number << endl;

11:

12: cout.setf(ios::showbase);

13: cout << "Liczba to " << hex << number << endl;

14:

15: cout << "Liczba to " ;

16: cout.width(10);

17: cout << hex << number << endl;

18:

19: cout << "Liczba to " ;

20: cout.width(10);

21: cout.setf(ios::left);

22: cout << hex << number << endl;

23:

24: cout << "Liczba to " ;

25: cout.width(10);

26: cout.setf(ios::internal);

27: cout << hex << number << endl;

28:

29: cout << "Liczba to:" << setw(10) << hex << number << endl;

30: return 0;

31: }

Wynik

Liczba to 185

Liczba to b9

Liczba to 0xb9

Liczba to 0xb9

Liczba to 0xb9

Liczba to 0x b9

Liczba to:0x b9

Analiza

W linii 7. stała typu int zostaje zainicjalizowana wartością 185. Ta wartość zostaje wypisana w linii 8.

Wartość jest wyświetlana ponownie w linii 10., ale tym razem w połączeniu z manipulatorem hex, który powoduje, że wartość ta jest wypisywana w systemie szesnastkowym (jako b9). (Szesnastkowa cyfra b odpowiada dziesiętnej liczbie 11. Jedenaście razy szesnaście równa się 176; po dodaniu 9 otrzymujemy wartość 185.)

W linii 12. zostaje ustawiony znacznik showbase. Powoduje on, że do wszystkich liczb szesnastkowych zostaje dodany przedrostek 0x (co widać w kolejnych liniach wyniku).

W linii 16. szerokość pola zostaje ustawiona na dziesięć znaków, a wypisywana wartość jest wyrównywana do prawej strony pola. W linii 20. szerokość także zostaje ustawiona na dziesięć znaków, ale tym razem wypisywana liczba zostaje wyrównana do lewej strony.

W linii 25. szerokość także zostaje ustawiona na dziesięć znaków, ale tym razem zostaje zastosowany znacznik internal. W związku z tym przedrostek (0x) jest wypisywany po lewej stronie, a sama wartość (b9) po prawej.

Na koniec, w linii 29. do ustawienia szerokości dziesięciu znaków zostaje użyty połączony [Author ID1: at Thu Nov 8 11:38:00 2001 ]operator łączenia (konkatenacji) [Author ID1: at Thu Nov 8 11:38:00 2001 ]setw(), po czym wartość jest wypisywana ponownie.

Strumienie kontra funkcja printf()

Większość implementacji C++ posiada standardowe biblioteki wejścia-wyjścia z języka C, zawierające między innymi funkcję printf(). Choć funkcja ta może być łatwiejsza w użyciu niż obiekt cout, jednak jej użycie nie jest zalecane.

Funkcja printf() nie zapewnia bezpieczeństwa typów, więc łatwo jest przypadkowo nakazać jej wypisanie wartości całkowitej jako znaku, i odwrotnie. Funkcja ta nie obsługuje klas, więc nie ma możliwości poinformowania jej jak ma wypisywać dane klasy; trzeba jej przekazywać kolejno wszystkie wartości, które mają zostać wypisane.

Ponieważ korzysta z tej funkcji duża ilość starszego kodu, w tym podrozdziale pokrótce omówimy jej działanie. Aby móc skorzystać z tej funkcji, musisz pamiętać o dołączeniu do kodu pliku nagłówkowego stdio.h. W swojej najprostszej formie funkcja printf() przyjmuje jako pierwszy parametr łańcuch formatujący, zaś jako następne parametry serię wartości.

Łańcuch formatujący jest ujętym w cudzysłowy łańcuchem znaków, zawierającym specyfikatory konwersji. Wszystkie specyfikatory konwersji muszą zaczynać się od symbolu procentów (%). Najczęściej stosowane specyfikatory zostały zebrane w tabeli 17.1.

Tabela 17.1. Powszechnie stosowane specyfikatory konwersji

Specyfikator

Zastosowanie

%s

Wypisywanie łańcuchów.

%d

Wypisywanie liczb całkowitych.

Wypisywanie długich liczb całkowitych.

Wypisywanie wartości typu double.

%f

Wypisywanie wartości typu float.

Każdy ze specyfikatorów konwersji może zawierać pole szerokości oraz pole precyzji, przedstawiane jako wartość zmiennoprzecinkowa, w której cyfry po lewej stronie kropki dziesiętnej oznaczają całkowitą szerokość pola, a cyfry po prawej stronie punktu dziesiętnego oznaczają dokładność zapisu liczb zmiennopozycyjnych. Tak więc specyfikator %5d określa szeroką na pięć cyfr liczbę całkowitą, a %15.5f jest specyfikatorem dla szerokiej na piętnaście cyfr wartości typu float, w której na części dziesiętną ma zostać przeznaczone pięć ostatnich cyfr. Różne zastosowania funkcji printf() przedstawia listing 17.15.

Listing 17.15. Drukowanie z użyciem funkcji printf()

0: //17.15 Drukowanie z użyciem funkcji printf()

1: #include <stdio.h>

2:

3: int main()

4: {

5: printf("%s","Witaj swiecie\n");

6:

7: char *phrase = "Witaj ponownie!\n";

8: printf("%s",phrase);

9:

10: int x = 5;

11: printf("%d\n",x);

12:

13: char *phraseTwo = "Oto kilka wartosci: ";

14: char *phraseThree = " i jeszcze te: ";

15: int y = 7, z = 35;

16: long longVar = 98456;

17: float floatVar = 8.8f;

18:

19: printf("%s %d %d",phraseTwo,y,z);

20: printf("%s %ld %f\n",phraseThree,longVar,floatVar);

21:

22: char *phraseFour = "Sformatowane: ";

23: printf("%s %5d %10d %10.5f\n",phraseFour,y,z,floatVar);

24:

25: return 0;

26: }

Wynik

Witaj swiecie

Witaj ponownie!

5

Oto kilka wartosci: 7 35 i jeszcze te: 98456 8.800000

Sformatowane: 7 35 8.80000

Analiza

Pierwsza instrukcja printf(), zawarta w linii 5., używa formy standardowej: nazwy printf, po której następuje ujęty w cudzysłowy łańcuch ze specyfikatorem konwersji (w tym przypadku %s), a po nim wartość, która ma zostać wstawiona w miejsce specyfikatora konwersji.

Specyfikator %s wskazuje, że jest to łańcuch, a wartością łańcucha w tym przypadku jest ujęte w cudzysłowy "Witaj swiecie".

Druga instrukcja printf() jest podobna do pierwszej, ale tym razem jako drugi parametr funkcji został użyty wskaźnik do typu char, a nie stała łańcuchowa.

Trzecia instrukcja printf(), w linii 11., używa specyfikatora konwersji dla liczb całkowitych, zaś wstawianą wartością jest zmienna całkowita x. Czwarta instrukcja printf(), w linii 19., jest już bardziej skomplikowana. Są w niej łączone trzy wartości. Dla każdej z nich istnieje specyfikator konwersji, a odpowiednie wartości są przekazywane jako rozdzielone przecinkami kolejne parametry funkcji.

Na koniec, w linii 23. specyfikatory konwersji zostają użyte do określenia szerokości i dokładności. Jak widać, jest to nieco łatwiejsze niż używanie manipulatorów.

Wspomnieliśmy wcześniej o ograniczenie funkcji printf(), które polega na braku kontroli typów. Oprócz tego, funkcji printf() nie można zadeklarować jako funkcji zaprzyjaźnionej lub funkcji składowej klasy. W związku z tym, gdy chcemy wypisywać różne dane składowe klasy, musimy jawnie przekazywać każdy z akcesorów klasy do funkcji printf().

Często zadawane pytanie

Czy możesz podsumować sposoby manipulowania wyjściem?

Odpowiedź (specjalne podziękowania dla Roberta Francisa) Aby sformatować wyjście w C++, należy użyć kombinacji znaków specjalnych, manipulatorów wyjścia oraz znaczników.

Poniższe znaki specjalne są dołączane do wyjściowego łańcucha wysyłanego do cout za pomocą operatora wstawiania:

\n - nowa linia

\r - powrót karetki

\t - tabulator

\\ - lewy ukośnik

\o[Author ID1: at Thu Nov 8 11:39:00 2001 ]ddd[Author ID1: at Thu Nov 8 11:39:00 2001 ] (liczba ósemkowa) - znak ASCII

\a - alarm (sygnał dźwiękowy).

Na przykład:

cout << "\aWystąpił błąd!\t"

powoduje powstanie sygnału dźwiękowego, wypisanie komunikatu błędu oraz przejście do następnego tabulatora. Manipulatory są używane z obiektem cout. Manipulatory przyjmujące argumenty wymagają dołączenia pliku nagłówkowego ioman[Author ID1: at Thu Nov 8 11:40:00 2001 ]in[Author ID1: at Thu Nov 8 11:40:00 2001 ]p do pliku standardowego.

Oto lista manipulatorów, które nie wymagają pliku iomanip:

flush - zrzuca bufor wyjściowy

endl - wstawia znak nowej linii i zrzuca bufor wyjściowy

oct - ustawia system wypisywanych liczb na ósemkowy

dec - ustawia system wypisywanych liczb na dziesiętny

hex - ustawia system wypisywanych liczb na szesnastkowy.

Oto lista manipulatorów wymagających pliku iomanip:

setbase(base) - ustawia system liczbowy (0 = dzies[Author ID1: at Thu Nov 8 11:40:00 2001 ]ś[Author ID1: at Thu Nov 8 11:40:00 2001 ]ię[Author ID1: at Thu Nov 8 11:40:00 2001 ]e[Author ID1: at Thu Nov 8 11:40:00 2001 ]tny, 8 = ósemkowy, 10 = dziesiętny, 16 = szesnastkowy)

setw(width) - ustawia minimalną szerokość pola

setfill(ch) - ustawia znak wypełniania pustych obszarów pola

setprecision(p) - ustawia dokładność wypisywania liczb zmiennopozycyjnych

setiosflags(f) - ustawia jeden lub więcej znaczników ios

resetiosflags(f) - przywraca stan jednego lub więcej znaczników ios.

Na przykład:

cout << setw(12) << setfill('#') << hex << x << endl;

ustawia szerokość pola na dwanaście znaków, ustawia znak wypełniania na '#', nakazuje wypisywanie liczb w systemie szesnastkowym, wypisuje wartość zmiennej x, umieszcza w buforze znak nowej linii i zrzuca zawartość bufora. Wszystkie manipulatory, z wyjątkiem flush, endl oraz setw, obowiązują aż do wprowadzenia jawnej zmiany lub końca programu. Domyślna wartość manipulatora setw jest przywracana po wykonaniu bieżącego cout.

Wraz z manipulatorami setiosflags oraz resetiosflags mogą być używane poniższe znaczniki ios:

ios::left - wyrównuje wynik do lewej strony pola

ios::right - wyrównuje wynik do prawej strony pola

ios::internal - znak lub przedrostek zostaje wyrównany do lewej, a liczba do prawej

ios::dec - dziesiętny system liczbowy

ios::oct - ósemkowy system liczbowy

ios::hex - szesnastkowy system liczbowy

ios::showbase - dodaje przedrostek 0x do liczb szesnastkowych i przedrostek 0 do liczb ósemkowych

ios::showpoint - dodaje zera końcowe, zgodnie z wymaganą dokładnością

ios::uppercase - litery w liczbach szesnastkowych i w wartościach wyświetlanych w zapisie inżynierskim[Author ID1: at Thu Nov 8 11:41:00 2001 ] wykładni[Author ID1: at Thu Nov 8 11:41:00 2001 ]czym[Author ID1: at Thu Nov 8 11:41:00 2001 ] są wyświetlane jako wielkie

ios::showpos - przed wartościami dodatnimi jest wyświetlany znak plus

ios::scientific - liczby zmiennoprzecinkowe[Author ID1: at Thu Nov 8 11:41:00 2001 ]ozycyjne[Author ID1: at Thu Nov 8 11:41:00 2001 ] są wyświetlane w zapisie inżynierskim[Author ID1: at Thu Nov 8 11:41:00 2001 ] wykładniczym[Author ID1: at Thu Nov 8 11:41:00 2001 ]

ios::fixed - liczby zmiennoprzecinkowe[Author ID1: at Thu Nov 8 11:42:00 2001 ]ozycyjne[Author ID1: at Thu Nov 8 11:42:00 2001 ] są wyświetlane w zapisie dziesiętnym.

Dodatkowe informacje na ten temat można znaleźć w pliku ios oraz w dokumentacji kompilatora.

Wejście i wyjście z użyciem plików

Strumienie zapewniają jednolity sposób obsługi danych przychodzących z klawiatury lub z [Author ID1: at Thu Nov 8 11:42:00 2001 ]twardego dysku oraz wychodzących na ekran lub do [Author ID1: at Thu Nov 8 11:42:00 2001 ]twardego[Author ID1: at Thu Nov 8 11:42:00 2001 ]y[Author ID1: at Thu Nov 8 11:42:00 2001 ] dysku[Author ID1: at Thu Nov 8 11:42:00 2001 ]. W każdym z powyższych przypadków możemy korzystać z operatorów wstawiania i ekstrakcji lub innych, powiązanych z nimi funkcji i manipulatorów. Do otwarcia i zamknięcia pliku należy użyć obiektów typu [Author ID1: at Thu Nov 8 11:42:00 2001 ]ifstream i ofstream, których działanie omówimy w kilku następnych podrozdziałach.

ofstream

Poszczególne obiekty używane do odczytu z pliku lub zapisu do pliku są nazywane obiektami ofstream. Są one wyprowadzone z używanych przez nas dotąd obiektów iostream.

Aby rozpocząć zapis do pliku, musimy najpierw stworzyć obiekt typu [Author ID1: at Thu Nov 8 11:43:00 2001 ]ofstream, a następnie powiązać go z konkretnym plikiem na dysku. Aby móc używać obiektów typu [Author ID1: at Thu Nov 8 11:43:00 2001 ]ofstream, do kodu należy dołączyć plik nagłówkowy fstream.h.

UWAGA Ponieważ plik fstream.h dołącza plik iostream.h, nie ma potrzeby jawnego dołączania tego drugiego pliku.

Stany strumieni

Obiekty typu [Author ID1: at Thu Nov 8 11:43:00 2001 ]iostream przechowują znaczniki określające stan wejścia i wyjścia. Możemy je sprawdzać za pomocą logicznych funkcji eof(), bad(), fail() oraz good(). Funkcja eof() zwraca wartość true, gdy obiekt typu [Author ID1: at Thu Nov 8 11:44:00 2001 ]iostream napotkał koniec pliku. Funkcja bad() zwraca wartość true, gdy próbujemy wykonać niedozwoloną operację. Funkcja fail()zwraca wartość true, gdy funkcja bad() zwraca wartość true lub operacja się nie powiodła. Na koniec, funkcja good() zwraca wartość true, gdy wszystkie trzy pozostałe funkcje zwracają wartość false.

Otwieranie plików dla wejścia i wyjścia

Aby otworzyć plik myfile.cpp z obiektem typu [Author ID1: at Thu Nov 8 11:44:00 2001 ]ofstream, deklarujemy egzemplarz obiektu typu [Author ID1: at Thu Nov 8 11:44:00 2001 ]ofstream i jako parametr przekazujemy mu nazwę pliku:

ofstream fout("myfile.cpp");

Otwarcie tego pliku do odczytu wygląda podobnie, ale wykorzystujemy w tym celu obiekt ifstream:

ifstream fin("myfile.cpp");

Zwróć uwagę na użyte nazwy, fout oraz fin; nazwa fout została użyta w celu zwrócenia uwagi na podobieństwo z cout, a nazwa fin w celu wykazania podobieństwa do nazwy cin.

Jedną z ważnych funkcji strumieni związanych z plikami, jest funkcja close(). Każdy tworzony obiekt strumienia otwiera plik albo do zapisu, albo do odczytu, albo do obu tych operacji. Po zakończeniu zapisywania lub odczytywania danych należy zamknąć plik funkcją close(); to zapewnia, że plik nie zostanie uszkodzony[Author ID1: at Thu Nov 8 11:44:00 2001 ]e[Author ID1: at Thu Nov 8 11:44:00 2001 ] i że zawarte w buforze dane zostaną zrzucone na dysk.

Gdy obiekty strumieni plików zostaną już powiązane z plikami na dysku, mogą zostać użyte tak samo jak wszystkie inne obiekty strumieni. Ilustruje to listing 17.16.

Listing 17.16. Otwieranie plików do odczytu i zapisu

0: //Listing 17.16 Otwieranie plików do odczytu i zapisu

1: #include <fstream>

2: #include <iostream>

3: using namespace std;

4:

5: int main()

6: {

7: char fileName[80];

8: char buffer[255]; // dla danych użytkownika

9: cout << "Nazwa pliku: ";

10: cin >> fileName;

11:

12: ofstream fout(fileName); // otwieramy do zapisu

13: fout << "Ta linia jest zapisana bezposrednio do pliku...\n";

14: cout << "Wpisz tekst dla pliku: ";

15: cin.ignore(1,'\n'); // odrzucamy znak nowej linii po nazwie pliku

16: cin.getline(buffer,255); // odczytujemy dane wprowadzone przez użytkownika

17: fout << buffer << "\n"; // i zapisujemy je do pliku

18: fout.close(); // zamykamy plik; będzie gotów do ponownego otwarcia

19:

20: ifstream fin(fileName); // ponownie otwieramy plik do odczytu

21: cout << "Oto zawartosc pliku:\n";

22: char ch;

23: while (fin.get(ch))

24: cout << ch;

25:

26: cout << "\n***Koniec zawartosci pliku.***\n";

27:

28: fin.close(); // dbałość zawsze popłaca

29: return 0;

30: }

Wynik

Nazwa pliku: test1

Wpisz tekst dla pliku: Ten tekst zostal zapisany do pliku!

Oto zawartosc pliku:

Ta linia jest zapisana bezposrednio do pliku...

Ten tekst zostal zapisany do pliku!

***Koniec zawartosci pliku.***

Analiza

W linii 7. zostaje przygotowany bufor przeznaczony na nazwę pliku, zaś w linii 8. został przygotowany bufor przeznaczony na dane wprowadzane przez użytkownika. W linii 9. użytkownik jest proszony o podanie nazwy pliku, a wprowadzona przez niego nazwa jest umieszczana w buforze fileName. W linii 12. zostaje stworzony obiekt typu [Author ID1: at Thu Nov 8 11:45:00 2001 ]ofstream o nazwie fout, który jest wiązany z nową nazwą pliku. To powoduje otwarcie pliku; jeśli plik istniał już wcześniej, jego zawartość zostaje usunięta.

W linii 13. do pliku zostaje bezpośrednio zapisany [Author ID1: at Thu Nov 8 11:45:00 2001 ]łańcuch znaków. W linii 14. użytkownik jest proszony o wpisanie danych. Znak końca linii pozostały po wpisaniu przez użytkownika nazwy pliku jest odrzucany w linii 15., po czym w linii 16. następne dane wpisane przez użytkownika są umieszczane w buforze. Dane te są w linii 17. zapisywane do pliku wraz ze znakiem nowej linii, po czym w linii 18. następuje zamknięcie pliku.

W linii 20. plik jest otwierany ponownie, tym razem w trybie do odczytu danych, po czym w liniach 23. i 24. jego zwartość jest odczytywana i wypisywana na ekranie (każdy znak wypisywany jest osobno).

Zmiana domyślnego zachowania obiektu ofstream w trakcie otwierania pliku

Domyślnym działaniem[Author ID1: at Thu Nov 8 11:46:00 2001 ] wykonywanym przy otwieraniu pliku jest stworzenie pliku, gdy jeszcze nie istnieje, lub obcięcie pliku (tj. usunięcie całej jego zawartości), gdy już istnieje. Gdy takie zachowanie domyślne nie jest pożądane, możemy jawnie przekazać drugi argument do konstruktora obiektu typu [Author ID1: at Thu Nov 8 11:46:00 2001 ]ofstream.

Dostępne argumenty to:

Dla ciekawych: nazwa app pochodzi od słowa append (dołącz), ate od at end (na końcu) oraz trunc od truncate (obetnij). Listing 17.17 ilustruje użycie dołączania (append) [Author ID1: at Thu Nov 8 11:46:00 2001 ]przez otwarcie pliku stworzonego w listingu 17.16 i dopisanie do niego dalszej [Author ID1: at Thu Nov 8 11:47:00 2001 ]treści.

Listing 17.17. Dopisywanie do końca pliku

0: //Listing 17.17 Dopisywanie do końca pliku

1: #include <fstream>

2: #include <iostream>

3: using namespace std;

4:

5: int main() // zwraca 1 w przypadku błędu

6: {

7: char fileName[80];

8: char buffer[255];

9: cout << "Prosze ponownie wpisac nazwe pliku: ";

10: cin >> fileName;

11:

12: ifstream fin(fileName);

13: if (fin) // czy już istnieje?

14: {

15: cout << "Biezaca zawartosc pliku:\n";

16: char ch;

17: while (fin.get(ch))

18: cout << ch;

19: cout << "\n***Koniec zawartosci pliku.***\n";

20: }

21: fin.close();

22:

23: cout << "\nOtwieranie " << fileName << " w trybie dopisywania...\n";

24:

25: ofstream fout(fileName,ios::app);

26: if (!fout)

27: {

28: cout << "Nie mozna otworzyc " << fileName << " do dopisywania.\n";

29: return(1);

30: }

31:

32: cout << "\nWpisz tekst dla pliku: ";

33: cin.ignore(1,'\n');

34: cin.getline(buffer,255);

35: fout << buffer << "\n";

36: fout.close();

37:

38: fin.open(fileName); // ponownie przypisujemy istniejacy obiekt fin!

39: if (!fin)

40: {

41: cout << "Nie mozna otworzyc " << fileName << " do odczytu.\n";

42: return(1);

43: }

44: cout << "\nOto zawartosc pliku:\n";

45: char ch;

46: while (fin.get(ch))

47: cout << ch;

48: cout << "\n***Koniec zawartosci pliku.***\n";

49: fin.close();

50: return 0;

51: }

Wynik

Prosze ponownie wpisac nazwe pliku: test1

Biezaca zawartosc pliku:

Ta linia jest zapisana bezposrednio do pliku...

Ten tekst zostal zapisany do pliku!

***Koniec zawartosci pliku.***

Otwieranie test1 w trybie dopisywania...

Wpisz tekst dla pliku: Wiecej tekstu dla pliku!

Oto zawartosc pliku:

Ta linia jest zapisana bezposrednio do pliku...

Ten tekst zostal zapisany do pliku!

Wiecej tekstu dla pliku!

***Koniec zawartosci pliku.***

Analiza

Użytkownik jest proszony o ponowne wpisanie nazwy pliku. Tym razem w linii 12. jest tworzony obiekt strumienia wejściowego z pliku. Wynik otwierania jest sprawdzany w linii 13. i jeśli plik istnieje, jego zawartość jest wypisywana w liniach od 15. do 19. Zwróć uwagę, że if(fin) jest synonimem if(fin.good()).

Następnie zamykany jest plik wejściowy; ten sam plik zostaje otwarty ponownie w linii 25., tym razem w trybie dopisywania. Po tym otwarciu (i wszystkich innych) sprawdzamy, czy plik został otwarty poprawnie. Zwróć uwagę, że if(!fout) jest tym samym, co if(fout.fail()). Użytkownik jest proszony o wpisanie tekstu, który jest dopisywany do pliku. W linii 36. plik zostaje ponownie zamknięty.

Na koniec, tak jak w listingu 17.16, plik zostaje ponownie otwarty w trybie do odczytu. Jednak tym razem obiekt fin nie musi być deklarowany ponownie, jest mu po prostu przypisywana ta sama nazwa pliku. W linii 39. ponownie następuje sprawdzenie poprawności otwarcia i gdy wszystko jest w porządku, zawartość pliku zostaje wypisana na ekranie, a sam plik jest ostatecznie zamykany.

TAK

NIE

Sprawdzaj każde otwarcie pliku, aby mieć pewność że został on poprawnie otwarty.

Ponownie wykorzystuj istniejące obiekty ifstream lub[Author ID1: at Thu Nov 8 11:47:00 2001 ]o[Author ID1: at Thu Nov 8 11:47:00 2001 ] ofstream.

Po wykorzystaniu obiektów ofstream zamykaj je

Nie próbuj zamykać lub ponownie przypisywać cin i cout.

Pliki binarne a pliki tekstowe

Niektóre systemy operacyjne, takie jak DOS, dokonują rozróżnienia pomiędzy plikami tekstowymi a plikami binarnymi. Pliki tekstowe (jak można się domyślać) przechowują wszystko jako tekst, więc duże liczby, takie jak 54 325 są przechowywane jako łańcuchy cyfr (`5', `4', `3', `2', `5'). To może być nieefektywne, ale ma tę zaletę, że tekst może zostać odczytany przez proste programy, takie jak DOS-owy program type.

Aby dopomóc systemowi plików w odróżnieniu pliku tekstowego od binarnego, C++ udostępnia znacznik ios::binary. W wielu systemach ten znacznik jest ignorowany, gdyż wszystkie dane są przechowywane w postaci binarnej. W jeszcze innych systemach znacznik ten jest niedozwolony i uniemożliwia skompilowanie programu!

Pliki binarne mogą przechowywać nie tylko liczby i łańcuchy, ale także całe struktury danych. Można do nich zapisywać wszystkie dane jednocześnie, używając metody write() klasy fstream.

Gdy użyjemy metody write(),do odczytania zapisanych danych możemy użyć funkcji read(). Każda z tych funkcji oczekuje wskaźnika do znaku, więc adres obiektu trzeba rzutować na [Author ID1: at Thu Nov 8 11:47:00 2001 ]do[Author ID1: at Thu Nov 8 11:47:00 2001 ] wskaźnika[Author ID1: at Thu Nov 8 11:47:00 2001 ] do znaku.

Drugim argumentem obu funkcji jest ilość znaków do zapisu lub odczytu, którą można wyznaczyć za pomocą funkcji sizeof(). Pamiętaj, że zapisywane są tylko dane klasy, a [Author ID1: at Thu Nov 8 12:33:00 2001 ]nie jej [Author ID1: at Thu Nov 8 11:48:00 2001 ]metody. Odczytywane są także tylko dane. Zapis zawartości klasy do pliku ilustruje listing 17.18.

Listing 17.18. Zapis zawartości klasy do pliku

0: //Listing 17.18. Zapis zawartości klasy do pliku

1: #include <fstream>

2: #include <iostream.h>

3: using namespace std;

4:

5: class Animal

6: {

7: public:

8: Animal(int weight,long days):itsWeight(weight),DaysAlive(days){}

9: ~Animal(){}

10:

11: int GetWeight()const { return itsWeight; }

12: void SetWeight(int weight) { itsWeight = weight; }

13:

14: long GetDaysAlive()const { return DaysAlive; }

15: void SetDaysAlive(long days) { DaysAlive = days; }

16:

17: private:

18: int itsWeight;

19: long DaysAlive;

20: };

21:

22: int main() // zwraca 1 w przypadku błędu

23: {

24: char fileName[80];

25:

26:

27: cout << "Prosze wpisac nazwe pliku: ";

28: cin >> fileName;

29: ofstream fout(fileName,ios::binary);

30: if (!fout)

31: {

32: cout << "Nie mozna otworzyc " << fileName << " do zapisu.\n";

33: return(1);

34: }

35:

36: Animal Bear(50,100);

37: fout.write((char*) &Bear,sizeof Bear);

38:

39: fout.close();

40:

41: ifstream fin(fileName,ios::binary);

42: if (!fin)

43: {

44: cout << "Nie mozna otworzyc " << fileName << " do odczytu.\n";

45: return(1);

46: }

47:

48: Animal BearTwo(1,1);

49:

50: cout << "BearTwo.GetWeight(): " << BearTwo.GetWeight() << endl;

51: cout << "BearTwo.GetDaysAlive(): " << BearTwo.GetDaysAlive() << endl;

52:

53: fin.read((char*) &BearTwo, sizeof BearTwo);

54:

55: cout << "BearTwo.GetWeight(): " << BearTwo.GetWeight() << endl;

56: cout << "BearTwo.GetDaysAlive(): " << BearTwo.GetDaysAlive() << endl;

57: fin.close();

58: return 0;

59: }

Wynik

Prosze wpisac nazwe pliku: Animals

BearTwo.GetWeight(): 1

BearTwo.GetDaysAlive(): 1

BearTwo.GetWeight(): 50

BearTwo.GetDaysAlive(): 100

Analiza

W liniach od 5. do 20. została zadeklarowana okrojona klasa Animals. W liniach od 24. do 34. zostaje stworzony plik otwarty do zapisu w trybie binarnym. W linii 36. zostaje stworzony obiekt, którego waga zostaje ustawiona na 50, a wiek na 100 dni. Dane zawarte w tym obiekcie zostają w linii 37. zapisane do pliku.

W linii 39. plik jest zamykany, a w linii 41. ponownie otwierany do odczytu w trybie binarnym. W linii 48. tworzony jest drugi obiekt klasy Animal, o wadze 1 i wieku wynoszącym tylko jeden dzień. W linii 53. do nowego obiektu odczytywane są dane z pliku, zastępując istniejące dane obiektu danymi odczytanymi z pliku.

Przetwarzanie linii polecenia

Wiele systemów operacyjnych, takich jak DOS czy UNIX, umożliwia użytkownikowi przekazywanie parametrów do programu podczas jego uruchamiania. Parametry te są nazywane opcjami linii polecenia i zwykle są oddzielone od siebie spacjami. Na przykład:

SomeProgram Param1 Param2 Param3

Te parametry nie są przekazywane bezpośrednio do funkcji main(), zamiast tego funkcja main() każdego programu otrzymuje dwa parametry. Pierwszym jest ilość parametrów w linii polecenia (parametr typu int). Nazwa pliku także jest wliczana do ilości parametrów, więc każdy program posiada co najmniej jeden parametr. Przedstawiona powyżej przykładowa linia poleceń zawiera cztery parametry (nazwa programu, SomeProgram, plus trzy parametry daje cztery argumenty wywołania programu).

Drugim parametrem przekazywanym do funkcji main() jest tablica wskaźników do łańcuchów znaków. Ponieważ nazwa tablicy jest stałym wskaźnikiem do pierwszego elementu tablicy, możemy zadeklarować ten wskaźnik jako wskaźnik do wskaźnika do [Author ID1: at Thu Nov 8 11:48:00 2001 ]typu char, jako [Author ID1: at Thu Nov 8 11:49:00 2001 ]wskaźnik do tablicy elementów char lub jako [Author ID1: at Thu Nov 8 11:49:00 2001 ]wskaźnik do tablicy tablic elementów char.

Zwykle pierwszy argument ma nazwę argc (argument count, ilość argumentów), ale możesz nazwać go tak, jak chcesz. Drugi argument często nosi nazwę argv (argument vector, wektor argumentów), ale to także tylko konwencja.

Zwykle sprawdza się wartość parametru argc, aby upewnić się, czy program otrzymał oczekiwaną ilość argumentów, a następnie poprzez argv odwołuje się do samych łańcuchów parametrów. Pamiętaj, że argv[0] jest nazwą programu, a argv[1] jest jego pierwszym parametrem, reprezentowanym przez łańcuch. Jeśli argumentami programu mają być liczby, należy je zamienić z łańcuchów na wartości numeryczne. W rozdziale 21. zobaczymy, jak można wykorzystać w tym celu standardowe funkcje biblioteczne konwersji. Sposób korzystania z argumentów linii polecenia przedstawia listing 17.19.

Listing 17.19. Używanie argumentów linii polecenia

0: //Listing 17.19. Używanie argumentów linii polecenia

1: #include <iostream>

2: int main(int argc, char **argv)

3: {

4: std::cout << "Otrzymano " << argc << " argumentow...\n";

5: for (int i=0; i<argc; i++)

6: std::cout << "argument " << i << ": " << argv[i] << std::endl;

7: return 0;

8: }

Wynik

TestProgram Teach Yourself C++ In 21 Days

Otrzymano 7 argumentow...

argument 0: TestProgram.exe

argument 1: Teach

argument 2: Yourself

argument 3: C++

argument 4: In

argument 5: 21

argument 6: Days

UWAGA Musisz uruchomić ten kod albo z linii poleceń (tj. z okna DOS-a) lub ustawić parametry linii polecenia w kompilatorze (zajrzyj do dokumentacji swojego kompilatora).

Analiza

Funkcja main() deklaruje dwa argumenty: argc jest zmienną całkowitą zawierającą ilość argumentów linii polecenia, a argv jest wskaźnikiem do tablicy łańcuchów. Każdy łańcuch w tablicy wskazywanej przez argv jest jednym argumentem linii polecenia. Zauważ, że argv mogłoby równie łatwo zostać zadeklarowane jako char *argv[] lub char argv[][]. Jest to zależne wyłącznie od stylu programowania. nawet mimo iż [Author ID1: at Thu Nov 8 11:51:00 2001 ]w[Author ID1: at Thu Nov 8 11:50:00 2001 ]W[Author ID1: at Thu Nov 8 11:51:00 2001 ] tym programie, mimo iż[Author ID1: at Thu Nov 8 11:51:00 2001 ] argv zostało zadeklarowane jako wskaźnik do wskaźnika, to [Author ID1: at Thu Nov 8 11:51:00 2001 ]jednak do dostępu[Author ID1: at Thu Nov 8 11:52:00 2001 ] do poszczególnych[Author ID1: at Thu Nov 8 11:52:00 2001 ]kolejnych [Author ID1: at Thu Nov 8 11:52:00 2001 ] łańcuchów (będących argumentami z linii poleceń) [Author ID1: at Thu Nov 8 11:52:00 2001 ]odwołujemy się jak do [Author ID1: at Thu Nov 8 11:53:00 2001 ]nadal są wykorzystywane[Author ID1: at Thu Nov 8 11:53:00 2001 ] elementów[Author ID1: at Thu Nov 8 11:53:00 2001 ]y[Author ID1: at Thu Nov 8 11:53:00 2001 ] tablicy.

W linii 4. argument argc zostaje wykorzystany przy wypisywaniu ilości argumentów linii polecenia: siedem, wliczając w to nazwę programu.

W liniach 5. i 6. zostaje wypisany każdy z argumentów linii polecenia; do cout przekazywane są zakończone zerem łańcuchy znaków pobierane z tablicy łańcuchów.

Bardziej popularne zastosowanie argumentów linii polecenia zostało przedstawione na listingu 17.20, powstałym w wyniku zmodyfikowania listingu 17.18 tak, by program [Author ID1: at Thu Nov 8 11:54:00 2001 ]odczytywał nazwę pliku z linii polecenia.

Listing 17.20. Użycie argumentów linii polecenia

0: //Listing 17.20. Użycie argumentów linii polecenia

1: #include <fstream>

2: #include <iostream>

3: using namespace std;

4:

5: class Animal

6: {

7: public:

8: Animal(int weight,long days):itsWeight(weight),DaysAlive(days){}

9: ~Animal(){}

10:

11: int GetWeight()const { return itsWeight; }

12: void SetWeight(int weight) { itsWeight = weight; }

13:

14: long GetDaysAlive()const { return DaysAlive; }

15: void SetDaysAlive(long days) { DaysAlive = days; }

16:

17: private:

18: int itsWeight;

19: long DaysAlive;

20: };

21:

22: int main(int argc, char *argv[]) // zwraca 1 w przypadku błędu

23: {

24: if (argc != 2)

25: {

26: cout << "Uzycie: " << argv[0] << " <nazwa_pliku>" << endl;

27: return(1);

28: }

29:

30: ofstream fout(argv[1],ios::binary);

31: if (!fout)

32: {

33: cout << "Nie mozna otworzyc " << argv[1] << " do zapisu.\n";

34: return(1);

35: }

36:

37: Animal Bear(50,100);

38: fout.write((char*) &Bear,sizeof Bear);

39:

40: fout.close();

41:

42: ifstream fin(argv[1],ios::binary);

43: if (!fin)

44: {

45: cout << "Nie mozna otworzyc " << argv[1] << " do odczytu.\n";

46: return(1);

47: }

48:

49: Animal BearTwo(1,1);

50:

51: cout << "BearTwo.GetWeight(): " << BearTwo.GetWeight() << endl;

52: cout << "BearTwo.GetDaysAlive(): " << BearTwo.GetDaysAlive() << endl;

53:

54: fin.read((char*) &BearTwo, sizeof BearTwo);

55:

56: cout << "BearTwo.GetWeight(): " << BearTwo.GetWeight() << endl;

57: cout << "BearTwo.GetDaysAlive(): " << BearTwo.GetDaysAlive() << endl;

58: fin.close();

59: return 0;

60: }

Wynik

BearTwo.GetWeight(): 1

BearTwo.GetDaysAlive(): 1

BearTwo.GetWeight(): 50

BearTwo.GetDaysAlive(): 100

Analiza

Deklaracja klasy Animal jest taka sama jak na listingu 17.18. Tym razem jednak zamiast prosić użytkownika o nazwę pliku, wykorzystujemy argument linii polecenia. W linii 22. funkcja main() została zadeklarowana jako przyjmująca dwa parametry: ilość argumentów linii polecenia oraz wskaźnik do tablicy łańcuchów tych argumentów.

W liniach od 24. do 28. program sprawdza, czy otrzymał wymaganą ilość argumentów (dokładnie dwa). Jeśli użytkownik nie poda pojedynczej nazwy pliku, zostanie wypisany komunikat błędu:

Uzycie TestProgram <nazwa_pliku>

Następnie program kończy działanie. Zwróć uwagę, że używając argv[0] zamiast sztywno określonej nazwy programu, możemy skompilować ten program tak, aby przyjmował dowolną nazwę i by była ona automatycznie wyświetlana w tym komunikacie.

W linii 30. program próbuje otworzyć wskazany plik do zapisu binarnego. Nie ma powodu, by kopiować nazwę pliku do tymczasowego bufora lokalnego, gdyż można bezpośrednio wykorzystać argument argv[1].

Ta technika zostaje powtórzona w linii 42., gdzie ten sam plik zostaje otwarty do odczytu; a także w liniach 33. i 45., w których wypisywane są komunikaty błędów otwarcia pliku.

2 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

4 F:\korekta\r17-06.doc[Author ID2: at Wed Nov 28 09:25:00 2001 ]C:\Moje dokumenty\jr\doc\Korek[Author ID2: at Wed Nov 28 09:25:00 2001 ]t_rzeczo\3\Kopia r17-05.doc[Author ID2: at Wed Nov 28 09:25:00 2001 ]



Wyszukiwarka

Podobne podstrony:
8001
8001
8001
8001
8001
8001
8001
8001
Radio CB Alan 8001
8001 Wypełnione Mrd 5(1) 2
CAŁOŚĆ SPIS INWENTARZA 8001 9000
alan 8001 reaktywacja

więcej podobnych podstron