C++ w dwunastu długich krokach

63
Uniwersytet Wrocławski Wydział Fizyki i Astronomii Zbigniew Koza C++ w dwunastu długich krokach Wrocław 2006

Transcript of C++ w dwunastu długich krokach

Page 1: C++ w dwunastu długich krokach

Uniwersytet WrocławskiWydział Fizyki i Astronomii

Zbigniew Koza

C++w dwunastu długich krokach

Wrocław 2006

Page 2: C++ w dwunastu długich krokach

Redakcja techniczna, opracowanie tekstu i skład: Zbigniew Koza

Copyright c© 2006 by Zbigniew Koza and Uniwersytet Wrocławski

Drukarnia Uniwersytetu WrocławskiegoPlac Solny 12, 50-061 Wrocławtel. 713438389, 713752305, fax 713447258

Page 3: C++ w dwunastu długich krokach

Wszystkim Kózkom – dużym i małym

Page 4: C++ w dwunastu długich krokach
Page 5: C++ w dwunastu długich krokach

Spis treści

1 Pierwszy program w C++ 131.1 Dla kogo jest ta książka? . . . . . . . . . . . . . . . . . . . . . . . . 131.2 Rys historyczny . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

1.2.1 Dlaczego C++? . . . . . . . . . . . . . . . . . . . . . . . . . 141.2.2 C++ a C . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161.2.3 Aktualny standard języka . . . . . . . . . . . . . . . . . . . 16

1.3 Zanim napiszemy swój pierwszy program . . . . . . . . . . . . . . 161.3.1 Środowisko programistyczne . . . . . . . . . . . . . . . . . . 16

1.4 Pierwszy program . . . . . . . . . . . . . . . . . . . . . . . . . . . 181.4.1 Kompilacja kodu źródłowego . . . . . . . . . . . . . . . . . 181.4.2 Błędy kompilacji . . . . . . . . . . . . . . . . . . . . . . . . 191.4.3 Uruchamianie programu w środowisku Dev-C++ . . . . . . 201.4.4 Struktura prostego programu w C++ . . . . . . . . . . . . 20

1.5 Obiekt std::cout i literały . . . . . . . . . . . . . . . . . . . . . . 211.6 Definiowanie obiektów . . . . . . . . . . . . . . . . . . . . . . . . . 221.7 Identyfikatory, słowa kluczowe i dyrektywy . . . . . . . . . . . . . 241.8 Zapis programu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241.9 Cztery działania matematyczne i typ double . . . . . . . . . . . . 251.10 Jeszcze więcej matematyki . . . . . . . . . . . . . . . . . . . . . . . 261.11 Upraszczanie zapisu obiektów i funkcji biblioteki standardowej . . 281.12 Źródła informacji . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281.13 Q & A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291.14 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301.15 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30

2 Wyrażenia i instrukcje 312.1 Instrukcje sterujące . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

2.1.1 Instrukcja if... else... . . . . . . . . . . . . . . . . . . . 312.2 Pętle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

2.2.1 Pętla for . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332.2.2 Pętle while i do . . . . . . . . . . . . . . . . . . . . . . . . 352.2.3 Instrukcje break i continue . . . . . . . . . . . . . . . . . 35

2.3 Typy wbudowane . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362.3.1 Typy całkowite . . . . . . . . . . . . . . . . . . . . . . . . . 362.3.2 Typy zmiennopozycyjne . . . . . . . . . . . . . . . . . . . . 382.3.3 Typ logiczny . . . . . . . . . . . . . . . . . . . . . . . . . . 402.3.4 Zapis literałów całkowitych i zmiennopozycyjnych . . . . . 40

5

Page 6: C++ w dwunastu długich krokach

6 Spis treści

2.4 Wyrażenia arytmetyczne, promocje i konwersje standardowe . . . . 412.5 Tworzenie obiektów stałych . . . . . . . . . . . . . . . . . . . . . . 43

2.5.1 Modyfikator const . . . . . . . . . . . . . . . . . . . . . . . 432.6 Popularne typy standardowe . . . . . . . . . . . . . . . . . . . . . . 44

2.6.1 Strumienie . . . . . . . . . . . . . . . . . . . . . . . . . . . 442.6.2 Napisy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462.6.3 Wektory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472.6.4 Słowniki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

2.7 Obiekty lokalne i globalne. Zasięg. Przesłanianie . . . . . . . . . . 502.8 Operatory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

2.8.1 Priorytet operatorów . . . . . . . . . . . . . . . . . . . . . . 532.8.2 Łączność operatorów . . . . . . . . . . . . . . . . . . . . . . 532.8.3 Wartość operatorów . . . . . . . . . . . . . . . . . . . . . . 532.8.4 Opis wybranych operatorów . . . . . . . . . . . . . . . . . . 532.8.5 Operatorowe patologie . . . . . . . . . . . . . . . . . . . . . 56

2.9 Wyrażenia i instrukcje . . . . . . . . . . . . . . . . . . . . . . . . . 562.10 Q & A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 572.11 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 572.12 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

3 Funkcje 593.1 Referencje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593.2 Funkcje swobodne . . . . . . . . . . . . . . . . . . . . . . . . . . . 603.3 Po co są funkcje? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623.4 Funkcje składowe – wprowadzenie . . . . . . . . . . . . . . . . . . . 643.5 Argumenty funkcji . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

3.5.1 Przekazywanie argumentów przez wartość . . . . . . . . . . 643.5.2 Przekazywanie argumentów przez referencję . . . . . . . . . 643.5.3 Przekazywanie argumentów przez stałą referencję . . . . . . 65

3.6 Funkcje zwracające referencję . . . . . . . . . . . . . . . . . . . . . 673.7 Operatory jako funkcje swobodne . . . . . . . . . . . . . . . . . . . 683.8 Stos funkcji . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 713.9 Funkcje otwarte (inline) . . . . . . . . . . . . . . . . . . . . . . . 733.10 Funkcje jako argumenty innych funkcji . . . . . . . . . . . . . . . . 753.11 Rekurencja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 753.12 Argumenty domyślne . . . . . . . . . . . . . . . . . . . . . . . . . . 763.13 Lokalne obiekty statyczne . . . . . . . . . . . . . . . . . . . . . . . 773.14 Funkcja main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

3.14.1 Argumenty funkcji main . . . . . . . . . . . . . . . . . . . . 793.14.2 Wartość funkcji main . . . . . . . . . . . . . . . . . . . . . . 79

3.15 Polimorfizm nazw funkcji . . . . . . . . . . . . . . . . . . . . . . . 803.16 Deklaracja a definicja funkcji . . . . . . . . . . . . . . . . . . . . . 803.17 Q & A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 813.18 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 813.19 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82

Page 7: C++ w dwunastu długich krokach

Spis treści 7

4 Tablice i wskaźniki 834.1 Wskaźniki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

4.1.1 Definiowanie wskaźników . . . . . . . . . . . . . . . . . . . 834.1.2 Wskaźniki typu void*, czyli wycieczka w stronę C . . . . . 854.1.3 Wskaźnik zerowy . . . . . . . . . . . . . . . . . . . . . . . . 854.1.4 Czym grozi nieumiejętne użycie wskaźników? . . . . . . . . 854.1.5 Wskaźniki stałe i wskaźniki na stałe . . . . . . . . . . . . . 864.1.6 Wskaźniki na wskaźniki . . . . . . . . . . . . . . . . . . . . 86

4.2 Tablice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 874.2.1 Tablice wielowymiarowe . . . . . . . . . . . . . . . . . . . . 884.2.2 Inicjalizacja tablic . . . . . . . . . . . . . . . . . . . . . . . 884.2.3 Zastosowanie operatora sizeof do tablic . . . . . . . . . . 904.2.4 Tablice a wskaźniki . . . . . . . . . . . . . . . . . . . . . . . 904.2.5 Tablice wskaźników i wskaźniki na tablice . . . . . . . . . . 914.2.6 Tablice jako argumenty funkcji . . . . . . . . . . . . . . . . 914.2.7 Teksty literalne i tablice znaków . . . . . . . . . . . . . . . 924.2.8 Porównanie tablic i wektorów . . . . . . . . . . . . . . . . . 93

4.3 Pamięć wolna (sterta) . . . . . . . . . . . . . . . . . . . . . . . . . 944.4 Q & A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 964.5 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 974.6 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

5 Klasy i obiekty 995.1 Struktury . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

5.1.1 Podstawowe zasady definiowania i używania struktur . . . . 995.1.2 Inicjalizacja struktur . . . . . . . . . . . . . . . . . . . . . . 1015.1.3 Dostęp do składowych poprzez wskaźnik . . . . . . . . . . . 101

5.2 Co to są klasy? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1015.3 Definiowanie klas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

5.3.1 Klasa jako „zmodyfikowana” struktura . . . . . . . . . . . . 1025.3.2 Konstruktory . . . . . . . . . . . . . . . . . . . . . . . . . . 1035.3.3 Destruktor . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

5.4 Funkcje składowe (metody) . . . . . . . . . . . . . . . . . . . . . . 1075.4.1 Metody i metody stałe . . . . . . . . . . . . . . . . . . . . . 1085.4.2 Przeciążanie operatorów w klasie . . . . . . . . . . . . . . . 1095.4.3 Konstruktor kopiujący i operator = . . . . . . . . . . . . . . 1105.4.4 Wskaźnik this . . . . . . . . . . . . . . . . . . . . . . . . . 112

5.5 Udostępnianie składowych . . . . . . . . . . . . . . . . . . . . . . . 1135.5.1 Sekcje public i private . . . . . . . . . . . . . . . . . . . . 1135.5.2 Funkcje i klasy zaprzyjaźnione . . . . . . . . . . . . . . . . 114

5.6 Interfejs i implementacja . . . . . . . . . . . . . . . . . . . . . . . . 1155.6.1 Podział definicji klasy na interfejs i implementację . . . . . 115

5.7 Kontrakty, niezmienniki i asercje . . . . . . . . . . . . . . . . . . . 1185.7.1 Kontrakty . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1185.7.2 Niezmienniki . . . . . . . . . . . . . . . . . . . . . . . . . . 119

5.8 Hermetyzacja danych . . . . . . . . . . . . . . . . . . . . . . . . . . 1205.9 Różnice między klasami i strukturami . . . . . . . . . . . . . . . . 1205.10 Dygresja: składowe statyczne . . . . . . . . . . . . . . . . . . . . . 1215.11 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

Page 8: C++ w dwunastu długich krokach

8 Spis treści

5.12 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

6 Dynamiczne struktury danych 1256.1 Stos na bazie tablicy dynamicznej . . . . . . . . . . . . . . . . . . 125

6.1.1 Interfejs stosu . . . . . . . . . . . . . . . . . . . . . . . . . . 1256.1.2 Implementacja stosu . . . . . . . . . . . . . . . . . . . . . . 1266.1.3 Test stosu . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

6.2 Stos na bazie listy pojedynczo wiązanej . . . . . . . . . . . . . . . 1326.2.1 Rekurencyjne struktury danych . . . . . . . . . . . . . . . . 1336.2.2 Interfejs klasy . . . . . . . . . . . . . . . . . . . . . . . . . . 1336.2.3 Implementacja . . . . . . . . . . . . . . . . . . . . . . . . . 134

6.3 Dygresja: przestrzenie nazw i zagnieżdżanie definicji klas . . . . . . 1366.4 Q & A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1386.5 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1386.6 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138

7 Dziedziczenie i polimorfizm 1397.1 Dziedziczenie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139

7.1.1 Do czego służy dziedziczenie? . . . . . . . . . . . . . . . . . 1397.1.2 Definiowanie klasy pochodnej . . . . . . . . . . . . . . . . . 1417.1.3 Inicjalizacja klasy bazowej . . . . . . . . . . . . . . . . . . . 1427.1.4 Relacje „X ma Y”, „X jest Y” oraz „X zarządza Y” . . . . 1447.1.5 Kolejność inicjalizacji i destrukcji obiektu . . . . . . . . . . 1467.1.6 Sekcja protected . . . . . . . . . . . . . . . . . . . . . . . 1467.1.7 Zastępowanie (overridnig) funkcji składowych . . . . . . . . 147

7.2 Polimorfizm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1497.2.1 Niedoskonałości „zwykłego” dziedziczenia . . . . . . . . . . 1497.2.2 Definiowanie metod polimorficznych . . . . . . . . . . . . . 1517.2.3 vtable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1537.2.4 Dygresja: klasy abstrakcyjne . . . . . . . . . . . . . . . . . 155

7.3 Jak to się robi w Qt? . . . . . . . . . . . . . . . . . . . . . . . . . . 1567.4 Q & A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1597.5 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1597.6 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

8 Strumienie 1618.1 Strumienie buforowane i niebuforowane . . . . . . . . . . . . . . . 1618.2 Klawiatura, konsola, plik, strumień napisowy . . . . . . . . . . . . 1628.3 Stan strumienia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1648.4 Manipulatory i formatowanie strumienia . . . . . . . . . . . . . . . 164

8.4.1 Manipulator std::setw . . . . . . . . . . . . . . . . . . . . 1658.4.2 Manipulator std::setprecision . . . . . . . . . . . . . . . 166

8.5 Strumienie wyjścia . . . . . . . . . . . . . . . . . . . . . . . . . . . 1678.6 Strumienie wejścia . . . . . . . . . . . . . . . . . . . . . . . . . . . 167

8.6.1 Funkcje składowe get i getline . . . . . . . . . . . . . . . 1688.6.2 Inne funkcje operujące na strumieniach wejściowych . . . . 169

8.7 Przykład . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1698.8 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1728.9 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172

Page 9: C++ w dwunastu długich krokach

Spis treści 9

9 Biblioteki 1739.1 Podział programu na pliki . . . . . . . . . . . . . . . . . . . . . . . 173

9.1.1 Zasady kompilacji programów podzielonych na pliki . . . . 1749.1.2 Przygotowanie plików źródłowych i nagłówkowych . . . . . 1759.1.3 Kompilacja przy pomocy wiersza poleceń . . . . . . . . . . 1769.1.4 Kompilacja przy pomocy programu make . . . . . . . . . . 1789.1.5 Kompilacja przy pomocy projektów . . . . . . . . . . . . . 1799.1.6 Automatyzacja tworzenia pliku Makefile . . . . . . . . . . . 181

9.2 Używanie gotowych bibliotek . . . . . . . . . . . . . . . . . . . . . 1829.2.1 Dekorowanie nazw i deklaracja extern "C" . . . . . . . . . 1829.2.2 Przykłady . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183

9.3 Kompilacja i instalacja bibliotek z plików źródłowych . . . . . . . . 1869.3.1 Przykłady . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1869.3.2 Systemy kontroli wersji . . . . . . . . . . . . . . . . . . . . 187

9.4 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1889.5 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188

10 Preprocesor i szablony 18910.1 Preprocesor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189

10.1.1 Rola preprocesora w C++ . . . . . . . . . . . . . . . . . . . 18910.1.2 Dyrektywy preprocesora . . . . . . . . . . . . . . . . . . . . 19010.1.3 Przykład . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19310.1.4 Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . 195

10.2 Szablony . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19510.2.1 Szablony klas . . . . . . . . . . . . . . . . . . . . . . . . . . 19510.2.2 Szablony funkcji składowych . . . . . . . . . . . . . . . . . 19810.2.3 Szablony funkcji swobodnych . . . . . . . . . . . . . . . . . 19910.2.4 Specjalizacja szablonu . . . . . . . . . . . . . . . . . . . . . 20010.2.5 Używanie szablonów funkcji do upraszczania pracy z sza-

blonami klas . . . . . . . . . . . . . . . . . . . . . . . . . . 20110.2.6 Gdzie umieszczać definicje szablonów? . . . . . . . . . . . . 20210.2.7 Szablony a programowanie generyczne . . . . . . . . . . . . 20210.2.8 Dygresja: konstrukcja typedef . . . . . . . . . . . . . . . . 202

10.3 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20310.4 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204

11 Wprowadzenie do STL 20511.1 Co to jest STL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20511.2 Pojemniki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20611.3 Iteratory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206

11.3.1 Co to są iteratory? . . . . . . . . . . . . . . . . . . . . . . . 20611.3.2 Przykład użycia iteratorów . . . . . . . . . . . . . . . . . . 20711.3.3 Rodzaje iteratorów . . . . . . . . . . . . . . . . . . . . . . . 209

11.4 Algorytmy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20911.4.1 Co to są algorytmy? . . . . . . . . . . . . . . . . . . . . . . 20911.4.2 Funktory . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21011.4.3 Obiekty funkcyjne . . . . . . . . . . . . . . . . . . . . . . . 21211.4.4 Wartości pobierane i zwracane przez algorytmy . . . . . . . 21311.4.5 Obiekty funkcyjne a efektywność szablonów funkcji . . . . . 214

Page 10: C++ w dwunastu długich krokach

10 Spis treści

11.5 Wektory (std::vector) . . . . . . . . . . . . . . . . . . . . . . . . 21611.5.1 Opis szablonu std::vector<T> . . . . . . . . . . . . . . . . 21611.5.2 Przykłady . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219

11.6 Liczby zespolone . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22011.7 Napisy (std::string) . . . . . . . . . . . . . . . . . . . . . . . . . 22111.8 Q & A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22311.9 Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22311.10Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224

12 Pojemniki i algorytmy 22512.1 Przegląd pojemników STL . . . . . . . . . . . . . . . . . . . . . . . 225

12.1.1 Słowniki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22612.1.2 Zbiory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23012.1.3 Wielosłowniki i wielozbiory . . . . . . . . . . . . . . . . . . 23112.1.4 Słowniki i zbiory mieszające . . . . . . . . . . . . . . . . . . 23112.1.5 Kolejki o dwóch końcach . . . . . . . . . . . . . . . . . . . . 23312.1.6 Listy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23412.1.7 Stosy, kolejki i kolejki priorytetowe . . . . . . . . . . . . . . 23412.1.8 Wektory i zbiory bitów . . . . . . . . . . . . . . . . . . . . 23612.1.9 Wektory numeryczne (std::valarray<T>) . . . . . . . . . 237

12.2 Przegląd algorytmów swobodnych . . . . . . . . . . . . . . . . . . . 23712.2.1 Algorytmy niemodyfikujące . . . . . . . . . . . . . . . . . . 23812.2.2 Algorytmy modyfikujące . . . . . . . . . . . . . . . . . . . . 23912.2.3 Sortowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . 24212.2.4 Algorytmy numeryczne . . . . . . . . . . . . . . . . . . . . 24412.2.5 Inne algorytmy . . . . . . . . . . . . . . . . . . . . . . . . . 244

12.3 Kontrola poprawności użycia STL . . . . . . . . . . . . . . . . . . 24512.4 Składniki dodatkowe . . . . . . . . . . . . . . . . . . . . . . . . . . 24512.5 Q & A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24512.6 Problemy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246

13 Co dalej? 247

A Wybrane opcje kompilatora g++ 249

B Dodatkowe elementy języka 251B.1 Instrukcja goto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251B.2 Instrukcja switch . . . . . . . . . . . . . . . . . . . . . . . . . . . 251

Page 11: C++ w dwunastu długich krokach

Wstęp

Niniejszy podręcznik stanowi zapis wykładu prowadzonego w 2006 r. w Instytu-cie Fizyki Uniwersytetu Wrocławskiego w ramach finansowanego ze środków UniiEuropejskiej projektu „Technologie Informatyczne od Podstaw”. Wykład adre-sowany jest do osób pracujących, które znają już jakiś język programowania (np.Pascal, Basic, PHP) i chciałyby poznać język C++ w celu podniesienia swoichkwalifikacji.Dlaczego C++? Odpowiedź jest prosta – w ciągu blisko dwudziestu lat swe-

go istnienia C++ osiągnął status języka programowania najczęściej używanegow zastosowaniach komercyjnych. Co więcej, każdy kto pozna C++, niejako mi-mochodem nauczy się rozumieć drugi podstawowy język programowania – językC – i będzie miał znacznie ułatwioną drogę do opanowania wielu innych języków(np. Java, C#, bash).Decydujący wpływ na zakres zawartego tu materiału ma długość kursu – 12

dwugodzinnych wykładów i tyleż ćwiczeń w laboratorium komputerowym. Od-powiada to zaledwie 80% typowego jednosemetralnego kursu uniwersyteckiego.Z własnego doświadczenia wykładowcy języka C++ wiem jednak, że w praktycenawet dwa semestry nie starczają, by w pełni omówić wszystkie ważne aspek-ty programowania w C++. Dlatego w swoim wykładzie skoncentrowałem się nanajważniejszych cechach języka. Mam nadzieję, że to ograniczenie wpłynie pozy-tywnie na jakość wykładu – większość książek dostępnych obecnie w księgarniachjest bowiem bardzo rozwlekła, gdyż ich autorzy usiłują przedstawić kompletnyopis języka, włącznie z tymi jego elementami, których sami nigdy w życiu nie uży-wali. Natomiast mój podręcznik z założenia stawia sobie znacznie skromniejszycel: rzetelne nauczenie Czytelnika podstawowych zasad programowania we współ-czesnym C++. Po zakończeniu kursu jego uczestnicy powinni móc uczestniczyćw typowym projekcie programistycznym i samodzielnie rozwijać swoje umiejęt-ności w zakresie programowania, np. studiując odpowiednie biblioteki C lub C++potrzebne do realizacji danego zadania.Język C++ jest trudny zarówno do nauczania, jak i uczenia się. Nie powstał

bowiem (jak np. Pascal) w wyniku teoretycznych rozważań nad metodologią pro-gramowania; przeciwnie, C++ został zaprojektowany przez praktyków (a kon-kretnie przez Bjarne’a Stroustrupa) jako narzędzie mające pomóc w rozwiązaniuproblemów praktycznych. Problemów, dodajmy, które zazwyczaj objawiają siędopiero w dużych projektach i mają związek z wydajnością i kosztami pracyprogramistów. Tu właśnie tkwi źródło trudności związanych z dydaktyką C++:niemal wszystkie charakterystyczne elementy tego języka, które odróżniają go odtakich języków, jak C czy Pascal, znajdują zastosowanie (i uzasadnienie) dopiero

11

Page 12: C++ w dwunastu długich krokach

12 Wstęp

w dużych projektach. Tymczasem kurs poświęcony podstawom języka musi ogra-niczać się do omawiania krótkich programów; analogicznie ćwiczenia laboratoryj-ne muszą bazować na niewielkich projektach, które można zrealizować w ciągukilku godzin. Ale jak osobę, która pisze wyłącznie programy liczące 100-200 wier-szy i uparła się, by wszystkie zmienne (włącznie ze zmiennymi sterującymi pętli)umieszczać w przestrzeni globalnej („bo tak szybciej”) przekonać, że popełniagrzech główny? Jak przekonać ją o użyteczności klas czy wyjątków? Jak wyjaśnićzasady dzielenia programów na osobne pliki bez sprawiania wrażenia, że są tojakieś kosmiczne wymysły mające tylko utrudniać ludziom życie?Mając na względzie powyższe trudności oraz fakt, że moim celem jest naucze-

nie C++ „od podstaw” w 12 wykładach, podręcznik podzieliłem na 12 rozdzia-łów, przy czym stopień zaawansowania kilku początkowych dostosowany jest dopotrzeb (i poziomu) osób piszących nie tyle nawet programy, ile krótkie „wpraw-ki”. Pierwszy rozdział zawiera krótki opis języka, kilka przykładowych programóworaz informacje niezbędne do ich kompilacji. Kolejne 3 rozdziały zawierają zwię-zły opis nieobiektowych cech języka, a więc m.in. konstrukcji wyrażeń i instruk-cji, definiowania zmiennych i tablic oraz posługiwania się funkcjami swobodnymi.Cztery pierwsze rozdziały wprowadzają więc Czytelnika w świat programowaniaproceduralnego w C++ lub, innymi słowy, w świat C++ traktowanego jako „ulep-szona” wersja języka C. Kolejne cztery rozdziały poświęcone są programowaniuobiektowemu w C++. Znajdziemy więc tu m.in. objaśnienie tak fundamental-nych pojęć, jak obiekty i klasy (z metodami, konstruktorami i destruktorami),dziedziczenie, hermetyzacja danych i polimorfizm. Rozdział 9 przedstawia spo-soby wykorzystywania bibliotek zewnętrznych (napisanych w C lub C++) orazmetody dzielenia własnego projektu na wiele plików źródłowych. Trzy ostatnierozdziały poświęcone są omówieniu programowania generycznego, co obejmujewprowadzenie w takie zagadnienia, jak szablony i biblioteka STL.Układ książki może sugerować, iż wykład rozpoczyna się od prezentacji języka

C, po czym przechodzi do omawiania C++ jako rozszerzenia języka C. W rzeczy-wistości jednak nie zamierzam opisywać tu samego języka C, który – mimo że defacto jest podzbiorem języka C++ – zbytnio ustępuje swemu następcy, by wartobyło poświęcać mu oddzielnie uwagę. Układ podręcznika odzwierciedla natomiastfakt, że język C++ jest tworem eklektycznym, umożliwiającym programowaniew co najmniej trzech godnych polecenia stylach (proceduralnym, obiektowym,generycznym) lub ich kombinacji.Niniejszy podręcznik powstał w oparciu o moje wieloletnie doświadczenie

w nauczaniu C++. Wiele jego rozdziałów zostało zainspirowanych książkamiB. Stroustrupa [1], B. Milewskiego [2] i J. Liberty’ego [3]. Pierwsza z nich jestlekturą obowiązkową każdego programisty C++. Na tę jedyną książkę nie wartożałować pieniędzy – po prostu trzeba ją mieć i sięgać po nią możliwie często.Niestety, nie jest to lektura łatwa, nie jest też pomyślana jako podręcznik dlapoczątkujących. Dwie pozostałe cenię za oryginalne podejście pedagogiczne dotematu. Żadna z nich nie stara się zastąpić książki Stroustrupa; usiłują nato-miast z dość dobrym skutkiem przedstawić język C++ od podstaw w sposóbzrozumiały dla przeciętnego entuzjasty programowania.Na zakończenie pozwolę sobie jeszcze podziękować Bartoszowi Milewskiemu

– mojemu pierwszemu nauczycielowi C++.

Page 13: C++ w dwunastu długich krokach

Rozdział 1

Pierwszy program w C++

Dla kogo jest ta książka? Rys historyczny. Pierwszy program. Środowisko programistycz-ne. Kompilator i kompilacja. Błędy kompilacji. Typy int i double, obiekty std::cini std::cout. Instrukcja using namespace std.

1.1 Dla kogo jest ta książka?

Podręcznik napisałem z myślą o osobie pragnącej nauczyć się języka C++ odpodstaw. Zakładam, że Czytelnik biegle posługuje się komputerem (np. potra-fi zainstalować program lub „odstrzelić” zawieszoną aplikację), ma dostęp doInternetu i zna już jakiś język programowania. Oczywiście niezbędna jest teżprzyzwoita znajomość języka angielskiego – co najmniej w stopniu pozwalają-cym swobodnie rozumieć komunikaty diagnostyczne kompilatora, a najlepiej napoziomie pozwalającym rozumieć oryginalną dokumentację bibliotek.

1.2 Rys historyczny

Dawno, dawno temu, czyli w połowie XX-go wieku komputery były bardzo kosz-townymi urządzeniami, które wymagały niezwykle pracochłonnej obsługi. W tychzamierzchłych czasach to ludzie dostosowywali się do możliwości maszyn cyfro-wych i obsługiwali je, wpisując programy jako ciągi „zer” i „jedynek” poprzezręczne ustawienie przekaźników; wyniki obliczeń również otrzymywano jako se-kwencje zer i jedynek odczytywanych ze stanu lampek. Nieco później pojawiłysię asemblery – specjalne programy, które pozwalały obsłudze komputerów pisaćprogramy w nieco bardziej dla ludzi zrozumiałych kategoriach rozkazów, liczb,zmiennych; programy te były następnie tłumaczone przez specjalny program nakod maszynowy – człowiek mógł wreszcie „zapomnieć” o zerach i jedynkach.Następnie pojawiły się języki proceduralne, np. FORTRAN; programy w nichnapisane przypominały już wzory matematyczne, były więc już całkiem dobrzedostosowane do rozwiązywania wielu zagadnień technicznych. Wciąż jednak kosztkomputerów był ogromny, a ich moc – szczególnie z dzisiejszej perspektywy – by-ła bardzo ograniczona. W tych czasach komputery były obsługiwane wyłącznieprzez wysoko wykwalifikowany personel, a główną troską programistów było pisa-nie programów działających możliwie jak najszybciej i zajmujących jak najmniej

13

Page 14: C++ w dwunastu długich krokach

14 Rozdział 1. Pierwszy program w C++

zasobów maszyny (głównie pamięci operacyjnej). W tych warunkach nie mar-twiono się specjalnie kwestią wygody obsługi komputerów – łatwiej i taniej byłowyszkolić ludzi do obsługi stosunkowo prymitywnych komputerów niż odwrotnie.Dziś komputery są jednocześnie i bardzo szybkie, i tanie, a przez to powszech-

nie dostępne; co więcej, dysponują zasobami sprzętowymi i mocą obliczeniową ty-siące lub nawet miliony razy przewyższającymi te dostępne jeszcze 30 lat temu.Typowymi użytkownikami komputerów nie są już wyłącznie błyskotliwi i wszech-stronnie wykształceni fachowcy – obecnie komputerami posługują się już nawetprzedszkolacy. Ci nowi, nierzadko niepiśmienni użytkownicy wymagają, by kom-putery wyposażone były w zupełnie inny rodzaj programów – niezawodnych i ła-twych w obsłudze. Takie programy są jednak z natury bardzo złożone1. Ich wytwo-rzenie wymaga zaangażowania dziesiątek, setek, a czasami tysięcy ludzi. Masowyrynek oznacza potencjalnie wielkie zyski, ale też i ogromne koszty oraz ryzykoutraty rynku na rzecz konkurencji. Stąd wynika potrzeba ograniczenia kosztów.Te z kolei najefektywniej jest redukować już podczas pisania programów, co ozna-cza konieczność wyposażenia programistów w możliwe jak najlepsze „narzędziaprodukcji”: edytory (do wprowadzania tekstu), debuggery (do wyszukiwania błę-dów), systemy kontroli wersji (do zarządzania poprawkami w programach) i –przede wszystkim – język programowania z możliwie najlepszym kompilatorem,czyli programem tłumaczącym programy napisane przez ludzi na zrozumiały dlakomputera kod maszynowy.

1.2.1 Dlaczego C++?

W tej sytuacji pod koniec lat osiemdziesiątych na scenę wkracza C++. Językten, zaprojektowany jako obiektowe rozszerzenie języka C, szybko osiągnął sta-tus najpopularniejszego niespecjalistycznego języka programowania. Stało się takdlatego, że pozwala on na zwiększenie wydajności programistów. Szacuje się, żejeden programista stosujący C++ może sobie poradzić z kilkukrotnie większą(w sensie funkcjonalnym) porcją kodu niż jego kolega posługujący się C. Ponadtokod napisany w C++ jest łatwiejszy w utrzymaniu, rozwoju i ponownym wyko-rzystaniu w innym projekcie; nie ustępuje przy tym wcale prędkością programomnapisanym w C, natomiast w zakresie tym bije na głowę takie popularne języki,jak Java, C# czy Lisp.Jakie są główne zalety C++?

• C++ umożliwia stosowanie wielu stylów programowania i pracęna wielu poziomach abstrakcji. Można w nim stosować wstawki asem-blerowe, a więc programować w języku maszyny; można też programowaćproceduralnie w stylu takich języków, jak Pascal czy C; można też progra-mować obiektowo, a więc na wysokim poziomie abstrakcji, ułatwiającymprogramistom myślenie w kategoriach rozwiązywanego przez nich zadania,a nie abstrakcyjnych zer i jedynek, rozkazów maszynowych, adresów czyrejestrów.

• C++ zawiera mechanizmy ułatwiające wielokrotne wykorzysty-wanie tego samego kodu. Jest to najważniejsza cecha języka. Programyw C++ (tak jak w C) można dzielić na osobne części („moduły”); części

1Na przykład projekt KDE to ponad 4 miliony wierszy kodu nad którym pracuje ponad 800programistów; tylko w maju 2002 roku dokonano 11 014 zmian w kodzie źródłowym [4].

Page 15: C++ w dwunastu długich krokach

Rys historyczny 15

te można następnie wykorzystywać w dowolnym innym programie C++,przy czym taki moduł wystarczy raz skompilować, a następnie tylko do-łączać do nowych programów (w ten sposób otrzymuje się „biblioteki”).Ponadto w C++ takie moduły można w bardziej naturalny sposób niż w Codseparować od siebie (dzięki hermetyzacji danych i interfejsom), tworzącz nich prawdziwe „czarne skrzynki”. Dzięki tzw. wyjątkom twórcy bibliotekmogą precyzyjnie określić zachowanie się ich kodu w wypadku sytuacji nad-zwyczajnych i nawet wymusić określone zachowanie na użytkownikach tychbibliotek. Tak jak w innych językach programowania, w C++ można unik-nąć powielania tego samego kodu poprzez zastosowanie funkcji. Ale C++oferuje dużo więcej – szablony, dzięki którym ten sam kod można stosowaćdo obiektów różnych typów, a także przestrzenie nazw, dzięki którym łatwojest zarządzać nazwami używanymi w różnych bibliotekach.

• C++ jest rozszerzeniem najpopularniejszego języka lat 80-tych– języka C. Dlatego programy napisane w C++ mają pełny dostęp dowszystkich niezliczonych bibliotek kiedykolwiek napisanych w C.

• Kompilator C++ przejmuje na siebie wiele zadań, za realizacjęktórych jeszcze niedawno całkowitą odpowiedzialność ponosił pro-gramista. Kompilator sprawdza na przykład, czy użycie zmiennych lubfunkcji jest zgodne z ich deklaracją. Możemy też poinstruować go, że pewnezmienne mogą być wykorzystywane tylko w określonych partiach programualbo że ich wartości nie mogą być tam modyfikowane, albo że muszą byćzainicjalizowane. W związku z tym, że kompilator C++ z definicji znaczniedokładniej analizuje kod źródłowy, zaleca się nawet, by programy napisanew „czystym” C kompilować kompilatorem C++.

• C++ posiada konstrukcje ułatwiające zarządzanie zasobami kom-putera. Należą do nich m.in. konstruktory, destruktory i wyjątki. Stosującje systematycznie, można zagwarantować np., że z naszego programu niebędzie „wyciekać” pamięć operacyjna.

• Programy napisane w C++ są co najmniej równie szybkie, jak tenapisane w C. A niekiedy szybsze (np. dzięki szablonom sortowanie jestnieco szybsze w C++ niż w C).

• Biblioteki standardowe C++ są o wiele bardziej funkcjonalne odich odpowiedników ze standardowej biblioteki C. Standardowa bi-blioteka C++ zawiera m.in. wysokopoziomowy typ napisów std::string,który całkowicie zwalnia programistę od troski o to, w jaki sposób napisysą faktycznie interpretowane przez procesor. Podobnie klasa std::vectorjest dużo bardziej elastyczna od standardowych tablic języka C, a o czymśtakim jak słownik (std::map) możemy w C tylko pomarzyć.

Oczywiście C++ ma też wady:

• C++ jest trudny zarówno do nauczania, jak i uczenia się;• C++ stanowi niezwykle trudne wyzwanie dla twórców kompilatorów. O ilemi wiadomo, żaden powszechnie używany kompilator (tj. gcc i MS VisualC++) nie jest jeszcze w 100% zgodny ze standardem ISO z 1998 r.

• Ze względu na swój olbrzymi potencjał, w rękach niedouczonego programi-sty C++ jest jak przysłowiowa zapałka w ręku dziecka.

Page 16: C++ w dwunastu długich krokach

16 Rozdział 1. Pierwszy program w C++

1.2.2 C++ a C

Jak już wspomniałem, C++ jest rozszerzeniem C. Wiele osób wyciąga stąd wnio-sek, że aby nauczyć się „trudnego” C++, dobrze jest rozpocząć od „łatwego” C.Wniosek ten jest jednak równie fałszywy jak teza, że przed nauką jazdy samocho-dem należy nauczyć się jeździć rowerem (bo także ma kierownicę, hamulce i koła).Język C wymaga stosowania od samego początku dość karkołomnych konstrukcji,których niemal nie używa się w C++ (np. funkcji printf/scanf czy nieustannejżonglerki wskaźnikami). Dlatego programiści obeznani w języku C, aby dobrzeopanować C++ muszą oduczyć się wielu nawyków nabytych podczas pracy w C.Jeżeli ostatecznym celem jest C++, nauka C jest marnowaniem czasu!

1.2.3 Aktualny standard języka

Na przestrzeni lat język C++ podlegał ciągłej ewolucji. Większość zmian polega-ła na dodawaniu nowych cech rozszerzających możliwości języka. Kilka istotnychmodyfikacji doprowadziło jednak do niezgodności nowszych wersji języka z wer-sjami starszymi. Dlatego programy napisane 10 lat temu mogą nie spełniać wy-magań aktualnego standardu. W niniejszym podręczniku za standard w wymiarzeteoretycznym uznaje się standard ISO C++ opisany w piątym wydaniu książkiBjarne’a Stroustrupa „Język C++”. Z kolei w wymiarze praktycznym za stan-dardowe uznaje się tu programy kompilowane szeroko dostępnym kompilatoremGNU g++ w wersji ­3.4.2 z opcjami -ansi -pedantic. Oba te warunki powinnyzapewnić całkowitą zgodność prezentowanych tu konstrukcji języka z aktualnieobowiązującym standardem ISO C++ z 1998 roku, a więc w efekcie uniezależnićprzedstawiane tu informacje od platformy czy konkretnej implementacji kompi-latora.

1.3 Zanim napiszemy swój pierwszy program

1.3.1 Środowisko programistyczne

Programista, jak każdy zawodowiec, potrzebuje specjalistycznego warsztatu pra-cy. Nie mam tu oczywiście na myśli samego komputera, lecz specjalnych progra-mów służących do tworzenia nowych lub ulepszania starych programów. Takiezastaw programów nazywa się środowiskiem programistycznym.Istnieją dwa podstawowe rodzaje środowisk. Pierwsze, typowe dla systemu

Unix i jego pochodnych, to po prostu kolekcja oddzielnych programów urucha-mianych z powłoki systemowej; do tej grupy należy m.in. używany w tym pod-ręczniku kompilator gcc. Drugi rodzaj środowisk to tzw. zintegrowane środowiskaprogramistyczne (IDE, ang. Integrated Development Environments), w którychprogramista do wszystkich potrzebnych narzędzi ma dostęp z jednego programusterującego zintegrowanego ze specjalistycznym edytorem; do tej kategorii należąm.in. kompilatory firm Microsoft (Visual Studio) i Borland (C++ Builder).Zaletą pierwszego rozwiązania jest to, że daje użytkownikom pełną swobodę

w sposobie posługiwania się kompilatorem. W szczególności kompilator gcc moż-na łatwo i ściśle zintegrować z wieloma dostępnymi edytorami tekstu (np. emacsczy vim) lub istniejącymi środowiskami zintegrowanymi (np. C++ Builder), moż-na też na jego podstawie zbudować od podstaw zupełnie nowe środowisko typu

Page 17: C++ w dwunastu długich krokach

Zanim napiszemy swój pierwszy program 17

Okienko edycji programu

Paski narzędzi

Okno widoku projektu, klasy lub zmiennych

Menu

Zakładki okna raportu

Pasek stanu

Rysunek 1.1: Główne elementy okna programu Dev-C++.

IDE (np. KDeveloper i Anjuta w systemie Linux). Podczas zajęć laboratoryjnychtowarzyszących bieżącemu kursowi korzystać będziemy z programu Dev-C++,który jest (darmowym) zintegrowanym środowiskiem programistycznym opar-tym na kompilatorach serii gcc i pracującym w systemie Windows. Program tenmożna pobrać z Sieci ze strony http://www.bloodshed.net2. Wybraliśmy go,gdyż (i) jego używanie nie jest obwarowane licencjami komercyjnymi; (ii) opartyjest na bardzo dobrym zestawie kompilatorów gcc/MinGW; (iii) zajmuje niewielemiejsca, jest szybki i łatwy w instalacji i obsłudze; (iv) towarzyszy mu ogromnaliczba opcjonalnych bibliotek; (v) jest programem dobrze nam znanym, od kilkulat używanym (z wyboru) przez naszych studentów.Typowy wygląd okna programu Dev-C++ przedstawia Rys. 1.1. Głównymi

elementami interfejsu użytkownika w tym programie są:

• Okno edytora tekstu. Jest dostosowane do pisania programów w C lubC++, a wiele jego właściwości może być swobodnie konfigurowanych przezużytkownika.

• Paski narzędzi. Ułatwiają dostęp do często wykonywanych czynności, np.kompilacji programu, obsługi projektów, wyszukiwania tekstu.

• Menu. Udostępnia wszystkie funkcje programu.

• Okno widoku projektu. Ułatwia orientację w dużych programach.

• Okna raportu. Zawierają dodatkowe informacje, np. pełny komunikatkompilatora o przebiegu kompilacji.

• Pasek stanu. Podaje podstawowe informacje o edytowanym pliku.

2Jeśli Czytelnik zdecyduje się zainstalować u siebie środowisko Dev-C++, warto rozszerzyćje o najnowszą wersję środowiska MSYS (http://www.mingw.org), które zwiera programy nie-zbędne do kompilowania programów i bibliotek dostępnych na licencji GNU. Jeszcze większemożliwości daje instalacja systemu CygWin (http://sources.redhat.com).

Page 18: C++ w dwunastu długich krokach

18 Rozdział 1. Pierwszy program w C++

1.4 Pierwszy program

Oto nasz pierwszy, najprostszy program w C++. Jego celem jest wyświetlenie naekranie napisu Pierwszy program w C++.

Wydruk 1.1: Pierwszy program w C++.

#include <iostream>

int main(){std :: cout << ”Pierwszy program w C++” << ”\n”;

}

Tekst programu należy zapisać w pliku o odpowiednim rozszerzeniu. W przy-padku kompilatora gcc 3.4.2 za standardowe rozszerzenia nazw plików zawierają-cych programy w C++ uznaje się plik.cc, plik.cp, plik.cxx, plik.cpp, plik.CPP,plik.c++ oraz plik.C. W niniejszym kursie stosować będę bodaj najpopularniejszerozszerzenie plik.cpp.

1.4.1 Kompilacja kodu źródłowego

Programy pisze się w postaci możliwie jak najbardziej zrozumiałej dla ludzi, poczym przy pomocy specjalnych programów tłumaczy się je na ciąg instrukcjiprzeznaczonych bezpośrednio dla komputera. Obie te wersje zwyczajowo zwiesię „programem”. Aby uniknąć ewentualnych nieporozumień, program w postacizrozumiałej dla ludzi zwie się kodem źródłowym, a w postaci przeznaczonej dlakomputera – kodem maszynowym lub wynikowym3.Program tłumaczący kod źródłowy na maszynowy zwany jest kompilatorem,

a sam proces tłumaczenia – kompilacją. Słowa ‘kompilator’ i ‘kompilacja’ są jed-nak wieloznaczne. Jeszcze nie tak dawno temu proces tłumaczenia kodu źró-dłowego na maszynowy składał się z wielu kroków wykonywanych przez osobneprogramy. W wypadku języka C były to m.in. preprocesor, kompilator, asembler,konsolidator. Najważniejszym instrumentem w tej orkiestrze był (i jest) kompi-lator – od jego jakości w największym stopniu zależy jakość kodu maszynowego.Stąd po pewnym czasie cały proces translacji kodu źródłowego zaczęto nazywaćkompilacją; analogicznie cały zestaw programów zaangażowanych w tak rozumia-ną kompilację zwie się kompilatorem.Z biegiem czasu pojawiła się tendencja, by możliwie jak najbardziej uprościć

obsługę procesu tłumaczenia programów. Obecnie w zintegrowanych środowi-skach programistycznych translację programu można wykonać po prostu za naci-śnięciem myszą odpowiedniego przycisku. Z kolei twórcy kompilatora gcc przyjęlizasadę, że wszystkie etapy translacji można (jawnie lub nie) wykonać przy po-mocy polecenia gcc, przy czym szczegółami samego procesu translacji steruje siętu przy pomocy rozlicznych opcji podawanych w wierszu poleceń.Wśród profesjonalistów żelazną regułą jest, by duże programy dzielić na sto-

sunkowo małe fragmenty umieszczane w osobnych plikach źródłowych. W tym

3Słowo „program” ma jeszcze trzecie znaczenie informatyczne – ‘kod znajdujący się w fazierealizacji przez maszynę’; aby uniknąć dwuznaczności, zwie się go ‘procesem’.

Page 19: C++ w dwunastu długich krokach

Pierwszy program 19

wypadku translację kodu źródłowego dzieli się na kompilację poszczególnych pli-ków, w wyniku czego otrzymuje się tzw. pliki obiektowe, i łączenie (zwane też kon-solidacją lub linkowaniem) plików obiektowych z plikami bibliotecznymi w jedenplik wykonywalny. W środowisku kompilatora gcc proces ten zazwyczaj sterowa-ny jest specjalnym programem make, natomiast w zintegrowanych środowiskachprogramistycznych efektywna praca z programem podzielonym na wiele plikówwymaga utworzenia tzw. projektu. Z początku będziemy pisać wyłącznie małeprogramy mieszczące się w jednym pliku i ani szczegółami obsługi programu make,ani sposobami tworzenie projektów nie musimy się jeszcze zajmować; ważne jestjednak, by zdawać sobie sprawę z pewnego zamieszania pojęciowego panującego wzintegrowanych środowiskach programistycznych. Wiele z nich, np. Visual C++czy Anjuta, pod nazwą ‘compile’ rozumie wyłącznie pierwszą fazę translacji, czylitłumaczenie bieżącego pliku źródłowego na kod obiektowy. Z kolei Dev-C++ podnazwą ‘compile’ (lub ‘kompiluj’) rozumie cały proces translacji aż do utworzeniapliku wykonywalnego4.Pora na małe podsumowanie tego przydługiego wywodu. Aby przetłumaczyć

plik źródłowy plik.cpp na plik wykonywalny plik.exe należy

• Kompilator gcc: Wydać polecenie g++ plik.cpp -o plik.exe• Środowisko Dev-C++: Przycisnąć kombinację Ctrl+F9.

Zwróćmy uwagę na to, że polecenie wywołujące kompilator C++ nazywa sięg++ a nie gcc. W gruncie rzeczy g++ i gcc to ten sam kompilator; wywołanie gojako g++ (lub c++) powoduje jedynie automatyczne połączenie kompilowangeoprogramu ze standardowymi bibliotekami C++ (zamiast C).Kompilator gcc posiada mnóstwo opcji. Opis najważniejszych z nich znajduje

się w dodatku A.

1.4.2 Błędy kompilacji

Dość rzadko zdarza się, by już pierwsza wersja programu była całkowicie popraw-na. Zazwyczaj kody źródłowe zawierają różnego rodzaju naruszenia składni języ-ka. Typowy błąd to literówka, pominięcie nawiasu lub definicji zmiennej. Dobrykompilator powinien nie tylko wskazać miejsce wystąpienia błędu, ale także zasu-gerować sposób usunięcia go. W praktyce bywa z tym różnie, a do prawidłowego‘rozszyfrowania’ komunikatów kompilatora potrzebna jest wieloletnia wprawa.Rozpatrzmy prosty przykład. Załóżmy, że w programie 1.1 ze str. 18 zapo-

mniano zakończyć średnikiem instrukcję cout <<... . Po skompilowaniu takiegobłędnego programu w środowisku Dev-C++ uzyskamy efekt jak na Rys. 1.2.Brązowy pasek i krzyżyk na lewym marginesie wskazują wiersz, w którym kom-pilator wykrył błąd. Komunikat o błędzie wyświetlany jest na dole w okienkukomunikatów. Zawiera on numer wiersza (tu: 6), nazwę pliku (tu: 1.cpp) orazkomunikat diagnostyczny (expected ‘;’before ‘\}’token). Gdyby błędów byłowięcej, moglibyśmy łatwo dotrzeć do miejsca detekcji każdego z nich – w tymcelu wystarczyłoby kliknąć odpowiedni wiersz w okienku komunikatów.Miejsce detekcji błędu często różni się od jego rzeczywistej lokalizacji. W na-

szym przypadku różnica jest niewielka – tylko jeden wiersz – ale czasami możenawet dojść do sytuacji, gdy kompilator odkryje błąd w innym pliku niż ten,

4Ta sama czynność w języku Visual C++ zwie się budowaniem (ang. ‘build’).

Page 20: C++ w dwunastu długich krokach

20 Rozdział 1. Pierwszy program w C++

Lista komunikatów kompilatora

Tu brakuje średnika

Tu wykryto błąd

Rysunek 1.2: Wygląd okna programu Dev-C++ w przypadku wykrycia przezkompilator błędu w kodzie źródłowym.

w którym błąd faktycznie popełniono. Sam komunikat również nie jest klarownynawet dla osób biegle władających językiem angielskim. Nie narzekajmy jednakzbytnio: gdyby kompilator mógł sam poprawiać nasze błędy, mógłby też sampisać programy, a więc my nie bylibyśmy już do niczego potrzebni.

1.4.3 Uruchamianie programu w środowisku Dev-C++

Aby uruchomić w środowisku Dev-C++ skompilowany program, wystarczy wy-brać z menu pozycję Uruchom/Uruchom lub wcisnąć kombinację Ctrl+F10. Jeślijednak w ten sposób uruchomimy program 1.1, spotka nas przykra niespodzianka:Dev-C++ otworzy okienko konsoli, uruchomi w niej nasz program, po czym na-tychmiast zamknie okno wraz z konsolą! Całość ledwie mignie nam przed oczami.Aby uchronić okno programu przed natychmiastowym zamknięciem, zazwyczajna samym końcu funkcji main umieszcza się instrukcję

system(”pause”);

1.4.4 Struktura prostego programu w C++

Króciutkie (a więc też, niestety, raczej trywialne) programy w C++ mają nastę-pującą strukturę:

Wydruk 1.2: Ogólna struktura prostych programów w C++.#include <iostream>

int main(){...}

przy czym wielokropek należy zastąpić tu instrukcjami programu. Na razie tonie do końca prawdziwe stwierdzenie proszę przyjąć jak dogmat. Wyjaśnienie rolii znaczenia poszczególnych konstrukcji użytych w powyższym schemacie zostanieprzedstawione w kolejnych rozdziałach.

Page 21: C++ w dwunastu długich krokach

Obiekt std::cout i literały 21

1.5 Obiekt std::cout i literały

Nasz pierwszy program (wydruk 1.1 na str. 18) zawiera tylko jedną instrukcję:

std :: cout << ”Pierwszy program w C++” << ”\n”;

Jej znaczenie jest następujące. std::cout oznacza strumień danych związany z bie-żącym okienkiem. Do strumienia tego przesyłane są dane przy pomocy operatora<<. Najpierw przesyłany jest napis Pierwszy program w C++. Następnie do obiek-tu std::cout trafia kolejny, dość tajemniczy napis \n – jednak tak naprawdę niejest to „zwyczajny” napis, lecz specjalny znak sterujący, którego przesłanie nakonsolę powoduje przemieszczenie kursora na początek kolejnego wiersza.Spójrzmy teraz na tę samą instrukcję z bardziej ogólnego punktu widzenia:

• std:: w nazwie obiektu std::cout oznacza, że mamy do czynienia z obiek-tem z biblioteki standardowej; std jest tu nazwą przestrzeni nazw, a ::tzw. operatorem zasięgu. Zapis std::cout oznacza, że używamy obiektucout z przestrzeni nazw std.

• Nazwa obiektu cout to skrót od angielskiego „console output” (wyjście nakonsolę).

• Przed pierwszym użyciem strumienia std::cout należy zastosować makro-definicję #include <iostream>

• „Zwyczajne” napisy w C++ umieszcza się między znakami cudzysłowu.Dalej takie napisy będę nazywał tekstami.

• Znaki sterujące (zwane też znakami specjalnymi) zapisuje się jako ciągdwóch liter, z których pierwsza jest ukośnikiem (\).

• Zapis << oznacza specjalny operator. Służy on m.in. do przesyłania danychna konsolę.

• Wywołania operatora << można łączyć ze sobą w ciąg; odpowiada to kiero-waniu na konsolę kolejnych tekstów lub obiektów.

• Dwa kolejne teksty (ujęte w cudzysłów) można ze sobą łączyć w jeden. Dla-tego zamiast std::cout << "Pierwszy program w C++"<< "\n" można napi-sać

std :: cout << ”Pierwszy program w C++\n”;

• Każda instrukcja (prosta) musi kończyć się średnikiem.

Liczby wyświetla się na konsoli równie łatwo, jak napisy. Jedyna komplikacjapolega na tym, że komputery rozróżniają wiele rodzajów liczb. W szczególnościstarannie rozróżniają liczby całkowite od rzeczywistych.Spójrzmy na wydruk 1.3:

Wydruk 1.3: Program wyświetlający liczby i napisy.

#include <iostream>

int main(){std :: cout << ”Mam ” << 40 << ” lat\nLiczba pi = ” << 3.14 << ”...\n”;

}

Page 22: C++ w dwunastu długich krokach

22 Rozdział 1. Pierwszy program w C++

Program ten wyświetla dwie linijki tekstu:

Mam 40 latLiczba pi = 3.14...

Jak widać, obiekt std::cout w taki sam sposób – operatorem << – wyświetlateksty, liczby całkowite i liczby rzeczywiste (oczywiście te ostatnie zapisujemyz kropką, a nie przecinkiem dziesiętnym!). Warto też zwrócić uwagę na stosowanieodstępów w napisach występujących przed lub po liczbach. Liczby wyświetlanesą bowiem bez żadnych odstępów.

1.6 Definiowanie obiektów

Nasze dotychczasowe programy mają pewną ułomność: ograniczają się do wy-świetlania liczb lub tekstów umieszczonych dosłownie („literalnie”) w tekścieprogramu. W fachowym żargonie mówi się, że te programy wyświetlają warto-ści literałów . Wartość literału jest znana już podczas kompilacji i nie może uleczmianie podczas wykonywania programu. Naturalne pytanie brzmi: w jaki spo-sób zdefiniować obiekty, których wartość mogłaby ulegać zmianie podczas pracyprogramu? Odpowiedź przynosi program 1.4:

Wydruk 1.4: Program modyfikujący wartości zmiennych.

1 #include <iostream>#include <string>

int main()5 {

std :: cout << ”Jak masz na imie? ”;std :: string imie;std :: cin >> imie;

10 std :: cout << ”ile masz lat? ”;int wiek;std :: cin >> wiek;

std :: cout << ”Witaj, ” << imie15 << ”! Nie wiedzialem, ze masz ” << wiek << ” lat!\n”;

}

Definicja funkcji main składa się z siedmiu instrukcji5 ułożonych w trzechgrupach. W wierszach 6-8 program wczytuje imię użytkownika, w wierszach 10-12 prosi go o podanie wieku, po czym w długiej instrukcji zajmującej wiersze14 i 15 wyświetla komunikat zawierający zarówno imię jak i wiek użytkownika.Przyjrzyjmy się teraz szczegółowo sposobowi realizacji tych zadań.Znaczenie wierszy 6, 10 i 14-15 powinno już być jasne: program wyświetla

w nich komunikaty dla użytkownika. Natomiast w wierszach 7 i 11 mamy przy-kłady zastosowania nowej konstrukcji języka – definicji obiektu. Obiekt definiujesię w bardzo prosty sposób: najpierw podajemy nazwę klasy obiektu, a po niej –

5Liczba instrukcji równa jest liczbie średników.

Page 23: C++ w dwunastu długich krokach

Definiowanie obiektów 23

nazwę definiowanego obiektu6. Definicja obiektu jest traktowana jak instrukcja,dlatego należy zakończyć ją średnikiem. Co więcej, w C++ obowiązuje

Reguła 1.1 Definicje mogą przeplatać się ze zwykłymi instrukcjami

Dlatego obiekty można (i należy!) definiować dopiero tam, gdzie są napraw-dę potrzebne. W naszym przykładzie zdefiniowano dwa obiekty: imie, (klasystd::string) oraz wiek (klasy int). Klasa std::string służy do obsługi wszel-kiego rodzaju tekstów, natomiast int to klasa liczb całkowitych (int to skrót odangielskiego słowa integer oznaczającego właśnie liczbę całkowitą). Po zdefinio-waniu obiekty są gotowe do użycia. W wierszach 8 i 12 zmienia się ich wartości nate, które zostaną wprowadzone przez użytkownika przy pomocy klawiatury. Słu-ży do tego obiekt std::cin (jego nazwa pochodzi od angielskiego zwrotu consoleinput, czyli ‘wejście z konsoli’) oraz operator >>.W przeciwieństwie do wielu innych języków programowania, w C++ nie czyni

się żadnych niejawnych założeń co do klasy niezadeklarownych obiektów. Proszęzapamiętać następującą zasadę:

Reguła 1.2 Każdy obiekt musi mieć jawnie zadeklarowaną klasę (tj. typ)

Po przeczytaniu powyższej reguły uważnemu Czytelnikowi mogą przyjść dogłowy dwa pytania. Po pierwsze, gdzie w programie 1.4 znajdują się wymaganeprzez nią definicje obiektów std::cout i std::cin? Odpowiedź brzmi: za deklaracjętych obiektów odpowiedzialna jest dyrektywa #include <iostream>. Jej działaniepolega na dołączeniu do kodu programu wszystkich deklaracji związanych z dzia-łaniem standardowych strumieni wejścia i wyjścia. Bez tej dyrektywy kompilatornie wiedziałby, jaka jest klasa tych obiektów, a w związku z tym uznałby programza błędny.Drugie pytanie brzmi: skoro tak stanowczo wymaga się, by przed użyciem

jakiegokolwiek obiektu zdefiniować jego klasę, jak rozwiązano w C++ zagadnie-nie definiowania samych klas? W szczególności skąd kompilator „wie”, jakie jestznaczenie słówek int oraz std::string użytych w programie na oznaczenie klasobiektów? Otóż kilka klas podstawowych, m.in. int, jest zdefiniowanych w samymjęzyku C++; wszystkie inne muszą być jawnie zdefiniowane przed pierwszymużyciem. W szczególności definicja klasy std::string włączana jest do programudyrektywą #include <string>.Odpowiedź na powyższe pytania zawiera jednocześnie wyjaśnienie znacze-

nia dwóch pierwszych wierszy programu 1.4. Znajdujące się w nich dyrektywy7

#include służą do dosłownego wstawienia w miejscu ich wystąpienia treści plikówo nazwach podanych w nawiasach ostrokątnych. Takie pliki zwie się plikami na-główkowymi . Użyte w programie pliki iostream i string wchodzą w skład każdejinstalacji kompilatora C++, gdyż stanowią część biblioteki standardowej tegojęzyka. Dodatkowo każda niestandardowa biblioteka instaluje w kompilatorze do-datkowe pliki nagłówkowe (w moim kompilatorze jest ich – bagatela – około 1270).Zasadniczo pliki nagłówkowe służą do informowania kompilatora o możliwościachdanej biblioteki (tj. stanowią jej interfejs).

6Wiele podręczników C++ w użytym tu kontekście zastosowałoby terminy „zmienna” (za-miast „obiekt”) i „typ” (zamiast „klasa”). Ja uważam takie rozróżnianie za zbędne i niepedago-giczne; prowadzi ono bowiem do sytuacji, gdy słowa „obiekt” i „klasa” pojawiają się w połowiekursu, co sugeruje, że obiekty i klasy to coś niesamowicie skomplikowanego.7Dyrektywy zostaną szczegółowo omówione w Rozdziale 10.

Page 24: C++ w dwunastu długich krokach

24 Rozdział 1. Pierwszy program w C++

Plik nagłówkowe bardzo często same także zawierają dyrektywy #include włą-czające kolejne pliki nagłówkowe. Wiąże się z tym następująca ciekawostka: dwieniewinnie wyglądające w programie 1.4 dyrektywy #include w rzeczywistości włą-czają do jego tekstu zawartość nie dwóch, lecz 98 plików nagłówkowych, a po ichwłączeniu całkowita liczba wierszy programu wynosi nie 17, lecz. . . 23 726 (danedla kompilatora gcc 3.4.4 cygming special).

1.7 Identyfikatory, słowa kluczowe i dyrektywy

Definiując obiekty, musimy nadać im nazwy. Jak się wkrótce przekonamy, w C++można (i zazwyczaj trzeba) definiować też inne „byty”: funkcje, klasy, wyliczeniaitp. Wszystkie one muszą mieć nazwy. W żargonie C/C++ nie używa się jednakw tym kontekście słowa „nazwa”, lecz „identyfikator”.Identyfikatory mogą składać się wyłącznie liter, cyfr i znaku podkreślenia (_),

przy czym pierwszym znakiem nie może być cyfra (gdyż cyframi rozpoczynaćsię mogą wyłącznie liczby). Ilość znaków w identyfikatorze (we współczesnychkompilatorach) jest praktycznie nieograniczona. We wszystkich identyfikatorachlitery małe uważa się za różne od wielkich. Poprawnymi (i wzajemnie różnymi)identyfikatorami są więc np. rozmiar, Rozmiar, rozmiar_tablic, s00, M_PI.W punkcie 1.6 wspomniałem już, że pewne identyfikatory, np. int, zdefinio-

wane są w samym języku. Są to tak zwane słowa kluczowe. Słowa kluczowe mająściśle określone, niezmienne znaczenie. Nie można więc używać ich jak zwykłychidentyfikatorów (np. jako nazw zmiennych lub typów). W C++ istnieją aż 73słowa kluczowe (w praktyce używa się ok. 50). W niniejszym podręczniku słowakluczowe wyróżniono pogrubieniem.Oprócz słów kluczowych specjalną rolę w programie odgrywają tzw. dyrekty-

wy preprocesora. Są to specjalne „identyfikatory” poprzedzone znakiem #. Jestich w sumie kilkanaście, z czego w praktyce używa się ok. 6. W podręczniku dy-rektywy preprocesora również wyróżniam pogrubieniem (tak jak słowa kluczowe).Regułę 1.2 można teraz rozszerzyć następująco:

Reguła 1.3 Każdy identyfikator, który nie jest słowem kluczowym lub dyrektywąpreprocesora, najpierw musi być zadeklarowany, a dopiero potem może zostaćużyty.

Jak już wiemy, do deklarowania identyfikatorów bibliotecznych służy dyrektywa#include<...>; z kolei najczęściej spotykaną metodą deklaracji własnych identy-fikatorów jest podanie ich definicji (tj., definicji obiektu, funkcji, etc.).

1.8 Zapis programu

Wróćmy jeszcze raz do programu 1.4 (str. 22) i przyjrzyjmy się jego zapisowi.Mam nadzieję, że Czytelnik zwrócił uwagę na jego czytelność. Aby ją osiągnąć,zastosowałem następujące zasady:

• Dyrektywy #include rozpoczynają się od pierwszej kolumny i poprzedzająwszelkie inne fragmenty programu.

• Definicja funkcji rozpoczyna się od pierwszej kolumny, natomiast jej treśćjest (równomiernie) wcięta.

Page 25: C++ w dwunastu długich krokach

Cztery działania matematyczne i typ double 25

• Klamry otwierająca ({ ) i zamykająca (}) znajdują się w tej samej kolumnie.• Klamry są jedynymi znakami w swoich wierszach.• W jednym wierszu znajduje się co najwyżej jedna instrukcja;• Funkcjonalnie różne fragmenty programu zostały oddzielone od siebie pu-stymi wierszami.

• Identyfikatorom nadano nazwy odpowiadające ich przeznaczeniu.• Operatory oddzielono od reszty kodu odstępami.• Dzięki zastosowaniu w wierszu 15 wcięcia wyrównującego położenie opera-torów << nie ma wątpliwości, że jest on kontynuacją wiersza 14.

Podobnych zasad jest dużo więcej. Należy je sobie stopniowo przyswajać i sto-sować od samego początku nauki. Zabałaganiony program to zaproszenie dlawszelkiego rodzaju rodzaju błędów – a w konsekwencji strata czasu i pieniędzy.

Reguła 1.4 Programy pisze się dla ludzi a nie dla komputerów.

Znaczenie tej reguły jest take, że tekst programu musi być czytelny przede wszyst-kim dla człowieka, bo tylko człowiek może zrozumieć sens programu.

1.9 Cztery działania matematyczne i typ double

Pierwsze komputery i pierwsze programy służyły wyłącznie do wykonywania nu-żących obliczeń matematycznych. W gruncie rzeczy procesory komputerów wciążpotrafią niewiele więcej niż dodawać, odejmować, mnożyć i dzielić. Sposób wyko-nywania tych operacji w C++ przedstawia wydruk 1.5.

Wydruk 1.5: Cztery operacje arytmetyczne w akcji.

3 int main(){double x, y;std :: cout << ”Podaj dwie liczby, x i y.\nx = ”;

7 std :: cin >> x;std :: cout << ”y = ”;std :: cin >> y;std :: cout << x << ” + ” << y << ” = ” << x + y << ”\n”;std :: cout << x << ” − ” << y << ” = ” << x − y << ”\n”;

12 std :: cout << x << ” ∗ ” << y << ” = ” << x ∗ y << ”\n”;std :: cout << x << ” / ” << y << ” = ” << x / y << ”\n”;std :: cout << ”2(” << x << ” + ” << y << ”) = ” << 2∗(x + y) << ”\n”;

}

Podaj dwie liczby, x i y.x = 3y = 43 + 4 = 73 - 4 = -13 * 4 = 123 / 4 = 0.752(3 + 4) = 14

Page 26: C++ w dwunastu długich krokach

26 Rozdział 1. Pierwszy program w C++

Jak widać, dodawanie, odejmowanie, mnożenie i dzielenie wykonuje się za po-mocą znanych ze szkoły operatorów +, -, * oraz /. Dodatkowo w zapisie działańmatematycznych można używać nawiasów. Jak zwykle, mnożenie i dzielenie ma-ją pierwszeństwo nad dodawaniem i odejmowaniem (czyli wartością wyrażenia2+2*2 jest 6 a nie 8).W piątym wierszu programu 1.5 pojawiło się nowe słowo kluczowe: double.

Służy ono do definiowania liczb rzeczywistych.

1.10 Jeszcze więcej matematyki

Operacje matematyczne to wdzięczny motyw nauki programowania, istnieje bo-wiem ogromna liczba gotowych i łatwych w użyciu bibliotek matematycznych.Ot, choćby wchodząca w skład biblioteki standardowej C++ biblioteka cmath.Jak już Czytelnik zapewne się domyśla, włącza się ją do programu makrodefinicją#include<cmath>. Udostępnia ona najbardziej podstawowe i najczęściej używanestałe i funkcje matematyczne znane ze szkoły lub studiów. Podstawowe stałematematyczne przedstawiam w tabeli 1.1, a wybrane funkcje – w tabeli 1.2.Stałe zdefiniowane w bibliotece cmath można by obliczyć przy pomocy funkcji

z tablicy 1.2, ale to kosztowałoby dużo więcej czasu niż zastosowanie gotowychwartości. Z kolei wśród funkcji warto zwrócić uwagę na kilka „smaczków”.

• Powszechny wśród początkujących błąd polega na stosowaniu abs do liczbzmiennopozycyjnych. Do takich liczb należy używać fabs lub std::abs.

• Kłopoty z funkcjami abs i fabs wynikają stąd, że C++ „odziedziczył” jez języka C. W C++ zastąpiono je jedną funkcją std::abs. Wiele osób za-pomina jednak pisać „przedrostek” std:: przed abs.

• Podobny problem jest z funkcją liczącą potęgi. Funkcja pow została przejętaz języka C i oblicza potęgi poprzez złożenie funkcji wykładniczej i logaryt-micznej (tj. zawsze korzysta ze wzoru xy = exp(y lnx)). W C++ dodanobardziej efektywną funkcję std::pow, która potęgi całkowite (np. x4) obliczawyłącznie przy pomocy mnożeń i dodawań, stosując przy tym bardzo małą(często najmniejszą możliwą) liczbę tych operacji.

• Funkcja atan2 przydaje się w sytuacji, gdy mamy współrzędne punktui chcemy obliczyć kąt, jaki tworzy on z osią „x”.

Proszę się nie obawiać – w dalszej części nie będę korzystał z funkcji Bessla;należy za to zwrócić uwagę na to, że nawet tak skomplikowane funkcje mamy „nawyciągnięcie ręki”.

nazwa wartość nazwa wartość nazwa wartośćM_E exp(1) M_PI π M_1_PI 1/πM_LN2 ln 2 M_PI_2 π/2 M_2_PI 2/πM_LN10 ln 10 M_PI_4 π/4 M_SQRT2

√2

M_LOG10E log10 e M_2_SQRTPI 2/√π M_SQRT1_2 1/

√2

M_LOG2E log2 e

Tabela 1.1: Stałe matematyczne z biblioteki standardowej C i C++ .

Page 27: C++ w dwunastu długich krokach

Jeszcze więcej matematyki 27

nazwa opisabs wartość bezwzględna liczby całkowitej (tj. |n|)fabs wartość bezwzględna liczby rzeczywistej (tj. |x|)std::abs wartość bezwzględna dowolnej liczbysin, cos, tan, ctan funkcje trygonometryczneasin, acos, atan, actan odwrotne funkcje trygonometryczneatan2 dwuargumentowa wersja funkcji atanexp, pow funkcja wykładnicza i potęgowastd::pow „inteligentna” funkcja potęgowalog, log10, log2 logarytmy (naturalny, dziesiętny i o podstawie 2)sqrt, cbrt, hypot, √ , 3√ i długość przeciwprostokątnej trójkataexpm1, log1p odpowiednio exp(x)− 1 oraz ln(1 + x)sinh, cosh, tanh funkcje hiperboliczneasinh, acosh, atanh odwrotne funkcje hiperboliczneerf, erfc, tgamma funkcje specjalnej0, j1, jn, y0, y1, yn funkcje Bessla pierwszego i drugiego rodzaju

Tabela 1.2: Wybrane funkcje udostępniane w standardowej bibliotece cmath.

Biblioteka cmath posłuży do wprowadzenia kolejnych koncepcji: inicjalizacjiobiektów, wywoływania funkcji bibliotecznych oraz dodawania komentarzy. Za-gadnienia te ilustruję w programie 1.6. Jego celem jest pobranie od użytkownikainformacji o długości boków trójkąta (a, b, c) i obliczenie pola trójkąta ze wzoruHerona: S =

√p (p− a)(p− b)(p− c), gdzie p = (a+ b+ c)/2.

Definicja funkcji main rozpoczyna się definicją trzech zmiennych rzeczywi-stych o nazwach a, b, i c. Zwróćmy uwagę na to, że w jednej instrukcji możnazdefiniować kilka obiektów tego samego typu (tej samej klasy), oddzielając ichnazwy przecinkami. Następnie w wierszach 10-15 zmiennym a, b, i c przypisywanesą wartości wprowadzane z klawiatury przez użytkownika. Wiersz 16 przedstawiakonstrukcję bardzo typową dla C++. Otóż definiuje się tu zmienną p klasy double(a więc p odpowiada liczbie rzeczywistej), po czym w tej samej instrukcji nadajesię jej wartość początkową.

Reguła 1.5 Obiekty definiuj dopiero wtedy, gdy są naprawdę potrzebne. Jeśli jestto możliwe, od razu określ ich wartość początkową.

Wwierszu 17 mamy podobną sytuację – definiujemy obiekt pole i od razu przypi-sujemy mu wartość początkową. W tym celu posługujemy się funkcją bibliotecznąsqrt, która – zgodnie z tabelą 1.2 – oblicza pierwiastek kwadratowy swojego argu-mentu. Żeby móc użyć tej funkcji, w wierszu 4 dołączyłem do programu deklaracjewszystkich funkcji standardowej biblioteki matematycznej.Dodatkowo w powyższym programie w wierszach 1 i 8 zastosowałem (skrom-

ne) komentarze. Komentarze w C++ rozpoczynają się parą ukośników (//) i koń-czą wraz z końcem wiersza. Są zupełnie pomijane przez kompilator, a służą opi-sowi programu (por. Reguła 1.4 ze str. 25). W C++ można też stosować (choćnie jest to zalecane) komentarze jak w języku C: rozpoczyna się je znakami /*,a kończy parą */.

Page 28: C++ w dwunastu długich krokach

28 Rozdział 1. Pierwszy program w C++

Wydruk 1.6: Zastosowanie funkcji sqrt z biblioteki cmath.

1 // program oblicza pole trójkąta ze wzoru Herona. (c) Z. Koza, 2005

#include <iostream>#include <cmath>

5

int main(){double a, b, c; // boki trójkąta

10 std :: cout << ”podaj dlugosci bokow a, b i c trojkata:\na = ”;std :: cin >> a;std :: cout << ”b = ”;std :: cin >> b;std :: cout << ”c = ”;

15 std :: cin >> c;double p = (a + b + c)∗0.5;double pole = sqrt(p∗(p − a)∗(p − b)∗(p − c));std :: cout << ”pole trojkata o podanych dlugosciach bokow wynosi ”

<< pole << ”\n”;20 }

podaj dlugosci bokow a, b i c trojkata:a = 3b = 4c = 5pole trojkata o podanych dlugosciach bokow wynosi 6

1.11 Upraszczanie zapisu obiektów i funkcji bi-blioteki standardowej

Wiele osób narzeka, że w zasadzie nazwy wszystkich obiektów, klas i funkcjibiblioteki standardowej C++ należy poprzedzać „przedrostkiem” std::. Ja tenprzedrostek lubię, bo informuje mnie o źródle pochodzenia danej nazwy. Istniejeprosty sposób, by pozbyć się konieczności używania std::. W tym celu wystar-czy na początku programu umieścić instrukcję using namespace std;. Ilustruje toprogram8 1.7.

1.12 Źródła informacji

Podczas pisania programu niezbędny jest dostęp do dokładnego opisu językai używanych bibliotek. Najlepszy opis języka C++ znajdziemy w książce Stro-ustrupa [1]. Opis biblioteki standardowej języka C (która wchodzi w skład bi-blioteki standardowej) dostępny jest powszechnie w wielu formatch (m.in. manpages i info w Linuksie, HLP w Windows). Plik w formacie HLP można „pod-piąć” do środowiska Dev-C++. W tym celu wystarczy zainstalować pakiet „GNU8Przy okazji na wydruku 1.7 zapoznajemy się z dwoma nowymi słowami kluczowymi: using

oraz namespace.

Page 29: C++ w dwunastu długich krokach

Q & A 29

Wydruk 1.7: Zastosowanie instrukcji using namespace do uproszczenia zapisu

1 #include <iostream>

using namespace std;

5 int main(){cout << ”jestem krotki\n”; // teraz nie muszę pisać std::cout

}

C Library Reference”. Opis biblioteki standardowej C++ znajduje się w książceStroustrupa [1], natomiast nie ma go jeszcze w postaci elektronicznej. Bardzodobry opis biblioteki STL (która wchodzi w skład biblioteki standardowej C++)można znaleźć na stronie http://www.sgi.com. W przypadku bibliotek niestan-dardowych ich opis (tj. dokumentacja) musi stanowić część samej biblioteki.

1.13 Q & A

Jaka jest różnica między zmienną a obiektem?W wielu podręcznikach rozróżnia się słowa ‘zmienna’ i ‘obiekt’. Wg. tej kon-wencji zmienna ma typ (klasę) zdefiniowaną w samym języku, (np. double, int),a obiekt ma typ zdefiniowany w bibliotece (np. standardowej) lub przez progra-mistę. W sumie różnica jest wyłącznie terminologiczna.Czy można ignorować ostrzeżenia generowane przez kompilator?Nie, nie i jeszcze raz nie! Kompilator ostrzega przed konstrukcjami zgodnymiz definicją języka (a więc formalnie poprawnymi), jednak z reguły inaczej in-terpretowanymi przez kompilator niż przez człowieka. Każdą instrukcję możnai należy tak napisać, by jej kompilacja nie generowała ostrzeżeń.Podobno w kompilatorze gcc istnieje kilka poziomów ostrzeżeń?Tak (i dotyczy to każdego kompilatora C/C++). Domyślnie kompilator gcc prak-tycznie nie generuje żadnych ostrzeżeń. Pierwszy poziom uzyskuje się opcją -Wall,a drugi – opcją -Wextra (lub równoważną jej opcją -W). Niektórzy dorzucajądo tego -pedantic. Poziom trzeci uzyskuje się po gruntownym przeanalizowaniupodręcznika kompilatora i wielu eksperymentach z kilkudziesięcioma opisanymiw nim dodatkowymi opcjami (por. zadanie 1 oraz s. 249).Czy w tekstach programów można używać polskich liter?Polskie litery mogą pojawić się wyłącznie w napisach lub komentarzach. Niepolecam używania ich w programach uruchamianych w konsoli Windows, gdyżzamiast liter na ekranie zobaczy się „krzaki”. Z kolei używanie polskich literw komentarzach prowadzi do kłopotów (znów te „krzaki”!) przy przenoszeniuprogramów między systemami operacyjnymi.Dlaczego w innych podręcznikach C++ zamiast #include<iostream> uży-wa się #include<iostream.h>?Bo to są stare podręczniki opisujące standard C++ z zeszłego tysiąclecia. W no-wym standardzie nazwy plików nagłówkowych biblioteki standardowej C++ niezawierają rozszerzenia .h.

Page 30: C++ w dwunastu długich krokach

30 Rozdział 1. Pierwszy program w C++

Co oznacza litera ‘c’ w nazwie biblioteki cmath?Oznacza, że biblioteka ta rozszerza bibliotekę standardową języka C. Oryginal-na nazwa tej rozszerzanej biblioteki nie zawiera początkowego ‘c’ i kończy sięrozszerzeniem .h (w tym wypadku jest to math.h). W ten sposób obie bibliotekimogą funkcjonować bezkolizyjnie. Proszę zwrócić uwagę na to, że użyłem słowarozszerza – biblioteka cmath z reguły zawiera dyrektywę #include<math.h> orazdużo dodatkowych deklaracji.

1.14 Quiz

1. W jaki sposób w Twoim kompilatorze kompiluje się programy?2. Czym różni się kompilacja od łączenia programów?3. Jakie dwa znaczenia ma słowo kompilacja w kontekście informatycznym?4. Dlaczego należy posługiwać się możliwie jak najnowszą wersją kompilatora?5. W jaki sposób w Twoim środowisku programistycznym ustala się opcjekompilatora?

1.15 Problemy

1. W zależności od używanego środowiska programistycznego ustaw jego pa-rametry tak, aby Twój kompilator domyślnie generował rozsądną liczbękomunikatów diagnostycznych i traktował ostrzeżenia jak błędy9.[Dev-C++] Wybierz z menu programu Dev-C++ opcję Narzędzia / Opcjekompilatora i na zakładce kompilator w polu Dodaj te polecenia do wierszapoleceń kompilatora umieść -W -Wall -Weffc++ -Wfloat-equal -Werror.[Linux/Unix] Jeżeli programy kompilujesz bezpośrednio poleceniem g++,utwórz do niego alias tak, by było rozwijane dog++ -W -Wall -Weffc++ -Wfloat-equal -Werror.[MS Visual Studio] Ustaw „warning level” na 3.

2. Napisz, skompiluj i uruchom program, który wczytuje liczbę i wyświetla jejodwrotność.

3. Napisz, skompiluj i uruchom program, który wczytuje współrzędne punktu(x i y) w układzie kartezjańskim i oblicza jego odległość od środka układuwspółrzędnych oraz kąt, jaki tworzy on z osią „x”.

4. Napisz, skompiluj i uruchom program, który wczytuje wartości współczyn-ników trójmianu kwadratowego ax2 + bx + c i wyznacza jego pierwiastki(x1,2 = (−b±

√∆)/2a, gdzie ∆ = b2 − 4ac).

5. Przetestuj poprzedni program dla różnych wartości parametrów, włączającw to parametry, dla których pierwiastki nie istnieją.

6. Odnajdź w swojej instalacji kompilatora plik math.h, a w nim odszukajdefinicję stałej M_PI.

9Doświadczeni programiści nie używają opcji -Werror, gdyż mają ją w głowie.

Page 31: C++ w dwunastu długich krokach

Rozdział 2

Wyrażenia i instrukcje

Instrukcje sterujące. Pętle. Typy wbudowane. Obiekty stałe. Strumienie, napisy, wektoryi słowniki. Obiekty lokalne i globalne. Operatory. Wyrażenia i instrukcje.

Programy przedstawione w tym rozdziale wciąż będą się ograniczać do jednejfunkcji main, ale zakres wyłożonego tu materiału pozwoli nam pisać całkiem jużskomplikowany kod o właściwościach, które trudno byłoby osiągnąć w takichjęzykach, jak FORTRAN, C czy Pascal. A to dopiero drugi rozdział!

2.1 Instrukcje sterujące

2.1.1 Instrukcja if... else...

Zamieszczony w poprzednim rozdziale program 1.6 (str. 27) ma pewną wadę:przed przystąpieniem do obliczeń nie sprawdza, czy trójkąt o bokach podanychprzez użytkownika w ogóle istnieje. Wydruk 2.1 ilustruje, w jaki sposób możnado programu 1.6 dodać odpowiedni test sensowności danych.W wierszu 15 dodano instrukcję sterującą if. Po słowie kluczowym if umiesz-

czamy w nawiasach okrągłych specjalny test z reguły zawierający operatory re-lacyjne (zestawienie wszystkich takich operatorów zawiera tabela 2.1). W przy-padku programu 2.1 testowane wyrażenie to znany ze szkoły warunek trójkąta.Instrukcja występująca bezpośrednio za testem wykonywana jest wtedy i tylkowtedy, gdy testowany warunek jest spełniony. W naszym przypadku nie jest tozwykła pojedyncza instrukcja, lecz ich ciąg ujęty w nawiasy klamrowe (od wiersza16 do 21). Taki ujęty w klamry zestaw instrukcji zwany jest instrukcją blokową.Po tej instrukcji może (ale nie musi) pojawić się słowo kluczowe else i kolejnainstrukcja. Instrukcja następująca po else wykonywana jest wtedy i tylko wtedy,gdy warunek testowany w instrukcji if nie jest spełniony.Oczywiście każda z instrukcji występujących po if lub else sama może być

instrukcją warunkową. Typowy przykład przedstawia wydruk 2.2. Złożone in-strukcje warunkowe są mało czytelne i podatne na błędy. Dlatego należy ichunikać.W programie 2.1 warto zwrócić jeszcze uwagę na dwie nowinki. Pierwsza

występuje w wierszu 15 i jest nią operator and, który oznacza koniunkcję (ilo-czyn) logiczny. Wyrażenie warunek1 and warunek2 jest prawdziwe wtedy i tylko

31

Page 32: C++ w dwunastu długich krokach

32 Rozdział 2. Wyrażenia i instrukcje

Wydruk 2.1: Przykład zastosowania instrukcji if

1 #include <iostream>#include <cmath>

int main()5 {

double a, b, c; // boki trójkąta

std :: cout << ”podaj dlugosci bokow a, b i c trojkata:\na = ”;std :: cin >> a;

10 std :: cout << ”b = ”;std :: cin >> b;std :: cout << ”c = ”;std :: cin >> c;// boki trójkąta muszą spelniać 3 nierówności trójkąta :

15 if ( (a + b > c) and (a + c > b) and (b + c > a) ){double p = (a + b + c)∗0.5;double pole = std::sqrt(p∗(p − a)∗(p − b)∗(p − c));std :: cout << ”pole trojkata o podanych dlugosciach bokow wynosi ”

20 << pole << ”\n”;}elsestd :: cerr << ”∗∗∗ Trojkat o podanych dlugosciach bokow nie istnieje ∗∗∗\n”;

}

Wydruk 2.2: Złożony ciąg instrukcji if ...else

...if (z > 0)sign = 1;

else if (z == 0)sign = 0;

elsesign = −1;

...

Operator Przykład użycia Znaczenie< if(x < y) mniejszy niż<= if(x <= y) mniejszy niż lub równy== if(x == y) równy>= if(x >= y) większy niż lub równy> if(x > y) większy niż!= if(x != y) różny od

Tabela 2.1: Operatory relacyjne (porównawcze) w C++.

Page 33: C++ w dwunastu długich krokach

Pętle 33

wtedy, gdy prawdziwe są jego oba argumenty. Alternatywny (i zdecydowanie bar-dziej popularny, gdyż odziedziczony z języka C) zapis operatora and to && (np.if(x >0 && x < 10). . .Druga nowinka występuje w wierszu 23, w którym zastosowano strumień

std::cerr do natychmiastowego wyświetlenia komunikatu diagnostycznego naekranie. W przeciwieństwie do strumienia std::cout, strumień std::cerr nie jestoptymalizowany pod kątem efektywności i zapisywane w nim znaki są natych-miast wyświetlane na ekranie.

2.2 Pętle

2.2.1 Pętla for

Wyobraźmy sobie następujący problem: co miesiąc do funduszu emerytalnegowpłacamy x = 100 złotych składki. Fundusz potrąca z niej y = 10%, a resztęinwestuje, osiągając stały dochód z = 5% w skali roku. Jaką kwotą będziemydysponować w momencie przejścia na emeryturę po n = 35 latach pracy?Prosty program rozwiązujący ten problem przedstawiam na wydruku 2.3.

Wydruk 2.3: Przykład zastosowania instrukcji for.

1 #include <iostream>

int main(){

5 double skladka = 100.0; // 100 zlotychdouble prowizja = 0.1; // 10% prowizji od kazdej skladkidouble rentownosc = 0.05; // 5% rentownosci inwestycji (w skali roku)int ile lat = 35;

10 double kapital = 0.0;for (int i = 0; i < 12∗ ile lat ; i++){kapital ∗= (1.0 + rentownosc/12.0);kapital += skladka ∗ (1.0 − prowizja);

15 }std :: cout << ”suma wplaconych skladek: ” << skladka ∗ 12 ∗ ile lat;std :: cout << ”\nkapital koncowy = ” << kapital << ”\n”;

}

Wiersze 5-10 poświęcone są definicji zmiennych. Warto zwrócić uwagę na to,że od samego początku staram się nadawać zmiennym nazwy adekwatne do ichprzeznaczenia i przypisać im wartości początkowe. W wierszu 11 pojawia się kilkanowych konstrukcji, wśród których wyróżnia się instrukcja for. Służy ona dowielokrotnego powtarzania tych samych instrukcji i oprócz słowa kluczowego forskłada się z dwóch części: preambuły (ujętej w nawiasy okrągłe) i wielokrotniewykonywanej instrukcji, przy czym sama preambuła składa się z trzech częścioddzielonych średnikami. Ilustruje to następujący schemat:

for (instr. inicjująca; warunek kontynuacji; instr. kończąca krok)INSTRUKCJA;

Page 34: C++ w dwunastu długich krokach

34 Rozdział 2. Wyrażenia i instrukcje

Znaczenie poszczególnych składników instrukcji for jest następujące.

• Instrukcja inicjująca. Dowolna instrukcja służąca do rozruchu pętli. Wy-konywana jest dokładnie raz. W praktyce umieszcza się w niej definicjezmiennej sterującej działaniem pętli wraz z inicjatorem. Czasami pomijasię część definicyjną, a pozostawia inicjator. Czasami widuje się tu definicjekilku zmiennych sterujących (tego samego typu) z inicjatorami. Wg. aktual-nego standardu C++ wszelkie zmienne zdefiniowane w instrukcji inicjującejsą dostępne wyłącznie wewnątrz pętli. W przypadku programu 2.3 ozna-cza to, że zmiennej i można używać wyłącznie w wierszach 11-15. Starszekompilatory (np. MS Visual C++ 6.0) zakładały, że zmienne definiowanew instrukcji inicjującej są dostępne także za pętlą.

• Warunek kontynuowania pętli. Absolutnie dowolne wyrażenie (nie in-strukcja!), którego wartość można przypisać zmiennej typu bool. Wartośćtego wyrażenia obliczana jest każdorazowo przed wykonaniem głównej in-strukcji pętli. Jeżeli wartość ta równa jest true, nastąpi wywołanie IN-STRUKCJI, a po niej instrukcji kończącej krok. W przeciwnym wypadku(gdy warunek kontynuowania pętli nie jest spełniony), pętla jest przerywanai sterowanie programu przechodzi do następnej instrukcji za pętlą for.

• Instrukcja kończąca krok. Instrukcja, która wykonywana jest zawszepo właściwej instrukcji pętli. Najczęściej modyfikuje zmienne zdefiniowanew instrukcji inicjującej.

• INSTRUKCJA.Wielokrotnie wykonywana instrukcja (poniżej zwana in-strukcją główną). Najczęściej jest to instrukcja blokowa (por. str. 31).

W naszym przykładowym programie 2.3 instrukcja for steruje wykonanieminstrukcji blokowej zapisanej w wierszach 12-15. Ta główna instrukcja pętli składasię z dwóch kroków: w wierszu 13 powiększamy kapitał o zysk z inwestycji (w sto-sunku miesięcznym, stąd dzielenie przez 12), a w wierszu 14 dodajemy kolejnąskładkę (pomniejszoną o prowizję towarzystwa, 10%). Aby obliczyć wzrost kapi-tału w ciągu miesiąca, należy go pomnożyć przez 1 + rentownosc/12. Służy dotego operator *=. Analogicznie, aby uwzględnić wzrost kapitału na skutek wpłace-nia kolejnej składki, należy powiększyć go o skladka * (1 - prowizja). Operacjitej odpowiada operator +=. Szerzej o operatorach += i *= piszę w rozdziale 2.8.Preambuła instrukcji for ma w programie 2.3 typową postać. Instrukcją ini-

cjującą jest int i = 0. Jej zadaniem jest zdefiniowanie zmiennej sterującej i oraznadanie jej wartości początkowej 0. Zmienna ta przechowuje informację o tym,ile razy wykonywana była główna instrukcja pętli. Każdorazowo przed wykona-niem głównej instrukcji pętli sprawdzany jest warunek jej kontynuacji. W naszymprzypadku brzmi on i < 12 * ile_lat. Uwzględniając, że ile_lat ma wartość 35,powyższy warunek równoważny jest zapisowi i < 12 * 35. Każdorazowo po wy-konaniu głównej instrukcji wykonywana jest instrukcja kończąca krok. W naszymwypadku brzmi ona i++. Zapis ten oznacza rozkaz zwiększenia zmiennej i o je-den. Dzięki temu mamy gwarancję, że pętla zostanie wykonana 12 * 35 razy, czylidokładnie tyle, ile jest miesięcy w ciągu 35 lat.W dobrze skonstruowanej pętli for preambuła powinna zawierać wyłącznie

instrukcje związane ze sterowaniem pętli. Czasem instrukcje sterujące umieszczasię również w głównej instrukcji pętli, ale powinno się tego unikać (wyjątkiem są

Page 35: C++ w dwunastu długich krokach

Pętle 35

omówione poniżej, „eleganckie” instrukcje break i continue). Oddzielenie częścisterującej od wykonawczej to kwintesencja prawidłowego zastosowania pętli for.Dowolna część preambuły instrukcji for może być pusta. Jeżeli pominiemy

warunek kontynuowania pętli, kompilator uzna, że chodzi nam o pętlę nieskoń-czoną (czyli tak, jakbyśmy w miejsce warunku wpisali true).

Reguła 2.1 Preambuła for( ; ; ) definiuje pętlę nieskończoną.

2.2.2 Pętle while i do

Czasami sterowanie pętlą for nie wymaga definiowania specjalnych zmiennychsterujących. W tych wypadkach zamiast instrukcji for z pustą instrukcją inicju-jącą i pustą instrukcją kończącą krok stosuje się instrukcję while lub do...while.Pierwszą z nich stosujemy w wypadku, gdy liczba realizacji instrukcji sterowanejpętlą może być równa zero; jeśli instrukcja ma być wykonana co najmniej raz,bardziej poręczna jest instrukcja do...while.

Spostrzeżenie 2.1 Instrukcja

for ( ; warunek; )INSTRUKCJA;

Jest równoważna instrukcji

while (warunek)INSTRUKCJA; // <−− ta instrukcja może nie wywołać się ani razu

Spostrzeżenie 2.2 Ciąg instrukcji

INSTRUKCJA;for ( ; warunek; )

3 INSTRUKCJA; // <-- ta sama instrukcja, co przed for

Jest równoważny instrukcji

doINSTRUKCJA; // <−− ta instrukcja wykona się co najmniej razwhile(warunek);

2.2.3 Instrukcje break i continue

Wydruk 2.4 przedstawia przykład zastosowania pętli nieskończonej. W programietym prosimy użytkownika o hasło1 tak długo, aż wpisze słowo „Gucio”. W tymmomencie instrukcja break przerywa działanie pętli i przenosi sterowanie do wier-sza 14.Oprócz instrukcji break do sterowania pętlą można wykorzystać instrukcję

continue. Jak wskazuje jej nazwa, służy ona do kontynuowania danej pętli. Kon-kretnie rzecz ujmując, continue powoduje natychmiastowe uznanie bieżącego wy-wołania zestawu instrukcji sterowanych pętlą za zakończone i przejście do kroku

1W programie profesjonalnym wpisywane hasło nie byłoby – tak jak tu – wyświetlane naekranie.

Page 36: C++ w dwunastu długich krokach

36 Rozdział 2. Wyrażenia i instrukcje

Wydruk 2.4: Użycie instrukcji break do przerwania nieskończonej pętli for.

1 #include <iostream>#include <string>

int main()5 {

std :: string kod;for ( ; ; ){std :: cout << ”podaj haslo: ”;

10 std :: cin >> kod;if (kod == ”Gucio”)break;

}std :: cout << ”Witam w systemie!\n”;

15 }

kończącego krok. Użycie continue pozwala uniknąć stosowania rozbudowanych in-strukcji if...else. Przykład zastosowania tej instrukcji przedstawiam w dalszejczęści podręcznika (np. na wydruku 4.5 ze str. 93).Oczywiście wewnątrz pętli można jako instrukcje stosować („zagnieżdżać”)

inne pętle. W tym przypadku obowiązuje następująca reguła.

Reguła 2.2 Instrukcja break użyta w pętli zagnieżdżonej w innej pętli przerywadziałanie tylko jednej z nich (tj. pętli wewnętrznej).

2.3 Typy wbudowane

W poprzednim rozdziale pobieżnie zapoznaliśmy się z dwoma typami (czyli kla-sami) wbudowanymi – int i double. Oba te typy należą do kategorii tzw. typówprostych, czyli typów (= klas) zapisywanych w formie słów kluczowych i repre-zentujących elementarne „atomy”, z których budowane są typy złożone.Typy proste dzielą się na całkowite i zmiennopozycyjne. Typy całkowite od-

powiadają liczbom całkowitym, a zmiennopozycyjne – rzeczywistym.

2.3.1 Typy całkowite

Jak pamiętamy z lekcji matematyki, nie istnieje najmniejsza ani największa liczbacałkowita. Oczywiście komputery nie są w stanie zapisać w swojej pamięci liczbdowolnie dużych czy dowolnie małych. To oznacza, że komputery nie używajądo obliczeń tych samych liczb, które poznajemy w szkole. Komputery używa-ją okrojonych liczb całkowitych, tzn. takich podzbiorów liczb całkowitych (lubnaturalnych), w których istnieją zarówno wartość najmniejsza jak i największa.W C++ zdefiniowano cztery rodzaje takich namiastek liczb naturalnych i ty-

leż namiastek liczb całkowitych. Ich właściwości prezentuje Tabela 2.2. Wartościminimalne dla poszczególnych typów podane w tej w tabeli mają charakter orien-tacyjny: standard nie nakłada (niemal) żadnych ograniczeń na te wartości i w

Page 37: C++ w dwunastu długich krokach

Typy wbudowane 37

Typ Wartość minimalna Wartość maksymalnalong int −2 147 483 648 2 147 483 647int −2 147 483 648 2 147 483 647short −32 768 32 767signed char −128 127unsigned long int 0 4 294 967 295unsigned int 0 4 294 967 295unsigned short 0 65 535unsigned char 0 255

Dodatkowe typy dostępne w kompilatorach gcc:

long long −9 223 372 036 854 775 808 9 223 372 036 854 775 807unsigned long long 0 18 446 744 073 709 551 615

Tabela 2.2: Wbudowane typy całkowite C++. Wartości minimalne i maksymal-ne dla różnych kompilatorów i platform mogą być różne. Tu podałem wartościużywane przez 32-bitowy kompilator MinGW g++ 3.4.2.

różnych kompilatorach mogą mieć one inne wartości. Spowodowane to zostałotroską o wydajność – przyjęto bowiem, że typ int powinien odpowiadać najbar-dziej wydajnemu typowi całkowitemu na danej platformie. Dlatego w 16-bitowychkomputerach klasy PC (lata 80-te ubiegłego wieku) największą wartością liczbtypu int było zaledwie 32 767; obecnie jest to 2 147 483 647 a w przyszłości możeto być 9 223 372 036 854 775 807.Jak widać w tabeli 2.2, obiekty części typów całkowitych mogą przyjmować

wyłącznie wartości dodatnie lub zero. Typy te zwie się typami bez znaku. Nazwytych typów zawierają modyfikator unsigned. Pozostałe typy mogą przyjmowaćwartości ujemny lub dodatnie i należą do typów ze znakiem.Standard nie określa, co się stanie, gdy wynik działania matematycznego prze-

kroczy dozwolony zakres wartości w danym typie. W praktyce komputerowe typycałkowite mają strukturę pierścienia: zwiększenie o jeden liczby największej dajew wyniku liczbę najmniejszą. I odwrotnie: zmniejszenie najmniejszej możliwejliczby o jeden daje w wyniku liczbę największą.

Spostrzeżenie 2.3 Suma dwóch dodatnich liczb typu int może być ujemna. Po-dobnie suma dwóch liczb ujemnych może być dodatnia, a ich iloczyn ujemny.

Spostrzeżenie 2.4 Suma liczb dowolnego typu całkowitego może być mniejszaod każdego ze składników.

O powyższych właściwościach „komputerowych” liczb całkowitych należy za-wsze pamiętać podczas pisania programów. Wartości minimalne i maksymalnetypu int są obecnie dość duże, jednak bez trudu można natrafić na sytuację,w której wartości wyrażeń przekroczą dopuszczalny zakres. Należy umieć prze-widywać możliwość wystąpienia takiej sytuacji i odpowiednio dostosować do niejprogram.Oprócz typów przedstawionych w tabeli 2.2, wiele kompilatorów standardo-

wo rozpoznaje dodatkowe typy. Ich nazwy z reguły kończą się znakami _t. Dotej kategorii należą m.in. size_t (typ wartości operatora sizeof), time_t (typ

Page 38: C++ w dwunastu długich krokach

38 Rozdział 2. Wyrażenia i instrukcje

używany przez funkcję time) czy int32_t (typ całkowity ze znakiem zajmującydokładnie 32 bity). Tak naprawdę wszystkie one równoważne są jakiemuś typowiwbudowanemu z tabeli 2.2.Pewną osobliwością wśród typów całkowitych jest typ char i jego typy pochod-

ne: signed char i unsigned char. Można go używać jak zwykłych liczb całkowi-tych o mocno ograniczonym zakresie, jednak przy wyświetlaniu obiektów tegotypu na ekranie pojawiają się nie liczby, lecz odpowiadające im znaki (z tegopowodu obiekty tych klas zwie się znakami). Ilustruje to następujący fragmentprogramu:

Wydruk 2.5: Wyświetlanie obiektów różnych typów całkowitych.

int i = 100;short s = 100;char c = 100;std :: cout << i << ” ” << s << ” ” << c << ”\n”;

100 100 d

Wysłanie do strumienia std::cout obiektów typu int i short powoduje wyświe-tlenie ich wartości, natomiast wysłanie obiektu typu char powoduje wyświetlenieznaku o kodzie ASCII równym wartości liczby (tu: 100).Oto kilka rad praktycznych dotyczących typów całkowitych:

• W normalnych warunkach do celów obliczeniowych należy wykorzystywaćwyłącznie typ int.

• Do przechowywania ogromnych ilości niewielkich liczb można stosować typyshort lub char.

• Typy bez znaku należy używać wyłącznie do manipulowania bitami.• Zasadniczo typ char należy traktować jak znak a nie liczbę.• Typ long long jest niestandardowy, a operacje na nim są bardziej czaso-chłonne niż operacje na typie int.

2.3.2 Typy zmiennopozycyjne

Jak uczy matematyka, między dowolnymi (różnymi) liczbami rzeczywistymi ist-nieje nieskończenie wiele liczb rzeczywistych. Skoro jednak w komputerach możnazapisać tylko skończoną ilość różnych liczb, nie mogą one przechowywać dowol-nych liczb rzeczywistych. Dlatego komputery przechowują przybliżone wartościliczb rzeczywistych. Służą do tego specjalne typy zwane typami zmiennopozycyj-nymi .W języku C++ istnieją trzy wbudowane typy zmiennopozycyjne – double,

long double i float. Ich podstawowe właściwości (dla kompilatora CygWin gcc3.4.4) przedstawia tabela 2.3. Pierwsza kolumna przedstawia wartość największąw danym typie. Druga zawiera wartość tzw. parametru epsilon (ε), który określawzględną dokładność, z jaką w danym typie przybliżane są liczby rzeczywiste: je-żeli w którymś z typów zmiennopozycyjnych zechcemy zapisać liczbę rzeczywistąx, to popełnimy błąd2 mniejszy niż εx. Trzecia kolumna przedstawia ilość bajtów

2Przy poprawnym zaokrąglaniu błąd jest mniejszy lub równy εx/2.

Page 39: C++ w dwunastu długich krokach

Typy wbudowane 39

Nazwa Wartość maksymalna ε Bajty

double 1.79769 · 10308 2.22045 · 10−16 8long double 1.18973 · 104932 1.08420 · 10−19 12float 3.40282 · 1038 1.19209 · 10−7 4

Tabela 2.3: Wbudowane typy zmiennopozycyjne C++. Podane tu wartości sąużywane przez kompilator CygWin g++ 3.4.4.

zajmowanych przez obiekty danego typu. Parametr ten nie musi odpowiadać do-kładności danego typu. Np. w procesorach klasy Pentium (32-bitowych) obiektytypu long double zajmują 12 bajtów, ale wykorzystywane jest tylko 10 z nich, cowynika z architektury tych procesorów.

Spostrzeżenie 2.5 Jeżeli w dwóch zmiennych typu double zapiszemy z dokład-nością ε dodatnie liczby rzeczywiste, to błąd ich sumy, iloczynu i ilorazu będzierzędu ε, ale błąd różnicy może równie dobrze wynosić ε, jak i 1016 · ε .

Spostrzeżenie 2.6 Suma dwóch dodatnich liczb zmiennopozycyjnych może byćrówna jednemu ze składników (tj. x>0, y>0 i (x+y) = y).

Spostrzeżenie 2.7 Nie jest prawdą, że między dwoma różnymi liczbami zmien-nopozycyjnymi istnieje inna liczba tego samego typu.

Spostrzeżenie 2.8 Obliczanie tej samej wielkości zmiennopozycyjnej na różnesposoby daje zazwyczaj różne wyniki. Na przykład (a + b) + c rzadko kiedy matę samą wartość co a + (b + c).

Spostrzeżenie 2.9 Jeżeli pewną wielkość obliczymy na 2 różne sposoby i zapi-szemy wyniki w zmiennych zmiennopozycyjnych w1, i w2, to mogą one spełniaćdowolny z warunków w1 < w2, w1 == w2, w1 > w2.

Reguła 2.3 Wystrzegaj się stosowania operatora == do typów zmiennopozycyj-nych. Zamiast if(x == y) pisz np. if(fabs(x-y) < eps) lub podobną instrukcję.

Do obliczeń powinno się używać typu double. Typu long double można uży-wać do przechowywania pośrednich wyników obliczeń. Z kolei typ float, niegdyśpodstawowy typ zmiennopozycyjny, dziś ma zastosowanie wyłącznie w sytuacji,gdy musimy przechować ogromne ilości liczb i chcemy zaoszczędzić nieco pamięcikomputera.W typach zmiennoprzecinkowych pewne wartości mają znaczenie specjalne

i służą nie do reprezentowania liczb rzeczywistych, lecz do sygnalizacji błędów(takich jak dzielenie przez zero, logarytmowanie liczb ujemnych itp.). Do tej ka-tegorii należą INF (nieskończoność), INF (−nieskończoność) i NaN (nie-liczba, ang.Not a Number). Ich znaczenie ilustruje następujący wiersz kodu, wyświetlającykolejno nieskończoność (jako 1.INF) i NaN (jako -1.#IND).

std::cout << 1.0/0.0 << "\n" << sqrt(-1) << "\n";

1.#INF-1.#IND

Page 40: C++ w dwunastu długich krokach

40 Rozdział 2. Wyrażenia i instrukcje

Sygnały błędów propagują poprzez wyrażenia arytmetyczne aż do wartości koń-cowej. Jeżeli jeden z argumentów wyrażenia arytmetycznego ma wartość NaN,to całe wyrażenie ma także wartość NaN. Na przykład wartością wyrażenia0*sqrt(-1) + 1 jest NaN. Wartością 1.0/0.0 + 1 jest 1.#INF, ale 10/(1.0/0.0)mawartość 0. Zwróćmy uwagę, że tego typu sygnalizacji nie posiadają typy całkowite;dlatego próba obliczenia wyrażenia 1/0 zazwyczaj kończy się padem programu.

2.3.3 Typ logiczny

W języku C++ istnieje tzw. typ logiczny o nazwie bool. Obiekty tego typu mo-gą przyjmować dwie wartości: false (fałsz) i true (prawda). Obiektom tych ty-pów można przypisywać dowolne liczby (całkowite lub nawet zmiennopozycyjne).Efektem takiego przypisania jest false, jeśli przypisywana liczba ma wartość zero,natomiast w każdym innym przypadku wartością obiektu typu bool jest true. Zkolei jeżeli obiekt typu bool zastosujemy w dowolnym wyrażeniu arytmetycznym,false zostanie zamienione na 0, a true na 1.

Reguła 2.4 Dla dowolnego wyrażenia arytmetycznego wyr i zmiennej b typu boolprzypisanie b = wyr jest równoważne wyrażeniu b = (wyr != 0);

Spostrzeżenie 2.10 Zapis if(x)... oznacza if(x != 0)...

2.3.4 Zapis literałów całkowitych i zmiennopozycyjnych

Zapis liczb typu int składa się wyłącznie z cyfr, które mogą być poprzedzoneznakiem + lub −. Przykład: 12, -123290. Oczywiście w zapisie liczb nie wolnoużywać odstępów.Liczby można zapisywać w notacji dziesiętnej, ósemkowej lub szesnastkowej.

Pierwszą cyfrą liczby dziesiętnej musi być cyfra różna od zera. W przypadku liczbósemkowych pierwszą cyfrą jest zero, a drugą musi być dowolna cyfra z przedziału0. . . 7. Zapis liczb szesnastkowych rozpoczyna się cyfrą 0, po której musi wystąpićlitera x (lub X), a po niej ciąg cyfr szesnastkowych (0. . . 9, a,b,. . . ,f; A,B,. . . ,F).Liczbę 30 można więc w C++ zapisać jako 30 (notacja dziesiętna), 036 (notacjaósemkowa) lub 0x1e (notacja szesnastkowa). Notacji ósemkowej lub dziesiętnejużywa się wyłącznie do manipulowania bitami.Aby wskazać, że dana liczba całkowita jest obiektem bez znaku (np. typu

unsigned), należy zakończyć ją literą u. Tak więc w instrukcji int x = 1 zmiennej xprzypisuje się wartość typu int, a wartość typu unsigned w instrukcji int x = 1u.Zapis liczb całkowitych może też kończyć się literą s (co wskazuje na typ short)

lub l (dla typu long). Dlatego literał 0xaul to zapisana w notacji szesnastkowejliczba typu unsigned long o wartości 10.Nieco łatwiejsze reguły obowiązują w zapisie liczb zmiennopozycyjnych. Licz-

by typu double mogą się składać ze znaku (+ lub −), części całkowitej (zawszeinterpretowanej dziesiętnie), kropki dziesiętnej i części ułamkowej, np. 3.14. Do-datkowo można użyć notacji naukowej, w której po literze e lub E podaje sięwykładnik (przy „podstawie” 10). Na przykład liczbę 3.14 można zapisać np.jako 0.314e1, 3.14e0, 314e-2 lub 314E-2.Cechą odróżniającą zapis liczb zmiennopozycyjnych od całkowitych jest krop-

ka dziesiętna lub litera e.

Page 41: C++ w dwunastu długich krokach

Wyrażenia arytmetyczne, promocje i konwersje standardowe 41

Zapis liczb zmiennopozycyjnych może się kończyć literą f (na oznaczenie typufloat), lub l (typ long double). Jako przykład niech posłuży nam instrukcjalong double pi = 3.1415926535897932385L;. Gdybyśmy w zapisie literału opuściliL, kompilator potraktowałby jego typ jako double, skutkiem czego stracilibyśmyok. 4 cyfr znaczących dokładności (wartością pi byłoby 3.1415926535897931160).Osobną kwestię stanowią obiekty typu char. Zapisuje się je jako litery ujęte

w apostrofy, np. ’a’, ’8’, ’;’ itd. Pewnych wartości w typie char nie można za-pisać w postaci znaków. W tych wypadkach zapisuje się je jako ujęte w apostrofysekwencje dwóch znaków, z których pierwszym jest ukośnik (\). Do tej kategoriinależą m.in. ’\n’ (znak końca linii), ’\b’ (cofnięcie kursora o jedną pozycję w le-wo), ’\r’ (cofnięcie kursora na początek bieżącego wiersza), ’\a’ („dzwonek”),’\t’ (tabulator) oraz ’\’’ (apostrof).Oto kilka przykładów:

int n = 100u; // liczba typu unsigned;n ˆ= 0xff00; // liczba typu int w zapisie szesnastkowym;float f = 1.0e−30f; // liczba typu float

2.4 Wyrażenia arytmetyczne, promocje i konwer-sje standardowe

Wyrażenia arytmetyczne buduje się głównie z operatorów +, -, * oraz / i %(resztaz dzielenia liczb całkowitych). Obowiązuje następująca reguła:

Reguła 2.5 Jeżeli oba argumenty wbudowanego operatora arytmetycznego są te-go samego typu T, to wynik jest też typu T.

Regułę tę ilustruje następujący przykład:

Wydruk 2.6: Przykład ilustrujący regułę 2.5.

#include <iostream>int main(){double x = 1/3;std :: cout << ”x = ” << x << ”\n”;

}

x = 0

Wpowyższym programie zmiennopozycyjnemu obiektowi x przypisuje się wartośćilorazu 1/3. Jednakże zarówno 1 jak i 3 są tego samego typu (int). Dlatego,w myśl reguły 2.5, ich iloraz też musi być typu int i wynosi 0. Dlatego ostateczniex ma wartość 0 (a nie, jak można by naiwnie oczekiwać, 0.33333...). Kolejnyproblem z dzieleniem pojawia się w sytuacji, gdy licznik i mianownik są różnegoznaku. Ponieważ standard (świadomie) nie definiuje, czy w tym wypadku resztaz dzielenia jest dodatnia czy ujemna, wynik dzielenia liczb o różnych znakach jestnieokreślony (tj. może być inny w różnych komputerach).

Spostrzeżenie 2.11 Wyrażenia, w których występuje operator dzielenia, spra-wiają kłopoty nawet doświadczonym programistom. Zawsze sprawdzaj, czy nie-świadomie nie dzielisz dwóch wyrażeń całkowitych.

Page 42: C++ w dwunastu długich krokach

42 Rozdział 2. Wyrażenia i instrukcje

Nie ma żadnych przeszkód, by argumenty operatorów arytmetycznych byłyróżnych typów (wbudowanych). Przed obliczeniem wartości operacji matematycz-nej kompilator uzgadnia typy obu argumentów tak, aby były identyczne. W tymcelu zamienia się typ „mniej pojemny” na typ „bardziej pojemny’. Zakłada sięprzy tym, że typy zmiennopozycyjne są „bardziej pojemne” od całkowitych, a ty-py bez znaku są pojemniejsze od typów ze znakiem o tej samej liczbie bajtóww reprezentacji maszynowej. Proces ten zwie się konwersją (niejawną). Jeżeli obaargumenty operatora są typu „mniejszego” od int (np. char, unsigned char, booli short), to zamienia się je na int. Proces ten zwie się promocją całkowitą. Jakwidać, podczas opracowywania wyrażeń kompilator stara się nie gubić informacji.Oczywiście nie zawsze jest to możliwe – na przykład konwersja dużych liczb typuint na float wiąże się z pewną utratą dokładności.

Spostrzeżenie 2.12 Jeżeli x jest typu int, a y typu unsigned, to każde z wyrażeńx-y, x+y, x*y i x/y jest typu unsigned i nie może mieć wartości ujemnej.

Ta pozornie niewinna właściwość może mieć poważne następstwa. Ilustruje towydruk 2.7.

Wydruk 2.7: Mieszanie typów ze znakiem i bez to recepta na ból głowy.

1 #include <iostream>int main(){unsigned u duza = 4294967290u;

5 unsigned u mala = 3u;int i ujemna = −2;if (2 − u mala > u duza) // jesli 2 − 4294967290u > 4294967290ustd :: cout << ”niespodzianka\n”;

if (i ujemna > u duza) // jesli −2 > 4294967290u10 std :: cout << ”druga niespodzianka\n”;

}

niespodziankadruga niespodzianka

Autor tego programu mógł myśleć, że wartością wyrażenia 2 - u_mala jest2-3, czyli -1. Jednak w rzeczywistości typem tego wyrażenia jest unsigned, a jegowartość to 4294967295. Nieco inny problem pojawia się w wierszu 9, w którymporównujemy liczbę ze znakiem z liczbą bez znaku. W wypadku mieszanych po-równań komputer zamienia typ bez znaku na typ ze znakiem (int), a więc liczbę294967290u zinterpretuje jako -6, natomiast całe wyrażenie jako if (-2 > -6). . .

Reguła 2.6 Należy unikać mieszania typów ze znakiem i bez znaku.

Niestety, w C++ trudno jest dziś pominąć typy bez znaku, gdyż kilka niezwyklepopularnych funkcji biblioteki standardowej zwraca właśnie tego typu wartości.Co prawda kompilatory z reguły ostrzegają przed użyciem typów mieszanychw porównaniach, ale zezwalają na mieszania typów w operacjach arytmetycznych.W przypadku programu 2.7 kompilator gcc 4.4.2 ostrzeże nas przed konstrukcjąw wierszu 9, ale nie dopatrzy się niczego podejrzanego w wierszu 7.

Page 43: C++ w dwunastu długich krokach

Tworzenie obiektów stałych 43

Wydruk 2.8: Zastosowanie modyfikatora const.

1 #include <iostream>#include <string>

int main()5 {

const std:: string tajny kod = ”Gucio”;const int maks liczba prob = 5;

bool poprawny kod = false;10 for (int i = 0; i < maks liczba prob; i++)

{std :: string haslo;std :: cout << ”podaj haslo:”;std :: cin >> haslo;

15 poprawny kod = (haslo == tajny kod);if (poprawny kod)break;

}if (poprawny kod)

20 std :: cout << ”Witam w systemie!\n”;elsestd :: cout << ”Nie znasz hasla?”;

}

2.5 Tworzenie obiektów stałych

2.5.1 Modyfikator const

W programach (lub ich fragmentach) pewne wartości nigdy nie powinny uleczmianie. Na przykład znana z lekcji matematyki liczba π ma zawsze tę samąwartość 3.14 . . . Właściwość tę można zapisać w programie przy pomocy słowakluczowego const umieszczanego w definicji obiektu przed nazwą jego typu (lubpo niej). Na przykład liczbę π można zdefiniować instrukcją

const double pi = 3.1415926535897932;

lub

double const pi = 3.1415926535897932;

Obiekty tego typu nazywamy stałymi lub obiektami stałymi . Stałe typu licz-bowego lub napisowego zwane są stałymi symbolicznymi . W przypadku definicjistałych typu matematycznego, użycie stałych symbolicznych wiąże się z wygodą– łatwiej zapamiętać ich nazwy niż wartości.Przykład użycia modyfikatora const przedstawia wydruk 2.8 stanowiący mo-

dyfikację programu 2.4 ze strony 36. W programie tym zdefiniowano dwie stałe:tajny_kod oraz maks_liczba_prob o dość oczywistym znaczeniu.Jaką korzyść daje zastosowanie w tym programie obiektów stałych? Czy wiersz

10 nie mógłby brzmieć po prostu for (int i = 0; i < 5; i++)? Owszem, mógł-by. Ale kod, w którym wartości stałych wpisywane są bezpośrednio w instrukcjach

Page 44: C++ w dwunastu długich krokach

44 Rozdział 2. Wyrażenia i instrukcje

programu (w postaci literałów) byłby niezwykle trudny do pielęgnacji i rozsze-rzania. Wyobraźmy sobie, że nasz kod stanowi fragment większej biblioteki i żenależy zmienić liczbę dozwolonych prób wpisania hasła z 5 do 6. Programistamający wykonać takie zadanie musiałby przejrzeć kod całego programu w po-szukiwaniu wszystkich miejsc, w których użyto liczby 5, gdyż maksymalna liczbaprób wpisania hasła mogłaby być użyta także w innych miejscach programu. Pro-gramista musiałby więc dokładnie wczytać się w kod programu by odróżnić różnepowody użycia liczby 5. W przypadku użycia stylu programowania zastosowane-go w programie 2.8, do zmiany wartości dopuszczalnej liczby prób wpisania hasławystarczyłaby modyfikacji instrukcji w wierszu 7.Płyną stąd następujące morały:

Reguła 2.7 Jeżeli jakaś wartość w programie nie jest oczywista lub jeżeli istniejechoć śladowe prawdopodobieństwo, że jej wartość kiedyś może ulec zmianie, (np.w komputerach o większej precyzji zmiennopozycyjnej), należy zdefiniować stałąsymboliczną i posługiwać się wyłącznie tą stałą.

Reguła 2.8 Najlepszym sposobem unikania błędów jest ograniczenie liczby oka-zji do ich popełnienia. Dlatego należy ograniczać do niezbędnego minimum liczbę(i rodzaj) instrukcji, w których można użyć danego obiektu.

Spostrzeżenie 2.13 Programy, w których systematyczne używa się modyfikatoraconst są łatwiejsze w utrzymaniu i z reguły zawierają mniej błędów.

2.6 Popularne typy standardowe

Opisane w rozdziale 2.3 typy wbudowane zaliczają się do tzw. typów niskopozio-mowych, czyli typów bezpośrednio odpowiadających możliwościom mikroproceso-rów. Stąd właśnie przy posługiwaniu się nimi trzeba ciągle pamiętać, że stanowiąone tylko różne „namiastki” znanych nam ze szkoły zbiorów liczb całkowitychczy rzeczywistych. Ze względu na potrzebę zapewnienia maksymalnej szybkościprogramów, typy te nie oferują żadnej kontroli błędów. Jest to niewygodne, aleefektywne.Oprócz typów wbudowanych język C++ oferuje tzw. typy standardowe, czyli

typy (klasy) zdefiniowane w bibliotece standardowej C++. Klasy te należą dotypów wysokopoziomowych. Ich definicja jest dostosowana bardziej do potrzebprogramistów niż możliwości komputerów, zawiera też opcjonalne mechanizmywyłapywania błędów w czasie wykonywania programu.Z kilkudziesięciu typów dostępnych w bibliotece standardowej C++ poniżej

skrótowo omawiam cztery najbardziej potrzebne. Dokładniejszy opis przedsta-wionych poniżej klas oraz reszty tej biblioteki znajduje się w rozdziałach 11 i 12.

2.6.1 Strumienie

Jak już wiemy, odczyt danych z klawiatury następuje za pośrednictwem strumie-nia std::cin i operatora >>. Podobnie zapis danych na ekranie monitora następujepoprzez strumień std::cout (lub std::cerr) oraz operator <<.W bardzo podobny sposób można posługiwać się strumieniami związanymi

z plikami. Demonstruje to program 2.9, który otwiera plik dane.txt, odczytujez niego wszystkie liczby i zapisuje ich sumę w pliku suma.txt.

Page 45: C++ w dwunastu długich krokach

Popularne typy standardowe 45

Wydruk 2.9: Praca z plikami.

1 #include <fstream> // <−− biblioteka strumieni związanych z plikami

int main(){

5 std :: ifstream F (”dane.txt”); // <−− otwarcie pliku do czytaniaif (F){double suma = 0.0;do

10 {double x;F >> x; // <−− czytanie z plikuif (F) // <−− jeżeli plik jest w ”dobrym” staniesuma += x;

15 }while(F); // <−− dopóki plik jest w ”dobrym” staniestd :: ofstream G (”suma.txt”); // <−− otwarcie pliku do pisaniaG << suma << ”\n”; // <−− zapisywanie w pliku

}}

W pierwszym wierszu tego programu włącza się plik nagłówkowy fstreamzawierający wszystkie deklaracje niezbędne do posługiwania się plikami w C++.Wiersz 5 zawiera definicję strumienia F klasy std::ifstream. Nazwa tej klasypochodzi od wyrażenia input file stream, czyli „strumień wejściowy związanyz plikiem”). Obiekty tej klasy służą wyłącznie do odczytywania danych z plików– za ich pośrednictwem nie można plików modyfikować. Obiekt F inicjalizowanyjest tekstem "dane.txt". Oznacza to, że strumień F ma być związany z plikiemdane.txt. Innymi słowy, obiekt F ma reprezentować dane odczytywane z plikudane.txt.Instrukcja if w wierszu 6 sprawdza, czy próba otwarcia pliku w wierszu 5

rzeczywiście się powiodła. Zasada jest następująca: dowolny strumień (np. F lubstd::cout) może być przypisany obiektowi typu bool (wymaganemu m.in. jakoargument instrukcji if). Wartością tego obiektu będzie true, jeżeli strumień jestw stanie poprawnym; w przeciwnym wypadku nastąpił jakiś błąd, strumień został„zepsuty” i przypisanie go zmiennej typu bool umieści w niej wartość false.

Spostrzeżenie 2.14 Instrukcja if (F) jest najprostszą metodą sprawdzania sta-nu strumienia F.

Wwierszu 12 odczytujemy z pliku F wartość liczby rzeczywistej i przypisujemyją zmiennej zmiennopozycyjnej x.

Spostrzeżenie 2.15 Odczytywanie danych z plików wykonuje się tak samo, jakodczytywanie danych z klawiatury – w każdym wypadku podstawową metodą jestużycie obiektu strumieniowgo i operatora >>.

W wierszu 13 ponownie sprawdzany jest stan strumienia. Test ten należywykonywać po każdej operacji na strumieniu choćby po to, by sprawdzić, czy nie

Page 46: C++ w dwunastu długich krokach

46 Rozdział 2. Wyrażenia i instrukcje

doszliśmy do końca pliku. W wierszu 15 definiuje się poprawność stanu strumieniajako warunek kontynuowania wczytywania danych.Po dojściu programu do wiersza 16 strumień F jest w stanie niepoprawnym,

uznajemy więc, że wszystkie dane zostały wczytane. Następnie otwieramy plikG, w którym zapiszemy sumę wczytanych liczb. Robimy to niemal dokładnie taksamo, jak w wypadku pliku do odczytu – jedyną różnicą jest typ definiowanegoobiektu: std::ofstream (ang. output file stream).W wierszu 17 zapisujemy dane w pliku G.

Spostrzeżenie 2.16 Zapisywanie danych w pliku wykonuje się tak samo, jakwyświetlanie ich na monitorze – podstawową metodą jest użycie operatora <<.

Strumienie w języku C++ to temat-rzeka, któremu poświęcono osobne, wielu-setstronicowe podręczniki. Niemniej jednak podane dotąd informacje (wraz z opi-saną na następnej stronie funkcją getline) wystarczają w 99% praktycznych sy-tuacji.

2.6.2 Napisy

Z napisami, czyli obiektami klasy std::string, mieliśmy już do czynienia wielo-krotnie (m.in. w programie 1.4 na str. 22). W zasadzie potrafimy już je wczytywaćz klawiatury lub wyświetlać na ekranie.Napisy są typami wysokopoziomowymi i można przy ich pomocy wykony-

wać wiele skomplikowanych operacji. Kilka najbardziej użytecznych właściwościnapisów demonstruje program 2.10.

Wydruk 2.10: Podstawowe operacje na obiektach klasy std :: string .

6 std :: string s1(”Baba”);std :: string s2(”Jaga”);std :: string s ;s = s1 + ” ” + s2; // dodawanie napisów

10 std :: cout << s << ”\n”;std :: cout << s.size() << ”\n”; // z ilu znaków składa się napis?std :: cout << s[1] << ”\n”; // jaki jest drugi znak napisu?s [4] = ’−’; // zmiana piątego znakustd :: cout << s << ”\nWpisz tekst skladajacy sie z kilku slow:\n”;

15 getline (std :: cin , s ); // wczytywanie napisu wielowyrazowegos += ”...”;std :: cout << s << ”\n”;std :: cout << s.substr(0,2) << ”\n”; // pobranie fragmentu napisu

Baba Jaga9aBaba-JagaWpisz tekst skladajacy sie z kilku slow:Jan KowalskiJan Kowalski...Ja

Page 47: C++ w dwunastu długich krokach

Popularne typy standardowe 47

Wiersz 9 ilustruje dodawanie napisów. W sumach można mieszać napisy typustd::string i literały napisowe, o ile obiekt klasy std::string będzie pierwszymlub drugim składnikiem sumy.Wiersz 11 zawiera wywołanie funkcji size, która zwraca ilość znaków wcho-

dzących w skład napisu. Zwróćmy uwagę na nietypowy sposób wywołania tejfunkcji: jej nazwa znajduje się po nazwie obiektu, na rzecz którego jest wywoły-wana; dodatkowo od nazwy obiektu funkcja oddzielona jest kropką. Puste nawia-sy po nazwie funkcji informują, że poza obiektem s funkcja size nie przyjmujeżadnych innych argumentów. Funkcje „pisane po kropce” są charakterystycznedla języków obiektowych i zwane są funkcajmi składowymi lub metodami . Pozasposobem wywołania niczym nie różnią się one od zwykłych funkcji. Funkcjamiskładowymi zajmiemy się bliżej w rozdziale 5 – na razie wystarczy nam wiedzao ich istnieniu i sposobach używania.Wiersze 12 i 13 zawierają kolejny nowy element języka: operator [], zwany

także operatorem indeksowania. Dzięki niemu napis s można w pewnych sytu-acjach traktować jak ciąg znaków (obiektów typu char). Operator [] umożliwiadostęp do kolejnych elementów tego ciągu. Wyrażenie s[n] oznacza n + 1-wszyelement tego ciągu. Innymi słowy s[0] to pierwszy znak napisu s, s[1] to znakdrugi itd. Ostatnim znakiem niepustego napisu s jest więc s[s.size()-1]. Wiersz12 demonstruje odczytywanie n-tego znaku napisu, a wiersz 13 – jego modyfikację.W definicji operacji strumieniowych przyjęto zasadę, że podczas wczytywa-

nia danych operator >> pomija tzw. białe znaki, czyli spacje, tabulatory, znakikońca wiersza itp. Aby do obiektu typu std::string wczytać napis składającysię z wielu wyrazów, należy posłużyć się inną metodą. Jedna z nich polega naużyciu funkcji getline (wiersz 15), która wczytuje wszystkie znaki aż do końcabieżącego wiersza.

Reguła 2.9 Operator >> pomija w strumieniach wszelkie tzw. białe znaki, czy-li spacje, tabulatory, znaki końca wiersza itp. Aby pobrać ze strumienia napiszawierający białe znaki, można użyć funkcji getline.

Wiersz 16 demonstruje zastosowanie operatora += do dopisywania tekstu na końcunapisu. Z kolei wiersz 17 demonstruje funkcję składową substr klasy std::string.Wyrażenie s.substr(n,k) to napis składający się z k kolejnych znaków napisu spocząwszy od znaku s[n].

2.6.3 Wektory

Wektory są niewątpliwie najczęściej stosowanym typem standardowym. Prostyprogram ilustrujący ich użycie przedstawia program 2.11, który wczytuje ze stan-dardowego wejścia wiersz i wyświetla na ekranie informacje o tym, jakie wystąpiływ nim znaki i ile razy.W wierszu 4 włączany jest do programu plik nagłówkowy vector zawierający

wszystkie deklaracje niezbędne do posługiwania się wektorami. Z kolei w wierszu8 znajduje się definicja wektora v. Ma ona bardzo charakterystyczną postać, z któ-rą spotykamy się po raz pierwszy. Przy definiowaniu wektorów obowiązują dwiezasady. Po pierwsze, definiując wektor, koniecznie należy w nawiasach ostrokąt-nych podać typ (klasę) elementów wektora. Typy, w których definicji wymaga siępodania nazwy dodatkowego typu, zwie się typami parametrycznymi . W naszymprzypadku typ int jest parametrem typu std::vector<int>. Jako typ elementów

Page 48: C++ w dwunastu długich krokach

48 Rozdział 2. Wyrażenia i instrukcje

można podać dosłownie dowolny typ zdefiniowany w programie (nawet inny wek-tor!). Po drugie, po nazwie definiowanego wektora można w nawiasach okrągłychumieścić parametr lub parametry służące do jego inicjalizacji. W naszym przy-kładzie do inicjalizacji wektora v używa się liczby 100. Oznacza to, że tworzonywektor ma mieć dokładnie 100 elementów. Uwaga: wektorów (i innych typów pa-rametrycznych) nie można inicjalizować w „zwykły” sposób, tj. poprzez użycieoperatora =. Zapis std::vector<int> v = 100 jest błędny.

Wydruk 2.11: Zastosowanie klasy std :: vector.

1 #include <iostream>#include <string>#include <cctype> // <−− deklaracja funkcji isgraph#include <vector> // <−− deklaracja typu std::vector

5

int main(){std :: vector<int> v(256); // <−− wektor 256 liczb typu intstd :: string s ;

10 getline (std :: cin , s ); // <−− wczytanie wiersza z std::cin do sfor (int i = 0; i < s. size (); i++){char c = s[i ]; // <−− (i+1)−y znak w napisiev[c]++; // <−− uaktualnienie licznika znaków

15 }for (int i = 0; i < 256; i++){if (v[ i ] != 0 and isgraph(i)){

20 char c = i;std :: cout << c << ”: ” << v[i] << ”\t”;

}}

}

Ala ma kota. To kot Ali..: 2 A: 2 T: 1 a: 3 i: 1 k: 2 l: 2 m: 1 o: 3 t: 2

Domyślnie wszystkie elementy nowotworzonego wektora wypełniane są zera-mi. Wynika stąd, że wiersz 8 zawiera definicję wektora v składającego się ze 100obiektów typu int, każda o wartości 0.Dostęp do elementów wektora zapewnia znany nam już z rozdziału 2.6.2 ope-

rator []. W wektorach zastosowano tę samą konwencję co w napisach: pierwszyelement wektora ma indeks 0, drugi ma indeks 1, n-ty ma indeks n+ 1, a ostat-nim elementem niepustego wektora v jest v[v.size()-1]. Operator indeksowaniaużywany jest w wierszach 14 (zliczanie wystąpień poszczególnych liter) i 21 (od-czytywanie wartości elementu wektora).

Reguła 2.10 Dowolny wektor v posiada elementy o indeksach od 0 do v.size()-1.Nigdy, przenigdy nie wolno indeksować wektora liczbą spoza tego zakresu.

Wektory mogą być puste – w tym wypadku metoda size zwraca 0. Wektorówtakich w ogóle nie można indeksować operatorem [].

Page 49: C++ w dwunastu długich krokach

Popularne typy standardowe 49

Wyjaśnienia wymaga jeszcze wiersz 18, a szczególnie użyta w nim funkcjaisgraph. Funkcja ta służy do sprawdzenia, czy jej argument jest znakiem, którymożna bezpiecznie wyświetlić na ekranie (istnieją bowiem znaki, które są przezkonsolę interpretowane jako znaki sterujące; przesłanie takiego znaku na konsolęmoże całkowicie zmienić tryb jej pracy).Reasumując, w wierszu 10 program wczytuje z klawiatury linijkę tekstu, po

czym w pierwszej pętli for rozbija tekst na poszczególne litery i zlicza je w wek-torze v, a w drugiej pętli for wyświetla zawartość tych niezerowych elementówwektora v, którym odpowiadają „normalne”, drukowalne znaki.

2.6.4 Słowniki

Słowniki stanowią swoiste rozszerzenie wektorów i w związku z tym są typemdanych o wszechstronnych zastosowaniach. Nie mam najmniejszych wątpliwości,że powinny znajdować się w podręcznym „arsenale” każdego programisty C++.Niestety, moje doświadczenie wykładowcy C++ prowadzi do wniosku, że studen-ci niezwykle rzadko korzystają ze słowników. Prawdopodobnie wynika to z faktu,że aby móc w pełni wykorzystać możliwości tej klasy danych, należy mieć zasobą praktycznie cały kurs C++, a już na pewno dobrze opanować tajniki stan-dardowej biblioteki STL. Żeby odwrócić tę niekorzystną tendencję, poniżej, jakoswoisty aperitif, przedstawiam nieco uproszczony3 program zliczający ilość wy-stąpień w kodzie programu słowa kluczowego int.

Wydruk 2.12: Prosty przykład zastosowania słowników (std ::map).

1 #include <iostream>#include <map > // <−− deklaracja funkcji słownikowych

int main()5 {

std ::map<std::string, unsigned int> slownik; // definicja słownikafor ( ; ; ) // nieskończona pętla{std :: string s ; // definicja pustego napisu s

10 std :: cin >> s; // próba wczytania kolejnego słowaif (std :: cin) // jeżeli udało się wczytać kolejne słowoslownik[s]++; // aktualizacja licznika wystąpień słowa selse // w przeciwnym wypadkubreak; // koniec pętli

15 }std :: cout << ”slowo ’int’ wystapilo ” << slownik[”int”] << ” razy\n”;

}

Ponieważ program wczytuje dane ze standardowego wejścia, uruchamiającgo, należy przekierować standardowy strumień wejścia z klawiatury do pliku, np.uruchamiając program komendą program.exe < plik.Na pełne omówienie tego programu jest nieco za wcześnie, tym niemniej warto

zwrócić uwagę na kilka użytych w nim właściwości słowników.

3Uproszczenie polega na tym, iż program ten definiuje słowa nieco inaczej niż jesteśmy dotego przyzwyczajeni.

Page 50: C++ w dwunastu długich krokach

50 Rozdział 2. Wyrażenia i instrukcje

• Słowniki zdefiniowane są w pliku nagłówkowym map (wiersz 2).

• Słowniki są typami sparametryzowanymi dwoma innymi typami (wiersz 6).Pierwszy z nich zwany jest typem klucza, drugi – typem wartości.

• Dostęp do elementów słownika zapewnia operator [] (wiersze 12 i 16),przy czym typ argumentu tego operatora jest typem klucza słownika (tu:std::string), a typ wartość zwracanej przez operator [] to drugi typ pa-rametryzujący słownik (tu: unsigned int).

2.7 Obiekty lokalne i globalne. Zasięg. Przesła-nianie

Programy pisane w C++ nierzadko pisane są przez zespoły wielu programistów,którzy nie muszą ze sobą ściśle współpracować. Prowadzi to do wielu problemów.Jeden z nich wiąże się z nazwami identyfikatorów. Czy przed użyciem jakiegoś no-wego identyfikatora na oznaczenie obiektu lub funkcji, np. size, programista musisprawdzać, czy nazwy tej nie użył już wcześniej on sam lub któryś z jego czter-dziestu kolegów? Oczywiście odpowiedź brzmi – tylko w absolutnie wyjątkowych,dobrze zdefiniowanych sytuacjach! W C++ istnieją rozwinięte mechanizmy kon-troli zakresu widzialności identyfikatorów, które pozwalają praktycznie zupełniewyeliminować konflikty nazw.Podstawowe zasady unikania konfliktu nazw w C++ ilustruje program 2.13.

Wydruk 2.13: Zakresy lokalne i globalne; przesłanianie identyfikatorów.

1 #include <iostream>

int x = 0; // <−− zmienna w zakresie globalnym

5 int main(){ // <−− tu się zaczyna zakres lokalnystd :: cout << ”x = ” << x << ”\n”;int x = 7; // <−− przesłania definicję z wiersza 3std :: cout << ”x = ” << x << ”\n”;

10 for (int i = 0; i < 2; i++){double x = 11.11; // <−− przesłania definicję z wiersza 8std :: cout << ”x = ” << x << ”\n”;

} // <−− odsłania definicję z wiersza 315 std :: cout << ”lokalne x = ” << x << ”\n”;

std :: cout << ”globalne x = ” << ::x << ”\n”;} // <−− tu się kończy zakres lokalny

x = 0x = 7x = 11.11x = 11.11lokalne x = 7globalne x = 0

Page 51: C++ w dwunastu długich krokach

Operatory 51

W programie tym, nie licząc obiektu std::cout, występują wyłącznie zmienneo nazwie x. Są to oczywiście różne zmienne – każda z nich przechowuje innąwartość, o czym świadczy wynik działania programu. Pierwsza zmienna x zde-finiowana jest w wierszu 3. Miejsce jej definicji jest dość niezwykłe – jest topierwszy przypadek użycia w tym podręczniku instrukcji poza funkcją main, wtzw. zakresie globalnym.W C++ starannie rozróżnia się dwa obszary kodu programu. Pierwszy z nich

to obszar objęty (dowolną) parą nawiasów klamrowych zwany zakresem lokalnym.Drugi to obszar pozostający poza jakąkolwiek parą klamer, zwany zakresem glo-balnym. Obowiązuje następująca zasada: obiekty zdefiniowane w zakresie global-nym (czyli tzw. obiekty globalne) mogą być wykorzystywane w całym programie,choćby składał się z tysięcy plików i milionów wierszy kodu. W przypadku du-żych programów właściwość ta jest potencjalnie bardzo niebezpieczna i niezwykleutrudnia pielęgnację kodu, dlatego zmiennych globalnych używa się w wyjątko-wych sytuacjach. Jak dotąd poznaliśmy tylko trzy obiekty globalne: std::cin,std::cout i std::cerr; wprowadzania innych obiektów globalnych nie planuję.

Reguła 2.11 Należy zdecydowanie unikać stosowania obiektów globalnych.

Zakresy lokalne definiowane są przez pary odpowiadających sobie klamer.W naszym przykładowym programie mamy dwa zakresy lokalne. Pierwszy obej-muje wiersze 6-17, drugi składa się z wierszy 11-14 i zawiera się w pierwszym za-kresie. Obiekty lokalne istnieją wyłącznie w zakresie, w którym zostały utworzone(stąd nazwa ‘lokalne’). Wraz z dojściem sterowania programu do klamry zamy-kającej zakres niszczone są wszystkie zdefiniowane w nim obiekty. Np. zmiennax zdefiniowana w wierszu 12 „ginie” po dojściu sterowania programu do klamryw wierszu 14, jest więc niewidoczna w wierszu 15. W każdym zakresie można defi-niować zmienne o dowolnych nazwach, nie przejmując się zmiennymi w zakresachnadrzędnych (oczywiście w każdym zakresie można definiować tylko jeden obiekto danej nazwie). Jeżeli przypadkiem w zakresie lokalnym użyje się nazwy wyko-rzystanej już w zakresie nadrzędnym, kompilator uzna za obowiązującą definicjęz zakresu wewnętrznego. Zjawisko to zwane jest przesłanianiem nazw ; wyjaśniaono, dlaczego w wierszu 13 program używa zmiennej x zdefiniowanej w wierszu12, a nie zmiennych zdefiniowanych w wierszu 3 lub 8.

Reguła 2.12 Należy unikać przesłaniania nazw zmiennych.

Każdy obiekt globalny musi być dostępny w całym programie. Aby uzyskaćdostęp do przesłoniętego obiektu globalnego, należy jego nazwę poprzedzić ope-ratorem ::, co ilustruje wiersz 16. Nawet jeżeli zmienna globalna nie jest przesło-nięta, warto jej nazwę poprzedzać tym operatorem w celu zwiększenia czytelnościkodu.

2.8 Operatory

Jak już wiemy, w języku C++ instrukcje tworzy się z wyrażeń, a podstawowymbudulcem wyrażeń są identyfikatory obiektów i funkcji oraz operatory. Pełnywykaz operatorów C++ przedstawia tabela 2.4.

Page 52: C++ w dwunastu długich krokach

52 Rozdział 2. Wyrażenia i instrukcje

Tabela 2.4: Operatory w C++.priorytet operator nazwa/opis1 :: zasięg2 . wybór składowej

-> wybór składowej[] indeksowanie() wywołanie funkcji++ zwiększ o 1 (n++)-- zmniejsz o 1 (n--)typeid identyfikacja typudynamic_cast<...>(...) dynamiczna konwersja typustatic_cast<...>(...) statyczne konwersja typureinterpret_cast<...>(...) niesprawdzana konwersja typuconst_cast<...>(...) konwersja z lub na const

3 sizeof rozmiar obiektu lub wyrażenia++ zwiększ o 1 (++n)-- zmniejsz o 1 (--n)~ negacja bitowa! negacja logiczna+ plus jednoargumentowy- minus jednoargumentowy& pobranie adresu* wyłuskanie wartościnew przydział pamięcidelete zwolnienie obiektudelete [] zwolnienie tablicy(typ)wyrażenie konwersja typu w stylu C

4 .* wybór składowej->* wybór składowej

5 * mnożenie/ dzielenie% obliczanie reszty z dzielenia

6 +, - dodawanie, odejmowanie7 << bitowe przesunięcie w lewo

>> bitowe przesunięcie w prawo8 < mniejszy

<= mniejszy lub równy> większy>= większy lub równy

9 == równy!= nierówny

10 & iloczyn bitowy11 ^ bitowa różnica symetryczna12 | suma bitowa13 && lub and koniunkcja wyrażeń logicznych14 || lub or suma wyrażeń logicznych15 ? : wyrażenie warunkowe16 = przypisanie wartości

*=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= wykonaj operację i przypisz17 throw zgłoszenie wyjątku18 , operator przecinkowy

Page 53: C++ w dwunastu długich krokach

Operatory 53

W praktyce większość wyrażeń zawiera kilka operatorów. Jeżeli w wyrażeniunie ma nawiasów, kompilator musi w sposób deterministyczny określić porzą-dek wartościowania poszczególnych operatorów. Służą do tego reguły związanez priorytetem i łącznością operatorów.

2.8.1 Priorytet operatorów

Zgodnie z tabelą 2.4, operatory podzielono na 18 grup różniących się priorytetem.Jeżeli w jakiś wyrażeniu zastosuje się operatory o różnym priorytecie, zawszenajpierw zostanie wyznaczona wartość tego z nich, który ma najwyższy priorytet.Tak więc w wyrażeniu 2+2*2 najpierw zostanie wykonane mnożenie (priorytet 5),a dopiero później dodawanie (priorytet 6).

2.8.2 Łączność operatorów

Jeżeli w wyrażeniu występuje kilka operatorów o tym samym priorytecie, o ichkolejności decyduje tzw. łączność. Operatory mogą być łączne albo prawostron-nie, albo lewostronnie. Łączność lewostronna oznacza wartościowanie operatorówod lewej do prawej. Łączność prawostronna oznacza wartościowanie od prawej dolewej.

Reguła 2.13 Prawostronnie łączne są operatory jednoargumentowe oraz opera-tory przypisania. Pozostałe operatory są łączne lewostronnie.

Zgodnie z regułą 2.13, w wyrażeniu 1 + 1e-100 - 1 najpierw zostanie wykonanedodawanie (łączność lewostronna), w związku z czym wartością całego wyraże-nia będzie 0.0. Analogicznie w wyrażeniu x = y = 0 najpierw zostanie wykonaneprzypisanie y = 0 (łączność prawostronna).

2.8.3 Wartość operatorów

Niemal wszystkie operatory mają wartość (z tego powodu można je traktowaćjak funkcje o specjalnym zapisie). W każdym miejscu programu, w którym możepojawić się wyrażenie, może też pojawić się operator. Dlatego z punktu widzeniaskładni języka poprawne są instrukcje if (a=0) czy if (a == 3,14), choć w każ-dym z tych przypadków kompilator wygeneruje ostrzeżenie4. Wartości poszcze-gólnych operatorów zostaną omówione poniżej, dla każdego operatora osobno.

2.8.4 Opis wybranych operatorów

Operatory ++ i --

Jak już wiemy, operator ++ służy do powiększania wartości zmiennej o jeden.Operator ten występuje w dwóch wcieleniach: jako operator przedrostkowy (pi-sany przed zmienną, np. ++x) i przyrostkowy pisany za zmienną, np. x++. Obiewersje tego operatora różnią się zwracaną przezeń wartością.

Reguła 2.14 Wartością wyrażenia x++ jest stara wartość x (przed zwiększeniemo 1), a wartością wyrażenia ++x jest nowa wartość x (po zwiększeniu o 1).

4Pierwszy warunek jest zawsze fałszywy, drugi – zawsze prawdziwy.

Page 54: C++ w dwunastu długich krokach

54 Rozdział 2. Wyrażenia i instrukcje

Dlatego po wykonaniu ciągu instrukcji

int x = 0;int y = ++x;int xx = 0;int yy = x++;

zmienne x i xx będą miały wartość 1, wartością y będzie 1, a wartością yy będzie 0.

Operator sizeof

Operator sizeof zwraca liczbę bajtów zajmowanych przez zmienne danego typu.Typ można podac bezpośrednio jako argument lub pośrednio poprzez wyrażenie.Z definicji sizeof(char) ma wartość 1. Przykłady: sizeof(int), sizeof(x + 2.0).

Operator konwersji statycznej

Operator static_cast służy do zamiany typu (tzw. rzutowania) obiektu lub wy-rażenia. Pożądany typ podajemy w nawiasach ostrokątnych, a konwertowane wy-rażenie w nawiasach okrągłych. Typowy przykład to instrukcja

double z = static cast<double>(m)/n;

gdzie m i n są typu int. Bez zastosowania static_cast operator dzielenia ob-ciąłby część ułamkową wyniku. Zamiast static_cast<TYP>(WYRAŻENIE) można teżstosować zapis (TYP)(WYRAŻENIE) lub TYP(WYRAŻENIE). Powyższą instrukcję moż-na by więc zapisać jako double z = double(m)/n lub double z = (double)m / n.W C/C++ rzutowanie typów zmiennoprzecinkowych na całkowite odbywa się po-przez obcięcie części ułamkowej. Wartością wyrażenia int(-2.5) jest więc -2.

Operatory arytmetyczne

Operatory arytmetyczne (priorytet 5 i 6 w tabeli 2.4) zostały opisane w rozdzia-łach 1.9 (str. 25) i 2.4 (str. 41). Do omówienia pozostał operator %, który obliczaresztę z dzielenia. Na przykład wartością 8 % 3 jest 2, gdyż resztą z dzielenia 8przez 3 jest właśnie 2.

Operatory relacyjne

Z operatorami relacyjnymi zetknęliśmy się już w rozdziale 2.1.1 (str. 31). Należypamiętać, że ich wartością może być wyłącznie false lub true

Operator negacji logicznej

Operator ! zamienia true na false i vice versa.

Operatory bitowe

Każdą liczbę typu całkowitego (np. int lub unsigned) można traktować jak liczbęw układzie dwójkowym, tj. składającą się z jedynek (= „prawda”)lub zer (=„fałsz”). Operator & służy do obliczania iloczynu dwóch liczb bit po bicie. Naprzykład 7&3 równa się 3. Analogicznie operator | oblicza alternatywę dwóch liczbbit po bicie, np. 7|3 równa się 7. Z kolei operator ^ wyznacza różnicę symetryczną

Page 55: C++ w dwunastu długich krokach

Operatory 55

(7^3 ma wartość 4). Jednoargumentowy operator ~ zamienia wszystkie zera najedynki, a jedynki na zera. Z kolei operator << przesuwa wszystkie bity w lewo,a operator >> w prawo. Na przykład 3<<2 ma wartość 12 (przesunięcie o dwiepozycje w lewo).W praktyce operator & służy do testowania wartości lub zerowania bitów,

operator | do ich ustawiania na 1, a operator ^ do ich „przewracania” z 0 na 1lub z 1 na 0. Z kolei operatory << i >> ułatwiają konstruowanie masek bitowych.

Operatory alternatywy i koniunkcji logicznej

Jak już wiemy, operator && można zapisać w postaci słownej (and), a operatororor w postaci or. Operatory te mają pewną niezwykle ważną właściwość: wyra-żenia opracowywane są w nich w kierunku od lewej do prawej, przy czym jeżeli wjakimś punkcie obliczeń można przewidzieć wartość wyrażenia, to nie wartościujesię jego pozostałych argumentów. Własność tę można zilustrować na przykładzieinstrukcji if (n!=0 and m/n > 10) n++. Jeżeli pierwszy człon koniunkcji (n != 0)jest fałszywy, całe wyrażenie na pewno też jest fałszywe, dlatego sprawdzanie dru-giego warunku (m/n > 10) zostanie całkowicie pominięte. Dzięki temu wyrażenieif (n != 0 and m/n > 10) jest bezpieczne nawet dla n == 0.Analogicznie w wyrażeniu if(f(x) or g(y))... funkcja g będzie wywołana

tylko wtedy, gdy f(x) zwróci false (lub 0).

Wyrażenie warunkowe

Operator ? : jest operatorem trójargumentowym i stanowi swoiste uogólnienieinstrukcji warunkowej if. Składnia tego operatora wygląda następująco:

WARUNEK ? WARTOŚĆ TRUE : WARTOŚĆ FALSE

Przed znakiem zapytania zapisuje się dowolne wyrażenie, które można poddaćkonwersji na typ bool. Jeżeli WARUNEK jest spełniony, wartością operatora jestWARTOŚĆ_TRUE; w przeciwnym wypadku jego wartością jest WARTOŚĆ_FALSE.

Reguła 2.15 Jeżeli to tylko możliwe, zamiast nieczytelnego operatora ? : należyposługiwać się instrukcją if.

W pewnych wypadkach wyrażenia warunkowego nie można jednak zastąpić in-strukcją warunkową. Należą do nich inicjalizacje, np. instrukcja

const int rozmiar_tablicy = (wymiar == 2 ) ? 1000 : 100;

definiuje obiekt rozmiar_tablicy klasy const int i nadaje mu wartość początkową1000 lub 100 zależnie od tego, czy wartość zmiennej wymiar równa jest 2.

Operatory przypisania

W C++ istnieje kilkanaście operatorów przypisania. Najpopularniejszy jest ope-rator „zwyczajnego przypisania”, czyli =. Jak wiemy, instrukcja x = y; nadajezmiennej x wartość zmiennej y. Zgodnie z tabelą 2.4, do dyspozycji mamy jesz-cze 10 innych operatorów przypisania. Stanowią one skrócony zapis wykonaniapewnej operacji (arytmetycznej) i przypisania. Na przykład wyrażenie x += zrównoważne jest wyrażeniu x = x + z. Analogicznie x >>= z to x = x >> z itd.

Page 56: C++ w dwunastu długich krokach

56 Rozdział 2. Wyrażenia i instrukcje

Spostrzeżenie 2.17 Złożone operatory przypisania, np. +=, zwiększają czytel-ność programu.

Wartością każdego operatora przypisania jest wartość jego lewego argumen-tu po przypisaniu mu argumentu prawego. Dlatego x = y += 2 czy if (x=1) sąwyrażeniami (formalnie) poprawnymi.

Operator przecinkowy

Operator przecinkowy to koncepcja z natury chora; nadużywana prowadzi do nie-obliczalnych konsekwencji. Trzeba go jednak omówić, gdyż dość często używanyjest nieświadomie. Operator ten służy do ustalania kolejności obliczeń. Wyrażenief(x), g(x) oznacza: „najpierw oblicz f(x), potem g(x), a jako wartość wyrażeniaprzyjmij wartość wyrażenia po ostatnim przecinku (czyli g(x)). Oto dwa popu-larne przykłady błędnego użycia tego operatora:

pi = 3,14; // pi będzie miało wartość 3!v[i,j] = 0; // równoważne: v[j] = 0;

Jedyny znane mi miejsce, w którym operator przecinkowy ma rozsądne zastoso-wanie, to skomplikowana preambuła pętli for, np.

for (i = 0, x = 10.0; x >= 0 && i < 100; i++, x -= f(x))

W powyższej pętli dzięki operatorowi przecinkowemu udało się umieścić inicja-lizację 2 zmiennych w części inicjalizacyjnej pętli; podobnie w kroku kończącympętli zmieściły się dwie instrukcje modyfikujące wartości zmiennych sterujących.

Reguła 2.16 Strzeż się operatora przecinkowego.

2.8.5 Operatorowe patologie

Niektóre wyrażenia operatorowe mają wartość nieokreśloną w definicji języka,mimo że z formalnego punktu widzenia są zupełnie poprawne. Rozpatrzmy takąoto „niewinną” instrukcję:

int n = f(j) + g(j);

Standard nie definiuje, czy podczas wyznaczania sumy wyrażeń f(j) i g(j) naj-pierw zostanie wywołana funkcja f czy g, co może mieć wpływ na wartość przy-pisywaną zmiennej n. Z tego samego względu wartość wyrażenia x++ + ++x jesttakże nieokreślona. Jedynymi operatorami, dla których zdefiniowano kolejnośćwartościowania argumentów są operatory &&, || oraz operator przecinkowy.Inny problem pojawia się w dzieleniu liczb o różnych znakach. Otóż stan-

dard języka nie gwarantuje, że dla liczb całkowitych m i n o przeciwnych znakachwyrażenia m/n i m % n będą miały w każdej implementacji C++ tę samą wartość.Analogicznie nie zdefiniowano wartości operatorów << i >>, gdy jeden z ich

argumentów jest ujemny.

2.9 Wyrażenia i instrukcje

Ten rozdział zawiera tylko jedno stwierdzenie: dowolną instrukcję prostą two-rzy wyrażenie zakończone średnikiem. Oznacza to m.in., że 7; jest (formalnie)poprawną instrukcją C++. Cóż, takie są reguły tej gry!

Page 57: C++ w dwunastu długich krokach

Q & A 57

2.10 Q & A

Słowo kluczowe while występuje zarówno w pętli while jak i do...while.Czy nie prowadzi to do zbytniego zamieszania?Owszem, prowadzi. Aby programista mógł łatwo stwierdzić, w jakim znaczeniuużyto słowo while, stosuje się następującą konwencję w zapisie programów. Je-żeli słowo while rozpoczyna pętlę while, musi być pierwszym słowem w danymwierszu. Jeżeli słowo to kończy pętlę do, powinno następować bezpośrednio poklamrze zamykającej instrukcję blokową – przykład takiego zapisu znajduje sięw piętnastym wierszu programu 2.9 (str. 45).Dlaczego w definicji języka nie zdefiniowano zachowania się operato-rów /, %, << i >> dla ujemnych argumentów?Powodem jest troska o wydajność. Gdyby wprowadzić takie definicje do języka,na niektórych platformach każdemu wywołaniu powyższych operatorów musiało-by towarzyszyć sprawdzenie znaku jego argumentów, które w 99,9% wypadkówbyłoby zbędne, bo w praktyce ludzie i tak używaliby niemal wyłącznie argumen-tów dodatnich, a więc niewymagających takiego sprawdzenia. Poza tym C++posiada mechanizmy tworzenia własnych klas danych; można więc samemu zbu-dować klasę liczbową, w której omawiane tu operatory będą miały ściśle określonewartości dla każdej pary argumentów.Czy muszę uczyć się całej tabeli operatorów wraz z ich priorytetami?Oczywiście, że nie! Tabela 2.4 ma charakter informacyjny, a jej głównym celemjest uczulenie Czytelników na kwestię priorytetów. W praktyce w razie jakichkol-wiek wątpliwości co do kolejności obliczeń problem rozwiązuje się poprzez użycienawiasów. Jednak używanie wielu nawiasów szkodzi czytelności wyrażeń. Priory-tety wprowadzono m.in. po to, by nawiasy były potrzebne jak najrzadziej.Czy zamiast funkcji getline do wczytywania wierszy nie można posłu-żyć się funkcją get?Owszem, ale posługiwanie się funkcją get jest nieco bardziej skomplikowane, gdyżw przeciwieństwie do getline nie może ona jako argumentu przyjąć obiektu typustd::string; poza tym get jest funkcją składową, a tych wolę nie wprowadzaćzbyt wielu na tak wczesnym etapie nauki. Dla niecierpliwych podaję przykładzastosowania funkcji składowej get do kopiowania strumieni:

void kopiuj(std ::ostream & wy, std::istream & we){

char c;while(we.get(c))wy.put(c);

}

2.11 Quiz

1. Jaka jest podstawowa różnica między liczbami całkowitymi a obiektamitypu int?

2. Jaka jest podstawowa różnica między liczbami rzeczywistymi a obiektamitypu double?

Page 58: C++ w dwunastu długich krokach

58 Rozdział 2. Wyrażenia i instrukcje

3. Podaj przykład twierdzenia matematycznego, które jest słuszne dla liczbrzeczywistych, a nie jest słuszne dla obiektów typu double.

4. W wielu firmach sofwareowych nie wolno używać liczb zmiennopozycyjnychbez zgody kierownika projektu. Co może być tego powodem?

5. Dlaczego w dużych programach należy ograniczać liczbę obiektów global-nych?

6. Dlaczego warto używać modyfikatora const?

7. Jak można sprawdzić długość napisu?

8. Jaka jest różnica między instrukcjami while i do?

9. Do czego służy preambuła pętli for?

10. Które operatory są łączne prawostronnie, a które lewostronnie?

11. Który operator ma najmniejszy, a który największy priorytet?

2.12 Problemy

1. Oblicz (najlepiej bez komputera) wartości następujących wyrażeń:0xa - 012 13 % 3 3,14 + 1 34678 ^ 346781 << 4 0xf & 0x8 4 > 3 > 2 123456 + ~123456

1 + 1e-20 - 1 3 == 3 == 3 1/2 8 >> 10xff ^ 0xf0 ~(-1) 0 - 1u > 0 -1 < 2 & 1 < 20xff | 0xaa 11218917 & 1 11218917 | 1 300 << 1

2. Ciąg Fibonacciego fn zdefiniowany jest wzorami rekurencyjnymi f1 = 1,f2 = 1, fn = fn−2 + fn−1 dla n ­ 3. Napisz program, który w wektorze 10liczb typu int umieści 10 kolejnych liczb Fibonacciego, a następnie wyświetlije na ekranie, po jednej liczbie w wierszu.

3. Napisz program, który wczytuje linijkę z konsoli, po czym wyświetla jąw od końca do początku (np. po wpisaniu napisu Jan Kowalski programwyświetli ikslawoK naJ).

4. Napisz program szyfrujący. Powinien on do każdego wczytanego znaku do-dawać 13. Wejście i wyjście programu powinno być skojarzone z plikamio nazwach wczytywanych z klawiatury.

5. Napisz dekoder do poprzedniego zadania.

6. Proszę przerobić program 2.11 (str. 48) tak, aby zliczał wystąpienia dowol-nego znaku w całym pliku.

7. Rozpatrzmy ciąg liczb naturalnych cn spełniający warunek

cn ={3cn−1 + 1, jeśli cn−1 jest nieparzystecn−1/2, jeśli cn−1 jest parzyste

Istnieje nieudowodniona hipoteza, że niezależnie od wartości pierwszegoelementu tego ciągu prędzej czy później jeden z jego elementów będzie miałwartość 1. Napisz program, który sprawdza tę hipotezę dla wszystkich c1mniejszych od 1000.

Page 59: C++ w dwunastu długich krokach

Skorowidz

adaptacja pojemnika, 234algorytm (STL), 209, 237argc, 79argumentdomyślny funkcji, 76tablicowy, 91wskaźnikowy, 91

argv, 79asercja (assert), 118, 130, 192

bajt zerowy, 92bool, 40boost (biblioteka), 247

char, 38CVS, 187

default (etykieta), 252definicjafunkcji, 80obiektu, 22

deklaracjafunkcji, 80

dekorowanie nazw funkcji, 182destruktor, 106double, 38dynamiczna struktura danych, 125dyrektywa preprocesora, 24, 189define, 176, 190elif, 191else, 191endif, 176, 191if, 191ifdef, 191ifndef, 176, 191include, 190undefine, 191

dziedziczenie, 139hierarchia, 145prywatne, 159

enum, 127a const int, 127

etykieta, 251exit, 80

float, 38friend, 114funkcjaargumenty faktyczne, 61argumenty formalne, 61exit, 80globalna, 61main, 79mieszająca, 231nazwa kwalifikowana, 137overriding, 147prototyp, 80rekurencyjna, 75składowa, 47, 108statyczna, 121sygnatura, 80zaprzyjaźniona, 114

funktor, 210

generyczne programowanie, 202goto, 251

hermetyzacja danych, 120

identyfikator, 24implementacja, 63, 126klasy, 115

inicjalizacjastruktur, 101tablic, 88

instrukcjaa wyrażenie, 56blokowa, 31, 34break, 35, 252continue, 35

253

Page 60: C++ w dwunastu długich krokach

254 Skorowidz

do, 35for, 33goto, 251if...else, 31return, 61switch, 251while, 35

interfejs, 63, 125klasy, 115

iterator (STL), 206

jednostka kompilacji, 174

klasa, 101a przestrzeń nazw, 136abstrakcyjna, 155bazowa, 141implementacja, 115interfejs, 115pochodna, 141podstawowa, 141rekurencyjna, 133zagnieżdżona, 137zaprzyjaźniona, 114

klucz (słownika, zbioru), 225, 226kolejka (STL), 235priorytetowa, 235

kompilacjaprogramu, 18, 174warunkowa, 191

kompilator, 14, 18g++, opcje, 249

komunikaty, 108konkretyzacja (szablonu), 198konsolidator, 18, 174konstruktor, 103domyślny, 105kopiujący, 105domyślny, 110w formie szblonu, 201preambuła, 103

kontrakt, 118, 130konwersjaniejawna, 42standardowa, 41

liczby zespolone, 220lista (STL), 234lista inicjalizacyjna, 103

literał, 22, 40long, 36long double, 38long long, 36

main, 79metoda, 47, 108, 152czysto wirtualna, 155statyczna, 121stała, 108

namespace, 136NaN, 39napisy, 46, 221NDEBUG, 119niezmiennik, 119

obiektfunkcyjny, 210, 212globalny, 50lokalny, 50statyczny, 77stały, 43zanurzony, 145

operator, 51. (kropka), 101, (przecinek), 56::, 148!, 54&&, ||, and, or, 55++, 53* (wyłuskanie), 84@ (adres), 84->, 101+, -, *, /, %, 54--, 53=, +=, -=, *=, /=, %=, 55<<=, >>=, ^=, |=, &=, 55<, <=, >, >= , ==, 54^, ~, |, &, 54? :, 55[], 47łączność, 53priorytet, 53przeciążony, 80przypisaniadomyślny, 111sizeof, 54, 90, 145static cast<...>, 54

Page 61: C++ w dwunastu długich krokach

Skorowidz 255

pamięć wolna, 94plik, 162binarny, 162Makefile, 178nagłówkowy, 23, 115obiektowy, 174tekstowy, 162

pojemnik, 206polimorfizm, 149, 152nazw funkcji, 80

predykat, 212preprocesor, 18, 189private:, 113programowanie generyczne, 202projekt (programu), 181promocja całkowita, 42protected:, 146prototyp funkcji, 80przeciążanie operatorów, 80przestrzeń nazw, 136

gnu cxx, 137, 206, 233, 238std, 28, 136

przesłanianie zmiennych, 50public:, 113

referencje, 59stałe, 60

rekurencja, 75struktur danych, 133

relacja „X jest Y”, 144relacja „X ma Y”, 144relacja „X zarządza Y”, 144repozytorium CVS, 187

size t, 37składowachroniona, 147klasy, 102prywatna, 113, 147publiczna, 113, 147struktury, 100

sortowanie, 242kryterium, 243list, 234stable sort, 230w stylu C (qsort), 214

static, 77statycznaskładowa klasy, 121

zmienna lokalna, 77statyczność, 94stałe, 43symboliczne, 43, 127

std::pair, 201, 226std::string, 162std::vector, 216sterta, 94STL, 205algorytmy, 209, 237funktory, 210iteratory, 206pojemniki, 206, 225predykaty, 212

stos (STL), 234stos funkcji, 72struktura, 99rekurencyjna, 133

strumień, 44, 161buforowany, 161cerr, cin, clog, cout, 162napisowy, 162tryb binarny, 162tryb tekstowy, 162wejścia, 167wyjścia, 167

sygnatura funkcji, 80szablonfunkcji, 198funkcji swobodnej, 199klasy, 195konkretyzacja, 198specjalizacja, 200częściowa, 200

słowa kluczowe, 24słownik (STL), 49, 226mieszający, 231

tablica, 87a wskaźnik, 90inicjalizacja, 88jako argument funkcji, 91wielowymiarowa, 88

tablica mieszająca, 231template, 195this, 112typedef, 202typybez znaku, 37

Page 62: C++ w dwunastu długich krokach

256 Skorowidz

całkowite, 36parametryczne, 47ze znakiem, 37zmiennopozycyjne, 36, 38

using namespace, 28

vtable, 153

wartownik pliku, 176wektor (STL), 47, 216bitowy, 236numeryczny, 237wielowymiarowy, 219

wielosłownik (STL), 231wielozbiór (STL), 231wiązanie dynamiczne, 153wskaźnik, 83, 84a tablica, 90na funkcję, 75, 91

na stałą, 86na wskaźnik, 86stały, 86stały na stałą, 86this, 112void*, 85zerowy, 85

wyliczenia, 127wzorzec, patrz: szablon

zakresglobalny, 51lokalny, 51

zaprzyjaźnianie funkcji, 114zasięg, 50zbiór (STL), 230bitowy, 236mieszający, 231

znak, 38

Page 63: C++ w dwunastu długich krokach

Bibliografia

[1] Bjarne Stroustrup, Język C++, wydanie piąte zmienione i rozszerzone,WNT, Warszawa 2000.

[2] Bartosz Milewski, C++ in Action. Industrial-Strength ProgrammingTechniques, Addison-Wesley, New York 2001; wersja elektroniczna:http://www.relisoft.com/book.

[3] Jesse Liberty, C++ dla każdego, Helion 2002 (okrojona wersja angiel-ska Teach Yourself C++ in 21 Days dostępna jest w Internecie, np.http://newdata.box.sk/bx/c/).

[4] http://www.kde.org/whatiskde/project.php#factsandfigures.

[5] M. Galssi i in., GNU Scientific Library Reference Manual, Edition 1.7 forGSL Version 1.7, http://www.gnu.org/software/gsl/manual/.

[6] Donald Knuth, Sztuka programowania, WNT, Warszawa 2002.

[7] Qt Reference Documentation, http://doc.trolltech.com/4.0/.

[8] The GNU Make Manual, http://www.gnu.org/software/make/manual/.

[9] Gary V. Vaughan, Ben Elliston, Tom Tromey i Ian L. Taylor, GNU Autoconf,Automake and Libtool, http://sources.redhat.com/autobook/.

[10] Karl Fogel i Moshe Bar, Open Source Development with CVS, 3rd Edition,http://cvsbook.red-bean.com/.

[11] Tomasz Marciniak, Podstawy CVS, http://bsd.amu.edu.pl/wyklady/cvs.html.

[12] Ben Collins-Sussman, Brian W. Fitzpatrick i C. Michael Pilato, VersionControl with Subversion, http://svnbook.red-bean.com/.

[13] Bjarne Stroustrup, A Brief Look at C++0x,http://www.artima.com/cppsource/cpp0x.html.

[14] Standard Template Library Programmer’s Guide,http://www.sgi.com/tech/stl/.

[15] http://pl.wikipedia.org/wiki/Sito Eratostenesa.

[16] Biblioteka boost: http://www.boost.org/.

[17] Scott Meyers C++. 50 efektywnych sposobów na udoskonalenie Twoich pro-gramów, Helion, 2003.

257