You are on page 1of 336

Jzyk C++ podstawy

programowania

Uniwersytet Marii Curie-Skodowskiej


Wydzia Matematyki, Fizyki i Informatyki
Instytut Informatyki

Jzyk C++ podstawy


programowania

Pawe Mikoajczak

Lublin 2011

Instytut Informatyki UMCS


Lublin 2011
Pawe Mikoajczak

Jzyk C++ podstawy programowania


Recenzent: Marek Stabrowski
Opracowanie techniczne: Karol Kuczyski, Marcin Denkowski
Projekt okadki: Agnieszka Kumierska

Praca wspnansowana ze rodkw Unii Europejskiej w ramach


Europejskiego Funduszu Spoecznego

Publikacja bezpatna dostpna on-line na stronach


Instytutu Informatyki UMCS: informatyka.umcs.lublin.pl.

Wydawca
Uniwersytet Marii Curie-Skodowskiej w Lublinie
Instytut Informatyki
pl. Marii Curie-Skodowskiej 1, 20-031 Lublin
Redaktor serii: prof. dr hab. Pawe Mikoajczak
www: informatyka.umcs.lublin.pl
email: dyrii@hektor.umcs.lublin.pl

Druk
ESUS Agencja Reklamowo-Wydawnicza Tomasz Przybylak
ul. Ratajczaka 26/8
61-815 Pozna
www: www.esus.pl

ISBN: 978-83-62773-11-4

Spis treci

Przedmowa

ix

1 Specyficzne elementy jzyka C++


1.1. Wstp . . . . . . . . . . . . . . . . . . . . .
1.2. Rys historyczny . . . . . . . . . . . . . . . .
1.3. Strumienie wejcia/wyjcia w C++ . . . . .
1.4. Nowa posta komentarzy . . . . . . . . . . .
1.5. Dowolne rozmieszczenie deklaracji . . . . .
1.6. Przekazywanie argumentw przez referencje
1.7. Argumenty domniemane . . . . . . . . . . .
1.8. Przecianie funkcji . . . . . . . . . . . . .
1.9. Wzorce funkcji . . . . . . . . . . . . . . . .
1.10. Funkcje inline . . . . . . . . . . . . . . . . .
1.11. Dynamiczne zarzdzanie pamici . . . . .
1.12. Sowa kluczowe jzyka C++ . . . . . . . . .
1.13. Schemat programu w jzyku C++ . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

1
2
2
4
6
7
7
11
13
14
16
17
23
24

2 Wprowadzenie do programowania obiektowego


2.1.
2.2.
2.3.
2.4.
2.5.
2.6.
2.7.
2.8.

Wstp . . . . . . . . . . . . .
Paradygmaty programowania
Obiekty i klasy . . . . . . . .
Hermetyzacja danych . . . . .
Dziedziczenie . . . . . . . . .
Polimorzm . . . . . . . . . .
Podsumowanie terminologii .
rodowisko programistyczne .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

27
28
28
30
34
35
36
37
38

. . . . . .
. . . . . .
obiektu)
. . . . . .
. . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

41
42
42
43
43
47

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

3 Klasy i obiekty
3.1.
3.2.
3.3.
3.4.
3.5.

Wstp . . . . . . . . . . . . . .
Deklaracja i denicja klasy . .
Wystpienie klasy (deniowanie
Dostp do elementw klasy . .
Metody klasy . . . . . . . . . .

vi

SPIS TRECI
3.6. Klasa z akcesorami . . . . . . . . . . . . . . . . . . . . . . . . 50
3.7. Funkcje skadowe const . . . . . . . . . . . . . . . . . . . . . 53

4 Konstruktory i destruktory
4.1. Wstp . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2. Inicjalizacja obiektu klasy . . . . . . . . . . . . . . .
4.3. Konstruktory i destruktory domylne . . . . . . . . .
4.4. Konstruktor jawny . . . . . . . . . . . . . . . . . . .
4.5. Wywoywanie konstruktorw i destruktorw . . . . .
4.6. Rozdzielenie interfejsu od implementacji . . . . . . .
4.7. Wskanik do obiektu this . . . . . . . . . . . . . . .
4.8. Wskanik this kaskadowe wywoania funkcji . . . .
4.9. Tablice obiektw . . . . . . . . . . . . . . . . . . . .
4.10. Inicjalizacja tablic obiektw nie bdcych agregatami
4.11. Tablice obiektw tworzone dynamicznie . . . . . . .
4.12. Kopiowanie obiektw . . . . . . . . . . . . . . . . . .
4.13. Klasa z obiektami innych klas . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

57
58
58
59
61
63
65
67
68
71
73
75
77
78

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

83
84
84
88
92
98
104
117

5 Dziedziczenie i hierarchia klas


5.1.
5.2.
5.3.
5.4.
5.5.
5.6.
5.7.

Wstp . . . . . . . . . . . . . . . . . . . . .
Hierarchiczna struktura dziedziczenia . . . .
Notacja UML (Unied Modeling Language)
Proste klasy pochodne . . . . . . . . . . . .
Konstruktory w klasach pochodnych . . . .
Dziedziczenie kaskadowe . . . . . . . . . . .
Dziedziczenie wielokrotne bezporednie . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

6 Funkcje zaprzyjanione
6.1.
6.2.
6.3.
6.4.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

121
122
123
134
141

Wstp . . . . . . . . . . . . . . . . . . . . . . . . . . .
Denicje . . . . . . . . . . . . . . . . . . . . . . . . . .
Przeciony operator dodawania ( + ) . . . . . . . . .
Przeciony operator mnoenia ( * ) . . . . . . . . . .
Funkcja operatorowa w postaci niezalenej funkcji . .
Przecianie operatorw rwnoci i nierwnoci . . . .
Przecianie operatora przypisania ( = ) . . . . . . . .
Przecianie operatora wstawiania do strumienia ( )

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

143
144
144
147
150
154
161
165
173

Wstp . . . . . . . . . . . . . . . . . . . . . .
Funkcja niezalena zaprzyjaniona z klas . .
Funkcja skadowa zaprzyjaniona z inn klas
Klasy zaprzyjanione . . . . . . . . . . . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

7 Przecianie operatorw
7.1.
7.2.
7.3.
7.4.
7.5.
7.6.
7.7.
7.8.

vii

SPIS TRECI

8 Funkcje statyczne i wirtualne


8.1.
8.2.
8.3.
8.4.
8.5.

Wstp . . . . . . . . . .
Metody i dane statyczne
Polimorzm . . . . . . .
Funkcje wirtualne . . . .
Funkcje abstrakcyjne . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

177
178
178
183
187
194

9 Klasy wirtualne i zagniedone

203
9.1. Wstp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
9.2. Klasy wirtualne . . . . . . . . . . . . . . . . . . . . . . . . . . 204
9.3. Klasy zagniedone . . . . . . . . . . . . . . . . . . . . . . . . 212

10 Wskaniki do klas

217
10.1. Wstp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
10.2. Wskaniki klasowe . . . . . . . . . . . . . . . . . . . . . . . . 218
10.3. Wskaniki skadowych klas . . . . . . . . . . . . . . . . . . . 224

11 Techniki obsugi bdw


11.1. Wstp . . . . . . . . . . . . . . . .
11.2. Funkcje walidacyjne . . . . . . . .
11.3. Graniczne wartoci plik limits.h .
11.4. Narzdzie assert() i funkcja abort()
11.5. Przechwytywanie wyjtkw . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

229
230
230
234
237
239

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

265
266
266
276
292

12 Szablony w C++
12.1. Wstp . . . . . . . .
12.2. Przecianie funkcji
12.3. Szablony funkcji . .
12.4. Szablony klas . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

Sownik angielsko-polski

307

Skorowidz

325

Przedmowa
Jzyk C++ jest uznawany za najlepszy jzyk do programowania obiektowego. Jest on nadzbiorem jzyka C. Powsta w Bell Labs (USA), gdzie
Bjarne Stroustrup stworzy go na pocztku lat osiemdziesitych. Potem jzyk C++ by wiele lat rozwijany. Prace nad standardem jzyka zaczy si
w roku 1990. Ostatecznie, po rozszerzeniu jzyka o wyjtki, RTTI, szablony
oraz bibliotek STL w 1998 roku przyjto midzynarodowy standard (ISO/IEC 14882:1998). W roku 2003 pojawia si druga wersja standardu, ale
zawieraa ona tylko drobne korekty.
Trudno sobie obecnie wyobrazi zawodowego programist, ktry nie zna
przynajmniej podstaw jzyka C++.
Jzyk C++ jest jzykiem skomplikowanym i trudnym, niemniej moliwym do opanowanie. Jak pisze twrca jzyka, B. Stroustrup w swoim najnowszym podrczniku opublikowanym w 2009 roku, z ponad tysica studentw pierwszego roku, ktrych uczy w Texas A&M University wikszo z
nich odniosa sukces (zdaa egzaminy) mimo, e 40 procent z nich nie miao
nigdy do czynienia z programowaniem. To wyznanie wybitnego fachowca
jest optymistyczne. Naley pamita, e klasyczne i kompletne podrczniki
programowania s bardzo obszerne (podrcznik B. Stroustrupa liczy sobie 1106 stron, a podrcznik S. Prata 1303 strony). Skrypt akademicki
z natury rzeczy jest tylko wprowadzeniem do tematyki przedmiotu, ma za
zadnie przedstawi wstpne i elementarne zagadnienia zwizane z omawian
dziedzin, std jego objto nie moe by zbyt dua.
Na potrzeby studentw kierunku informatyka opracowalimy ten podrcznik wybierajc do arbitralnie prezentowany materia. Prezentujc jzyk C++, zakadamy, e studenci znaj jzyk C.
Niniejszy podrcznik jest przeznaczony gwnie dla studentw 3-letnich
studiw zawodowych (licencjatw) a take dla studentw 2-letnich studiw
uzupeniajcych. Podrcznik powsta na podstawie notatek wykorzystywanych przeze mnie w trakcie prowadzenia wykadw z przedmiotw Jzyki
programowania, oraz Jzyk C i C++ dla studentw UMCS w latach
1995 2005 i dalszych.
W podrczniku przyjem zasad, e nauka programowania oparta jest

Przedmowa
na licznych przykadach, ktre ilustruj praktyczne zastosowania paradygmatw programowania i skadni jzyka C++.
Podrcznik ma by pomoc dydaktyczn wspierajc nauk programowania realizowana w ramach 30-godzinnego wykadu i wicze laboratoryjnych. Naley pamita, e podrcznik nie stanowi penego kompendium
wiedzy o jzyku C++, jest jedynie prostym i przystpnym wprowadzeniem
w fascynujcy wiat pisania efektywnych i efektownych programw komputerowych. Wszystkie przykady programw, ktre zamieciem w tym
podrczniku zostay przetestowane za pomoc kompilatora C++ Builder 6
rmy Borland, na platformie Windows. Wikszo programw sprawdzana
take bya na platformie Linuksowej oraz QT.

Rozdzia 1
Specyficzne elementy jzyka C++

1.1.
1.2.
1.3.
1.4.
1.5.
1.6.
1.7.
1.8.
1.9.
1.10.
1.11.
1.12.
1.13.

Wstp . . . . . . . . . . . . . . . . . . . . . .
Rys historyczny . . . . . . . . . . . . . . . .
Strumienie wejcia/wyjcia w C++ . . . . .
Nowa posta komentarzy . . . . . . . . . . .
Dowolne rozmieszczenie deklaracji . . . . . .
Przekazywanie argumentw przez referencje .
Argumenty domniemane . . . . . . . . . . .
Przecianie funkcji . . . . . . . . . . . . . .
Wzorce funkcji . . . . . . . . . . . . . . . . .
Funkcje inline . . . . . . . . . . . . . . . . .
Dynamiczne zarzdzanie pamici . . . . . .
Sowa kluczowe jzyka C++ . . . . . . . . .
Schemat programu w jzyku C++ . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

2
2
4
6
7
7
11
13
14
16
17
23
24

1. Specyczne elementy jzyka C++

1.1. Wstp
Jzyk C oraz C++ s najbardziej popularnymi jzykami programowania.
W orodkach akademickich, a take w duych korporacjach komputerowych,
tworzone s co jaki czas nowe jzyki programowania wysokiego poziomu, ale
jak uczy dowiadczenie, wszystkie te pomysy s z pocztku entuzjastycznie przyjmowane przez programistw, nowe jzyki maj swoich zagorzaych
wielbicieli, a po jakim czasie powoli odchodz w niepami, lub s marginalizowane. Moda na dany jzyk przemija. Jedynie jzyk C trwa i ma si
bardzo dobrze, dziki take swoim modykacjom (jzyk C++ jest nadzbiorem jzyka C). Naley przypomnie, e jzyk C jest integralnie zwizany z
systemem Unix. Jzyk C++ zosta zaprojektowany jako obiektowa wersja
jzyka C. W jzyku C++ powstao i powstaje wiele znaczcego oprogramowania: systemy operacyjne, programy narzdziowe, programy uytkowe,
programy do obsugi sieci komputerowych. Na rynku pracy, programista
bez znajomoci jzyka C/C++ i systemu Unix, ma nike szanse. Obecnie od programisty wymaga si znajomoci przynajmniej dwch jzykw
programowania i znajomoci minimum dwch systemw operacyjnych, jzyk C/C++ i Unix s bezwzgldnie wymagane, znajomo innych jzykw
programowania oraz innych systemw (systemy operacyjne rodziny Windows) jest dodatkowym atutem. Obecnie (rok 2010) dominujcym jest jzyk
C++ integralnie zwizany z programowaniem obiektowym. Jzyk C++ jest
nadzbiorem jzyka C. Zwykle podrczniki dydaktyczne do nauki programowania w jzyku C++ zawieraj obszern cz powicon programowaniu
w jzyku C. Aby przyspieszy projektowanie i implementowanie programw,
zostay opracowane specjalne systemy wspomagajce prace programistw.
Doskonaymi narzdziami do tworzenia aplikacji s pakiety takie jak np.
C++ Builder rmy Borland czy Visual C++ rmy Microsoft. Te produkty
nale do systemw szybkiego projektowania aplikacji (ang. RAD - Rapid
Application Development). Korzystajc z tych pakietw moemy efektywnie
konstruowa 32-bitowe programy pracujce w systemie Windows.
Wydaje si rozsdne, aby dobrze wyksztacony informatyk opanowa
nastpujce narzdzia programistyczne:
jzyk C
jzyk C++
jzyk Java

1.2. Rys historyczny


Historia powstawania jzyka C a nastpnie C++ jest duga, rozwojem
tych jzykw zajmowao si wielu wspaniaych fachowcw. Jzyk C powsta

1.2. Rys historyczny


dziki przejciu podstawowych zasad z dwch innych jzykw: BCPL i jzyka B. BCPL opracowa w 1967 roku Martin Richards. Ken Thompson
tworzy jzyk B w oparciu o BCPL. W 1970 roku w Bell Laboratories na
podstawie jzyka B opracowano system operacyjny UNIX.
Twrc jzyka C jest Dennis M. Ritchie, ktry take pracowa w Bell
Laboratories. W 1972 roku jzyk C by implementowany na komputerze
DEC PDP-11. Jedn z najwaniejszych cech jzyka C jest to, e programy
pisane w tym jzyku na konkretnym typie komputera, mona bez wikszych
kopotw przenosi na inne typy komputerw. Jzyk C by intensywnie rozwijany w latach siedemdziesitych. Za pierwszy standard przyjto opis jzyka zamieszczony w dodatku pt. C Reference Manual podrcznika The
C Programming Language. Publikacja ukazaa si w 1978 roku.
Opublikowany podrcznik deniowa standard jzyka C, reguy opisane
w tym podrczniku nazywamy standardem K&R jzyka C. W 1983 roku
Bell Laboratories wydao dokument pt. The C Programming Language Reference Manual, ktrego autorem by D. M. Ritchie. W 1988 Kernighan
i Ritchie opublikowali drugie wydanie The C Programming Language.
Na podstawie tej publikacji, w 1989 roku Amerykaski Narodowy Instytut
Normalizacji ustali standard zwany standardem ANSI jzyka C. Za twrc jzyka C++ (przyjmuje si, e jest to nadzbir jzyka C) uwaany jest
Bjarne Stroustrup, ktry w latach osiemdziesitych pracujc w Bell Laboratories rozwija ten jzyk, tak aby zrealizowa programowanie obiektowe.
Bjarne Stroustrup wsplnie z Margaret Ellis opublikowa podrcznik The
Annotated C++ Reference Manual. W 1994 roku Amerykaski Narodowy
Instytut Normalizacji opublikowa zarys standardu C++.
Nowym jzykiem, silnie rozwijanym od 1995 roku jest jzyk programowania Java. Jzyk Java opracowany w rmie Sun Microsystem, jest oparty
na jzyku C i C++. Java zawiera biblioteki klas ze skadnikami oprogramowania do tworzenia multimediw, sieci, wielowtkowoci, graki, dostpu
do baz danych i wiele innych. Jak ju to zasygnalizowano, jzyk C jest
podstaw jzyka C++ i Javy. Wobec tego naley doskonale zna ten jzyk
programowania.
W 1997 roku ukazao si trzecie wydanie ksiki Bjarnea Stroustrupa
Jzyk programowania C++. Oczywicie jzyk C++ jest nadal rozwijany,
ale uznano, e trzecie wydanie podrcznika Stroustrupa wyczerpujco ujmuje nowe elementy jzyka i de facto moe by traktowane jako standard.
Ostateczne standard jzyka C++ zosta przyjty przez komitet ANSI/ISO
w 1998 roku, zostaa wtedy ustalona jednolita specykacja jzyka. W 2002
roku przyjto kolejn poprawion wersj tego standardu. Dokument opisujcy ten standard jest dostpny za porednictwem sieci Internet pod numerem
14882 na stronie http://www.ansi.org. Jzyk C++ jest dzisiaj najpopularniejszym jzykiem programowania stosowanym przez zawodowcw. Jzyk

1. Specyczne elementy jzyka C++


ten jest take podstaw wyksztacenia informatykw. Na uniwersytetach,
gdzie przez wiele lat jzyk Pascal by gwnym jzykiem programowania,
obecnie podstaw nauczania staje si jzyk C/C++.
Istotnym elementem rozwoju jzyka C++ byo doczenie do standardu
bardzo elastycznej i o duych moliwociach biblioteki szablonw, znanej
pod nazw Standardowej Biblioteki Szablonw (ang. Standard Template
Library, STL). Przyjcie tej biblioteki do standardu jzyka nastpio w 1997
roku. Faktycznie biblioteka szablonw umoliwia realizowanie nowego paradygmatu programowania programowanie uoglnionego ( ang. generic
programming).
Zasadnicza rnica pomidzy jzykami C i C++ polega na tym, e
C++ posiada specyczne elementy zwizane bezporednio z programowaniem obiektowy oraz kilka istotnych usprawnie. W jzyku C++ wystpuj
te specyczne elementy nie zwizane z programowaniem obiektowym. S
to:
Nowe mechanizmy wejcia/wyjcia
Nowa posta komentarzy
Dowolne rozmieszczenie deklaracji
Przekazywanie argumentw przez referencje
Argumenty domniemane
Przecianie funkcji
Dynamiczne zarzdzanie pamici
Funkcje inline

1.3. Strumienie wejcia/wyjcia w C++


W C++ pojawiy si nowe mechanizmy wejcia/wyjcia. Standardowe
strumienie jzyka C to:
stdin
stdout
stderr
stdlog
W jzyku C++ wystpuj dodatkowe, predeniowane strumienie:
cin
cout
cerr
clog
Tego typu mechanizmy obsugi wejcia/wyjcia s bardzo wygodne i
korzystnie zastpuj funkcje printf() i scanf(). Na przykad:
c o u t << wyrazenie_1 << wyrazenie_2 <<wyrazenie_3

1.3. Strumienie wejcia/wyjcia w C++


wysya do strumienia cout (oznaczajcego normalne wyjcie stdout) kolejne
wartoci wyrae. Podobnie
c i n >> wartosc_1 >> wartosc_2 >>wartosc_3

odczytuje ze strumienia cin (standardowe wejcie stdin) warto jednego z


typw: char, short, int, long, oat, double lub char *.
W celu zilustrowania typowej dla C++ obsugi wejcia/wyjcia napiszemy pokazany na wydruku 1.1. program w konwencji jzyka C uywajc
funkcji printf() i scanf() i dla kontrastu pokaemy program 1.2 typowy dla
jzyka C++ wykorzystujcy strumienie cin i cout.
Listing 1.1. Program napisany w konwencji jzyka C
1

#include <s t d i o . h>


#include <c o n i o . h>

11

13

15

i n t main ( )
{
int x ;
float y ;
p r i n t f ( " \ nPodaj l i c z b e c a l k o w i t a : " ) ;
s c a n f ( "%d" ,&x ) ;
p r i n t f ( " \ nPodaj l i c z b e r z e c z y w i s t a : " ) ;
s c a n f ( "%f " ,&y ) ;
p r i n t f ( " \n%d r a z y %f =%f " , x , y , xy ) ;
getch () ;
return 0 ;
}

W pokazanym klasycznym programie napisanym w jzyku C naley


wprowadzi liczb cakowit i liczb rzeczywist. Program wywietli iloczyn
tych liczb. Taki sam program, ale napisany w konwencji C++ pokazuje wydruk 1.2.
Listing 1.2. Program napisany w konwencji C++
1

#include <i o s t r e a m . h>


#include <c o n i o . h>

11

i n t main ( )
{
int x ;
float y ;
c o u t << " Podaj l i c z b e c a l k o w i t a : " ;
c i n >> x ;
c o u t << " Podaj l i c z b e r z e c z y w i s t a : " ;
c i n >> y ;
c o u t << x << " r a z y " << y << " = " << xy ;

1. Specyczne elementy jzyka C++


getch () ;
return 0 ;

13

15

Naley zwrci uwag na istotne rnic pomidzy pokazanymi programami. Po pierwsze, w programie w konwencji jzyka C++ naley wczy
inny plik nagwkowy:
#include <i o s t r e a m . h>

Zamiast instrukcji:
p r i n t f ( " \ nPodaj l i c z b e c a l k o w i t a : " ) ;

mamy instrukcj:
c o u t << " Podaj l i c z b e c a l k o w i t a : " ;

acuch Podaj liczbe calkowita zosta przesany do strumienia przy pomocy operatora . W zwykym C jest to operator przesunicia bitw w
lew stron, ktry dziaa na wartociach cakowitych przesuwajc ich bity
o podan ilo miejsc. Podobnie dziaa operator przesunicia bitw w praw stron , ktry przesuwa bity w prawo. W C++ operatory i dalej
funkcjonuj jako operatory przesuwania bitw, ale mog mie take inne
znaczenie, zalene od kontekstu. W pokazanym przykadzie, znak moe
take by uyty do wysania danych wyjciowych do strumienia cout i z tego powodu nazywany jest operatorem umieszczajcym (ang. insertor), tzn.
umieszcza dane w strumieniu cout. Podobnie znak moe by wykorzystany
do odczytania danych wejciowych pochodzcych ze strumienia cin, dlatego
zwany jest operatorem pobierajcym (ang. extractor). Ta zdolno nadawania nowego znaczenia typowym operatorom nazywana jest przecieniem
operatorw ( ang. operator overloading) jest charakterystyczn cech jzyka
C++.

1.4. Nowa posta komentarzy


W jzyku C++ wprowadzono dodatkowo symbol jednoliniowego komentarza //. C++ ignoruje zawarto linii nastpujcej po symbolu //. Pierwotnie standard ANSI jzyka C ustala, e tylko zawarto umieszczona
pomidzy znakami /* i */ jest komentarzem. W tym przypadku, nie ma
ogranicze, co do iloci linii. Jeeli mamy krtki komentarz to zazwyczaj
uywamy symbolu //, dla dugich komentarzy dalej uywamy symboli /*
i */. Komentarze s istotna czci programw komputerowych. Nie wpy-

1.5. Dowolne rozmieszczenie deklaracji


waj na dziaanie programu, dokumentuj podejmowane akcje i specykuj uywane zmienne i stae. Komentarzy nie mona zagnieda. W fazie
wstpnej, analizy tekstu programu komentarz zostaje przeksztacony w spacje. W tej sytuacji napis:
z=x / i l o c z y n / y

zostanie przeksztacony w napis:


z = x y

1.5. Dowolne rozmieszczenie deklaracji


Jzyk C++ zezwala na umieszczanie deklaracji w dowolnym miejscu,
pod warunkiem, e wykorzystanie jej nastpi przed uyciem deklarowanej
zmiennej. Mona take uywa wyrae w inicjalizacji zmiennych. Jak pamitamy, standard jzyka C wymaga aby deklaracje byy umieszczone na
pocztku funkcji lub bloku. Przykadowy fragment kodu moe mie posta:
int
a =
int
b =
int

a;
10;
b;
100;
y = a b;

1.6. Przekazywanie argumentw przez referencje


W C++ argument moe by przekazywany przez referencj. W tym celu
naley poprzedzi jego nazw symbolem & w nagwku i prototypie funkcji.
Uywajc standardowo funkcji mamy na uwadze dwa ograniczenia: argumenty s przekazywane przez warto, funkcja moe zwrci tylko jedn
warto. W jzyku C to ograniczenie jest usuwane gdy zastosujemy wskaniki. W jzyku C++ przekazywanie argumentw dodatkowo poprzez referencj moe usun dodatkowo wymienione ograniczenia. W C++ przekazywanie argumentw, oprcz przekazywanie przez warto moemy realizowa
dodatkowo jeszcze na dwa sposoby: z wykorzystaniem wskanikw i z wykorzystaniem referencji. Naley pamita, e w tym przypadku funkcja nie
pracuje na kopii, program przekazuje funkcji oryginalny obiekt. Omwimy
trzy krtkie programy, w ktrych wykorzystana bdzie funkcja zamiana().
Funkcja zamiana() ma otrzyma dwie zmienne, zainicjalizowane w funkcji main() i zmieni ich wartoci. Do funkcji zamiana() przekazane bd

1. Specyczne elementy jzyka C++


wartoci argumentw, wskaniki i referencje. Struktura trzech programw
pokazanych na wydrukach 1.3, 1.4 i 1.5 jest bardzo podobna. Zanalizujemy program pokazany na wydruku 1.3. Wewntrz funkcji main() program
inicjalizuje dwie zmienne cakowite, zmienna x otrzymuje warto 1 a zmienna y - warto 100. Nastpnie wywoywana jest funkcja zamiana() z argumentami x i y. Nastpuje klasyczne przekazanie argumentw przez warto.
Funkcja zamiana() zmienia wartoci zmiennych (co wida na wydruku). Ale
gdy ponownie sprawdzane s wartoci zmiennych w funkcji main() okazuje
si, e wartoci nie ulegy zmianie.
Podczas przekazywania argumentw przez warto, tworzone s lokalne
kopie tych zmiennych wewntrz funkcji zamiana(). Po skoczeniu zadania
kopie s niszczone i wartoci zmiennych w funkcji main() pozostaj niezmienione.
Listing 1.3. Argumenty przekazywane do funkcji przez warto
1

11

13

15

17

19

21

23

25

#include <i o s t r e a m . h>


#include <c o n i o . h>
void zamiana ( int , i n t ) ;
i n t main ( )
{
int x = 1 , y = 100;
c o u t << " Funkcja main , wynik p r z e d zamiana x : "
<< x << " y : " << y << " \n" ;
zamiana ( x , y ) ;
c o u t << " Funkcja main , wynik po zamiana x : "
<< x << " y : " << y << " \n" ;
getch () ;
return 0 ;
}
void zamiana ( i n t x , i n t y )
{
i n t temp ;
c o u t << " Funkcja zamiana , wynik p r z e d zamiana x : "
<< x << " y : " << y << " \n" ;
temp = x ;
x = y;
y = temp ;
c o u t << " Funkcja zamiana , wynik po zamiana x : "
<< x << " y : " << y << " \n" ;
}

Po uruchomieniu programu mamy nastpujcy wynik:


Funkcja
Funkcja
Funkcja
Funkcja

main , wynik p r z e d zamiana x : 1 y : 100


zamiana , wynik p r z e d zamiana x : 1 : y : 100
zamiana , wynik po zamiana x : 100 y : 1
main , wynik po zamiana x : 1 y : 100

1.6. Przekazywanie argumentw przez referencje


Omwimy podobny program, ale w tym przykadzie posuymy si wskanikami. Program pokazany jest na wydruku 1.4. W zmiennej wskanikowej
umieszczony jest adres obiektu, przekazujc wskanik przekazujemy adres
obiektu i dlatego funkcja moe bezporednio operowa na wartoci zmiennej znajdujcej si pod wskazanym adresem, adne kopie nie s potrzebne.
Za pomoc wskanikw umoliwimy funkcji zamiana() rzeczywist zamian
wartoci zmiennych x i y. W prototypie funkcji zamiana() deklarujemy, e
argumentami funkcji bd dwa wskaniki do zmiennych cakowitych:
void zamiana ( i n t , i n t ) ;

Listing 1.4. Przekazanie przez referencj z wykorzystaniem wskanikw


1

11

13

15

17

19

21

23

25

#include <i o s t r e a m . h>


#include <c o n i o . h>
void zamiana ( i n t , i n t ) ;
i n t main ( )
{
int x = 1 , y = 100;
c o u t << " Funkcja main , wynik p r z e d zamiana x : "
<< x << " y : " << y << " \n" ;
zamiana(&x , &y ) ;
c o u t << " Funkcja main , wynik po zamiana x : "
<< x << " y : " << y << " \n" ;
getch () ;
return 0 ;
}
void zamiana ( i n t px , i n t py )
{
i n t temp ;
cout<<" Funkcja zamiana , wynik p r z e d zamiana px : "
<<px <<" py : " << py << " \n" ;
temp = px ;
px = py ;
py = temp ;
c o u t << " Funkcja zamiana , wynik po zamiana px : "
<< px << " py : " << py << " \n" ;
}

Funkcja main() wywouje funkcj zamiana(), przekazywanymi argumentami s adresy zmiennych x i y:


zamiana(&x , &y ) ;

W funkcji zamiana() zmiennej temp nadawana jest warto zmiennej x:


temp = px ;

10

1. Specyczne elementy jzyka C++


W tej instrukcji stosujemy operator wyuskania (czyli dereferencj). Pod
adresem zawartym w zmiennej wskanikowej px umieszczona jest warto
x. W nastpnej linii:
px = py ;

zmiennej wskazywanej przez px przypisana jest warto wskazywana przez


py. W nastpnej linii:
py = temp ;

warto przechowywana w zmiennej temp jest przypisana zmiennej wskazywanej przez py. Dziki takim mechanizmom dokonywana jest prawdziwa
zamiana wartoci zmiennych x i y co wida po uruchomieniu programu:
Funkcja
Funkcja
Funkcja
Funkcja

main , wynik p r z e d zamiana x : 1 y : 100


zamiana , wynik p r z e d zamiana px : 1 py : 100
zamiana , wynik po zamiana px : 100 py : 1
main , wynik po zamiana x : 100 y : 1

Program pokazany na wydruku 1.4 dziaa ale styl programowania daleko


odbiega od prostoty. Istnieje inny sposb wykonania tego samego zadania.
W kolejnym programie pokazanym na wydruku 1.5. zastosowano referencj.
Listing 1.5. Zastosowanie referencji
2

10

12

14

16

18

20

22

#include <i o s t r e a m . h>


#include <c o n i o . h>
void zamiana ( i n t &, i n t &) ;
i n t main ( )
{
int x = 1 , y = 100;
c o u t << " Funkcja main , wynik p r z e d zamiana x : "
<< x << " y : " << y << " \n" ;
zamiana ( x , y ) ;
c o u t << " Funkcja main , wynik po zamiana x : "
<< x << " y : " << y << " \n" ;
getch () ;
return 0 ;
}
void zamiana ( i n t &rx , i n t &ry )
{
i n t temp ;
c o u t << " Funkcja zamiana , wynik p r z e d zamiana rx : "
<< rx << " ry : " << ry << " \n" ;
temp = rx ;
rx = ry ;
ry = temp ;
c o u t << " Funkcja zamiana , wynik po zamiana rx : "

1.7. Argumenty domniemane


<< rx << " ry : " << ry << " \n" ;

24

Prototyp funkcji zamiana() ma posta:


void zamiana ( i n t &, i n t &) ;

Wywoanie funkcji zamiana() ma posta:


zamiana ( x , y ) ;

naley zauway, e teraz nie s przekazywane adresy zmiennych x i y ale


same zmienne. Teraz sterowanie zostaje przekazane do linii:
void zamiana ( i n t &rx , i n t &ry )

gdzie zmienne zostan zidentykowane jako referencje. W dalszych instrukcjach funkcji zamiana() nastpuje rzeczywista zamiana wartoci zmiennych.
Poniewa parametry funkcji zamiana() zostay zadeklarowane jako referencje, wartoci w funkcji main() zostay przekazane przez referencj, dlatego
s zamienione rwnie w tej funkcji. Wynikiem dziaania programu jest
komunikat:
Funkcja
Funkcja
Funkcja
Funkcja

main , wynik p r z e d zamiana x : 1 y : 100


zamiana , wynik p r z e d zamiana rx : 1 ry : 100
zamiana , wynik po zamiana rx : 100 ry : 1
main , wynik po zamiana x : 100 y : 1

1.7. Argumenty domniemane


Do czsto mamy do czynienia z sytuacj, gdy przekazujemy funkcji
kilka argumentw. Moe si okaza, e w wielokrotnie wywoywanej funkcji zmianie ulega tylko jeden parametr. W jzyku C++ programista moe
okreli, e dany argument jest argumentem domniemanym (domylnym) i
wybrane argumenty. W wywoaniu funkcji moemy pomin argument domniemany. Pominity argument domniemany jest automatycznie wstawiany
przez kompilator. Argumenty domniemane musz by umieszczone na licie
argumentw najdalej z prawej strony. Wartoci domylne mog by staymi, zmiennymi globalnymi lub wywoaniami funkcji. Argumenty domylne powinny by okrelone wraz z pierwszym wystpieniem nazwy funkcji.
W praktyce wartoci argumentw domylnych umieszcza si w prototypie
funkcji. Na kolejnym wydruku przedstawiono wykorzystanie argumentw
domniemanych. W pokazanym na wydruku 1.6 programie naley obliczy

11

12

1. Specyczne elementy jzyka C++


objto ( praktycznie jest to mnoenie trzech liczb). Prototyp funkcji obliczajcej iloczyn trzech liczb ma posta:
void o b j e t o s c ( i n t x = 1 , i n t y =1 , i n t z = 1 ) ;

Listing 1.6. Uycie argumentw domniemanych


1

#include <i o s t r e a m . h>


#include <c o n i o . h>

11

13

15

17

19

i n t main ( )
{
objetosc () ;
objetosc (10) ;
o b j e t o s c (10 , 10) ;
o b j e t o s c (10 , 10 , 10) ;
getch () ;
return 0 ;
}
void o b j e t o s c ( i n t x , i n t y , i n t z )
{
int vol ;
vol = x y z ;
c o u t << "x= " << x << " y= " << y << " z= " << z << e n d l ;
c o u t << " o b j e t o s c = " << v o l << e n d l ;
}

W funkcji main() wywoywana jest funkcja objetosc() z rn iloci


argumentw. Pierwsze wywoanie ma posta:
objetosc () ;

Uytkownik nie okreli argumentw, wszystkie trzy argumenty s domniemane, wobec tego w tym wywoaniu argumenty maj wartoci:
x = 1;

y = 1;

z = 1;

W kolejnym wywoaniu:
objetosc (10) ;

pierwszy argument jest okrelony i ma warto 10, pozostae s domniemane


wobec tego wartoci argumentw s nastpujce:
x = 10; y = 1;

z = 1;

1.8. Przecianie funkcji


Ostatnie wywoanie przekazuje argumenty nie uywajc wartoci domylnych.

1.8. Przecianie funkcji


Zamy, e w programie musimy oblicza kwadraty liczb. Denicja funkcji obliczajc kwadrat liczby cakowitej moe mie posta:
i n t kwadrat ( i n t x ) { return x x ; }

a wywoanie tej funkcji moe mie posta:


kwadrat ( 3 )

Funkcja kwadrat() moe obsuy jedynie liczby cakowite (zmienne typu


int). Gdy chcemy obliczy kwadrat liczby rzeczywistej (zmiennej typu double) w jzyku C musimy zdeniowa now funkcj. W jzyki C++ istnieje
specjalny mechanizm noszcy nazw przecianie funkcji, ktry pozwala na
legalne wywoania typu:
kwadrat ( 3 )
kwadrat ( 3 . 3 )

Moemy zdeniowa dwie wersje funkcji kwadrat() i w zalenoci od typu przesyanego argumentu (int lub double) C++ wybierze odpowiedni
wersj funkcji. Oczywicie wersji funkcji moe by wicej ni dwie. Przecianie funkcji jest proste; w naszym przypadku musimy tylko utworzy dwie
denicje funkcji. Program pokazany na wydruku 1.7. uywa przecionej
funkcji kwadrat() do obliczania kwadratu liczby typu int i typu double.
Funkcje przeciane s odrniane przez swoje sygnatury sygnatura jest
poczeniem nazwy funkcji i typw jej parametrw.
Listing 1.7. Przecianie funkcji
2

#include <i o s t r e a m . h>


#include <c o n i o . h>
i n t kwadrat ( i n t x ) { return x x ; }
double kwadrat ( double y ) { return y y ; }

10

12

i n t main ( )
{
c o u t << " kwadrat l i c z b y c a l k o w i t e j 3 = "
<< kwadrat ( 3 ) ;
c o u t << " \ nkwadrat l i c z b y r z e c z y w i s t e j 3 . 3 = "
<< kwadrat ( 3 . 3 ) ;

13

14

1. Specyczne elementy jzyka C++


getch () ;
return 0 ;

14

1.9. Wzorce funkcji


W jzyku C++ mamy jeszcze jeden mechanizm pozwalajcy na uycie
funkcji do operowania na rnych typach zmiennych s to wzorce funkcji. Programista pisze pojedyncz denicj funkcji w oparciu o typy argumentw w wywoywanej funkcji, kompilator C++ automatycznie generuje
oddzielne funkcje wzorcowe, aby poprawnie obsuy dany typ danych.
Program pokazany na wydruku 1.8. ilustruje uycie wzorca funkcji wieksza()
do okrelenia wikszej z dwch wartoci typu int, typu double i typu char.
W tworzonych szablonach (wzorcach) wszystkie denicje wzorcw funkcji
rozpoczynaj si od sowa kluczowego template, po ktrym nastpuje lista
formalnych typw parametrw do tego wzorca, ujta w nawiasy ostre. Kady parametr formalnego typu jest poprzedzony sowem kluczowym class.
Denicja funkcji wzorcowej uytej w naszym przykadzie ma posta:
template <c l a s s T>
T w i e k s z a ( T war1 , T war2 )
{
T war ;
war = ( war1 > war2 ) ? war1 : war2 ;
return war ;
}

Pokazany wzorzec funkcji deklaruje parametr typu formalnego T (nazwa jest


dowolna) jako typ danych do sprawdzania przez funkcj wieksza(). Podczas
kompilacji typ danych przekazywanych do funkcji wieksza() jest zastpowany przez konkretny typ za pomoc penej denicji wzorca, C++ tworzy
kompletn funkcj w celu okrelenia wikszej z dwch wartoci konkretnego typu. Nastpnie, nowo utworzona funkcja jest kompilowana. Realizacja
funkcji z danymi wartociami np. typu int ma posta:
i n t w i e k s z a ( i n t war1 , i n t war2 )
{
i n t war ;
war = ( war1 > war2 ) ? war1 : war2 ;
return war ;
}

15

1.9. Wzorce funkcji


Podczas realizacji, typ uoglniony ( w naszym przykadzie T) zostaje zastpiony konkretnym typem ( w naszym przykadzie int). Kady parametr
typu w denicji wzorca musi pojawi si na licie parametrw funkcji przynajmniej jeden raz.

Listing 1.8. Wzorce funkcji

10

12

14

16

18

20

22

24

26

28

#include <i o s t r e a m . h>


#include <c o n i o . h>
template <c l a s s T>
T w i e k s z a ( T war1 , T war2 )
{
T war ;
war = ( war1 > war2 ) ? war1 : war2 ;
return war ;
}
i n t main ( )
{
i n t x1 , y1 ;
c o u t << " \ npodaj dwie l i c z b y c a l k o w i t e : " ;
c i n >> x1 >> y1 ;
c o u t << " w i e k s z a t o : " << w i e k s z a ( x1 , y1 ) ;
double x2 , y2 ;
c o u t << " \ npodaj dwie l i c z b y r z e c z y w i s t e : " ;
c i n >> x2 >> y2 ;
c o u t << " w i e k s z a t o : " << w i e k s z a ( x2 , y2 ) ;
char x3 , y3 ;
c o u t << " \ npodaj dwa z n a k i : " ;
c i n >> x3 >> y3 ;
c o u t << " w i e k s z a t o : " << w i e k s z a ( x3 , y3 ) ;
getch () ;
return 0 ;
}

Po uruchomieniu programu mamy nastpujcy komunikat:


podaj dwie l i c z b y c a l k o w i t e : 1 5
wieksza to : 5
podaj dwie l i c z b y r z e c z y w i s t e : 1 . 1
wieksza to :
5.5
podaj dwa z n a k i : a z
wieksza to : z

5.5

Wzorce stay si integraln czci jzyka C++, s one elastyczne, a przy


tym bezpieczne pod wzgldem obsugi rnych typw.

16

1. Specyczne elementy jzyka C++

1.10. Funkcje inline


Kolejnym nowym elementem w C++ s funkcje typu inline (rozwijalne,
tzn. wplecione w kod programu). Funkcje rozwijalne mog znacznie przyspieszy wykonanie programu (ale w pewnych przypadkach wcale nie musz). Dla deniowanej funkcji kompilator tworzy w pamici osobny zestaw
instrukcji. Podczas wywoywania funkcji wykonywany jest skok do tego zestawu. Gdy funkcja koczy dziaanie, wykonanie wraca do instrukcji nastpnej po instrukcji wywoania funkcji.
Listing 1.9.
2

#include <i o s t r e a m . h>


#include <c o n i o . h>

i n l i n e i n t Kwadrat ( i n t ) ;

i n t main ( )
{
i n t kw ;
c o u t << " \ nPodaj l i c z b e : " ;
c i n >> kw ;
kw = Kwadrat (kw) ;
c o u t << "Kwadrat t e j l i c z b y t o " << kw ;
getch () ;
return 0 ;
}

10

12

14

16

18

20

i n t Kwadrat ( i n t kw )
{
return kw kw ;
}

Obsuga wywoania funkcji jest bardzo kosztowna w szczeglnoci potrzebny jest kod niezbdny do umieszczania wartoci na stosie, wywoania
funkcji, pobrania parametru ze stosu i zakoczenia dziaania funkcji. Jeeli
funkcja zostanie zadeklarowana ze sowem kluczowym inline, kompilator nie
tworzy prawdziwej funkcji tylko kopiuje kod z funkcji rozwijalnej bezporednio do kodu funkcji wywoujcej (w miejscu wywoania funkcji inline).Nie
jest wykonywany aden skok, program dziaa tak, jakby w tym miejscu byy instrukcje funkcji. Na wydruku 1.9 pokazano uycie funkcji typu inline.
Funkcja Kwadrat() jest deklarowana jako funkcja typu inline, otrzymujca
parametr typu int i zwracajca warto typu int. Program kompiluje si do
kodu, ktry ma posta tak, jakby w kadym miejscu wystpienia instrukcji:
kw = Kwadrat ( kw ) ;

1.11. Dynamiczne zarzdzanie pamici


znajdowaa si instrukcja:
kw = kw kw ;

Naley pamita, e gdy funkcja bdzie wywoywana w kilku miejscach,


jej kod bdzie kopiowany w tych miejscach. Moe to spowodowa znaczny
wzrost objtoci pliku wykonywalnego, co w efekcie moe spowolni wykonywanie programu a nie zwikszenie szybkoci jak mona by si spodziewa.
Naley zdawa sobie spraw, e sowo kluczowe inline jest w rzeczywistoci
yczeniem postawionym kompilatorowi a nie jego dyrektyw. Oznacza to,
e funkcja bdzie rozwinita, jeli bdzie to moliwe, gdy funkcji nie mona
rozwin tworzona jest zwyczajna wywoywana funkcja.

1.11. Dynamiczne zarzdzanie pamici


Do przechowywania danych program potrzebuje odpowiedniej iloci pamici. Przydzielanie pamici moe odbywa si automatycznie, gdy np. wystpi deklaracja:
char nazw [ ] =" Jan Kowalsk i " ;

Taka deklaracja powoduje zarezerwowanie obszaru pamici potrzebnego do


przechowywania tablicy znakw Jan Kowalski. Mona rwnie zarezerwowa okrelon ilo pamici, tak jak w tej deklaracji:
int l i c z b y [ 1 0 0 ] ;

Tego typu deklaracja powoduje zarezerwowanie dla tablicy liczby 100 jednostek pamici, z ktrych kada jest w stanie przechowywa warto typu
int. W jzyku C mona rezerwowa pami w trakcie dziaania programu.
Suy do tego celu funkcja malloc(), ktra pobiera tylko jeden argument
ilo potrzebnej pamici w bajtach. Znajduje ona odpowiedni obszar wolnej pamici i zwraca adres jego pierwszego bajtu. Rozpatrzmy nastpujce
instrukcje wykorzystujce malloc() do utworzenia tablicy:
double wsk ;
wsk = ( double ) m a l l o c ( 30 s i z e o f ( double ) ) ;

Powyszy kod rezerwuje pami dla trzydziestu wartoci typu double. Jak
pamitamy, nazwa tablicy jest adresem jej pierwszego elementu, std przypisanie wskanikowi wsk adresu pierwszej wartoci double sprawia, e mona
z niego korzysta tak jak ze zwykej nazwy tablicy wsk[]. Schemat postpowania jest nastpujcy:

17

18

1. Specyczne elementy jzyka C++


deklarujemy wskanik
wywoujemy funkcj malloc()
odwoujemy si do elementw tablicy za pomoc nazwy wskanika
Ta metoda pozwala na tworzenie tablic dynamicznych, czyli takich, ktrych rozmiar jest okrelany w trakcie dziaania programu. Tak moliwo
ilustruje program pokazany na wydruku 1.10.
Listing 1.10. Dynamicznie alokowana pami
2

10

12

14

16

18

20

22

24

26

28

#include <s t d i o . h>


#include < s t d l i b . h>
#include <c o n i o . h>
i n t main ( )
{
double wsk ;
i n t max , l i c z b a ;
int i = 0 ;
p u t s ( " Podaj i l o s c elementow : " ) ;
s c a n f ( "%d" , &max) ;
wsk = ( double ) m a l l o c ( max s i z e o f ( double ) ) ;
i f ( wsk ==NULL)
{
p u t s ( " Blad p r z y d z i a l u pamieci , k o n i e c " ) ;
e x i t (EXIT_FAILURE) ;
}
p u t s ( " Podaj elemnty t a b l i c y . q k onczy program " ) ;
while ( i < max && s c a n f ( "%l f " , &wsk [ i ] ) == 1 )
++i ;
p r i n t f ( " L i s t a %d elementow : \ n" , l i c z b a = i ) ;
f o r ( i =0; i <l i c z b a ; i ++)
{
p r i n t f ( " %7.2 f " , wsk [ i ] ) ;
i f ( i % 7 == 6 )
p u t c h a r ( \n ) ;
}
i f ( i % 7 != 0 )
p u t c h a r ( \n ) ;
p u t s ( " Koniec " ) ;
f r e e ( wsk ) ;
getch () ;
return 0 ;
}

Przebieg dziaania programu moe mie posta:


Podaj i l o s c elementow :
3
Podaj e l e m e n t y t a b l i c y . q k onczy program
20 30 50 50 70 q
l i s t a 3 elementow
20.00
30.00
50.00
Koniec

1.11. Dynamiczne zarzdzanie pamici


Wpisalimy 5 liczb, ale program zaakceptowa tylko 3 liczby. Spowodowane
jest to faktem, e rozmiar tablicy zosta ustalony jako 3. Pobranie rozmiaru
tablicy realizuj instrukcje:
p u t s ( " Podaj i l o s c elementow : " ) ;
s c a n f ( "%d" , &max ) ;

W instrukcji:
wsk = ( double ) m a l l o c (max s i z e o f ( double ) ) ;

rezerwujemy miejsce w pamici dla danej iloci elementw i przypisujemy


adres bloku pamici wskanikowi wsk. Gdy nie mona przydzieli pamici,
funkcja malloc() zwraca wskanik zerowy i program koczy dziaanie:
i f ( wsk ==NULL)
{
p u t s ( " Blad p r z y d z i a l u pamieci , k o n i e c " ) ;
e x i t (EXIT_FAILURE) ;
}

Funkcja free() zwalnia zarezerwowan pami. Funkcje malloc() i free() zarzdzaj razem pamici komputera. Dziki tablicom dynamicznym wykorzystujemy optymalnie pami. Jeeli wiemy, e program wikszo czasu
bdzie potrzebowa tylko 100 elementw a od czasu do czasu albo tylko
jeden raz bdzie potrzebowa 10000 elementw to naley stosowa tablice
dynamiczne. Bez moliwoci korzystania z tablic dynamicznych naleaoby utworzy tablic o rozmiarze 10000 elementw. Taka tablica cay czas
zajmowaaby niepotrzebnie pami. Jest to zwyke marnowanie pamici.
Poniewa dynamiczne przydzielanie pamici jest istotne dla tworzenia
wydajnych programw C++ oferuje dodatkowe, bardzo wygodne narzdzia
jakim s operatory new (przydzia) i delete (zwalnianie). Skadnia wyrae
z tymi operatorami ma posta:
new ty p_obiek tu
delete wsk aznik _obiek tu

Uycie operatora new ma dwie zalety w stosunku do funkcji malloc():


nie wymaga uycia operatora sizeof
operator new zwraca wskanik danego typu, nie ma potrzeby wykonywania konwersji typu wskanika
Instrukcja:
new unsigned short i n t

19

20

1. Specyczne elementy jzyka C++


alokuje na stercie dwa bajty( ta ilo zaley od konkretnego kompilatora), a zwracan wartoci jest adres pamici. Musi on zosta przypisany
do wskanika. Aby stworzy na stercie obiekt typu unsigned short, moemy
uy instrukcji:
unsigned short i n t wsk ;
wsk = new unsigned short i n t ;

Mona te zainicjalizowa wskanik w trakcie tworzenia:


unsigned short i n t wsk = new unsigned short i n t ;

Tak okrelony wskanik zachowuje si jak kady inny wskanik, wobec tego
moemy np. przypisa obiektowi na stercie dowoln warto:
wsk = 9 9 ;

Ta instrukcja oznacza: Umie 99 jako warto obiektu wskazywanego przez


wsk lub Przypisz obszarowi sterty wskazywanemu przez wskanik wsk
warto 99. Gdy operator new nie jest w stanie przydzieli pamici, zgasza
wyjtek. Gdy przydzielona pami nie jest ju potrzebna, naley j zwolni.
Do tego celu naley uy sowa kluczowego delete (ang. usu) z waciwym
wskanikiem. Instrukcja delete zwalnia pami zaalokowan na stercie, ta
pami moe by wykorzystana ponownie do innych zada. Naley pamita, e pami zaalokowana operatorem new nie jest zwalniana automatycznie, staje si niedostpna, tak sytuacj nazywamy wyciekiem pamici (ang.
memory leak). Pami moemy zwrci w nastpujcy sposb:
delete wsk ;

Najczciej operatora new uywa si do obsugi tablic. Dziesicioelementowa


tablica liczb cakowitych moe by utworzona i przypisana w nastpujcy
sposb:
i n t wsk = new i n t [ 10 ] ;

Zwolnienie tak przydzielonej pamici mona zrealizowa za pomoc instrukcji:


delete [ ] wsk ;

Jak wida obsuga dynamicznego przydziau pamici dziki operatorom jest


bardzo wygodna. Naley wiedzie, e operator delete zwalnia pami, na
ktr on wskazuje, wskanik nadal pozostaje wskanikiem. Ponowne wywoanie delete dla tego wskanika spowoduje zaamanie programu. Zaleca si,

1.11. Dynamiczne zarzdzanie pamici


aby podczas zwalniania wskanika ustawi go na zero (NULL). Kompilator gwarantuje, e wywoanie delete z pustym wskanikiem jest bezpieczne.
Program pokazany na wydruku 1.11 ilustruje zastosowanie operatorw new
i delete. Pami jest dwukrotnie przydzielona i dwukrotnie zwalniana.
Listing 1.11. Dynamiczny przydzia pamici. Operatory new i delete
1

#include <i o s t r e a m . h>


#include <c o n i o . h>

11

13

15

17

19

i n t main ( )
{
i n t zmienna = 5 5 ;
i n t zm = &zmienna ;
i n t s t e r t a = new i n t ;
sterta = 155;
c o u t << " zmienna : " <<
c o u t << " zm : " << zm
c o u t << " s t e r t a : " <<
delete s t e r t a ;
s t e r t a = new i n t ;
sterta = 111;
c o u t << " s t e r t a : " <<
delete s t e r t a ;
getch () ;
return 0 ;
}

zmienna << \n ;
<< \n ;
s t e r t a << \n ;

s t e r t a << \n ;

Po uruchomieniu programu mamy nastpujcy komunikat:


zmienna :
zm : 55
sterta :
sterta :

55
155
111

W instrukcjach
i n t zmienna = 5 5 ;
i n t zm = &zmienna ;

program deklaruje i inicjalizuje lokaln zmienn wartoci 55 oraz deklaruje


i inicjalizuje wskanik, przypisujc mu adres tej zmiennej. W instrukcji:
i n t s t e r t a = new i n t ;

deklarowany jest wskanik sterta inicjalizowany wartoci uzyskan w wyniku wywoania operatora new. Powoduje to zaalokowanie na stercie miejsca
dla wartoci typu int. W instrukcji:

21

22

1. Specyczne elementy jzyka C++


sterta = 155;

przypisano warto 155 do nowo zaalokowanej pamici.


Instrukcje:
c o u t << " zmienna : " << zmienna << \n ;
c o u t << " zm : " << zm << \n ;
c o u t << " s t e r t a : " << s t e r t a << \n ;

wypisuj odpowiednie wartoci. Wydruki wskazuj, e rzeczywicie mamy


dostp do zaalokowanej pamici. W kolejnej instrukcji:
delete s t e r t a ;

pami zaalokowana jest zwracana na stert. Czynno ta zwalnia pami


i odcza od niej wskanik. Teraz sterta moe wskazywa na inny obszar
pamici. Nowy adres i warto przypisana jest w kolejnych instrukcjach:
s t e r t a = new i n t ;
sterta = 111;

a w kocu wypisujemy wynik i zwalniamy pami:


c o u t << " s t e r t a : " << s t e r t a << \n ;
delete s t e r t a ;

Wspominalimy ju o zjawisku wycieku pamici. Taka sytuacja moe nastpi, gdy ponownie przypiszemy wskanikowi adres bez wczeniejszego
zwolnienia pamici, na ktr on wskazuje. Rozwamy fragment programu:
i n t wsk = new i n t ;
wsk = 1 1 1 ;
wsk = new i n t ; // e r r o r
wsk = 9 9 9 ;

Na pocztku tworzymy wskanik wsk i przypisujemy mu adres rezerwowanego na stercie obszaru pamici. W tym obszarze umieszczamy warto 111.
W trzeciej instrukcji przypisujemy wskanikowi wsk adres innego obszaru
pamici a czwarta instrukcja umieszcza w tym obszarze warto 999. Nie
ma sposobu na odzyskanie pierwszego obszaru pamici. Poprawna posta
tego kodu moe by nastpujca:
i n t wsk = new i n t ;
wsk = 1 1 1 ;
delete wsk ;
wsk = new i n t ;
wsk = 9 9 9 ;

23

1.12. Sowa kluczowe jzyka C++


Zaleca si, aby za kadym razem, gdy uyto sowa kluczowego new, powinno
si uy sowa kluczowego delete.

1.12. Sowa kluczowe jzyka C++


Obecnie (rok 2010, cytujemy B. Strostrupa) w standardzie jzyka C++
zdeniowano 74 sowa kluczowe. Sowa kluczowe s identykatorami wykorzystywanymi w programach do nazywania konstrukcji jzyka C++. List
sw kluczowych umieszczono tabeli 1.1.Pamietamy, e sowa kluczowe pisane s zawsze ma liter. Nie wolno ich uywa do innych celw ni do
nazywania konstrukcji jzyka.
Tabela 1.1. Sowa kluczowe jzyka C++

bool
class
default
else
false
if
namespace
or
register
sizeof
template
typedef
using
while

break
compl
delete
enum
oat
inline
new
or eq
reinterpret cast
static
this
typeid
virtual
xor

case
const
do
explicit
for
int
not
private
return
static cast
throw
typename
void
xor eq

catch
const cas
double
export
friend
long
not eq
protected
short
struct
true
union
volatile

char
continue
dynamic cast
extern
goto
mutable
operator
public
signed
switch
try
unsigned
wchar t

wracamy uwag, e w standardzie ANSI/ISO C++ do jzyka C++ dodano nowy typ bool. Zmienna typu bool (zmienna logiczna) moe przyjmowa jedn z dwch wartoci : true (prawda) lub false (fasz). Pierwotnie
jzyk C++ tak jak i jzyk C nie posiada typu logicznego. Jak pamitamy,
warto zero interpretowana bya jako fasz, kada warto rna od zera
bya interpretowana jako prawda. Posta instrukcji z typem bool moe mie
posta:
i n t cena = true ;
i n t odpowiedz = f a l s e ;

24

1. Specyczne elementy jzyka C++

1.13. Schemat programu w jzyku C++


Oglna posta programu w jzyku C++ nie rni si zbytnio od oglnego
schematu programu napisanego w jzyku C i zazwyczaj konstruowana jest
nastpujco:

nagwek
deklaracje klas bazowych
deklaracje klas pochodnych
prototypy funkcji oglnych
prototypy funkcji
ciao funkcji main()
denicje funkcji oglnych

Naley zauway, e z powodu dania kompatybilnoci wstecznej, jzyk


C++ jako nadzbir jzyka C musi zawiera biblioteki tego jzyka. Ponadto
rozwj jzyka C++ spowodowa stworzenie nowej biblioteki standardowej.
Prowadzi to do pewnego zamieszania poniewa w programach jzyka C++
moemy uywa jednoczenie a trzech rnych bibliotek. Stosujc rne
biblioteki musimy nadawa plikom nagwkowym rne nazwy. Stosowane
konwencje pokazane s w tabeli 1.2
Tabela 1.2. Konwencje nazw bibliotek jzyka C++

biblioteka
Jzyk C
Jzyk C++, stara wersja
Jzyk C++, nowa wersja

nazewnictwo
<nazwa pliku.h>
<nazwa pliku.h>
<nazwa pliku>

przykad
<stdio.h>
<iostream.h>
<iostream>

Jest jeszcze jedna istotna rnica, gdy chcemy stosowa now bibliotek jzyka C++ (zamiast np. pliku iostream.h, wczamy plik nagwkowy
iostream). Stosowanie nowej biblioteki wymaga udostpnienia dyrektywy
przestrzeni nazw, aby udostpni elementy np. strumienia iostream w programach:
using namespace s t d :

Dziki dyrektywie using wczamy standardow przestrze nazw i moemy


bez kopotu uywa zmiennych cout czy cin. Mona pomin dyrektyw
using, ale wtedy aby korzysta z elementw nowej biblioteki iostream musimy napisa wprost typ przestrzeni nazw, tak jak w nastpujcym przykadzie:
s t d : : c o u t << " k o l o s a l n e z a m i e s z a n i e " << s t d : : e n d l ;

1.13. Schemat programu w jzyku C++


Poniewa przy kadym wystpieniu cout, cin itp. naley pisa typ przestrzeni nazw, w praktyce zawsze uywamy dyrektywy using namespace std.
Poniej pokazujemy programy wykorzystujce star bibliotek oraz z
now
// Program C++, s t a r a b i b l i o t e k a
#i n l c u d e <i o s t r e a m . h>
i n t main ( )
{
return 0 ;
}

// Program C++, nowa b i b l i o t e k a


#i n l c u d e <i o s t r e a m >
using namespace s t d ;
i n t main ( )
{
return 0 ;
}

25

Rozdzia 2
Wprowadzenie do programowania
obiektowego

2.1.
2.2.
2.3.
2.4.
2.5.
2.6.
2.7.
2.8.

Wstp . . . . . . . . . . . . . .
Paradygmaty programowania .
Obiekty i klasy . . . . . . . . .
Hermetyzacja danych . . . . .
Dziedziczenie . . . . . . . . . .
Polimorzm . . . . . . . . . .
Podsumowanie terminologii . .
rodowisko programistyczne .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

28
28
30
34
35
36
37
38

28

2. Wprowadzenie do programowania obiektowego

2.1. Wstp
Ponad trzydzieci lat temu (w latach osiemdziesitych) Bjarne Stroustrup tworzy jzyk C++ jako ulepszona wersje jzyka C. Zasadnicza nowoci jzyka C++ byo:
eliminacja istotnych wad jzyka C
Wprowadzenie obsugi obiektw i moliwo
realizacji programowania obiektowego
Programowanie obiektowe byo nowoci w latach osiemdziesitych, stosunkowo niedawno powstay propozycje teoretyczne nowego paradygmatu
programowania i powsta pierwszy jzyk realizujcy ten paradygmat - jzyk
Smalltalk. W programowaniu obiektowym analizuje si zadanie do rozwizania a nastpnie opracowuje si reprezentacje tego problemu w postaci klasy.
Konstrukcja klasy (wywodzca si pierwotnie ze struktury jzyka C) umoliwia realizacje paradygmatu programowania obiektowego i jest centralnym
elementem programw obiektowych pisanych w jzyku C++.

2.2. Paradygmaty programowania


Mamy wiele metod programowania w jzyku C++, wydaje si jednak,
e decydujce znaczenie maj trzy paradygmaty programowania:
Programowanie proceduralne
Programowanie strukturalne
Programowanie zorientowane obiektowo
Powstanie i rozwj jzykw i stylw programowania ma swj pocztek w
pracach matematykw z pierwszej poowy XX wieku. Wprowadzono wtedy
pojcie funkcji obliczalnej, oznaczajce funkcj, ktrej warto dla dowolnych wskazanych argumentw mona obliczy w sposb efektywny (w skoczonym czasie). Program w jzyku proceduralnym jest zestawem instrukcji
deniujcych algorytm dziaania. Do grupy jzykw proceduralnych zaliczaj si: assemblery oraz jzyki wysokiego poziomu, takie jak Fortran, Basic,
Pascal, jzyk C. Kady algorytm skada si z opisu podmiotw oraz opisu
czynnoci, ktre maj by na tych podmiotach wykonane. W zapisanym w
jzyku proceduralnym programie opisom tym odpowiadaj, odpowiednio:
dane (struktury danych) i instrukcje.
Modykujc stylu programowania proceduralnego wypracowano koncepcj tzw. programowania strukturalnego. Zakada ona grupowanie fragmentw kodu w podprogramy (procedury i funkcje) i deniowanie zasad komunikowania si tych podprogramw midzy sob. Istotne zalety programowania strukturalnego polegay na poprawie czytelnoci programu i moliwoci tworzenia i uytkowania bibliotek podprogramw. Styl programowania

2.2. Paradygmaty programowania


strukturalnego doskonale nadawa si do pisania maych i rednio duych
programw przenonych, nie sprawdza si (by kosztowny i generowa duo
bdw) przy realizacji duych i bardzo zaawansowanych programw. Coraz
wiksze wymagania stawiane programistom spowodoway powstanie nowego
stylu programowania programowania obiektowego.
Na jego gruncie wyrosa szeroka klasa jzykw obiektowych: tak specjalizowanych, np. O++, jak i oglnego przeznaczenia, np. C++, Object
Pascal, Java. W programowaniu proceduralnym i strukturalnym program
jest traktowany jako seria procedur (funkcji) dziaajcych na danych. Dane
s cakowicie odseparowane od procedur, programista musi pamita, ktre
funkcje byy wywoane i jakie dane zostay zmienione. Przy realizacji duych programw, dominuje programowanie obiektowe. Programowanie zorientowane obiektowo dostarcza technik zarzdzania zoonymi elementami,
umoliwia ponowne wykorzystanie komponentw i czy w logiczn cao
dane oraz manipulujce nimi funkcje. Zadaniem programowania zorientowanego obiektowo jest modelowanie obiektw a nie danych. Jzyk C++
zosta stworzony dla programowania zorientowanego obiektowo. Zasadniczymi elementami stylu programowania obiektowego s:
klasy (abstrakcyjne typy danych),
hermetyzacja danych (kapsukowanie),
dziedziczenie,
polimorzm.
Naley pamita, e jzyk C++ nie jest czystym jzykiem programowania obiektowego. Swoj popularno jzyk C++ zawdzicza przede wszystkim zgodnoci z jzykiem C. W jzyku C++ mona programowa obiektowo, ale take i strukturalnie. Jednym z najlepszych jzykw zorientowanych
obiektowo jest Smalltalk, rozwinity we wczesnych latach 70-tych w Palo
Alto Research Center amerykaskiej rmy Xerox. Smalltalk jest idealnie
zaprojektowanym obiektowo jzykiem dosownie wszystko jest obiektem.
Niestety jzyk Smalltalk nie zdoby takiej popularnoci, jak ma jzyk C++.
Jzyk C++ jest jzykiem hybrydowym umoliwia programowanie albo w
stylu strukturalnym (podobnym do C) albo w stylu zorientowanym obiektowo. Decyzja o wykorzystaniu jzyka C++ jako jzyka programowania obiektowego naley do programisty. Bardzo trudno jest zdeniowa precyzyjnie
pojcie jzyk obiektowy, a take ustali czy dany jest jzykiem zorientowanym obiektowo czy nie jest. Bruce Eckel w podrczniku Thinking in
C++ przytoczy pi podstawowych cech jzyka Smalltalk (by to jeden z
pierwszych jzykw obiektowych).
1. Wszystko jest obiektem. Obiekt jest szczeglnym rodzajem zmiennej,
przechowuje ona dane, ale moe take wykona operacje na sobie samej
2. Program jest grup obiektw, przekazujcych sobie wzajemnie informacje o tym, co naley zrobi za pomoc komunikatw. Komunikat moe

29

30

2. Wprowadzenie do programowania obiektowego


by traktowany jako danie wywoania funkcji nalecej do tego obiektu.
3. Kady obiekt posiada wasn pami, zoon z innych obiektw. Oznacza to, e nowy rodzaj obiektu jest tworzony przez utworzenie pakietu
zoonego z ju istniejcych obiektw.
4. Kady obiekt posiada typ. Mwimy, e kady obiekt jest egzemplarzem
jakiej klasy. Klasa jest synonimem sowa typ.
5. Wszystkie obiekty okrelonego typu mog odbiera te same komunikaty.
Jzyk C++ ma wszystkie cechy, ktre dla wielu specjalistw s charakterystyczne dla jzyka programowania obiektowego.
Wikszo zrealizowanych duych komercyjnych projektw oprogramowania albo dziaa zawodnie, albo nie tak jak powinno. S one zbyt drogie
i za mao niezawodne. Jest to rezultatem ogromnej zoonoci programw,
ktre s obecnie tworzone. Ocenia si, e prom kosmiczny NASA, samolot
Boeing 747 i system operacyjny Windows s w historii czowieka najbardziej
skomplikowanymi wynalazkami. Widzimy, e nowoczesne programy zaliczane s do najbardziej skomplikowanych rzeczy, jakie wymyli czowiek. Wielu specjalistw widzi w programowaniu obiektowym nadziej na tworzenie
produktw, ktre bd dziaa tak jak powinny.

2.3. Obiekty i klasy


Kluczowe znaczenie w technologii programowania obiektowego maj obiekty i klasy. Wydaje si, e czowiek postrzega wiat jako zbir obiektw.
Ogldajc obraz na ekranie monitora, widzimy np. raczej twarz aktora ni
zbir kolorowych pikseli. Aktor jest obiektem, ktry ma odpowiednie cechy i moe wykonywa rnorodne akcje. Innym przykadem obiektu jest
np. samochd. Rwnie samochd ma odpowiednie cechy i moe wykonywa rne dziaania. Otaczajca nas rzeczywisto zoona jest z obiektw.
Obiekty maj atrybuty takie jak np. wielko, kolor, ksztat. Obiekty mog
dziaa, np. aktor biegnie samochd hamuje, itp. Obserwujemy i rozumiemy
wiat badajc cechy obiektw i ich zachowanie. Rne obiekty mog mie
podobne atrybuty i podobne dziaania. Np. aktor ma wag, tak samo jak
samochd, aktor moe si porusza z odpowiedni prdkoci, podobnie
samochd. Obiekty takie jak czowiek czy samochd w istocie s bardzo
skomplikowanymi i zoonymi obiektami, jest prawie niemoliwe dokadne
ich opisanie. Abstrakcja umoliwia nam zredukowanie zoonoci problemu.
Waciwoci (cechy) i procesy (dziaania, akcje) zostaj zredukowane do
niezbdnych cech i akcji zgodnie z celami, jakie chcemy osign. Inaczej
bdzie przeprowadzona redukcja cech i dziaa samochodu, gdy chcemy go
sprzedawa (istotny wtedy jest typ samochodu, producent, kolor, cena, moc

2.3. Obiekty i klasy


silnika, itp.) a inaczej bdzie wyglda proces abstrakcji, gdy zechcemy modelowa opr powietrza samochodu ( istotne s ksztaty, rozmiary np. lusterek bocznych, kt pochylenia szyby przedniej, itp.). Dziki abstrakcji uzyskujemy moliwo rozsdnego opisania wybranych obiektw i zarzdzania
zoonymi systemami. Rne obiekty mog mie podobne atrybuty. Obiekty takie jak samochd, samolot i okrt maj wiele wsplnego (maj wag,
przewo pasaerw, bilet na dany pojazd ma cen, itp.). Obiekt nie musi
reprezentowa realnie istniejcego bytu. Moe by cakowicie abstrakcyjny
lub reprezentowa jaki proces. Obiekt moe reprezentowa rozdawanie kart
do pokera lub mecz piki nonej. W tym ostatnim przykadzie atrybutami
obiektu mog by nazwy druyn, ilo strzelonych goli, nazwiska trenerw,
itp.
W jzyku C/C++ istnieje typ danych zwany struktur, ktry umoliwia
czenie rnych typw danych. Wprowadzenie do jzykw programowania
struktury, byo milowym krokiem w rozwoju jzykw programowania, poniewa umoliwio posugiwanie si zestawami rnych cech przy pomocy
jednego typu danych. Struktura stanowi jeden ze sposobw zastosowania
abstrakcji w programach. Np. struktura pracownik moe mie posta:
struct pracownik {
char nazwisk o [MAXN] ;
char i m i e {MAXI}
int rok_urodzenia ;
};

Deklaracja ta opisuje struktur zoon z dwch tablic i jednej zmiennej typu int. Nie tworzy ona rzeczywistego obiektu w pamici, a jedynie okrela,
z czego skada si taki obiekt. Opcjonalna etykieta (znacznik, ang. structure tag) pracownik jest nazw przyporzdkowan strukturze. Ta nazwa jest
wykorzystywana przy deklaracji zmiennej:
struct pracownik k i e r o w n i k ;

Deklaracja ta stwierdza, e kierownik jest zmienn strukturaln o budowie pracownik. Nazwy zdeniowane wewntrz nawiasw klamrowych denicji struktury s skadowymi struktury (elementami struktury, polami
struktury). Denicja struktury pracownik zawiera trzy skadowe, dwie typu
char nazwisko i imie oraz jedna typu int rok urodzenia. Dostp do skadowych struktur jest moliwy dziki operatorom dostpu do skadowych.
Mamy dwa takie operatory: operator kropki (.) oraz operator strzaki (->).
Za pomoc operatora kropki moliwy jest dostp do skadowych struktury przez nazw lub referencj do obiektu. Np. aby wydrukowa skadow
rok urodzenia moemy posuy si wyraeniem:

31

32

2. Wprowadzenie do programowania obiektowego


c o u t << k i e r o w n i k . r o k _ u r o d z e n i a ;

Skadowe tej samej struktury musz mie rne nazwy, jednak dwie rne
struktury mog zawiera skadowe o tej samej nazwie. Skadowe struktury
mog by dowolnego typu. Struktury danych umoliwiaj przechowywanie
cech (waciwoci, atrybutw) obiektw. Ale jak wiemy z obiektem zwizane
s rwnie najrniejsze dziaania. Dziaania te tworz interfejs obiektu. W
jzyku C nie ma moliwoci umieszczenia w strukturze atrybutw obiektu i
operacji, jakich mona na obiekcie wykona. W jzyku C++ wprowadzono
nowy abstrakcyjny typ danych, ktry umoliwia przechowywanie atrybutw
i operacji. Tworzenie abstrakcyjnych typw danych odbywa si w jzyku
C++ (tak samo jak w innych jzykach programowania obiektowego) za pomoc klas. Klasa stanowi implementacj abstrakcyjnego typu danych.
Na kady projektowany obiekt skadaj si dane (atrybuty) i dobrze
okrelone operacje (dziaania). Do danych nie mona dotrze bezporednio,
naley do tego celu wywoa odpowiedni metod. Dziki temu chronimy
dane przed niepowoanym dostpem. Komunikacja uytkownika z obiektem
(a take komunikacja wybranego obiektu z innym obiektem) zaczyna si
od wysania do niego odpowiedniego dania (ang. request). Po odebraniu dania (inaczej komunikatu) obiekt reaguje wywoaniem odpowiedniej
metody lub wysya komunikat, e nie moe dania obsuy.
Klasa opisuje obiekt. Z formalnego punktu widzenia klasa stanowi typ.
W programie moemy tworzy zmienne typu okrelonego przez klas. Zmienne te nazywamy instancjami (wcieleniami). Instancje stanowi realizacj
obiektw opisanych przez klas.
Jak ju wiemy, operacje wykonywane na obiektach nosz nazw metod.
Aby wykona konkretn operacj, obiekt musi otrzyma komunikat, ktry przetwarzany jest przez odpowiedni metod. Cech charakterystyczn
programw obiektowych jest przesyanie komunikatw pomidzy obiektami.
Poniewa jzyk C++ jest w istocie jzykiem hybrydowym, panuje pewne
zamieszanie w stosowanej terminologii. Metody nosz nazw funkcji (procedury i podprogramy w jzyku C++ nosz te nazwy funkcji), a instancje
nazywana s obiektami konkretnymi. W specykacji jzyka C++ pojcie
instancji nie jest w ogle uywane. Dla okrelania instancji klasy uywa si
po prostu terminu obiekt. Rwnie istnieje dua rnorodno, jeeli chodzi
o terminologi stosowan w opisie elementw klasy. Mamy takie terminy
jak: elementy, skadowe, atrybuty, dane. Operacje nazywane s metodami,
funkcjami skadowymi lub po prostu funkcjami.
W jzyku C++ klasa jest struktur, ktrej skadowe mog take by
funkcjami (metodami).
Deklaracja klasy precyzuje, jakie dane i funkcje publiczne s z ni zwi-

33

2.3. Obiekty i klasy


zane, czyli do jakich danych ma dostp uytkownik klasy. Deklaracja klasy
punkt moe mie posta:
c l a s s punkt
{
private :
int x ;
int y ;
public :
void i n i t ( int , i n t ) ;
void p r z e s u n ( int , i n t )
};

Do deklarowania klasy suy sowo kluczowe class. Po nim podajemy


nazw tworzonej klasy, a nastpnie w nawiasach klamrowych umieszczamy
zmienne wewntrzne (dane) i metody (funkcje skadowe). Deklaracj koczy si rednikiem. W tym przykadzie klasa punkt zawiera dwie prywatne
dane skadowe x i y oraz dwie publiczne funkcje skadowe init() i przesun().
Deklaracja tej klasy nie rezerwuje pamici na ni. Mwi ona kompilatorowi,
co to jest punkt, jakie dane zawiera i co moe wykona. Obiekt nowego typu
deniuje si tak, jak kad inn zmienn, np. typu int:
i n t r a d i a n ; // d e f i n i c j a i n t
punkt s r o d e k ;
// d e f i n i c j a punktu

W tym przykadzie deniujemy zmienn o nazwie radian typu int oraz


srodek , ktry jest typu punkt. Denicja klasy skada si z denicji wszystkich funkcji skadowych. Deniujc funkcje skadowe podajemy nazw klasy
bazowej przy uyciu operatora zakresu(::). Denicja funkcji skadowej init()
moe mie posta:
void punkt : :
{
x = xp ;
y = yp ;
}

i n i t ( i n t xp , i n i t yp )

W tej denicji x i y reprezentuj dane skadowe x i y obiektu klasy punkt.


Aby skorzysta z klasy punkt, powinnimy zadeklarowa obiekty klasy punkt:
punkt p1 , p2 ;

Zostay utworzone dwa obiekty klasy punkt. Dostp do publicznej funkcji


skadowej init() uzyskujemy przy pomocy operatora kropki:
p1 . i n i t ( 1 0 , 1 0 ) ;

34

2. Wprowadzenie do programowania obiektowego


Zostaa wywoana publiczna funkcja skadowa init() klasy, do ktrej naley
obiekt p1, to znaczy do klasy punkt.

2.4. Hermetyzacja danych


Hermetyzacja (ang. encapsulation) oznacza poczenie danych i instrukcji programu w jednostk programow, jakim jest obiekt. Hermetyzacja
obejmuje interfejs i denicj klasy. Podstawow zalet hermetyzacji jest
moliwo zabezpieczenia danych przed rwnoczesnym dostpem ze strony rnych fragmentw kodu programowego. W tym celu wszystkie dane
(pola w obiekcie, atrybuty) i zapisy instrukcji (metody w obiekcie, funkcje
skadowe) dzieli si na oglnodostpne (interfejs obiektowy) i wewntrzne
(implementacja obiektu). Dostp do pl i metod wewntrznych jest moliwy
tylko za porednictwem cza obiektowego - pl i metod oglnodostpnych. Wybrane pola i metody mona wic ukry przed okrelonymi (w tym
- wszystkimi) obiektami zewntrznymi. Hermetyzacja umoliwia separacj
interfejsu od implementacji klasy. Jak pamitamy, w klasycznej strukturze
danych w jzyku C mamy swobodny dostp do skadowych struktury. Oznacza to, e z dowolnego miejsca w programie moemy dokona zmiany tych
danych. Nie jest to dobra cecha. Zastosowanie takiego modelu dostpu do
danych obiektu grozi wystpieniem niespjnoci danych, co prowadzi zwykle do generacji bdw. W jzyku C++ rozwizano problem panowania
nad dostpem do danych przy pomocy hermetyzacji danych. W literaturze
przedmiotu spotkamy si z zamiennie stosowanymi terminami takimi jak:
ukrywanie danych, kapsukowanie czy zgoa cakiem egzotycznym terminem
enkapsulacja.
W celu uniemoliwienia programistom wykonywania niekontrolowanych
operacji na danych obiektu silnie ograniczono dostp do jego skadowych
za pomoc dobrze zdeniowanego interfejsu. Programista moe na obiekcie
wykona tylko te operacja, na ktre pozwoli mu projektant klasy. W jzyku
C++ dostp do skadowych klasy kontrolowany jest przez trzy specykatory:
private
public
protected
Skadowe zadeklarowane po sowie kluczowym private nie s dostpne
dla programisty aplikacji. Posiada on dostp do skadowych oraz moe wywoywa funkcje skadowe zadeklarowane po sowie kluczowym public. Dostp do atrybutw obiektu z reguy jest moliwy za porednictwem funkcji.
Jeeli deniujc klas pominiemy specykator dostpu, to adna skadowa klasy nie bdzie dostpna na zewntrz obiektu, domylnie przyjmowa-

2.5. Dziedziczenie
ny jest sposb dostpu okrelony specykatorem private. Hermetyzacja ma
ogromne znaczenie dla przenonoci programw i optymalizowania nakadw potrzebnych na ich modykacje. Wpywa take dodatnio na osiganie
niezawodnoci w projektach programistycznych.

2.5. Dziedziczenie
Jedna z najistotniejszych cech programowania zorientowanego obiektowo jest dziedziczenie (ang. inheritance). Mechanizm dziedziczenia suy w
jzykach obiektowych do odwzorowania wystpujcych czsto w naturze powiza typu generalizacja - specjalizacja. Umoliwia programicie deniowanie potomkw istniejcych obiektw. Kady potomek dziedziczy przy tym
(wszystkie lub wybrane) pola i metody obiektu bazowego, lecz dodatkowo
uzyskuje pewne pola i wasnoci unikatowe, nadajce mu nowy charakter.
Typ takiego obiektu potomnego moe sta si z kolei typem bazowym do
zdeniowania kolejnego typu potomnego. Bjarne Stroustrup w podrczniku
Jzyk C++ rozwaajc zalenoci wystpujce pomidzy klas gura i
klas okrg, tak zdeniowa paradygmat programowania obiektowego:
zdecyduj, jakie chcesz mie klasy;
dla kadej klasy dostarcz peny zbir operacji;
korzystajc z mechanizmu dziedziczenia, jawnie wska to, co jest wsplne
Typowe aplikacje operuj na wielu obiektach, programici zmuszeni s
do projektowania wielu klas, ktre powstaj dziki duemu nakadowi czasu
i kosztw. Stosunkowo szybko zorientowano si, e nie zawsze trzeba projektowa klas od pocztku, mona wykorzysta istniejce ju inne, przetestowane i sprawdzone klasy. W praktyce okazao si take, e wiele klas
ma zazwyczaj kilka wsplnych cech. Naturalnym jest wic denie, aby
analizujc podobne klasy wyodrbni wszystkie wsplne cechy i stworzy
uoglniona klas, ktra bdzie zwiera tylko te atrybuty i metody, ktre
s wsplne dla rozwaanych klas. W jzykach obiektowych tak uoglnion
klas nazywamy superklas lub klas rodzicielsk, a kada z analizowanych
klas nazywa si podklas lub klas potomn.
W jzyku C++ wprowadzono koncepcje klasy bazowej (ang. base class)
i klasy pochodnej (ang. derived class). Klasa bazowa (klasa podstawowa)
zawiera tylko te elementy skadowe, ktre s wsplne dla wyprowadzonych
z niej klas pochodnych. Podczas tworzenia nowej klasy, zamiast pisania cakowicie nowych danych oraz metod, programista moe okreli, e nowa klas
odziedziczy je z pewnej, uprzednio zdeniowanej klasy podstawowej. Kada
taka klasa moe w przyszoci sta si klas podstawow. W przypadku
dziedziczenia jednokrotnego klasa tworzona jest na podstawie jednej klasy
podstawowej. W sytuacji, gdy nowa klasa tworzona jest w oparciu o wiele

35

36

2. Wprowadzenie do programowania obiektowego


klas podstawowych mwimy o dziedziczeniu wielokrotnym. W jzyku C++
projektujc now klas pochodn mamy nastpujce moliwoci:
w klasie pochodnej mona dodawa nowe zmienne i metody
w klasie pochodnej mona redeniowa metody klasy bazowej
W jzyku C++ moliwe s trzy rodzaje dziedziczenia: publiczne, chronione oraz prywatne. Klasy pochodne nie maj dostpu do tych skadowych
klasy podstawowej, ktre zostay zadeklarowane jako private. Oczywicie
klasa pochodna moe si posugiwa tymi skadowymi, ktre zostay zadeklarowane jako public lub protected. Jeeli chcemy aby jakie skadowe
klasy podstawowej byy niedostpne dla klasy pochodnej, to deklarujemy je
jako private. Z takich prywatnych danych, klasa pochodna moe korzysta
wycznie za pomoc funkcji dostpu znajdujcych si w publicznym oraz
zabezpieczonym interfejsie klasy podstawowej.
Mechanizm dziedziczenia obiektw ma znaczenie dla optymalizowania
nakadw pracy potrzebnych na powstanie programu (optymalizacja kodu,
moliwo zrwnoleglenia prac nad fragmentami kodu programowego) i jego
pniejsze modykacje (przeprowadzenie zmian w klasie obiektw wymaga
przeprogramowania samego obiektu bazowego).

2.6. Polimorzm
Drug istotn cech (obok dziedziczenia) programowania zorientowanego obiektowo jest polimorzm (ang. polimorphism). Sowo polimorzm
oznacza dosownie wiele form. Polimorzm, stanowicy uzupenienie dziedziczenia sprawia, e moliwe jest pisanie kodu, ktry w przyszoci bdzie
wykorzystywany w warunkach nie dajcych si jeszcze przewidzie. Mechanizm polimorzmu wykorzystuje si te do realizacji pewnych metod w trybie nakazowym, abstrahujcym od szczegowego typu obiektu. Zachowanie
polimorczne obiektu zaley od jego pozycji w hierarchii dziedziczenia. Jeli dwa lub wicej obiektw maj ten sam interfejs, ale zachowuj si w
odmienny sposb, s polimorczne. Jest to bardzo istotna cecha jzykw
obiektowych, gdy pozwala na zrnicowanie dziaania tej samej funkcji w
zalenoci od rodzaju obiektu. Zagadnienie polimorzmu jest trudne pojciowo, w klasycznych podrcznikach programowania jest omawiane zazwyczaj razem z funkcjami wirtualnymi (ang. virtual function). Przy zastosowaniu funkcji wirtualnych i polimorzmu moliwe jest zaprojektowanie
aplikacji, ktra moe by w przyszoci prosto rozbudowywana. Jako przykad rozwamy zbir klas modelujcych gury geometryczne takie jak koo,
trjkt, prostokt, kwadrat, itp. Wszystkie s klasami pochodnymi klasy
bazowej gura. Kada z klas pochodnych ma moliwo narysowania siebie,
dziki metodzie rysuj. Poniewa mamy rne gury, funkcja jest inna w

2.7. Podsumowanie terminologii


kadej klasie pochodnej. Z drugiej strony, poniewa mamy klas bazow
gura, to dobrze by byo, aby niezalenie, jak gur rysujemy, powinnimy
mie moliwo wywoania metody rysuj klasy bazowej gura i pozwoli
programowi dynamicznie okreli, ktr z funkcji rysuj z klas pochodnych
ma zastosowa. Jzyk C++ dostarcza narzdzi umoliwiajcych stosowanie
takiej koncepcji. W tym celu naley zadeklarowa metod rysuj w klasie
bazowej, jako funkcj wirtualn. Funkcja wirtualna moe mie nastpujc
deklaracj:
v i r t u a l void r y s u j ( ) const ;

i powinna by umieszczona w klasie bazowej gura. Powyszy prototyp deklaruje funkcj rysuj jako sta, nie zawierajc argumentw, nie zwracajc
adnej wartoci i wirtualn. Jeeli funkcja rysuj zostaa zadeklarowana w
klasie bazowej jako wirtualna, to gdy nastpnie zastosujemy wskanik w
klasie bazowej lub referencj do obiektu w klasie pochodnej i wywoamy
funkcje rysuj stosujc ten wskanik to program powinien wybra dynamicznie waciw funkcje rysuj.
Polimorzm umoliwia tworzenie w typach potomnych tzw. metod wirtualnych, nazywajcych si identycznie jak w typach bazowych, lecz rnicych si od swych odpowiednikw pod wzgldem znaczeniowym.

2.7. Podsumowanie terminologii


W powyszych rozwaaniach wprowadzilimy duo nowych terminw
zwizanych z programowaniem zorientowanym obiektowo. Wydaje si celowe sporzdzenie krtkiego spisu nowych terminw z krtkimi objanieniami.
Atrybuty. S to dane klasy, inaczej skadowe klasy, ktre opisuj biecy
stan obiektu. Atrybuty powinny by ukryte przed uytkownikami obiektu, a dostp do nich powinien by okrelony i zdeniowany w interfejsie.
Obiekt. Obiekt jest bytem istnieje i moe by opisany. Obiekt charakteryzuj atrybuty i operacje. Obiekt jest instancj klasy (egzemplarzem
klasy).
Klasa. Klasa jest formalnie w jzyku C++ typem. Okrela ona cechy i
zachowanie obiektu. Klasa jest jedynie opisem obiektu. Naley traktowa
klas jako szablon do tworzenia obiektw.
Dziedziczenie. Dziedziczenie jest rodzajem relacji pomidzy klasami. Klasy bardziej wyspecjalizowane dziedzicz po klasach oglniejszych.
Hermetyzacja. Hermetyzacja obejmuje interfejs i abstrakcj klasy, Hermetyzacja polega na ukrywaniu danych i cisej kontroli dostpu do pl
i metod.

37

38

2. Wprowadzenie do programowania obiektowego


Polimorzm. Dosownie oznacza wiele form. Dwa obiekty s polimorczne, jeeli maj ten sam interfejs, ale zachowuj si w odmienny sposb.
Interfejs. Jest to widoczna funkcjonalno klasy. Interfejs tworzy pomost
pomidzy obiektem i uytkownikiem obiektu. Uytkownicy posuguj si
obiektami poprzez interfejs.
Implementacja. Jest to wewntrzna funkcjonalno i atrybuty klasy. Implementacja klasy jest ukryta przed uytkownikiem klasy. Uytkownicy
operuj obiektami, poprzez interfejs, nie musz wiedzie jak obiekt jest
implementowany.

2.8. rodowisko programistyczne


Podczas nauki podstaw jzyka C++ bardzo czsto ilustruje si elementy jzyka na przykadzie prostych programw. Aby programowa w jzyku
C++ potrzebny jest edytor, kompilator i konsolidator. Kompilator kupuje
si wraz z konsolidatorem i zbiorem funkcji bibliotecznych. Istnieje wiele
wersji kompilatorw jzyka C++. Niektre kompilatory jzyka C++ sprzedawane s razem z tzw. zintegrowanym rodowiskiem do tworzenia aplikacji
(ang. integrated development environment - IDE). Jest to bardzo wygodne
narzdzie. Przykadem takiego kompilatora jest pakiet Borland C++ (niestety rma Borland zawiesia dziaalno). Bardzo wiele programw uruchamianych jest w rodowisku Windows. Pisanie przyjaznych dla uytkownika programw (takie programy charakteryzuj si du iloci okienek
i ikon) jest zagadnieniem do skomplikowanym i uciliwym technicznie.
Aby usprawni proces pisania programw dla rodowiska Windows powstay
systemy byskawicznego projektowania aplikacji (ang. RAD Rapid Application Development).
Centralne miejsce w RAD zajmuje edytor kodu. W okienku edytora
wygenerowany jest szkielet programu, moemy go przystosowa do naszych
celw. W szkielecie znajduj si zazwyczaj specyczne dla danego RAD
dyrektywy (tak jak pokazano na przykadzie z RAD Borland Bulider 6)
//
#include <v c l . h>
#pragma h d r s t o p
//
#pragma a r g s u s e d
i n t main ( i n t argc , char argv [ ] )
{
return 0 ;
}
//

2.8. rodowisko programistyczne


Jest to poprawny program komputerowy, ktry nie robi nic. W programie
mamy trzy dyrektywy preprocesora.
Dyrektywa:
#include <v c l . h>

nakazuje doczenie pliku nagwkowego vcl.h. Przy pomocy dyrektywy


pragma:
#pragma h d r s t o p
#pragma a r g s u s e d

przekazujemy kompilatorowi dodatkowe informacje. Pierwsza (ang. header


stop) mwi, e wanie nastpi koniec listy plikw nagwkowych. Uycie
drugiej (ang. argument used) zapobiega wywietleniu ostrzeenia, e argument funkcji main() nie zosta uyty. Majc tak przygotowane rodowisko
moemy przystpi to napisania pierwszego programu. Chcemy aby na ekranie monitora by wywietlony napis Zaczynamy programowanie w C++
Builder. Aby wywietli taki komunikat musimy napisa instrukcj:
c o u t << "Zaczynamy programowanie wC++ B u i l d e r " ;

Wywietlaniem napisw na ekranie zajmuje si klasa iostream. Klasa iostream obsuguje podstawowe operacje wejcia/wyjcia wykorzystujc mechanizm strumieni (ang. stream). Do wysyania danych do standardowego wyjcia (ekran) suy klasa cout, do pobierania danych wejciowych ze standardowego wejcia (klawiatura) suy klasa cin. Do obsugi strumieni potrzebne
s dwa operatory: wstawiania i pobierania . Naley zaznaczy, e uycie strumienia cout ma sens tylko w aplikacjach tekstowych. W aplikacjach
gracznych do wyprowadzania informacji potrzebne s inne mechanizmy.
Gdy odwoujemy si do strumienia cout, naley wczy plik nagwkowy
<iostream.h>. Wykorzystujc edytor tekstowy piszemy tekst naszego programu. Zazwyczaj usuwamy zbdne dyrektywy sugerowane przez RAD oraz
zbdne argumenty. Program po poprawkach przedstawiony jest na wydruku
2.1.
Listing 2.1. Pierwszy program testowy
1

#include <i o s t r e a m . h>


#include <c o n i o . h>

i n t main ( )
{
c o u t << " Zaczynamy programowanie wC++ B u i l d e r " ;
getch () ;
return 0 ;

39

40

2. Wprowadzenie do programowania obiektowego


9

Po napisaniu tekstu programu moemy go wykona. W systemach RAD


mamy do dyspozycji odpowiednia opcje menu (obsuga myszk) lub skrt
klawiszowy). Wystpujca w programie instrukcja
getch () ;

realizuje tzw. przytrzymanie ekranu. Bez tej instrukcji (albo innej np.
PAUSE) w wikszoci RAD nie jestemy w stanie zobaczy wyniku dziaania programu. Funkcja getch() odczytuje nacinicie pojedynczego klawisza.
Dopki nie naciniemy dowolnego klawisza, program jest wstrzymany, dziki
temu moemy oglda wynik dziaania programu na ekranie.
Tak napisany program powinien by zapisany na dysku w wybranym katalogu. Podczas zapisywania projektu RAD tworzy rne pliki. Plik projektu ( ang. project le) zawieraj informacje potrzebne do generowania kodu
wynikowego. Plik rdowy (ang. source le) o rozszerzeniu zazwyczaj .cpp
zawiera tekst rdowy programu. Widzimy, e tworzenie aplikacji w jzyku C++ tworzy si w zintegrowanym rodowisku programistycznym (IDE)
stosunkowo atwo. Programowanie w takim rodowisku ma wiele zalet:
rodowisko generuje szkielet programu
Zazwyczaj wbudowany edytor podwietla sowa kluczowe,
Zazwyczaj kompilacja i uruchomienie programu realizowane s w jednym
kroku
rodowisko ma wbudowany debuger, wykrywanie prostych bdw jest
efektywne
W Internecie mona znale wiele bezpatnych rodowisk programistycznych. Wybr rodowiska jest kwesti gustu. Jeeli wemiemy pod uwag
wielko pamici potrzebnej do zainstalowania takiego rodowiska to rekomendujemy doskonae rodowisko Dev-C++ przeznaczone dla systemu
operacyjnego Windows. Dev-C++ jest tzw. oprogramowaniem otwartym
(open-source). Jeeli chcemy pracowa z tym rodowiskiem, moemy je pobra nieodpatnie z witryny Bloodshed Software (www.bloodshed.net).

Rozdzia 3
Klasy i obiekty

3.1.
3.2.
3.3.
3.4.
3.5.
3.6.
3.7.

Wstp . . . . . . . . . . . . . . . . . . . .
Deklaracja i denicja klasy . . . . . . . .
Wystpienie klasy (deniowanie obiektu)
Dostp do elementw klasy . . . . . . . .
Metody klasy . . . . . . . . . . . . . . . .
Klasa z akcesorami . . . . . . . . . . . .
Funkcje skadowe const . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

42
42
43
43
47
50
53

42

3. Klasy i obiekty

3.1. Wstp
Zasadnicz cech jzyka C++ jest moliwo uywania klas (ang. class).
Niezbyt precyzyjnie mwic, klasa jest bardzo podobna do typu strukturalnego znanego z jzyka C. Zasadnicza rnica midzy klas a struktur
polega na fakcie, e struktura przechowuje jedynie dane, natomiast klasa
przechowuje zarwno dane jak i funkcje. Klasa jest typem deniowanym
przez uytkownika. Gdy deklarowane s zmienne tego typu, s one obiektami. Mwic krtko: w jzyku C++ klasy s formalnymi typami, obiekty
s specycznymi zmiennymi tego typu. Klasa jest waciwie zbiorem zmiennych, czsto rnego typu i skojarzonymi z tymi danymi metodami, czyli
funkcjami wykorzystywanymi wycznie dla cile okrelonych danych. Operowanie obiektami w programie jest istot programowania obiektowego.

3.2. Deklaracja i denicja klasy


Zmienna obiektowa (obiekt) jest elementem klasy, klasa jest typem deniowanym przez uytkownika. Klasa tworzona jest przy pomocy sowa
kluczowego class (podobnie jak struktura przy pomocy sowa kluczowego
struct) i moe zawiera dane i prototypy funkcji. W skad klasy wchodz
zmienne proste oraz zmienne innych klas. Zmienna wewntrz klasy jest nazywana zmienn skadow lub dan skadow. Funkcja w danej klasie zwykle
odnosi si do zmiennych skadowych. Funkcje klasy nazywa si funkcjami
skadowymi lub metodami klasy. Klasa moe by deklarowana:
Na zewntrz wszystkich funkcji programu. W tym przypadku jest ona
widoczna przez wszystkie pliki programu.
Wewntrz denicji funkcji. Tego typu klasa nazywa si lokaln, poniewa
jest widzialna tylko wewntrz funkcji.
Wewntrz innej klasy. Tego typu klasa jest nazywana klas zagniedon
Deklaracja klasy o nazwie Kot moe mie nastpujc posta:
c l a s s Kot
{
i n t wiek ;
i n t waga ;
void Glos ( ) ;
};

Zostaa utworzona klasa o nazwie Kot, zawiera ona dane: wiek i waga a
ponadto jedn funkcje Glos(). Taka deklaracja nie alokuje pamici, informuje jedynie kompilator, czym jest typ Kot, jakie zawiera dane i funkcje.

3.3. Wystpienie klasy (deniowanie obiektu)


Na podstawie tych informacji kompilator wie jak du potrzeba przygotowa pami w przypadku tworzenia zmiennej typu Kot. W tym konkretnym
przykadzie zmienna typu Kot zajmuje 8 bajtw (przy zaoeniu, e typ int
potrzebuje 4 bajty). Dla funkcji skadowych (w naszym przykadzie funkcja
Glos()) miejsce w pamici nie jest rezerwowane.

3.3. Wystpienie klasy (deniowanie obiektu)


Wystpienie klasy deklaruje si tak samo jak zmienne innych typw, np.
Kot Filemon ;

W ten sposb deniujemy zmienn o nazwie Filemon, ktra jest obiektem


klasy Kot. Mwimy, e ten obiekt jest indywidualnym egzemplarzem klasy
Kot. Deklaracja obiektw moliwa jest take w trakcie deklaracji klasy, np.
c l a s s Kot
{
i n t wiek ;
i n t waga ;
void Glos ( ) ;
} Filemon ;

Naley pamita o nastpujcych ograniczeniach przy deklarowaniu skadowych klasy:


Deklarowana skadowa nie moe by inicjalizowana w deklaracji klasy
Nazwy skadowych nie mog si powtarza
Deklaracje skadowych nie mog zawiera sw kluczowych auto, extern
i register
Zamknicie zwizanych ze sob elementw klasy (danych i metod) w
jedn cao nazywane jest hermetyzacj albo enkapsulacj (ang. encapsulation). Zdeniowan klas traktujemy jako typ, ktry moe by wykorzystany
na rne sposoby. Ponisze deklaracje ilustruj to zagadnienie:
Kot Filemon ;
// d e k l a r a c j a o b i e k t y t y p u Kot
Kot Grupa_koty [ 1 0 ] ; // d e k l a r a c j a t a b l i c y o b i e k t o w Kot
Kot wskKot ;
// w s k a z n i k do o b i e k t u Kot

3.4. Dostp do elementw klasy


Dostp do skadowej obiektu klasy uzyskuje si przy pomocy operatora
dostpu, zwanego te operatorem kropki (.). Gdy chcemy przypisa zmiennej
skadowej o nazwie waga warto 10, naley napisa nastpujc instrukcj:

43

44

3. Klasy i obiekty
Filemon . waga = 1 0 ;

Podobnie w celu wywoania funkcji Glos(), naley napisa nastpujc instrukcj:


Filemon . Glos ( ) ;

Dla zmiennych wskanikowych dostp do elementw klasy realizuje si przy


pomocy operatora dostpu (->).
W jzyku C++ s trzy kategorie dostpu do elementw klasy:
Prywatny etykieta private
Publiczny etykieta public
Chroniony etykieta protected
Poziomy dostpu okrelaj sposb wykorzystania elementw klasy przez
jej uytkownikw. Specykatory dostpu (sowa kluczowe private, public
i protected) odgrywaj du rol w programowaniu obiektowym. Poniewa
kada klasa powinna komunikowa si z otoczeniem, powinna posiada cz
publiczn (inaczej interfejs), czyli elementy, do ktrych mona odwoywa
si z zewntrz. Bardzo czsto chcemy, aby funkcje skadowe klasy nie byy
dostpne z zewntrz; funkcje te tworz cz prywatn, czyli implementacj
klasy. Ukrywanie wewntrznych szczegw implementacji przed dostpem
z zewntrz jest jednym z elementw dobrego projektowania klas i nosi nazw abstrahowania danych. Elementy klasy objte dostpem chronionym
(sowo kluczowe protected) nie s dostpne z zewntrz generalnie, jednak
s dostpne dla klas od nich pochodnych. Domyln kategori dostpu dla
wszystkich elementw klasy jest kategoria private. Oznacza to, gdy nie podano specykatora dostpu, kompilator przyjmie domylnie etykiet private.
W tym przypadku dane skadowe s dostpne jedynie dla funkcji skadowych
i tzw. funkcji zaprzyjanionych. Zastosowanie kategorii public powoduje, e
wystpujce po tej etykiecie nazwy deklarowanych skadowych mog by
uywane przez dowolne funkcje. Rozwamy nastpujcy przykad.
Listing 3.1. Dostp do skadowych publicznych
1

11

#include <i o s t r e a m . h>


#include <c o n i o . h>
class Liczba
{
public :
int x ;
};
i n t main ( )
{
Liczba a , b , c ;

45

3.4. Dostp do elementw klasy


a . x = 3;
b . x = 33;
c = &a ;
c o u t << "x w o b i e k c i e a = " << a . x << e n d l ;
c o u t << "x w o b i e k c i e b = " << b . x << e n d l ;
c o u t << "x w o b i e k c i e a przy pisany m do wsk aznik a c = "
<< c > x << e n d l ;
c o u t << "x w o b i e k c i e a przy pisany m do wsk aznik a c = "
<< ( c ) . x << e n d l ;
c = &b ;
c o u t << "x w o b i e k c i e b przy pisany m do wsk aznik a c = "
<<(c ) . x << e n d l ;
getch () ;
return 0 ;

13

15

17

19

21

23

25

Wynik wykonania programu:

W programie wystpuje klasa Liczba, ktra ma zmienn skadow x.


Zmienna skadowa jest skadow publiczn i dlatego mona w obiektach a
i b bezporednio przypisywa jej warto 3 i 33. W instrukcji:
Liczba a , b , c ;

zadeklarowano wskanik c do klasy Liczba. W instrukcji:


c = &a ;

oraz

c = &b ;

temu samemu wskanikowi c przypisano kolejno obiekty a i b klasy Liczba.


W instrukcjach:
c o u t << "xw o b i e k c i e a przy pisany m do wsk aznik a c = "
<< c > x << e n d l ;
c o u t << "xw o b i e k c i e a przy pisany m do wsk aznik a c = "
<< ( c ) . x << e n d l ;

zademonstrowano operator dostpu (operator kropki i operator strzaki) do


skadowej x w rwnowanych postaciach. W kolejnym przykadzie wszystkie skadowe klasy Kot s publiczne, typy zmiennych s rne, w naszym
przypadku int, oat i char.

46

3. Klasy i obiekty
Listing 3.2. Dostp do publicznych skadowych klasy

10

12

14

16

18

20

#include <i o s t r e a m . h>


#include <c o n i o . h>
c l a s s Kot
{
public :
i n t wiek ;
f l o a t waga ;
char k o l o r ;
};
i n t main ( )
{
Kot Filemon ;
Filemon . wiek = 3 ;
Filemon . waga = 3 . 5 ;
Filemon . k o l o r = " rudy " ;
c o u t << " Filemon t o " << Filemon . k o l o r << " k ot " << e n d l ;
c o u t << "Wazy " << Filemon . waga << " k ilogramow " ;
c o u t << " i ma " << Filemon . wiek << " l a t a " << e n d l ;
getch () ;
return 0 ;
}

Wynik dziaania programu:


Filemon t o rudy k ot
Wazy 3 . 5 k ilogramow i ma 3 l a t a

Deklaracja klasy Kot ma posta:


c l a s s Kot
{
public :
i n t wiek ;
f l o a t waga ;
char k o l o r ;
};

Poniewa skadowe klasy s publiczne to moemy utworzy obiekt Filemon


i przypisa im odpowiednie wartoci:
Kot Filemon ;
Filemon . wiek = 3 ;
Filemon . waga = 3 . 5 ;
Filemon . k o l o r = " rudy " ;

Jeeli w deklaracji klasy Kot nie bdzie kwantykatora public, to kompi-

3.5. Metody klasy


lator uzna, e skadowe klasy s prywatne i prba przypisania im wartoci
zakoczy si niepowodzeniem.

3.5. Metody klasy


Klas tworz dane i funkcje skadowe. Funkcje skadowe (s to tzw.
metody) mog by deklarowane jako inline, static i virtual, nie mog by
deklarowane jako extern.
Listing 3.3. Dostp do publicznych skadowych klasy
2

10

#include <i o s t r e a m . h>


#include <c o n i o . h>
c l a s s Kot
{
public :
i n t wiek ;
i n t waga ;
char k o l o r ;
void Glos ( ) ;
};

12

14

void Kot : : Glos ( )


{ c o u t << " miauczy " ; }

16

18

20

22

24

26

28

30

i n t main ( )
{
Kot Filemon ;
Filemon . wiek = 3 ;
Filemon . waga = 5 ;
Filemon . k o l o r = " rudy " ;
c o u t << " Filemon t o " << Filemon . k o l o r << " k ot " << e n d l ;
c o u t << "Wazy " << Filemon . waga << " k ilogramow " ;
c o u t << " i ma " << Filemon . wiek << " l a t a " << e n d l ;
c o u t << " gdy j e s t glodny t o " ;
Filemon . Glos ( ) ;
getch () ;
return 0 ;
}

Wynik wykonania programu:

47

48

3. Klasy i obiekty
W denicji klasy Kot pojawia si funkcja skadowa (metoda) o nazwie
Glos():
c l a s s Kot
{
public :
i n t wiek ;
i n t waga ;
char k o l o r ;
void Glos ( ) ;
};

Funkcja skadowa Glos() jest zdeniowana w nastpujcy sposb:


void Kot : : Glos ( )
{ c o u t << " miauczy " ; }

Jak widzimy, ta metoda nie ma parametrw i nie zwraca adnej wartoci.


Metoda zawiera nazw klasy (tutaj Kot), dwa dwukropki (operator zasigu) oraz nazw funkcji ( tutaj Glos ). Ta skadnia informuje kompilator,
e ma do czynienia z funkcj skadow zadeklarowan w klasie Kot. Dwuargumentowy operator zasigu :: znajdujcy si pomidzy nazw klasy i
nazw funkcji skadowej ustala zasig funkcji Glos(). Napis Kot:: informuje
kompilator, e wystpujca po nim funkcja jest funkcj skadow klasy Kot.
Aby wywoa funkcj Glos() naley umieci instrukcj:
Filemon . Glos ( ) ;

Aby uy metod klasy naley j wywoa. W naszym przykadzie zostaa


wywoana metoda Glos() obiektu Filemon. Mona deniowa funkcje wewntrz klasy:
c l a s s Kot
{
public :
i n t wiek ;
i n t waga ;
char k o l o r ;
void Glos ( ) { c o u t << " miauczy " ; } ;
};

Jeeli funkcja skadowa (metoda) jest zdeniowana wewntrz klasy to jest


traktowana jako funkcja o atrybucie inline. Metod mona take zadeklarowa jako inline poza klas:
c l a s s Kot
{

3.5. Metody klasy


public :
i n t wiek ;
i n t waga ;
char k o l o r ;
void Glos ( ) ;
};
i n l i n e void Kot : : Glos ( )
{ c o u t << " miauczy " ; }

Elementami klasy s dane i metody, nale one do zasigu klasy. Zasig klasy
oznacza, e wszystkie skadowe klasy s dostpne dla funkcji skadowych
poprzez swoj nazw. Poza zasigiem klasy, do skadowych odnosimy si
najczciej za porednictwem tzw. uchwytw referencji i wskanikw. Jak
ju mwilimy, w jzyku C++ mamy dwa operatory dostpu do skadowych
klasy:
Operator kropki (.) stosowany z nazw obiektu lub referencj do niego
Operator strzaki (->) stosowany w poczeniu ze wskanikiem do obiektu
Operator strzaki ( ang. arrow member selection operator) jest nazywany w polskiej literaturze rnie, najczciej jako skrtowy operator zasigu
lub krtko operator wskazywania. Aby przykadowo wydrukowa na ekranie skadow sekunda klasy obCzas, z wykorzystaniem wskanika czasWs,
stosujemy instrukcj:
czasWs = &obCzas ;
c o u t << czasWs > sekunda ;

Wyraenie:
czasWs > sekunda

jest odpowiednikiem
( czasWs ) . sekunda

To wyraenie dereferuje wskanik, a nastpnie udostpnia skadow sekunda za pomoc operatora kropki. Konieczne jest uycie nawiasw okrgych,
poniewa operator kropki ma wyszy priorytet ni operator dereferencji.
Poniewa taki zapis jest do skomplikowany, w jzyku C++ wprowadzono
skrtowy operator dostpu poredniego operator strzaki. Uywanie operatora strzaki jest preferowane przez wikszo programistw. W prostym
programie zademonstrujemy korzystanie z klasy Liczba dla zilustrowania
sposobw dostpu do skadowych klasy za pomoc omwionych operatorw
wybierania skadowych.

49

50

3. Klasy i obiekty
Listing 3.4. Dostp do skadowych: operator kropki i strzaki
1

#include <i o s t r e a m . h>


#include <c o n i o . h>

11

13

15

17

19

21

23

25

27

29

31

class Liczba
{
public :
float x ;
void pokaz ( ) { c o u t << x << e n d l ; }
};
i n t main ( )
{
L i c z b a nr ;
L i c z b a nrWsk ;
L i c z b a &nrR ef = nr ;

// o b i e k t nr t y p u L i c z b a
// w s k a z n i k
// r e f e r e n c j a

nr . x = 1 . 0 1 ;
c o u t << " nazwa o b i e k t u , sk ladowa xma w a r t o s c : " ;
nr . pokaz ( ) ;
nrR ef . x = 2 . 0 2 ;
c o u t << " r e f e r e n c j a o b i e k t u , sk ladowa xma w a r t o s c : " ;
nrR ef . pokaz ( ) ;
nrWsk = &nr ;
nrWsk > x = 3 . 0 3 ;
c o u t << " wsk aznik o b i e k t u , sk ladowa xma w a r t o s c : " ;
nrWsk > pokaz ( ) ;
getch () ;
return 0 ;
}

3.6. Klasa z akcesorami


Dane skadowe klasy powinny by kwalikowane jako prywatne. Wobec
tego klasa powinna posiada publiczne funkcje skadowe, dziki ktrym moliwe bdzie manipulowanie danymi skadowymi. Publiczne funkcje skadowe zwane s funkcjami dostpowymi lub akcesorami, poniewa umoliwiaj
odczyt zmiennych skadowych i przypisywanie im wartoci. W kolejnym
przykadzie pokaemy zastosowanie publicznych akcesorw.
Akcesory s funkcjami skadowymi, ktre uyjemy do ustawiania wartoci prywatnych zmiennych skadowych klasy i do odczytu ich wartoci.

51

3.6. Klasa z akcesorami


Listing 3.5. Dostp do prywatnych skadowych klasy - akcesory
1

11

#include <i o s t r e a m . h>


#include <c o n i o . h>
class Prost
{ private :
int a ;
int b ;
public :
i n t get_a ( ) ;
i n t get_b ( ) ;
void set_a ( i n t aw ) ;
void set_b ( i n t bw) ;
};

13

15

17

19

21

23

25

27

29

int Prost
int Prost
void P r o s t
void P r o s t

: : get_a ( ) { return
: : get_b ( ) { return
: : set_a ( i n t aw )
: : set_b ( i n t bw)

a ;}
b ;}
{ a = aw ; }
{b = bw ; }

i n t main ( )
{ P r o s t p1 ;
int p ole ;
p1 . set_a ( 5 ) ;
p1 . set_b ( 1 0 ) ;
p o l e = p1 . get_a ( ) p1 . get_b ( ) ;
//
p o l e = p1 . a p1 . b ;
// ni epoprawna i n s t r u k c j a
c o u t << " P o w i e r z c h n i a = " << p o l e << e n d l ;
getch () ;
return 0 ;
}

Wynik dziaania programu:


P o w i e r z c h n i a = 50

W tym programie po wprowadzeniu dugo i szeroko prostokta obliczamy jego pole powierzchni. Klasa o nazwie Prost ma nastpujc deklaracj:
class Prost
{ private :
int a ;
int b ;
public :
i n t get_a ( ) ;
i n t get_b ( ) ;
void set_a ( i n t aw ) ;
void set_b ( i n t bw) ;
};

52

3. Klasy i obiekty
Dane skadowe a i b s danymi prywatnymi. Wszystkie funkcje skadowe s
akcesorami publicznymi. Dwa akcesory ustawiaj zmienne skadowe:
void set_a ( i n t aw ) ;
void set_b ( i n t bw) ;

a dwa inne:
i n t get_a ( ) ;
i n t get_b ( ) ;

zwracaj ich wartoci. Akcesory s publicznym interfejsem do prywatnych


danych klasy. Kady akcesor (tak jak kada funkcja) musi mie denicj.
Denicja akcesora jest nazywana implementacj akcesora. W naszym przykadzie denicje akcesorw publicznych s nastpujce:
int Prost
int Prost
void P r o s t
void P r o s t

: : get_a ( ) { return
: : get_b ( ) { return
: : set_a ( i n t aw )
: : set_b ( i n t bw)

a ;}
b ;}
{ a = aw ; }
{b = bw ; }

Posta denicji funkcji skadowej jest do prosta. Np. zapis:


i n t P r o s t : : get_a ( ) { return a ; }

zawiera denicje funkcji get a(). Ta metoda nie ma parametrw, zwraca


warto cakowit. Metoda klasy zawiera nazw klasy (w naszym przypadku jest to Prost), dwa dwukropki i nazw funkcji. Tak skadnia informuje
kompilator, e deniowana funkcja get a() jest funkcj skadow klasy Prost.
Bardzo prosta funkcja get a() zwraca warto zmiennej skadowej a. Skadowa a jest skadow prywatn klasy Prost i funkcja main() nie ma do niej
dostpu bezporedniego. Z drugiej strony get a() jest metod publiczna, co
oznacz, e funkcja main() ma do niej peny dostp. Widzimy, e funkcja
main() za porednictwem metody get a() ma dostp do danej prywatnej a.
Podobnie ma si sprawa z metod set a(). Kod wykonywalny zawarty jest
w ciele funkcji main(). W instrukcji:
P r o s t p1 ;

zadeklarowany jest obiekt Prost o nazwie p1. W kolejnych instrukcjach:


p1 . set_a ( 5 ) ;
p1 . set_b ( 1 0 ) ;

zmiennym a i b przypisywane s wartoci przy pomocy akcesorw set a()


i set b(). Wywoanie tej metody polega na napisaniu nazwy obiektu (tu-

53

3.7. Funkcje skadowe const


taj p1) a nastpnie uyciu operatora kropki (.) i nazwy metody (w tym
przypadku set a() i set b() ). W instrukcji:
p o l e = p1 . get_a ( ) p1 . get_b ( ) ;

obliczane jest pole powierzchni prostokta.

3.7. Funkcje skadowe const


Dobrze napisane programy s zabezpieczone przed przypadkow modykacj obiektw. Dobrym sposobem okrelania czy obiekt moe by zmieniony czy nie jest deklarowanie obiektw przy pomocy sowa kluczowego
const. W wyraeniu:
const L i c z b a nr ( 1 0 . 0 5 ) ;

zadeklarowano stay obiekt nr klasy Liczba z jednoczesn inicjacj. Podobnie deklarowane mog by funkcje skadowe. W prototypie i w denicji funkcji skadowej naley uy sowa kluczowego const. Na przykad nastpujca
denicja funkcji skadowej klasy Liczba
i n t L i c z b a : : pokazWartosc ( ) const { return x ; } ;

zwraca warto jednej z prywatnych danych skadowych funkcji. Prototyp


tej funkcji moe mie posta:
i n t pokazWartosc ( ) const ;

Wraz z modykatorem const czsto deklarowane s akcesory. Deklarowanie funkcji jako const jest bardzo dobrym zwyczajem programistycznym,
efektywnie pomaga wykrywa bdy przy przypadkowej prbie modykowania staych obiektw. Funkcje skadowe zadeklarowane jako const nie
mog modykowa danych obiektu, poniewa nie dopuci do tego kompilator. Obiekt stay nie moe by modykowany za pomoc przypisa, ale moe
by zainicjalizowany. W takim przypadku mona wykorzysta odpowiednio
zbudowany konstruktor. Do konstruktora musi by dostarczony odpowiedni
inicjator, ktry zostanie wykorzystany jako warto pocztkowa staej danej
klasy. W kolejnym przykadzie klasa Punkt posiada trzy dane prywatne, w
tym jedn typu const. Do celw dydaktycznych w programie umieszczono
dwa konstruktory (w danym momencie moe by czynny tylko jeden z nich).
Jeeli program uruchomimy z konstruktorem w postaci:
Punkt : : Punkt ( i n t xx , i n t yy , i n t k )
{ x = 1 ; y = 1 ; sk ok = k ; }

//ERROR ! ! !

54

3. Klasy i obiekty
zostanie wygenerowany nastpujcy komunikat:
[ C++ Warning ] f t e s t 1 . cpp [ 2 3 ] : W8038 Constant member
Punkt : : sk ok i s not i n i t i a l i z e d
[ C++ E r r o r ] f t e s t 1 . cpp [ 2 3 ] : E2024 Cannot modify
a const o b j e c t

W denicji konstruktora jest prba zainicjowania danej skadowej skok za


pomoc przypisania a nie przy uyciu inicjatora skadowej i to wywouje
bdy kompilacji.
Listing 3.6. Obiekty typu const
2

10

12

#include <i o s t r e a m . h>


#include <c o n i o . h>
c l a s s Punkt
// d e k l a r a c j a k l a s y p u n k t
{
public :
// s k l a d o w e p u b l i c z n e
Punkt ( i n t xx = 0 , i n t yy = 0 , i n t k = 1 ) ;
void przesunY ( ) {y += sk ok ; }
void pokaz ( ) const ;
private :
// s k l a d o w e prywatne
int x , y ;
const i n t sk ok ;
};

14

16

18

20

22

24

Punkt : : Punkt ( i n t xx , i n t yy , i n t k ) : sk ok ( k )
{ x = 1; y = 1;}
// k o n s t r u k t o r
/
Punkt : : Punkt ( i n t xx , i n t yy , i n t k )
{ x = 1; y = 1; skok = k ; }
//ERROR ! ! !
/
void Punkt : : pokaz ( ) const
// d e f i n i c j a metody
{ c o u t << " sk ok = " << sk ok << " x= "
<< x << " y= " << y << e n d l ;
}

26

28

30

32

34

36

38

i n t main ( )
{
Punkt p1 ( 1 , 1 , 2 ) ;
// d e k l a r a c j a o b i e k t u
// p1 t y p u Punkt
c o u t << " dane poczatk owe " << e n d l ;
p1 . pokaz ( ) ;
c o u t << " zmiana w s p o l r z e d n e j y " << e n d l ;
f o r ( i n t j = 0 ; j <5; j ++)
{
p1 . przesunY ( ) ;
p1 . pokaz ( ) ;
}

55

3.7. Funkcje skadowe const


getch () ;
return 0 ;

40

Program zadziaa poprawnie, gdy wykorzystane zostan inicjatory do


zainicjowania staej danej skadowej skok klasy Punkt. Poprawny konstruktor ma posta:
Punkt : : Punkt ( i n t xx , i n t yy , i n t k ) : sk ok ( k )
{ x = 1; y = 1;}

// k o n s t r u k t o r

Wynik dziaania programu:

W denicji konstruktora wida notacj zapoyczon przez C++ z jzyka Simula. Inicjowanie jakiej zmiennej x wartoci np. 44 najczciej ma
posta:
int x = 44;

Dopuszczalne jest take rwnowana posta:


int x (44) ;

Wykorzystujc drug notacj otrzymujemy denicje konstruktora klasy Punkt.


Wyraenie
: sk ok ( k )

powoduje zainicjowanie skadowej skok wartoci k. Moemy mie list zmiennych skadowych z wartociami inicjalnymi :
Punkt : : Punkt ( i n t a , i n t b ) : xx ( a ) , yy ( b )

Po dwukropku naley kolejno umieszcza inicjatory rozdzielajc je przecinkami.

Rozdzia 4
Konstruktory i destruktory

4.1.
4.2.
4.3.
4.4.
4.5.
4.6.
4.7.
4.8.
4.9.
4.10.
4.11.
4.12.
4.13.

Wstp . . . . . . . . . . . . . . . . . . . . . . . . . . .
Inicjalizacja obiektu klasy . . . . . . . . . . . . . . . .
Konstruktory i destruktory domylne . . . . . . . . .
Konstruktor jawny . . . . . . . . . . . . . . . . . . . .
Wywoywanie konstruktorw i destruktorw . . . . .
Rozdzielenie interfejsu od implementacji . . . . . . .
Wskanik do obiektu this . . . . . . . . . . . . . . . .
Wskanik this kaskadowe wywoania funkcji . . . .
Tablice obiektw . . . . . . . . . . . . . . . . . . . . .
Inicjalizacja tablic obiektw nie bdcych agregatami
Tablice obiektw tworzone dynamicznie . . . . . . . .
Kopiowanie obiektw . . . . . . . . . . . . . . . . . .
Klasa z obiektami innych klas . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.

58
58
59
61
63
65
67
68
71
73
75
77
78

58

4. Konstruktory i destruktory

4.1. Wstp
Klasa jest typem deniowanym przez uytkownika. Gdy deklarowane s
zmienne tego typu, s one obiektami. Mamy nastpujce denicje.
Klasa jest zdeniowanym przez uytkownika typem danych, zawiera pola
danych oraz funkcje.
Obiekt jest egzemplarzem utworzonego (na podstawie klasy) typu.
Klasa ma charakter unikatowy i istnieje tylko jedna posta klasy
Moe istnie wiele obiektw okrelonego typu (utworzonego na podstawie
konkretnej klasy)
Sama denicja klasy nie deniuje adnych obiektw. W denicji klasy,
dane nie mog by inicjalizowane (nie mona przypisa im wartoci). Dane mona inicjalizowa dla konkretnego obiektu (egzemplarza klasy). Po
utworzeniu klasy mona powoa do ycia jej egzemplarz, czyli obiekt. Dobr praktyk programistyczn jest tworzenie obiektw z zainicjalizowanymi
danymi. Inicjalizacja obiektw nie jest zadaniem trywialnym.

4.2. Inicjalizacja obiektu klasy


Bardzo czsto chcemy, aby w momencie tworzenia obiektu, jego skadowe
byy zainicjalizowane. Dla prostych typw denicja zmiennej i jednoczesna
inicjalizacja ma posta:
i n t x1 = 1 3 3 ;

Inicjalizacja czy w sobie deniowanie zmiennej i pocztkowe przypisanie


wartoci. Oczywici pniej moemy zmieni wartoci zmiennej. Inicjalizacja
zapewnia, e zmienna bdzie miaa sensown warto pocztkow. Skadowe klasy take moemy inicjalizowa. Dane skadowe klasy inicjalizowane s
za pomoc funkcji skadowej o nazwie konstruktor (ang. costructor). Konstruktor jest funkcj o nazwie identycznej z nazw klasy. Konstruktor jest
wywoywany za kadym razem, gdy tworzony jest obiekt danej klasy. Konstruktor moe posiada argumenty, ale nie moe zwraca adnej wartoci.
Destruktor (ang. destructor) klasy jest specjaln funkcj skadow klasy.
Destruktor jest wywoywany przy usuwaniu obiektu. Destruktor nie otrzymuje adnych parametrw i nie zwraca adnej wartoci. Klasa moe posiada tylko jeden destruktor. Nazwa destruktora jest taka sama jak klasy,
poprzedzona znakiem tyldy (). Gdy jest zadeklarowany jeden konstruktor,
powinien by take zadeklarowany destruktor. Wyrniamy konstruktory i
dekonstruktory inicjujce, konstruktory i dekonstruktory domylne i konstruktor kopiujcy.

59

4.3. Konstruktory i destruktory domylne


Zadaniem konstruktora jest konstruowanie obiektw. Po wywoaniu konstruktora nastpuje:
Przydzielenie pamici dla obiektu
Przypisanie wartoci do zmiennych skadowych
Wykonanie innych operacji (np. konwersja typw)
Podczas deklaracji obiektu, mog by podane inicjatory (ang. initializers). S one argumentami przekazywanymi do konstruktora. Programista
nigdy jawnie nie wywouje konstruktora, moe za to wysa do niego argumenty.

4.3. Konstruktory i destruktory domylne


Kada klasa zawiera konstruktor i destruktor. Jeeli nie zostay zadeklarowane jawnie, uczyni to w tle kompilator. Zagadnienie to ilustrujemy
popularnym przykadem realizacji klasy Punkt do obsugi punktw na
paszczynie.
Listing 4.1. Klasa Punkt (punkt na paszczynie)
1

#include <i o s t r e a m . h>


#include <c o n i o . h>

11

13

15

17

19

21

23

25

27

29

c l a s s Punkt
{ public :
void ustaw ( int , i n t ) ;
void p r z e s u n ( int , i n t ) ;
void pokaz ( ) ;
private :
int x , y ;
};

// d e k l a r a c j a k l a s y p u n k t
// s k l a d o w e p u b l i c z n e

// s k l a d o w e prywatne

void Punkt : : ustaw ( i n t a , i n t b ) // d e f i n i c j a metody


{ x = a;
y = b;
}
void Punkt : : p r z e s u n ( i n t da , i n t db ) // d e f i n i c j a metody
{ x = x + da ;
y = y + db ;
}
void Punkt : : pokaz ( )
// d e f i n i c j a metody
{ c o u t << " w s p o l r z e d n e : " << x << " " << y << e n d l ;
}
i n t main ( )
// program t e s t u j a c y u z y c i e k l a s y Punkt
{ Punkt p1 ;
// d e k l a r a c j a o b i e k t u p1 t y p u Punkt
p1 . ustaw ( 5 , 1 0 ) ; // i n i c j a l i z a c j a
p1 . pokaz ( ) ;
p1 . p r z e s u n ( 1 0 , 1 0 ) ;

60

4. Konstruktory i destruktory
p1 . pokaz ( ) ;
getch () ;
return 0 ;

31

33

Wynik dziaania programu:


Wspolrzedne : 5
Wspolrzedne : 15

10
20

Klasa Punkt ma nastpujc posta:


c l a s s Punkt
{
public :
void ustaw ( int , i n t ) ;
void p r z e s u n ( int , i n t ) ;
void pokaz ( ) ;
private :
int x , y ;
};

// d e k l a r a c j a k l a s y p u n k t
// s k l a d o w e p u b l i c z n e

// s k l a d o w e prywatne

Deklaracja klasy skada si z prywatnych danych ( wsprzdne kartezjaskie punktu x i y) oraz z publicznych funkcje skadowych: ustaw(), przesun()
i pokaz(). Denicja klasy skada si z denicji wszystkich funkcji skadowych
(metod) i ma posta:
void Punkt : : ustaw ( i n t a , i n t b )
{ x = a;
y = b;
}

// d e f i n i c j a metody

void Punkt : : p r z e s u n ( i n t da , i n t db ) // d e f i n i c j a metody


{ x = x + da ;
y = y + db ;
}
void Punkt : : pokaz ( )
// d e f i n i c j a metody
{ c o u t << " w s p o l r z e d n e : " << x << " " << y << e n d l ;
}

Wewntrz denicji wszystkie dane i funkcje skadowe s bezporednio dostpne (bez wzgldu na to czy s publiczne czy prywatne). Dane skadowe
nalece do klasy pamici typu static s dostpne dla wszystkich obiektw
danej klasy. Przez domniemanie , dane statyczne s inicjalizowane wartoci
zerow. Jak ju mwilimy, jeeli nie stworzymy konstruktora, kompilator
stworzy konstruktor domylny bezparametrowy. Potrzeba tworzenia kon-

4.4. Konstruktor jawny


struktora jest natury technicznej wszystkie obiekty musz by konstruowane i niszczone, dlatego czsto tworzone s nic nie robice funkcje. Przypumy, e chcemy zadeklarowa obiekt bez przekazywania parametrw:
Punkt p1 ;

to wtedy musimy posiada konstruktor w postaci:


Punkt ( ) ;

Gdy deniowany jest obiekt klasy, wywoywany jest konstruktor. Zamy,


e konstruktor klasy Punkt ma dwa parametry, mona zdeniowa obiekt
Punkt piszc:
Punkt p1 ( 5 , 1 0 ) ;

Dla konstruktora z jednym parametrem mamy instrukcj:


Punkt p1 ( 5 ) ;

Jeeli konstruktor jest domylny (nie ma adnych parametrw) mona opuci nawiasy i napisa po prostu:
Punkt p1 ;

Jest to wyjtek od reguy, ktra mwi, e funkcje musz mie nawiasy nawet
wtedy, gdy nie maj parametrw. Dlatego zapis:
Punkt p1 ;

jest interpretowany przez kompilator jako wywoanie konstruktora domylnego w tym przypadku nie wysyamy parametrw i nie piszemy nawiasw.
Gdy deklarowany jest konstruktor, powinien by deklarowany destruktor,
nawet gdy nic nie robi.

4.4. Konstruktor jawny


Zmienimy teraz klas Punkt w ten sposb, e do inicjalizacji obiektu
uyjemy konstruktora jawnego, pokaemy take jawn posta destruktora.
Listing 4.2. Klasa Punkt z konstruktorem
1

#include <i o s t r e a m . h>


#include <c o n i o . h>
c l a s s Punkt
{ public :

61

62

4. Konstruktory i destruktory
5

11

13

15

17

19

21

23

25

27

Punkt ( int , i n t ) ;
~Punkt ( ) ;
void p r z e s u n ( int , i n t ) ;
void pokaz ( ) ;
private :
int x , y ;

// k o n s t r u k t o r
// d e s t r u k t o r

};
// i m p l e m e n t a c j a k o n s t r u k t o r a
Punkt : : Punkt ( i n t a , i n t b )
// k l a s y Punkt
{ x = a;
y = b;
}
Punkt : : ~Punkt ( )
{ }
// i m p l e m e n t a c j a d e s t r u k t o r a
void Punkt : : p r z e s u n ( i n t da , i n t db )
{ x = x + da ;
y = y + db ;
}
void Punkt : : pokaz ( )
{ c o u t << " w s p o l r z e d n e : " << x << " " << y << e n d l ;
}
i n t main ( )
{
Punkt p1 ( 5 , 1 0 ) ;
p1 . pokaz ( ) ;
p1 . p r z e s u n ( 1 5 , 1 5 ) ;
p1 . pokaz ( ) ;
getch () ;
return 0 ;
}

// u s t a w i a p u n k t
// p o k a z u j e w s p o l r z e d n e
// p r z e s u w a p u n k t
// p o k a z u j e nowe w s p o l r z e d n e

Wynik wykonania programu ma posta:


w s p o l r z e d n e : 5 10
w s p o l r z e d n e : 20 25

Klasa Punkt z konstruktorem jawnym ma posta:


c l a s s Punkt
{
public :
Punkt ( int , i n t ) ;
~Punkt ( ) ;
void p r z e s u n ( int , i n t ) ;
void pokaz ( ) ;
private :
int x , y ;
};

// k o n s t r u k t o r
// d e s t r u k t o r

Konstruktor ma dwa parametry, dziki ktrym ustawiamy wartoci wsprzdnych punktu:


Punkt : : Punkt ( i n t a , i n t b )
{ x = a;
y = b;
}

// k o n s t r u k t o r k l a s y Punkt

4.5. Wywoywanie konstruktorw i destruktorw


Destruktor w tym programie nic nie robi. Poniewa deklaracja klasy zawiera
deklaracj destruktora musimy zamieci implementacj destruktora:
Punkt : : ~Punkt ( )
{
}

// i m p l e m e n t a c j a d e s t r u k t o r a

W funkcji main() naley zwrci uwag na instrukcj:


Punkt p1 ( 5 , 1 0 ) ;

// u s t a w i a p u n k t

Mamy tutaj denicj obiektu p1, stanowicego egzemplarz klasy Punkt. Do


konstruktora obiektu p1 przekazywane s wartoci 5 i 10.

4.5. Wywoywanie konstruktorw i destruktorw


Konstruktory i destruktory wywoywane s automatycznie. Kolejno
ich wywoywania jest do skomplikowanym zagadnieniem wszystko zaley
od kolejnoci w jakiej wykonywany program przetwarza obiekty globalne i
lokalne. Gdy obiekt jest zdeniowany w zasigu globalnym, jego konstruktor
jest wywoywany jako pierwszy a destruktor, gdy funkcja main() koczy prac. Dla automatycznych obiektw lokalnych konstruktor jest wywoywany
w miejscu, w ktrym zostay one zdeniowane, a destruktor jest wywoany
wtedy, gdy program opuszcza blok, w ktrym obiekt ten by zdeniowany.
Dla obiektw typu static konstruktor jest wywoywany w miejscu, w ktrym
obiekt zosta zdeniowany a destruktor jest wywoany wtedy, gdy funkcja
main() koczy prac.
Do demonstracji kolejnoci wywoywania konstruktorw i destruktorw
adaptowalimy program opisany w monograi H.Deitela i P.Deitela Arkana
C++.
Listing 4.3. Kolejno wywoywania konstruktorw i destruktorw
1

#include <i o s t r e a m . h>


#include <c o n i o . h>

c l a s s Kon_Des
{ public :
Kon_Des( i n t ) ;
~Kon_Des ( ) ;
private :
i n t nr ;
};

// k o n s t r u k t o r
// d e s t r u k t o r

11

13

Kon_Des : : Kon_Des( i n t numer )


{ nr = numer ;

63

64

4. Konstruktory i destruktory
c o u t << " k o n s t r u k t o r o b i e k t u " << nr ;
15

17

19

21

23

25

27

29

31

33

35

37

39

41

43

45

47

49

51

}
Kon_Des : : ~Kon_Des ( )
{ c o u t << " d e s t r u k t o r o b i e k t u " << nr << e n d l ;
}
void fun_ob ( void ) ;
// p r o t o t y p f u n k c j i t w o r z a c e j
// nowe o b i e k t y
Kon_Des ob1 ( 1 ) ;
// o b i e k t g l o b a l n y
void fun_ob ( void )
{Kon_Des ob5 ( 5 ) ;
// o b i e k t l o k a l n y
c o u t << " ( automatyczny o b i e k t l o k a l n y w fun_ob ( ) ) "
<< e n d l ;
s t a t i c Kon_Des ob6 ( 6 ) ;
// o b i e k t l o k a l n y
c o u t << " ( s t a t y c z n y o b i e k t l o k a l n y w fun_ob ( ) ) "
<< e n d l ;
Kon_Des ob7 ( 7 ) ;
// o b i e k t l o k a l n y
c o u t << " ( automatyczny o b i e k t l o k a l n y w fun_ob ( ) ) "
<< e n d l ;
}
i n t main ( )
{ c o u t << " ( g l o b a n y utworzony p r z e d f u n k c j a main ( ) ) "
<< e n d l ;
Kon_Des ob2 ( 2 ) ;
// o b i e k t l o k a l n y
c o u t << " ( automatyczny o b i e k t l o k a l n y w main ( ) ) "
<< e n d l ;
s t a t i c Kon_Des ob3 ( 3 ) ;
// o b i e k t l o k a l n y
c o u t << " ( s t a t y c z n y o b i e k t l o k a l n y w main ( ) ) "
<< e n d l ;
fun_ob ( ) ;
// w y w o l a n i e f u n k c j i t w o r z a c e j o b i e k t y
Kon_Des ob4 ( 4 ) ;
// o b i e k t l o k a l n y
c o u t << " ( automatyczny o b i e k t l o k a l n y w main ( ) "
<< e n d l ;
getch () ;
return 0 ;
}

Wynik wykonania programu:

Dalsze wywoania powinny mie posta:

4.6. Rozdzielenie interfejsu od implementacji


Destruktor
Destruktor
Destruktor
Destruktor
Destruktor

obiektu
obiektu
obiektu
obiektu
obiektu

4
2
6
3
1

4.6. Rozdzielenie interfejsu od implementacji


Zaleca si, aby rozdziela interfejs od implementacji. W tej metodzie kod
programu rozdzielamy na wiele plikw. Deklaracj klasy zaleca si umieszcza w pliku nagwkowym. Zwyczajowo plik taki ma rozszerzenie .h . Denicje funkcji skadowych klasy powinny by umieszczone w odrbnym pliku
rdowym, zwyczajowo plik taki ma rozszerzenie .cpp, a nazw tak sam jak plik z deklaracj klasy. Do programu gwnego wczamy wszystkie
potrzebne, utworzone przez nas pliki nagwkowe. Zintegrowane rodowiska
programistyczne maj osobne narzdzia do obsugi programw wieloplikowych.
Zagadnienie programw wieloplikowych ilustrujemy programem wykorzystujcym klas punkt obsugujc punkty na paszczynie. Mamy trzy
pliki:
1. punkt1.h deklaracja klasy punkt
2. punkt1.cpp denicje funkcji skadowych
3. ftest1.cpp denicja funkcji main()
Listing 4.4. Program wieloplikowy - ftest1.cpp
1

11

13

// p l i k f t e s t 1 . cpp , program w y k o r z y s t u j e k l a s e p u n k t
#include <i o s t r e a m . h>
#include <c o n i o . h>
#include " punkt1 . h"
i n t main ( )
{
punkt p1 ( 0 . 5 , 1 . 5 ) ;
p1 . w y s w i e t l ( ) ;
p1 . p r z e s u n ( 0 . 5 , 0 . 5 ) ;
p1 . w y s w i e t l ( ) ;
getch () ;
return 0 ;
}

Listing 4.5. Program wieloplikowy - punkt1.h


// k l a s a p u n k t

65

66

4. Konstruktory i destruktory
2

10

12

#i f n d e f _PUNKT1_H
#define _PUNKT1_H
c l a s s punkt
{ private :
float x , y ;
public :
punkt ( f l o a t , f l o a t ) ;
void p r z e s u n ( f l o a t , f l o a t ) ;
void w y s w i e t l ( ) ;
};
#endif

Listing 4.6. Program wieloplikowy - punkt1.cpp


2

// d e f i n i c j e k l a s y p u n k t
#include " punkt1 . h"
#include <i o s t r e a m . h>

punkt : : punkt ( f l o a t xx , f l o a t yy )
{ x = xx ; y = yy ;
}

10

void punkt : : p r z e s u n ( f l o a t dx , f l o a t dy )
{ x = x + dx ;
y = y + dy ;
}

12

14

void punkt : : w y s w i e t l ( )
{ c o u t << " x i y= " << x << " " << y << e n d l ;
}

Wynik wykonania programu pokazanego na wydruku 4.4a ma posta:

Umieszczone w pliku pokazanym na wydruku 4.4b dyrektywy preprocesora :


#i f n d e f
#define
.......
.......
.......
#endif

_PUNKT1_H
_PUNKT1_H
................
................
................

zapobiegaj wielokrotnemu doczaniu tego pliku do innego.

67

4.7. Wskanik do obiektu this

4.7. Wskanik do obiektu this


W jzyku C++ ze wzgldu na oszczdno pamici istnieje tylko jedna
kopia funkcji danej klasy. Obiekty danej klasy korzystaj wsplnie z tej
funkcji, natomiast kady obiekt ma swoj wasn kopi danych skadowych.
Funkcje skadowe s zwizane z denicj klasy a nie z deklaracjami obiektw
tej klasy. Cech charakterystyczn funkcji skadowych klasy jest fakt, e w
kadej takiej funkcji jest zadeklarowany wskanik specjalny this jako:
X_klas const t h i s ;

gdzie X klas jest nazw klasy. Ten wskanik jest inicjalizowany wskanikiem
do obiektu, dla ktrego wywoano funkcj skadow. Wskanik this zadeklarowany jest jako *const, co oznacza, e nie mona go zmienia. Wskanik
this jest niejawnie uywany podczas odwoywania si zarwno do danych jak
i funkcji skadowych obiektu. Wskanik this jest przekazywany do obiektu
jako niejawny argument kadej niestatycznej funkcji skadowej wywoywanej
na rzecz obiektu. Uywanie wskanika this w odwoaniach nie jest konieczne, ale czsto stosuje si go do pisania metod, ktre dziaaj bezporednio
na wskanikach. Wskanik this moe by wykorzystywany take w sposb
jawny, co jest pokazane w kolejnym przykadzie.
Listing 4.7. Wskaznik this
1

#include <i o s t r e a m . h>


#include <c o n i o . h>

class Liczba
{ public :
Liczba ( f l oa t = 0) ;
void pokaz ( ) ;
private :
f l o a t nr ;
};

// k o n s t r u k t o r domyslny

11

L i c z b a : : L i c z b a ( f l o a t xx ) { nr = xx ; } // k o n s t r u k t o r
13

15

17

19

21

23

void L i c z b a : : pokaz ( )
{
c o u t << " nr = " << nr << e n d l ;
c o u t << " t h i s >= " << t h i s > nr << e n d l ;
c o u t << " ( t h i s ) . nr = " << ( t h i s ) . nr << e n d l ;
}
i n t main ( )
{ Liczba ob iek t ( 1 . 0 1 ) ;
o b i e k t . pokaz ( ) ;
getch () ;

68

4. Konstruktory i destruktory
return 0 ;

25

Elementami klasy Liczba s dwie metody publiczne i prywatna dana


oat nr. Wskanik this zosta wykorzystany przez funkcj skadow pokaz()
do wydrukowania prywatnej danej skadowej nr egzemplarza klasy:
void L i c z b a : : pokaz ( )
{
c o u t << " nr = " << nr << e n d l ;
c o u t << " t h i s >= " << t h i s > nr << e n d l ;
c o u t << " ( t h i s ) . nr = " << ( t h i s ) . nr << e n d l ;
}

Funkcja skadowa pokaz() trzy razy wywietla warto danej skadowej nr.
W pierwszej instrukcji mamy klasyczn metod bezporedni dostp do
warto nr. Funkcja skadowa ma dostp nawet do prywatnych danych swojej klasy. W drugiej instrukcji:
c o u t << " t h i s >= " << t h i s > nr << e n d l ;

wykorzystano operator strzaki do wskanika. Jest to popularna jawna metoda stosowania wskanika this. W nastpnej instrukcji zastosowano operator
kropki dla wskanika po dereferencji:
c o u t << " ( t h i s ) . nr = " << ( t h i s ) . nr << e n d l ;

Jak ju mwiono nawias w zapisie (*this) jest konieczny.

4.8. Wskanik this kaskadowe wywoania funkcji


Wskanik this umoliwia tzw. kaskadowe wywoania funkcji skadowych.
W programie przedstawione zostao zwracanie referencji do obiektu klasy
Punkt3D. W pliku punkt3D.cpp, ktry zawiera denicje funkcji skadowych
Punkt3D kada z funkcji:
// u s t a w i a c a l y p u n k t
ustawPunkt ( int , int , i n t ) ;
ustawX ( i n t ) ;
// u s t a w i a X
ustawY ( i n t ) ;
// u s t a w i a Y
ustawZ ( i n t ) ;
// u s t a w i a Z

zwraca *this typu Punkt3D &. Biorc po uwag, e operator kropki wie
od strony lewej do prawej, wiemy, e w wyraeniu:
p1 . ustawX ( 1 0 ) . ustawY ( 2 0 ) . ustawZ ( 3 0 ) ;

4.8. Wskanik this kaskadowe wywoania funkcji


najpierw bdzie wywoana funkcja ustawX( 10 ), ktra zwrci referencj do
obiektu p1, dziki czemu pozostaa cz przybiera posta:
p1 . ustawY ( 2 0 ) . ustawZ ( 3 0 ) ;

Kolejno wywoana funkcja ustawY ( 20 ) zwrci ponownie referencje do


obiektu p1 i mamy ostatecznie:
p1 . ustawZ ( 3 0 ) ;

W ten sam sposb realizowane jest wywoanie kaskadowe w wyraeniu:


p1 . ustawPunkt ( 5 0 , 5 0 , 5 0 ) . pokaz ( ) ;

W tym wyraeniu naley zwrci uwag na zachowanie kolejnoci. Funkcja


pokaz() nie zwraca referencji do p1, wobec tego musi by umieszczona na
kocu.
Listing 4.8. Kaskadowe wywoanie metod - deklaracja klasy - plik
punkt3D.h
1

// k l a s a Punkt3D
#i f n d e f _PUNKT3D_H
#define _PUNKT3D_H

11

13

c l a s s Punkt3D
{
public :
Punkt3D ( i n t = 0 , i n t = 0 , i n t = 0 ) ; // k o n s t r u k t o r
Punkt3D &ustawPunkt ( int , int , i n t ) ; // u s t a w i a
// c a l y p u n k t
// u s t a w i a X
Punkt3D &ustawX ( i n t ) ;
Punkt3D &ustawY ( i n t ) ;
// u s t a w i a Y
// u s t a w i a Z
Punkt3D &ustawZ ( i n t ) ;

15

17

19

i n t p o b i e r z X ( ) const ; // p o b i e r a X
i n t p o b i e r z Y ( ) const ; // p o b i e r a Y
i n t p o b i e r z Z ( ) const ; // p o b i e r a Z
void pokaz ( ) const ;
// p o k a z u j e w s p o l r z e d n e
// punktu X, Y, Z

21

23

25

27

private :
int x ;
int y ;
int z ;
};
#endif

69

70

4. Konstruktory i destruktory
Listing 4.9. Kaskadowe wywoanie metod - denicja klasy - plik
punkt3D.cpp
1

// d e f i n i c j e k l a s y Punkt3D
#include "punkt3D . h"
#include <i o s t r e a m . h>

11

13

15

17

19

21

Punkt3D : : Punkt3D ( i n t xx , i n t yy , i n t z z )
{ ustawPunkt ( xx , yy , z z ) ; }
Punkt3D &Punkt3D : : ustawPunkt ( i n t x1 , i n t y1 , i n t z1 )
{
ustawX ( x1 ) ;
ustawY ( y1 ) ;
ustawZ ( z1 ) ;
return t h i s ;
}
Punkt3D
{ x =
Punkt3D
{ y =
Punkt3D
{ z =

&Punkt3D : : ustawX ( i n t x1 )
x1 ; return t h i s ; }
&Punkt3D : : ustawY ( i n t y1 )
y1 ; return t h i s ; }
&Punkt3D : : ustawZ ( i n t z1 )
z1 ; return t h i s ; }

23

25

i n t Punkt3D : : p o b i e r z X ( ) const
i n t Punkt3D : : p o b i e r z Y ( ) const
i n t Punkt3D : : p o b i e r z Z ( ) const

{ return x ; }
{ return y ; }
{ return z ; }

27

29

31

void Punkt3D : : pokaz ( ) const


{ c o u t << " x= " << x << e n d l ;
c o u t << " y= " << y << e n d l ;
c o u t << " z = " << z << e n d l ;
}

Listing 4.10. Kaskadowe wywoanie metod - testowanie - plik ftest1.cpp


2

10

12

// Kaskadowe w y w o l a n i e metod
#include <i o s t r e a m . h>
#include <c o n i o . h>
#include "punkt3D . h"
i n t main ( )
{ Punkt3D p1 , p2 ;
p1 . ustawX ( 1 0 ) . ustawY ( 2 0 ) . ustawZ ( 3 0 ) ;
c o u t << " punkt s t a r t o w y p1" << e n d l ;
p1 . pokaz ( ) ;
c o u t << " nowy punkt p1 " << e n d l ;
p1 . ustawPunkt ( 5 0 , 5 0 , 5 0 ) . pokaz ( ) ;

71

4.9. Tablice obiektw


c o u t << " inny punkt p2 " << e n d l ;
p2 . ustawPunkt ( 1 0 0 , 1 0 0 , 1 0 0 ) . pokaz ( ) ;
getch () ;
return 0 ;

14

16

Wynikiem dziaania programu jest nastpujcy wydruk:

4.9. Tablice obiektw


Obiekty klas s zmiennymi deniowanymi przez uytkownika i dziaaj
analogicznie jak zmienne typw wbudowanych. Zgodnie z tym moemy grupowa obiekty w tablicach. Deklaracja tablicy obiektw jest identyczna jak
deklaracja tablicy zmiennych innych typw. Dla przypomnienia, w jzyku
C++ tablice obiektw wbudowanych maj posta:
i n t temp [ 5 0 ] ;
f l o a t waga [ 1 0 0 ] ;
char tabWsk [ 1 0 ] ;

// 50 elementowa t a b l i c a t y p u i n t
// 100 elementowa t a b l i c a t y p u f l o a t
// 10 elementowa t a b l i c a wskazni kow do c h a r

W podobny sposb tworzona jest tablica obiektw jakiej klasy. Niech klasa
punkt ma bardzo prost posta;
c l a s s punkt
{ public :
int x ; int y ;
};

Tablica obiektw klasy punkt moe mie posta;


punkt ptab [ 1 0 ] ;

Powysz denicj moemy czyta w nastpujcy sposb: ptab jest 10 elementow tablic obiektw klasy punkt. W tej prostej klasie wszystkie dane
s publiczne, wic dostp do nich jest nastpujcy:

72

4. Konstruktory i destruktory
c o u t << ptab [ 1 ] . x ;
c o u t << ptab [ 5 ] . y ;

Dostp do elementu tablicy realizowany jest przy pomocy dwch operatorw. Waciwy element tablicy jest wskazywany za pomoc operatora
indeksu [], po czym stosujemy operator kropki (.) wydzielajcy okrelon zmienn skadow obiektu. Moemy take zdeniowa wskanik, ktry
bdzie wskazywa na obiekty klasy punkt:
punkt

pWsk ;

W instrukcji:
pWsk = & ptab [ 5 ] ;

ustawiono wskanik tak aby wskazywa na konkretny element tablicy. Przy


pomocy wskanika moemy odwoa si do danych klasy:
pWsk

> x ;

W pokazanym programie tworzona jest tablica obiektw prostej klasy punkt:


c l a s s punkt
{ public :
int x ; int y ;
};

Zasadniczym problemem jest inicjalizacja, czyli nadawanie wartoci pocztkowych w momencie denicji obiektu. Mog wystpi trzy przypadki:
tablica jest agregatem
tablica nie jest agregatem
tablica jest tworzona dynamicznie ( operator new)
Agregatem nazywamy tablic obiektw bdcych egzemplarzami klasy, ktra nie ma danych prywatnych i nie ma konstruktorw. W naszym
przykadzie mamy do czynienia z agregatem. W takim przypadku tablica
obiektw jest inicjalizowana do prosto: list inicjalizatorw umieszczamy
w nawiasach klamrowych i oddzielamy przecinkami:
punkt ptab [ 5 ] =
{ 0, 0,
1, 1,
2, 2,
3, 3,
1 0 , 20
};

// i n i c j a l i z a c j a
// x i y d l a p t a b
// x i y d l a p t a b
// x i y d l a p t a b
// x i y d l a p t a b
// x i y d l a p t a b

tablicy
[0]
[1]
[2]
[3]
[4]

4.10. Inicjalizacja tablic obiektw nie bdcych agregatami


Oto przykad tworzenia tablicy obiektw i jej inicjalizowanie. W programie zademonstrowano take uycie wskanikw do elementw obiektu.
Listing 4.11. Tablica obiektw - agregaty
1

#include <i o s t r e a m . h>


#include <c o n i o . h>
c l a s s punkt
{ public :
int x ; int y ;
};

11

13

15

17

19

21

23

i n t main ( )
{ punkt pWsk ;
// w s k a z n i k na o b i e k t t y p u p u n k t
punkt ptab [ 5 ] =
// i n i c j a l i z a c j a t a b l i c y
{ 0, 0,
// x i y d l a p t a b [ 0 ]
1, 1,
// x i y d l a p t a b [ 1 ]
2, 2,
// x i y d l a p t a b [ 2 ]
3, 3,
// x i y d l a p t a b [ 3 ]
1 0 , 20
// x i y d l a p t a b [ 4 ]
};
c o u t << "x y " << e n d l ;
f o r ( i n t i =0; i <5; i ++)
c o u t << ptab [ i ] . x << " " << ptab [ i ] . y << e n d l ;
pWsk = &ptab [ 4 ] ;
// w s k a z n i k i n i c j a l i z o w a n y adresem
c o u t << " d o s t e p p r z e z wsk aznik x= " << pWsk > x ;
getch () ;
return 0 ;
}

Efektem dziaania programu jest nastpujcy wydruk:

4.10. Inicjalizacja tablic obiektw nie bdcych agregatami


W przypadku, gdy mamy do czynienia z danymi prywatnymi w danej
klasie, aby zainicjalizowa tablic obiektw musimy posuy si konstruktorem. Program ilustruje zagadnienie inicjalizacji tablicy obiektw w takim
przypadku. Naley zwrci uwag na deklaracj klasy widzimy tam zwyky
konstruktor i konstruktor domniemany.

73

74

4. Konstruktory i destruktory
Listing 4.12. Tablica obiektw - tablica nie jest agregatem
2

10

12

14

16

18

20

22

24

26

28

30

#include <i o s t r e a m . h>


#include <c o n i o . h>
c l a s s punkt
{ private :
int x ; int y ;
public :
punkt ( i n t xx , i n t yy ) ;
// k o n s t r u k t o r
punkt ( ) ;
// k o n s t r u k t o r domyslny
void pokaz ( ) ;
};
punkt : : punkt ( i n t xx , i n t yy ) : x ( xx ) , y ( yy ) { } ;
punkt : : punkt ( ) { x = 0 ; y = 0 ; }
void punkt : : pokaz ( )
{ c o u t << x << " " << y << e n d l ; }
i n t main ( )
// i l o s c punktow , r o z m i a r t a b l i c y
{ const i n t nr = 5 ;
punkt ptab [ nr ] =
// t a b l i c a o b i e k t o w
{
punkt ( 1 5 , 1 5 ) ,
// k o n s t r u k t o r
punkt ( 1 0 , 1 0 ) ,
punkt ( 2 0 , 2 0 ) ,
punkt ( ) ,
// k o n s t r u k t o r domyslny
punkt ( 1 , 1 )
} ;
c o u t << "x y " << e n d l ;
f o r ( i n t i =0; i <nr ; i ++)
ptab [ i ] . pokaz ( ) ;
getch () ;
return 0 ;
}

Efektem dziaania programu jest wydruk:

Denicje konstruktorw maj posta:


punkt : : punkt ( i n t xx , i n t yy ) : x ( xx ) , y ( yy ) { } ;
punkt : : punkt ( ) { x = 0 ; y = 0 ; }
// k o n s t r u k t o r domyslny

W denicji konstruktora naley zwrci uwag na inicjalizacj skadowych x


i y wykorzystano list inicjalizacyjn poprzedzon dwukropkiem. Denicja
5 elementowej tablicy obiektw klasy punkt ma posta:

4.11. Tablice obiektw tworzone dynamicznie


const i n t nr = 5 ;
punkt ptab [ nr ] =
{
punkt ( 1 5 , 1 5 ) ,
punkt ( 1 0 , 1 0 ) ,
punkt ( 2 0 , 2 0 ) ,
punkt ( ) ,
punkt ( 1 , 1 )
} ;

// i l o s c punktow , r o z m i a r t a b l i c y
// t a b l i c a o b i e k t o w
// k o n s t r u k t o r

// k o n s t r u k t o r domyslny

Widzimy, e pomidzy nawiasami klamrowymi umieszczona jest lista inicjalizatorw, oddzielonych przecinkami. Poszczeglne wywoania konstruktorw inicjalizuj tablic obiektw. Konstruktor domniemany nie jest konieczny, ale jest dobrym zwyczajem umieszcza go. W sytuacji, gdy na przykad
tablica ma 10 elementw a na licie inicjalizatorw umieszczono jedynie 5
konstruktorw, wtedy kompilator niejawnie wywouje dla pozostaych elementw tablicy obiektw konstruktor domylny. Gdy w takiej sytuacji nie
bdzie konstruktora domylnego, kompilator zasygnalizuje bd.

4.11. Tablice obiektw tworzone dynamicznie


W przypadku tworzenia tablic obiektw w pamici swobodnej (tablice
dynamiczne) naley pamita, e nie mona ich jawnie inicjalizowa. Obowizuj zasady:
klasa nie ma adnego konstruktora
klasa ma konstruktor domniemany
Inicjowanie tablic obiektw w takich przypadkach odbywa si za pomoc funkcji skadowych. Pokazany program tworzy tablic 5 obiektw typu
punkt w pamici swobodnej:
const i n t nr = 5 ; // r o z m i a r t a b l i c y
punkt twsk ;
// w s k a z n i k
twsk = new punkt [ nr ] ; // t a b l i c a o b i e k t o w

W powyszym fragmencie mamy deklaracj tablicy twsk zawierajc 5 elementw typu punkt. Caa tablica jest tworzona na stercie, za pomoc wywoania new punkt[nr]. Usunicie tablicy twsk automatycznie zwrci ca
przydzielon pami gdy zostanie uyty operator delete:
delete [ ] twsk ;

Listing 4.13. Tablica obiektw - alokacja dynamiczna


1

#include <i o s t r e a m . h>

75

76

4. Konstruktory i destruktory
#include <c o n i o . h>
3

11

13

c l a s s punkt
{
private :
int x , y ;
public :
punkt ( )
void s e t
{
i n t getX
i n t getY
};

{};
// k o n s t r u k t o r domyslny
( i n t xx , i n t yy )
// f u n k c j a s k l a d o w a
x = xx ; y = yy ; }
( ) { return x ; }
// a k c e s o r
( ) { return y ; }

15

17

19

21

23

25

27

29

31

33

i n t main ( )
{
const i n t nr = 5 ;
punkt twsk ;
twsk = new punkt [ nr ] ;
i f ( ! twsk )
{ c e r r << " nieudana a l o k a c j a \n" ;
return 1 ;
}
f o r ( i n t i = 0 ; i < nr ; i ++)
{
twsk [ i ] . s e t ( i +1 , i +3) ;
c o u t << twsk [ i ] . getX ( )<< " " << twsk [ i ] . getY ( ) <<e n d l ;
}
delete [ ] twsk ;
getch () ;
return 0 ;
}

Efektem dziaania programu jest wydruk:

Inicjowanie tablicy obiektw realizowane jest w ptli for przy pomocy


funkcji skadowej set(int, int):
twsk [ i ] . s e t ( i +1 , i +3) ;

Wypisywanie wartoci danych realizowane jest przy pomocy akcesorw getX()


i getY():

4.12. Kopiowanie obiektw


c o u t << twsk [ i ] . getX ( )<< " " << twsk [ i ] . getY ( ) <<e n d l ;

4.12. Kopiowanie obiektw


Obiekty tej samej klasy mog by przypisywane sobie za pomoc domylnego kopiowania skadowych. W tym celu wykorzystywany jest operator przypisania (=). Kopiowanie obiektw przez wywoanie domylnego
konstruktora kopiujcego daje poprawne wyniki jedynie dla obiektw, ktre
nie zawieraj wskaza na inne obiekty, na przykad, gdy klasa wykorzystuje
dane skadowe, ktrym dynamicznie przydzielona jest pami operacyjna.
W programie tworzone s dwa obiekty klasy Data o nazwach d1 i d2 :
Data d1 ( 3 1 , 1 , 2 0 0 2 ) , d2 ;

Obiekt d1 jest inicjalizowany jawnym konstruktorem, za obiekt d2 inicjalizowany jest konstruktorem domylnym.
Listing 4.14. Kopiowanie obiektw
1

11

13

15

17

19

21

23

25

27

#include <i o s t r e a m . h>


#include <c o n i o . h>
c l a s s Data
{ public :
Data ( i n t = 1 , i n t = 1 , i n t = 2 0 0 0 ) ;
void pokaz ( ) ;
private :
int dzien ;
int miesiac ;
i n t rok ;
};
Data : : Data ( i n t kd , i n t km, i n t k r )
{ d z i e n = kd ;
m i e s i a c = km ;
rok = k r ;
}
void Data : : pokaz ( )
{ c o u t << d z i e n << " . " << m i e s i a c << " . " << rok << e n d l ;
}
i n t main ( )
{ Data d1 ( 3 1 , 1 , 2 0 0 2 ) , d2 ;
c o u t << " d1= " ;
d1 . pokaz ( ) ;
c o u t << " d2= " ;
d2 . pokaz ( ) ;
d2 = d1 ;
c o u t << " p r z y p i s a n i e , d2 = " ;

77

78

4. Konstruktory i destruktory
d2 . pokaz ( ) ;
c o u t << " tworzy o b i e k t d3 = " ;
Data d3 = d1 ;
d3 . pokaz ( ) ;
getch () ;
return 0 ;

29

31

33

35

Efektem dziaania programu jest nastpujcy wydruk:

W instrukcji:
d2 = d1 ;

mamy proste przypisanie, obiektowi d2 przypisane s skadowe obiektu d1.


W kolejnej instrukcji:
Data d3 = d1 ;

Tworzony jest nowy obiekt klasy Data o nazwie d3 i jemu przypisane s


skadowe obiektu d1.

4.13. Klasa z obiektami innych klas


Bardzo czsto klasy korzystaj z obiektw innych klas. Tego typu konstrukcje nazywamy zoeniem (ang. composition). Tworzymy wieloplikowy
program do rejestracji osb. Tworzymy dwie klasy Data i Osoba. Klasa
Osoba zawiera skadowe: imie, nazwisko, miasto oraz dataUrodzenia. Konstruktor ma posta:
Osoba : : Osoba ( char wsimie , char wsnazwisko ,
char wsmiasto , i n t urDzien ,
i n t u r M i e s i a c , i n t urRok )
: d a t a U r o d z e n i a ( urDzien , u r M i e s i a c , urRok )

Konstruktor ma sze parametrw, znak dwukropka rozdziela list parametrw od listy inicjatorw skadowych. Inicjatory skadowych przekazuj
argumenty konstruktora Osoba do konstruktorw obiektw skadowych. Na
kolejnych wydrukach pokazano deklaracje klas Osoba i Data, ich denicje i
w pitym pliku pokazano program testujcy. W klasie Osoba:

4.13. Klasa z obiektami innych klas


c l a s s Osoba
{ public :
Osoba ( char , char , char , int , int , i n t ) ;
void pokaz ( ) const ;
private :
char i m i e [ 3 0 ] ;
char nazwisk o [ 3 0 ] ;
char m i a s t o [ 3 0 ] ;
const Data d a t a U r o d z e n i a ;
};

widzimy skadow dataUrodzenia, ktra jest obiektem klasy Data.


Listing 4.15. Klasa wykorzystuje obiekt innej klasy - plik pracow.h
2

10

12

14

16

// pracow . h
// d e k l a r a c j a k l a s y Pracownik
#i f n d e f PRACOW1_H
#define PRACOW1_H
#include " data1 . h"
c l a s s Osoba
{ public :
Osoba ( char , char , char , int , int , i n t ) ;
void pokaz ( ) const ;
private :
char i m i e [ 3 0 ] ;
char nazwisk o [ 3 0 ] ;
char m i a s t o [ 3 0 ] ;
const Data d a t a U r o d z e n i a ;
};
#endif

Wystpujce w kodzie dyrektywy preprocesora:


#i f n d e f PRACOW1_H
#define PRACOW1_H
........................................
#endif

zapobiegaj wielokrotnego wczania tych samych plikw do programu.


Listing 4.16. Klasa wykorzystuje obiekt innej klasy - plik pracow.cpp
2

// pracow . cpp
// d e f i n i c j e f u n k c j i s k l a d o w y c h k l a s y Pracownik
#include <i o s t r e a m . h>
#include <s t r i n g . h>
#include " pracow . h"
#include " data1 . h"

79

80

4. Konstruktory i destruktory
8

10

12

14

16

18

20

22

24

26

Osoba : : Osoba ( char wsimie , char wsnazwisko ,


char wsmiasto , i n t urDzien , i n t u r M i e s i a c , i n t urRok )
: d a t a U r o d z e n i a ( urDzien , u r M i e s i a c , urRok )
{ i n t d l u g o s c = s t r l e n ( wsimie ) ;
s t r n c p y ( imie , wsimie , d l u g o s c ) ;
i m i e [ d l u g o s c ] = \0 ;
d l u g o s c = s t r l e n ( wsnazwisk o ) ;
s t r n c p y ( nazwisk o , wsnazwisko , d l u g o s c ) ;
i m i e [ d l u g o s c ] = \0 ;
d l u g o s c = s t r l e n ( wsmiasto ) ;
s t r n c p y ( miasto , wsmiasto , d l u g o s c ) ;
i m i e [ d l u g o s c ] = \0 ;
}
void Osoba : : pokaz ( ) const
{ c o u t << nazwisk o << " , " << i m i e << e n d l ;
c o u t << " m i e j s c e u r o d z e n i a : " << m i a s t o << e n d l ;
c o u t << " Data u r o d z e n i a : " ;
d a t a U r o d z e n i a . pokaz ( ) ;
c o u t << e n d l ;
}

Do obsugi acuchw wykorzystano funkcje biblioteczne z pliku <string.h>:


i n t d l u g o s c = s t r l e n ( wsimie ) ;
s t r n c p y ( imie , wsimie , d l u g o s c ) ;
i m i e [ d l u g o s c ] = \0 ;

Funkcja strlen() podaje dugo acucha, a funkcja strcpy() kopiuje ten


acuch. W trzeciej linii instrukcja koczy tablic znakw znakiem koca
acucha \0. Nie przeprowadzono kontroli dugoci napisw. Dla wszystkich trzech napisw zarezerwowano tablice 30 elementowe, tzn. acuchy
nie mog zawiera wicej ni 29 znakw. Gdy imi lub nazwisko jest krtsze, wtedy zarezerwowane elementy tablicy niepotrzebnie bd zajmowa
pami. W zasadzie naleaoby zastosowa dynamiczny przydzia pamici.
Listing 4.17. Klasa wykorzystuje obiekt innej klasy - plik data1.h
1

11

// d a t a 1 . h ,
d e k l a r a c j a k l a s y Data
#i f n d e f DATA1_H
#define DATA1_H
c l a s s Data
{ public :
Data ( i n t = 1 , i n t = 1 , i n t = 1 9 0 0 ) ;
void pokaz ( ) const ;
private :
int dzien ;
int miesiac ;
i n t rok ;
};

4.13. Klasa z obiektami innych klas


13

#endif

Listing 4.18. Klasa wykorzystuje obiekt innej klasy - plik data1.cpp


1

// d a t a 1 . cpp , d e f i n i c j e f u n k c j i s k l a d o w y c h k l a s y Data
#include <i o s t r e a m . h>
#include " data1 . h"
Data : : Data ( i n t d , i n t m, i n t r )
{ dzien = d ;
m i e s i a c = m;
rok = r ;
}

11

void Data : : pokaz ( ) const


{ c o u t << d z i e n << " . " << m i e s i a c << " . " << rok ;
}

Listing 4.19. Klasa wykorzystuje obiekt innej klasy - plik pracow.cpp


2

// k l a s a z o b i e k t e m i n n e j k l a s y
#include <v c l . h>
#include <i o s t r e a m . h>
#include <c o n i o . h>
#include " pracow . h"

10

12

i n t main ( )
{ Osoba o s ( "Ewa" , " F a s o l a " , " L u b l i n " , 1 3 , 3 , 1 9 6 6 ) ;
c o u t << \n ;
o s . pokaz ( ) ;
getch () ;
return 0 ;
}

Efektem wykonania tego programu jest wydruk: Naley zwrci uwa-

g na do skomplikowany sposb obsugi acuchw. By moe lepszym


wyjciem byoby wykorzystanie moliwoci obiektw biblioteki string.

81

Rozdzia 5
Dziedziczenie i hierarchia klas

5.1.
5.2.
5.3.
5.4.
5.5.
5.6.
5.7.

Wstp . . . . . . . . . . . . . . . . . . . . . .
Hierarchiczna struktura dziedziczenia . . . .
Notacja UML (Unied Modeling Language) .
Proste klasy pochodne . . . . . . . . . . . .
Konstruktory w klasach pochodnych . . . . .
Dziedziczenie kaskadowe . . . . . . . . . . .
Dziedziczenie wielokrotne bezporednie . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

. 84
. 84
. 88
. 92
. 98
. 104
. 117

84

5. Dziedziczenie i hierarchia klas

5.1. Wstp
Najistotniejszymi cechami programowania zorientowanego obiektowo s
dziedziczenie (ang. inheritance), polimorzm (ang. polimorphism) i dynamiczne zwizywanie (ang. dynamic binding). Dziedziczenie jest form ponownego uywania kodu uprzednio opracowanej klasy w nowotworzonej klasie pochodnej. Deniowana na nowo klasa przejmuje pewne cechy od innych
zdeniowanych ju klas, dziedziczc po nich wybrane metody i pola. W procesie tworzenia nowej klasy, programista moe wykorzysta istniejc ju
klas i okreli, e nowa klasa odziedziczy pewne dane i funkcje skadowe.
Istniejc star klas nazywamy klas podstawow lub bazow (ang. base
class, parent class, or superclass), a nowa klasa dziedziczca z klasy podstawowej dane i funkcje nazywana jest klas pochodn (ang. derived class,
child class, or subclass). Mwimy, e klasa pochodna dziedziczy wszystkie
cechy swojej klasy bazowej. Dziki mechanizmowi dziedziczenie w klasie
pochodnej moemy:
doda nowe zmienne i funkcje skadowe
przedeniowa funkcje skadowe klasy bazowej
Wynika z tego, e klasa pochodna jest bardziej szczegowa od bazowej i
jest wiksza, w tym sensie, e moe mie wicej danych i funkcji skadowych
ni klasa bazowa.
W jzyku C++ moliwe s trzy rodzaje dziedziczenia: publiczne, chronione oraz prywatne. Nas najbardziej bdzie interesowa dziedziczenie publiczne. Klasy pochodne nie maj dostpu do tych skadowych klasy bazowej, ktre zostay zadeklarowane jako prywatne (private). W przypadku
dziedziczenia publicznego dane i metody klasy bazowej zadeklarowane jako
public i private staj si odpowiednio skadowymi publicznymi oraz prywatnymi klasy pochodnej. Naley zwrci take uwag na fakt, e funkcje
zaprzyjanione z klas nie s dziedziczone, take konstruktory i destruktory
nie s dziedziczone. Obiekty klas pochodnych mog by traktowane jako
obiekty klasy podstawowej.

5.2. Hierarchiczna struktura dziedziczenia


Klasy pochodne mog by tworzone na rne sposoby: mog dziedziczy cechy jednej klasy, mog dziedziczy cechy wielu klas. Klasa C moe
dziedziczy cechy klasy B, ktra dziedziczy cechy klasy A. Moemy mie
take sytuacj, gdy klasa C dziedziczy jednoczenie (bezporednio) cechy
klasy A i B. Wykorzystujc schemat dziedziczenia moemy budowa hierarchiczne, wielopoziomowe struktury klas. Jeeli w hierarchicznej strukturze
dziedziczenia, kada klasa dziedziczy tylko cechy jednej klasy, to otrzymu-

5.2. Hierarchiczna struktura dziedziczenia


jemy struktur drzewiast. Jeeli klasa pochodna dziedziczy cechy wielu
innych klas to otrzymujemy struktur zwan grafem acyklicznym.
Podstawowa klasykacja ma posta:
Dziedziczenie pojedyncze (jednokrotne) (ang. simple inheritance)
Dziedziczenie mnogie (wielokrotne) (ang. multiple inheritance)
Rozpatrzmy teraz prost struktur dziedziczenia. Zamy, e nasz program musi obsuy gury geometryczne takie jak np.: koo, walec, stoek.
Te gury maj wspln cech promie. Relacje pomidzy tymi gurami
pokazuje rysunek. Cechy klasy bazowej opisujcej koo, moe przej klas
pochodna opisujca walec oraz klasa pochodna opisujca stoek.

Rysunek 5.1. Relacje pomidzy obiektami i struktura dziedziczenia

Rozpatrzmy program obsugujcy koo i walec. Mona przyj, e klasa


modelujca koo bdzie klas bazow dla klasy opisujcej walec. Schemat
dziedziczenia pokazany jest na rysunku.
Zgodnie z konwencj, strzaka zawsze jest skierowana od klasy pochodnej
do klasy bazowej. Relacja ukazana na rysunku 5.2 jest przykadem prostego
dziedziczenia, kada klasa pochodna ma tylko jedn, bezporedni klas
bazow. Jeeli w naszym programie zechcemy obsuy kolejn gur, jak jest stoek, to ponownie moemy skorzysta z klasy Kolo, ktra bdzie
klas bazow dla nowej klasy pochodnej Stozek. Nowa hierarchia dziedzi-

85

86

5. Dziedziczenie i hierarchia klas

Rysunek 5.2. Hierarchiczna struktura dziedziczenia dziedziczenie proste

czenia pokazana jest na rysunku, w dalszym cigu mamy do czynienia z


dziedziczeniem prostym.

Rysunek 5.3. Hierarchiczna struktura dziedziczenia dziedziczenie proste

Moemy opracowa bardziej zoony program, np. program rysujcy walec. Elementem gury geometrycznej takiej jak walec jest koo. Rysujc koo
na ekranie, musimy zna pooenie rodka koa. Wygodnie jest wiec opracowa klas Punkt. Taka klasa bdzie obsugiwaa wsprzdne kartezjaskie
punktu. Tworzc klas obsugujc koo (nazwijmy t klas Kolo)moemy
wykorzysta klas Punkt. Klasa Punkt bdzie klas bazow klasy pochodnej Kolo. W tym przypadku mamy do czynienia z dziedziczeniem prostym
(klasa Kolo dziedziczy bezporednio cechy klasy Punkt). Majc opracowana

5.2. Hierarchiczna struktura dziedziczenia


klas Kolo, moemy opracowa klas obsugujc walec (nazwijmy ta klas
Walec). Klasa Walec dziedziczy bezporednio cechy klasy Kolo, klasa Kolo
dziedziczy bezporedni cechy klasy Punkt. Klasa Walec dziedziczy porednio cechy klasy Punkt. Mamy tu do czynienia z dziedziczeniem kaskadowym,
typ dziedziczenia to dziedziczenie proste. Opisana hierarchiczna struktura
dziedziczenia pokazana jest na rysunku 5.4.

Rysunek 5.4. Hierarchiczna struktura dziedziczenia dziedziczenie proste, kaskadowe

Opracowujc program obsugi walca, mamy take moliwo innego zaprojektowania klasy Walec. Klasa Walec moe jednoczenie dziedziczy cechy klasy Punkt i cechy klasy Kolo. W takim przypadku mamy do czynienia
z dziedziczeniem mnogim. Tego typu hierarchia dziedziczenia pokazana jest
na rysunku 5.5
Moliwe s bardziej skomplikowane struktury dziedziczenia mnogiego.
Przykad takiej rozbudowanej hierarchii pokazany jest na rysunku 5.6.
W praktyce hierarchiczne struktury dziedziczenia mog by bardzo skomplikowane. Rozwamy program obsugujcy osoby zwizane z uczelni. Najbardziej oglny podzia to pracownicy uczelni i studenci. Pracownicy uczelni
mog dzieli si na pracownikw administracji i nauczycieli akademickich.
Tworzc model osb zwizanych z uczelni (opracowujc odpowiednie klasy) moemy zaprojektowa struktur dziedziczenia pokazan na rysunku
5.7. Naley zwrci uwag na klas P Funkcyjny. Ta klasa ma za zadanie obsuy pracownikw uczelni, ktrzy sprawuj funkcje takie jak np.
rektor czy dziekan. S oni bardzo czsto jednoczenie pracownikami dydaktycznymi i pracownikami administracji. W tym przypadku projektujc

87

88

5. Dziedziczenie i hierarchia klas

Rysunek 5.5. Hierarchiczna struktura dziedziczenia dziedziczenie mnogie,


dwu-bazowe

Rysunek 5.6. Hierarchiczna struktura dziedziczenia dziedziczenie mnogie (klasa


pochodna 1 2), skierowany graf acykliczny

klas P Funkcyjny korzystamy z dziedziczenia mnogiego (klasa P Funkcyjny


dziedziczy jednoczenie cechy klasy P Dydaktyczny i P Administracji).

5.3. Notacja UML (Unied Modeling Language)

Rysunek 5.7. Hierarchiczna struktura dziedziczenia model osb zwizanych z


uczelni

5.3. Notacja UML (Unied Modeling Language)


Jak wida z przytoczonych przykadw, hierarchia struktury klas moe
by cakiem skomplikowana, szczeglnie gdy wzrasta ilo klas oraz ich wzajemne relacje zwizane z dziedziczeniem. Programy obiektowe s tworzone w
postaci systemw wsppracujcych klas, ktre obejmuj zarwno dane jak
i metody. Bardzo czsto do uwidocznienia wzajemnych relacji pomidzy klasami oraz danymi i metodami klas stosowany jest jzyk UML (ujednolicony
jzyk programowania, ang. unied modeling language). UML jest jzykiem
o szerokim zakresie zastosowa, mona go uywa w rnych typach systemw, dziedzin i procesw. Formalnie rzecz biorc, UML jest jzykiem sucym do specykacji, wizualizacji, konstrukcji i dokumentacji artefaktw
procesu zorientowanego na system. Specykacja obejmuje tworzenie modelu
opisujcego system. Model jest opisywany za pomoc diagramw. Dla naszych celw wykorzystamy notacje UML do opisu klas i obiektw. Dziki
uyciu odpowiednich diagramw skomplikowane relacje pomidzy klasami
bazowymi i pochodnymi stan si bardziej jasne. Obiekty w jzyku UML
traktowane s jako egzemplarze klasy. Klasa jest formalnie typem obiektu.
Klasa opisuje atrybuty oraz zachowanie obiektu. W jzyku UML klasa moe by przedstawiona w postaci gracznej tworzony jest diagram klasy.
Jest to prostokt podzielony na trzy czci, kada cz przechowuje inn
informacj:
nazwa klasy
atrybuty (dane)
lista operacji ( metody)
Oglny sposb przedstawiania klasy w jzyku UML pokazany jest na rysunku.
Jako przykad przedstawimy diagram klasy Punkt. Niech deklaracja klasy Punkt ma posta:

89

90

5. Dziedziczenie i hierarchia klas

Rysunek 5.8. Oglny diagram klasy

c l a s s Punkt
{
private :
int x ;
int y ;
public :
void i n i t ( int , i n t ) ;
void p r z e s u n ( int , i n t ) ;
void pokaz ( ) ;
};

Diagram tej klasy pokazany jest na rysunku.

Rysunek 5.9. Diagram klasy Punkt z danymi i metodami

W pierwszej czci widnieje nazwa klasy, w tym przypadku Punkt. W


drugiej czci przedstawione s dane (atrybuty). W klasie mamy dwa atrybuty: x i y. Zapis:
x : int

informuje, e w klasie mamy atrybut o nazwie x, jest on typu cakowitego


(int) oraz jego widoczno jest prywatna (znak -). W trzeciej czci pokazane zostay operacje (metody). W klasie Punkt mamy trzy metody: init(),
przesun() i pokaz(). Zapis:
+ init ()

5.3. Notacja UML (Unied Modeling Language)


mwi, e w przedstawianej klasie mamy operacj init() oraz widoczno tej
operacji jest publiczna ( znak + ). W jzyku UML skadnia atrybutu jest
nastpujca:
w i d o c z n o s c nazwa [ l i c z n o s c porzadk owanie ]
: typ = wartosc_poczatkowa

Przykadem skadni atrybutu moe by nastpujcy zapis:


num_telefon [ 1 . . o r d e r e d ] : S t r i n g

Skadnik widoczno jest opcjonalny, informuje on o dostpnoci atrybutu.


Mamy trzy moliwoci oznaczenia: znak minus ( - ), znak plus ( + ) oraz
znak hasz ( #).
Znak minus ( - ) oznacza widoczno prywatn; atrybut jest niedostpny
spoza swojej klasy.
Znak plus ( + ) oznacza widoczno publiczn; atrybut jest dostpny
spoza klasy.
Znak hasz ( # ) oznacza widoczno chronion; atrybut jest dostpny
dla klas, ktre maj relacje generalizacji z jego klas.
Typ jest opcjonalny, wskazuje typ danych, ktre moe zawiera atrybut.
W jzyku UML mamy nastpujce typy danych:
Boolean warto logiczna
Integer liczba cakowita
Real liczba rzeczywista
String acuch (cig znakw)
W praktyce moemy w diagramach klas uywa typw specycznych
dla danego jzyka. Np. w C++ moemy stosowa typy podstawowe takie
jaki int, double, oat, char itp. Liczno jest opcjonalna, oznacza liczb
wartoci, ktre mog by przechowywane w atrybucie. Naley poda granic
doln i granic grn. Gdy granica grna jest nieskoczona umieszczamy
znak gwiazdki. Zapis 0..* oznacza od zera do nieskoczonej liczby wartoci.
Gdy liczno jest rwna 1 to jej nie podajemy (jest to warto domylna).
Porzdkowanie jest opcjonalne, mamy dwie moliwoci:
unordered oznacza, e wartoci nie s uporzdkowane
ordered oznacza, e wartoci s uporzdkowane
Wartoci domyln jest unordered. Warto pocztkowa jest opcjonalna. Gdy atrybut posiada warto pocztkow piszemy j po znaku =. Domylnie atrybut nie ma wartoci pocztkowej. Podobnie jak atrybuty opisywane s operacje (w trzeciej czci diagramu klas). Przypominamy, e
operacja jest tym, co obiekt z danej klasy moe zrobi. Jest to specykacja
usugi udostpnianej przez obiekt. Metoda oznacza sposb, w jaki obiekt z

91

92

5. Dziedziczenie i hierarchia klas


danej klasy wykonuje swoje dziaanie. W jzyku UML skadnia operacji jest
nastpujca:
w i d o c z n o s c nazwa ( l i s t a _ p a r a m e t r o w ) : typ_zwracany

Przykadem skadni opisu operacji mog by nastpujce zapisy:


+ ustawNazwisko ( i n Nazwisko : S t r i n g )
+ pokazNazwisko ( ) : S t r i n g

Skadnik widoczno jest opcjonalny, informuje on o dostpnoci operacji.


Mamy trzy moliwoci oznaczenia: znak minus ( - ), znak plus ( + ) oraz
znak hasz ( #).
Znak minus ( - ) oznacza widoczno prywatn; operacja jest niedostpna
spoza swojej klasy. Znak plus ( + ) oznacza widoczno publiczn; operacja
jest dostpna spoza klasy. Znak hasz ( # ) oznacza widoczno chronion;
operacja jest dostpna dla klas, ktre maj relacje generalizacji z jej klas. Typ zwracany jest opcjonalny, wskazuje typ danych, zwracanych przez
operacj do tego, kto j wywoa. Lista parametrw jest opcjonalna. Na
licie umieszczamy parametry oddzielone przecinkami. Lista wyszczeglnia
parametry przekazywane do operacji i otrzymywane z operacji. Parametr
ma rozbudowan skadni:
r o d z a j nazwa : typ = wartosc_poczatkowa

W tym zapisie rodzaj wskazuje kierunek przesyania, mamy trzy moliwoci:


in, out i inout. Znaczenie symboli jest nastpujce:
in oznacza, e parametr jest wejciowy i nie moe by modykowany
przez operacj
out oznacza, e parametr jest tylko wyjciowy, i moe by modykowany
przez operacj
inout oznacza, e parametr jest wejciowy i moe by zmodykowany
przez operacj

5.4. Proste klasy pochodne


Zajmiemy si teraz deklaracj klasy pochodnej, ktra dziedziczy cechy
klasy podstawowej. Omwimy podstawowe zagadnienia zwizane z dziedziczeniem prostym (dziedziczenie pojedyncze). Niech klasa bazowa nazywa si
Punkt a pochodna Kwadrat. Deklaracja klasy pochodnej ma nastpujc
posta:
c l a s s Kwadrat : s p e c y f i k a t o r _ d o s t e p u Punkt
{

93

5.4. Proste klasy pochodne


// . . . . . . . . . . . . . . . . . . . . . . .
};

Specykator dostpu oznacza rodzaj dziedziczenia: public, private lub protected. Jeeli mamy do czynienia z dziedziczeniem publicznym to piszemy:
c l a s s Kwadrat : public Punkt
{
// . . . . . . . . . . . . . . . . . . . . . . .
};

Podamy przykad zastosowania klasy pochodnej. Niech klasa bazowa o nazwie punkt ma dwie skadowe prywatne x i y, ktre s wsprzdnymi punktu na paszczynie oraz funkcje skadowe do obsugi tego punktu. Naley
utworzy now klas pochodn punktB od klasy bazowej punkt. Klasa pochodna punktB powinna zawiera now funkcj, dziki ktrej bdzie mona obliczy odlego punktu od pocztku ukadu wsprzdnych. Jeeli
wsprzdne punktu wynosz x i y to odlego punktu od pocztku ukadu
wsprzdnych wyraa si wzorem:
r=

x2 + y 2

(5.1)

Deklaracja klasy punkt zawarta w pliku punkt.h ma posta:


Listing 5.1. Dziedziczenie publiczne - plik punkt.h
// d e k l a r a c j a k l a s y p u n k t
2

10

12

14

c l a s s punkt
{ double x , y ;
public :
void i n i t ( double xx =0.0 , double yy =0.0)
{ x = xx ;
y = yy ;
}
void pokaz ( )
{ c o u t << " w s p o l r z e d n e : " << x << " " << y << e n d l ;
}
double wsX ( ) { return x ; }
double wsY ( ) { return y ; }
};

Klasa punkt zawiera: dane prywatne x i y (s to wsprzdne punktu),


funkcj skadow init(), funkcj skadow pokaz() do wypisywania wsprzdnych kartezjaskich punktu i dwie funkcje dostpu wsX() i wsY(). Na
podstawie klasy bazowej punkt tworzymy klas pochodn, ktra zawiera

94

5. Dziedziczenie i hierarchia klas


dodatkow funkcj promien(). Na wydruku 5.2 pokazano klas pochodn o
nazwie punktB oraz funkcj testujc.
Listing 5.2. Dziedziczenie publiczne - klasa pochodna i funkcja testujca
1

11

// d z i e d z i c z e n i e p u b l i c z n e
#include <i o s t r e a m . h>
#include <c o n i o . h>
#include <math . h>
#include " punkt . h"
c l a s s punktB : public punkt
{
public :
double promien ( )
{ return s q r t ( wsX( ) wsX( ) + wsY( ) wsY( ) ) ;
};

13

15

17

19

21

i n t main ( )
{
punktB a ;
a. init ( 3, 4 ) ;
a . pokaz ( ) ;
c o u t << " promien : " << a . promien ( ) ;
getch () ;
return 0 ;
}

Po uruchomieniu programu mamy nastpujcy komunikat:


wspolrzedne : 3 4
promien
: 5

Klasa pochodna punktB ma posta:


c l a s s punktB : public punkt
{
public :
double promien ( )
{ return s q r t (wsX ( ) wsX ( ) + wsY ( ) wsY ( ) ) ;
}
};

Zawiera ona funkcj skadow promien (), ktra oblicza pierwiastek kwadratowy z sumy kwadratw wsprzdnych punku. Jest to odlego punktu
od pocztku ukadu wsprzdnych.
W liniach:
punktB a ;

5.4. Proste klasy pochodne

Rysunek 5.10. Diagram klasy punkt i punktB w notacji UML. Klasa punktB dziedziczy skadowe klasy punkt.

a. init ( 3, 4 ) ;
a . pokaz ( ) ;

tworzony jest obiekt a klasy punktB, wsprzdnym x i y nadawane s


wartoci 3 i 4 oraz wywoywana jest funkcja skadowa klasy bazowej pokaz()
do wywietlania wsprzdnych punktu (obiektu a). W linii:
c o u t << " promien : " << a . promien ( ) ;

wywoywana jest funkcja skadowa klasy pochodnej do obliczania odlegoci


punktu od pocztku ukadu wsprzdnych. Klasa punktB nie ogranicza, jak
wida dostpu do dziedziczonych skadowych klasy punkt. Oznacza to, e
odziedziczone publiczne funkcje skadowe takie jak na przykad init() mog
by wywoywane take dla obiektw klasy punktB.
Klasy pochodne mog by specykowane jako publiczne, prywatne i
chronione. Kada taka specykacja rodzi okrelone konsekwencje:
Jeeli specykatorem jest public, to wszystkie skadowe publiczne w klasie bazowej s take skadowymi publicznymi w klasie pochodnej. Klasa
pochodna dziedziczy wszystkie skadowe klasy bazowej.
Jeeli specykatorem jest private, to wszystkie skadowe publiczne klasy
bazowej s prywatnymi skadowymi klasy pochodnej. Skadowe prywatne klasy bazowej s niedostpne dla klasy pochodnej.
Jeeli specykatorem jest protected to klasa pochodna ma dostp do
skadowych chronionych.
Skadowe klasy bazowej mog by typu private, protected i public. Z
kolei klasa bazowa moe by take zadeklarowana dla klasy pochodnej jako

95

96

5. Dziedziczenie i hierarchia klas


private, protected i public. Wymaga to omwienia. Rozpatrzmy nastpujc
klas bazow punkt:
c l a s s punkt
{
private :
int x ;
protected :
int y ;
public :
int z ;
};

Mamy nastpujce przypadki:


Klasa bazowa jest publiczna. Dostpne publiczne skadowe klasy bazowej
nadal dostpne s publicznie w klasie pochodnej. Skadowe o dostpie
chronionym i prywatnym w klasie bazowej, zachowuj odpowiedni rodzaj dostpu w klasie pochodnej. W naszym przykadzie zmienna y jest
skadow chronion w klasie pochodnej (jest skadow prywatn, ale
dostpn dla klas pochodnych)
Klasa bazowa jest prywatna. W tym przypadku dostp do wszystkich
skadowych dziedziczonych po klasie bazowej staje si prywatny. Skadowe te przestaj by dostpne zarwno dla uytkownika klasy pochodnej,
jak i dla jej klas pochodnych. W naszym przykadzie zmienne y i z staj
si prywatnymi skadowymi klasy pochodnej.
Klasa bazowa jest chroniona. W takim przypadku wszystkie publiczne
i chronione skadowe klasy bazowej staj si chronionymi skadowymi
klasy pochodnej. W naszym przykadzie zmienne y i z stan si chronionymi skadowymi klasy pochodnej.
Aby zilustrowa powysze rozwaania utworzymy klas bazow punkt,
w ktrej skadowe x i y s chronione:
Listing 5.3. Dziedziczenie publiczne - plik punkt.h - dane typu protected
1

// d e k l a r a c j a k l a s y p u n k t

c l a s s punkt
{ protected :
double x , y ;
public :
void i n i t ( double xx =0.0 , double yy =0.0)
{ x = xx ;
y = yy ;
}
void pokaz ( )
{ c o u t << " w s p o l r z e d n e : " << x << " " << y << e n d l ;
}

11

13

5.4. Proste klasy pochodne


double wsX ( ) { return x ; }
double wsY ( ) { return y ; }

15

};

Klasa pochodna punktB ma dostp do zmiennych x i y i wtedy funkcja


skadowa promien() istotnie si upraszcza:
c l a s s punktB : public punkt
{ public :
double promien ( )
{ return s q r t ( xx + yy ) ;
}
};

Funkcja testujca razem z klas pochodn punktB ma posta:


Listing 5.4. Klasa pochodna i funkcja testujca - dane typu protected
// d z i e d z i c z e n i e p u b l i c z n e dane p r o t e c t e d
2

10

12

14

16

18

20

22

24

#include
#include
#include
#include

<i o s t r e a m . h>
<c o n i o . h>
<math . h>
" punkt . h"

c l a s s punktB : public punkt


{
public :
double promien ( )
{ return s q r t ( xx + yy ) ;
}
};
i n t main ( )
{
punktB a ;
a . i n i t (3 ,4) ;
a . pokaz ( ) ;
c o u t << " promien : " << a . promien ( ) ;
getch () ;
return 0 ;
}

W tym przykadzie widzimy, e funkcja skadowa promien() klasy pochodnej punktB ma prost posta:
double promien ( )
{ return s q r t ( x x + y y ) ;
}

97

98

5. Dziedziczenie i hierarchia klas


Poniewa dane x i y s zadeklarowane jako protected, klasa pochodna ma
bezporedni dostp do nich. W poprzednim przykadzie, dane x i y byy
zadeklarowane jako private, wobec czego funkcja skadowa klasy pochodnej
musiaa mie do skomplikowan posta.
Denicja tej funkcji (wydruk 5.2) miaa posta:
double promien ( )
{ return s q r t ( wsX ( ) wsX ( ) + wsY ( ) wsY ( ) ) ;
}

5.5. Konstruktory w klasach pochodnych


Konstruktory i destruktory nie s dziedziczone. Konstruktory oraz destruktory mona tworzy dla klas bazowych, dla klas pochodnych oraz dla
obu z nich jednoczenie. Gdy tworzony jest obiekt klasy pochodnej, wywoywany jest najpierw konstruktor klasy bazowej (o ile istnieje), a nastpnie
konstruktor klasy pochodnej. Podczas tworzenia obiektu klasy pochodnej,
aby zainicjowa dane skadowe klasy podstawowej musi zosta wywoany jej
konstruktor. Jednak moliwe jest ich wywoanie odpowiednio w konstruktorach i operatorach przypisania klasy pochodnej. Wywoywanie konstruktora
klasy bazowej zilustrujemy przykadem.
Listing 5.5. klasa pochodna i konstruktory
1

#include <i o s t r e a m . h>


#include <c o n i o . h>

c l a s s punkt
// k l a s a bazowa
{ public :
punkt ( double xx , double yy ) {x = xx ; y = yy ; }
void pokazXY ( ) ;
private :
double x , y ;
};

11

13

15

17

19

21

void punkt : : pokazXY ( )


{ c o u t << "x= " << " " << x << " y= " << y << e n d l ; }
c l a s s punktB : public punkt
// k l a s a pochodna
{ public :
punktB ( double k , double m, double n ) : punkt (m, n )
{ z = k; }
void pokaz ( )
{ pokazXY ( ) ;
c o u t << " z = " << z << e n d l ;
}

5.5. Konstruktory w klasach pochodnych


private :
double z ;

23

25

};

27

i n t main ( )
{punktB p1 ( 5 , 5 0 , 5 0 ) ;
p1 . pokaz ( ) ;
getch () ;
return 0 ;
}

29

31

Po uruchomieniu tego programu mamy nastpujcy wydruk:


x = 50
z = 5

y = 50

Klasa bazowa punkt ma posta:


c l a s s punkt
// k l a s a bazowa
{
public :
punkt ( double xx , double yy ) {x = xx ; y = yy ; }
void pokazXY ( ) ;
private :
double x , y ;
};

Konstruktor klasy bazowej ma posta:


punkt ( double xx , double yy ) { x = xx ; y = yy ; }

i wymaga dwch argumentw dla inicjacji skadowych x i y. Klasa pochodna


punktB zawiera dodatkow dan z i ma posta:
c l a s s punktB : public punkt
// k l a s a pochodna
{
public :
punktB ( double k , double m, double n ) : punkt (m, n )
{ z = k; }
void pokaz ( )
{ pokazXY ( ) ;
c o u t << " z = " << z << e n d l ;
}
private :
double z ;
};

Konstruktor klasy pochodnej ma posta:

99

100

5. Dziedziczenie i hierarchia klas


punktB ( double k , double m, double n ) : punkt ( m, n )
{ z = k; }

Konstruktor punktB musi wywoa konstruktor punkt, ktry jest odpowiedzialny za zainicjowanie tej czci obiektu klasy punktB, ktra pochodzi z
klasy podstawowej punkt. W tym celu zosta wykorzystany tzw. inicjator
skadowej. Zastosowano rozszerzon posta deklaracji konstruktora klasy
pochodnej, ktra pozwala przekazywa argumenty do jednego lub kilku konstruktorw klasy bazowej.
Formalna posta rozszerzonej deklaracji jest nastpujca:
k onstruk tor_k lasa_pochodna ( l i s t a _ p a r a m e t r o w )
: nazwa_klasy_pochodnej_1 ( l i s t a argumentow ) ,
: nazwa_klasy_pochodnej_2 ( l i s t a argumentow ) ,
.......................................
: nazwa_klasy_pochodnej_N ( l i s t a argumentow ) ,
{
// c i a l o k o n s t r u k t o r a k l a s y p o c h o d n e j
}

Przy pomocy znaku dwukropka oddzielamy deklaracj konstruktora klasy


pochodnej od specykacji klasy bazowej. Gdy mamy wicej klas bazowych
dziedziczonych przez klas pochodn, uywamy przecinka, do oddzielenia
ich specykacji. W naszym przykadzie mamy tylko jedn klas bazow,
ktra ma dwa parametry, wobec czego mamy napis:
: punkt (m, n )

Jak wiec widzimy, nasz konstruktor klasy pochodnej jest funkcj o trzech
argumentach, dwa argumenty musz by przesane do konstruktora klasy
bazowej. W momencie utworzenia obiektu p1:
punktB

p1 ( 5 , 5 0 , 50 ) ;

nastpuje przekazanie argumentw aktualnych 50 i 50 do konstruktora klasy


bazowej i wykonanie konstruktora:
punkt ( xx , yy )

a nastpnie wykonanie konstruktora klasy pochodnej. Bardzo czsto zachodzi sytuacja, gdy tworzone s obiekty klasy pochodnej a klasa bazowa i
klasa pochodna zawieraj rne skadowe. Jeeli tworzony jest obiekt klasy
pochodnej to kolejno wywoywania konstruktorw jest nastpujca:
Wywoywane s konstruktory obiektw klasy bazowej
Wywoywany jest konstruktor klasy bazowej

5.5. Konstruktory w klasach pochodnych


Wywoywany jest konstruktor klasy pochodnej
Destruktory wywoywane s w odwrotnej kolejnoci
Zagadnienie kolejnoci wywoywania konstruktorw zilustrujemy kolejnym przykadem, w ktrym dwie klasy bazowa i pochodna posiadaj
wasne konstruktory i destruktory. Klas bazow jest klasa punkt, ktrej
danymi chronionymi s wsprzdne punktu, klasa pochodna kolo ma jedn
dan prywatn, jest ni promie koa. Moment wywoywania konstruktorw i destruktorw bdzie wypisywany na ekranie w trakcie wykonywania
funkcji testujcej. Dla celw dydaktycznych, program skada si z 5 plikw:
deklaracji klasy punkt, denicja klasy punkt, deklaracja klasy pochodnej
kolo, denicja klasy kolo oraz funkcji testujcej.
Zmienne x i y s danymi chronionymi klasy bazowej punkt. Denicja
klasy pokazana jest na wydruku.
Listing 5.6. klasa pochodna - konstruktory
1

// p l i k " p u n k t . h"
// d e k l a r a c j a k l a s y p u n k t

11

13

#i f n d e f _PUNKT_H
#define _PUNKT_H
c l a s s punkt
{
public :
punkt ( double = 0 . 0 , double = 0 . 0 ) ; // k o n s t r u k t o r domyslny
~punkt ( ) ;
// d e s t r u k t o r
protected :
double x , y ;
};

15

#endif

Klasa posiada konstruktor domylny i destruktor:


punkt ( double = 0 . 0 , double = 0 . 0 ) ; // k o n s t r u k t o r domyslny
~punkt ( ) ;
// d e s t r u k t o r

W denicji klasy bazowej punkt, konstruktor jak i destruktor wywietlaj


komunikat o obiekcie, na rzecz ktrego zostay wywoane. Deklaracja klasy punkt umieszczona jest umieszczona w odrbnym pliku i pokazana na
wydruku.
Listing 5.7. klasa pochodna - konstruktory
2

// p l i k p u n k t . cpp
// d e f i n i c j a k l a s y p u n k t

101

102

5. Dziedziczenie i hierarchia klas

10

#include <i o s t r e a m . h>


#include " punkt . h"
punkt : : punkt ( double xx , double yy )
{
x = xx ;
y = yy ;
c o u t << " k o n s t r u k t o r o b i e k t u k l a s y punkt " ;
c o u t << " x= " << x << " y= " << y << e n d l ;
}

12

14

16

punkt : : ~ punkt ( )
{
c o u t << " d e s t r u k t o r o b i e k t u k l a s y punkt " ;
c o u t << " x= " << x << " y= " << y << e n d l ;
}

Klasa pochodna kolo dziedziczy od klasy bazowej punkt i jej deklaracja


pokazana jest na wydruku. Skadowa klasy pr zadeklarowana zostaa jako
prywatna.
Listing 5.8. klasa pochodna - konstruktory
1

// p l i k " k o l o . h"
// d e k l a r a c j a k l a s y k o l o

#i f n d e f _KOLO_H
#define _KOLO_H

#include " punkt . h"

11

13

15

17

19

c l a s s k o l o : public punkt
{
public :
k o l o ( double r =0.0 , double xx =0.0 , double yy =0.0) ;
// k o n s t r u k t o r domyslny
~kolo ( ) ;
// d e s t r u k t o r
private :
double pr ;
};
#endif

Klasa kolo posiada konstruktor i destruktor:


k o l o ( double r =0.0 , double xx =0.0 , double yy =0.0) ;
// k o n s t r u k t o r domyslny
~kolo () ;
// d e s t r u k t o r

Na kolejnym wydruku pokazana jest denicja klasy pochodnej kolo.

5.5. Konstruktory w klasach pochodnych


Listing 5.9. klasa pochodna - konstruktory
1

// p l i k " k o l o . cpp "


// d e f i n i c j a k l a s y k o l o
#include <i o s t r e a m . h>
#include " k o l o . h"

11

k o l o : : k o l o ( double r , double xx , double yy )


: punkt ( xx , yy )
{
pr = r ;
c o u t << " k o n s t r u k t o r o b i e k t u k l a s y k o l o " ;
c o u t << " pr= " << pr << " x= " << x <<
" y= " << y << e n d l ;
}

13

15

17

19

kolo : : ~ kolo ()
{
c o u t << " d e s t r u k t o r o b i e k t u k l a s y k o l o " ;
c o u t << " pr= " << pr << " x= " << x <<
" y= " << y << e n d l ;
}

Konstruktor klasy kolo wywouje rwnoczenie konstruktor klasy punkt


:
k o l o : : k o l o ( double r , double xx , double yy )
// k o n s t r u k t o r k l a s y k o l o
: punkt ( xx , yy ) // k o n s t r u k t o r k l a s y p u n k t
{ pr = r ;
c o u t << " k o n s t r u k t o r o b i e k t u k l a s y k o l o " ;
c o u t << " pr= " << pr << " x= " << x << " y= "
<< y << e n d l ;
}

Listing 5.10. klasa pochodna; konstruktory; funkcja testujca


2

10

12

14

#include <i o s t r e a m . h>


#include <c o n i o . h>
#include " punkt . h"
#include " k o l o . h"
i n t main ( )
{
{
c o u t <<" o b i e k t p1" << e n d l ;
punkt p1 ( 1 0 , 2 0 ) ;
}
{
c o u t << " o b i e k t k1 " <<e n d l ;
k o l o k1 ( 5 , 1 0 0 , 2 0 0 ) ;
}
{
c o u t << " o b i e k t k2 " <<e n d l ;
k o l o k2 ( 5 0 , 1 5 0 , 2 5 0 ) ;

103

104

5. Dziedziczenie i hierarchia klas


}
getch () ;
return 0 ;

16

18

Przykadowy program pokazany na wydrukach 5.6 5.10 skada si z


5 plikw, jego schemat zyczny pokazano na rysunku. Fizyczn struktur
kadego programu C++ mona okreli jako zbir plikw. Niektre z nich
bd plikami nagwkowymi(.h), a inne plikami implementacji (.cpp). Przyjmuje si, e komponent jest najmniejszym elementem projektu zycznego.
Strukturalnie komponent stanowi niepodzieln jednostk zyczn, ktrej
czci nie mog by uyte niezalenie od pozostaych. Komponent skada
si z jednego pliku nagwkowego i jednego pliku implementacji. Graczne
przedstawienie komponentw znacznie uatwia zbadanie wzajemnych relacji pomidzy plikami i bibliotekami. W omawianym przykadzie, w funkcji
main() tworzony jest obiekt p1 klasy bazowej punkt. Nastpnie tworzone s
kolejno dwa obiekty k1 i k2 klasy pochodnej kolo. Po uruchomieniu funkcji
testujcej mamy nastpujcy wynik:

Na pocztku tworzony jest egzemplarz obiektu klasy punkt. Wywoywany jest konstruktor a potem destruktor. Nastpnie tworzony jest obiekt k1
klasy pochodnej kolo. Wywoywany jest najpierw konstruktor klasy punkt,
ktry pokazuje przekazane wartoci a nastpnie wywoany jest konstruktor
klasy kolo, ktry te pokazuje przekazane wartoci. Kolejno wywoywany
jest destruktor klasy koo i destruktor klasy punkt (wywoywanie destruktorw odbywa si w odwrotnej kolejnoci). Nastpnie utworzony zostaje
kolejny obiekt k2 klasy kolo.

5.6. Dziedziczenie kaskadowe


Rozpatrywalimy dotychczas proste przypadki, gdy klasa pochodna miaa tylko jedn klas bazow. W bardziej rozbudowanych projektach moe

5.6. Dziedziczenie kaskadowe

Rysunek 5.11. Fizyczne diagramy komponentw (wydruki 5.6 -5.9) i programu


testujcego (wydruk 5.10)

si okaza, e potrzebne bdzie utworzenie klasy, ktra bdzie dziedziczya


cechy wielu klas. Moliwe s dwa schematy dziedziczenia:
Klasa C dziedziczy od klasy B, klasa B dziedziczy od klasy A. Klasa C
ma bezporedni klas bazow B i poredni klas bazow A. W oglnym
schemacie klasa pochodna moe w ten sposb dziedziczy cechy wielu
klas, ale ma tyko jednego bezporedniego przodka. W tym przypadku
mwimy o dziedziczeniu kaskadowym.
Proces bezporedni. Klasa pochodna C ma dwie bezporednie klasy bazowe: A i B. W oglnym przypadku, klasa pochodna moe dziedziczy
jednoczenie cechy wielu klas. W tym przypadku mwimy o dziedziczeniu
wielokrotnym ( mnogim).

105

106

5. Dziedziczenie i hierarchia klas


Dziedziczenie kaskadowe zilustrujemy przykadem. Najpierw utworzymy
klas punkt. Na jej podstawie tworzymy klas pochodn kolo. Z klasy kolo
tworzymy kolejn klas pochodn walec.
W naszym przykadzie najpierw utworzymy klas punkt i plik z programem testujcym. Utworzymy trzy pliki: punkt.h, punkt.cpp, ftest1.cpp.
Listing 5.11. klasa punkt
2

10

12

// p l i k p u n k t . h
// d e f i n i c j a k l a s y p u n k t
#i f n d e f PUNKT_H
#define PUNKT_H
c l a s s punkt
{ public :
punkt ( double = 0 . 0 , double = 0 . 0 ) ; // k o n s t r u k t o r domyslny
void pokaz ( ) ;
// p o k a z u j e w s p o l r z e d n e
protected :
double wX, wY;
};
#endif

Listing 5.12. klasa punkt


2

10

// p l i k p u n k t . cpp
// f u n k c j e s k l a d o w e k l a s y p u n k t
#include " punkt . h"
#include <i o s t r e a m . h>
punkt : : punkt ( double xx , double yy ) // k o n s t r u k t o r k l a s y p u n k t
{ wX = xx ; wY = yy ;
}
void punkt : : pokaz ( )
{ c o u t << "x= " << wX << " y= " << wY << e n d l ;
}

Listing 5.13. klasa punkt; funkcja main()


2

10

12

// p l i k f t e s t 1 . cpp
#include <i o s t r e a m . h>
#include <c o n i o . h>
#include " punkt . h"
i n t main ( )
{
double wX,wY;
c o u t << " Podaj X= " ;
c i n >> wX;
c o u t << " Podaj Y= " ;
c i n >> wY;
punkt p1 (wX, wY) ;

5.6. Dziedziczenie kaskadowe


p1 . pokaz ( ) ;
getch () ;
return 0 ;

14

16

Rysunek 5.12. Fizyczne diagramy komponentw (wydruki 5.11 i 5.12) i programu


testujcego (wydruk 5.13). Program bez dziedziczenia klas.

W wyniku uruchomienia programu testujcego mamy nastpujcy wydruk:


Podaj X= 2
Podaj Y= 2
x= 2 y= 2

Rozbudujemy nasz projekt o moliwo obsuenia obiektu typu koo. Najpierw tworzymy klas punkt a potem na podstawie tej klasy tworzymy klas
kolo. Realizujemy model dziedziczenia jednokrotnego (prostego).

107

108

5. Dziedziczenie i hierarchia klas

Rysunek 5.13. Diagram klasy punkt i kolo w notacji UML. Klasa kolo dziedziczy
skadowe klasy punkt.

Nasz projekt zbudowany jest z dwch komponentw i pliku z funkcj


main(). Fizycznie mamy pi plikw: punkt.h, punkt.cpp, kolo.h, kolo.cpp
i ftest2.cpp. Klasa pochodna kolo dziedziczy cechy klasy bazowej punkt
(rys.5.13).
Listing 5.14. dziedziczenie proste - klasa bazowa punkt
1

// p l i k p u n k t . h
// d e f i n i c j a k l a s y p u n k t
#i f n d e f PUNKT_H
#define PUNKT_H

11

13

c l a s s punkt
{ public :
punkt ( double = 0 . 0 , double = 0 . 0 ) ;
// k o n s t r u k t o r domyslny
void pokazP ( ) ;
// p o k a z u j e w s p o l r z e d n e
protected :
double wX, wY;
};
#endif

5.6. Dziedziczenie kaskadowe


Listing 5.15. dziedziczenie proste - klasa bazowa punkt
2

// p l i k p u n k t . cpp
// f u n k c j e s k l a d o w e k l a s y p u n k t
#include " punkt . h"
#include <i o s t r e a m . h>

10

12

14

// k o n s t r u k t o r k l a s y p u n k t
punkt : : punkt ( double xx , double yy )
{ wX = xx ; wY = yy ;
}
void punkt : : pokazP ( )
{ c o u t << "x= " << wX << " y= " << wY << e n d l ;
}

Listing 5.16. dziedziczenie proste - klasa pochodna kolo


2

// p l i k k o l o . h
// d e f i n i c j a k l a s y k o l o
#i f n d e f KOLO_H
#define KOLO_H

#include " punkt . h"


8

10

12

14

16

18

20

c l a s s k o l o : public punkt
{
public :
k o l o ( double r = 0 . 0 , double wX = 0 . 0 , double wY = 0 . 0 ) ;
// k o n s t r u k t o r
void pokazK ( ) ;
// p o k a z u j e w s p o l r z e d n e
double poleK ( ) const ;
protected :
double pr ;
};
#endif

Listing 5.17. dziedziczenie proste - klasa pochodna kolo


2

// p l i k k o l o . cpp
// d e f i n i c j e f u n k c j i s k a l d o w y c h k l a s y k o l o
#include <i o s t r e a m . h>
#include " k o l o . h"

k o l o : : k o l o ( double r , double wX, double wY) : punkt (wX, wY)


{
pr = r ;

109

110

5. Dziedziczenie i hierarchia klas


}
10

12

14

16

18

void k o l o : : pokazK ( )
{ c o u t << "x= " << wX << " y= " << wY << " r= "
<< pr << e n d l ;
}
double k o l o : : poleK ( ) const
{ return 3 . 1 4 1 5 9 2 6 pr pr ;
}

Listing 5.18. dziedziczenie proste; klasa punkt i kolo; funkcja main()

10

12

14

16

18

// program t e s t u j a c y k l a s e k o l o
#include <i o s t r e a m . h>
#include <c o n i o . h>
#include " punkt . h"
#include " k o l o . h"
i n t main ( )
{ double wX, wY, pr ;
c o u t << " Podaj X= " ;
c i n >> wX;
c o u t << " Podaj Y= " ;
c i n >> wY;
c o u t << " Podaj promien , pr= " ;
c i n >> pr ;
k o l o k1 ( pr , wX, wY) ;
k1 . pokazK ( ) ;
c o u t <<" p o w i e r z c h n i a k o l a = " << k1 . poleK ( ) << e n d l ;
getch () ;
return 0 ;
}

Komponenty projektu pokazano na rysunku 5.14. W wyniku wykonania


programu testujcego otrzymujemy nastpujcy komunikat:

5.6. Dziedziczenie kaskadowe

Rysunek 5.14. Fizyczne diagramy komponentw (wydruki 5.14, 5.15, 5.16 i 5.17) i
pliku ftest2.cpp (wydruk 5.18). Program z dziedziczeniem prostym.

Rysunek 5.15. Diagram klasy punkt, kolo i walec w notacji UML. Jest to przykad
dziedziczenia kaskadowego. Klasa walec dziedziczy skadowe klasy kolo, klasa kolo
dziedziczy skadowe klasy punkt.

Obecnie zbudujemy projekt wykorzystujcy dziedziczenie kaskadowe.


Rozbudujemy poprzedni projekt przyjmujc hierarchi punkt, kolo, walec.
Najpierw utworzymy klas punkt. Nastpnie tworzymy klas kolo. Klasa

111

112

5. Dziedziczenie i hierarchia klas


kolo dziedziczy cechy klasy punkt. W kocu tworzymy klas walec. Klasa
walec dziedziczy cechy klasy kolo. Aby sprawdzi dziaanie utworzonych
klas tworzymy program testujcy ftest3.cpp. Powsta projekt wieloplikowy,
otrzymalimy nastpujce pliki: pliki punkt.h, punkt.cpp, kolo.h, kolo.cpp,
walec.h, walec.cpp, ftest3.cpp. W naszym projekcie klasa walec jest klas
pochodn od klasy kolo, klasa kolo jest klas pochodn od klasy punkt, jednoczenie jest klas bazow dla klasy walec. Klasa punkt jest klas bazow
dla klasy kolo.
Listing 5.19. dziedziczenie kaskadowe; klasa bazowa punkt
1

11

// p l i k p u n k t . h , d e f i n i c j a k l a s y p u n k t
#i f n d e f PUNKT_H
#define PUNKT_H
c l a s s punkt
{
public :
punkt ( double = 0 . 0 , double = 0 . 0 ) ; // k o n s t r u k t o r domyslny
void pokazP ( ) ;
// p o k a z u j e w s p o l r z e d n e
protected :
double wX, wY;
};

13

#endif

Listing 5.20. dziedziczenie kaskadowe; klasa bazowa punkt


// p l i k p u n k t . cpp , f u n k c j e s k l a d o w e k l a s y p u n k t
2

#include " punkt . h"


#include <i o s t r e a m . h>
punkt : : punkt ( double xx , double yy ) // k o n s t r u k t o r k l a s y p u n k t

10

12

{ wX = xx ;
}

wY = yy ;

void punkt : : pokazP ( )


{ c o u t << "x= " << wX << " y= " << wY << e n d l ;
}

Listing 5.21. dziedziczenie kaskadowe; klasa pochodna kolo


// p l i k k o l o . h , d e f i n i c j a k l a s y k o l o
2

#i f n d e f KOLO_H
#define KOLO_H
#include " punkt . h"

5.6. Dziedziczenie kaskadowe


6

10

12

14

16

c l a s s k o l o : public punkt
{
public :
k o l o ( double r = 0 . 0 , double wX = 0 . 0 , double wY = 0 . 0 ) ;
// k o n s t r u k t o r
void pokazK ( ) ;
// p o k a z u j e w s p o l r z e d n e
double poleK ( ) const ;
protected :
double pr ;
};
#endif

Listing 5.22. dziedziczenie kaskadowe; klasa pochodna kolo


1

// p l i k k o l o . cpp , d e f i n i c j e f u n k c j i s k l a d o w y c h k l a s y k o l o
#include <i o s t r e a m . h>
#include " k o l o . h"

k o l o : : k o l o ( double r , double wX, double wY) : punkt (wX, wY)


{
pr = r ;
}

11

13

15

17

void k o l o : : pokazK ( )
{ c o u t << "x= " << wX << " y= " << wY << " r= " << pr
<< e n d l ;
}
double k o l o : : poleK ( ) const
{ return 3 . 1 4 1 5 9 2 6 pr pr ;
}

Listing 5.23. dziedziczenie kaskadowe; klasa pochodna walec


1

// p l i k w a l e c . h , d e f i n i c j a k l a s y w a l e c
#i f n d e f WALEC_H
#define WALEC_H

#include " k o l o . h"


7

11

13

c l a s s w a l e c : public k o l o
{
public :
w a l e c ( double h = 0 . 0 , double r = 0 . 0 ,
double wX = 0 . 0 , double wY = 0 . 0 ) ;
// k o n s t r u k t o r domyslny
void pokazW ( ) ;

113

114

5. Dziedziczenie i hierarchia klas


double poleW ( ) const ; // o b l i c z a p o w i e r z c h n i e w a l c a
double objetoscW ( ) const ; // o b l i c z a o b j e t o w s c w a l c a
protected :
double wy sok osc ;
// w y s o k o s c w a l c a

15

17

19

21

};
#endif

Listing 5.24. dziedziczenie kaskadowe; klasa pochodna walec


1

// p l i k w a l e c . cpp , d e f i n i c j e f u n k c j i s k l a d o w y c h k l a s y w a l ec
#include <i o s t r e a m . h>
#include " w a l e c . h"

w a l e c : : w a l e c ( double h , double r , double wX, double wY)


: k o l o ( r , wX, wY)
{ wy sok osc = h ; }

11

13

15

17

19

21

23

25

void w a l e c : : pokazW ( )
{
c o u t << "x= " << wX << " y= " << wY << e n d l ;
c o u t << " promien= " << pr << " wy sok osc= " << wy sok osc
<< e n d l ;
}
double w a l e c : : poleW ( ) const
{
return 2 k o l o : : poleK ( ) +
2 3 . 1 4 1 5 9 2 6 pr wy sok osc ;
}
double w a l e c : : objetoscW ( ) const
{
return k o l o : : poleK ( ) wy sok osc ;
}

Listing 5.25. dziedziczenie kaskadowe; klasa punkt; kolo; walec; funkcja


main()
2

// Program t e s t u j a c y k l a s e walec
#include <i o s t r e a m . h>
#include <c o n i o . h>

#include " punkt . h"


#include " k o l o . h"
#include " w a l e c . h"

i n t main ( )

5.6. Dziedziczenie kaskadowe


10

{
double wX, wY, pr , wy sok osc ;
c o u t << " Podaj X= " ;
c i n >> wX;
c o u t << " Podaj Y= " ;
c i n >> wY;
c o u t << " Podaj promien , pr= " ;
c i n >> pr ;
c o u t << " Podaj wysokosc , wy sok osc= " ;
c i n >> wy sok osc ;
w a l e c w1 ( wysokosc , pr , wX, wY) ;
w1 . pokazW ( ) ;
c o u t << " p o w i e r z c h n i a walca= " << w1 . poleW ( ) << e n d l ;
c o u t << " o b j e t o s c walca= " << w1 . objetoscW ( ) << e n d l ;

12

14

16

18

20

22

24

getch () ;
return 0 ;

26

Program testujcy tworzy obiekt w1 klasy walec:


w a l e c w1 ( wysokosc , pr , wX, wY) ;

a nastpnie posugujc si funkcjami pokazW(), poleW() i objetoscW() pokazuje dane i oblicza pole i objto utworzonego walca. Kompletny wydruk
otrzymany po uruchomieniu programu testujcego ma posta:

Projekt zyczny jest rozbudowany, skada si z 3 komponentw (kady


komponent to dwa pliki: plik nagwkowy i plik implementacji) i pliku z programem testujcym, w sumie mamy siedem plikw. Diagramy komponentw
i program testujcy zostay pokazane na rysunku.

115

116

5. Dziedziczenie i hierarchia klas

Rysunek 5.16. Fizyczne diagramy komponentw (wydruki 5.19 - 5.24) i pliku


ftest3.cpp (wydruk 5.25). Program z dziedziczeniem kaskadowym.

117

5.7. Dziedziczenie wielokrotne bezporednie

5.7. Dziedziczenie wielokrotne bezporednie


Klasa pochodna moe by utworzona z kilku klas bazowych. Gdy klasa
pochodna dziedziczy od np. dwch klas bazowych to instrukcja deklaracji
klasy pochodnej ma posta:
c l a s s pochodna_1 : public bazowa_1 ,
{
// c i a l o k l a s y pochodna
};

public bazowa_2

W tym przykadzie klasa pochodna nosi nazw pochodna 1, nazwy publicznych klas bazowych to bazowa 1 i bazowa 2. Widzimy, e w przypadku
dziedziczenia mnogiego, wystpuje lista klas bazowych, kolejne klasy bazowe
poprzedzone s swoimi specykatorami dostpu i oddzielone s przecinkami.
Krtki przykad zilustruje zasad dziedziczenia mnogiego. Utworzymy
dwie klasy bazowe o nazwach bazowa 1 oraz bazowa 2, a nastpnie klas
pochodna o nazwie pochodna 1. Klasa pochodna 1 dziedziczy za pomoc
dziedziczenia wielokrotnego z klas bazowa 1 i bazowa 2. Diagram klas pokazany jest na rysunku 5.17.

Rysunek 5.17. Diagram klasy bazowa 1, bazowa 2 i pochodna 1 w notacji UML.


Jest to przykad dziedziczenia wielokrotnego. Klasa pochodna 1 dziedziczy jednoczenie skadowe klasy bazowa 1 i klasy bazowa 2.

118

5. Dziedziczenie i hierarchia klas


Klasa bazowa 1 zawiera jedn dan skadow x typu protected, klasa
bazowa 2 zawiera jedn dan skadow y take typu protected. Klasa pochodna pochodna 1 zawiera jedna dan skadow z typu private.
Listing 5.26. dziedziczenie mnogie; 2 klasy bazowe
2

// d z i e d z i c z e n i e mnogie
#include <i o s t r e a m . h>
#include <c o n i o . h>

10

12

14

16

18

20

c l a s s bazowa_1
{
protected :
double x ;
public :
bazowa_1 ( double xx )
{ x = xx ; }
double pokazX ( ) { return x ; }
};
c l a s s bazowa_2
{ protected :
double y ;
public :
bazowa_2 ( double yy )
{ y = yy ; }
double pokazY ( ) { return y ; }
};

// k o n s t r u k t o r

// zwraca x

// k o n s t r u k t o r 2
// zwraca y

22

24

26

28

30

32

c l a s s pochodna_1 : public bazowa_1 , public bazowa_2


{ private :
double z ;
public :
pochodna_1 ( double , double , double ) ; // k o n s t r u k t o r 3
void pokazXYZ ( )
{ c o u t << " x= " << pokazX ( ) ;
c o u t << " y= " << pokazY ( ) ;
c o u t << " z= " << z << e n d l ;
};
};

34

36

38

40

42

44

pochodna_1 : : pochodna_1 ( double a , double b , double c )


: bazowa_1 ( a ) , bazowa_2 ( b ) // k o n s t r u k t o r k l a s y
// p o c h o d n e j
{ z = c; }
i n t main ( )
{
pochodna_1 ob1 ( 1 , 1 1 , 1 1 1 ) ;
ob1 . pokazXYZ ( ) ;
c o u t << " x= " << ob1 . pokazX ( ) << e n d l ;
c o u t << " y= " << ob1 . pokazY ( ) << e n d l ;

119

5.7. Dziedziczenie wielokrotne bezporednie


getch () ;
return 0 ;

46

48

Po uruchomieniu tego programu otrzymujemy nastpujcy wydruk:


x= 1 y=11
x= 1
y= 11

z =111

W funkcji testujcej tworzony jest obiekt o nazwie ob1 z parametrami 1,11


i 111 :
pochodna_1 ob1 ( 1 , 1 1 , 1 1 1 ) ;

Wyspecykowane parametry zostaj przekazane w odpowiedniej kolejnoci


do konstruktorw klas bazowych:
public :
bazowa_1 ( double xx )
{ x = xx ; }
public :
bazowa_2 ( double yy )
{ y = yy ; }

// k o n s t r u k t o r

// k o n s t r u k t o r 2

Zostaj utworzone obiekty klas bazowa 1 i bazowa 2. Nastpnie wykonany


zostanie konstruktor klasy pochodnej:
public :
pochodna_1 ( double , double , double ) ;

// k o n s t r u k t o r 3

a konkretnie konstruktor
public :
pochodna_1 ( 1 , 1 1 , 111 ) ;

// k o n s t r u k t o r 3

W rezultacie zostaje utworzony i zainicjalizowany obiekt ob1 klasy pochodna 1 z pod-obiektami klas bazowych. Stosowanie dziedziczenia wielokrotnego w praktyce jest do skomplikowane. Wielu autorw zaleca umiar w
tworzeniu klas z dziedziczeniem wielokrotnym, wielu te zaleca unikanie
takich konstrukcji.

Rozdzia 6
Funkcje zaprzyjanione

6.1.
6.2.
6.3.
6.4.

Wstp . . . . . . . . . . . . . . . . . . . . . . .
Funkcja niezalena zaprzyjaniona z klas . . .
Funkcja skadowa zaprzyjaniona z inn klas
Klasy zaprzyjanione . . . . . . . . . . . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

122
123
134
141

122

6. Funkcje zaprzyjanione

6.1. Wstp
W jzyku C++ to klasa jest chroniona przed nieuprawnionym dostpem
a nie obiekt. W takiej sytuacji funkcja skadowa konkretnej klasy moe uywa wszystkich skadowych prywatnych dowolnego obiektu tej samej klasy.
W jzykach czysto obiektowych mamy do czynienia z mechanizmem odbioru komunikatu przez obiekt. Wywoujc funkcj obsugujc dany obiekt,
wysyamy odpowiedni komunikat, obiekt peni rol odbiorcy komunikatu.
Treci komunikatu s wartoci zmiennych, adresy, itp. przekazywane jako argumenty aktualne wywoywanej funkcji. Jeeli mamy klas o nazwie
punkt, oraz funkcj skadow klasy punkt, ktrej prototyp ma posta int
ustaw( int, int ), to deklaracja obiektu a moe mie posta:
punkt a ;

Dostp do publicznej skadowej funkcji klasy uzyskujemy przy uyciu operatora dostpu, np.
a . ustaw ( 5 , 5 ) ;

W ten sposb wywoujemy funkcj skadow ustaw() klasy, do ktrej naley


obiekt a, to znaczy klasy punkt. Z formalnego punktu widzenia, to wywoanie moemy traktowa jako zaadresowany do obiektu a komunikat o nazwie
ustaw, ktrego treci s wartoci dwch zmiennych (5 i 5).

Rysunek 6.1. Do danych prywatnych klasy dostp maj jedynie funkcje skadowe
(metody) klasy

Z drugiej strony wiemy, e do skadowych prywatnych utworzonego obiektu nie maj dostpu funkcje niezalene ani funkcje innej klasy. W wielu jednak przypadkach istnieje potrzeba dostpu do chronionych danych obiektu.
Jzyk C++ znany ze swojej elastycznoci ma specjalny mechanizm pozwala-

6.2. Funkcja niezalena zaprzyjaniona z klas


jcy na dostp, w sposb kontrolowany, do formalnie niedostpnych danych.
Moemy, wykorzystujc odpowiednie konstrukcje jzyka C++ spowodowa,
e zwyka funkcja bdzie moga wykonywa operacje na obiekcie w taki sposb, jak czyni to funkcja skadowa klasy. W tym przypadku, obiekt a
moe by traktowany jako jeden z argumentw formalnych funkcji. W takim
przypadku wywoanie moe mie posta:
ustaw ( a , 5 , 5 ) ;

W tym wywoaniu, obiekt a jest traktowany tak samo jak pozostae argumenty funkcji. Tym specjalnym mechanizmem w jzyku C++ jest mechanizm deklaracji zaprzyjanienia. Deklaracja zaprzyjanienia (ang. friend)
pozwala zadeklarowa w danej klasie funkcje, ktre bd miay dostp do
skadowych prywatnych, ale nie s funkcjami skadowymi klasy. Wyrniamy nastpujce sytuacje:
Funkcja niezalena zaprzyjaniona z klas X
Funkcja skadowa klasy Y zaprzyjaniona z klas X
Wszystkie funkcje klasy Y s zaprzyjanione z klas X (klasa zaprzyjaniona)
Funkcje zaprzyjanione maj dostp do wszystkich skadowych klasy
zadeklarowanych jako private lub protected. Z formalnego punktu widzenia,
funkcje zaprzyjanione ami zasad hermetyzacji (ukrywania) danych, ale
czasami ich uycie moe by korzystne.

6.2. Funkcja niezalena zaprzyjaniona z klas


Funkcja zaprzyjaniona z klas jest zdeniowana na zewntrz jej zasigu i ma dostp do skadowych prywatnych klasy. Aby zadeklarowa funkcj
jako zaprzyjanion (majc dostp do danych prywatnych) z jak klas,
prototyp funkcji w jej denicji naley poprzedzi sowem kluczowym friend.
Mimo, e prototyp funkcji umieszczony jest w denicji klasy, nie jest ona jej
funkcj skadow. Etykiety deniujce sposb dostpu do skadowych, takie jak private, public i protected nie maj nic wsplnego z denicj funkcji
zaprzyjanionych. Dobrym zwyczajem programistycznym jest umieszczanie
prototypw wszystkich funkcji zaprzyjanionych z dan klas na jej pocztku, ale nie jest to konieczne. Rozwamy program, pokazany na wydruku 6.1,
w ktrym wykorzystamy funkcje zaprzyjanion. W pokazanym przykadzie
mamy prost klas punkt:
c l a s s punkt
{
int x , y ;
public :

123

124

6. Funkcje zaprzyjanione
punkt ( i n t xx = 0 , i n t yy = 0 )
{x = xx ;
y = yy ; }
friend void pokaz ( punkt ) ;
};

Dwie dane skadowe x i y s prywatne, dostp do nich moliwy jest


jedynie poprzez funkcje skadowe klasy punkt. Klasa posiada konstruktor.
Jeeli chcemy, aby funkcja zewntrzna miaa dostp do danych skadowych
klasy punkt, musimy jawnie zadeklarowa tak funkcj jako zaprzyjanion.
W naszym przykadzie tworzymy funkcj zaprzyjanion o nazwie pokaz():
friend void pokaz ( punkt ) ;

Listing 6.1. Dostp do skadowych prywatnych; funkcje zaprzyjanione


1

11

13

15

17

19

21

#include <i o s t r e a m . h>


#include <c o n i o . h>
c l a s s punkt
{ private :
int x ;
int y ;
public :
punkt ( i n t xx = 0 , i n t yy = 0 )
{x = xx ;
y = yy ; }
friend void pokaz ( punkt ) ;
};
void pokaz ( punkt p1 )
{ c o u t << " P o z y c j a X= " << p1 . x << e n d l ;
c o u t << " P o z y c j a Y= " << p1 . y << e n d l ;
}
i n t main ( )
{ punkt a1 ( 2 , 4 ) ;
pokaz ( a1 ) ;
getche () ;
return 0 ;
}

Dziki specykacji friend funkcja pokaz() staje si funkcj zaprzyjanion, ma dostp do prywatnych danych klasy punkt. Funkcja pokaz() wywietla aktualne wartoci danych prywatnych x i y. Funkcja pokaz() jest
samodzieln funkcj w stylu jzyka C nie jest ona funkcj skadow klasy
punkt:
void pokaz ( punkt p1 )
{ c o u t << " P o z y c j a X= " << p1 . x << e n d l ;
c o u t << " P o z y c j a Y= " << p1 . y << e n d l ;
}

6.2. Funkcja niezalena zaprzyjaniona z klas


W programie testujcym:
i n t main ( )
{ punkt a1 ( 2 , 4 ) ;
pokaz ( a1 ) ;
return 0 ;
}

tworzymy obiekt a1 klasy punkt i inicjujemy go wartociami 2 i 4. Aby


wywietli na ekranie monitora wartoci x i y musimy wywoa funkcje
pokaz(). Poniewa jest to funkcja zaprzyjaniona, moemy wywoywa t
funkcje tak, jak zwyk funkcj w jzyku C:
pokaz ( a1 ) ;

W naszym przykadzie funkcja pokaz() pobiera jako argument a1. Pamitamy, e gdyby funkcja pokaz() bya funkcj skadow klasy punkt, to wywoanie jej miaoby posta:
a1 . pokaz ( ) ;

Pomimo, e funkcja pokaz() nie jest skadow klasy punkt, ze wzgldu na


deklaracj zaprzyjanienia ma ona peny dostp do prywatnych skadowych
tej klasy. Funkcja zaprzyjaniona pokaz() jest wywoywana bez uycia operatora kropka. Nie jest funkcj skadow, nie moe by poprzedzana nazw
obiektu.
W omawianym przykadzie, zamiany funkcji skadowej klasy na funkcj
zaprzyjanion z klas niczego nie wnosi, a faktycznie nawet osabia podstawow zasad programowania obiektowego, jak jest zasada ukrywania
danych, to zdarzaj si sytuacje gdzie stosowanie funkcji zaprzyjanionych
jest korzystne a nawet konieczne.

Rysunek 6.2. Funkcja zaprzyjaniona z klas deniowana jest na zewntrz jej zasigu, a mimo tego ma dostp do jej skadowych prywatnych

125

126

6. Funkcje zaprzyjanione
Na pokazanym diagramie widzimy dwie metody dostpu do prywatnych
danych klasy przy pomocy funkcji skadowych klasy lub przy pomocy
funkcji zewntrznych, ale zaprzyjanionych z klas. W kolejnym przykadzie opracujemy klas punkt, ktra ma jedn funkcj skadow inicjujc
dane klasy (ustalimy wsprzdne punktu na paszczynie) i jedn funkcj
zaprzyjanion (obliczy ona odlego punktu od pocztku ukadu wsprzdnych). W omwionym przykadzie (program z wydruku 6.2), tworzymy
obiekt p1 a nastpnie przy pomocy funkcji skadowej ustaw() przypisujemy
wartoci prywatnym zmiennym skadowym:
p1 . ustaw ( 1 . 0 , 1 . 0 ) ;

Funkcja zaprzyjaniona promien() nie moe odwoa si do zmiennych skadowych bezporednio, wykorzystuje do tego celu obiekt n klasy punkt. W
wywoaniu:
c o u t << " O d l e g l o s c = " << promien ( p1 ) << e n d l ;

funkcji zaprzyjanionej promien() przekazywany jest argument aktualny p1.


Listing 6.2. Dostp do skadowych prywatnych; funkcje zaprzyjanione
1

11

13

15

17

19

21

23

#include <i o s t r e a m >


#include <math>
#include <c o n i o >
using namespace s t d ;
c l a s s punkt
{ float x , y ;
public :
void ustaw ( f l o a t a , f l o a t b ) ;
friend f l o a t promien ( punkt n ) ;
};
void punkt : : ustaw ( f l o a t a , f l o a t b )
{ x = a;
y = b;
}
f l o a t promien ( punkt n )
{ return s q r t ( n . x n . x + n . y n . y ) ;
}
i n t main ( )
{ punkt p1 ;
p1 . ustaw ( 1 . 0 , 1 . 0 ) ;
c o u t << " O d l e g l o s c = " << promien ( p1 ) << e n d l ;
getche () ;
return 0 ;
}

Jeszcze bardziej uproszczona wersje poprzedniego przykadu pokazano


na kolejnym przykadzie (wydruk 6.3).

6.2. Funkcja niezalena zaprzyjaniona z klas


Listing 6.3. Dostp do skadowych prywatnych; funkcje zaprzyjanione
1

11

13

15

17

19

#include <i o s t r e a m >


#include <math>
#include <c o n i o >
using namespace s t d ;
c l a s s punkt
{ float x , y ;
friend f l o a t promien ( punkt n , f l o a t a , f l o a t b ) ;
};
f l o a t promien ( punkt n , f l o a t a , f l o a t b )
{ n.x = a;
n.y = b;
return s q r t ( n . x n . x + n . y n . y ) ;
}
i n t main ( )
{ punkt p1 ;
c o u t << " o d l e g l o s c = " << promien ( p1 , 1 . 0 , 1 . 0 ) << e n d l ;
getche () ;
return 0 ;
}

Zmodykowana klasa punkt ma posta:


c l a s s punkt
{ float x , y ;
friend f l o a t promien ( punkt n , f l o a t a , f l o a t b ) ;
};

Skadowe x i y s danymi prywatnymi klasy (gdy nie podamy specykatora


private, skadowe domylnie s zadeklarowane jako prywatne). Poniewa
klasa nie posiada adnych funkcji skadowych, nie ma potrzeby stosowania specykatora public (funkcje zaprzyjanione nie podlegaj specykacji
dostpu, moemy je umieszcza w dowolnej sekcji zarwno prywatnej jak i
publicznej). Funkcja zaprzyjaniona:
f l o a t promien ( punkt n , f l o a t a , f l o a t b )
{ n.x = a;
n.y = b;
return s q r t ( n . x n . x + n . y n . y ) ;
}

otrzymuje potrzebne argumenty i ustawia zmienne x i y, a nastpnie oblicza


odlego punktu od pocztku ukadu wsprzdnych.
W jzyku C++ argumenty w wywoywanej funkcji przekazywane s
przez warto lub przez referencj. Przypomnimy krtko zasady posugiwania si referencj. Zasadniczo referencja jest to niejawny wskanik. Kiedy
argument przekazywany jest przez warto, tworzona jest kopia wartoci

127

128

6. Funkcje zaprzyjanione
argumentu i ta kopia przekazywana jest do wywoywanej funkcji. Moemy
dokonywa dowolnych zmian na kopii, oryginalna warto jest bezpieczna
nie ulega zmianom. Przekazywanie argumentw przez warto jest bezpieczne, ale bardzo wolne. Przekazywanie parametrw funkcji przez referencje
jest korzystne ze wzgldu na wydajno jest po prostu procesem bardzo
szybkim. Za kadym razem, gdy przekazywany jest obiekt do funkcji przez
warto, tworzona jest kopia tego obiektu. Za kadym razem, gdy zwracany
jest obiekt z funkcji tworzona jest kolejna kopia. Obiekty kopiowane s na
stos. Wymaga to sporej iloci czasu i pamici. W przypadku zdeniowanych przez programist duych obiektw, ten koszt staje si bardzo duy.
Rozmiar obiektu umieszczonego na stosie jest sum rozmiarw wszystkich
jego zmiennych skadowych. Stosowanie referencji jest korzystne, poniewa
eliminuje koszty zwizane z kopiowaniem duych iloci danych.
Parametr referencji jest aliasem (synonimem) do odpowiadajcego mu
argumentu. Referencja jest specjalnym, niejawnym wskanikiem, ktry dziaa jak alternatywna nazwa dla zmiennej. Zmienn o podanej po nazwie typu
z przyrostkiem &, np.
T& nazwa

gdzie T jest nazw typu, nazywamy referencj do typu T. Na przykad,


deklaracja
i n t &l i c z b a

umieszczona w nagwku funkcji oznacza liczba jest referencj do int. W


krtkim przykadzie przypomnimy przekazywanie argumentw przez referencj.
Listing 6.4. Przekazywanie parametrw przez referencj
1

11

13

15

#include <i o s t r e a m >


#include <c o n i o >
using namespace s t d ;
void Kwadrat ( i n t & ) ;
i n t main ( )
{ int x = 2 ;
c o u t << " zmiennax zmodyfikowana : " <<e n d l ;
c o u t << "x= " << x << e n d l ;
Kwadrat ( x ) ;
c o u t << "x= " << x << e n d l ;
int a = x ;
c o u t << " zmiennax n i e zmodyfikowana : " <<e n d l ;
c o u t << "x= " << x << e n d l ;
Kwadrat ( a ) ;
c o u t << "x= " << x << e n d l ;
getche () ;

6.2. Funkcja niezalena zaprzyjaniona z klas


return 0 ;

17

}
19

void Kwadrat ( i n t &x r e f )


{ x r e f = x r e f ; }

// argument zmodyf i kowany

Po uruchomieniu programu otrzymujemy wydruk:


zmienna x zmodyfikowana :
x = 2
x = 4
zmienna x n i e zmodyfikowana :
x = 4
x = 4

W programie parametr funkcji Kwadrat() jest przekazywany przez referencj, co wida wyranie w prototypie:
void Kwadrat ( i n t & ) ;

Programista analizujc wywoanie funkcji:


Kwadrat ( x ) ; l u b

Kwadrat ( a ) ;

nie jest wstanie zorientowa si, czy parametry przekazywane s przez warto czy przez referencj (tak pewno ma tylko wtedy, gdy zanalizuje prototyp funkcji). Moe to prowadzi do nieoczekiwanych skutkw ubocznych,
co demonstruje nasz program. Po pierwszym wywoaniu funkcji Kwadrat()
zmienna x zmieniaa warto. Aby unikn takich skutkw wielu programistw przesya niemodykowalne argumenty stosujc referencj do staych.
Poniewa zagadnienia wydajnoci s istotne, w kolejnym programie demonstrujcym wykorzystanie funkcji zaprzyjanionych, zastosujemy przekazywanie argumentw przez referencj.
Mamy nastpujc klas:
c l a s s Demo_1
{
friend void ustawA ( Demo_1 &, i n t ) ;
// f u n k c j a z a p r z y j a z n i o n a
public :
Demo_1( ) { a = 0 ; }
void pokaz ( ) const { c o u t << a << e n d l ; }
private :
int a ;
};

W tej klasie jest zadeklarowana dana skadowa a, ktra jest prywatna, czyli

129

130

6. Funkcje zaprzyjanione
jest niedostpna dla funkcji nie bdcych skadowymi klasy. Aby funkcja zewntrzna ustawA() miaa dostp do tej danej, zadeklarowano j jako friend:
friend void ustawA ( Demo_1 &, i n t ) ;
// f u n k c j a z a p r z y j a z n i o n a

Denicja funkcji ustawA() ma posta:


void ustawA ( Demo_1 &x , i n t i l e )
{
x.a = ile ;
}

Funkcja ustawA() ma moliwo modykacji danej a poniewa jest zaprzyjaniona z klas Demo 1. Cay program ma posta przedstawiona na wydruku 6.1.
Listing 6.5. Dostp do skadowych prywatnyc; funkcje zaprzyjanione
1

11

13

15

17

19

21

23

#include <i o s t r e a m . h>


#include <c o n i o . h>
c l a s s Demo_1
{
friend void ustawA ( Demo_1 &, i n t ) ;
// f u n k c j a z a p r z y j a z n i o n a
public :
Demo_1( ) { a = 0 ; }
void pokaz ( ) const { c o u t << a << e n d l ; }
private :
int a ;
};
void ustawA ( Demo_1 &x , i n t i l e )
{ x.a = ile ;
}
i n t main ( )
{ Demo_1 zm ;
c o u t << "zm . a po u t w o r z e n i u o b i e k t u : " ;
zm . pokaz ( ) ;
c o u t << "zm . a po wywolaniu f u n k c j i f r i e n d ustawA : " ;
ustawA ( zm , 1 0 ) ;
zm . pokaz ( ) ;
getch () ;
return 0 ;
}

Po uruchomieniu otrzymujemy nastpujcy wynik:


zm . a po u t w o r z e n i u o b i e k t u : 0
zm . a po wywolaniu f u n k c j i friend ustawA : 10

W trakcie wykonywania programu zostaje utworzony obiekt zm i danej a jest

6.2. Funkcja niezalena zaprzyjaniona z klas


przypisana warto 0. Nastpnie wywoana zostaje funkcja zaprzyjaniona
ustawA() i danej a zostaje przypisana warto 10. Funkcja skadowa pokaz():
void pokaz ( )

const { c o u t << a << e n d l ; }

zostaa zadeklarowana jako const, co oznacza, e nie moe ona modykowa


danych obiektw. Funkcja deklarowana jest jako const zarwno w prototypie
jak i w denicji przez wstawienie sowa kluczowego po licie jej parametrw
i, w przypadku denicji przed nawiasem klamrowym rozpoczynajcym ciao
funkcji. Programista moe okreli, ktre obiekty wymagaj modykacji, a
ktre pod adnym pozorem nie mog by zmieniane. Gdy obiekt nie moe
by zmieniony, programista moe posuy si sowem kluczowym const.
Wszystkie prby pniejszej modykacji takiego obiektu znajdowane s ju
w trakcie kompilacji programu. W kolejnym przykadzie, pokaemy jak napisa niezalen funkcj pokaz(), zaprzyjanion z klas punkt, wypisujc na
ekranie wsprzdne punktu. Deklaracja wyjciowa klasy punkt ma posta:
c l a s s punkt
{
int x , y ;
public :
punkt ( i n t xx = 0 , i n t yy = 0 )
{ x = xx ;
y = yy ;
}
};

Aby mie dostp do prywatnych danych klasy punkt, co jest nam potrzebne,
aby wywietli wsprzdne punktu x i y, musimy dysponowa zewntrzn
funkcj pokaz(), zaprzyjanion z klas punkt. Argumenty do tej funkcji
musz by przekazane jawnie. Wobec tego prototyp tej funkcji moe mie
posta:
void pokaz ( punkt ) ;

gdy chcemy przekazywa argumenty przez warto, lub:


void pokaz

( punkt & ) ;

gdy chcemy przekaza argumenty przez referencj. Moemy usprawni funkcj pokaz() wiedzc, e ta funkcja nie zmienia wsprzdnych i zastosowa
modykator const:
void pokaz ( const punkt & ) ;

131

132

6. Funkcje zaprzyjanione
Modykacja pierwotnej klasy punkt moe mie posta pokazan na wydruku
6.6.
Listing 6.6. Dostp do skadowych prywatnych; funkcje zaprzyjanione
1

11

// d e k l a r a c j a k l a s y punkt ,
p l i k punkt1 . h
#i f n d e f _PUNKT1_H
#d e f i n e _PUNKT1_H
c l a s s punkt
{ int x , y ;
public :
friend void pokaz ( const punkt & ) ;
punkt ( i n t xx = 0 , i n t yy = 0 )
{ x = xx ;
y = yy ;
}
};
#e n d i f

Funkcja suca do wywietlenia wsprzdnych ma posta:


friend void pokaz ( const punkt &) ;

Funkcja pokaz() jest zaprzyjaniona z klas punkt i ma dostp do prywatnych danych x i y. Jeeli powstanie obiekt klasy punkt o nazwie p to moemy
uzyska dostp do jego skadowych klasycznie:
p.x

p.y

Denicj funkcji pokaz() umieszczamy w oddzielnym pliku (wydruk 6.7).


Listing 6.7. Dostp do skadowych prywatnych; funkcje zaprzyjanione
1

// d e f i n i c j a k l a s y p u n k t , p l i k p u n k t 1 . cpp
#include " punkt1 . h"
#include <i o s t r e a m . h>
void pokaz ( const punkt &p )
{ c o u t << " w s p o l r z e d n e punktu : " << p . x << " " << p . y
<< " \n" ;
}

Funkcja testujca pokazana jest na wydruku 6.8:


Listing 6.8. Dostp do skadowych prywatnych; funkcje zaprzyjanione
2

// program t e s t u j a c y f u n k c j e z a p r z y j a z n i o n e
#include <i o s t r e a m . h>
#include <c o n i o . h>
#include <punkt1 . h>

6.2. Funkcja niezalena zaprzyjaniona z klas

10

12

14

i n t main ( )
{
c o u t << " zmienna automatyczna : " << e n d l ;
punkt p1 ( 1 0 , 2 0 ) ;
pokaz ( p1 ) ;
c o u t << " zmienna dynamiczna : " << e n d l ;
punkt wsk ;
wsk = new punkt ( 2 0 , 4 0 ) ;
pokaz ( wsk ) ;
getch () ;
return 0 ;
}

Po uruchomieniu otrzymujemy nastpujcy wydruk;


zmienna automatyczna :
w s p o l r z e d n e punktu : 10 20
zmienna dynamiczna :
w s p o l r z e d n e punktu : 20 40

W pokazanym programie przypomniano take tworzenie obiektw dynamicznych. W jzyku C++ dla kadego programu przydzielany jest pewien
obszar pamici dla alokacji obiektw tworzonych dynamicznie. Obszar ten
jest zorganizowany w postaci kopca (ang. heap). Na kopcu alokowane s
obiekty dynamiczne. Obiekty dynamiczne tworzone s przy pomocy operatora new. Operator new alokuje (przydziela) pami na kopcu. Gdy obiekt
nie jest ju potrzebny naley go zniszczy przy pomocy operatora delete.
Aby mona byo zastosowa operator new naley najpierw zadeklarowa
zmienn wskanikow, dla przykadu:
int

wsk ;

Tworzenie zmiennej dynamicznej ma posta:


wsk = new typ ;

lub
wsk = new typ ( w a r t o s c ) ;

gdzie typ oznacza typ zmiennej (np. int, oat, itp.) Moemy deklaracj
zmiennej wskanikowej poczy z tworzeniem zmiennej dynamicznej:
typ wsk = newt typ ;

Tak zoona instrukcja moe mie przykadow posta:

133

134

6. Funkcje zaprzyjanione
i n t wsk = new i n t ;

W pokazanym programie utworzono zmienn dynamiczn w nastpujcy


sposb:
c o u t << " zmienna dynamiczna : " << e n d l ;
punkt wsk ;
wsk = new punkt ( 2 0 , 4 0 ) ;

6.3. Funkcja skadowa zaprzyjaniona z inn klas


Funkcja zaprzyjaniona danej klasy moe by te funkcj skadow zupenie innej klasy. Taka funkcja ma dostp do prywatnych danych swojej
klasy i do danych klasy, z ktr si przyjani. Kolejny przykad ilustruje
to zagadnienie. Mamy dwie klasy - klas prostokat i klas punkt. Klasa
prostokat deniuje prostokt przy pomocy wsprzdnych dwch punktw
lewego dolnego rogu prostokta i prawego grnego rogu prostokta. Klasa punkt opisuje punkt na podstawie jego wsprzdnych kartezjaskich.
Majc dany punkt i dany prostokt naley okreli czy punkt znajduje si
wewntrz prostokta czy te ley poza nim.
W programie deklarujemy dwie klasy punkt i prostokat z konstruktorami. Funkcja miejsce() jest funkcj skadow klasy prostakat i jest zaprzyjaniona z klas punkt. W programie testujcym wywoujemy funkcj
miejsce(), aby ustali pooenie punktu wzgldem prostokta.
Listing 6.9. Dostp do skadowych prywatnych; funkcje zaprzyjanione
1

#include <i o s t r e a m >


#include <c o n i o >
using namespace s t d ;
c l a s s punkt ;
// d e k l a r a c j a z a p o w i a d a j a c a

11

13

15

17

class prostokat
{ i n t xp , yp , xk , yk ;
public :
p r o s t o k a t ( i n t xpo , i n t ypo , i n t xko , i n t yko ) ;
void m i e j s c e ( punkt &p ) ;
};
c l a s s punkt
{ i n t x1 , y1 ;
public :
punkt ( i n t ax , i n t ay ) ;
friend void p r o s t o k a t : : m i e j s c e ( punkt &p ) ;
};

6.3. Funkcja skadowa zaprzyjaniona z inn klas


19

21

p r o s t o k a t : : p r o s t o k a t ( i n t xpo , i n t ypo , i n t xko , i n t yko )


{ xp = xpo ; yp = ypo ;
xk = xko ; yk = yko ;
}

23

25

punkt : :
punkt ( i n t ax , i n t ay )
{ x1 = ax ;
y1 = ay ;
}

27

29

31

33

35

void p r o s t o k a t : : m i e j s c e ( punkt &pz )


{ if (
( pz . x1 >= xp ) && ( pz . x1 <= xk )
&&
( pz . y1 >= yp ) && ( pz . y1 <= yk )
)
c o u t << " punkt l e z y w p o l u " << e n d l ;
else
c o u t << " punkt l e z y poza polem " << e n d l ;
}

37

39

41

43

i n t main ( )
{ p r o s t o k a t pr ( 0 , 0 , 1 0 0 , 1 0 0 ) ;
punkt pu ( 1 0 , 1 0 ) ;
pr . m i e j s c e ( pu ) ;
getche () ;
return 0 ;
}

Po uruchomieniu tego programu mamy nastpujcy wydruk:


punkt l e z y w p o l u

Jak widzimy deklaracja klasy prostokat ma posta:


class prostokat
{ i n t xp , yp , xk , yk ;
public :
p r o s t o k a t ( i n t xpo , i n t ypo , i n t xko , i n t yko ) ;
void m i e j s c e ( punkt &p ) ;
};

Funkcja miejsce() jest zwyk funkcj skadow klasy prostokat. W deklaracji funkcji skadowej miejsce() argumentem jest obiekt klasy punkt, dlatego konieczna jest deklaracja zapowiadajca klas punkt. Deklaracja klasy
punkt ma posta:
c l a s s punkt
{ i n t x1 , y1 ;
public :
punkt ( i n t ax , i n t ay ) ;

135

136

6. Funkcje zaprzyjanione
friend void p r o s t o k a t : : m i e j s c e ( punkt &p ) ;
};

Deklaracja funkcji zaprzyjanionej ma posta:


friend void p r o s t o k a t : : m i e j s c e ( punkt &p ) ;

W deklaracji zaprzyjanionej funkcji miejsce() musimy poda nazw klasy,


w ktrej ta funkcja jest funkcj skadow, w naszym przypadku jest to klasa
prostokat.
Bardzo czsto argumentuje si, e nie naley naduywa funkcji zaprzyjanionych, poniewa istot programowania obiektowego jest ukrywanie informacji. Funkcja zaprzyjaniona nie jest skadow klasy a mimo tego ma
dostp do danych prywatnych. W wielu jednak przypadkach zastosowanie
funkcji zaprzyjanionych znacznie usprawnia algorytm. Klasycznym przykadem jest program do wykonania mnoenie wektora przez macierz. Zamy, e s zdeniowane dwie klasy: wektor i macierz. Kada z nich ukrywa
swoje dane i dostarcza odpowiedni zbir operacji do dziaania na obiektach
swojego typu. Naley zdeniowa funkcj mnoc macierz przez wektor.
Ustalmy konkretne warunki. Niech wektor ma cztery elementy indeksowane
0,1,2,3. Wektor zapamitywany bdzie w postaci tablicy jednowymiarowej.
Macierz ma rozmiar 4x4 i bdzie zapamitywana w postaci tablicy dwuwymiarowej. Funkcja obliczajca iloczyn musi korzysta z danych pochodzcych z dwch klas, jest wic oczywiste, e musi by z nimi zaprzyjaniona. W
tym przypadku konieczne jest take uycie deklaracji referencyjnej(zwan
take deklaracj zapowiadajc, albo referencj zapowiadajc), w przypadku deklaracji klasy wekt musi wystpi deklaracja klasy macierz i podobnie
w deklaracji klasy macierz musi wystpi deklaracja klasy wekt.
Jest to konieczne, gdy w klasie wekt w deklaracji iloczyn() istnieje odwoanie do niezadeklarowanej jeszcze klasy macierz. Deklaracje poszczeglnych
klas i ich denicje zapisujemy w oddzielnych plikach. Musimy na pocztku
opracowa klas wekt do obsugi wektorw. Deklaracja klasy wekt moe
mie posta:
Listing 6.10. Iloczyn macierzy przez wektor; funkcje zaprzyjanione
1

// p l i k w e k t o r 1 . h , d e k l a r a c j a k l a s y wekt
#i f n d e f _WEKTOR1_H
#define _WEKTOR1_H

class macierz ;
7

c l a s s wekt
{

// d e k l a r a c j a z a p o w i a d a j a c a

6.3. Funkcja skadowa zaprzyjaniona z inn klas

11

13

15

17

double v [ 4 ] ;
// w e k t o r o 4 s k l a d o w y c h
public :
wekt ( double v1 =0 , double v2 =0 ,
double v3 =0 , double v4=0)
{v [ 0 ] = v1 ; v [ 1 ] = v2 ; v [ 2 ] = v3 ; v [ 3 ] = v4 ; }
friend wekt i l o c z y n ( const m a c i e r z &, const wekt &) ;
void pokaz ( ) ;
};
#endif

Podobnie w deklaracji klasy macierz musimy zastosowa deklaracje zapowiadajc klasy wekt. Deklaracja klasy macierz do obsugi macierzy moe
mie posta:
Listing 6.11. Iloczyn macierzy przez wektor; funkcje zaprzyjanione
// p l i k m a c i e r z . h , d e k l a r a c j a k l a s y m a c i e r z
2

#i f n d e f _MACIERZ_H
#define _MACIERZ_H

c l a s s wekt ;

class macierz
{
double mac [ 4 ] [ 4 ] ;
// m a c i e r z 4 x4
public :
m a c i e r z ( ) ; // k o n s t r u k t o r z i n i c j a c j a na 0
m a c i e r z ( double t [ 4 ] [ 4 ] ) ; // k o n s t r u k t o r , dane z t a b l i c y
friend wekt i l o c z y n ( const m a c i e r z &, const wekt &) ;
};

10

12

14

16

#endif

W klasie wekt mamy deklaracj funkcji skadowej pokaz(), ktra suy do


wywietlania skadowych wektora. Denicja funkcji pokaz() moe mie posta:
Listing 6.12. Iloczyn macierzy przez wektor; funkcje zaprzyjanione
1

// p l i k p o k a z . cpp , d e f i n i c j a s k l a d o w e j p o k a z ( )
#include <i o s t r e a m . h>
#include " wek tor1 . h"
void wekt : : pokaz ( )
{ int i ;
f o r ( i =0; i < 4 ; i ++)
c o u t << " \n" ;
}

c o u t << v [ i ] << " " ;

137

138

6. Funkcje zaprzyjanione
W klasie macierz jest zadeklarowany konstruktor macierz(). Jego denicja
moe mie posta:
Listing 6.13. Iloczyn macierzy przez wektor; funkcje zaprzyjanione
1

11

// p l i k konmac . cpp , d e f i n i c j a k o n s t r u k t o r a
#include <i o s t r e a m . h>
#include " m a c i e r z . h"

k l a s y macierz

m a c i e r z : : m a c i e r z ( double t [ 4 ] [ 4 ] )
{ int i ;
int j ;
f o r ( i =0; i <4; i ++)
f o r ( j =0; j <4; j ++)
mac [ i ] [ j ] = t [ i ] [ j ] ;
}

Naley opracowa funkcje zaprzyjanion iloczyn(). Denicja tej funkcji


(wykonuje ona mnoenie macierzy przez wektor) moe mie posta:
Listing 6.14. Iloczyn macierzy przez wektor; funkcje zaprzyjanione
1

// p l i k i l o c z y n . cpp
// d e f i n i c j a f u n k c j i i l o c z y n
#include " wek tor1 . h"
#include " m a c i e r z . h"

11

13

15

wekt i l o c z y n ( const m a c i e r z & m, const wekt & x )


{ int i , j ;
double suma ;
wekt wynik ;
f o r ( i =0; i <4; i ++)
{ f o r ( j =0 , suma=0; j <4; j ++)
suma += m. mac [ i ] [ j ] x . v [ j ] ;
wynik . v [ i ] = suma ;
};
return wynik ;
}

Moemy zacz testowa mnoenie macierzy przez wektor. Funkcje iloczyn() moemy wykorzysta do realizacji przeksztace w przestrzeni 3D. W
grace komputerowej wykorzystuje si tzw. wsprzdne jednorodne. W tych
wsprzdnych punkt (x,y,z) reprezentowany jest jako punkt w przestrzeni
4-wymiarowej (x,y,z,1). Poszczeglne przeksztacenia takie jak przesuniecie,
skalowanie czy obroty reprezentowane s macierzami 4x4. Aby otrzyma
pooenie nowego punktu P naley punkt pocztkowy P pomnoy przez
macierz przeksztacenia M:
P = M P

(6.1)

139

6.3. Funkcja skadowa zaprzyjaniona z inn klas


Przesunicie w przestrzeni 3D ma posta:

T (dx , dy , dz ) =

1
0
0
0

0
1
0
0

0 dx
0 dy

1 dz
0 1

(6.2)

Operacja skalowania przedstawiana jest nastpujco:

S(sx , sy , sz ) =

sx 0 0
0 sy 0
0 0 sz
0 0 0

0
0
0
1

(6.3)

Mamy trzy macierze reprezentujce obroty wok osi x,y i z. Obrt wok
osi z przedstawia si nastpujco:

Rz () =

cos sin 0 0
sin cos 0 0
0
0
1 0
0
0
0 1

1
0
0
0
0 cos sin 0
0 sin cos 0
0
0
0
1

cos
0
sin
0

(6.4)

Obrt wok osi x ma posta:

Rx () =

(6.5)

Obrt wok osi y ma posta:

Ry () =

0 sin 0
1
0
0

0 cos 0
0
0
1

(6.6)

Te transformacje mona atwo zwerykowa: wynikiem obrotu o 90o jednostkowego wektora osi x
h

1 0 0 1

iT

powinien by jednostkowy wektor

iT

osi y.
0 1 0 1
Oglnie mamy do czynienia z mnoeniem macierzy przez wektor:

x
y
z
1

M11
M21
M31
M41

M12
M22
M32
M42

M13
M23
M33
M43

M14
M24
M34
M44

x
y
z
1

(6.7)

W programie testujcym wyliczymy wsprzdne punktu po przeksztaceniach.

140

6. Funkcje zaprzyjanione
Listing 6.15. iloczyn macierzy przez wektor; funkcje zaprzyjanione

10

12

14

16

18

20

22

24

26

28

30

32

34

36

#include <i o s t r e a m . h>


#include <c o n i o . h>
#include " wek tor1 . h"
#include " m a c i e r z . h"
i n t main ( )
{ wekt w ( 1 , 0 , 0 , 1 ) ;
wekt wynik ;
wynik = w ;
c o u t << " punkt poczatkowy : " ;
wynik . pokaz ( ) ;
double t r a n s [ 4 ] [ 4 ] = { 1 , 0 , 0 , 2 ,
0, 1, 0, 3,
0, 0, 1, 1,
0 , 0 , 0 , 1 };
macierz a = t r an s ;
wynik = i l o c z y n ( a , w) ;
c o u t << " po t r a n s l a c j i : " ;
wynik . pokaz ( ) ;
double s k a l [ 4 ] [ 4 ] = { 2 , 0 , 0 , 0 ,
0, 2, 0, 0,
0, 0, 2, 0,
0 , 0 , 0 , 1 };
macierz b = s k a l ;
wynik = i l o c z y n ( b , w) ;
c o u t << " po s k a l o w a n i u : " ;
wynik . pokaz ( ) ;
double obrZ [ 4 ] [ 4 ] = {0 , 1 , 0 , 0 ,
1, 0, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
m a c i e r z c = obrZ ;
wynik = i l o c z y n ( c , w) ;
c o u t << " po o b r o c i e o PI /2 : " ;
wynik . pokaz ( ) ;
getch () ;
return 0 ;
}

Po uruchomieniu programu testujcego otrzymamy nastpujcy wynik:


punkt poczatkowy : 1 0 0 1
po t r a n s l a c j i : 3 3 1 1
po s k a l o w a n i u : 2 0 0 1
po o b r o c i e o PI /2 : 0 1 0 1

141

6.4. Klasy zaprzyjanione

6.4. Klasy zaprzyjanione


Kada klasa moe mie wiele funkcji zaprzyjanionych, mona nawet
uczyni wszystkie funkcje skadowe jednej klasy zaprzyjanione z inn klas.
Moemy przy pomocy sowa kluczowego friend uczyni dan klas zaprzyjanion z inn klas. Jeeli zadeklarujemy, e caa klasa A jest uznawana
za przyjaciela klasy B to ten fakt zapisujemy w nastpujcy sposb:
class B
{
friend c l a s s A ;
// . . . . . . . . . . . . . . . . . . . . .
};

Zapis:
friend c l a s s A ;

oznacza, e wszystkie funkcje skadowe klasy A maja dostp do danych prywatnych i chronionych klasy B. Jeeli klasa A ma by zaprzyjaniona z klas
B, to deklaracja klasy A musi poprzedza deklaracj klasy B. Zagadnienie
to zilustrujemy przykadem.
Listing 6.16. Klasy zaprzyjanione
1

#include <i o s t r e a m >


#include <c o n i o >

using namespace s t d ;
5

11

c l a s s Dane
{
i n t x1 , x2 ;
public :
Dane ( i n t a , i n t b ) { x1 = a ;
friend c l a s s Test ;
};

x2 = b ; }

13

15

17

19

21

23

c l a s s Test
{
public :
i n t min ( Dane a ) { return a . x1 < a . x2 ? a . x1 : a . x2 ; }
i n t i l o c z y n ( Dane a ) { return a . x1 a . x2 ; }
};
i n t main ( )
{
Dane l i c z b y ( 1 0 , 2 0 ) ;
Test t ;

142

6. Funkcje zaprzyjanione
c o u t << " m n i e j s z a t o : " << t . min ( l i c z b y ) << e n d l ;
c o u t << " i l o c z y n = " << t . i l o c z y n ( l i c z b y ) << e n d l ;

25

27

getche () ;
return 0 ;

29

Po uruchomieniu programu mamy nastpujcy wydruk:


m n i e j s z a t o : 10
i l o c z y n = 200

W pokazanym programie klasa Test ma dwie funkcje skadowe (jedna wyznacza mniejsz z dwch liczb, druga wylicza iloczyn dwch liczb) i ma
dostp do danych prywatnych klasy Dane. Naley pamita, e klasy zaprzyjanione stosuje si stosunkowo rzadko w praktyce.

Rozdzia 7
Przecianie operatorw

7.1.
7.2.
7.3.
7.4.
7.5.
7.6.
7.7.
7.8.

Wstp . . . . . . . . . . . . . . . . . . . . . . . . .
Denicje . . . . . . . . . . . . . . . . . . . . . . .
Przeciony operator dodawania ( + ) . . . . . . .
Przeciony operator mnoenia ( * ) . . . . . . . .
Funkcja operatorowa w postaci niezalenej funkcji
Przecianie operatorw rwnoci i nierwnoci . .
Przecianie operatora przypisania ( = ) . . . . .
Przecianie operatora wstawiania do strumienia (

. .
. .
. .
. .
. .
. .
. .
)

.
.
.
.
.
.
.
.

144
144
147
150
154
161
165
173

144

7. Przecianie operatorw

7.1. Wstp
Programujc w jzyku C++ programista moe korzysta zarwno z typw wbudowanych jak i typw stworzonych przez siebie. W jzyku C++
zosta zdeniowany zestaw operatorw, wszystkie one s uywane w poczeniu z typami wbudowanymi. W jzyku C++ nie mona tworzy nowych
operatorw. Czsto jednak zachodzi potrzeba zmienienia sposobu dziaania
konkretnego operatora. Jzyk C++ pozwala na przecianie istniejcych
operatorw, to znaczy moemy nadawa im nowe znaczenie dla operacji
wykonywanych czciowo lub w caoci na obiektach typu klasa. By moe
przecianie operatorw na pierwszy rzut oka jest do egzotyczn technik,
ale wielu programistw nie zdaje sobie sprawy z tego, e uywa przecionych operatorw. Przykadem przecionego operatora jest operator dodawania (+). Operator dodawania dziaa inaczej na danych typu int i inaczej
na danych typu double. Takie dziaanie jest moliwe, poniewa operator
dodawania zosta przeciony w samym jzyku C++.

7.2. Denicje
Aby przeciy operator deniuje si funkcj (z nagwkiem i ciaem) w
zwykej postaci z wyjtkiem tego, e nazw funkcji jest sowo kluczowe operator poprzedzajce symbol przecianego operatora. Na przykad nazwa
funkcji operator+ mogaby by uyta do przecienie operatora dodawania
(+). Przecianie operatora realizowane jest:
albo w postaci niezalenej funkcji (zazwyczaj zaprzyjanionej z jedn
lub kilkoma klasami)
albo w postaci funkcji skadowej
W pierwszym przypadku, jeeli oper jest operatorem dwuargumentowym,
to zapis:
x oper y

jest rwnoznaczny wywoaniu:


operator o p e r ( x , y )

w drugim przypadku ten sam zapis odpowiada wywoaniu:


x . operator o p e r ( y )

145

7.2. Denicje
Tabela 7.1. Tabela operatorw, ktre mog by przeciane

Operator
()
[]
>
New
Delete
++

*
*
/
%
+
<<
>>
<
<=
>
>=
==
!=
&&
||
&

|
=
+= -= *=
/= %= &=
= | =
<<=>>=
,

Opis
Wywoanie funkcji (Function call)
Element tablicy (Array element)
Operator dostepu (Structure memeber pointer reference)
Dynamicznie alokowana pami (Dynamically allocate memory)
Dynamicznie usuwana pami (Dynamically deallocated memory)
Inkrementacja (Increment)
Dekrementacja (Decrement)
Minus (Unary minus)
Logiczna negacja (Logical negation)
Dopenienie logiczne (Ones complement)
Dereferencja (Indirection)
Mnoenie (Multiplication)
Dzielenie (Division)
Modulo (Modulus (remainder))
Dodawanie (Addition)
Odejmowanie (Subtraction)
Przesunicie (Left shift)
Przesunicie (Right shift)
Mniej ni (Less than)
Mniej niz lub rowne (Less than or equel to)
Wiksze ni (Greater than)
Wiksze ni lub rwne (Greater than or equel to)
Rwne (Equal to)
Rne (Not equal to)
Logiczne AND (Logical AND)
Logiczne OR (Logical OR)
Bitowe AND (Bitwise AND)
Bitowe XOR (Bitwise exlusive OR)
Bitowe OR (Bitwise inclusive OR)
Przypisanie (assignment)
Przypisanie (assignment)
Przypisanie (assignment)
Przypisanie (assignment)
Przypisanie (assignment)
Przecinek (Comma)

Przeciony operator musi mie jako jeden z operandw obiekt. W jzyku


C++ mona przecia prawie wszystkie operatory, a lista operatorw, ktre

146

7. Przecianie operatorw
mog by przeciane jest pokazana w tabeli. Kady z tych operatorw moe
by przeciony w dowolny sposb (np. operator dodawania po przecieniu
wcale nie musi wykonywa operacji dodawania). Oczywicie s pewne cise
reguy. Operator binarny musi pozosta binarnym, a operator unarny musi
pozosta unarnym.
Dziaanie symboli pokazanych w tabeli moe by przedeniowane tak,
aby jak najlepiej obsugiwa swoj klas.
Naley pamita o nastpujcych ograniczeniach:
Symbole, ktre nie s umieszczone w tabeli nie mog by przeciane.
Odnosi si to takich symboli jak:
operator kropka(.)
operator wyuskania wskanika do skadowej (.*)
operator zasigu (::)
operator warunkowy (?:)
operator rozmiaru (sizeof)
symbole # i ##
Nie mona kreowa nowych symboli operatorw. Np. nie jest operatorem w C++, nie moe by kreowany jako operator klasy. W jzyku
C++ nie istnieje operator potgowania, wic prba tworzenia wasnego
operatora potgowania nie wydaje si konieczna, w jzyku istnieje odpowiednia funkcja, ktr naley wywoa w celu wykonania potgowania.
Ani kolejno (ang. precedence) ani czno (ang. associativity) operatorw C++ nie moe by modykowana. Nie mona np. operatorowi
dodawania nada wyszy priorytet ni ma operator dzielenia.
Nie mona redeniowa operatorw dla typw wbudowanych
Operator unarny nie moe by zmieniany na binarny, a binarny nie moe
by zmieniany na unarny.
Operator musi by albo czonkiem klasy albo by tak deniowany, aby
przynajmniej jeden czonek klasy by jego operandem.
Zazwyczaj konstruujc jak klas naley okreli, czy potrzebne bd
specjalne operatory. Na przykad, aby porwna dwa trjwymiarowe wektory, naley kolejno sprawdzi czy odpowiednie skadowe (x, y oraz z) dwch
wektorw s rwne. Zdeniowana przez uytkownika operacja (operator)
jest projektowana jako funkcja, ktra redeniuje wbudowany w C++ symbol operatora i moe by wykorzystana przez klas. Funkcja, ktra deniuje
operacje na obiektach klasy i wykorzystuje wbudowane w C++ symbole operatorw nosi nazw funkcji operatorowej (operator function). Funkcje operatorowe s deklarowane i implementowane w taki sam sposb, jak wszystkie
funkcje skadowe, z jednym wyjtkiem wszystkie funkcje operatorowe maj
nazw postaci:

7.3. Przeciony operator dodawania ( + )


operator op

gdzie op jest jednym z symboli pokazanych w tabeli przecianych operatorw. Na przykad, nazwa funkcji operator + jest nazw funkcji dodawania,
a nazwa funkcji operator == jest nazw funkcji porwnujcej rwny z.

7.3. Przeciony operator dodawania ( + )


Omwimy prosty przykad ilustrujcy dziaanie przecionego operatora
dodawania. Funkcja operatora jest funkcj skadow klasy.
Listing 7.1. Przecianie operatora funkcja operatorowa jest skadow
1

11

13

15

17

19

#include <i o s t r e a m >


#include <c o n i o >
using namespace s t d ;
c l a s s wek
{ i n t vx , vy ;
public :
void ustaw ( i n t ux , i n t uy ) ;
void pokaz ( ) ;
wek operator+ ( wek v ) ;
};
void wek : : ustaw ( i n t ux , i n t uy )
{ vx = ux ;
vy = uy ;
}
void wek : : pokaz ( )
{ c o u t << " sk ladowe wek tora : " ;
c o u t << vx << " , " << vy << e n d l ;
}

21

23

25

27

29

31

33

35

wek wek : : operator+ ( wek v )


{ wek wek tor ;
wek tor . vx = vx + v . vx ;
wek tor . vy = vy + v . vy ;
return wek tor ;
}
i n t main ( )
{wek a , b , suma_wek ;
a . ustaw ( 1 , 1 ) ;
b . ustaw ( 1 , 2 ) ;
a . pokaz ( ) ;
b . pokaz ( ) ;
c o u t << " po dodaniu " ;
suma_wek = a + b ;

147

148

7. Przecianie operatorw
// suma_wek = a . o p e r a t o r +(b ) ;
suma_wek . pokaz ( ) ;
getche () ;
return 0 ;

37

39

41

Po uruchomieniu tego programu otrzymujemy nastpujcy wydruk:


sk ladowe wek tora : 1 , 1
sk ladowe wek tora : 1 , 2
po dodaniu sk ladowe wek tora : 2 , 3

Ten przykadowy program uywa klasy wek i funkcji skadowej, ktra jest
przecionym operatorem dodawania. W omawianym programie wykonywane jest dodawanie dwch wektorw (dwuwymiarowych), w wyniku otrzymuje trzeci wektor. Operacja dodawania wektorw polega na oddzielnym
dodawaniu skadowych poszczeglnych wektorw:
a+b=c

(7.1)

ax + bx = cx

(7.2)

ay + by = cy

(7.3)

Zadanie dodawania wektorw zrealizujemy za pomoc operatora +, przecionego w taki sposb, aby wykona pokazan metod dodawanie skadowych wektorw. W konwencji uywanej przez ten program po dodaniu
wektora a do wektora b otrzymamy wektor c. Opracowana klasa ma posta:
c l a s s wek
{ i n t vx , vy ;
public :
void ustaw ( i n t ux , i n t uy ) ;
void pokaz ( ) ;
wek operator+ ( wek v ) ;
};

W deklaracji klasy wek widzimy dwie dane prywatne vx i vy, oraz trzy
funkcje skadowe. Funkcja dostpu ustaw() suy do inicjowania skadowych
wektora, funkcja pokaz() suy do wywietlania skadowych wektora. W deklaracji klasy wek mamy take funkcj operatorow (funkcja przecionego
operatora), ktra nadaje nowe znaczenie operatowi +:
wek operator+ ( wek v ) ;

Denicja funkcji operatorowej ma posta:

7.3. Przeciony operator dodawania ( + )


wek wek : : operator+ ( wek v )
{ wek wek tor ;
wek tor . vx = vx + v . vx ;
wek tor . vy = vy + v . vy ;
return wek tor ;
}

Naley zauway, e w tej deklaracji pierwszym argumentem operatora jest


wektor, (poniewa operator jest skadow klasy wek), a drugim argumentem
jest take wektor (poniewa typem parametru jest wek). Wek przed sowem
kluczowym operator oznacza, e rezultatem uycia operatora (zwracanym
przez niego typem) jest nowy wektor. W funkcji main() deniujemy trzy
egzemplarze klasy wek, a, b i suma wek. Dane skadowe wektorw a i b
inicjalizujemy za pomoc funkcji ustaw(). Do zmiennej suma wek przypisujemy wynik wywoania przecionego operatora:
suma_wek = a + b ;

Naley zauway, e funkcja operatorowa ma tylko jeden parametr, a wiemy,


e przeciamy operator dwuargumentowy. W jzyku C++ obowizuje zasada, e jeeli operator dwuargumentowy jest przeciany za pomoc funkcji skadowej klasy, to jawnie przekazywany jest mu tylko jeden argument.
Drugi argument jest przekazywany niejawnie za pomoc wskanika this. Tak
wic, zmienna vx wystpujca w instrukcji funkcji operator+():
wek tor . vx = vx + v . vx ;

oznacza this > vx, czyli element vx obiektu, ktry spowodowa wywoanie
funkcji operatora. Naley zapamita nastpujc regu:
Obiekt powodujcy wywoanie funkcji operatora znajduje si
zawsze po lewej stronie operacji. Obiekt wystpujcy po prawej
stronie jest przekazywany funkcji w postaci argumentu.
Jeeli funkcja operatorowa jest skadow klasy to przypadku przecienia
operatora jednoargumentowego nie ma potrzeby przesyania argumentw.
W obu sytuacjach (przecianie operatorw jednoargumentowych i przecianie operatorw dwuargumentowych) obiekt powodujcy wywoanie funkcji operatora jest jej niejawnie przekazywany za pomoc wskanika this. W
denicji funkcji operatorowej w instrukcji:
wek tor . vx = vx + v . vx ;

vx jest prywatn dan klasy wek, udostpnion za pomoc niejawnego


wskanika this. T instrukcj moemy zapisa z jawnym wskanikiem this:
wek tor . vx = this>vx + v . vx ;

149

150

7. Przecianie operatorw
Z kolei w funkcji testujcej instrukcj:
suma_wek = a + b ;

moemy zastpi ekwiwalentn instrukcj:


suma_wek = a . operator +(b ) ;

Ten ostatni zapis pomaga zrozumie, w jaki sposb funkcja operator+()


otrzymuje niejawny wskanik this, odnoszcy si do obiektu klasy a. Za
pomoc instrukcji
suma_wek = a + b ;

wektor suma wek otrzymuje wartoci skadowych, ktre s sum poszczeglnych skadowych wektorw a i b.
Wykonanie pokazanej instrukcji skada si z dwch operacji:
1. Najpierw wywoany jest operator dodawania (+), ktrego dwoma argumentami s wektory a i b. Operator dodawania zwraca nowy tymczasowy
obiekt wektora jako wynik swojego dziaania.
2. Nowy wektor zwrcony przez operator dodawania jest przypisany wektorowi suma wek za pomoc domylnego operatora przypisania, ktry
kopiuje skadowe wektorw.
Niektre operatory posiadaj specjalne waciwoci, do takich naley
operator przypisania (=). Domylny operator przypisania zdeniowany jest
dla kadej klasy. Przypisuje on poszczeglne skadowe obiektw i zwraca
obiekt, ktremu przypisana zostaa nowa warto. Przecianie operatora
przypisania wymaga rozwizania kilku wanych kwestii, szczeglnie, gdy
skadowymi klas s wskaniki. Istniej take inne operatory automatycznie
generowane dla kadej klasy. S to:
operator pobrania adresu ( & ), obiektu danej klasy
operator przecinka ( , )
operator tworzenia obiektw dynamicznych ( new )
operator niszczenia obiektw dynamicznych ( delete )

7.4. Przeciony operator mnoenia ( * )


Przecianie operatora mnoenia ( * ) zilustrujemy przykadem w ktrym funkcja operatorowa (operator* ( ) ) umoliwi mnoenie dwch uamkw. Funkcja operatorowa jest funkcj skadow klasy.
Listing 7.2. mnoenie uamkw przeciony operator *
1

#include <i o s t r e a m >

7.4. Przeciony operator mnoenia ( * )


#include <c o n i o >
3

using namespace s t d ;
5

11

13

15

c l a s s ulamek
{ i n t l i , mia ;
public :
ulamek ( ) ;
ulamek ( int , i n t ) ;
ulamek operator ( ulamek ) ;
void pokaz ( ) ;
};
ulamek : : ulamek ( ) :
{ }

l i ( 0 ) , mia ( 1 ) // i n i c j u j e ulamek zerem

17

19

21

23

25

ulamek : : ulamek ( i n t a , i n t b )
{ i f ( b == 0 )
{ c e r r << " mianownik = 0 " << e n d l ;
e x i t (EXIT_FAILURE) ;
}
li = a;
mia = b ;
}

27

29

void ulamek : : pokaz ( )


{ c o u t << l i << " / " << mia << e n d l ;
}

31

33

ulamek ulamek : : operator ( ulamek x )


{ return ulamek ( l i x . l i , mia x . mia ) ;
}

35

37

39

41

43

45

47

i n t main ( )
{ ulamek u1 ( 3 , 4 ) , u2 ( 5 , 6 ) ;
c o u t << " ulamek 1 = " ;
u1 . pokaz ( ) ;
c o u t << " ulamek 2 = " ;
u2 . pokaz ( ) ;
ulamek wynik ;
wynik = u1 u2 ;
c o u t << " i l o c z y n ulamkow= " ;
wynik . pokaz ( ) ;
getche () ;
return 0 ;
}

Po uruchomieniu tego programu mamy nastpujcy wydruk:


ulamek 1 = 3/4

151

152

7. Przecianie operatorw
ulamek 2 = 5/6
i l o c z y n ulamkow = 15/24

W pokazanym przykadzie klasa ulamek ma posta:


c l a s s ulamek
{ i n t l i , mia ;
public :
ulamek ( ) ;
ulamek ( int , i n t ) ;
ulamek operator ( ulamek ) ;
void pokaz ( ) ;
};

Uamek przedstawiamy w postaci licznik/mianownik, wobec czego klasa


ulamek ma dwie dane prywatne li i mia (licznik i mianownik). Klasa ma
dwa konstruktory oraz funkcj skadow pokaz() do wywietlania licznika i
mianownika uamka. Deklaracje konstruktorw s nastpujce:
ulamek ( ) ;
ulamek ( int , i n t ) ;

Domylny konstruktor ulamek() ma posta:


ulamek : : ulamek ( ) :
{ }

l i ( 0 ) , mia ( 1 ) // i n i c j u j e ulamek zerem

i inicjuje uamek 0/1 ( licznik jest rwny zeru, mianownik jest rwny jeden). W denicji konstruktora wykorzystalimy konstrukcj z tak zwan
list inicjacji. Po nazwie konstruktora umieszczamy znak dwukropka, po
nim pojawia si lista inicjacji atrybutw. Powysza denicja konstruktora
rwnowana jest klasycznej konstrukcji:
ulamek : : ulamek ( )
{
l i = 0;
mia = 1 ;
}

Zauwamy, e w jzyku C++ moemy deklarowa zmienne i inicjowa je na


trzy sposoby:
1. int x = 0;
2. int x(0);
3. int x;
x = 0;
Drugi konstruktor posiada dwa parametry, i wywoywany jest wtedy,

7.4. Przeciony operator mnoenia ( * )


gdy tworzymy konkretny uamek podajc warto licznika i mianownika.
Konstruktor sprawdza take, czy mianownik nie jest zerem:
ulamek : : ulamek ( i n t a , i n t b )
{ i f ( b == 0 )
{ c e r r << " mianownik = 0 " << e n d l ;
e x i t (EXIT_FAILURE) ;
}
li = a;
mia = b ;
}

W klasie ulamek mamy take deklarowana funkcj operatorow:


ulamek operator ( ulamek ) ;

Widzimy, e pierwszym argumentem operatora mnoenia jest uamek, poniewa deklaracja funkcji operatorowej jest wewntrz klasy ulamek. Drugim
argumentem operatora jest take uamek, poniewa ten typ zosta umieszczony na licie argumentw operatora. Rezultatem zastosowania operatora
mnoenia bdzie nowy uamek, poniewa typ ulamek poprzedza sowo kluczowe operator. Implementacja operatora mnoenia ma posta:
ulamek ulamek : : operator ( ulamek x )
{
return ulamek ( l i x . l i , mia x . mia ) ;
}

Operator mnoenia zdeniowany w klasie ulamek jest operatorem dwuargumentowym. Zastosowanie operatora zakresu postaci ulamek :: sprawia,
e pierwszym argumentem operatora mnoenia jest obiekt klasy ulamek,
na rzecz ktrego operator zosta wywoany. Drugi argument jest umieszczony na licie argumentw operatora mnoenia jest to take obiekt klasy
ulamek. W programie testujcym tworzone s dwa uamki, korzystamy z
konstruktorw:
ulamek u1 ( 3 , 4 ) , u2 ( 5 , 6 ) ;

Tworzone s dwa obiekty: u1 (uamek 3/4 ) oraz u2 (uamek 5/6 ). Deklarujemy jeszcze jeden uamek i wykonujemy mnoenie uamkw:
ulamek wynik ;
wynik = u1 u2 ;

W programowaniu obiektowym nie istnieje globalna funkcja mnoenia dwch


uamkw, moemy wykona jedynie odpowiedni operacj. Do istniejcego
uamka wysyany jest komunikat: wykonaj mnoenie z przekazanym za po-

153

154

7. Przecianie operatorw
moc parametru uamkiem. W naszym przypadku komunikatem tym jest
funkcja operatorowa, a odbiorc tego komunikatu jest uamek. Wyraenie
postaci:
u1 u2

interpretowane jest jak:


u1 . operator ( u2 )

W kontekcie naszego programu operator mnoenia ( * ) mnoy dwa uamki,


jest to operator dwuargumentowy. Pierwszym argumentem jest uamek u1
( jest to obiekt, na rzecz ktrego zostaa wywoana funkcja operatorowa,
umieszczony jest on po lewej stronie sowa kluczowego operator). Drugim
argumentem jest u2, jest to argument funkcji operatorowej. Wywoana funkcja operatorowa zwraca uamek, ktry jest iloczynem uamkw u1 i u2.
Najprostsza denicja funkcji operatorowej moe mie posta:
ulamek ulamek : : operator ( ulamek x )
{ ulamek ulamek12 ;
ulamek12 . l i = l i + x . l i ;
// o b l i c z a l i c z n i k
ulamek12 . mia = mia + x . mia ;
// o b l i c z a mianownik
return ulamek12 ;
// zwraca wyni k mnozenia
}

Ta denicja jest zrozumiaa, ale moemy j uproci.


W bardziej wydajnej implementacji nie tworzymy lokalnego obiektu tymczasowego (ulamek ulamek12 ):
ulamek ulamek : : operator ( ulamek x )
{ return ulamek ( l i x . l i , mia x . mia ) ;
}

7.5. Funkcja operatorowa w postaci niezalenej funkcji


Jak ju mwilimy funkcja operatorowa moe by implementowana jako
funkcja skadowa klasy albo jako zwyka funkcja globalna. Naley pamita,
e zwyka funkcja nie ma dostpu bezporedniego do danych prywatnych.
Jeeli chcemy, aby funkcja globalna miaa dostp do danych prywatnych klasy musimy funkcj operatorow zadeklarowa jako funkcj zaprzyjanion
z klas. Zanim pokaemy przykad z funkcj operatorow w postaci funkcji
zaprzyjanionej, omwimy program, dziki ktremu wykonamy mnoenie
uamkw korzystajc z funkcji zaprzyjanionych (nie uyjemy przecienia
operatora.). Klasa ulamek ma nastpujc posta:

7.5. Funkcja operatorowa w postaci niezalenej funkcji


c l a s s ulamek
{ i n t l i , mia ;
public :
ulamek ( int , i n t ) ;
friend i n t i l o c z y n _ L i ( ulamek &, ulamek &) ;
friend i n t iloczy n_Mi ( ulamek &, ulamek &) ;
void pokaz ( ) ;
};

Listing 7.3. mnoenie uamkow funkcje zaprzyjanione


2

#include <i o s t r e a m >


#include <c o n i o >

using namespace s t d ;

c l a s s ulamek
{ i n t l i , mia ;
public :
ulamek ( int , i n t ) ;
// k o n s t r u k t o r
friend i n t i l o c z y n _ L i ( ulamek &, ulamek &) ;
friend i n t iloczy n_Mi ( ulamek &, ulamek &) ;
void pokaz ( ) ;
};

10

12

14

16

18

20

ulamek : : ulamek ( i n t a , i n t b )
{ i f ( b == 0 )
{ c e r r << " mianownik = 0 " << e n d l ;
e x i t (EXIT_FAILURE) ;
}
li = a;
mia = b ;
}

22

24

26

28

30

32

34

36

38

void ulamek : : pokaz ( )


{ c o u t << l i << " / " << mia << e n d l ;
}
i n t i l o c z y n _ L i ( ulamek &x , ulamek &y )
{ return ( x . l i y . l i ) ;
}
i n t iloczy n_Mi ( ulamek &x , ulamek &y )
{ return ( x . mia y . mia ) ;
}
i n t main ( )
{ ulamek u1 ( 3 , 4 ) , u2 ( 5 , 6 ) ;
i n t l i c z n i k , mianownik ;
c o u t << " ulamek 1 = " ;
u1 . pokaz ( ) ;
c o u t << " ulamek 2 = " ;
u2 . pokaz ( ) ;
l i c z n i k = i l o c z y n _ L i ( u1 , u2 ) ;

155

156

7. Przecianie operatorw
mianownik = iloczy n_Mi ( u1 , u2 ) ;
ulamek u3 ( l i c z n i k , mianownik ) ;
c o u t << " i l o c z y n ulamkow= " ;
u3 . pokaz ( ) ;
getche () ;
return 0 ;

40

42

44

46

Po uruchomieniu tego programu mamy nastpujcy wydruk:


ulamek 1 = 3/4
ulamek 2 = 5/6
i l o c z y n ulamkow = 15/24

W denicji klasy ulamek mamy dwie dane prywatne li i mia (s to wartoci


odpowiednio licznika i mianownika), dwie funkcje skadowe (konstruktor i
funkcja pokaz() ) oraz dwie funkcje zaprzyjanione. Iloczyn dwch uamkw jest nowym uamkiem jego licznik jest rwny iloczynowi licznikw
a mianownik jest rwny iloczynowi mianownikw. Do obliczenia iloczynw
uyjemy funkcji zaprzyjanionych:
friend i n t i l o c z y n _ L i ( ulamek &, ulamek &) ;
friend i n t iloczy n_Mi ( ulamek &, ulamek &) ;

Implementacja tych funkcji ma posta:


i n t i l o c z y n _ L i ( ulamek &x , ulamek &y )
{ return ( x . l i y . l i ) ;
}
i n t iloczy n_Mi ( ulamek &x , ulamek &y )
{ return ( x . mia y . mia ) ;
}

Naley zwrci uwag, e argumentami funkcji s referencje obiektw. W


funkcji testujcej tworzymy dwie zmienne pomocnicze licznik i mianownik
oraz dwa uamki u1 i u2:
ulamek u1 ( 3 , 4 ) , u2 ( 5 , 6 ) ;
i n t l i c z n i k , mianownik ;

a nastpnie wywoujemy funkcje zaprzyjanione:


l i c z n i k = i l o c z y n _ L i ( u1 , u2 ) ;
mianownik = iloczy n_Mi ( u1 , u2 ) ;

dziki ktrym obliczamy iloczyn licznikw uamkw u1 i u2 oraz iloczyn

7.5. Funkcja operatorowa w postaci niezalenej funkcji


mianownikw tych uamkw. Majc obliczony nowy licznik i nowy mianownik tworzymy uamek u3 bdcy iloczynem uamkw u1 i u2:
ulamek u3 ( l i c z n i k , mianownik ) ;

Analizujc powyszy przykad widzimy, e obliczanie iloczynu uamkw przy


pomocy funkcji zaprzyjanionych wymaga skomplikowanych wywoa tych
funkcji. Pokaemy, e w znaczcy sposb moemy uproci program wprowadzajc przeciony operator mnoenia jako funkcj operatorow zaprzyjanion z klas.
Listing 7.4. mnoenie uamkw funkcja operatorowa zaprzyjaniona
1

11

13

15

17

19

21

#include <i o s t r e a m >


#include <c o n i o >
using namespace s t d ;
c l a s s ulamek
{ i n t l i , mia ;
public :
ulamek ( ) ;
// k o n s t r u k t o r
ulamek ( int , i n t ) ;
// k o n s t r u k t o r
friend ulamek operator ( ulamek , ulamek ) ;
void pokaz ( ) ;
};
ulamek : : ulamek ( i n t a , i n t b )
{ i f ( b == 0 )
{ c e r r << " mianownik = 0 " << e n d l ;
e x i t (EXIT_FAILURE) ;
}
li = a;
mia = b ;
}
ulamek : : ulamek ( ) : l i ( 0 ) , mia ( 1 )
{ }

23

25

void ulamek : : pokaz ( )


{ c o u t << l i << " / " << mia << e n d l ;
}

27

29

31

33

35

37

ulamek operator ( ulamek x , ulamek y )


{ ulamek u ;
u. li = x. li y. li ;
u . mia = x . mia y . mia ;
return u ;
}
i n t main ( )
{ ulamek u1 ( 3 , 4 ) , u2 ( 5 , 6 ) , u3 ;
c o u t << " ulamek 1 = " ;

157

158

7. Przecianie operatorw
u1 . pokaz ( ) ;
c o u t << " ulamek 2 = " ;
u2 . pokaz ( ) ;
u3 = u1 u2 ;
c o u t << " i l o c z y n ulamkow= " ;
u3 . pokaz ( ) ;
getche () ;
return 0 ;

39

41

43

45

W powyszym przykadzie deklaracja klasy jest nastpujca:


c l a s s ulamek
{ i n t l i , mia ;
public :
ulamek ( ) ;
// k o n s t r u k t o r
ulamek ( int , i n t ) ;
// k o n s t r u k t o r
friend ulamek operator ( ulamek , ulamek ) ;
void pokaz ( ) ;
};

Deklaracja klasy ulamek zawiera deklaracje dwch danych prywatnych li i


mia, dwch konstruktorw, jednej funkcji skadowej pokaz() oraz operatorowej funkcji zaprzyjanionej. Naley pamita o konstruktorze bezparametrowym, bez jego jawnej denicji kompilacja naszego programu nie powiedzie
si. Deklaracja funkcji zaprzyjanionej ma posta:
friend ulamek operator ( ulamek , ulamek ) ;

W jzyku C++ operatory mog by przeciane take przy pomocy funkcji,


ktre nie s skadowymi klasy. Mona stosowa zwyke funkcje globalne a
take funkcje zaprzyjanione. Aby dana funkcj zadeklarowa jako funkcj
zaprzyjanion z konkretn klas, naley wstawi prototyp takiej funkcji
do wntrza denicji klasy (tak, jakby to bya metoda danej klasy) i poprzedzi ten prototyp sowem kluczowym friend. W zastosowaniach taka funkcja
bdzie traktowana jakby bya metod nalec do danej klasy. Jak pamitamy, funkcje zaprzyjanione maj dostp do danych prywatnych klasy. W
pokazanym przykadzie funkcja operatorowa jest funkcj zaprzyjanion.
Poniewa do funkcji zaprzyjanionych nie jest przekazywany wskanik
this, zaprzyjaniona funkcja operatorowa wymaga przekazania operandw
w sposb jawny. Wobec tego funkcja operatorowa jednoargumentowa wymaga przekazania jednego parametru, funkcja operatorowa dwuargumentowa wymaga przekazania dwch argumentw. Naley pamita o kolejnoci przekazywanych argumentw. W funkcji operatorowej zaprzyjanionej lewy operand jest przekazywany jako pierwszy, a prawy jako drugi. Do

7.5. Funkcja operatorowa w postaci niezalenej funkcji


przeciania operatorw najczciej wykorzystuje si funkcje zaprzyjanione, poniewa funkcje zaprzyjanione s bardziej elastyczne w porwnaniu z
funkcjami skadowymi. Skadowe funkcje operatorowe wymagaj zgodnoci
typw operandw, natomiast w przypadku funkcji zaprzyjanionych nie jest
wymagane, aby lewy operand koniecznie by obiektem klasy. Std wynika
uyteczno zaprzyjanionych funkcji operatorowych - moemy miesza typy
operandw, co jest przydatne, jeeli chcemy stosowa w wyraeniach obiekty i dane numeryczne. Implementacja przecienia operatora mnoenia przy
pomocy funkcji zaprzyjanionej moe mie posta:
ulamek operator ( ulamek x , ulamek y )
{ ulamek u ;
u. li = x. li y. li ;
u . mia = x . mia y . mia ;
return u ;
}

Przypominamy, e w denicji klasy zaprzyjanionej nie podajemy nazwy


klasy bazowej (bo funkcja zaprzyjaniona nie jest funkcj skadow klasy)
cznie z operatorem zakresu. Funkcja operatorowa pobiera dwa argumenty
typu ulamek i zwraca obiekt typu ulamek. W ciele funkcji tworzymy tymczasowy obiekt ulamek u. W funkcji testujcej tworzymy trzy obiekty typu
ulamek:
ulamek u1 ( 3 , 4 ) , u2 ( 5 , 6 ) , u3 ;

Przy pomocy prostej instrukcji:


u3 = u1 u2 ;

mnoymy dwa uamki, wykorzystujc przeciony operator mnoenia (*).


T prost instrukcj naley porwna z pokazana poprzednio metoda mnoenia uamkw:
l i c z n i k = i l o c z y n _ L i ( u1 , u2 ) ;
mianownik = iloczy n_Mi ( u1 , u2 ) ;
ulamek u3 ( l i c z n i k , mianownik ) ;

aby doceni zalety uywania przecionych operatorw. Bardziej wydajna


wersja programu ilustrujcego wykorzystanie przecionego operator mnoenia pokazana jest na kolejnym wydruku. Argumenty zaprzyjanionej funkcji operatorowej s przekazywane przez referencj. Deklaracja funkcji operatorowej ma posta:
friend ulamek operator ( ulamek &x , ulamek &y ) ;

159

160

7. Przecianie operatorw
Implementacja moe mie posta:
ulamek operator ( ulamek &x , ulamek &y )
{
return ulamek ( x . l i y . l i , x . mia y . mia ) ;
}

Jeeli t implementacj porwnamy z implementacj, gdzie argumenty s


przekazywane przez warto:
ulamek operator ( ulamek x , ulamek y )
{ ulamek u ;
u. li = x. li y. li ;
u . mia = x . mia y . mia ;
return u ;
}

to widzimy widoczne zalety gdy argumenty przekazywane s przez referencj. Przede wszystkim nie tworzymy kopii argumentw, nie tworzymy take
tymczasowego obiektu, ktry musi by nastpnie zniszczony. Zagadnienie
wydajnoci w naszym przykadzie nie odgrywa wikszej roli, gdy mnoymy
dwa uamki, staj si jednak istotne, gdy zechcemy dokona mnoe tysicy
uamkw.
Listing 7.5. mnoenie uamkw funkcja operatorowa zaprzyjaniona
2

// f u n k c j a operatorowa , z a p r z y j a z n i o n a , r e f e r e n c j e
#include <i o s t r e a m >
#include <c o n i o >

using namespace s t d ;
6

10

12

14

c l a s s ulamek
{ i n t l i , mia ;
public :
ulamek ( ) ;
// k o n s t r u k t o r
ulamek ( int , i n t ) ;
// k o n s t r u k t o r
friend ulamek operator ( ulamek &x , ulamek &y ) ;
void pokaz ( ) ;
};

22

ulamek : : ulamek ( i n t a , i n t b )
{ i f ( b == 0 )
{ c e r r << " mianownik = 0 " << e n d l ;
e x i t (EXIT_FAILURE) ;
}
li = a;
mia = b ;
}

24

ulamek : : ulamek ( ) : l i ( 0 ) , mia ( 1 )

16

18

20

7.6. Przecianie operatorw rwnoci i nierwnoci


{ }
26

28

30

32

34

36

38

40

42

44

void ulamek : : pokaz ( )


{ c o u t << l i << " / " << mia << e n d l ;
}
ulamek operator ( ulamek &x , ulamek &y )
{ return ulamek ( x . l i y . l i , x . mia y . mia ) ;
}
i n t main ( )
{ ulamek u1 ( 3 , 4 ) , u2 ( 5 , 6 ) , u3 ;
c o u t << " ulamek 1 = " ;
u1 . pokaz ( ) ;
c o u t << " ulamek 2 = " ;
u2 . pokaz ( ) ;
u3 = u1 u2 ;
c o u t << " i l o c z y n ulamkow= " ;
u3 . pokaz ( ) ;
getche () ;
return 0 ;
}

Mimo zalet zwizanych ze stosowaniem operatorowych funkcji zaprzyjanionych naley pamita o szeregu ogranicze. Podczas przeciania operatorw inkrementacji i dekrementacji konieczne jest stosowanie parametrw
referencyjnych. Kolejnym ograniczenie jest fakt, e za pomoc funkcji zaprzyjanionych nie mona przecia nastpujcych operatorw:
operator przypisani ( = )
operator odwoania do elementu tablicy ( [ ] )
operator odniesienia do skadowej klasy ( > )
operator ()

7.6. Przecianie operatorw rwnoci i nierwnoci


Zagadnienie przeciania operatorw rwnoci i nierwnoci zilustrujemy
przykadami. Zamy, e chcemy przeadowa operatory rwnoci (==) i
nierwnoci (!=). Dla prostoty wemy klas reprezentujc trjwymiarowy
wektor. Naley przeciy operator == oraz != w ten sposb, aby mona byo ustali rwno i nierwno dwch wektorw. Przecianie naley
zrealizowa:
jako funkcje skadowe
jako funkcje zaprzyjanion
Denicja klasy moe mie posta:
c l a s s wek tor3d
{
double x , y , z ;

161

162

7. Przecianie operatorw
public :
wek tor3d ( double w1 =0.0 , double w2 =0.0 ,
double w3 = 0 . 0 )
{
x = w1 ;
y = w2 ;
z = w3 ;
}
i n t operator == ( wek tor3d ) ;
i n t operator != ( wek tor3d ) ;
};

W deklaracji klasy wektor3d mamy trzy dane prywatne (skadowe wektora),


konstruktor oraz dwie operatorowe funkcje skadowe.
Listing 7.6. porwnanie wektorw przecianie operatorw
2

// d e f i n i c j a z f u n k c j a m i skladowymi
#include <i o s t r e a m . h>
#include <c o n i o . h>

10

12

14

16

18

20

22

24

26

28

30

32

34

36

c l a s s wek tor3d
double x , y , z ;
{
public :
wek tor3d ( double w1 =0.0 , double w2 =0.0 ,
double w3 = 0 . 0 )
{
x = w1 ;
y = w2 ;
z = w3 ;
}
i n t operator == ( wek tor3d ) ;
i n t operator != ( wek tor3d ) ;
};
i n t wek tor3d : : operator == ( wek tor3d v )
{
i f ( ( v . x == x ) && ( v . y == y ) && ( v . z == z ) )
return 1 ;
e l s e return 0 ;
}
i n t wek tor3d : : operator != ( wek tor3d v )
{
return ! ( ( t h i s ) == v ) ;
}
i n t main ( )
{
wek tor3d v1 ( 1 0 , 1 0 , 2 0 ) , v2 ( 2 0 , 3 0 , 4 0 ) , v3 ( 1 0 , 1 0 , 2 0 ) ;
// 3 o b i e k t y
i f ( v1 == v2 )
c o u t << " \n wek tory v1 i v2 s a i d e n t y c z n e " << e n d l ;
else
c o u t << " \n wek tory v1 i v2 n i e s a rowne " << e n d l ;
i f ( v1 == v3 )
c o u t << " \n wek tory v1 i v3 s a i d e n t y c z n e " << e n d l ;
else
c o u t << " \n wek tory v1 i v3 n i e s a rowne " << e n d l ;

7.6. Przecianie operatorw rwnoci i nierwnoci


38

40

42

i f ( v1 != v2 )
c o u t << " \n wek tory v1 i v2 n i e s a rowne " << e n d l ;
getch () ;
return 0 ;
}

Po uruchomieniu mamy nastpujcy komunikat:


wek tory v1 i v2 n i e s a rowne
wek tory v1 i v3 s a i d e n t y c z n e
wek tory v1 i v2 n i e s a rowne

Jak wida w klasie wektor3d zadeklarowane s dwie funkcje skadowe:


i n t operator == ( wek tor3d ) ;
i n t operator != ( wek tor3d ) ;

Kada z nich ma jeden argument typu wektor3d i jeden argument domniemany this. Implementacji funkcji operator== ma posta:
i n t wek tor3d : : operator == ( wek tor3d v )
{
i f ( ( v . x == x ) && ( v . y == y ) && ( v . z == z ) )
return 1 ;
e l s e return 0 ;
}

Skadowe dwch wektorw s kolejno porwnywane, gdy wszystkie s rwne


zwracana jest warto 1, w przeciwnym przypadku zwracana jest warto 0.
Naley zwrci uwag, e w funkcji implementujcej przeciony operator
nierwnoci wykorzystano przeciony operator rwnoci! Ta funkcja ma
posta:
i n t wek tor3d : : operator != ( wek tor3d v )
{
return ! ( ( t h i s ) == v ) ;
}

Kolejny wydruk pokazuje przeciania operatorw rwnoci i nierwnoci


wykorzystujc funkcje zaprzyjanione.
Listing 7.7. porwnanie wektorw przecianie operatorw
1

// d e f i n i c j a z f u n k c j a m i z a p r z y j a z n i o n y m i
#include <i o s t r e a m . h>
#include <c o n i o . h>
c l a s s wek tor3d
{ double x , y , z ;

163

164

7. Przecianie operatorw
public :
wek tor3d ( double w1 =0.0 , double w2 =0.0 ,
double w3 = 0 . 0 )
{
x = w1 ; y = w2 ; z = w3 ;
}
friend i n t operator == ( wektor3d , wek tor3d ) ;
friend i n t operator != ( wektor3d , wek tor3d ) ;

11

13

};

15

operator == ( wek tor3d v , wek tor3d w)


i f ( ( v . x == w . x ) && ( v . y == w . y ) && ( v . z == w . z ) )
{
return 1 ;
e l s e return 0 ;
}

17

19

operator != ( wek tor3d v , wek tor3d w)


{
return ! ( v == w) ;
}

21

23

25

i n t main ( )
{
wek tor3d v1 ( 1 0 , 1 0 , 2 0 ) , v2 ( 1 0 , 1 0 , 2 0 ) , v3 ( 1 0 , 2 0 , 2 0 ) ;

27

29

31

33

35

cout
<<
cout
<<

<< " v1 == v2 : " << ( v1 == v2 ? " prawda " : " f a l s z " )


endl ;
<< " v1 == v3 : " << ( v1 == v3 ? " prawda " : " f a l s z " )
endl ;

cout
<<
cout
<<

<< " v1 != v2 : " << ( v1 != v2 ? " prawda " : " f a l s z " )


endl ;
<< " v1 != v3 : " << ( v1 != v3 ? " prawda " : " f a l s z " )
endl ;
getch () ;
return 0 ;

37

39

Po uruchomieniu programu otrzymujemy nastpujcy wynik:


v1
v1
v1
v1

==
==
!=
!=

v2
v3
v2
v3

:
:
:
:

prawda
falsz
falsz
prawda

W klasie wektor3d zadeklarowano dwie operatorowe funkcje zaprzyjanione


o nazwach operator == i operator !=:
friend i n t operator == ( wektor3d , wek tor3d ) ;
friend i n t operator != ( wektor3d , wek tor3d ) ;

Bd one otrzymywa po dwa argumenty typu wektor3d. Implementacja


tych funkcji ma posta:

7.7. Przecianie operatora przypisania ( = )


operator == ( wek tor3d v , wek tor3d w)
{
i f ( ( v . x == w . x ) && ( v . y == w . y ) && ( v . z == w . z ) )
return 1 ;
e l s e return 0 ;
}
operator != ( wek tor3d v , wek tor3d w)
{
return ! ( v == w) ;
}

Widzimy, e operator przeciony moe by zdeniowany jako funkcja skadowa i jako funkcja globalna (lub zaprzyjaniona). Wobec tego musimy odpowiedzie na pytanie, jakie s kryteria wyboru implementacji przeciania
operatora. Prawd mwic nie ma generalnej zasady, wszystko zaley od
zastosowania przecionego operatora.
Jeeli operator modykuje operandy to powinien by zdeniowany jako funkcja skadowa klasy. Przykadem s tu takie operatory jak: =, +=, -=, *=, ++, itp. Jeeli operator nie modykuje
swoich operandw to naley go deniowa jako funkcj globaln
lub zaprzyjanion. Przykadem s tu takie operatory jak: +, -,
==, &, itp.

7.7. Przecianie operatora przypisania ( = )


Operator przypisania jest szczeglnym operatorem, poniewa w przypadku, gdy nie zostanie przeciony, jest on deniowany przez kompilator.
Tak wic, operacja przypisani jednego obiektu drugiemu jest zawsze wykonalna. Ilustruje ten fakt kolejny program. W naszym przykadzie zostaa
zaimplementowana klasa data:
c l a s s data
{
private :
int dzien ;
int miesiac ;
i n t rok ;
public :
data ( i n t = 1 , i n t = 1 , i n t = 2004 ) ; // k o n s t r u k t o r
void pokaz ( void ) ; // f u n k c j a skladowa , p o k a z u j e d a t e
};

Ta klasa nie zawiera funkcji operatorowej przypisania. W funkcji gwnej


main() mamy instrukcj:
d1 = d2 ;

165

166

7. Przecianie operatorw
co oznacz, e odpowiednie pola obiektu d2 s przypisane polom obiektu d1.
Ten typ przypisania nosi nazw przypisania danych skadowych (memberwise assignment).
Listing 7.8. przypisanie bez przeciania operatorw
1

11

#include <i o s t r e a m . h>


#include <iomanip . h>
#include <c o n i o . h>
c l a s s data
{ private :
int dzien ;
int miesiac ;
i n t rok ;
public :
data ( i n t = 1 , i n t = 1 , i n t = 2 0 0 4 ) ;
// k o n s t r u k t o r
void pokaz ( void ) ;
// f u n k c j a skladowa , p o k a z u j e d a t e
};

13

15

17

data : : data ( i n t dd , i n t mm, i n t r r )


{
d z i e n = dd ;
m i e s i a c = mm;
rok = r r ;
}

19

21

23

25

void data : : pokaz ( void )


{
c o u t << s e t f i l l ( 0 )
<< setw ( 2 ) << d z i e n << /
<< setw ( 2 ) << m i e s i a c << /
<< setw ( 4 ) << rok ;
return ;
}

27

29

31

33

35

37

39

i n t main ( )
{ data d1 ( 1 3 , 3 , 2 0 0 3 ) , d2 ( 1 5 , 5 , 2 0 0 4 ) ;
c o u t << " \n p i e r w s z a data , d1 : " ;
d1 . pokaz ( ) ;
c o u t << " \n druga data , d2 : " ;
d2 . pokaz ( ) ;
d1 = d2 ;
c o u t << " \ npo p o d s t a w i e n i u d1 : " ;
d1 . pokaz ( ) ;
c o u t << e n d l ;
getche () ;
return 0 ;
}

Po uruchomieniu programu otrzymujemy komunikat:


p i e r w s z a data , d1 : 13/03/2003
druga data d2 : 15/05/2004

7.7. Przecianie operatora przypisania ( = )


po p o d s t a w i e n i u d1 : 15/05/2005

Jeeli mamy proste przypisanie, tak jak to pokazano powyej, wygenerowane


przez kompilator przecienie jest wystarczajce, jednak w bardziej skomplikowanych przypadkach (np. gdy chcemy mie wielokrotne przypisanie
typu: d1 = d2 = d3) musimy zaprojektowa odpowiedni funkcj operatorow. W pokazanym programie wykorzystano przecienie operatora przypisania wygenerowanego przez kompilator. Plik nagwkowy <iomanip.h>
jest potrzebny, poniewa w programie wykonywane s formatowane operacje
wyjcia z tak zwanymi parametryzowanymi manipulatorami strumienia. Do
ustawienia szerokoci pola wydruku zastosowano manipulator strumienia
setw(), manipulator setll() okrela znak wypenienia pola. Jak mwilimy,
operator przypisania ( = ) jest szczeglnym operatorem, podobnie jak operator pobrania adresu ( & ) i przecinkowy ( , ) maj predeniowane znaczenie w odniesieniu do obiektw klas. Oczywicie moemy, przestrzegajc
odpowiednich regu przeciy operator przypisania. Deklaracja prostego
operatora przypisania moe mie posta:
void operator = ( nazwa_klasy & )

Sowo kluczowe void wskazuje, e przypisanie nie zwrci adnej wartoci,


napis operator = wskazuje, e przeciamy operator przypisania, a nazwa
klasy i znak & wewntrz nawiasw okrgych wskazuj, e argumentem
operatora jest referencja do klasy. Na przykad, aby zadeklarowa operator
przypisania dla naszej klasy data, moemy uy deklaracji:
void operator = ( data &) ;

Implementacja funkcji operatorowej moe mie posta:


void Data : : operator= ( Data & d )
{
dzien = d . dzien ;
miesiac = d . miesiac ;
rok = d . rok ;
}

W denicji operatora zastosowano referencj. W tej denicji d jest zdeniowane jako referencja do klasy Data. W ciele funkcji operatorowej skadowa
dzien obiektu d jest przypisana skadowej dzien aktualnego obiektu:
dzien = d . dzien ;

Ta sama operacja powtrzona jest dla skadowych miesiac i rok. Przypisanie


typu:

167

168

7. Przecianie operatorw
a . operator =(b ) ;

moe by zastosowane do wywoania przecionego operatora przypisania


i przypisania wartoci skadowych obiektu b do obiektu a. W tej sytuacji
zapis a.operator=(b) moe by zastpiony wygodnym zapisem a = b;.
Listing 7.9. przypisanie; przeciania operatorw
1

11

13

15

17

19

21

23

25

27

29

31

33

35

37

39

41

#include <i o s t r e a m >


#include <iomanip>
#include <c o n i o >
using namespace s t d ;
c l a s s Data
{ i n t d z i e n , m i e s i a c , rok ;
public :
Data ( i n t =1 , i n t = 1 , i n t = 2 0 0 0 ) ; // k o n s t r u k t o r
void pokaz ( ) ;
void operator= ( Data &) ;
};
Data : : Data ( i n t dd , i n t mm, i n t r r )
{
d z i e n = dd ;
m i e s i a c = mm;
rok = r r ;
}
void Data : : pokaz ( )
{ c o u t << s e t f i l l ( 0 )
<< setw ( 2 ) << d z i e n << /
<< setw ( 2 ) << m i e s i a c << /
<< setw ( 2 ) << rok % 100 << e n d l ;
}
void Data : : operator= ( Data & d )
{ dzien = d . dzien ;
miesiac = d . miesiac ;
rok = d . rok ;
}
i n t main ( )
{ Data d1 ( 3 1 , 1 2 , 2 0 0 4 ) , d2 ( 1 , 1 , 2 0 0 5 ) ;
c o u t << " data nr 1 : " ;
d1 . pokaz ( ) ;
c o u t << " data nr 2 : " ;
d2 . pokaz ( ) ;
d1 = d2 ;
c o u t << " P r z y p i s a n i e dat : " << e n d l ;
c o u t << " data nr 1 : " ;
d1 . pokaz ( ) ;
getche () ;
return 0 ;
}

7.7. Przecianie operatora przypisania ( = )


Po uruchomieniu tego programu mamy wydruk:
data nr 1 : 31/12/04
data nr 2 : 01/01/05
P r z y p i s a n i e dat :
data nr 1 : 01/01/05

Funkcja operatora przypisania zaprezentowana w tym przykadzie, aczkolwiek poprawna, nie obsuy poprawnie prby przypisania wielokrotnego postaci:
a = b = c;

Dzieje si tak, poniewa powyszy zapis interpretowany jest jako:


a = ( b = c );

Zgodnie z denicj, nasza funkcja operatorowa nie zwraca adnej wartoci,


wobec tego po wykonaniu przypisania b = c, adna warto nie bdzie zwrcona i nic nie moemy przypisa do a. W celu wykonywania wielokrotnego
przypisania, potrzebna jest funkcja operatorowa zwracajca referencj do
klasy swojego typu. Zanim pokaemy implementacj przecionego operatora przypisania wielokrotnego, przypomnimy znaczenie sowa kluczowego
this. Funkcje skadowe s zwizane z denicja klasy, a nie z deklaracjami
obiektw tej klasy. Za kadym razem, gdy obiekt jest kreowany, odrbne
obszary pamici s przydzielane do przechowywania danych skadowych. W
naszym przykadzie kreowane s dwa obiekty klasy Data d1 i d2. Organizacja przechowywanych w pamici danych pokazana jest na rysunku 7.1.
Jak wida na rysunku, kady zbir danych ma inny pocztkowy adres w
pamici, ktry jest adresem pierwszej danej skadowej obiektu. Tego typu
kopiowanie danych skadowych nie odnosi si do funkcji skadowych. Istnieje
tylko jeden egzemplarz kodu denicji danej funkcji skadowej. Kady obiekt
uywa tych samych funkcji. Poniewa jedna funkcja skadowa musi obsuy wiele obiektw, musi istnie sposb identykacji danych poszczeglnych
obiektw. Rozwizane jest to w ten sposb, e do funkcji przekazywany jest
adres wskazujcy gdzie w pamici znajduj si dane skadowe konkretnego
obiektu. Taki adres jest przekazywany jest poprzez nazw obiektu. Na przykad, jeeli uywamy naszej klasy Data i zaoymy, e a jest obiektem tej
klasy, to instrukcja a.pokaz() przesya adres obiektu a do funkcji skadowej
pokaz(). Moemy zapyta jak taki adres jest przesyany i gdzie jest przechowywany. Adres jest przechowywany w specjalnej zmiennej wskanikowej
o nazwie this, ktra jest automatycznie dostarczana jako ukryty parametr
do kadej niestatycznej funkcji skadowej, gdy funkcja jest wywoywana. W
naszym przykadzie, gdzie klasa Data ma dwie funkcje skadowe:

169

170

7. Przecianie operatorw

Rysunek 7.1. Przechowywanie dwch obiektw typu Data w pamici

Data ( i n t =1 , i n t = 1 , i n t = 2 0 0 0 ) ;
void pokaz ( ) ;

// k o n s t r u k t o r

Lista parametrw przekazywanych ma rwnowan posta:


Data ( Date this , i n t =1 , i n t = 1 , i n t = 2 0 0 0 ) ; // k o n s t r u k t o r
void pokaz ( Date t h i s ) ;

W ten sposb, kada funkcja skadowa otrzymuje aktualnie dodatkowy argument, ktry jest adresem struktury danych. Aby si o tym przekona
moemy uywa tych wskanikw w sposb jawny. Zademonstrujemy uycie wskanika this w krtkim programie (oczywicie w praktyce nie korzysta
si z tej techniki).
Listing 7.10. jawne uycie wskanika this
2

10

12

#include <i o s t r e a m >


#include <c o n i o >
using namespace s t d ;
c l a s s punkt
{ int x , y ;
public :
i n t ustaw_1 ( int , i n t ) ;
i n t ustaw_2 ( int , i n t ) ;
};
i n t punkt : : ustaw_1 ( i n t a , i n t b )
{ this>x = a ;
this>y = b ;
return x ;

7.7. Przecianie operatora przypisania ( = )


14

16

18

}
i n t punkt : : ustaw_2 ( i n t a , i n t b )
{ this>x = this>x + a ;
this>y = this>y + b ;
return x ;
}

20

22

24

26

28

i n t main ( )
{ punkt p1 ;
c o u t << " ustaw_1 , x= " ;
c o u t << p1 . ustaw_1 ( 1 0 , 10 ) << e n d l ;
c o u t << " ustaw_2 , x= " ;
c o u t << p1 . ustaw_2 ( 2 0 , 20 ) << e n d l ;
getche () ;
return 0 ;
}

Po uruchomieniu programu mamy nastpujcy wydruk:


Ustaw_1 , x = 10
Ustaw_2 , x = 30

Jak pamitamy funkcja operatorowa postaci:


void Data : : operator= ( Data & d )
{ dzien = d . dzien ;
miesiac = d . miesiac ;
rok = d . rok ;
}

nie umoliwia uycia wielokrotnego przypisania postaci a = b = c. Przy


pomocy wskanika this zmienimy implementacj operatorowej funkcji przypisania tak, aby moliwe byo wielokrotne przypisanie. Zasadnicz spraw
jest zaprojektowanie funkcji operator= tak, aby moga zwrci warto typu
Data. Prototyp takiej funkcji moe mie posta:
Data operator= ( const Data & ) ;

Zastosowalimy specykator const do parametru funkcji, aby mie pewno,


e ten operand nie bdzie zmieniony przez funkcj. Implementacja nowej
funkcji operatorowej moe mie posta:
Data Data : : operator =(const Data &d )
{ dzien = d . dzien ;
miesiac = d . miesiac ;
rok = d . rok ;
return t h i s ;
}

171

172

7. Przecianie operatorw
Naley pamita, e funkcja operatorowa przypisania musi by funkcj skadow klasy, nie moe by funkcja zaprzyjanion. W przypadku przypisania
takiego jak b = c , (rwnowana forma b.operator=(c) ), wywoana funkcja
zmienia dane skadowe obiektu b na dane obiektu c i zwraca now warto
obiektu b. Taka operacja umoliwia wielokrotne przypisanie typu a = b =
c. Implementacja przecionego operatora przypisania i jego zastosowanie
pokazano na kolejnym wydruku.
Listing 7.11. rozszerzona wersja operatora przypisania
2

10

#i n c l u d e <i o s t r e a m >
#include <iomanip>
#include <c o n i o >
using namespace s t d ;
c l a s s Data
{ i n t d z i e n , m i e s i a c , rok ;
public :
Data ( int , int , i n t ) ;
// k o n s t r u k t o r
void pokaz ( ) ;
Data operator =(const Data &) ;
};

12

14

16

Data : : Data ( i n t dd = 1 , i n t mm = 1 , i n t r r = 2 0 0 0 )
{ d z i e n = dd ;
m i e s i a c = mm;
rok = r r ;
}

18

20

22

24

26

28

30

32

34

36

38

40

void Data : : pokaz ( )


{ c o u t << s e t f i l l ( 0 )
<< setw ( 2 ) << d z i e n << /
<< setw ( 2 ) << m i e s i a c << /
<< setw ( 4 ) << rok << e n d l ;
}
Data Data : : operator =(const Data &d )
{
dzien = d . dzien ;
miesiac = d . miesiac ;
rok = d . rok ;
return t h i s ;
}
i n t main ( )
{ Data d1 ( 2 , 2 , 2 0 0 2 ) , d2 ( 3 , 3 , 2 0 0 3 ) , d3 ( 4 , 4 , 2 0 0 4 ) ;
c o u t << " p i e r w s z a data : " ;
d1 . pokaz ( ) ;
c o u t << " druga data : " ;
d2 . pokaz ( ) ;
c o u t << " t r z e c i a data : " ;
d3 . pokaz ( ) ;
d1 = d2 = d3 ;

7.8. Przecianie operatora wstawiania do strumienia ( )


c o u t << " p r z y p i s a n i e w i e l o k r o t n e , t e r a z " ;
c o u t << " p i e r w s z a data : " ;
d1 . pokaz ( ) ;
getche () ;
return 0 ;

42

44

46

Po uruchomieniu programu mamy wydruk;


p i e r w s z a data : 02/02/2002
druga data : 0 3 . 0 3 . 2 0 0 3
t r z e c i a data : 04/04/2004
p r z y p i s a n i e w i e l o k r o t n e , t e r a z p i e r w s z a data : 04/04/2004

7.8. Przecianie operatora wstawiania do strumienia ( )


Wiele zastosowa praktycznych maj funkcje realizujce przecianie
operatorw wstawiania danych ( ) do strumienia i operatorw pobierania
danych ( ) ze strumienia. Jak wiemy, w jzyku C++ operator jest
operatorem powodujcym przesuwanie bitw o dan liczb pozycji.
Fakt, e moemy uy tego operatora na przykad przy wywietlaniu
wartoci:
int x = 13;
c o u t << x ;

zawdziczamy technice przeciania operatorw. Powyszy zapis ma nastpujc interpretacj:


c o u t . operator <<(x ) ;

cout jest egzemplarzem obiektu klasy ostream, klasa ta jest zawarta w bibliotece standardowej. Operator jest zdeniowany w klasie ostream, a co
wicej jest on przeciony w ten sposb, e moemy go wykorzystywa dla
wszystkich wbudowanych typw danych. Moliwe jest rwnie takie przedeniowanie tego operatora, aby mona byo wywietla dane typw zdeniowanych przez uytkownika. Ma to due znaczenie praktyczne. Rozwamy
przykadow klas punkt:
c l a s s punkt
{
private :
int x ;
int y ;
public :

173

174

7. Przecianie operatorw
punkt ( i n t a , i n t b ) { x = a ; y = b ; }
i n t pokazX ( ) { return x ; }
i n t pokazY ( ) { return y ; }

// k o n s t r u k t o r

};

W klasie mamy dwie dane prywatne x i y oraz dwie funkcje skadowe pokazX() i pokazY() dla uzyskania dostpu do zmiennych prywatnych. Jeeli
chcemy wyswietli wartoci danych x i y do musimy napisa dwie instrukcje
strumienia cout z odpowiednimi argumentami dla operatora wstawiania .
To zagadnienie ilustruje pokazany poniej przykad. Aby wywietli wartoci
danych x i y musimy napisa dwie instrukcje:
c o u t << " x = " << p1 . pokazX ( ) << e n d l ;
c o u t << " y = " << p1 . pokazY ( ) << e n d l ;

Listing 7.12. operator wstawiania


2

#include <i o s t r e a m >


#include <c o n i o >
using namespace s t d ;

10

c l a s s punkt
{ int x , y ;
public :
punkt ( i n t a , i n t b ) { x = a ; y = b ; }
i n t pokazX ( ) { return x ; }
i n t pokazY ( ) { return y ; }
};

// k o n s t r u k t o r

12

i n t main ( )
14

{
punkt p1 ( 5 , 1 5 ) ;
c o u t << " x= " << p1 . pokazX ( ) << e n d l ;
c o u t << " y= " << p1 . pokazY ( ) << e n d l ;
getche () ;
return 0 ;

16

18

20

W kolejnym przykadzie pokaemy jak mona przeciy operator wstawiania, aby mona byo wywietli dane obiektu przy pomocy jednej instrukcji. Klasyczna posta denicji operatora wstawiania ( ) jest nastpujca:
ostream & operator<< ( ostream & os , nazwa_klasy & ob )
{
// i n s t r u k c j e
return o s ;
}

7.8. Przecianie operatora wstawiania do strumienia ( )


Zdeniowana funkcja jest klasy ostream &, czyli musi podawa referencj do
obiektu klasy ostream &. Pierwszym argumentem funkcji operator() jest
referencja do obiektu typu ostream. Oznacza to, e os musi by strumieniem wyjciowym. Drugim argumentem take jest referencja, do argumentu
ob przesya si obiekt typu nazwa klasy. Funkcja operator zawsze zwraca
referencj do swojego pierwszego argumentu, to znaczy do strumienia wyjciowego os. Zaprojektowana funkcja operatorowa musi by zaprzyjaniona
z klas nazwa klasy, jeeli chce mie dostp do skadowych chronionych
klasy. Jeeli mamy nastpujc klas:
c l a s s punkt
{
int x , y ;
public :
punkt ( i n t a , i n t b ) { x = a ; y = b ; }
// k o n s t r u k t o r
friend ostream& operator<< ( ostream &, punkt & ) ;
};

to funkcja operatorowa moe mie posta:


ostream & operator<< ( ostream & os , punkt & ob )
{ o s << "x = " << ob . x << e n d l ;
o s << "y = " << ob . y << e n d l ;
return o s ;
}

Krtki program ilustrujcy omawiane zagadnienie pokazany jest na wydruku 7.13.


Listing 7.13. przeciony operator wstawiania
1

#include <i o s t r e a m >


#include <c o n i o >
using namespace s t d ;
c l a s s punkt
{ int x , y ;
public :
punkt ( i n t a , i n t b ) { x = a ; y = b ; }
// k o n s t r u k t o r
friend ostream& operator<< ( ostream &, punkt & ) ;
};

11

13

15

ostream & operator<< ( ostream & os , punkt & ob )


{ o s << "x = " << ob . x << e n d l ;
o s << "y = " << ob . y << e n d l ;
return o s ;
}

17

i n t main ( )

175

176

7. Przecianie operatorw
19

21

23

25

punkt p1 ( 5 , 1 5 ) ;
c o u t << p1 ;
c o u t << " wy wolanie jawne : \n" ;
operator <<(cout , p1 ) ;
getche () ;
return 0 ;

Po wykonaniu tego programu mamy nastpujcy wynik:


x = 5
y = 15
wy wolanie jawne :
x = 5
y = 15

Jak pokazano w programie, wywoanie funkcji operatorowej moe mie dwie


formy:
c o u t << p1 ;

albo
operator<<(cout , p1 ) ;

Rozdzia 8
Funkcje statyczne i wirtualne

8.1.
8.2.
8.3.
8.4.
8.5.

Wstp . . . . . . . . . . .
Metody i dane statyczne
Polimorzm . . . . . . .
Funkcje wirtualne . . . .
Funkcje abstrakcyjne . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

178
178
183
187
194

178

8. Funkcje statyczne i wirtualne

8.1. Wstp
Jedn z fundamentalnych cech programowania obiektowego jest polimorzm (innymi wanymi cechami, jak pamitamy s abstrakcja danych,
ukrywanie danych i dziedziczenie). Polimorzm zapewnia tworzenie bardziej
zrozumiaego kodu a w powizaniu z wykorzystaniem funkcji wirtualnych
umoliwia zaprojektowanie i wdroenie programw, ktre s stosunkowo
bardziej rozszerzalne. Na etapie kompilacji polimorzm jest realizowany
przy pomocy przecianych funkcji i operatorw, na etapie wykonania jest
realizowany przy pomocy dziedziczenia i funkcji wirtualnych. Dla wielu pocztkujcych programistw pojcie funkcji wirtualnych moe by pocztkowo niejasne, poniewa nie posiadaj one swoich odpowiednikw w jzykach
proceduralnych. Twierdzi si, e funkcje wirtualne s fundamentem programowania obiektowego.

8.2. Metody i dane statyczne


W jzyku C++ kady obiekt klasy posiada swoj wasn kopi danych
skadowych. Jeeli tworzymy w programie wiele obiektw danej klasy, czsto
damy istnienia tylko jednej kopii zmiennej. Taki przypadek wystpuje czsto w przypadku dania informacji o liczbie obiektw. Oczywicie mona
zastosowa zmienne globalne, ale nie jest to rozwizanie elegancki i bezpieczne. W jzyku C++ dane skadowe klasy mog by zadeklarowane jako
statyczne skadowe klasy. Aby zadeklarowa statyczne dane, naley umieci
przed deklaracj skadowej sowo kluczowe static. W ten sposb powstaj
dane ktre nale do klasy jako cao a nie do poszczeglnych obiektw
pojedynczo. Skadowe statyczne tworzone s tylko raz, kady obiekt danej
klasy posiada dostp do skadowych statycznych klasy. Dostp do statycznych skadowych klasy kontrolowany jest przy pomocy specykatorw public, protected i private. Zmienn statyczn naley deniowa poza klas,
nie mona uywa (powtrnie) sowa kluczowego static. Skadowe statyczne
musz by inicjalizowane tylko raz w zasigu pliku. Co ciekawsze, statyczne
skadowe istniej nawet wtedy, gdy nie ma adnego obiektu. Dana skadowa
static inicjalizowana jest najczciej wartoci zero, gdy nie wykonamy jawnej inicjalizacji, dana skadowa static przez domniemanie otrzyma warto
0 (zero). Przykadowa klasa z danymi typu static moe mie posta:
c l a s s ST
{
private :
int x ;
static int y ;
static int z ;
};

179

8.2. Metody i dane statyczne

i n t ST : : y ;
i n t ST : : z = 1 ;

W pokazanej klasie mamy dwie skadowe y i z zadeklarowane jako statyczne.


Te zmienne mamy zdeniowane poza klas (co oznacza, e przydzielona
zostaa im pami). Do tak zdeniowanych zmiennych statycznych moemy
odwoywa si przez pen kwalikowan nazw:
ST : : y ;

lub przy pomocy operatora wyboru ( kropki lub strzaki). W pokazanym


przykadzie skadowa statyczna y zostanie zainicjalizowana wartoci zero.
Poniszy przykad ilustruje zastosowanie danych statycznych do zliczania
tworzonych obiektw danej klasy.
Listing 8.1. Dane skadowe statyczne
1

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
class l i c z n i k
{ public :
static int n ;
l i c z n i k ( ) { n++ ; }
};

int l i c z n i k

:: n;

// d e f i n i c j a d a n e j s t a t y c z n e j

11

13

15

17

19

i n t main ( )
{
l i c z n i k obj1 ;
c o u t << " i l o s c objek tow : " << l i c z n i k
l i c z n i k obj2 ;
c o u t << " i l o s c objek tow : " << l i c z n i k
getche () ;
return 0 ;
}

: : n << e n d l ;
: : n << e n d l ;

Po uruchomieniu programu mamy nastpujcy wydruk:


i l o s c objek tow : 1
i l o s c obiektow : 2

Moemy take deklarowa metody klasy jako statyczne, pamitajc jednak o kilku ograniczeniach. Statyczne funkcje skadowe mog odwoywa
si bezporednio jedynie do danych statycznych klasy (i oczywicie do danych globalnych). Skadowe funkcje statyczne nie posiadaj wskanika this,

180

8. Funkcje statyczne i wirtualne


nie mona take tworzy statycznych funkcji wirtualnych ani deklarowa
je jako const. W kolejnym przykadzie ilustrujemy wykorzystanie funkcji
statycznych. W programie obsugujemy list pacjentw. Dana skadowa n
jest inicjalizowana pocztkowo wartoci 0.
int p acjen t : : n = 0 ;

Listing 8.2. Statyczne dane skadowe i metody


1

11

13

15

#include <i o s t r e a m >


#include <s t r i n g . h>
#include <c o n i o . h>
using namespace s t d ;
class pacjent
{ public :
p a c j e n t ( char , char ) ;
~pacjent () ;
char get_imie ( ) ;
char get_nazwisk o ( ) ;
static int l i c z b a ( ) ;
private :
char i m i e ;
char nazwisk o ;
static int n ;
};

/ / statyczna funkcja

// l i c z b a p a c j e n t o w

17

int p acjen t : : n = 0 ;

// i n i c j a c j a d a n e j s t a t y c z n e j

19

i n t p a c j e n t : : l i c z b a ( ) { return n ; }
21

23

25

27

29

p a c j e n t : : p a c j e n t ( char x imie , char x nazwisk o )


{ i m i e = new char [ s t r l e n ( x i m i e ) + 1 ] ;
s t r c p y ( imie , x i m i e ) ;
nazwisk o = new char [ s t r l e n ( x nazwisk o ) + 1 ] ;
s t r c p y ( nazwisk o , x nazwisk o ) ;
++n ;
c o u t << " p a c j e n t : " <<i m i e << " "
<< nazwisk o<< e n d l ;
}

31

33

35

37

pacjent : : ~pacjent ()
{ c o u t <<" zbadany p a c j e n t "<< i m i e << " "
<< nazwisk o << e n d l ;
delete i m i e ;
delete nazwisk o ;
n ;
}

39

41

char p a c j e n t : : get_imie ( ) { return i m i e ; }


char p a c j e n t : : get_nazwisk o ( ) { return nazwisk o ; }

8.2. Metody i dane statyczne

43

45

47

49

51

53

55

57

59

i n t main ( )
{
c o u t << " l i c z b a p a c j e n t o w : "
<< p a c j e n t : : l i c z b a ( ) << e n d l ;
p a c j e n t p1 = new p a c j e n t ( " Jan " , " F a s o l a " ) ;
p a c j e n t p2 = new p a c j e n t ( " Emil " , " Burak " ) ;
p a c j e n t p3 = new p a c j e n t ( " Borys " , " D e l f i n " ) ;
c o u t << " l i c z b a p a c j e n t o w : " << p1>l i c z b a ( )
<< e n d l ;
delete p1 ;
delete p2 ;
delete p3 ;
c o u t << " p o z o s t a l a l i c z b a p a c j e n t o w : "
<< p a c j e n t : : l i c z b a ( ) << e n d l ;
getche () ;
return 0 ;
}

Dana skadowa n przechowuje liczb obiektw klasy pacjent. Mamy take statyczn funkcje skadow liczba(), ktra dostarcza informacji o liczbie
utworzonych obiektw klasy pacjent:
i n t p a c j e n t : : l i c z b a ( ) { return n ; }

Konstruktor dynamicznie przydziela pami dla danych pacjenta oraz inkrementuje statyczn dan n :
p a c j e n t : : p a c j e n t ( char x imie , char x nazwisk o )
{ i m i e = new char [ s t r l e n ( x i m i e ) + 1 ] ;
s t r c p y ( imie , x i m i e ) ;
nazwisk o = new char [ s t r l e n ( x nazwisk o ) + 1 ] ;
s t r c p y ( nazwisk o , x nazwisk o ) ;
++n ;
// z w i e k s z a l i c z n i k p a c j e n t o w
c o u t << " p a c j e n t : " <<i m i e << " " << nazwisk o<< e n d l ;
}

Do zwalniania pamici i dekrementacji statycznej danej n wykorzystujemy


destruktor:
pacjent : : ~pacjent ()
{ c o u t <<" zbadany p a c j e n t "<< i m i e << " "
<< nazwisk o << e n d l ;
delete i m i e ;
delete nazwisk o ;
n ;
// z m n i e j s z a l i c z n i k p a c j e n t o w
}

181

182

8. Funkcje statyczne i wirtualne


W funkcji main() testujemy klas pacjent. Najpierw tworzymy trzy obiekty
klasy pacjent i przy pomocy instrukcji:
c o u t << " l i c z b a p a c j e n t o w : " << p1>l i c z b a ( ) << e n d l ;

wywietlamy liczb utworzonych obiektw. Nastpnie przy pomocy operatora delete zwalniamy pami. W naszym przypadku w tym momencie nie
istnieje ju aden utworzony obiekt klasy pacjent. W takim przypadku informacja o iloci obiektw dostpna jest jedynie za pomoc statycznej metody
liczba() :
c o u t << " p o z o s t a l a l i c z b a p a c j e n t o w : "
<< p a c j e n t : : l i c z b a ( ) << e n d l ;

Metoda liczba() suy do informowania o liczbie aktualnie istniejcych obiektw klasy pacjent. Naley zwrci uwag, e podczas zwalniania pamici
(operator delete ) wywoywany jest destruktor obiektu i wtedy nastpuje
dekrementacja statycznej danej skadowej n.
Po uruchomieniu programu mamy nastpujcy wynik:
l i c z b a pacjentow : 0
p a c j e n t : Jan F a s o l a
p a c j e n t : Emil Burak
pacjent
: Borys D e l f i n
l i c z b a pacjentow : 3
zbadany p a c j e n t Jan F a s o l a
zbadany p a c j e n t Emil Burak
zbadany p a c j e n t Borys D e l f i n
p oz ost ala l i c z b a pacjentow : 0

Kolejny przykad ilustruje zastosowanie statycznej metody do obliczania


iloczynu skalarnego dla dwch wektorw 3D.
Listing 8.3. Statyczne metody iloczyn skalarny
1

11

13

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
c l a s s wek tor
int x , y , z ;
{
public :
void s e t ( int , int , i n t ) ;
s t a t i c i n t i l o c z y n ( wek tor &, wek tor &) ;
};
void wek tor : : s e t ( i n t xx , i n t yy , i n t z z )
{ x=xx ; y=yy ; z=z z ;
}

8.3. Polimorzm

15

i n t wek tor : : i l o c z y n ( wek tor &w1 , wek tor &w2 )


{ return w1 . x w2 . x + w1 . yw2 . y + w1 . z w2 . z ;
}

17

19

21

23

25

27

i n t main ( )
{ wek tor w1 , w2 ;
w1 . s e t ( 1 , 1 , 1 ) ;
w2 . s e t ( 2 , 2 , 2 ) ;
c o u t <<" i l o c z y n = " << wek tor : : i l o c z y n (w1 , w2 ) << e n d l ;
w1 . s e t ( 5 , 5 , 5 ) ;
c o u t <<" i l o c z y n = " << w1 . i l o c z y n ( w1 , w2 ) << e n d l ;
getche () ;
return 0 ;
}

Wynik wykonania programy ma posta:


iloczyn = 6
i l o c z y n = 30

Klasa wektor ma dwie metody set() do ustawiania wartoci skadowych


wektora oraz statyczn metod iloczyn() do obliczania iloczynu skalarnego
dwch wektorw:
i n t wek tor : : i l o c z y n ( wek tor &w1 , wek tor &w2 )
{ return w1 . x w2 . x + w1 . yw2 . y + w1 . z w2 . z ;
}

W funkcji testujcej main() demonstrujemy dwa sposoby wywoania metody


statycznej:
c o u t <<" i l o c z y n = " << wek tor : : i l o c z y n (w1 , w2 ) << e n d l ;
c o u t <<" i l o c z y n = " << w1 . i l o c z y n ( w1 , w2 ) << e n d l ;

W pierwszym wywoaniu funkcja iloczyn() moe by wywoana samodzielnie, niezalenie od obiektu. Musimy w wyraeniu uy nazwy klasy i operatora zakresu. W drugim przypadku funkcja iloczyn() jest wywoana na
rzecz obiektu w1, co jest standardowym wywoaniem metody.
Naley zauway, e dostp do zmiennych i wywoania funkcji statycznych danej klasy nie wymagaj istnienia obiektw tej klasy.

8.3. Polimorzm
Wana cech programowania obiektowego w C++ jest polimorzm. Dziki polimorzmowi istnieje moliwo otrzymania rnego dziaania metod
w odpowiedzi na ten sam komunikat . Taka technika jest wykorzystywana w

183

184

8. Funkcje statyczne i wirtualne


przypadku gdy metody s wywoywane przez obiekty rnych powizanych
klas, a chcemy eby dziaanie ich zaleao od konkretnej sytuacji. Ten sam
komunikat przesany do rnych typw obiektw przybiera rne formy
std nazwa polimorzm. Idea polimorzmu realizowana jest w C++ przy
pomocy funkcji wirtualnych.
Najprostsz technik polimorzmu jest tzw. nadpisanie funkcji (ang.
overriding of a function). Taki przypadek zachodzi, gdy funkcja zdeniowana
w klasie bazowej otrzymuje now posta w klasie dziedziczcej. Polimorzm
pozwala funkcji o tej samej nazwie wywoanej na rzecz obiektu klasy bazowej
da inny wynik ni wywoanie tej funkcji na rzecz obiektu klasy pochodnej.
W wielu przypadkach metoda nadpisywania nie dziaa tak jak oczekujemy.
Poprawne dziaanie nadpisywania ilustruje kolejny przykad.
Listing 8.4. nadpisanie metody klasy bazowej
2

#include <i o s t r e a m >


#include <c o n i o . h>
#include <math>
using namespace s t d ;

const double PI = 2 . 0 a s i n ( 1 . 0 ) ;

class kolo
{ protected :
double r ;
public :
k o l o ( double r r = 1 ) ;
double o b l i c z ( ) ;
};

10

12

14

16

k o l o : : k o l o ( double r r ) { r = r r ; }
double k o l o : : o b l i c z ( ) { return PI r r ; }

18

20

22

24

26

28

30

32

34

c l a s s w a l e c : public k o l o
{ protected :
double h ;
public :
w a l e c ( double r r = 1 . 0 , double hh = 1 . 0 ) : k o l o ( r r ) ,
h ( hh ) { }
double o b l i c z ( ) ;
};
double w a l e c : : o b l i c z ( )
{ return ( h k o l o : : o b l i c z ( ) ) ; }
i n t main ( )
{
k o l o k1 , k2 ( 2 ) ;
walec walec1 ( 3 ,4) ;
c o u t << " p o l e k o l a k1 = " << k1 . o b l i c z ( ) << e n d l ;

8.3. Polimorzm
c o u t << " p o l e k o l a k2 = " << k2 . o b l i c z ( ) << e n d l ;
c o u t << " o b j e t o s c walca = " << w a l e c 1 . o b l i c z ( ) << e n d l ;
getche () ;
return 0 ;

36

38

Wynikiem uruchomienia programu jest komunikat:


p o l e k o l a k1 = 3 . 1 4 1 5 9
p o l e k o l a k2 = 1 2 . 5 6 6 4
o b j e t o s c walca = 1 1 3 . 0 9 7

W programie naley zwrci uwag na denicj:


const double PI = 2 . 0 a s i n ( 1 . 0 ) ;

Jest to sprytny sposb wymuszenia na kompilatorze zwrcenia wartoci liczby Pi z maksymaln precyzj jak oferuje nasz komputer.
W pokazanych klasach programu mamy funkcje o takiej samej nazwie
oblicz(). Metoda w klasie bazowej oblicza pole powierzchni koa, w klasie pochodne oblicza objto walca. Nadpisanie bazowej funkcji skadowej
przez przecion pochodn funkcj skadow, tak jak to pokazano w programie jest przykadem polimorzmu. Polimorzm pozwala na rne dziaania funkcji skadowej o takiej samej nazwie w zalenoci na rzecz jakiego
obiektu zostaa wywoana. Te rne wywoania wida w instrukcjach:
c o u t << " p o l e k o l a k2 = " << k2 . o b l i c z ( ) << e n d l ;
c o u t << " o b j e t o s c walca = " << w a l e c 1 . o b l i c z ( ) << e n d l ;

Mona uy wskanika do obiektu klasy bazowej. Jeeli uyjemy wskanika


typu klasy bazowej do wywoania metody w hierarchii klas, zawsze wywoywana jest metoda z egzemplarza klasy bazowej. Zagadnienie to ilustrujemy
przez zmodykowanie poprzedniego programu.
Listing 8.5. nadpisanie metody klasy bazowej; bdne uycie wskanika
2

#include <i o s t r e a m >


#include <c o n i o . h>
#include <math>
using namespace s t d ;

const double PI = 2 . 0 a s i n ( 1 . 0 ) ;

class kolo
{ protected :
double r ;
public :

10

185

186

8. Funkcje statyczne i wirtualne


12

14

16

18

20

22

24

26

k o l o ( double r r = 1 ) ;
double o b l i c z ( ) ;
};
k o l o : : k o l o ( double r r ) { r = r r ; }
double k o l o : : o b l i c z ( ) { return PI r r ; }
c l a s s w a l e c : public k o l o
{ protected :
double h ;
public :
w a l e c ( double r r = 1 . 0 , double hh = 1 . 0 ) : k o l o ( r r ) ,
h ( hh ) { }
double o b l i c z ( ) ;
};
double w a l e c : : o b l i c z ( )
{ return ( h k o l o : : o b l i c z ( ) ) ; }

28

30

32

34

36

38

40

i n t main ( )
{
k o l o k1 ;
walec walec1 ( 3 ,4) ;
k o l o wsk ;
wsk = & k1 ;
c o u t << "wsk p o l e k o l a k1 = " << wsk>o b l i c z ( ) << e n d l ;
wsk = & w a l e c 1 ;
c o u t << "wsk o b j e t o s c walca = " << wsk>o b l i c z ( )
<< e n d l ;
getche () ;
return 0 ;
}

Wynikiem dziaania programu jest komunikat:


wsk p o l e k o l a k1 = 3 . 1 4 1 5 9
wsk o b j e t o s c walca = 2 8 . 2 7 4 3

We fragmencie kodu:
k o l o k1 ;
walec walec1 ( 3 ,4) ;
k o l o wsk ;
wsk = & k1 ;
c o u t << "wsk p o l e k o l a k1 = " << wsk>o b l i c z ( ) << e n d l ;
wsk = & w a l e c 1 ;
c o u t << "wsk o b j e t o s c walca = " << wsk>o b l i c z ( )
<< e n d l ;

zdeniowalimy egzemplarze obu klas oraz wskanik typu klasy bazowej wsk.
Wywoanie:

8.4. Funkcje wirtualne


wsk = & k1 ;
wsk>o b l i c z ( ) ;

zadziaa prawidowo, natomiast ustawienia wskanika na walec1:


wsk = & w a l e c 1 ;

spowoduje, e kolejne wywoanie


wsk>o b l i c z ( )

nie zadziaa tak jak si spodziewamy.


Otrzymany wynik nie jest prawidowy, uycie wskanika w pokazany
sposb jest kopotliwe. Chcemy mie moliwo uywania wskanika klasy
bazowej w celu dostpu do egzemplarzy dowolnej z klas pochodnych w tej
samej hierarchii. Do osignicia polimorzmu musimy zastosowa specjalny
mechanizm uy funkcji wirtualnych.

8.4. Funkcje wirtualne


Funkcja wirtualne jest to funkcja skadowa zadeklarowana w klasie bazowej przy pomocy sowa kluczowego virtual i zdeniowana w klasie pochodnej:
v i r t u a l typ_zwracany nazwa_funk cji ( ) ;

W klasie pochodnej, ktra dziedziczy metody klasy bazowej deniujemy


na nowo funkcj wirtualn, tak aby uwzgldni specyk klasy pochodnej.
Realizujemy wic zasad polimorzmu : jeden interfejs, wiele metod. Kada denicja funkcji wirtualnej w klasie pochodnej tworzy now metod.
Wirtualno metody jest cech dziedziczon. Funkcja zdeniowana w klasie
bazowej jako wirtualna pozostaje tak do koca dziaania programu nie
ma moliwoci zdjcia wirtualnoci z funkcji skadowej. W klasie pochodnej
podczas deniowania funkcji wirtualnej nie ma potrzeby uywania sowa
kluczowego virtual, ale jest to zalecane ze wzgldu na czytelno kodu. Pamita naley, e funkcje wirtualne spowalniaj wykonanie kodu (nieznaczne). W zasadzie funkcja wirtualna w typowym dziaaniu zachowuje si jak
zwyka funkcja skadowa klasy. Caa rnica dziaania funkcji wirtualnych
uwidacznia si gdy jest wywoywana przez wskanik.
Dokadnie omwimy prosty przykad (skorzystamy z koncepcji H.Schildta,
Programowanie C++), aby zapozna si z dziaaniem metod wirtualnych.

187

188

8. Funkcje statyczne i wirtualne


Listing 8.6. funkcje wirtualne; poprawne uycie wskanika
1

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
c l a s s bazowa
{ public :
v i r t u a l void w i r f u n ( ) {
c o u t << " w i r t u a l n a k l a s a bazowa "<< e n d l ;
}
};

11

13

15

17

19

c l a s s pochodna : public bazowa


{ public :
v i r t u a l void w i r f u n ( ) {
c o u t << " w i r t u a l n a k l a s a pochodna " << e n d l ;
}
};
// r e f e r e n c j a f u n k c j i b a z o w e j j a k o parametr :
void r e f f u n ( bazowa &z ) { z . w i r f u n ( ) ; }

21

23

25

27

29

31

33

35

37

39

41

i n t main ( )
{ bazowa wsk ;
// w s k a z n i k k l a s y b a z o w e j
bazowa x ;
// o b i e k t t y p u bazowa
pochodna y ;
// o b i e k t t y p u pochodna
c o u t << " wy wolanie f u n k c j i w i r t u a l n e j
przy pomocy wsk aznik a : "<< e n d l ;
wsk = &x ;
// p r z y p i s a n i e a d r e s u o b i e k t u
// bazowego x
wsk > w i r f u n ( ) ;
// metod k l a s y bazowa
wsk = &y ;
// p r z y p i s a n i e a d r e s u o b i e k t u
// pochodnego y
wsk > w i r f u n ( ) ;
// metod k l a s y pochodna
c o u t << " wy wolanie f u n k c j i w i r t u a l n e j
przy pomocy r e f e r e n c j i : "<< e n d l ;
reffun (x) ;
// p r z e k a z a n i e o b i e k t u bazowego
// do r e f f u n ( )
reffun (y) ;
// p r z e k a z a n i e o b i e k t u pochodnego
// do r e f f u n ( )
getche () ;
return 0 ;
}

Po uruchomieniu programu uzyskujemy nastpujcy komunikat:


wy wolanie
wirtualna
wirtualna
wy wolanie

funkcji
klasa
klasa
funkcji

w i r t u a l n e j przy pomocy wsk aznik a


bazowa
pochodna
w i r t u a l n e j przy pomocy r e f e r e n c j i

8.4. Funkcje wirtualne


w i r t u a l n a k l a s a bazowa
w i r t u a l n a k l a s a pochodna

W naszym programie mamy klas bazow i pochodn, w kadej z nich zdeniowana jest metoda wirfun(), zgodnie z oczekiwaniami klasy. Funkcja main() testuje nasze klasy. W programie mamy zadeklarowane cztery zmienne:
wsk (wskanik klasy bazowej), x (obiekt klasy bazowej), y (obiekt klasy
pochodnej) i z ( referencja klasy bazowej).
W instrukcjach
wsk = &x ;
// p r z y p i s a n i e a d r e s u o b i e k t u bazowego x
wsk > w i r f u n ( ) ; // metod k l a s y bazowa

zmiennej wsk jest przypisany adres x i wywoywana jest funkcja wirfun().


Dziki temu, e wsk jest wskanikiem do obiektu klasy bazowej, wykonana zostanie wersja tej funkcji zdeniowana w klasie bazowej. W kolejnych
instrukcjach:
wsk = &y ;
// p r z y p i s a n i e a d r e s u o b i e k t u pochodnego y
wsk > w i r f u n ( ) ; // metod k l a s y pochodna

zmiennej wsk przypisywany jest adres y ( obiekt klasy pochodnej) a metoda wirfun() jest ponownie wywoywana. W takim przypadku wywoywana
jest metoda wirfun() zdeniowana w klasie pochodnej. Najwaniejsze jest
to, e wybr wersji funkcji wirfun() dokonywany jest na podstawie typu
obiektu wskazywanego przez wskanik wsk. Mwimy, e realizacja wersji
metody wykonywana jest w trakcie dziaania programu, co oznacza, e realizowany jest polimorzm na etapie wykonania. Naley zwrci uwag, e
klasyczne przecianie funkcji a nadpisywanie funkcji przy pomocy metod
wirtualnych jest cakiem innym mechanizmem. Konieczne jest, aby prototyp
funkcji nadpisywany w klasie pochodnej by identyczny jak w klasie bazowej.
Pamitamy, e podczas klasycznego nadpisywania, sygnatury funkcji musz
si rni. Niesie to pewne niebezpieczestwo. Jeeli w trakcie stosowania
metod wirtualnych zmienimy przypadkowo jaki element prototypu, funkcji
nadany zostanie status funkcji przecionej i moe to spowodowa bdne
wykonanie programu. W instrukcji:
// r e f e r e n c j a f u n k c j i b a z o w e j j a k o parametr :
void r e f f u n ( bazowa &z ) { z . w i r f u n ( ) ; }

mamy prototyp funkcji, ktrej argumentem jest referencja klasy bazowej.


Jeeli zgodzimy si, e referencja jest niejawnym wskanikiem, to jasne staje
si, e z polimorcznych wasnoci funkcji wirtualnych moemy skorzysta,
wywoujc je za pomoc referencji. W takim przypadku, podobnie jak przy

189

190

8. Funkcje statyczne i wirtualne


wykorzystaniu wskanika, o wyborze wersji funkcji decyduje rodzaj obiektu wskazywanego przez referencj w momencie wywoywania. W kolejnych
instrukcjach mamy pokazane wywoanie odpowiednich metod przy pomocy
referencji:
c o u t << " wy wolanie f u n k c j i w i r t u a l n e j
przy pomocy r e f e r e n c j i : "<< e n d l ;
r e f f u n ( x ) ; // p r z e k a z a n i e o b i e k t u bazowego do r e f f u n ( )
r e f f u n ( y ) ; // p r z e k a z a n i e o b i e k t u pochodnego do r e f f u n ( )

Majc moliwo uycia metod wirtualnych moemy poprawi le dziaajcy program w ktrym ilustrowalimy poprzednio nadpisywanie funkcji.
Poprawny kod pokazany jest na kolejnym listingu.
Listing 8.7. funkcje wirtualne; poprawne uycie wskanika
2

#include <i o s t r e a m >


#include <c o n i o . h>
#include <math>
using namespace s t d ;

const double PI = 2 . 0 a s i n ( 1 . 0 ) ;

class kolo
{ protected :
double r ;
public :
k o l o ( double r r = 1 ) ;
v i r t u a l double o b l i c z ( ) ; // w i r t u a l n a f u n k c j a s k l a d o w a
};

10

12

14

16

k o l o : : k o l o ( double r r ) { r = r r ; }
double k o l o : : o b l i c z ( ) { return PI r r ; }

18

20

22

24

26

28

c l a s s w a l e c : public k o l o
{ protected :
double h ;
public :
w a l e c ( double r r = 1 . 0 , double hh = 1 . 0 ) : k o l o ( r r ) ,
h ( hh ) { }
double o b l i c z ( ) ;
};
double w a l e c : : o b l i c z ( )
{ return ( h k o l o : : o b l i c z ( ) ) ; }

30

32

34

i n t main ( )
{
k o l o k1 ;
walec walec1 ( 3 ,4) ;

8.4. Funkcje wirtualne

36

38

40

42

44

k o l o wsk ;
wsk = & k1 ;
c o u t << " wsk aznik , p o l e k o l a k1 = " << wsk>o b l i c z ( )
<< e n d l ;
wsk = & w a l e c 1 ;
c o u t << " wsk aznik , o b j e t o s c walca = " << wsk>o b l i c z ( )
<< e n d l ;
getche () ;
return 0 ;
}

Wynikiem dziaania tego programu s poprawne obliczenia:


wsk aznik , p o l e k o l a k1 = 3 . 1 4 1 5 9
wsk aznik , o b j e t o s c walca = 1 1 3 . 0 9 7

Do osignicia polimorzmu musimy zastosowa funkcj wirtualn. W


naszym przypadku funkcja oblicz() deklarowana w klasie bazowej kolo musi
by wyszczeglniona jako virtual:
v i r t u a l double o b l i c z ( ) ; // w i r t u a l n a f u n k c j a s k l a d o w a

Teraz uycie wskanika do obsugi wywoania metody oblicz() dziaa prawidowo! Jeeli w klasie pochodnej nadpisywana jest funkcja standardowa,
zdeniowana w klasie bazowej (nie wirtualna) mamy do czynienia z procesem nazywanym wizaniem funkcji (ang. function binding). W typowym
wywoaniu funkcji jest wykonane tzw. statyczne wizanie (ang. static binding). W statycznym wizaniu decyzja, jak wersj funkcji naley zrealizowa jest wykonana na etapie czasu kompilacji (ang. compile time). W wielu
przypadkach, chcemy aby zamiast decyzji o wizaniu statycznym, decyzja
o wersji metody bya podejmowana pniej, w czasie wykonania (ang. run
time), na podstawie typu obiektu, ktry wykona wywoanie funkcji. Oczywicie taki mechanizm zapewnia polimorzm, konkretnie funkcje wirtualne.
Ten typ wizania funkcji nosi nazw wizania dynamicznego (ang. dynamic
binding). Specykacja funkcji wirtualnej informuje kompilator jzyka C++
aby utworzy wskanik do funkcji lecz nie nadawa wartoci wskanikowi
dopki funkcja nie bdzie wywoana. W tej sytuacji w czasie wykonywania programu, na podstawie typu obiektu wykonujcego wywoanie metody, odpowiedni adres jest wykorzystany i odpowiednia wersja funkcji jest
zastosowana.
Jak ju pisalimy, wykorzystanie funkcji wirtualnych nieznacznie spowalnia wykonanie programu w porwnaniu z wykorzystaniem funkcji klasycznych. Jak ju opisalimy, dla klas niepolimorcznych metoda jest wybierana
w czasie kompilacji, mechanizm nosi nazw wczesnego wizania (ang. ear-

191

192

8. Funkcje statyczne i wirtualne


ly binding). Typ dynamiczny obiektu (zastosowanie wskanika) moe by
okrelony w czasie wykonania programu. Oznacza to, e w fazie kompilacji
po stwierdzeniu wywoania metody z klasy polimorcznej, kompilator nie
moe umieci kodu wykonywalnego odpowiedniej funkcji. Zamiast niego
jest umieszczany kod sprawdzajcy i podejmujcy decyzj pniej o wersji funkcji. Tego typu mechanizm nosi te nazw pnego wizania (ang.
late binding). Wszystko to powoduje nieznaczne spowolnienie wykonania
programu a take powikszenie niezbdnej pamici.
Gdy mamy wybiera pomidzy zwyk funkcj skadow a wirtualn
zaleca si wybr metody wirtualnej. Zaleca si take stosowanie wirtualnych
destruktorw zapobiega to wyciekowi pamici, poniewa gdy destruktor
nie jest wirtualny to bdzie wywoany destruktor waciwy dla typu obiektu
w czasie kompilacji (wczesne wizanie). Tworzc implementacj hierarchii
klas zaleca si aby wszystkie klasy w korzeniu drzewa dziedziczenia miay
metody wirtualne. Zwracamy uwag na fakt, e funkcje statyczne nie mog
by wirtualnie i tak samo metody wirtualne nie mog by statyczne. Jeeli
w klasie pochodnej funkcja nie jest deklarowana jako wirtualna, klasa ta
dziedziczy bezporednio denicj funkcji wirtualnej z klasy bazowej.
Projektowanie polimorcznych klas wymaga duej uwagi. Jednym z problemw ktry moe si pojawi jest dziaanie destruktora. W przetwarzaniu
dynamicznym, w trakcie niszczenia obiektu przy zastosowaniu operatora delete do wskanika klasy bazowej, wywoywana jest funkcja destruktora tej
klasy. Jest to niezalene od typu obiektu wskazywanego przez wskanik klasy
bazowej. Poniszy program ilustruje fakt wywoania destruktora bazowego,
mimo ustawienia wskanika na obiekt klasy pochodnej ( spodziewamy si
wywoania destruktora klasy pochodnej).
Listing 8.8. Desktruktor w hierarchii klas bdne uycie
1

11

13

15

17

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
c l a s s bazowa
{ public :
bazowa ( ) { c o u t << " k o n s t r u k t o r k l a s y bazowej " << e n d l ; }
~bazowa ( ) { c o u t << " d e s t r u k t o r k l a s y bazowej " << e n d l ; }
};
c l a s s pochodna : public bazowa
{ public :
pochodna ( ) { c o u t << " k o n s t r u k t o r k l a s y p o c h o d n e j"
<< e n d l ; }
~pochodna ( ) { c o u t << " d e s t r u k t o r k l a s y p o c h o d n e j"
<< e n d l ; }
};

193

8.4. Funkcje wirtualne

19

21

i n t main ( )
{ bazowa wsk = new pochodna ;
c o u t << " p r z y d z i e l o n y a d r e s = " << wsk << e n d l ;
delete wsk ;

23

getche () ;
return 0 ;

25

W wyniku uruchomienia tego programu mamy komunikat:


k o n s t r u k t o r k l a s y bazowej
konstruktor k lasy pochodnej
p r z y d z i e l o n y a d r e s 10050348
d e s t r u k t o r k l a s y bazowej

W trakcie wykonywania programu, wywoania konstruktorw wykonay si


prawidowo, pami dynamiczna zostaa przydzielona. Natomiast po wykonaniu instrukcji delete wsk otrzymalimy komunikat, e wywoany zosta
destruktor klasy bazowej (chocia mona by byo si spodziewa wywoania
konstruktora klasy pochodnej). Dzieje si tak, poniewa nie ma adnej wskazwki, ktry destruktor ma by wywoany. Taka sytuacja jest niepodana
poniewa prowadzi do tzw. wycieku pamici. W kolejnym programie pokazujemy metod, dziki ktrej operacja zwolnienia zasobw przydzielonych
obiektowi klasy pochodnej przebiegnie prawidowo. Osigniemy ten efekt
dziki uyciu destruktora wirtualnego.
Listing 8.9. wirtualny desktruktor w hierarchii klas poprawne uycie
2

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;

10

12

14

16

18

c l a s s bazowa
{ public :
bazowa ( ) { c o u t << " k o n s t r u k t o r k l a s y bazowej " << e n d l ; }
v i r t u a l ~bazowa ( ) { c o u t << " d e s t r u k t o r k l a s y bazowej "
<< e n d l ; }
};
c l a s s pochodna : public
{ public :
pochodna ( ) { c o u t <<
<<
~pochodna ( ) { c o u t <<
<<
};

bazowa
" k o n s t r u k t o r k l a s y p o c h o d n e j"
endl ; }
" d e s t r u k t o r k l a s y p o c h o d n e j"
endl ; }

194

8. Funkcje statyczne i wirtualne

20

22

24

26

i n t main ( )
{ bazowa wsk = new pochodna ;
c o u t << " p r z y d z i e l o n y a d r e s = " << wsk << e n d l ;
delete wsk ;
getche () ;
return 0 ;
}

W wyniku uruchomienia tego programu mamy komunikat:


k o n s t r u k t o r k l a s y bazowej
konstruktor k lasy pochodnej
p r z y d z i e l o n y a d r e s 10050348
d est r u k t or k lasy pochodnej
d e s t r u k t o r k l a s y bazowej

Analizujc program widzimy, e dziaanie destruktora jest zgodne z naszym


oczekiwaniem. Ten wynik otrzymalimy przy pomocy wirtualnego destruktora zdeniowanego w klasie bazowej:
v i r t u a l ~bazowa ( ) { c o u t << " d e s t r u k t o r k l a s y bazowej "
<< e n d l ; }

W klasie bazowej przy pomocy sowa kluczowego virtual zdeniowany zosta


destruktor. W klasie bazowej nastpia redenicja destruktora. Wirtualny
destruktor jest wywoany dla wskanika obiektu, mamy do czynienia z wizaniem dynamicznym. Wskanik wsk wskazuje na obiekt klasy pochodna,
dlatego zlecenie delete wsk wywoa destruktor tej klasy. W dalszej kolejnoci
zgodnie z oglnymi zasadami zostanie wywoany destruktor klasy bazowa.
Cay program dziaa poprawnie.

8.5. Funkcje abstrakcyjne


W praktycznych zastosowaniach dziedziczenia, wystpuj czsto przypadki, gdy w klasie bazowej nie wiemy jak uytecznie zdeniowa funkcj
wirtualn, natomiast doskonale wiemy j zrealizowa j w klasie pochodnej
lub te zachodzi przypadek, e pragmatycznie jest nie podawa denicji
funkcji wirtualnej w klasie bazowej. W jzyku C++ moemy w takich przypadkach wykorzysta koncepcj funkcji abstrakcyjnych.
Funkcja abstrakcyjna to taka, ktrej nie zdeniowano w klasie bazowej
oraz zostaa zainicjowana zerem. Formalnie deklaracja funkcji abstrakcyjnej
ma posta:
v i r t u a l typ_zwracany nazwa_funk cji ( ) = 0 ;

8.5. Funkcje abstrakcyjne


Czasami taka funkcja jest nazywana funkcj czysto wirtualn. Klasa zawierajca funkcj abstrakcyjn nosi nazw klasy abstrakcyjnej. Nie mona
utworzy obiektu na podstawie klasy abstrakcyjnej, mona tworzy obiekty
przy pomocy klas pochodnych. Prba realizacji obiektu klasy abstrakcyjnej
prowadzi do bdu skadni. Gdy funkcja wirtualna zostanie utworzona jako
funkcja abstrakcyjna, w klasach pochodnych koniecznie naley utworzy jej
nowe denicje. Jeeli pominiemy denicj funkcji abstrakcyjnej w klasach
pochodnych kompilator zgosi bd.
Zastosowanie funkcji abstrakcyjnych zilustrujemy prostym przykadem.
Listing 8.10. abstrakcyjna klasa bazowa z wirtualn funkcj skadow
1

11

13

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
c l a s s baza
{ public :
v i r t u a l i n t wynik ( ) = 0 ; // c z y s t a f u n k c j a w i r t u a l n a
};
c l a s s dana : public baza
{ int x ;
public :
dana ( i n t xx ) { x = xx ; }
i n t wynik ( ) { return x ; }
};

15

17

19

21

23

25

27

29

c l a s s suma : public baza


{ baza a , b ;
public :
suma ( baza aa , baza bb ) { a = aa ; b = bb ; }
i n t wynik ( ) { return a>wynik ( ) + b>wynik ( ) ; }
};
i n t main ( )
{ dana d1 ( 5 ) ;
dana d2 ( 1 0 ) ;
suma s (&d1 , &d2 ) ;
c o u t << d1 . wynik ( ) << " + " << d2 . wynik ( ) << " = "
<< s . wynik ( ) << e n d l ;
c o u t << "suma= " << s . wynik ( ) << e n d l ;
getche () ;
return 0 ;

31

33

195

196

8. Funkcje statyczne i wirtualne


Po uruchomieniu programu otrzymujemy nastpujcy komunikat:
5 + 10 = 15
suma = 15

W programie wykorzystano abstrakcyjn klas bazow o nazwie baza. W


klasie bazowej baza mamy abstrakcyjn funkcj wirtualn o nazwie wynik(). W klasach pochodnych dana i suma, abstrakcyjna funkcja wynik()
jest redeniowana. Funkcja wynik() wywoana na rzecz dowolnego obiektu
klasy pochodnej zwraca warto wyraenia zgodnie z denicj w danej klasie potomnej. Bardziej rozbudowany przykad uycia klas abstrakcyjnych
pokaemy na kolejnym przykadzie.
Listing 8.11. abstrakcyjna klasa bazowa; wirtualne funkcje skadowe
2

#include <i o s t r e a m >


#include <s t r i n g >
using namespace s t d ;

10

12

class zwierz
// a b s t r a k c y j n a k l a s a bazowa
{ public :
zwierz () { };
zwierz ( s t r i n g iimie ) { imie = iimie ; }
v i r t u a l void typ ( ) = 0 ;
v i r t u a l void g l o s ( ) = 0 ;
v i r t u a l void o p i s ( ) = 0 ;
s t r i n g imie ;
};

14

16

18

20

22

24

26

28

30

32

34

c l a s s p i e s : public z w i e r z
{ public :
pies ( string iimie ) : zwierz ( iimie ) { };
private :
void typ ( ) { c o u t << " p i e s " ; }
void g l o s ( ) { c o u t << " s z c z e k a " ; }
void o p i s ( ) { c o u t << " n i e l u b i k ota " ; }
};
c l a s s k ot : public z w i e r z
{ public :
k ot ( s t r i n g i i m i e ) : z w i e r z ( i i m i e ) { } ;
private :
void typ ( ) { c o u t << " k ot " ; }
void g l o s ( ) { c o u t << " miauczy " ; }
void o p i s ( ) { c o u t << " n i e l u b i myszy " ; }
};

197

8.5. Funkcje abstrakcyjne


36

38

40

42

44

46

void i n f o ( z w i e r z tab [ ] , i n t i l e )
{ f o r ( i n t i = 0 ; i < i l e ; i ++)
{ tab [ i ]>typ ( ) ;
c o u t << tab [ i ]> i m i e ;
tab [ i ]> g l o s ( ) ;
tab [ i ]> o p i s ( ) ;
c o u t << e n d l ;
}
};
i n t main ( )
{ z w i e r z tab [ ] =

48

50

i n f o ( tab , 3 ) ;
cin . get () ;
return 0 ;

52

54

{ new p i e s ( " Burek " ) ,


new p i e s ( " F a f i k " ) ,
new k ot ( " Pucek " )
};

Wynikiem uruchomienia programu jest komunikat:


p i e s Burek s z c z e k a n i e l u b i k ota
p ies Fafik
s z c z e k a n i e l u b i k ota
k ot Pucek miauczy n i e l u b i myszy

W naszym programie mamy abstrakcyjn klas bazow o nazwie zwierz.


Zawiera ona trzy czysto wirtualne funkcje:
v i r t u a l void typ ( ) = 0 ;
v i r t u a l void g l o s ( ) = 0 ;
v i r t u a l void o p i s ( ) = 0 ;

Taki projekt jest rozsdny, poniewa pozwala nam w klasach pochodnych


napisa dan wersj. W naszym przypadku metoda glos() ma inn implementacj w klasie pochodnej kot, a inn w klasie pochodnej pies. Podobnie
jest z innymi funkcjami wirtualnymi. Wszystkie informacje o naszych zwierztach otrzymujemy przy pomocy zwykej funkcji info(). Argumentem tej
funkcji jest tablica wskanikw do zwierz. Funkcja info() wywouje metody
typ(), glos() i opis(). Dziki polimorzmowi, wywoywane s waciwe metody. Naley zwrci uwag, e w tym programie moemy doczy now
klas pochodn, zbudowana tak samo jak klasy pies i kot, a adna inna
zmiana programu nie bdzie potrzebna funkcja info() bez kopotu obsuy now klas pochodn. Klasy abstrakcyjne i funkcje wirtualne maj
wiele zastosowa praktycznych wiele bibliotek jest implementowanych z
wykorzystaniem tych technik.

198

8. Funkcje statyczne i wirtualne


Moemy stworzy may program do obsugi funkcji matematycznych.
Listing 8.12. abstrakcyjna klasa bazowa; wirtualne funkcje skadowe
1

#include <i o s t r e a m >


#include <s t r i n g >
#include <math>
using namespace s t d ;

const double PI = 2 . 0 a s i n ( 1 . 0 ) ;
7

11

13

15

17

class o b l i c z
{ protected :
double x ;
// w a r t o s c argumentu
double y ;
// o b l i c z o n a w a r t o s c f u n k c j i
public :
o b l i c z ( double z ) { x = z ; }
double getwy nik ( ) { return y ; }
double g e t x ( ) { return x ; }
v i r t u a l void o b l i c z f u n ( ) = 0 ;
// c z y s t a f u n k c j a w i r t u a l n a
};

19

21

23

c l a s s f s i n : public o b l i c z
{ public :
f s i n ( double xx ) : o b l i c z ( xx ) { }
void o b l i c z f u n ( ) { y = s i n ( x ) ; }
};

25

27

29

c l a s s f c o s : public o b l i c z
{ public :
f c o s ( double xx ) : o b l i c z ( xx ) { }
void o b l i c z f u n ( ) { y = c o s ( x ) ; }
};

31

33

35

37

i n t main ( )
{ o b l i c z wsk ;
// w s k a z n i k k l a s y b a z o w e j
fsin
f s ( PI / 4 . 0 ) ;
// argument f u n k c j i s i n
wsk = &f s ;
c o u t << " d l a x= " << wsk>g e t x ( ) ;
wsk>o b l i c z f u n ( ) ;
c o u t << " s i n ( x )= " << wsk>getwy nik ( ) <<e n d l ;

39

fcos
f c ( PI / 4 . 0 ) ;
// argument f u n k c j i c o s
wsk = &f c ;
c o u t << " d l a x= " << wsk>g e t x ( ) ;
wsk>o b l i c z f u n ( ) ;
c o u t << " c o s ( x )= " << wsk>getwy nik ( ) <<e n d l ;
cin . get () ;
return 0 ;

41

43

45

47

199

8.5. Funkcje abstrakcyjne


Wynikiem tego programu jest komunikat:
d l a x= 0 . 7 8 5 3 9 8
d l a x= 0 . 7 8 5 3 9 8

s i n ( x )= 0 . 7 0 7 1 0 7
c o s ( x )= 0 . 7 0 7 1 0 7

W pokazanym przykadzie zrealizowana zostaa metoda jeden interfejs,


wiele metod. W programie mamy kolekcj funkcji trygonometrycznych, dla
danych ktw obliczamy wartoci funkcji sinus i cosinus. W klasie bazowej
znajduje si funkcja abstrakcyjna obliczfun(), ktra musi by zdeniowana
w kadej klasie pochodnej. Wynik oblicze bdzie zalea od wybranej wersji
klasy pochodnej. W programie mamy dwie klasy pochodne: fsin (do obliczania wartoci sinusa) oraz fcos (do obliczania wartoci cosinusa). W kadej
z tych klas mamy nadpisanie funkcji abstrakcyjnej obliczfun(). Widzimy
wyranie zalet stosowania klas abstrakcyjnych. W kadej chwili moemy
dopisa now klas (np. do obliczania funkcji tangensa), w programie nic
si nie zmieni.

Rysunek 8.1. Hierarchia klas z funkcjami wirtualnymi

W kolejnym programie zilustrujemy uycie funkcji wirtualnych w praktycznym przykadzie. Opracujemy program obliczajcy pola paskich gur:
koo, trjkt i prostokt. Zbudujemy odpowiedni hierarchi klas. W klasie bazowej umiecimy funkcj wirtualn realizujc obliczanie powierzchni
gur. Odpowiednie funkcje wirtualne, realizujce konkretne obliczenia zdeniowane bd w klasach pochodnych. Klasa bazowa ma posta:
class f i g u r a
protected :
{
double a , b ;
public :
void ustaw ( double aa = 0 . 0 , double bb = 0 . 0 )
{ a = aa ;
b = bb ;
}
v i r t u a l void p o l e ( ) { }
};

200

8. Funkcje statyczne i wirtualne


W klasie bazowej wystpuje funkcja pole(). Jest to oglna funkcja, metoda void pole() nie musi by tu deniowana, poniewa naleaoby poda
szczegowy wzr na obliczanie powierzchni konkretnej gury. W programie
tworzymy take trzy dodatkowe klasy pochodne: trojkat, prostokat i kolo.
W tych klasach umieszczamy konkretne denicje funkcji pole().
We wzorach na obliczanie pola powierzchni gury musimy poda odpowiednie dane. Dla trjkta musi poda warto podstawy i wysoko trjkta, dla prostokta musimy poda dugoci bokw a dla koa musimy poda
warto promienia. Naley zauway, e tworzc obiekt kolo, przekazujemy
jeden argument, w tej sytuacji funkcja ustaw() musi zawiera ustawienia
wartoci pocztkowych. Metoda ustaw() ma posta :
void ustaw ( double aa = 0 . 0 , double bb = 0 . 0 )
{ a = aa ;
b = bb ;
}

Gdyby metoda ta bya napisana w postaci (brak wartoci pocztkowych):


void ustaw ( double aa , double bb )
{ a = aa ;
b = bb ;
}

w momencie tworzenia obiektu:


f i g u r a wsk ;
kolo k ;
wsk = &k ;
wsk> ustaw ( 1 . 0 ) ;
wsk> p o l e ( ) ;

pojawi si komunikat:
[ C++ E r r o r ] Unit1 . cpp ( 5 0 ) : E2193 Too few p a r a m e t e r s
i n c a l l t o f i g u r a : : ustaw ( double , d o u b l e )

i program nie skompiluje si. Inna moliwa poprawna denicja dla metody
z drugim domylnym parametrem ma posta:
void ustaw ( double aa , double bb = 0 . 0 )
{ a = aa ;
b = bb ;
}

Poniej pokazujemy wydruk programu.


Listing 8.13. abstrakcyjna klasa bazowa; wirtualne funkcje skadowe
1

#i n c l u d e <i o s t r e a m >
using namespace s t d ;

8.5. Funkcje abstrakcyjne


3

11

13

15

17

class f i g u r a
{ protected :
double a , b ;
public :
void ustaw ( double aa = 0 . 0 , double bb = 0 . 0 )
{ a = aa ;
b = bb ;
}
v i r t u a l void p o l e ( ) { }
};
c l a s s t r o j k a t : public f i g u r a
{ public :
void p o l e ( )
{ c o u t << " p o l e t r o j k a t a = " << 0 . 5 a b << e n d l ;
}
};

19

21

23

25

27

29

31

c l a s s p r o s t a k a t : public f i g u r a
{ public :
void p o l e ( )
{ c o u t << " p o l e p r o s t o k a t a = " << a b << e n d l ;
}
};
c l a s s k o l o : public f i g u r a
{ public :
void p o l e ( )
{ c o u t << " p o l e k o l a = " << 3 . 1 4 1 5 9 2 6 a a << e n d l ;
}
};

33

35

37

i n t main ( )
{
f i g u r a wsk ;
trojkat t ;
prostakat p ;
kolo k ;

39

41

wsk = &t ;
wsk> ustaw ( 1 . 0 , 2 . 0 ) ;
wsk> p o l e ( ) ;

43

45

wsk = &p ;
wsk> ustaw ( 1 . 0 , 2 . 0 ) ;
wsk> p o l e ( ) ;

47

49

51

wsk = &k ;
wsk> ustaw ( 1 . 0 ) ;
wsk> p o l e ( ) ;
cin . get () ;
return 0 ;
}

201

202

8. Funkcje statyczne i wirtualne


Po uruchomieniu tego programu mamy nastpujcy komunikat:
pole trojkata = 1
pole prostokata = 2
pole kola = 3.14159

Zwracamy uwag, e w funkcji main() tworzymy wskanik do obiektu gura


(klasa bazowa) oraz tworzymy zmienne klas pochodnych trojkat, prostokt
i kolo :
f i g u r a wsk ;
trojkat t ;
prostakat p ;
kolo k ;

Wywoania metod dla naszych obiektw (klasy trojkat, prostokt i kolo)


metod wskanika klasy bazowej jest preferowan technik ( wydajne podejcie).
wsk = &t ;
wsk> ustaw ( 1 . 0 , 2 . 0 ) ;
wsk> p o l e ( ) ;

Rozdzia 9
Klasy wirtualne i zagniedone

9.1. Wstp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204


9.2. Klasy wirtualne . . . . . . . . . . . . . . . . . . . . . . 204
9.3. Klasy zagniedone . . . . . . . . . . . . . . . . . . . . 212

204

9. Klasy wirtualne i zagniedone

9.1. Wstp
Wan cech jzyka C++ jest moliwo stosowania wirtualnych klas
bazowych. Konieczno stosowania tego mechanizmu wynika z moliwoci
pojawienia si niejednoznacznoci gdy korzystamy w skomplikowany sposb
z wielu powizanych hierarchicznie klas bazowych. Inn cech jzyka jest
moliwo deniowania klas zagniedonych, to znaczy klas deniowanych
jedna w drugiej. W praktyce technika klas zagniedonych jest stosunkowo
rzadko stosowana.

9.2. Klasy wirtualne


W jzyku C++ mamy moliwo realizacji tzw. dziedziczenia wielobazowego (wielokrotnego), tzn. mamy do czynienia z sytuacj gdy klasa pochodna ma wiele klas bazowych. Wielu zawodowych programistw uwaa,
e dziedziczenie wielokrotne jest zbyt skomplikowane i najczciej zbdne w
programowaniu obiektowym. Autor cenionych podrcznikw o jzyku C++
Nicolai Josuttis wrcz namawia do unikania dziedziczenia (nawet prostego),
sugerujc rozwizania typu kompozycji (zoenia) a uywania dziedziczenia
tylko w sytuacjach, gdy inna implementacja nie jest moliwa. Te wnioski
odnosz si z jeszcze wiksz si do dziedziczenia wielokrotnego, tym bardziej, e ten typ dziedziczenia moe prowadzi do wielu koniktw (np.
konikt nazw). Deniowanie klasy pochodnej w przypadku dziedziczenia
wielokrotnego jest proste. W denicji naley te klasy po prostu wyliczy,
podajc specykator dostpu:
c l a s s pochodna : public bazowa_1 ,
{
// i n s t r u k c j e
};

public bazowa_2

W powyszym przykadzie mamy zdeniowan klas pochodn (o nazwie


pochodna), ktra ma dwie klasy bazowe (bazowa 1 oraz bazowa 2). Obiekt
klasy pochodna bdzie mia nastpujce waciwoci:
bdzie wywoywa wszystkie publiczne funkcje skadowe klasy bazowa 1
i bazowa 2
bdzie zawiera wszystkie pola klasy bazowa 1 i bazowa 2 podczas tworzenia nowego obiektu klasy pochodna, automatycznie jest wywoany
konstruktor domylny klas bazowa 1 oraz bazowa 2,
zniszczenie obiektu klasy pochodna automatycznie wywoa destruktory
klas bazowych.
W kolejnym przykadzie zilustrujemy zasady wykorzystania dziedziczenia wielokrotnego. Tworzymy dwie klasy bazowe o nazwach rower i motor

9.2. Klasy wirtualne


oraz klas pochodn o nazwie motorower. Uycie klasy pochodnej dziedziczcej wielokrotnie niczym nie rni si od uycia obiektu klasy pochodnej
prostej. Praktycznie kod klienta nie musi wiedzie, e wykorzystuje obiekt
klasy dziedziczcej wielokrotnie.
Listing 9.1. Przykad dziedziczenia wielokrotnego
2

#include <i o s t r e a m >


using namespace s t d ;
c l a s s rower
{ public :
v i r t u a l void pokaz_r ( )
{ c o u t << " rower madwa k o l a " << e n d l ;
}
};

10

12

14

16

18

20

22

c l a s s motor
{ public :
v i r t u a l void pokaz_m ( )
{ c o u t << " motor madwa c y l i n d r y " << e n d l ;
}
};
c l a s s motorower : public rower , public motor
{ public :
v i r t u a l void pokaz_mr ( )
{ c o u t << "mamy motorower " << e n d l ;
}
};

24

26

28

30

32

i n t main ( )
{ motorower mr ;
mr . pokaz_r ( ) ;
mr . pokaz_m ( ) ;
mr . pokaz_mr ( ) ;
cin . get () ;
return 0 ;
}

Po uruchomieniu tego programu, mamy nastpujcy wydruk:


rower ma dwa k o l a
motor ma dwa c y l i n d r y
mamy motorower

Zgodnie z oglnymi zasadami obiekt klasy motorower obsuguje wszystkie


metody publiczne klas motor i rower. Sytuacja komplikuje si, gdy w klasach bazowych wystpi funkcja skadowa o takiej samej nazwie. Zaoymy,

205

206

9. Klasy wirtualne i zagniedone


e w obu klasach bazowych mamy metod o nazwie waga(). Klasy bazowe
nie s ze sob powizane, nie dysponuj adn informacj o zdeniowanych
metodach. Dopki obiekt klasy pochodnej (w naszym przypadku klasy motorower) nie wywoa tej metody , nic specjalnego si nie dzieje. Gdy jednak
metoda waga() bdzie wywoana, kompilator wygeneruje bd. Pojawi si
informacja, e dane wywoanie jest niejednoznaczne.
Hierarchia klas wykorzystana w naszym przykadzie pokazana jest na
rysunku 9.1.

Rysunek 9.1. Hierarchia klas w dziedziczeniu wielokrotnym

W kolejnym przykadzie zilustrujemy bd wywoany niejednoznacznoci nazw.


Listing 9.2. Dziedziczenie wielokrotne; bd niejednoznacznoci nazw
1

11

13

15

17

19

21

23

#include <i o s t r e a m >


using namespace s t d ;
c l a s s rower
{ public :
v i r t u a l void pokaz_r ( )
{ c o u t << " rower madwa k o l a " << e n d l ;
}
v i r t u a l void waga ( )
// n i e j e d n o z n a c z n a nazwa
{ c o u t << "waga roweru = 10 kg " << e n d l ;
}
};
c l a s s motor
{ public :
v i r t u a l void pokaz_m ( )
{ c o u t << " motor madwa c y l i n d r y " << e n d l ;
}
v i r t u a l void waga ( )
// n i e j e d n o z n a c z n a nazwa
{ c o u t << "waga motoru= 5 kg " << e n d l ;
}
};
c l a s s motorower : public rower , public motor
{ public :
v i r t u a l void pokaz_mr ( )
{ c o u t << "mamy motorower " << e n d l ;

207

9.2. Klasy wirtualne


25

27

29

31

33

35

}
};
i n t main ( )
{ motorower mr ;
mr . pokaz_r ( ) ;
mr . pokaz_m ( ) ;
mr . pokaz_mr ( ) ;
mr . waga ( ) ;
cin . get () ;
return 0 ;
}

// Blad n i e j e d n o z n a c z n a nazwa !

Prba uruchomienia tego programu spowoduje wygenerowanie nastpujcego komunikatu:


[ C++ E r r o r ] Unit1 . cpp ( 3 6 ) : E2014
Member i s ambiguous : rower : : waga and motor : : waga

Problem mona rozwiza na kilka sposobw:


jawne rzutowanie obiektu
w klasie pochodnej na nowo deniujemy sporn metod
wprowadzenie dwukrotnego dziedziczenia po tej samej klasie
wprowadzenie klasy wirtualnej
Zmodykujemy funkcj main() tak, aby wykorzysta metod jawnego
rzutowania:
i n t main ( )
{ motorower mr ;
mr . pokaz_r ( ) ;
mr . pokaz_m ( ) ;
mr . pokaz_mr ( ) ;
static_cast<rower >(mr) . waga ( ) ; // jawne r z u t o w a n i e w g o r e
mr . motor : : waga ( ) ;
// poprawne w y w o l a n i e
cin . get () ;
return 0 ;
}

Po uruchomieniu naszego programu otrzymamy cakiem poprawny wynik:


rower ma dwa k o l a
motor ma dwa c y l i n d r y
mamy motorowe
waga roweru = 10 kg
waga motoru = 5 kg

Omwilimy zagadnie niejednoznacznoci nazw dla przypadku dziedziczenia


wielokrotnego na nieco zbyt dydaktycznym przykadzie. W praktyce jednak

208

9. Klasy wirtualne i zagniedone


dziedziczenie wielokrotne jest czsto uywane. Przykadem moe by hierarchia klas wykorzystana w bibliotece standardowej do stworzenia klasy
iostream (rys.9.2).
Klasa ios jest klas bazow dla klas istream i ostream. Klasa iostream
dziedziczy po klasach istream i ostream. Aby efektywnie rozwiza moliwy
konikt nazw stosuje si wirtualne klasy bazowe (podstawowe).

Rysunek 9.2. Diagram klas dla wielokrotnego dziedziczenie z klasy bazowej ios
potrzebny do utworzenia klasy pochodnej iostream (tzw. dziedziczenie rombowe
lub diamentowe)

Uycie klasy wirtualnej zilustrujemy cakiem praktycznym przykadem.


Pokaemy program sucy do obliczania powierzchni cakowitej walca. Projekt obliczeniowy skada si z etapw: osobno obliczamy pole podstawy
walca (klasa pole podstawy) oraz pole powierzchni bocznej walca (klasa
pole boczne). Cakowit powierzchni walca obliczamy przy pomocy klasy
pole walca. Hierarchia klas pokazana jest na rys. 9.3. W klasach pochodnych
mamy metod o nazwie licz pole(). Wywoanie tej metody moe prowadzi
do koniktu nazw. Problem koniktu nazw w dziedziczeniu wielokrotnym
rozwizuje si stosujc tzw. dziedziczenie wirtualne. Kada klasa podstawowa jest dziedziczona jako wirtualna. Formalnie aby wprowadzi dziedziczenie wirtualne naley nazw klasy bazowej poprzedzi sowem kluczowym
virtual. W naszym przykadzie tego typu deklaracje maj posta:
c l a s s pole_podstawy : v i r t u a l public w a l e c
{ . . . };
c l a s s pole_boczne : v i r t u a l public w a l e c
{ . . . };

W typ przykadzie klasy pochodne pole podstawy oraz pole boczne musz
zadeklarowa klas bazow walec jako wirtualn.
Listing 9.3. Dziedziczenie wielokrotne; klasa wirtualna
1

#include <i o s t r e a m >


#include <math>

9.2. Klasy wirtualne


3

using namespace s t d ;
const double PI = 2 . 0 a s i n ( 1 . 0 ) ;

11

class walec
{ protected :
double r , h ; // promien podstawy i w y s o k o s c
public :
w a l e c ( double r r =1 , double hh=1)
{ r = rr ;
h = hh ; }
void pokaz ( ) { c o u t << " r= " << r <<" h= " << h << e n d l ; }
};

13

15

17

19

21

c l a s s pole_podstawy : v i r t u a l public w a l e c
{ protected :
double pole_p ; // p o l e podstawy w a l c a
public :
pole_podstawy ( double r r , double hh ) : w a l e c ( r r , hh ) { }
void l i c z _ p o l e _ p ( )
{ pole_p = 2 . 0 PI r r ; // o b l i c z o n e p o l e 2 podstaw
}
};

23

25

27

29

31

c l a s s pole_boczne : v i r t u a l public w a l e c
{ protected :
double pole_b ; // p o l e podstawy w a l c a
public :
pole_boczne ( double r r , double hh ) : w a l e c ( r r , hh ) { }
void l i c z _ p o l e _ b ( )
{ pole_b = 2 . 0 PI r h ; // o b l i c z o n e p o l e s c i a n y b o c z n e j
}
};

33

35

37

39

41

43

45

47

49

51

53

c l a s s pole_walca : public pole_podstawy , public pole_boczne


{ protected :
double pole_w ; // p o l e p o w i e r z c h n i c a l e g o w a l c a
public :
pole_walca ( double r r , double hh ) : pole_podstawy ( r r , hh ) ,
pole_boczne ( r r , hh ) , w a l e c ( r r , hh ) { }
void licz_pole_w ( )
{ l i c z _ p o l e _ p ( ) ; // o b l i c z o n e p o l e podstawy
l i c z _ p o l e _ b ( ) ; // o b l i c z o n e p o l e b o c z n e
pole_w = pole_p + pole_b ; // o b l i c z o n e p o l e w a l c a
c o u t << " p o w i e r z c h n i a c a l k o w i t a walca = "
<< pole_w << e n d l ;
}
};
i n t main ( )
{ pole_walca w( 1 . 0 , 1 . 0 ) ;
w . pokaz ( ) ;
w . licz_pole_w ( ) ;
cin . get () ;

209

210

9. Klasy wirtualne i zagniedone


return 0 ;
55

Po uruchomieniu tego programu mamy nastpujcy wydruk:


r = 1
h = 1
p o w i e r z c h n i a c a l k o w i t a walca = 1 2 . 5 6 6 4

Zastosowana w naszym przykadzie hierarchia klas pokazana jest na rys.


9.3.

Rysunek 9.3. Hierarchia klas, dziedziczenie wielokrotne, klasa bazowa walec

Gdyby deklaracje klas pochodnych miay posta:


c l a s s pole_podstawy : public w a l e c
c l a s s pole_boczne : public w a l e c

to pojawia si komunikat kompilatora C++ (Builder 6, Borland):


[ C++ E r r o r ] Unit1 . cpp ( 4 1 ) : E2312 w a l e c i s
not an unambiguous b a s e c l a s s o f pole_walca
[ C++ E r r o r ] Unit1 . cpp ( 5 2 ) : E2014 Member i s ambiguous :
w a l e c : : pokaz and w a l e c : : pokaz

gdzie wyspecykowane (numeracja w nawiasach okrgych) linie kodu z bdem to:


(40)
(41)
(51)

pole_walca ( double r r , double hh ) : pole_podstawy ( r r , hh ) ,


pole_boczne ( r r , hh ) , w a l e c ( r r , hh ) { }
w . pokaz ( ) ;

211

9.2. Klasy wirtualne


Metoda pokaz() w tym przypadku nie jest jednoznacznie okrelona bo moe
by dziedziczona po klasach pole podstawy lub pole boczne.
Korzystanie z wirtualnych klas bazowych jest eleganckim i mao kopotliwym rozwizaniem. W pewnych przypadkach mona unikn niejednoznacznoci nazw metody stosujc kwantykator zakresu (operator czterokropka
:: ) z nazw klasy. W kolejnym przykadzie omwimy ten sposb usuwania
niejednoznacznoci nazw.
Listing 9.4. Dziedziczenie wielokrotne; operator ::
1

11

13

15

17

19

21

23

25

27

29

#include <i o s t r e a m >


using namespace s t d ;
// w e r s j a b e z metod w i r t u a l n y c h
c l a s s rower
{ public :
void pokaz_r ( )
{ c o u t << " rower madwa k o l a " << e n d l ;
}
void waga ( )
// n i e j e d n o z n a c z n a nazwa
{ c o u t << "waga roweru = 10 kg " << e n d l ;
}
};
c l a s s motor
{ public :
void pokaz_m ( )
{ c o u t << " motor madwa c y l i n d r y " << e n d l ;
}
void waga ( )
// n i e j e d n o z n a c z n a nazwa
{ c o u t << "waga motoru= 5 kg " << e n d l ;
}
};
c l a s s motorower : public rower , public motor
{ public :
void pokaz_mr ( )
{ c o u t << "mamy motorower " << e n d l ;
}
};

31

33

35

37

39

i n t main ( )
{ motorower mr ;
mr . pokaz_r ( ) ;
mr . pokaz_m ( ) ;
mr . pokaz_mr ( ) ;
mr . motor : : waga ( ) ;
cin . get () ;
return 0 ;
}

//usuwamy n i e j e d n o z n a c z n o s c !

212

9. Klasy wirtualne i zagniedone


Po uruchomieniu tego programu otrzymujemy komunikat:
rower ma dwa k o l a
motor ma dwa c y l i n d r y
mamy motorower
waga motoru = 5 kg

Zastosowanie operatora zakresu (::) w instrukcji:


mr . motor : : waga ( ) ;

//usuwamy n i e j e d n o z n a c z n o s c !

pozwala na uniknicie wygenerowania bdu zwizanego z niejednoznacznoci nazw. W sposb jawny zostaa wybrana metoda z klasy pochodnej
motor. Podobnie moemy wybra metod z klasy rower. Naley jednak zdawa sobie spraw z faktu, e ta metoda nie jest zbyt dobra, poniewa mamy
w klasie motorower wicej ni jedn kopi metody waga(). Rekomenduje si
w zasadzie stosowanie metod wirtualnych.

9.3. Klasy zagniedone


W jzyku C++ istnieje moliwo zdeniowania klasy wewntrz funkcji
lub innej klasy. Nie s to zbyt popularne techniki programistyczne, gdy
prawd mwic trudno znale realne zastosowania dla tego typu konstrukcji. Na pocztku omwimy denicje klasy wewntrz funkcji wykorzystujc
niewielki program testowy.
Listing 9.5. Klasa lokalna (wewntrz funkcji)
1

11

13

15

#include <i o s t r e a m >


using namespace s t d ;
void motorower ( )
// d e f i n i c j a k l a s y w f u n k c j i
{ class cc
{ int r , m ;
// waga : motor , rower
public :
void waga_r ( i n t r r ) { r = r r ; }
void waga_m ( i n t mm) { m = mm ; }
i n t get_r ( ) { return r ; }
i n t get_m ( ) { return m; }
} mr ;
mr . waga_r ( 1 0 ) ;
mr . waga_m ( 5 ) ;
c o u t << "waga motoroweru = "<< mr . get_r ( )
+ mr . get_m ( )<< " kg " << e n d l ;
}

17

19

i n t main ( )
{ motorower ( ) ;

213

9.3. Klasy zagniedone


cin . get () ;
return 0 ;

21

Wynikiem dziaania tego programu jest nastpujca informacja:


waga motoroweru = 15 kg

Poniewa klasa cc zostaa zadeklarowana wewntrz funkcji, jest widoczna wycznie wewntrz funkcji motorower(). Podczas tworzenia klasy lokalnej mamy szereg ogranicze. Wszystkie metody klasy musz by zdeniowane wewntrz deklaracji klasy. Klasy lokalne nie mog take zawiera zmiennych statycznych. Klasy lokalne nie mog korzysta ze zmiennych lokalnych
funkcji. Jzyk C++ dopuszcza moliwo deniowania klasy wewntrz innej klasy. Tego typu klasa nosi nazw klasy zagniedonej (inna nazwa
klasy wewntrznej, o klasie zewntrznej mwimy, e jest klas otaczajc.
W kolejnym przykadzie demonstrujemy wykorzystanie klasy zagniedonej, tworzc program do obsugi pacjentw, danymi wejciowymi s imi i
nazwisko pacjenta oraz koszt badania i koszt lekarstw. Na kocu drukowany
jest raport kocowy podajcy cakowity koszt wizyty pacjenta. W naszym
przykadzie, omawianym poniej, klasa Koszt jest klas zagniedon, a
klas Pacjent jest klas otaczajc. Klasy zagniedone s dostpne tylko
wewntrz klasy otaczajcej.
Listing 9.6. Klasa zagniedona
1

11

13

15

17

19

21

#include <i o s t r e a m >


#include <s t r i n g >
using namespace s t d ;
class Pacjent
s t a t i c i n t nr ;
{
s t r i n g nazwisk o ;
s t r i n g imie ;
const i n t i d ;
i n t badanie , aptek a ;
public :
c l a s s Koszt
{ int id ;
int i l e ;
public :
Koszt ( i n t id , i n t i l e ) : i d ( i d ) , i l e ( i l e ) {
void pokaz ( ) ;
};
P a c j e n t ( const s t r i n g &n , const s t r i n g &i= " " )
: nazwisk o ( n ) , i m i e ( i ) ,
i d(++nr ) , b a d a n i e ( 0 ) , aptek a ( 0 ) { }

214

9. Klasy wirtualne i zagniedone

23

25

27

P a c j e n t & wplata_b ( i n t w1 ) { b a d a n i e += w1 ; return t h i s ; }


P a c j e n t & wplata_a ( i n t w1 ) { aptek a += w1 ; return t h i s ; }
Koszt d a j _ k o s z t ( ) ;
const s t r i n g &naz ( ) const { return nazwisk o ; }
const s t r i n g &im ( )
const { return i m i e ; }
~Pacjent ( ) { }
};

29

31

33

35

37

39

41

43

i n t P a c j e n t : : nr = 0 ;
P a c j e n t : : Koszt P a c j e n t : : d a j _ k o s z t ( )
{ return new Koszt ( id , b a d a n i e + aptek a ) ;
}
void P a c j e n t : : Koszt : : pokaz ( )
{ c o u t << " i d : " << i d << " k o s z t : " << i l e << e n d l ;
}
i n t main ( )
{ P a c j e n t p1 ( " Kowalsk i " ) ;
p1 . wplata_b ( 1 0 0 ) . wplata_a ( 5 0 ) ;
c o u t << " p a c j e n t " << p1 . naz ( ) << " " << p1 . im ( ) <<e n d l ;
P a c j e n t : : Koszt w1 = p1 . d a j _ k o s z t ( ) ;
w1>pokaz ( ) ;

45

P a c j e n t p2 ( " Kwiatek " , "Ewa" ) ;


p2 . wplata_b ( 2 0 0 ) . wplata_a ( 1 5 0 ) ;
c o u t << " p a c j e n t " << p2 . naz ( ) <<" " << p2 . im ( )<< e n d l ;
P a c j e n t : : Koszt w2 = p2 . d a j _ k o s z t ( ) ;
w2>pokaz ( ) ;

47

49

51

cin . get () ;
return 0 ;

53

Po uruchomieniu tego programu mamy nastpujcy komunikat:


pacjent
id : 1
pacjent
id : 2

Kowalsk i
koszt :150
Kwiatek Ewa
k o s z t : 350

Klasa Koszt jest zadeklarowana wewntrz klasy Pacjent. Klasa moe by


zdeniowana wewntrz klasy zewntrznej lub poza ni. Rwnie metody
klasy zagniedonej mog by deniowane poza klas otaczajc. W naszym przypadku tak zdeniowana jest metoda pokaz(). Jeeli metoda klasy
zagniedonej deniowana jest na zewntrz musimy uy podwjnej kwalikacji, w naszym przypadku metoda pokaz() jest w zakresie klasy Koszt,
ktra z kolei jest w zakresie klasy Pacjent:

9.3. Klasy zagniedone


void P a c j e n t : : Koszt : : pokaz ( )
{ c o u t << " i d : " << i d << " k o s z t : " << i l e << e n d l ;
}

Do obsugi imienia i nazwiska pacjenta wykorzystalimy klas string (plik


nagwkowy <string>) , deklaracja zmiennych ma posta:
s t r i n g nazwisk o ;
s t r i n g imie ;

Wykorzystujemy take konstruktor, ktrego parametrem jest nazwisko i


opcjonalnie imi ( domylne imie to pusty acuch), inicjujemy nazwisko i
imie za pomoc przekazanych parametrw:
P a c j e n t ( const s t r i n g &n , const s t r i n g &i= " " )
: nazwisk o ( n ) , i m i e ( i ) ,
i d(++nr ) , b a d a n i e ( 0 ) , aptek a ( 0 ) { }

Zdeniowane s take dwie metody zwracajce nazwisko i imi:


const s t r i n g &naz ( ) const { return nazwisk o ; }
const s t r i n g &im ( )
const { return i m i e ; }

W funkcji main() tworzenie obiektw jest teraz proste:


P a c j e n t p1 ( " Kowalsk i " ) ;
p1 . wplata_b ( 1 0 0 ) . wplata_a ( 5 0 ) ;
c o u t << " p a c j e n t " << p1 . naz ( ) << " " << p1 . im ( ) <<e n d l ;
P a c j e n t : : Koszt w1 = p1 . d a j _ k o s z t ( ) ;
w1>pokaz ( ) ;

Klasy zagniedone stosowane s rzadko, najczciej praktycznie klasy zagniedone stosowane s do klas wyjtkw (obsuga bdw).
W przypadku klas zagniedonych obowizuj normalne zasady kontroli
dostpu. Na zewntrz dostp moliwy jest tylko, gdy klasa zagniedona
jest publiczna. Gdy klas zagniedon zadeklarujemy jako prywatn lub
chronion, dostpna bdzie tylko w klasie zewntrznej.

215

Rozdzia 10
Wskaniki do klas

10.1. Wstp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218


10.2. Wskaniki klasowe . . . . . . . . . . . . . . . . . . . . . 218
10.3. Wskaniki skadowych klas . . . . . . . . . . . . . . . . 224

218

10. Wskaniki do klas

10.1. Wstp
Zagadnienie stosowania wskanikw w odniesieniu do obiektw klasy jest
do wane aczkolwiek nieco skomplikowane (jak to zazwyczaj jest z niebanalnym wykorzystaniem wskanikw w jzyku C++). Obecnie koncentrowa
bdziemy si na zagadnieniach dostpu do danych klasy i funkcji skadowych
klasy przy pomocy wskanikw. Oczywicie wskanikami do obiektw posugujemy si podobnie jak wskanikami do zmiennych innych typw.

10.2. Wskaniki klasowe


W krtkim przykadzie zademonstrujemy jak przy pomocy wskanika
moemy uzyska dostp do obiektu.
Listing 10.1. Wskaniki; dostp do obiektu
1

#include<i o s t r e a m >
using namespace s t d ;

11

13

15

17

c l a s s punkt
{ int x ;
public :
: x(x)
punkt ( i n t x=0 )
i n t g e t ( ) { return x ; }
};

i n t main ( )
{ punkt p ( 1 0 ) , wp ;
wp = &p ;
c o u t <<" sk ladowa = " << wp>g e t ( ) << e n d l ;
cin . get () ;
return 0 ;
}

Wynikiem dziaania tego programu jest komunikat:


sk ladowa = 10

W instrukcjach:
punkt p ( 1 0 ) , wp ;
wp = &p ;

tworzymy obiekt p, inicjalizujemy skadow obiektu p wartoci 10, tworzymy zmienn wskanikow wp oraz przypisujemy jej adres obiektu p. W
kolejnej instrukcji:

219

10.2. Wskaniki klasowe


c o u t <<" sk ladowa = " << wp>g e t ( ) << e n d l ;

dysponujc wskanikiem uzyskujemy dostp do zmiennej skadowej obiektu


p korzystajc z operatora strzaki ( >). Zgodnie z arytmetyk wskanikow
moemy zwikszajc wskanik odwoywa si do kolejnych elementw tego
samego typu.
W kolejnym przykadzie stworzymy tablic obiektw i dziki wskanikowi uzyskamy dostp do elementw tablicy obiektw.
Listing 10.2. Wskaniki; dostp do elementw tablicy obiektw
1

#include<i o s t r e a m >
using namespace s t d ;

11

13

15

17

19

c l a s s punkt
{ int x ;
public :
punkt ( i n t x=0) : x ( x ) {
i n t g e t ( ) { return x ; }
};

i n t main ( )
{ punkt p [ 2 ] = { 1 , 1 3 } ;
punkt wp ;
wp = p ;
c o u t <<" sk ladowa 1 = " << wp>g e t ( ) << e n d l ;
wp++;
c o u t <<" sk ladowa 2 = " << wp>g e t ( ) << e n d l ;
cin . get () ;
return 0 ;
}

Wynikiem dziaania programu jest komunikat:


sk ladowa 1 = 1
sk ladowa 2 = 13

W instrukcjach:
punkt p [ 2 ] = { 1 , 1 3 } ;
punkt wp ;
wp = p ;

kolejno tworzona jest tablica obiektw, deklarowany jest wskanik wp, a


nastpnie do tego wskanika przypisany jest adres pocztkowy tablicy p[ ].
W instrukcjach:
wp++;

220

10. Wskaniki do klas


c o u t <<" sk ladowa 2 = " << wp>g e t ( ) << e n d l ;

inkrementacja wskanika wp powoduj, e uzyskujemy adres kolejnego obiektu i mona wydrukowa warto zmiennej skadowej drugiego obiektu. W
pokazanym przykadzie klasa miaa tylko jedn zmienn skadow. Wobec
tego inicjalizacja tablicy obiektw bya prosta:
punkt p [ 2 ] = { 1 , 1 3 } ;

W bardziej zoonych przypadkach do inicjalizacji elementw tablicy mona zastosowa konstruktor. W pokazanym programie trzeba go wywoa
oddzielnie dla kadego elementu tablicy.
Listing 10.3. Wskaniki; dostp do elementw tablicy obiektw
1

#include<i o s t r e a m >
using namespace s t d ;

c l a s s punkt
{ int x , y ;
public :
punkt ( i n t x =0 , i n t y=0) : x ( x ) , y ( y )
i n t get_x ( ) { return x ; }
i n t get_y ( ) { return y ; }
};

11

13

15

17

19

21

23

i n t main ( )
{ punkt p [ 2 ] = { punkt ( 1 , 1 1 ) , punkt ( 3 , 3 3 ) } ;
// i n i c j a l i z a c j a t a b l i c y
punkt wp ;
wp = p ;
c o u t <<" o b i e k t 1 , x= " << wp>get_x ( ) << e n d l ;
c o u t <<" o b i e k t 1 , y= " << wp>get_y ( ) << e n d l ;
wp++;
c o u t <<" o b i e k t 2 , x= " << wp>get_x ( ) << e n d l ;
c o u t <<" o b i e k t 2 , y= " << wp>get_y ( ) << e n d l ;
cin . get () ;
return 0 ;
}

W wyniku dziaanie tego programu mamy informacje:


obiekt
obiekt
obiekt
obiekt

1,
1,
2,
2,

x
y
x
y

=
=
=
=

1
11
3
33

10.2. Wskaniki klasowe


Poszczeglne elementy tablicy inicjalizowane zostay przy pomocy konstruktora klasy punkt :
punkt ( i n t x=0 , i n t y=0) : x ( x ) , y ( y ) { }
// k o n s t r u k t o r
punkt p [ 2 ] = { punkt ( 1 , 1 1 ) , punkt ( 3 , 3 3 ) } ;
// i n i c j a l i z a c j a t a b l i c y o b i e k t o w

Moemy wykorzystywa wskaniki do tworzenia tablicy obiektw w pamici


swobodnej ( dynamiczny przydzia pamici) kolejny przykad ilustruje to
zagadnienie.
Listing 10.4. Wskaniki klasowe; operator new
2

10

12

14

16

18

20

#include <i o s t r e a m >


using namespace s t d ;
c l a s s punkt
{ int x , y ;
public :
void s e t ( i n t xx , i n t yy ) { x = xx ; y = yy ; }
void pokaz ( ) { c o u t << " x= "<< x << " " <<"y= "<< y
<< e n d l ; }
};
i n t main ( )
{ punkt wsk = new punkt [ 3 ] ;
f o r ( i n t i =0; i <3; i ++)
{ wsk [ i ] . s e t ( i , i +3) ;
wsk [ i ] . pokaz ( ) ;
}
cin . get () ;
return 0 ;
}

W wyniku dziaania tego programu otrzymujemy nastpujcy komunikat:


x= 0
x= 1
x= 2

y = 3
y = 4
y = 5

W funkcji main() tworzymy dynamicznie tablice trzech obiektw typu punkt.


W ptli for nastpuje nadawanie wartoci danym oraz ich wywietlanie. Wykorzystano metody set() oraz ustaw() klasy punkt.
W jzyku C++ oglna zasada, cile przestrzegana mwi, e wskanik
ustalonego typu nie moe wskazywa na obiekt innego typu. Istnieje jednak
wany wyjtek od tej reguy dotyczy on obsugi obiektw klas pochodnych.

221

222

10. Wskaniki do klas


Mona utworzy wskanik typu klasy bazowej, ktry bez problemw obsuy obiekty klasy pochodnej. Odwrotna sytuacja nie jest moliwa (wskanik
klasy pochodnej nie obsuy obiektu klasy bazowej, zasadniczo). Pokazany
program ilustruje to zagadnienie. W omawianym przykadzie zostaje utworzony wskanik klasy bazowej, z jego pomoc uzyskamy dostp do pola
obiektu klasy pochodnej.
Listing 10.5. Wskaniki klasowe; dziedziczenie
1

11

13

15

17

19

21

23

#include<i o s t r e a m >
using namespace s t d ;
c l a s s punkt_x
{ int x ;
public :
punkt_x ( i n t x=0) : x ( x ) {
set_x ( i n t xx ) {x = xx ; }
i n t get_x ( ) { return x ; }
};

c l a s s punkt_y : public punkt_x


{ int y ;
public :
punkt_y ( i n t y=0) : y ( y ) { }
i n t get_y ( ) { return y ; }
};
i n t main ( )
{ punkt_x wsk ; // w s k a z n i k k l a s y b a z o w e j
punkt_y py ;
// o b i e k t k l a s y p o c h o d n e j
wsk = &py ;
// w s k a z n i k k l a s y b a z o w e j w s k a z u j e
// na o b i e k t k l a s y p o c h o d n e j
wsk>set_x ( 1 0 ) ; // u s t a w i e n i e d a n e j akcesorem
c o u t <<" o b i e k t k l a s y bazowej , x= " << wsk>get_x ( )
<< e n d l ;

25

wsk = punkt_x ( 1 0 0 ) ; // u s t a w i e n i a p o l a k o n s t r u k t o r e m
c o u t <<" o b i e k t k l a s y bazowej , x= " << wsk>get_x ( )
<< e n d l ;

27

29

cin . get () ;
return 0 ;

31

W wyniku dziaania pokazanego programu otrzymamy nastpujcy komunikat:


o b i e k t k l a s y bazowej ,
o b i e k t k l a s y bazowej ,

x =10
x =100

223

10.2. Wskaniki klasowe


Prba uzyskania dostpu do danej y klasy pochodnej przy pomocy wskanika do klasy bazowej:
wsk>get_y ( )

nie powiedzie si. Nie mona uzyska dostpu do danych klasy pochodnej wykorzystujc bezporednio wskanik klasy bazowej. Rozwizaniem jest
wykonanie odpowiedniego rzutowania wskanika klasy. Ilustruje to nastpny
program.
Listing 10.6. Wskaniki klasowe; rzutowanie wskanika
1

11

13

15

17

#include<i o s t r e a m >
using namespace s t d ;
c l a s s punkt_x
{ int x ;
public :
void set_x ( i n t xx ) {x = xx ; }
i n t get_x ( ) { return x ; }
};
c l a s s punkt_y : public punkt_x
{ int y ;
public :
void set_y ( i n t yy ) {y = yy ; }
i n t get_y ( ) { return y ; }
};
i n t main ( )
{ punkt_x wsk ;
// w s k a z n i k k l a s y b a z o w e j
punkt_y py ;
// o b i e k t k l a s y p o c h o d n e j
wsk = &py ;
// u s t a w i e n i e w s k a z n i k a

19

wsk>set_x ( 1 0 0 ) ;
c o u t <<" o b i e k t k l a s y bazowej , x= "
<< wsk>get_x ( ) << e n d l ;
( ( punkt_y ) wsk )>set_y ( 2 0 0 ) ; // metoda k l a s y p o c h o d n e j !
c o u t <<" o b i e k t k l a s y pochodnej , y= "
<< ( ( punkt_y ) wsk )>get_y ( ) << e n d l ;
cin . get () ;
return 0 ;

21

23

25

27

Wynikiem dziaania tego programu jest komunikat:


o b i e k t k l a s y bazowej ,
o b i e k t k l a s y pochodnej ,

x =100
y =200

Mimo oglnej reguy, mona obej ograniczenie i uzyska dostp do wszystkich skadowych klasy pochodnej. W tym celu, jak to pokazalimy, naley

224

10. Wskaniki do klas


dokona odpowiedniego rzutowania wskanika klasy bazowej na wskanik
klasy pochodnej:
( ( punkt_y ) wsk )>set_y ( 2 0 0 ) ;
c o u t <<" o b i e k t k l a s y pochodnej , y= "
<< ( ( punkt_y ) wsk )>get_y ( ) << e n d l ;

10.3. Wskaniki skadowych klas


Czasami zachodzi potrzeba uycia wskanika do elementu skadowego
klasy. W jzyku C++ mamy specjalny typ wskanika noszcy nazw wskanika skadowych klasy, ktry wskazuje oglnie skadow klasy a nie skadow
konkretnego wystpienia. Naley pamita, e obiekty tej samej klasy zawsze maj taki sam rozmiar w pamici. Z kolei skadowe obiektu znajduj
si zawsze w takiej samej odlegoci od pocztku tego obiektu (tzw. ofset
wzgldem pocztku). Poszczeglne skadowe s dobrze zlokalizowane w danym obiekcie. Dziki temu znamy ich pozycje w pamici. Naley pamita,
e nie chodzi tu o bezwzgldny adres skadowej obiektu a o jego przesuniecie
wzgldem pocztku obiektu. Gdy znamy adres obiektu to na podstawie tego
przesunicia wewntrz obiektu, kompilator jest w stanie wyliczy adres bezwzgldny. Wskaniki skadowych klas nie s zwykymi wskanikami, inne s
ich denicje i sposb posugiwania si nimi.
Listing 10.7. Wskaniki skadowych klas
1

#include <i o s t r e a m >


using namespace s t d ;

11

c l a s s punkt
{ public :
{ x = xx ; y = yy ; }
punkt ( i n t xx , i n t yy )
i n t kwadrat ( ) { return x x + yy ; }
int x , y ;
};
i n t punkt : : wskd ;
i n t ( punkt : : wsk f ) ( ) ;

// w s k a z n i k d a n e j
// w s k a z n i k metody

13

15

17

19

21

i n t main ( )
{ punkt p1 ( 3 , 4 ) ;
// t w o r z e n i e o b i e k t u
wskd = &punkt : : x ;
// p o b r a n i e o f s e t u x
c o u t << "x= " << p1 . wskd << e n d l ;
wskd = &punkt : : y ;
// p o b r a n i e o f s e t u y
c o u t << "y= " << p1 . wskd<< e n d l ;
wsk f = &punkt : : kwadrat ;
// p o b r a n i e o f s e t u metody
c o u t << " kwadrat= " << ( p1 . wsk f ) ( ) << e n d l ;

225

10.3. Wskaniki skadowych klas

cin . get () ;
return 0 ;

23

25

Wydruk z programu ma posta:


x= 3
y= 4
kwadrat = 25

W programie tworzone s dwa wskaniki skadowych:


i n t punkt : : wskd ;
i n t ( punkt : : wsk f ) ( ) ;

// w s k a z n i k d a n e j
// w s k a z n i k metody

Zwracamy uwag na skadni deklaracji wskanikw skadowych klas s


one inne ni zwykych wskanikw. Mamy specjalna konwencje tworzenia
wskanikw skadowych klasowych w jzyku C++. Deklaracja zmiennej
skadowej klasy ma posta:
typ_zm nazwa_klasy : : nazwa_wskaznika

gdzie typ zm oznacza typ skadowej w klasie nazwa klasy. Wartoci zmiennej nazwa wskaznika bdzie przesunicie (ofset) publicznej skadowej w obiekcie klasy nazwa klasy. W deklaracji obowizkowo wystpuje specykator
zakresu :: (w pokazanej denicji mamy zapis nazwa klasy :: ).
W pokazanym programie mamy w klasie punkt dwie zmienne skadowe: int x i int y. Denicja wskanika i przypisania (inicjowanie wskanikw
adresami elementw skadowych) mog mie posta:
i n t punkt : : wskd ;
wskd = &punkt : : x ;
wskd = &punkt : : y ;

// w s k a z n i k d a n e j
// p o b r a n i e o f s e t u x
// p o b r a n i e o f s e t u y

W tych przypisaniach symbol & nie oznacza tak jak w typowym wskaniku operatora pobranie bezwzgldnego adresu. W przypadku wskanikw
skadowych klas pobierane jest wzgldne przesuniecie skadowej x lub y
wzgldem pocztkowego obiektu klasy punkt. Nie mona wskanikw skadowych klas inkrementowa ani wykonywa innych operacji arytmetycznych
( operacja wskd++ jest nieprawomocna).
Zadeklarowany wskanik nie ma wikszego znaczenie jeeli nie zostanie
utworzony konkretny obiekt klasy. W naszym przykadzie tworzymy obiekt o
nazwie p1, przy pomocy konstruktora polom x i y przypisujemy odpowiednio
wartoci 3 i 4.

226

10. Wskaniki do klas


punkt p1 ( 3 , 4 ) ;

// t w o r z e n i e o b i e k t u

W tym momencie moemy ju odwoa si do konkretnej skadowej obiektu


przy pomocy operatora .* , tak jak to mamy w naszym przykadzie:
p1 . wskd
c o u t << "x= " << p1 . wskd << e n d l ;

Troch bardziej skomplikowana jest deklaracja wskanikw skadowych klasy dla metod (funkcji skadowych klasy). Przypominamy, e metody nie s
zawarte zycznie w obiektach istnieje tylko jedna wersja metody. Podczas
wywoywania metody, jest do niej przekazywany wskanik do wywoujcego
obiektu (tzn. do obiektu, ktry wywouje konkretn funkcj), jest to wskanik this. Dla funkcji skadowej klasy mamy nastpujc posta deklaracji
wskanika:
typ_m

( nazwa_klasy : : nazwa_wskaznika ) ( argumenty )

gdzie typ m jest typem zwracanym przez metod klasy nazwa klasy, nazwa wskaznika jest wskanikiem do funkcji skadowej klasy, argumenty oznaczaj list parametrw przekazywanych do funkcji. W deklaracji obowizkowo wystpuje specykator zakresu :: , nawiasy s konieczne do poprawnego
przyporzdkowanie operatora ::* . W pokazanym programie mamy w klasie
punkt metod o nazwie kwadrat(). Denicja wskanika i przypisania (inicjowanie wskanika metody) mog mie posta:
i n t ( punkt : : wsk f ) ( ) ;
punkt p1 ( 3 , 4 ) ;
wsk f = &punkt : : kwadrat ;

// w s k a z n i k metody
// t w o r z e n i e o b i e k t u
// p o b r a n i e o f s e t u metody

Odwoanie si do metody, korzystajc z operatora .* moe mie posta:


c o u t << " kwadrat= " << ( p1 . wsk f ) ( ) << e n d l ;

Czsto zamiast nazwy obiektu, moemy utworzy wskanik do obiektu. Aby


odwoa si do skadowej obiektu lub metody musimy posuy si innym
operatorem, operatorem > . W kolejnym przykadzie ilustrujemy to
zagadnienie.
Listing 10.8. Wskaniki skadowych klasy; wskanik do obiektu
1

#include <i o s t r e a m >


using namespace s t d ;

c l a s s punkt
{ public :

227

10.3. Wskaniki skadowych klas


punkt ( i n t x=0 , i n t y=0) : x ( x ) , y ( y ) { }
i n t kwadrat ( ) { return x x + yy ; }
int x , y ;

11

13

15

17

19

};
i n t main ( )
{ i n t punkt : : wskx ;
i n t punkt : : wsky ;
i n t ( punkt : : wsk f ) ( )
wskx = &punkt : : x ;
wsky = &punkt : : y ;
wsk f = &punkt : : kwadrat ;
punkt p1 ( 3 , 4 ) ;
punkt w1 = &p1 ;

//
//
; //
//
//
//
//
//

wskaznik danej x
wskaznik danej y
w s k a z n i k metody
pobranie of setu x
pobranie of setu y
pobranie o f e s t u kwadrat ()
tworzenie obiektu , i n i c j a c j a
w s k a z n i k do o b i e k t u , i n i c j a c j a

c o u t << " o b i e k t , x=" << p1 . wskx << e n d l ;


c o u t << " wsk aznik do o b i e k t u , y=" << w1>wsky << e n d l ;
c o u t << " wsk aznik do o b i e k t u , kwadrat = "
<< (w1>wsk f ) ( ) << e n d l ;

21

23

25

cin . get () ;
return 0 ;

27

Wynik dziaania programu ma posta :


obiekt , x = 3
wsk aznik do o b i e k t u , y = 4
wsk aznik do o b i e k t u , kwadrat = 25

W pokazanym programie, w1 jest nazw wskanika do obiektu klasy punkt.


Inicjalizacja wskanika (przypisanie mu adresu) oraz dostp do skadowej y
i metody kwadrat() maja posta:
punkt w1 = &p1 ; // w s k a z n i k do o b i e k t u , i n i c j a c j a
c o u t << " wsk aznik do o b i e k t u , y=" << w1>wsky << e n d l ;
c o u t << " wsk aznik do o b i e k t u , kwadrat = "
<< ( w1>wsk f ) ( ) << e n d l ;

Pokazano rwnie odwoanie si do skadowej klasy przy pomocy nazwy


obiektu i wskanika do zmiennej skadowej klasy:
c o u t << " o b i e k t , x=" << p1 . wskx << e n d l ;

Uytecznoci wskanikw do skadowych klasy nie jest zbyt dua. Argumentuje si, e czasami warto tej techniki uy do konstruowania rnego
rodzaju opcji wyboru (np. menu). W kolejnym przykadzie demonstrujemy
program w ktrym wykorzystano tablice wskanikw skadowych klasy.

228

10. Wskaniki do klas


Listing 10.9. Wskaniki skadowych klasy; tablice wskanikw
1

11

13

15

17

19

21

23

25

27

29

31

33

#include <i o s t r e a m >


using namespace s t d ;
c l a s s punkt
{ public :
i n t x , y , dx , dy ;
punkt ( i n t x =0 , i n t y=0 , i n t dx=0 , i n t dy=0)
: x ( x ) , y ( y ) , dx ( dx ) , dy ( dy ) {
}
i n t kwadrat ( )
{ return xx + yy ; }
i n t przesun_x ( )
{ x = x+dx ; return x ; }
i n t przesun_y ( )
{ y = y+dy ; return y ; }
};
i n t main ( )
{ i n t punkt : : p [ 4 ] ;
// t a b l i c a wskazni kow do zmiennych
i n t ( punkt : : m[ 3 ] ) ( ) ; // t a b l i c a wskazni kow do metod
p [ 0 ] = &punkt : : x ;
// p o b r a n i e o f s e t o w danych
p [ 1 ] = &punkt : : y ;
p [ 2 ] = &punkt : : dx ;
p [ 3 ] = &punkt : : dy ;
m[ 0 ] = &punkt : : kwadrat ; // p o b r a n i e o f s e t o w metod
m[ 1 ] = &punkt : : przesun_x ;
m[ 2 ] = &punkt : : przesun_y ;
punkt ob ( 1 , 2 , 1 , 1 ) ;
// u t w o r z e n i e o b i e k t u
c o u t <<"x = " << ob . p [ 0 ] << " dx= " << ob . p [ 2 ]
<< e n d l ;
c o u t <<"y = " << ob . p [ 1 ] << " dy= " << ob . p [ 3 ]
<< e n d l ;
c o u t <<"d s t a r e = " << ( ob . m[ 0 ] ) ( ) << e n d l ;
c o u t <<"x +dx= " << ( ob . m[ 1 ] ) ( ) << e n d l ;
c o u t <<"y +dy= " << ( ob . m[ 2 ] ) ( ) << e n d l ;
c o u t <<"dnowe= " << ( ob . m[ 0 ] ) ( ) << e n d l ;
cin . get () ;
return 0 ;
}

Po uruchomieniu tego programu mamy nastpujcy komunikat:


x
y
d
x
y
d

= 1
dx = 1
=1
dy = 1
stare = 5
+ dx = 2
+ dy = 3
nowe = 13

Pokazany wynik dziaania programu jest poprawny i wida, e wykorzystanie wskanikw do skadowych klas, nie powinno sprawia kopotw.

Rozdzia 11
Techniki obsugi bdw

11.1.
11.2.
11.3.
11.4.
11.5.

Wstp . . . . . . . . . . . . . . . . .
Funkcje walidacyjne . . . . . . . . .
Graniczne wartoci plik limits.h .
Narzdzie assert() i funkcja abort()
Przechwytywanie wyjtkw . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

230
230
234
237
239

230

11. Techniki obsugi bdw

11.1. Wstp
W zasadzie jest bardzo trudno unikn bdw w kodzie programu.
Nie istniej ugruntowane zasady jak pisa bezbdne programy, mamy w
podrcznikach lepsze lub gorsze zalecenia jak unika zoliwych bdw. Z
drugiej strony dymy do pisania bezbdnych kodw jzyk C++ i jego
biblioteki wspieraj obsug bdw. Wszystkie bdy moemy podzieli na
dwie kategorie:
bdy czasu kompilacji
bdy czasu wykonania
Przyjmuje si, e mniej grone s bdy powstajce w czasie kompilacji
wikszo kompilatorw wyposaona jest w doskonae (i bardzo rozbudowane) techniki diagnostyczne, tak, e przy pierwszym wykryciu bdu
kompilator przerwie proces kompilacji i wywietli odpowiedni komunikat o
bdzie. Oczywicie w wielu przypadkach komunikat taki bdzie mao czytelny, czasem bez sensu i nie zawsze wskazany wiersz kodu zawiera bd. Pocieszajcym jest fakt, e bd kompilacji zawsze si ujawni. Po stosunkowo
krtkim czasie, programista nabywa dowiadczenia i usuwanie bdw czasu kompilacji nie jest zbyt skomplikowane. Istniej popularne techniki (np.
wyczanie linijki kodu w ktrym kompilator sygnalizuje bd przy pomocy
znakw komentarza, czy te umieszczanie w programie testowych wydrukw czciowych wynikw) pomagajcy usun takie bdy. Wiele platform
programistycznych wyposaonych jest zestaw narzdzi diagnostycznych s
to tzw. debugery.
Bdy czasu wykonania s w wielu przypadkach trudne do zidentykowania i usunicia. Przyczyn tego jest fakt, e takie bdy nie generuj
komunikatw bdu oraz e mog pojawia si bardzo rzadko. Klasyczne
przyczyny wystpienia bdw to prba otworzenia nieistniejcego pliku,
danie wikszej iloci pamici ni moemy otrzyma, prba dzielenia przez
zero, napotkanie zej wartoci danych wejciowych, itp. W programach moemy stosowa rne techniki zapobiegajcy wystpieniom moliwych bdw, klasyczn technik jest stosowanie funkcji walidacyjnych, sprawdzanie poprawnoci danych wejciowych czy wykorzystanie wyspecjalizowanych
funkcji takich jak assert(). Odrbn technik obsugi bdw w jzyku C++
(podobnie jak w jzyku Java czy Pyton) jest przechwytywanie wyjtkw
(ang. exception).

11.2. Funkcje walidacyjne


Jeeli wiemy jakie warunki musz by spenione aby wykona zaplanowane operacje, moemy unikn bdw sprawdzajc te warunki. Bar-

11.2. Funkcje walidacyjne


dzo czsto znamy ograniczenia na wartoci argumentw przekazywanych
do funkcji. Klasycznym przykadem jest obliczanie pierwiastkw kwadratowych z liczb. Funkcja sqrt() z biblioteki math (lub math.h) akceptuje tylko
argumenty wiksze lub rwne zero. Sprawdzamy ten warunek i gdy argument jest ujemny wywoujemy funkcj biblioteczn exit(1), ktra formalnie
umieszczona jest w pliku nagwkowym stdlib.h .
i f ( x < 0) {
c o u t << " ujemna w a r t o s c x" << e n d l ;
getche () ;
exit (1) ;
}

Funkcja exit() jest bardzo uyteczna elegancko koczy dziaanie programu.


Oprnia ona wszystkie bufory wyjciowe, zamyka wszystkie otwarte strumienie i usuwa pliki tymczasowe. Nastpnie zwraca ona kontrol do systemu
operacyjnego. Przykad zastosowania tej funkcji pokazany jest poniej.
Listing 11.1. Sprawdzanie parametrw funkcji
1

11

13

15

17

19

#include <i o s t r e a m >


#include <c o n i o . h>
#include <math>
i n t main ( )
{
using namespace s t d ;
float x ;
c o u t << " Podaj x : " ;
c i n >> x ;
i f ( x < 0) {
c o u t << " ujemna w a r t o s c x" << e n d l ;
getche () ;
exit (1) ;
}
c o u t << " P i e r w i a s t e k z " << x << " = " << s q r t ( x ) ;
getche () ;
return 0 ;
}

Dziaanie programu jest nastpujce:


Podaj x: 9 Pierwiastek z 9 = 3
Podaj x : 4
Ujemna w a r t o s c

Pokazana technika zapobiegania bdom jest stosunkowo prosta i daje dobre


rezultaty. Gdy wystpuj bardziej skomplikowane warunki moemy zastoso-

231

232

11. Techniki obsugi bdw


wa odrbn funkcj, ktra sprawdzi te warunki. Taka funkcja walidacyjna
testuje pewne zaoenia i w przypadku ich poprawnoci zwraca kod, ktry
umoliwia kontynuowanie programu. Zastosujemy funkcj walidacyjn do
sprawdzenia czy nie damy obliczenia pierwiastka kwadratowego z ujemnego argumentu. Funkcja walidacyjna ma posta:
i n t w a l i d ( f l o a t xx )
{
i f ( xx >0) return 1 ;
else
return 0 ;
}

Wykorzystanie funkcji walidacyjne w programie pokazane jest poniej.


Listing 11.2. Sprawdzanie parametrw funkcji; funkcja walidacyjna
2

#include <i o s t r e a m >


#include <c o n i o . h>
#include <math>

int walid ( fl oat ) ;


6

10

12

14

16

18

20

22

24

i n t main ( )
{ using namespace s t d ;
float x ;
c o u t << " Podaj x : " ;
c i n >> x ;
i f ( w a l i d ( x ) ) c o u t << " p i e r w i a s t e k z "
<< x << " = " << s q r t ( x )<< e n d l ;
else
c o u t << " argument ujemny " << e n d l ;
getche () ;
return 0 ;
}
i n t w a l i d ( f l o a t xx )
{ i f ( xx >0) return 1 ;
else
return 0 ;
}

Kolejn technik zapobiegajc bdom jest prba zastpienia bdnie


wprowadzonego argumentu innym poprawnym. Jest to technika do ryzykowna, ale czasem stosowana. W kolejnym przykadzie obliczania pierwiastka kwadratowego , moemy zapobiec potencjalnym bdom sprawdzajc warto liczby, w przypadku gdy jest ona mniejsza ni zero zastpujemy
j liczb dodatni. W tym celu moemy posuy si prost funkcj :

11.2. Funkcje walidacyjne


f l o a t abs_war ( f l o a t xx )
{ i f ( xx < 0 )
xx = xx ;
return xx ;
}

Jeeli pomykowo wprowadzimy ujemn warto bdzie ona zamieniona na


warto dodatni, do funkcji wyliczajcej pierwiastek kwadratowy przekazana jest zawsze warto dodatnia.
W programie pokazanym niej pokazujemy takie rozwizanie, przy czym
stosujemy wasn funkcj obliczajc pierwiastek kwadratowy przy pomocy
iteracyjnej metody Newtona Raphsona. Jest to zadziwiajco dokadna i
szybka metoda. Algorytm obliczania wartoci pierwiastka kwadratowego z
liczby x z dokadnoci err (w naszym przykadzie err = 0.001) t metoda
ma posta pokazan na wydruku 11.3.
Algorytm Newtona-Raphsona obliczania pierwiastka kwadratowego:
1. Ustal warto pocztkow n0 (najczciej wybieramy n0 = 1)
2. Sprawdzamy wyraenie |n2i x| < err, jeli prawdziwe id do 4
3. Ustalamy kolejn warto ni = 0.5 (xx/ni1 + ni1 ), id do 2
4. Koniec oblicze, przybliona warto pierwiastka z x jest rwna ni
Listing 11.3. Sprawdzanie parametrw funkcji; automatyczna zmiana
2

10

12

14

16

18

20

22

24

26

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
f l o a t abs_war ( f l o a t ) ;
float pierwiastek ( float ) ;
i n t main ( )
{
float x ;
c o u t << " Podaj x : " ;
c i n >> x ;
x = abs_war ( x ) ;
c o u t << " P i e r w i a s t e k z " << x << " = "
<< p i e r w i a s t e k ( x )<< e n d l ; ;
getche () ;
return 0 ;
}
f l o a t abs_war ( f l o a t xx )
{ i f ( xx < 0 )
xx = xx ;
return xx ;
}
f l o a t p i e r w i a s t e k ( f l o a t xx )
{ float err = 0.001;
float n = 1.0 ;
int j = 0 ;
while ( abs_war ( nn xx ) >= e r r )
{
n = 0 . 5 ( xx/n + n ) ;

233

234

11. Techniki obsugi bdw


j ++; }
c o u t << " L i c z b a i t e r a c j i = " << j << e n d l ;
return n ;

28

30

Wykonanie pokazanego programu ma posta:


Podaj x : 25
Liczba i t e r a c j i = 5
P i e r w i a s t e k z 25 = 5 . 0 0 0 0 2

W przypadku wprowadzenia ujemnej wartoci wykonanie programu ma posta:


Podaj x : 100
Liczba i t e r a c j i = 7
P i e r w i a s t e k z 100 = 10

Jak widzimy, prosta funkcja testujca skutecznie zapobiega bdom.

11.3. Graniczne wartoci plik limits.h


W jzyku C i C++ nie s sprawdzane zakresy wartoci typw danych
(zakresy zale od iloci bajtw na jakiej kodowany jest konkretny typ w
konkretnym kompilatorze). W pliku bibliotecznym limits.h oraz climits pokazane s graniczne wartoci (maksymalne i minimalne) rozmaitych typw
danych jakie wykorzystywane s przez kompilator w danym systemie operacyjnym. Np. staa INT MAX reprezentuje maksymaln warto dla zmiennych cakowitych typu int, a np. UINT MAX dla typu unsigned int. Te
stae wykorzystujemy w programach do testowania, czy obliczana warto
mieci si w poprawnym zakresie. Zagadnie to ilustrujemy programem ktry
wylicza silni. Silnia nieujemnej liczby n, oznaczana jako n! jest iloczynem
n (n 1) (n 2) . . . . . . . . . 1

(11.1)

Przyjmuje si, e 0! =1 oraz 1! = 1. Silnia jest bardzo szybko rosnc funkcj, ju dla niewielkich wartoci n moe dawa bardzo du warto. W tej
sytuacji obliczenia silni bdziemy przeprowadza na typie unsigned long.
Specykacja jzyka C++ wymaga, aby zmienna typu unsigned long bya
przechowywana przynajmniej na 32 bitach (4 bajty), co daje zakres od 0 do
4294967295. Przekroczenie zakresu nie jest sygnalizowane w aden sposb,
programista musi sam zadba aby nie wyj poza zakres w czasie oblicze.
Tworzymy w programie funkcj:

11.3. Graniczne wartoci plik limits.h


bool w _ z a k r e s i e ( i n t nn )
{
unsigned long max = ULONG_MAX;
f o r ( i n t i = nn ; i >+1; i )
max /= i ;
i f (max < 1 )
return f a l s e ;
else
return true ;
}

ktra przyjmie warto true gdy n jest w zakresie oraz false gdy n jest zbyt
du liczb. Staa ULONG MAX (plik nagwkowy limits.h) jest rwna
maksymalnej liczbie cakowitej typu unsigned long jaki moe by uyty w
danym kompilatorze i systemie operacyjnym. Dostpne stae to:
Nazwa staej
UCHAR MAX
USHRT MAX
UINT MAX
ULONG MAX

Typ zmiennej
unsigned char
unsigned short
unsigned integer
unsigned long

Listing 11.4. Sprawdzanie granicznych wartoci; plik limits.h


2

#include <i o s t r e a m >


#include <c o n i o . h>
#include < l i m i t s . h>

using namespace s t d ;
6

10

12

14

16

18

20

bool w _ z a k r e s i e ( i n t ) ;
unsigned long s i l n i a ( i n t ) ;
i n t main ( )
{ int i ;
c o u t << " Podaj l i c z b e : " ;
c i n >> i ;
i f ( w_zakresie ( i ) )
c o u t << " S i l n i a z l i c z b y " << i << " = "
<< s i l n i a ( i ) << e n d l ;
else
c o u t << " Poza zak resem " << e n d l ;
getche () ;
return 0 ;
}

22

24

unsigned long s i l n i a ( i n t n )
{ i f ( n <= 1 )

235

236

11. Techniki obsugi bdw


return 1 ;
else
return n s i l n i a ( n 1 ) ;

26

28

30

bool w _ z a k r e s i e ( i n t nn )
{ unsigned long max = ULONG_MAX;
f o r ( i n t i = nn ; i >=1; i )
max /= i ;
i f (max < 1 )
return f a l s e ;
else
return true ;
}

32

34

36

38

Po uruchomieniu programu mamy wynik:


Podaj l i c z b e : 10
S i l n i a z l i c z b y 10 =

36288800

Podaj l i c z b e : 12
S i l n i a z l i c z b y 12 =

479001600

Podaj l i c z b e : 13
Poza zak resem

W nastpnym przykadzie pokaemy technik zapobiegajc wprowadzeniu


bdnych danych korzystajc z komunikatu jaki wysya obiekt cin. Jeeli
uytkownik nie poda wartoci liczbowej lub warto ujemn wtedy wyraenie
( ! ( c i n >> x ) | | x < 0 )

z deklaracj int x przyjmie warto true, obiekt strumienia cin zostanie


oprniony i przygotowany do kolejnej prby pobrania poprawnych danych.

Listing 11.5. Sprawdzanie stanu obiektu cin


1

#include <i o s t r e a m >


#include <math>

i n t main ( )
{
using namespace s t d ;
int x ;
c o u t << " Podaj d o d a t n i a l i c z b e c a l k o w i t a : " ;
while ( ! ( c i n >> x ) | | x < 0 )
{

237

11.4. Narzdzie assert() i funkcja abort()


11

13

15

17

19

cin . clear () ;
c i n . i g n o r e ( 1 0 0 , \n ) ;
c o u t << " Podaj d o d a t n i a l i c z b e c a l k o w i t a : " ;
}
c i n . i g n o r e ( 1 0 0 , \n ) ;
c o u t << " P i e r w i a s t e k z " << x << " = " << s q r t ( x ) ;
cin . get () ;
return 0 ;
}

Dziaanie programu moe mie posta:


Podaj d o d a t n i a l i c z b a
Podaj d o d a t n i a l i c z b a
Podaj d o d a t n i a l i c z b a
Podaj d o d a t n i a l i c z b a
Pierwiastek z 9 = 3

calkowita
calkowita
calkowita
calkowita

:
:
:
:

4
k
$
9

Dopki nie zostanie wprowadzona poprawnie dodatnia liczba cakowita program nie przystpi do obliczania pierwiastka kwadratowego (wprowadzenie
znaku powoduje bd poniewa cin oczekuje liczby cakowitej typu int, jeli
cin ma warto false, oznacza to, e wystpi bd)
while ( ! ( c i n >> x ) | | x < 0 )

Aby przywrci cin do stanu pocztkowego, tak aby ten obiekt mg ponownie przyj dane wejciowe moemy uy funkcji clear(), ktra wymazuje
znacznik bdu (ale nie czyci bufora).
cin . clear () ;

W celu odrzucenia wejcia (kasowanie zawartoci bufora) moemy uy funkcji ignore().


c i n . i g n o r e ( 1 0 0 , \n ) ;

W powyszym przykadzie bdzie odrzuconych 100 kolejno wczytanych znakw, chyba, e wczeniej bdzie napotkany znak nowego wiersza \n.

11.4. Narzdzie assert() i funkcja abort()


Zagadnienie zapobiegania bdom jest wane, dlatego te programici
korzystajcy z jzyka C i C++ maj cakiem dobre narzdzie diagnostyczne
jakim jest makro o nazwie assert(). Makro assert() pobiera wyraenie cako-

238

11. Techniki obsugi bdw


wite i je testuje. Jeeli wyraenie jest faszywe, wysyany jest komunikat bdu do standardowego wyjcia dla bdw (stderr) i wywoywana jest funkcja
abort(), ktra koczy program. W jzyku C assert() znajduje si w pliku
nagwkowym assert.h , natomiast abort() w pliku stdlib.h. Po przerwaniu
programu przez assert() wywietlony jest warunek, ktry nie zosta speniony, nazwa pliku i numer wiersza. W jzyku C++ aby stosowa asercj naley
zastosowa dyrektyw #include <cassert> lub #include<assert.h>.
Listing 11.6. Wykorzystanie asercji do obsugi bdw
1

#include
#include
#include
#include

<i o s t r e a m >
<math>
<c o n i o . h>
<a s s e r t . h>

11

13

15

17

19

i n t main ( )
{
using namespace s t d ;
int x , y ;
f l o a t m;
c o u t << " Podaj x : " ;
c i n >> x ;
c o u t << " Podaj y : " ;
c i n >> y ;
m = xx yy ;
a s s e r t (m >= 0 ) ;
c o u t << " P i e r w i a s t e k z " << m << " = " << s q r t (m) ;
getche () ;
return 0 ;
}

Jeeli w programie nie bdzie instrukcji:


a s s e r t (m >= 0 ) ;

to przykadowe wykonanie programu moe mie posta:


Podaj x : 1
Podaj y : 4
s q r t : DOMAIN e r r o r
P i e r w i a s t e k z 15 = nan

Jeeli w programie umiecimy asercje to program z tymi danym nie uruchomi si, nastpi przerwanie programu i powrt do rodowiska programistycznego.
Obsug bdu bardzo podobn do wywoania makra assert() jest wywoanie funkcji abort(). Zmodykujemy poprzedni program, rezygnujc z
assert() na rzecz zastosowania funkcji abort().

11.5. Przechwytywanie wyjtkw


Listing 11.7. Wykorzystanie funkcji abort() do obsugi bdw
2

10

12

14

16

18

20

22

#include <i o s t r e a m >


#include <math>
#include <c o n i o . h>
#include <c s t d l i b >
i n t main ( )
{
using namespace s t d ;
int x , y ;
f l o a t m;
c o u t << " Podaj x : " ;
c i n >> x ;
c o u t << " Podaj y : " ;
c i n >> y ;
m = xx yy ;
i f ( m <= 0 )
{ c o u t << " argument w s q r t ujemny " ;
abort () ;
}
c o u t << " P i e r w i a s t e k z " << m << " = " << s q r t (m) ;
getche () ;
return 0 ;
}

Zaleca si stosowanie makra assert(), poniewa w wyniku jego dziaania otrzymujemy wicej informacji na temat bdu (np. numer wiersza, w
ktrym bd wystpi).

11.5. Przechwytywanie wyjtkw


Jzyk C++ posiada bardzo wydajny mechanizm obsugi bdw, nazywany obsug wyjtkw. Obsuga wyjtkw pozwala programicie zarzdza
bdami wykonania w sposb zorganizowany, dajcy poczucie bezpieczestwa. Konstrukcje zwizane z obsug wyjtkw w jzyku C++ zwizana
jest z trzema sowami kluczowymi:
try - prbuj, testuj, sprawdzaj
throw - wyrzu, zgo, sygnalizuj, przelij
catch - zap, przechwy.
W bloku try umieszczamy instrukcje w ktrych mog wystpi wyjtki (bdy). Jeeli wystpi bd jest on zgaszany przy pomocy instrukcji
throw. Wyjtki (bdy) s wychwytywane w bloku catch, ktry znajduje
si za blokiem try. W bloku catch wyjtek jest odpowiednio przetwarzany
nastpi prba naprawienia bdu, zignorowania lub zakoczenia dziaania
programu. Jeli zostanie zgoszony wyjtek, ktry nie jest obsugiwany w
bloku catch, wykonywanie programu jest przerwane, w takim przypadku

239

240

11. Techniki obsugi bdw


wywoywana jest funkcja biblioteczna terminate(), ktra z kolei wywouje
funkcje abort(). Dla celw dydaktycznych pokaemy prosty przykad obsugi
wyjtkw. Rozwaymy program monitorujcy bezpieczestwo uczestnikw
spywu kajakowego kady kajakarz powinien posiada kamizelk ratunkow, co oznacza , e ilo kamizelek nie moe by mniejsza ni ilo kajakarzy.
Pokazany poniej program monitoruje t sytuacj. Jeeli uruchamiamy nasz
program na platformie Borlad Builder 6 naley w zakadce Tools/Debbuger
options. . . wyczy przycisk Integrated debugging.
Listing 11.8. Proste obsugiwanie wyjtkw
2

10

12

14

16

18

20

22

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
i n t main ( )
{ i n t kam , k a j ;
try
// b l o k t r y
{
c o u t << " L i c z b a k a m i z e l e k : " ;
c i n >> kam ;
c o u t << " L i c z b a k a j a k a r z y : " ;
c i n >> k a j ;
i f ( k aj >kam) throw ( k a j kam) ; // i n s t r u k c j a throw
c o u t << "Zaczynamy sply w kajakowy " ;
}
catch ( i n t t )
// b l o k c a t c h
{ c o u t << " Brak uje " << t
<<" k a m i z e l e k , sply w odwolany " ;
}
getche () ;
return 0 ;
}

Naley pamita, e demonstrowane tu programy z przykadami obsugi wyjtkw s bardzo proste i normalnie nikt nie zastosowa by takich
mechanizmw obsugi bdw. Przy spenionych warunkach bezpieczestwa
dziaanie programu moe mie posta:
Liczba kamizelek 5
Liczba kajakarzy 5
Zaczynamy sply w kajakowy

W przypadku gdy mamy wicej kajakarzy ni kamizelek, dziaanie programu


moe mie posta:
Liczba kamizelek 5
Liczba kajakarzy 7

11.5. Przechwytywanie wyjtkw


Brak uje 2 k a m i z e l e k , sply w odwolany

Przyjrzyjmy si pokazanemu programowi. Widzimy blok try :


try
{

// b l o k t r y
c o u t << " L i c z b a k a m i z e l e k : " ;
c i n >> kam ;
c o u t << " L i c z b a k a j a k a r z y : " ;
c i n >> k a j ;
i f ( k aj >kam) throw ( k a j kam) ; // i n s t r u k c j a throw
c o u t << "Zaczynamy sply w kajakowy " ;

W tym bloku znajduje si fragment kodu z obsug wyjtkw. Wewntrz


tego bloku jest instrukcja:
i f ( k aj >kam) throw ( k a j kam) ; // i n s t r u k c j a throw

Widzimy, e gdy liczba kajakarzy jest wiksza ni liczba kamizelek, dzieje


si co wyjtkowego, wykonana jest zatem instrukcja throw. Gdy liczba
kamizelek jest wiksza ni liczba kajakarzy, nie ma sytuacji wyjtkowej i
program wykonywany jest dalej.
Widzimy, e w bloku try umieszczony jest kod programu, w zalenoci od
danych wejciowych moemy otrzymywa rne wyniki, mona spodziewa
si sytuacji gdy naley podj specjalne dziaania. Jeeli co jest wyjtkowe
(w naszym przypadku naruszono zasady bezpieczestwa kajakarzy) to blok
wyrzuca (ang. throw) wyjtek, W naszym programie instrukcja throw wyrzuca liczb cakowit bdca rnic midzy liczb kajakarzy i kamizelek.
Oczywici wyrzuca mona wartoci rnych typw (w naszym przypadku
jest to warto typu int).W momencie wyrzucenia wyjtku zatrzymywane
jest wykonywanie kodu w bloku try, zaczyna si wykonywanie instrukcji
zawartych w bloku catch (ang. przechwycenie). Mwimy, e wyjtek jest
obsuony w bloku catch. W naszym programie blok catch ma posta:
catch ( i n t t )
// b l o k c a t c h
{ c o u t << " Brak uje " << t
<<" k a m i z e l e k , sply w odwolany " ;
}

Parametr t typu int w sowie kluczowym catch nosi nazw parametru bloku
catch. Parametr bloku catch ma okrelony typ, dziki czemu wiemy, jaki
zosta wyrzucony wyjtek (mona wyrzuca rne wyjtki). Po drugie, ma
on nazw, dziki czemu w bloku catch moemy na tej wartoci wykona
jakie operacje, co moe na przykad prowadzi do usunicia bdu.

241

242

11. Techniki obsugi bdw


Gdyby liczba kamizelek bya wiksza ni liczba kajakarzy, wyjtek by
nie zosta wyrzucony i wykonane byyby kolejne instrukcje bloku try. Po
wyjciu z bloku try, instrukcje zawarte w bloku catch s ominite i program
wykonuje pozostae instrukcje. Mamy wniosek - gdy nie zostaje wyrzucony
wyjtek, blok catch jest przez program ignorowany. Pamitajmy, e typ
wyjtku musi odpowiada typowi umieszczonemu w instrukcji catch. Dla
instrukcji catch z argumentem oat :
catch ( f l o a t t )
// b l o k c a t c h
{ c o u t << " Brak uje " << t
<<" k a m i z e l e k , sply w odwolany " ;
}

w pokazanym powyej programie, wyjtek nie bdzie obsuony i program


zakoczy dziaania.
Wyjtek moe zosta wyrzucony z funkcji, jeeli wywoanie funkcji jest
realizowane wewntrz bloku try, to taki wyjtek bdzie obsuony. Pokazuje
t sytuacj kolejny program.
Listing 11.9. Proste obsugiwanie wyjtkw; instrukcja throw w funkcji
2

10

12

14

16

18

20

22

24

26

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
void t e s t ( int , i n t ) ;
i n t main ( )
{ i n t kam , k a j ;
try
{ c o u t << " l i c z b a k a m i z e l e k : " ;
c i n >> kam ;
c o u t << " l i c z b a k a j a k a r z y : " ;
c i n >> k a j ;
t e s t (kam , k a j ) ;
c o u t << " zaczynamy sply w kajakowy " ;
}
catch ( i n t t )
{ c o u t << " b r a k u j e " << t
<<" k a m i z e l e k , sply w odwolany " ;
}
getche () ;
return 0 ;
}
void t e s t ( i n t x , i n t y )
{ int r ;
r = x y;
i f ( r < 0 ) throw r ;
}

11.5. Przechwytywanie wyjtkw


Blok try moe by umieszczony wewntrz funkcji.
Listing 11.10. Proste obsugiwanie wyjtkw; blok try i catch w funkcji
2

10

12

14

16

18

20

22

24

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
void t e s t ( int , i n t ) ;
i n t main ( )
{ i n t kam , k a j ;
c o u t << " l i c z b a k a m i z e l e k : " ;
c i n >> kam ;
c o u t << " l i c z b a k a j a k a r z y : " ;
c i n >> k a j ;
t e s t (kam , k a j ) ;
getche () ;
return 0 ;
}
void t e s t ( i n t x , i n t y )
{ try
{ i f ( x < y ) throw ( y x ) ;
c o u t << " zaczynamy sply w kajakowy " ;
}
catch ( i n t t )
{ c o u t << " b r a k u j e " << t
<<" k a m i z e l e k , sply w odwolany " ;
}
}

Blok try moe wyrzuca wicej ni jeden wyjtkw, wobec tego mamy
moliwo umieszczenia wielu blokw catch, naley jednak pamita, e kady taki blok catch musi przechwytywa wyjtek innego typu.
Instrukcje catch sprawdzane s w takiej kolejnoci w jakiej umieszczone
s w programie. Gdy wykonywany jest blok try moe by wyrzucony tylko
jeden wyjtek, ale tych wyjtkw moe by wiele (za kadym razem moe to
by wyjtek innego typu). Blok catch wychwytuje tylko jeden wyjtek okrelonego typu. Bloki catch umieszczone s jeden pod drugim, tak aby bya
moliwo dopasowania danego typu. Jeeli wyrzucony zostanie wyjtek,
ktrego typ nie jest obsugiwany przez aden blok catch mamy problem.
Program jest zatrzymywany awaryjnie. Naley unika takich sytuacji.
Gdy w programie przechwytujemy wiele wyjtkw, jest istotna kolejno
w jakiej umieszczamy bloki catch. Sprawdzanie s kolejne bloki catch, gdy
program natra na odpowiadajcy typ, ktry pasuje do typu wyrzuconego wyjtku, wykonywany jest ten blok. Widzimy, e dziaanie programu
jest zalene od kolejnoci wyrzucanych wyjtkw i kolejnoci poszczeglnych blokw catch. W naszym przykadzie instrukcje catch przechwytuj

243

244

11. Techniki obsugi bdw


typ cakowity (int) oraz tablic znakw (char *). Pierwszy jest wyrzucany
wyjtek typu int, blok catch z argumentem int take jest umieszczony jako
pierwszy.
Listing 11.11. Proste obsugiwanie wyjtkw; wiele blokw catch
1

#include <i o s t r e a m >


#include <c o n i o . h>

11

13

15

17

19

21

23

25

using namespace s t d ;
i n t main ( )
{ int i ;
char zn ;
c o u t << " Podaj l i c z b e nieujemna : " ;
c i n >> i ;
c o u t << " Podaj znak , q k onczy program : " ;
c i n >> zn ;
try
{ i f ( i < 0 ) throw ( i ) ;
else
c o u t << "Wprowadzona l i c z b a : " << i << e n d l ;
i f ( ( zn == q ) | | ( zn == Q ) ) throw " Koniec " ;
else
c o u t << "Wprowadzony znak : " << zn << e n d l ;
}
catch ( i n t t )
{ c o u t << " l i c z b a " << t <<" j e s t ujemna " ;
}
catch ( char n a p i s )
{ c o u t << n a p i s << " programu " ;
}
getche () ;
return 0 ;

27

29

W wyniku dziaania programu mamy nastpujce wyniki:


Podaj l i c z b e nieujemna : 5
Podaj znak , q k onczy program : w
Wprowadzona l i c z b a : 5
Wprowadzony znak : w

Zmieniajc dane wejsciowe moemy mie nastpujcy wydruk:


Podaj l i c z b e nieujemna : 3
Podaj znak , q k onczy program : t
L i c z b a 3 j e s t ujemna

Inny zestaw danych wejsciowych da wynik:

11.5. Przechwytywanie wyjtkw


Podaj l i c z b e nieujemna : 5
Podaj znak , q k onczy program : q
Wprowadzona l i c z b a : 5
Koniec programu

Na koniec wprowadzamy jeszcze inny zestaw danych:


Podaj l i c z b e nieujemna : 5
Podaj znak , q k onczy program : q
L i c z b a 5 j e s t ujemna

Analizujc wyniki widzimy, e w zalenoci od wprowadzonych danych mamy rne reakcje programu (otrzymujemy inne wyniki). W jzyku C++
mamy bardzo wygodn konstrukcj do przechwytywania wyjtkw. Jest to
blok catch, ktry przechwytuje wszystkie wyjtki. Dzieje si tak dlatego, e
nie ma podanego typu wyjtkw. Tego typu blok catch ma posta:
catch ( . . . ) {
// i n s t r u k c j e
}

Nawias, ktry posiada wewntrz trzy kropki, oznacza w takim bloku dowolny typ wyjtku. W nastpnym programie demonstrujemy dziaanie uniwersalnego bloku catch.
Listing 11.12. Proste obsugiwanie wyjtkw; uniwersalny blok catch
1

11

13

15

17

19

21

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
i n t main ( )
{ int i ;
char zn ;
c o u t << " Podaj l i c z b e : " ;
c i n >> i ;
c o u t << " Podaj znak : " ;
c i n >> zn ;
try
{ i f ( i < 0 ) throw i ;
i f ( ( zn == q ) | | ( zn == Q ) ) throw q ;
}
catch ( . . . )
{ c o u t << "Mamy w y j a t e k " << e n d l ;
}
c o u t << " Koniec " << e n d l ;
getche () ;
return 0 ;
}

245

246

11. Techniki obsugi bdw


Moemy mie nastpujcy wynik dziaania tego programu:
Podaj l i c z b e : 5
Podaj znak : w
Koniec

Dla innego zestawu danych wejciowych mamy wynik:


Podaj l i c z b e : 3
Podaj znak : t
Mamy w y j a t e k
Koniec

Przechwycenie drugiego wyjtku pokazane jest dla nowego zestawu danych:


Podaj l i c z b e : 7
Podaj znak : q
Mamy w y j a t e k
Koniec

Analizujc ten program, widzimy, e pojedynczy blok catch obsuguje wszystkie wyjtki. Gdy to moliwe zaleca si stosowanie tego uniwersalnego bloku
catch. Ma to jeszcze inn istotn zalet dziki przechwyceniu wszystkich
wyjtkw nie mamy awaryjnego zatrzymania programu, gdy wystpi nieobsugiwany bd. Jak ju pokazalimy, funkcja moe wyrzuca wyjtki. W
wielu przypadkach naley ograniczy typy wyjtkw jakie moe wyrzuca
funkcja, mona nawet zakaza funkcji wyrzucania jakichkolwiek wyjtkw.
Aby wprowadzi ten mechanizm musimy zmodykowa prototyp (oraz denicj ) naszej funkcji:
zwracany typ nazwa funkcji (lista argumentw) throw (lista typw)
W takiej sytuacji funkcja moe wyrzuci tylko takie wyjtki, ktrych typy
znajduj si na licie typw. Jeeli na licie typw nie ma adnego typu (lista
jest pusta) to funkcja nie moe zwrci adnego wyjtku. Naley pamita,
e gdy funkcja bdzie prbowaa wyrzuci wyjtek nieobsugiwanego typu
natychmiast zostanie wywoana funkcja biblioteczne unexpected(), ktra
wywoa funkcje abort(), ktra koczy wykonywanie programu.
Listing 11.13. Ograniczenia typw wyjtkw zgaszanych przez funkcj
2

#include <i o s t r e a m >


#include <c o n i o . h>
void t e s t ( int , char ) throw ( int , char , f l o a t ) ;
using namespace s t d ;
i n t main ( )
{ int i ;
char zn ;

247

11.5. Przechwytywanie wyjtkw


8

10

12

14

16

18

20

22

24

26

28

c o u t << " Podaj l i c z b e : " ;


c i n >> i ;
c o u t << " Podaj znak : " ;
c i n >> zn ;
try
{ t e s t ( i , zn ) ;
}
catch ( i n t n )
{ c o u t << "Mamy w y j a t e k typu i n t n= " << n <<e n d l ; }
catch ( char z z )
{ c o u t << "Mamy w y j a t e k typu c h a r c = " << z z << e n d l ; }
catch ( f l o a t x )
{ c o u t << "Mamy w y j a t e k typu f l o a t x= " << x << e n d l ; }
getche () ;
return 0 ;
}
void t e s t ( i n t i , char c ) throw ( int , char , f l o a t )
{
float x = 0.5 i ;
if
( i == 0 ) throw i ;
i f ( c == q ) throw Q ;
i f ( x < 1 ) throw x ;
}

Po uruchomieniu tego programu mamy wynik:


Podaj l i c z b e : 1
Podaj znak : s
Mamy w y j a t e k typu f l o a t x = 0 . 5

Z innym zestawem danych otrzymamy:


Podaj l i c z b e : 0
Podaj znak : r
Mamy w y j a t e k typu i n t n = 0

Widzimy, e pokazana funkcja test() poprawnie zgasza wyjtki obsugiwane


przez bloki catch w funkcji main(). Poniewa wyjtek moe by dowolnego
typu, powszechn praktyka jest deniowanie klas, ktrych obiekty bd zawiera dokadne informacje, ktre s przeznaczone do wyrzucenia a nastpnie do obsuenia przez instrukcj catch. W takich przypadkach mwimy, e
tworzymy klasy wyjtkw.
W kolejnym przykadzie pokazujemy proste wykorzystanie klasy do obsugi wyjtkw. W programie obliczamy redni harmoniczn (zmienna sh):
sh = 2.0 a b/(a + b);

(11.2)

Gdy liczba a = b, otrzymamy zero w mianowniku i prb dzielenia przez


zero, naszym zadaniem jest obsuenie tego bdu.

248

11. Techniki obsugi bdw


Listing 11.14. Wykorzystanie klasy wyjtkw
1

11

13

15

17

19

21

23

25

27

29

31

33

35

37

39

41

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
c l a s s zly_wynik
{
private :
int x , y ;
public :
zly_wynik ( ) { } ;
zly_wynik ( i n t xx , i n t yy ) : x ( xx ) , y ( yy ) { } ;
void i n f o ( ) ;
};
void zly_wynik : : i n f o ( )
{ c o u t << " x= " << x << " " << " y= " << y
<< e n d l ;
c o u t << " Z l e dane x=y " << e n d l ;
}
i n t main ( )
{ int a , b ;
f l o a t sh ;
c o u t << " Podaj dwie l i c z b y : " ;
while ( c i n >> a >> b )
{ try
{ i f ( a == b )
throw zly_wynik ( a , b ) ;
sh = 2 . 0 a b / ( a+b ) ;
c o u t << " s r e d n i a = " << sh << e n d l ;
c o u t << " Podaj k o l e j n e dwie l i c z b y , q konczy , : "
<< e n d l ;
}
catch ( zly_wynik e r )
{ c o u t << "mamy w y j a t e k " << e n d l ;
er . i n f o () ;
c o u t << " podaj k o l e j n e l i c z b y . \ n" ;
}
}
getche () ;
return 0 ;
}

Po uruchomieniu tego programu mamy testowe wyniki:


Podaj dwie l i c z b y : 1
2
S r e d n i a harmoniczna = 1 . 3 3 3 3 3
Podaj k o l e j n e dwie l i c z b y , q konczy , :

11.5. Przechwytywanie wyjtkw


1
1
Mamy w y j a t e k
x = 1
y = 1
Z l e dane x = y
Podaj k o l e j n e l i c z b y
q

Klasa wyjtku ma posta:


c l a s s zly_wynik
{ private :
int x , y ;
public :
zly_wynik ( ) { } ;
zly_wynik ( i n t xx , i n t yy ) : x ( xx ) , y ( yy ) { } ;
void i n f o ( ) ;
};

Obiekt zy wynik jest inicjalizowany w funkcji main() przy pomocy skadni:


i f ( a == b )
throw zly_wynik ( a , b ) ;

Jest to klasyczne wywoanie konstruktora dwuargumentowego klasy zy wynik


i inicjalizacja obiektu za pomoc przekazanych argumentw.
Wyjtek obsugiwany jest w bloku catch:
catch ( zly_wynik e r )
{ c o u t << "mamy w y j a t e k " << e n d l ;
er . i n f o () ;
c o u t << " podaj k o l e j n e l i c z b y . \ n" ;
}

Metoda info() klasy zy wynik wywoana na rzecz obiektu er dostarczy moe detalicznych informacji o wystpujcym wyjtku.Pokazany poniej program jest inn modykacj programu wyliczajcego redni harmoniczn.
Listing 11.15. Wykorzystanie klasy wyjtkw
1

#include <i o s t r e a m >


#include <s t r i n g . h>
#include <c o n i o . h>
using namespace s t d ;
c l a s s Wyjatek
{ public :
int x , y ;
char k [ 8 0 ] ;
Wyjatek ( ) ;

249

250

11. Techniki obsugi bdw

11

13

15

17

19

21

23

25

27

29

31

33

35

37

39

Wyjatek ( int , int , char ) ;


};
Wyjatek : : Wyjatek ( )
{x =0;
y =0;
k = 0 ;
}
Wyjatek : : Wyjatek ( i n t xx , i n t yy , char kk )
{ s t r c p y ( k , kk ) ; x = xx ; y = yy ;
}
i n t main ( )
{ int x , y ;
f l o a t sh ;
c o u t << " Podaj l i c z b e x : " ;
c i n >> x ;
c o u t << " Podaj l i c z b e y : " ;
c i n >> y ;
try
{ i f ( x == y )
throw Wyjatek ( x , y , " Niepoprawne argumenty " ) ;
sh = 2 . 0 xy / ( x+y ) ;
c o u t << " S r e d n i a harmoniczna z l i c z b "
<<" = " << sh ;
}
catch ( Wyjatek e r r )
{ c o u t << e r r . k << " "<< e n d l ;
c o u t << "x= " << e r r . x << " y= "
<< e r r . y << e n d l ;
c o u t << " x=y" << e n d l ;
}
getche () ;
return 0 ;
}

Po uruchomieniu programu ze zymi liczbami mamy wynik:


Podaj l i c z b e x : 5
Podaj l i c z b e y : 5
Niepoprawne argumenty
x = 5
y = 5
x = y

Kolejna modykacja pokazuje wykorzystanie klasy wyjtkw, ktre wyrzucane s przez funkcj usugow.
Listing 11.16. Klasy wyjtkw; funkcja ze specykacj wyjtkw
1

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
c l a s s zly_wynik
{
int x , y ;
public :

11.5. Przechwytywanie wyjtkw


7

11

13

15

17

19

21

23

25

27

29

31

33

35

37

39

41

zly_wynik ( i n t xx=0 , i n t yy = 0 )
: x ( xx ) , y ( yy ) {}
void i n f o ( ) ;
};
void zly_wynik : : i n f o ( )
{ c o u t << " x= " << x << " " << " y = "
<< y << e n d l ;
c o u t << " Z l e dane poniewaz x=y "
<< e n d l ;
}
double sr_harmon ( double , double ) throw ( zly_wynik ) ;
i n t main ( )
{ double a , b , sh ;
c o u t << " Podaj dwie l i c z b y : " ;
while ( c i n >> a >>b )
{ try
{
sh = sr_harmon ( a , b ) ;
c o u t << " S r e d n i a = " << sh << e n d l ;
c o u t << " Podaj k o l e j n a p a r e l i c z b ,
w aby s k o n c z y c : " ;
}
catch ( zly_wynik & e r )
{ er . i n f o () ;
c o u t << " Podaj k o l e j n e l i c z b y . \ n" ;
}
}
getche () ;
return 0 ;
}
double sr_harmon ( double x , double y ) throw ( zly_wynik )
{ i f ( x == y )
throw zly_wynik ( x , y ) ;
return 2 . 0 x y / ( x+y ) ;
}

Po uruchomieniu programu moemy mie komunikat:


Podaj dwie l i c z b y : 3
3
x = 3
y = 3
Z l e dane poniewaz x = y
Podaj k o l e j n e l i c z b y ,
q

W naszym przykadzie klasa wyjtkw ma posta:


c l a s s zly_wynik
{
int x , y ;

251

252

11. Techniki obsugi bdw


public :
zly_wynik ( i n t xx=0 , i n t yy = 0 ) : x ( xx ) , y ( yy ) {}
void i n f o ( ) ;
};

Obiekt zy wynik jest inicjalizowany za pomoc argumentw przekazanych


do funkcji sr harmon(). W deklaracji tej funkcji usugowej mamy dodan
specykacj wyjtkw dziki ktrej okrelamy jakie wyjtki ta funkcja bdzie wyrzuca:
double sr_harmon ( double , double ) throw ( zly_wynik ) ;

Funkcja sr harmon() wyrzuca wyjtki przy pomocy instrukcji:


i f ( x == y )
throw zly_wynik ( x , y ) ;

W tej instrukcji mamy klasyczne wywoanie konstruktora klasy zy wynik


i inicjalizacje obiektu za pomoc przekazanych argumentw. Blok try ma
posta:
try
{
sh = sr_harmon ( a , b ) ;
c o u t << " S r e d n i a = " << sh << e n d l ;
c o u t << " Podaj k o l e j n a p a r e l i c z b ,
w aby s k o n c z y c : " ;
}

W tym bloku wywoywana jest funkcja usugowa sr harmon(), ktra moe


wyrzuci wyjtek. Wyjtek przechwytuje blok catch:
catch ( zly_wynik & e r )
{ er . i n f o () ;
c o u t << " Podaj k o l e j n e l i c z b y . \ n" ;
}

W tym bloku wywoana jest funkcja info(), ktra jest metod klasy wyjtkw zy wynik. Poniewa preferowanym sposobem reprezentacji klasy wyjtkw jest uycie klasy wyjtkw omwimy kolejn prost aplikacj do
wykrycia bdu dzielenia przez zero.
Listing 11.17. Klasy wyjtkw; prosty przykad
2

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;

253

11.5. Przechwytywanie wyjtkw

10

12

14

16

c l a s s MianownikZero
{ int x , y ;
public :
MianownikZero ( ) : x ( 0 ) , y ( 0 ) { }
MianownikZero ( i n t a , i n t b ) : x ( a ) , y ( b ) { }
void komunikat ( )
{ c o u t << " proba d z i e l e n i a p r z e z z e r o " << e n d l ;
}
void pokaz ( )
{ c o u t << " d z i e l n a = " << x << " d z i e l n i k = "
<< y << e n d l ;
}
};

18

20

22

24

26

28

30

32

34

36

38

double d z i e l e n i e ( i n t l i , i n t mi ) throw ( MianownikZero )


{ i f ( mi == 0 ) throw MianownikZero ( l i , mi ) ;
return ( double ) l i /mi ;
}
i n t main ( )
{
i n t l i c z n i k , mianownik ;
double wynik ;
c o u t << " podaj dwie l i c z b y c a l k o w i t e : " ;
while ( c i n >> l i c z n i k >> mianownik )
{ try
{ wynik = d z i e l e n i e ( l i c z n i k , mianownik ) ;
c o u t << " wynik = " << wynik << e n d l ;
}
catch ( MianownikZero &e r )
{ e r . komunikat ( ) ;
e r . pokaz ( ) ;
}
c o u t << " podaj dwie l i c z b y c a l k o w i t e , q k onczy : " ;
}

40

42

getche () ;
return 0 ;
}

Wynik dziaania programu jest nastpujcy:


podaj dwie l i c z b y c a l k o w i t e
wynik = 0 . 5
podaj dwie l i c z b y c a l k o w i t e ,
wynik = 0 . 3 3 3 3 3 3
podaj dwie l i c z b y c a l k o w i t e ,
proba d z i e l e n i a p r z e z z e r o
dzielna = 1
dzielnik = 0
podaj dwie l i c z b y c a l k o w i t e ,

: 3 6
q k onczy : 1 3
q k onczy : 1 0

q k onczy : q

254

11. Techniki obsugi bdw


Klasa o nazwie MianownikZero jest klas wyjtkw, bdzie ona wykorzystana do wykrycia bdu dzielenia przez zero:
c l a s s MianownikZero
{
int x , y ;
public :
MianownikZero ( ) : x ( 0 ) , y ( 0 ) { }
MianownikZero ( i n t a , i n t b ) : x ( a ) , y ( b ) { }
void komunikat ( )
{ c o u t << " proba d z i e l e n i a p r z e z z e r o " << e n d l ;
}
};

W klasie mamy dwie dane prywatne x i y, wiemy take, e gdy chcemy


wykona dzielenie x/y, to y nie moe by zerem. W pokazanej klasie mamy
dwa konstruktory oraz funkcj komunikat(), ktra wywietla ostrzeenie o
wystpieniu bdu i funkcj pokaz(), ktra wywietla wartoci dwch wprowadzonych liczb. W programie wystpuje funkcja dzielenie():
double d z i e l e n i e ( i n t l i , i n t mi ) throw ( MianownikZero )
{ i f ( mi == 0 ) throw MianownikZero ( l i , mi ) ;
return ( double ) l i /mi ;
}

Funkcja najpierw sprawdza, czy mianownik jest rwny zero. Gdy jest to
prawda wyrzuca wyjtek typu MianownikZero, gdy mianownik jest rny
od zera wykonuje dzielenie i wynik jest zwracany do programu. W funkcji
main() mamy sekcj try catch:
try
{ wynik = d z i e l e n i e ( l i c z n i k , mianownik ) ;
c o u t << " wynik = " << wynik << e n d l ;
}
catch ( MianownikZero &e r )
{ e r . komunikat ( ) ;
}

W tym bloku nastpuje wywoanie funkcji dzielenie(). Jak wida z denicji


tej funkcji, zwraca ona wyjtek :
i f ( mi == 0 ) throw MianownikZero ( l i , mi ) ;

Jest on wykorzystywany w klauzuli catch:


catch ( MianownikZero &e r )
{ e r . komunikat ( ) ;
}

11.5. Przechwytywanie wyjtkw


Argumentem klauzuli catch jest referencja do obiektu wyjtku.
Pokazany program ma do rozbudowan klas wyjtkw, moemy j
uproci, tak jak to pokazano w kolejnym przykadzie.
Listing 11.18. Klasy wyjtkw; prosty przykad
1

11

13

15

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
c l a s s MianownikZero
{ char komunikat ;
public :
MianownikZero ( )
: komunikat ( " proba d z i e l e n i a p r z e z z e r o " ) { }
char kom ( ) { return komunikat ; }
};
double d z i e l e n i e ( i n t l i , i n t mi )
{ i f ( mi == 0 ) throw MianownikZero ( ) ;
return ( double ) l i /mi ;
}

17

19

21

23

25

27

29

31

i n t main ( )
{
i n t l i c z n i k , mianownik ;
double wynik ;
c o u t << " podaj dwie l i c z b y c a l k o w i t e : " ;
while ( c i n >> l i c z n i k >> mianownik )
{ try
{ wynik = d z i e l e n i e ( l i c z n i k , mianownik ) ;
c o u t << " wynik = " << wynik << e n d l ;
}
catch ( MianownikZero e r )
{ c o u t << e r . kom ( ) << e n d l ;
}
c o u t << " podaj dwie l i c z b y c a l k o w i t e , q k onczy : " ;
}

33

getche () ;
return 0 ;

35

W naszym przykadzie klasa wyjtkw o nazwie MianownikZero ma posta:


c l a s s MianownikZero
{ char komunikat ;
public :
MianownikZero ( )

255

256

11. Techniki obsugi bdw


: komunikat ( " proba d z i e l e n i a p r z e z z e r o " ) { }
char kom ( ) { return komunikat ; }
};

Klasa zawiera prywatn zmienn napisow, publiczn funkcj skadow oraz


konstruktor, ktry wskazuje pole komunikatu z napisem proba dzielenia
przez zero. Blok try catch otacza kod ktry moe zwrci wyjtek:
try
{ wynik = d z i e l e n i e ( l i c z n i k , mianownik ) ;
c o u t << " wynik = " << wynik << e n d l ;
}
catch ( MianownikZero e r )
{ c o u t << e r . kom ( ) << e n d l ;
}

Zwrmy uwag na fakt, e wyjtek moe by zgoszony w wywoywanej


funkcji dzielenie(). Gdy wystpi prba dzielenia przez zero, funkcja dzielenie() wyrzuci wyjtek, ktry bdzie zgoszony przez blok try. Ten wyjtek
zostaje wychwycony przez blok catch, ktry okrela odpowiedni typ dopasowany do zgoszonego wyjtku. W naszym programie wyjtek powinien
by typu MianownikZero. Gdy instrukcja if w funkcji dzielenie() wykryje,
e prbujemy podzieli przez zero, wygeneruje instrukcj throw, okrelajc
nazw konstruktora dla obiektu wyjtku. Zostanie utworzony wyjtek klasy
MianownikZero. Instrukcja catch przechwyci ten obiekt. Zgoszony obiekt
odbierany jest w argumencie okrelonym w procedurze obsugi catch:
catch ( MianownikZero e r )
{ c o u t << e r . kom ( ) << e n d l ;
}

co powoduje wywietlenia napisu z komunikatem o wystpieniu prby dzielenia przez zero.


Nic nie stoi na przeszkodzie aby wykorzysta dziedziczenie podczas stosowania wyjtkw. Rne klasy wyjtkw mog by klasami pochodnymi wsplnej klasy podstawowej, klasy wyjtkw mog tworzy hierarchie.
Oznacza to, e klauzula catch wychwyci moe wskanik lub referencj do
wszystkich obiektw wyjtkw klas pochodnych.
Kolejny przykad demonstrujcy wykorzystanie dziedziczenia do obsugi wyjtkw wzorowany jest na przykadzie pokazanym w monograi V.
Sheterna pt. C++ inynieria oprogramowania. Jak to bywa w klasycznych podrcznikach wykorzystano zadanie obliczania wartoci uamka po
wprowadzeniu z klawiatury licznika i mianownika.

257

11.5. Przechwytywanie wyjtkw


Listing 11.19. Klasy wyjtkw; dziedziczenie
1

#include <i o s t r e a m >


#include < l i m i t s . h>
#include <c o n i o . h>
using namespace s t d ;

//LONG_MAX

11

13

15

17

class Info
{
s t a t i c char t e k s t [ ] ;
public :
s t a t i c char kom( i n t n )
{ return t e k s t [ n ] ;
}
} ;
char I n f o : : t e k s t [ ] = { " mianownik rowny z e r o \n " ,
" mianownik ujemny " ,
" \n podaj l i c z n i k i nieujemny mianownik : " ,
" \n ( podaj q aby z a k o n c z y c ) " ,
" w a r t o s c ulamka : "
} ;

19

21

23

25

c l a s s MianownikZero
{ protected :
char kom ;
public :
MianownikZero ( char wiad ) : kom( wiad )
void pokaz ( ) { c o u t << kom ; }
};

27

29

31

33

c l a s s MianownikUjemny : public MianownikZero


{
long war ;
public :
MianownikUjemny ( char wiad , long w)
: MianownikZero ( wiad ) , war (w) { }
void pokaz ( ) { c o u t << kom << war << e n d l ; }
};

35

37

39

41

43

45

47

49

i n l i n e void odwrot ( long w, double & wynik )


throw ( MianownikZero , MianownikUjemny)
{ wynik = (w) ? 1 . 0 /w : LONG_MAX;
i f ( wynik == LONG_MAX)
throw MianownikZero ( I n f o : : kom ( 0 ) ) ;
i f (w < 0 )
throw MianownikUjemny ( I n f o : : kom ( 1 ) , w) ;
}
i n l i n e void ulamek ( long l i c z n i k , long mianownik ,
double & w a r t o s c )
throw ( MianownikZero , MianownikUjemny)
{ odwrot ( mianownik , w a r t o s c ) ;
wartosc = l i c z n i k wartosc ;
}

258

11. Techniki obsugi bdw


51

53

55

57

59

61

63

65

67

69

i n t main ( )
{ while ( true )
{ long l i c z n i k , mianownik ;
double u l ;
c o u t << I n f o : : kom ( 3 ) << I n f o : : kom ( 2 ) ;
i f ( ( c i n >> l i c z n i k >> mianownik ) == 0 )
try
{ ulamek ( l i c z n i k , mianownik , u l ) ;
c o u t << I n f o : : kom ( 4 ) << u l << e n d l ;
}
catch ( MianownikUjemny & neg )
{ neg . pokaz ( ) ;
}
catch ( MianownikZero & z e r )
{ z e r . pokaz ( ) ;
}
}
getche () ;
return 0 ;
}

break ;

Przykadowe dziaanie programu ma posta:


( podaj q aby z a k o n c z y c )
podaj l i c z n i k i nieujemny mianownik : 1 0
mianownik rowny z e r o
( podaj q aby z a k o n c z y c )
podaj l i c z n i k i nieujemny mianownik : 1 1
mianownik ujemny 1
( podaj q aby z a k o n c z y c )
podaj l i c z n i k i nieujemny mianownik : 1 3
w a r t o s c ulamka : 0 . 3 3 3 3 3 3
( podaj q aby z a k o n c z y c )
podaj l i c z n i k i nieujemny mianownik : 1 9
w a r t o s c ulamka : 0.111111
( podaj q aby z a k o n c z y c )
podaj l i c z n i k i nieujemny mianownik : q

W programie do obliczenia wartoci uamka wykorzystujemy dwie funkcje:


i n l i n e void odwrot ( long w, double & wynik )
throw ( MianownikZero , MianownikUjemny)
{ wynik = (w) ? 1 . 0 /w : LONG_MAX;
i f ( wynik == LONG_MAX)
throw MianownikZero ( I n f o : : kom ( 0 ) ) ;
i f (w < 0 )

11.5. Przechwytywanie wyjtkw


throw MianownikUjemny( I n f o : : kom ( 1 ) , w) ;
}
i n l i n e void ulamek ( long l i c z n i k , long mianownik ,
double & w a r t o s c )
throw ( MianownikZero , MianownikUjemny )
{
odwrot ( mianownik , w a r t o s c ) ;
wartosc = l i c z n i k wartosc ;
}

Funkcja odwrot() zwraca odwrotno argumentu. Przy argumencie rnym


od zera, funkcja oblicza jego odwrotno (zmienna wynik). Gdy argumentem
jest zero, zmienna wynik przyjmuje warto LONG MAX ( staa zdeniowana w pliku limits.h). Wtedy wyrzucany jest wyjtek stwierdzajcy, e
mianownika ma warto zero.
Gdy argument ma warto ujemn (z powodw technicznych przy obsudze uamkw lepiej unika ujemnego mianownika) generowany jest wyjtek sygnalizujcy, e mianownik ma warto ujemn. Funkcja odwrot()
jest wywoywana przez funkcj ulamek(). W tej funkcji nastpuje mnoenie wartoci jej pierwszego argumentu przez warto obliczon w funkcji
odwrot() (czyli odwrotno mianownika). Podczas wykonywania programu
zachodzi konieczno wykorzystania kilku komunikatw (napisw), ktre s
wywietlane na ekranie. Aby usprawni ich obsug, zostay one zgrupowane w klasie Info, i zdeniowane w postaci prywatnej statycznej tablicy
acuchw znakowych tekst[ ] :
class Info
{
s t a t i c char t e k s t [ ] ;
public :
s t a t i c char kom( i n t n )
}
{ return t e k s t [ n ] ;
} ;
char I n f o : : t e k s t [ ] = { " mianownik rowny z e r o \n " ,
" mianownik ujemny " ,
" \n podaj l i c z n i k i nieujemny mianownik : " ,
" \n ( podaj q aby z a k o n c z y c ) " ,
" w a r t o s c ulamka : "
} ;

Do konkretnego komunikatu odwoujemy si przy pomocy funkcji statycznej


o nazwie kom(), ktrej argumentem jest indeks odpowiedniego komunikatu tablicy tekst[ ]. Czsto podczas projektowania obsugi wyjtkw naley
ustali jakie dane powinny zosta wysane do klasy, ktrej obiekty bd

259

260

11. Techniki obsugi bdw


obsugiway wystpienie bdu. Metody takiej klasy powinny dopuszcza
dostp do danych obiektu wewntrz bloku catch. W naszym przykadzie
klasa MianownikZero obsuguje informacje o prbie dzielenia przez zero,
klasa MianownikUjemny obsuguje wystpienie ujemnego mianownika. W
naszym przypadku mona zaprojektowa hierarchi klas. Klasa MianownikZero jest klas bazow, klasa MianowniUjemny dziedziczy po tej klasie (jest
klas pochodn).
Klasa bazowa ma posta:
c l a s s MianownikZero
{ protected :
char kom ;
public :
MianownikZero ( char wiad ) : kom( wiad )
void pokaz ( ) { c o u t << kom ; }
};

Klasa MianownikUjemny wywodzi si z klasy MianownikZero:


c l a s s MianownikUjemny : public MianownikZero
{
long war ;
public :
MianownikUjemny ( char wiad , long w)
: MianownikZero ( wiad ) , war (w) { }
void pokaz ( ) { c o u t << kom << war << e n d l ; }
};

W funkcji main() wywoywana jest funkcja ulamek() wewntrz bloku try:


try
{ ulamek ( l i c z n i k , mianownik , u l ) ;
c o u t << I n f o : : kom ( 4 ) << u l << e n d l ;
}
catch ( MianownikUjemny & neg )
{ neg . pokaz ( ) ;
}
catch ( MianownikZero & z e r )
{ z e r . pokaz ( ) ;
}

Gdy podczas wykonywania funkcji ulamek() wystpi bd, wyjtek przechwytywany jest przez kolejne bloki catch.
Obecnie, wyjtki s w zasadzie czci jzyka. Plik nagwkowy exception deniuje klas exception, ktra jest klas bazow dla innych klas wyjtkw. Klasa ta posiada funkcj skadow what(), ktra wykonuje operacje na
obiektach tej klasy. Wirtualna funkcja skadowa what() zwraca cig znakw,
zaleny od implementacji, ten cig znakw opisuje wyjtek. Ponisza tabela

261

11.5. Przechwytywanie wyjtkw


pokazuje hierarchi standardowych klas wyjtkw. Klas bazow jest klasa
exception.

exception <

bad alloc
bad cast
bad typeid
logic error
ios base::failure
runtaim error
bad exception

Klas pochodna logic error jest klas nadrzdn dla kolejnych klas pochodnych. Kolejna tabela pokazuje standardowe klasy wyjtkw dziedziczcych z tej klasy.

logic error <

domain error
invalid argument
length error
out of range

Podobnie klasa runtime error jest klas nadrzdn dla innych klas pochodnych, hierarchia pokazana jest w kolejnej tabeli.

runtime error <

range error
overow error
underow error

Klasy wyjtkw zdeniowane s w rnych plikach nagwkowych. Informacje o najczciej uywanych klasach pokazuje kolejna tabela.

262

11. Techniki obsugi bdw


nr
1

klasa
bad alloc

plik
<new>

bad cast

<typeinfo>

bad typeid

<typeinfo>

bad exception

<exception>

ios::failure

<ios>

Opis
Wyjtek zgaszany przez operator new gdy nie uda si
przydzia pamici
Wyjtek zgaszany przez operator dynamic cast gdy nie
udaa si konwersja
Wyjtek zgaszany przez operator typeid, gdy wskanik bdcy jego argumentem jest
pusty
Wyjtek zgaszany gdy pojawi si nieznany wyjtek
Wyjtek zgaszany, gdy zmieni si stan strumienia w niepodany sposb

Dynamiczny przydzia pamici jest wan operacj, zaley nam aby si


powioda. Mamy dwa sposoby obsugi bdu przydziau pamici. Moemy
sprawdzi wskanik, gdy jest pusty, przydzia pamici nie powid si. Do
koczenia programu czsto korzystano z funkcji assert. Gdy warto zwracana przez wywoanie new wynosi 0, makroinstrukcja assert koczy program.
Jest to stosunkowo dobre rozwizanie, ale nie pozwala na powrt do normalnego stanu. Standard jzyka C++ okrela, e gdy alokacja pamici tworzona
operatorem new nie powiedzie si, zgaszany jest wyjtek bad alloc. W kolejnym programie pokazano obsug bdu przydziau pamici za pomoc
zgoszenia wyjtku.W ptli for, ktra zamknita jest wewntrz bloku try,
chcemy wykona 100 ptli i przydzieli w kadym przejciu 5000000 wartoci
typu double. Prdzej czy pniej moe zabrakn pamici i wtedy zostanie
zgoszony wyjtek bad alloc, ptla koczy dziaanie. Sterowanie programem
przekazywane jest to klauzuli catch , ktra wychwytuje wyjtek i go przetwarza:
catch ( b a d _ a l l o c w y j a t e k )
{ c o u t << " o b s l u g a wyjatku : " << w y j a t e k . what ( )
<< e n d l ;
}

Metoda what() zwraca komunikat, zaleny od wyjtku. W naszym przypadku jest to komunikat:
bad a l l o c e x c e p t i o n thrown

11.5. Przechwytywanie wyjtkw


Listing 11.20. Klasy wyjtkw; wywoanie new; zgoszenie bad alloc
1

#include <i o s t r e a m >


#include <c o n i o . h>
#include <new>

using namespace s t d ;

i n t main ( )
{double w [ 1 0 0 ] ;
long l i c z n i k = 0 ;
try
{ f o r ( i n t i =0; i <100; i ++)
{w [ i ] = new double [ 5 0 0 0 0 0 0 ] ;
++l i c z n i k ;
}
}
catch ( b a d _ a l l o c w y j a t e k )
{ c o u t << " o b s l u g a wyjatku : " << w y j a t e k . what ( ) << e n d l ;
}

11

13

15

17

19

c o u t <<" u d a l o s i e p r z y d z i e l i c " << l i c z n i k 1 <<" blokow "


<< e n d l ;
i n t r = s i z e o f ( double ) ;
c o u t <<" r o z m i a r d o u b l e t o : " << r << " bajtow " <<e n d l ;
c o u t <<"w sumie p r z y d z i e l o n o : "<< ( l i c z n i k 1) 5000000 r
<< " bajtow "<<e n d l ;
getche () ;
return 0 ;

21

23

25

27

Po uruchomieniu tego programu mamy nastpujcy wydruk:


o b s l u g a wyjatku : bad
udalo s i e p r z y d z i e l i c
r o z m i a r double t o : 8
w sumie p r z y d z i e l o n o :

a l l o c e x c e p t i o n thrown
48 blokow
bajtow
1920000000 bajtow

Wynik wykonania pokazanego programu oczywicie bdzie rny na rnych systemach. Zalee bdzie od iloci bajtw uytych do kodowania typu
double i iloci dostpnej pamici (zycznej pamici i przestrzeni dyskowej
dostpnej dla pamici wirtualnej).

263

Rozdzia 12
Szablony w C++

12.1.
12.2.
12.3.
12.4.

Wstp . . . . . . . .
Przecianie funkcji
Szablony funkcji . .
Szablony klas . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

266
266
276
292

266

12. Szablony w C++

12.1. Wstp
W jzyku C++ istnieje cisa kontrola zgodnoci typw. Czasami taka
cisa kontrola typw (ktra generalnie jest zalet) powoduje znaczne komplikacje przy tworzeniu wydajnego oprogramowania. Gdy na przykad opracujemy funkcj pobierajc argumenty typu int, to taka funkcja nie bdzie
poprawnie obsugiwaa argumentw typu double. Dla obsugi argumentw
innego typu musimy opracowa inn funkcj. Jzyk C++ oby omin to
ograniczenie cile zwizane z kontrol typw wprowadza szablony, czasami
zwane w literaturze wzorcami (ang. template). Przy pomocy kodu jednej
funkcji lub jednej klasy moemy obsugiwa dane rnych typw. Moemy
opracowa pojedynczy szablon (wzorzec) funkcji sortujcej tablice z danymi
typu int, double, char czy nawet napisw. Podobnie moemy pisa uniwersalne szablony klas.
Mechanizmy wzorcw zostay szczegowo opisane i omwione w pracy Bjarnea Stroustrupa w 1998 roku w pracy pod tytuem Parametrized
Types for C++.
Szablony, prawdopodobnie ze wzgldu na szerokie rozpowszechnienie si
zastosowa biblioteki szablonw STL (ang. Standard Template Library)
uwaane s za jedn z najwaniejszych waciwoci jzyka C++. Dziki
szablonom mamy moliwoci programowania oglnego (ang. generic programming). Tworzc szablon funkcji (podobnie jak szablon klasy) tworzymy
kod dziaajcy na nieopisanych jeszcze typach funkcja dziaa na typach
oglnych (ktre nie istniej w jzyku C++), w momencie wywoania funkcji pod te typy oglne podstawiamy konkretne typy, jak np. int czy char.
Przekazujemy szablonowi typy jako parametry, kompilator generuje funkcje
konkretnego typu.

12.2. Przecianie funkcji


Przecianie funkcji (inna nazwa polimorzm funkcji) jest bardzo eleganckim rozszerzeniem jzyka C++ wzgldem C. Dziki przecianiu funkcji
moemy zdeniowa ca rodzin funkcji realizujcych takie same zadania
ale dla rnych argumentw. Nie naley myli przeciania funkcji z szablonami funkcji. Formalnie jzyk C++ pozwala na deniowanie wielu funkcji
o tej samej nazwie, pod warunkiem, e bd miay rne zestawy argumentw. Kluczem do przeciania funkcji jest lista argumentw i zwracany typ.
Dla kadej funkcji kompilator tworzy tzw. sygnatur funkcji, koduje nazw
funkcji oraz liczb i typy jej parametrw ( nazywamy to znieksztaceniem
nazwy, ozdabianiem nazwy lub dekorowaniem funkcji). Jzyk C++ pozwala
nam deniowa funkcje o takich samych nazwach ale o rnych sygnaturach.

12.2. Przecianie funkcji


i n t kwadrat ( i n t x ) ;
i n t kwadrat ( i n t &x ) ;

argumenty przekazywane do funkcji s rne, ale nie mamy tu do czynienia z


przecianiem funkcji poniewa z punktu widzenia kompilatora wywoanie:
kwadrat ( x ) ;

pasuje do prototypu int x oraz do prototypu &x. W takiej sytuacji kompilator raczej nie jest w stanie zadecydowa jakiej wersji funkcji kwadrat() ma
uy. Kompilator traktuje referencje do danego typu i sam typ jako rwnowane. Podobnie, z punktu widzenia kompilatora nastpujce deklaracje s
rwnowane:
i n t suma ( i n t x ) ;
i n t suma ( i n t x [ ] ) ;

Dla kompilatora *x i x[ ] oznacza to samo.


Naley te mie na uwadze, e o przecieniu funkcji decyduje sygnatura
a nie typ funkcji. Nie moemy przeciy funkcji:
i n t suma ( i n t a , double b )
double suma ( i n t a , double b )

poniewa maj takie same sygnatury. Przeciy moemy nastpujcy zestaw funkcji:
i n t suma ( i n t a , double b )
double suma ( double a , double b )

Zagadnienie przeciania funkcji zilustrujemy klasycznym przykadem dydaktycznym jakim jest funkcja obliczajca kwadrat liczby.
Listing 12.1. Przykad przecienia funkcji
2

#include <i o s t r e a m >


#include <c o n i o . h>

i n t kwadrat ( i n t ) ;
double kwadrat ( double ) ;
i n t kwadrat ( int , i n t ) ;

using namespace s t d ;

10

12

i n t main ( )
{ c o u t << kwadrat ( 3 )
<< e n d l ;
c o u t << kwadrat ( 3 . 3 ) << e n d l ;
c o u t << kwadrat ( 3 , 4 ) << e n d l ;

267

268

12. Szablony w C++


14

16

18

20

22

24

26

getche () ;
return 0 ;
}
i n t kwadrat ( i n t x )
{ c o u t << x <<" do kwadratu= " ;
return xx ;
}
double kwadrat ( double x )
{ c o u t << x <<" do kwadratu= " ;
return xx ;
}
i n t kwadrat ( i n t a , i n t b )
{ c o u t << " a a +bb= " ;
return a a + bb ;
}

Po uruchomieniu tego programu mamy komunikat:


3 do kwadratu = 9
3 . 3 do kwadratu = 1 0 . 8 9
a a + bb = 25

Trzy funkcje o nazwie kwadrat() s rozrnialne bez kopotu przez kompilator ze wzgldu na typ parametrw (kwadrat(int) oraz kwadrat (double) )
oraz ze wzgldu na liczb argumentw (kwadrat (int) i kwadrat (int, int)).
W kolejnym przykadzie pokazano rozbudowany zestaw uytecznych funkcji
przecionych do wyznaczania najmniejszej liczby ze zbioru. Kompilator bez
trudu radzi sobie z identykacj funkcji.
Listing 12.2. Przykad przecienia funkcji
1

11

13

15

17

19

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
i n t min ( int , i n t ) ;
i n t min ( int , int , i n t ) ;
i n t min ( i n t , i n t ) ;
long min ( long , long ) ;
double min ( double , double ) ;
i n t main ( )
{ int a = 22 ,
b = 7,
c = 3;
long e = 8 ,
f = 11;
double g = 1 4 ,
h = 99;
c o u t << " min= " << min ( a , b ) << e n d l ;
c o u t << " min= " << min ( a , b , c ) << e n d l ;
c o u t << " min= " << min(&a , f ) << e n d l ;
c o u t << " min= " << min ( e , f ) << e n d l ;
c o u t << " min= " << min ( g , h ) << e n d l ;
c o u t << " min= " << min ( ( double ) a , h ) << e n d l ;
getche () ;

12.2. Przecianie funkcji


21

23

25

27

29

31

33

return 0 ;
}
i n t min ( i n t x , i n t y )
{ return x<y ? x : y ; }
i n t min ( i n t x , i n t y , i n t z )
{ i f ( x<y ) return x<z ? x : z ;
e l s e return y<z ? y : z ; }
i n t min ( i n t x , i n t y )
{ return x<y ? x : y ; }
long min ( long x , long y )
{ return x<y ? x : y ; }
double min ( double x , double y )
{ return x<y ? x : y ; }

Po uruchomieniu tego programu mamy nastpujcy wydruk:


min
min
min
min
min
min

=
=
=
=
=
=

7
3
11
8
14
22

Kompilator nie ma problemu z identykacj odpowiedniej realizacji funkcji


min().
W wywoaniu:
c o u t << "min= " << min ( ( double ) a , h ) << e n d l ;

musimy dokona konwersji jawnej, gdy kompilator nie jest w stanie w stanie
rozrni typw. Gdyby wywoanie miao posta:
c o u t << "min= " << min ( a , h ) << e n d l ;

pojawi si nastpujcy komunikat bdu:


[ C++ E r r o r ] : E2014 Ambiguity between
"min ( double , d o u b l e ) " and "min ( i n t , i n t ) "

W denicjach:
i n t min ( int , i n t ) ;
i n t min ( i n t , i n t ) ;

liczby parametrw s rwne ale typy si rni, druga funkcja ma wskanik


do typu int zamiast czystego int. To wystarcza kompilatorowi aby rozrni
te funkcje.

269

270

12. Szablony w C++


Do ciekaw konstrukcja jest przecianie funkcji przy pomocy wskanika do funkcji. Moemy deklarowa wskaniki i przypisywa im adresy funkcji przecionych. Poniej przypominamy w jaki sposb wykorzystujemy
wskanik do funkcji.
Listing 12.3. Przykad uycia wskanika do funkcji
2

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;

10

12

14

i n t kwadrat ( i n t ) ;
i n t main ( )
{ i n t ( w1 ) ( i n t ) ; // d e k l a r a c j a w s k a z n i k a
// do f u n k c j i t y p u i n t
w1 = &kwadrat ;
// p r z y p i s a n i e w s k a z n i k o w i a d r e s u f u n k c j i
c o u t << ( w1 ) ( 5 ) ; // w y w o l a n i e f u n k c j i z argumentem 5
getche () ;
// i w y s w i e t l e n i e kwadratu argumentu
return 0 ;
}
i n t kwadrat ( i n t x )
{ return xx ; }

W pokazanym poniej programie pokazano przecianie funkcji posugujc si wskanikami do funkcji.


Listing 12.4. Przykad uycia wskanika do przecianych funkcji
1

11

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
void ramka ( int , char ) ;
void ramka ( int , char , char ) ;
i n t main ( )
{
void ( w1 ) ( int , char ) ;
// w s k a z n i k do f u n k c j i
void ( w2 ) ( int , char , char ) ; // w s k a z n i k do f u n k c j i
w1 = ramka ;
// dopasowani e ( i n t , c h a r )
w2 = ramka ;
// dopasowani e ( i n t , char , c h a r )

13

w1 ( 1 0 , ) ;
w2 ( 1 0 , | , . ) ;
w1 ( 1 0 , \xCD ) ;
getche () ;
return 0 ;

15

17

19

21

void ramka ( i n t n , char z )


{ f o r ( i n t i =0; i <=n ; i ++) c o u t << ( char ) z ;

271

12.2. Przecianie funkcji


23

25

27

29

c o u t << e n d l ;
}
void ramka ( i n t n , char z1 , char z2 )
{ c o u t << z1 ;
f o r ( i n t i =1; i <n ; i ++) c o u t << z2 ;
c o u t << z1 << e n d l ; ;
}

Po uruchomieniu tego programu mamy nastpujcy wydruk:


- - - - - - - - - |. . . . . . . . .|
= = = = = = = = = =
W pokazanym przykadzie mamy zadeklarowane dwie funkcje o nazwie ramka. Rni si one liczb argumentw. Pierwsza umieszcza na ekranie zadana
liczb znakw, druga umieszcza na ekranie znak pocztkowy i kocowy oraz
drugi znak podan ilo razy.
W instrukcjach zadeklarowano wskaniki do dwch funkcji
void ( w1 ) ( int , char ) ;
void ( w2 ) ( int , char , char ) ;

// w s k a z n i k do f u n k c j i
// w s k a z n i k do f u n k c j i

a w instrukcjach:
w1 ( 1 0 , ) ;
w2 ( 1 0 , | , . ) ;
w1 ( 1 0 , \xCD ) ;

wywoano te funkcje. Dopasowanie funkcji do konkretnej postaci realizuje


si w instrukcjach:
w1 = ramka ;
w2 = ramka ;

// dopasowani e ( i n t , c h a r )
// dopasowani e ( i n t , char , c h a r )

Zadeklarowane metody moemy take przecia, pod znanym warunkiem, e inna jest liczba argumentw lub inne s ich typy. Na przykad w
klasie Osoba mamy dwie przecione metody o nazwie set().
Listing 12.5. Przykad przeciania metod
2

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;

c l a s s Osoba
{
s t r i n g nazwisk o ;
i n t rok ;
public :

272

12. Szablony w C++

10

12

14

16

18

20

22

24

void d r u k u j ( ) ;
void s e t ( s t r i n g nn ) { nazwisk o = nn ; } // p r z e c i a z o n a
// metoda
void s e t ( i n t r r ) { rok = r r ; }
// p r z e c i a z o n a metoda
};
void Osoba : : d r u k u j ( )
{ c o u t << nazwisk o << " " << rok ;
}
i n t main ( )
{ Osoba o s ;
o s . s e t ( " Kowalsk i " ) ;
os . s e t (1999) ;
os . drukuj ( ) ;
getche () ;
return 0 ;
}

Po uruchomieniu tego programu mamy wydruk:


Kowalsk i 1999

Jak pamitamy, w klasie moemy mie wiele konstruktorw, wszystkie o


takiej samej nazwie. Praktycznie mamy do czynienie z przecianiem konstruktorw. Kolejny przykad przypomni przecianie konstruktorw.
Listing 12.6. Przykad przeciania konstruktorw
1

#include <i o s t r e a m >


#include <iomanip>
#include <c o n i o . h>
using namespace s t d ;

11

13

15

17

19

21

c l a s s Data
i n t m i e s i a c , d z i e n , rok ;
{
public :
Data ( i n t = 8 , i n t = 7 , i n t = 2 0 0 6 ) ; // k o n s t r u k t o r
Data ( long ) ;
// k o n s t r u k t o r
void drukuj_Data ( ) ;
};
Data : : Data ( i n t mm, i n t dd , i n t r r )
{ m i e s i a c = mm;
d z i e n = dd ;
rok = r r ;
}
Data : : Data ( long k_data )
{ rok = i n t ( k_data / 1 0 0 0 0 . 0 ) ;
m i e s i a c = i n t ( ( k_data rok 1 0 0 0 0 . 0 ) / 1 0 0 . 0 ) ;
d z i e n = i n t ( k_data rok 1 0 0 0 0 . 0 m i e s i a c 1 0 0 . 0 ) ;
}

12.2. Przecianie funkcji


23

25

27

29

31

33

35

37

39

void Data : : drukuj_Data ( )


{ c o u t << s e t f i l l ( 0 )
<< setw ( 2 ) << m i e s i a c << /
<< setw ( 2 ) << d z i e n << /
<< setw ( 2 ) << rok %100 << e n d l ;
}
i n t main ( )
{ Data a , b ( 2 0 0 8 1 2 2 5 L) , c ( 5 , 1 , 1 9 4 7 ) ;
c o u t << " data a : " ;
a . drukuj_Data ( ) ;
c o u t << " data b : " ;
b . drukuj_Data ( ) ;
c o u t << " data c : " ;
c . drukuj_Data ( ) ;
getche () ;
return 0 ;
}

Po uruchomieniu programu, mamy nastpujcy wydruk:


data a : 08/07/06
data b : 12/25/08
data c : 05/01/47

W pokazanym przykadzie zastosowalimy konstruktor konwertujcy typy


(ang. type conversion constructor):
Data ( long ) ;

// k o n s t r u k t o r

W danych klasy nie mamy typu long. W naszym przykadzie konstruktor


konwertujcy bdzie przeksztaca dane typu long na obiekt typu Data.
Nasz obiekt typu Data opisuje dat w formie miesic/ dzie/ rok. Jest to
klasyczna konwencja stosowana w Stanach Zjednoczonych. Zmienna typu
long integer przechowuje dat w formie :
rok 10000 + m i e s i a c 100 + d z i e n

Reprezentowanie daty w tej postaci posiada kilka zalet: po pierwsze umoliwia reprezentowanie daty w postaci liczby cakowitej, dane tego typu maj
wzrastajcy porzdek, po drugie sortowanie dat staje si ekstremalnie atwe
i szybkie. Konstruktor konwertujcy ma posta:
Data : : Data ( long k_data )
{ rok = i n t ( k_data / 1 0 0 0 0 . 0 ) ;
m i e s i a c = i n t ( ( k_data rok 1 0 0 0 0 . 0 ) / 1 0 0 . 0 ) ;
d z i e n = i n t ( k_data rok 1 0 0 0 0 . 0 m i e s i a c 1 0 0 . 0 ) ;
}

273

274

12. Szablony w C++


W pokazanym przykadzie nie musimy stosowa przecionego konstruktora, moemy utworzy funkcj operatorow. Funkcja operatorowa przeksztaca dat w postaci 1,5,1947 (miesic, dzie rok) do postaci typu long integer.
Listing 12.7. Funkcja operatorowa do konwersji typu
1

#include <i o s t r e a m >


#include <iomanip>
#include <c o n i o . h>
using namespace s t d ;

11

13

15

17

19

21

23

25

27

29

31

33

c l a s s Data
{
i n t m i e s i a c , d z i e n , rok ;
public :
Data ( i n t = 8 , i n t = 7 , i n t = 2 0 0 6 ) ; // k o n s t r u k t o r
operator long ( ) ;
// f u n k c j a o p e r a t o r o w a
void drukuj_Data ( ) ;
};
Data : : Data ( i n t mm, i n t dd , i n t r r )
{ m i e s i a c = mm;
d z i e n = dd ;
rok = r r ;
}
Data : : operator long ( )
{ long rmd ;
rmd = rok 1 0 0 0 0 . 0 + m i e s i a c 1 0 0 . 0 + d z i e n ;
return rmd ;
}
void Data : : drukuj_Data ( )
{ c o u t << s e t f i l l ( 0 )
<< setw ( 2 ) << m i e s i a c << /
<< setw ( 2 ) << d z i e n << /
<< setw ( 2 ) << rok %100 << e n d l ;
}
i n t main ( )
{ Data a ( 1 , 5 , 1 9 4 7 ) ; // d e k l a r a c j a o b i e k t u t y p u Data
long b = a ;
// d e k l a r a c j a o b i e k t u t y p u l o n g
c o u t << " data a : " ;
a . drukuj_Data ( ) ;
c o u t << " t a data j a k o l o n g : " << b ;

35

getche () ;
return 0 ;

37

Po uruchomieniu tego programu otrzymujemy nastpujcy komunikat:


data a : 01/05/47
t a data j a k o long : 19470105

275

12.2. Przecianie funkcji


Deklaracja funkcji operatorowej ma posta:
operator long ( ) ;

// f u n k c j a o p e r a t o r o w a

a denicja jest nastpujca:


Data : : operator long ( )
{ long rmd ;
rmd = rok 1 0 0 0 0 . 0 + m i e s i a c 1 0 0 . 0 + d z i e n ;
return rmd ;
}

W kolejnym przykadzie pokaemy uyteczne przeciania konstruktora suce take do obsugi daty. Bardzo czsto zdarza si, e data podawana jest
w dwch formatach jako dane typu int albo dane s typu napisowego.
Przecienie konstruktora obsuy te dwa sposoby pisania dat. W programie
wykorzystamy specyczn funkcje wejcia jak jest funkcja sscanf().
Funkcja sscanf() ( wymagany jest plik <cstdio> ), ktrej prototyp ma
posta:
i n t s s c a n f ( ) ( const char buf , const char format ,

...) ;

suy do czytania danych, w przeciwiestwie do funkcji scanf() nie czyta


danych z standardowego wejcia stdin ale z tablicy wskazywanej przez buf.
W acuchu formatujcym wykorzystano symbol *. Symbol * umieszczony
miedzy znakiem % a kodem formatujcym sprawia, e wczytywane s dane
konkretnego typu, natomiast przypisania s ignorowane. W instrukcji:
s c a n f ( "%d c%d" , &a , &b ) ;

jeeli wpiszemy dane wejciowe w postaci 21/10, wtedy 21 zostanie przypisane zmiennej a, / zostanie zignorowane, a 10 bdzie przypisane zmiennej
b.
Listing 12.8. Przecianie konstruktorw (obsuga typu int i napis)
1

11

#include <i o s t r e a m >


#include <c o n i o . h>
#include <c s t d i o >
using namespace s t d ;
c l a s s Data
{
i n t d z i e n , m i e s i a c , rok ;
public :
Data ( char ) ;
// k o n s t r u k t o r : o b s l u g a napi sow
Data ( int , int , i n t ) ; // k o n s t r u k t o r : o b s l u g a l i c z b
void drukuj_Data ( ) ;
};
Data : : Data ( char buf )

276

12. Szablony w C++


13

15

17

19

21

23

25

27

29

31

{ s s c a n f ( buf , "%i %c%i %c%i " , &m i e s i a c , &d z i e n , &rok ) ;


}
Data : : Data ( i n t m, i n t d , i n t r )
{ dzien = d ;
m i e s i a c = m;
rok = r ;
}
void Data : : drukuj_Data ( )
{ c o u t << m i e s i a c << " / " << d z i e n ;
c o u t << " / " << rok << e n d l ;
}
i n t main ( )
{ Data d1 ( 1 1 , 2 1 , 2 0 0 8 ) ;
Data d2 ( " 12/27/2008 " ) ;
d1 . drukuj_Data ( ) ;
d2 . drukuj_Data ( ) ;
getche () ;
return 0 ;
}

Po uruchomieniu programu otrzymujemy komunikat:


11/21/2008
12/27/2008

Widzimy, e chocia obiekty typu Data byy inicjowane przy uyciu trzech
wartoci typu int :
Data d1 ( 1 1 , 2 1 , 2 0 0 8 ) ;

(obiekt d1) oraz za pomoc acucha znakowego:


Data d2 ( " 12/27/2008 " ) ;

(obiekt d2), to wywietlanie daty zostao wykonane poprawnie. Dziki przecianiu konstruktorw, uytkownik moe decydowa w jaki sposb bdzie
wprowadza daty do programu. Takie podejcie zmniejsza ryzyko popenienia bdu w danych wejciowych.

12.3. Szablony funkcji


W poprzednich rozwaaniach wykazalimy uyteczno przeciania funkcji. Jednak jak pokazuje wydruk programu 12.2 aby szeroko obsuy typy
danych jakie mog by argumentami w funkcji min() musielimy napisa
pi wersji tej funkcji. W naszym przykadzie nie byo zbyt duo instrukcji
do zapisu, ale atwo moemy sobie wyobrazi funkcj, ktrej kod skada si

12.3. Szablony funkcji


z np. 60 instrukcji. Napisanie piciu wersji takiej funkcji jest pracochonne,
ale co najwaniejsze wraz z iloci napisanych instrukcji wzrasta prawdopodobiestwo popenienie bdu. Mona zada pytanie, czy nie mona unikn
mudnego kodowania wielu funkcji wykonujcych takie samo zadanie. Jak
si domylamy, odpowied na to pytanie jest pozytywna. Nowoczesne kompilatory jzyka C++ pozwalaj na kodowanie funkcji z typami oglnymi
(nieokrelonymi), w momencie wywoania funkcji typy oglne zastpowane
s typami konkretnymi, mwimy o kodowaniu funkcji wzorcowych albo inaczej szablonw funkcji. Szablony umoliwiaj programowanie oglne (ang.
generic programming). Bardzo czsto mamy do czynienia np. z sortowaniem
tablic. W takim przypadku musimy napisa funkcj do sortowania tablic
przechowujcych np. elementy typu int. Gdybymy chcieli posortowa tablic z elementami typu double musielibymy na nowo napisa inn funkcj
sortujc. Szablon funkcji umoliwia napisanie jednej funkcji sortujcej elementy tablicy wykorzystujc typ oglny, a kompilator w zalenoci od sposobu wywoania wygeneruje automatycznie kod funkcji sortujcej konkretny
typ. Bd to w naszym przypadku dwie funkcje jedna sortujca elementy
typu int i jedna sortujca elementy typu double. Formalnie szablon funkcji
nie jest niczym nowym. Programici jzyka C (w ktrym nie mona pisa
szablonw funkcji) maj do dyspozycji makrodenicje preprocesora dziki
nim mona otrzyma rne realizacje kodu dla kadego typu wywoania.
Wad makrodenicji jest dua podatno na bdy i powstawanie efektw
ubocznych. Aby wyeliminowa kopoty zwizane z makrodenicjami, twrcy
jzyka C++ wprowadzili wzorce (szablony) funkcji.
Listing 12.9. Uycie wzorca funkcji
1

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
template <c l a s s T >
T kwadrat (T x )
{
return xx ;
}

11

13

15

i n t main ( )
{ c o u t << " wynik i n t : " << kwadrat ( 2 )
<< e n d l ;
c o u t << " wynik f l o a t : " << kwadrat ( 2 . 2 ) << e n d l ;
getche () ;
return 0 ;
}

277

278

12. Szablony w C++


Po uruchomieniu tego programu mamy nastpujcy wynik:
4
wynik i n t :
wynik f l o a t : 4 . 8 4

Szablon funkcji obliczajcej kwadrat argumentu moe mie posta:


template <c l a s s T >
T kwadrat (T x )
return xx ;
{
}

Denicja wzorca funkcji zaczyna si od sowa kluczowego template, za nim w


nawiasach ostrych umieszczona jest lista formalnych typw oglnych. Kady
parametr formalny poprzedzony jest sowem kluczowym class:
template < c l a s s T >

W naszym przykadzie mamy jeden parametr oglny o nazwie T. Sowa


kluczowe template i class s obowizkowe. W najnowszych kompilatorach
nieco mylce sowo kluczowe class mona zastpi innym sowem kluczowym
typename:
template < typenameT >

Obowizkowo musimy stosowa nawiasy ostre. Po deklaracji tworzenia szablonu nastpuje zwyka denicja funkcji:
T kwadrat (T x )
return xx ;
{
}

Widzimy zwracany typ (w naszym przypadku jest to typ oglny T), nazw
funkcji (w naszym przypadku kwadrat) oraz list argumentw. W nawiasach
klamrowych umieszczone jest ciao funkcji.
Bardziej rozbudowany przykad uycia szablonu funkcji pokazany jest
w kolejnym przykadzie. W programie uyjemy wzorca funkcji max(), ktra pobiera trzy argumenty i zwraca warto najwikszego argumentu oraz
funkcji foo() ktra wczytuje dane z klawiatury i wywietla wyniki. Dziaanie
szablonw funkcji testujemy na trzech typach danych : int, double i char.
Listing 12.10. Uycie wzorcw funkcji
1

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;

12.3. Szablony funkcji


5

11

13

15

17

19

21

23

25

27

template <c l a s s T>


T max(T w1 , T w2 , T w3 )
{ T max = w1 ;
i f ( w2 > max ) max = w2 ;
i f ( w3 > max ) max = w3 ;
return max ;
}
template <c l a s s T>
void f o o (T x1 , T x2 , T x3 )
c i n >> x1 ;
{ c o u t << " \ nargument 1 : " ;
c o u t << " \ nargument 2 : " ;
c i n >> x2 ;
c o u t << " \ nargument 3 : " ;
c i n >> x3 ;
c o u t << " \ n n a j w i e k s z a w a r t o s c t o : " << max ( x1 , x2 , x3 ) ;
}
i n t main ( )
{ i n t n1 , n2 , n3 ;
// w e r s j a i n t
f o o ( n1 , n2 , n3 ) ;
double d1 , d2 , d3 ;
// w e r s j a d o u b l e
f o o ( d1 , d2 , d3 ) ;
char c1 , c2 , c3 ;
// w e r s j a c h a r
f o o ( c1 , c2 , c3 ) ;
getche () ;
return 0 ;
}

Po uruchomieniu programu mamy nastpujcy wydruk:


argument 1 : 10
argument 2 : 22
argument 3 : 11
n a j w i e k s z a w a r t o s c t o : 22
argument 1 : 1 . 1
argument 2 : 3 . 3
argument 3 : 1 . 7
najwieksza wartosc to : 3.3
argument 1 : x
argument 2 : y
argument 3 : z
najwieksza wartosc to : z

Program dziaa zgodnie z oczekiwaniem. Gdy do szablonu funkcji przekazujemy np. argumenty typu int, kompilator tworzy kompletna funkcje do
obliczenia najwikszej wartoci spord trzech wprowadzonych danych typu
int. Gdy wprowadzimy dane typu char tworzona jest funkcja do obsugi argumentw typu char. W programie zdeniowalimy dwa szablony, szablon
funkcji max():
template <c l a s s T>
T max(T w1 , T w2 , T w3 )

279

280

12. Szablony w C++


{ T max = w1 ;
i f ( w2 > max ) max = w2 ;
i f ( w3 > max ) max = w3 ;
return max ;
}

oraz foo():
template <c l a s s T>
void f o o (T x1 , T x2 , T x3 )
{ c o u t << " \ nargument 1 : " ;
c i n >> x1 ;
c i n >> x2 ;
c o u t << " \ nargument 2 : " ;
c o u t << " \ nargument 3 : " ;
c i n >> x3 ;
c o u t << " \ n n a j w i e k s z a w a r t o s c t o : " << max ( x1 , x2 , x3 ) ;
}

Realizacja szablonu funkcji max() dla obsugi argumentw typu int ma posta:
i n t max( i n t w1 , i n t w2 , i n t w3 )
{ i n t max = w1 ;
i f ( w2 > max ) max = w2 ;
i f ( w3 > max ) max = w3 ;
return max ;
}

Szablony funkcji mog wykorzystywa jako argumenty dane nie bdce


prostymi typami. W kolejnym przykadzie zmienne s zdeniowane strukturami. W programie porwnujemy dwa wektory i wybieramy wektor duszy.
Pamitamy, e dla wektora dwuwymiarowego, ktry ma skadowe (x, y)
dugo wektora okrelona jest wzorem:
||d|| =

x2 + y 2

(12.1)

Aby okreli, ktry z wektorw jest duszy wystarczy obliczy sum kwadratw skadowych wektorw. Wektor zdeniowany jest nastpujc struktur:
struct wek tor
{ int x , y ;
wek tor ( i n t xx =0 , i n t yy = 0 ) : x ( xx ) , y ( yy ) { }
bool operator < ( wek tor & w) // p r z e c i a z o n y o p e r a t o r
{ return ( xx + yy ) < (w . xw . x + w . yw . y ) ;
}
};

12.3. Szablony funkcji


Listing 12.11. Uycie wzorcw funkcji; wykorzystanie struktury
1

11

13

15

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
struct wek tor
{ int x , y ;
wek tor ( i n t xx =0 , i n t yy = 0 ) : x ( xx ) , y ( yy ) { }
bool operator < ( wek tor & w) // p r z e c i a z o n y o p e r a t o r
{ return ( xx + yy ) < (w . xw . x + w . yw . y ) ;
}
};
template < c l a s s T>
T max(T a , T b )
{ return a < b ? b : a ;
}

17

19

21

i n t main ( )
{ wek tor w1 ( 1 , 1 ) , w2 ( 2 , 2 ) , ww;
ww = max (w1 , w2 ) ;
c o u t << " d l u z s z y wek tor ma sk ladowe : "<< e n d l ;
c o u t << ww. x << " " << ww. y << e n d l ;

23

getche () ;
return 0 ;

25

Po uruchomieniu programu mamy wydruk:


d l u z s z y wek tor ma sk ladowe :
2
2

Wzorzec funkcji doskonale radzi sobie ze zmienn zdeniowan struktur.


Szablon funkcji okrelajcej ktry z wektorw jest duszy ma posta:
template < c l a s s T>
T max(T a , T b )
{ return a < b ? b : a ;
}

Przypominamy, e w deklaracji szablonu moemy uy sowa kluczowego


typename:
template < typename T>
T max(T a , T b )
{ return a < b ? b : a ;
}

281

282

12. Szablony w C++


Tworzc szablony funkcji moemy korzysta z wicej ni jednego typu oglnego.
Listing 12.12. Uycie wzorcw funkcji dwa typy uoglnione
2

10

12

14

16

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
template <c l a s s T, c l a s s R>
void pokaz (T x1 , R x2 )
{ c o u t << " argument 1 : " << x1 ;
c o u t << " , argument 2 : "<< x2 << e n d l ;
}
i n t main ( )
{ i n t n1 = 1 9 4 7 ;
double d1 = 9 9 . 9 9 ;
char xx = "Wacek" ;
pokaz ( n1 , xx ) ;
pokaz ( n1 , d1 ) ;
getche () ;
return 0 ;
}

Po uruchomieniu programu mamy komunikat:


argument 1 : 1947 , argument 2 : Wacek
argument 1 : 1947 , argument 2 : 9 9 . 9 9

Aby wprowadzi dan ilo argumentw uoglnionych w nagwku szablonu funkcji musimy to zadeklarowa przy pomocy sowa kluczowego class:
class T1, class T2, class T3. . . . . . W naszym przykadzie dalimy dwch
typw uoglnionych, wobec tego szablon funkcji mia posta:
template <c l a s s T, c l a s s R>
void pokaz ( T x1 , R x2 )
{ c o u t << " argument 1 : " << x1 ;
c o u t << " , argument 2 : "<< x2 << e n d l ;
}

Argument formalny x1 jest typu uoglnionego T, a argument formalny x2


jest typu oglnego R. Szablony funkcji moemy przecia. Moemy np.
szuka wikszego z dwch wprowadzonych argumentw lub te szuka najwikszego elementu tablicy. W tym celu moemy przeciy szablon funkcji,
podobnie jak to robimy ze zwykymi funkcjami.
Listing 12.13. Uycie wzorcw funkcji przecianie szablonw funkcji
1

#include <i o s t r e a m >


#include <c o n i o . h>

12.3. Szablony funkcji


3

using namespace s t d ;

template <c l a s s T>


T max (T & x1 , T & x2 )
{ i f ( x1 > x2 ) return x1 ;
else
return x2 ;
}

11

13

15

17

19

21

23

25

template <c l a s s R>


R max (R m, i n t n )
{ R mm = m [ 0 ] ;
f o r ( i n t i = 1 ; i < n ; i++ )
i f (m[ i ] > mm) mm = m[ i ] ;
return mm;
}
i n t main ( )
{ i n t n1 = 1 0 , n2 = 3 3 ;
c o u t << " w i e k s z a z dwoch= " << max ( n1 , n2 ) ;
double dd [ 1 0 ] = { 2 , 3 , 4 , 1 1 , 5 , 7 7 , 3 3 , 1 , 9 , 1 0 } ;
c o u t << " \ n n a j w i e k s z a w t a b l i c y = " << max( dd , 1 0 ) ;
getche () ;
return 0 ;
}

Po uruchomieniu tego programu mamy nastpujcy wynik:


w i e k s z a z dwoch = 33
n a j w i e k s z a w t a b l i c y = 77

Gdy w pokazanym programie kompilator dojdzie do wywoania:


c o u t << " w i e k s z a z dwoch= " << max( n1 , n2 ) ;

stwierdzi, e uyto dwch argumentw typu int wobec czego wywoa pasujcy szablon:
template <c l a s s T>
T max (T & x1 , T & x2 )
{ i f ( x1 > x2 ) return x1 ;
else
return x2 ;
}

W kolejnym wywoaniu funkcji max():


c o u t << " \ n n a j w i e k s z a w t a b l i c y = " << max ( dd , 1 0 ) ;

argumentem funkcji jest tablica typu double i cakowita liczba 10, wobec
czego wywoany zostanie szablon:

283

284

12. Szablony w C++


template <c l a s s R>
R max (R m, i n t n )
{ R mm = m [ 0 ] ;
f o r ( i n t i = 1 ; i < n ; i++ )
i f (m[ i ] > mm) mm = m[ i ] ;
return mm;
}

Zwrmy uwag, e w szablonie funkcji max():


template <c l a s s R>
R max (R m, i n t n )

mamy poczenie parametrw uoglnionych (R *m) oraz standardowych


(int n). czenie parametrw uoglnionych i standardowych jest czsto wykorzystywane w praktycznych zastosowaniach i nie sprawia kompilatorowi
adnych kopotw.
Posugujc si szablonami moemy je przecia w sposb jawny, to
znaczy obok szablonu funkcji moemy jednoczenie zdeniowa przeciona
zwyk funkcj.
W kolejnym przykadzie zilustrujemy to zagadnienie.
Listing 12.14. Uycie wzorcw funkcji jawne przecianie szablonw
2

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;

10

12

14

16

18

20

22

24

template <c l a s s T>


T max (T & x1 , T & x2 )
{ i f ( x1 > x2 ) return x1 ;
else
return x2 ;
}
double max ( double y1 , double y2 )
{ c o u t << " j e s t e m w d o u b l e " << e n d l ;
i f ( y1 > y2 ) return y1 ;
else
return y2 ;
}
i n t main ( )
{ i n t n1 = 1 0 , n2 = 3 3 ;
c o u t << " w i e k s z a z dwoch i n t = " << max ( n1 , n2 ) ;
char c1 = A , c2 = a ;
c o u t << " w i e k s z a z dwoch c h a r = " << max ( c1 , c2 ) ;
double d1 = 1 3 . 1 3 , d2 = 9 9 . 9 9 ;
c o u t << " w i e k s z a z dwoch d o u b l e = " << max( d1 , d2 ) ;
getche () ;
return 0 ;
}

12.3. Szablony funkcji


Po uruchomieniu tego programu mamy wynik:
w i e k s z a z dwoch i n t = 33
w i e k s z a z dwoch char = a
j e s t e m w double
w i e k s z a z dwoch double = 9 9 . 9 9

Pamitamy, e w kodzie ASCII znak A ma kod dziesitny 65, a znak a ma


kod 97. Aby przekona si, e rzeczywicie kompilator uy jawnie przecionej wersji funkcji max() w kodzie tej funkcji umiecilimy odpowiedni
komunikat:
double max ( double y1 , double y2 )
{ c o u t << " j e s t e m w d o u b l e " << e n d l ;
i f ( y1 > y2 ) return y1 ;
else
return y2 ;
}

W programach, w ktrych mamy szablony funkcji i zwykle funkcje, ktre


przeciaj je, moemy w sposb jawny wymusi wykonanie danych operacji przez funkcje szablonowe. Kolejny przykad ilustruje to zagadnienie.
Listing 12.15. Uycie wzorcw funkcji jawne przecianie szablonw
1

11

13

15

17

19

21

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
template <typename T>
T max( T n1 , T n2 )
{ return c o u t << " s z a b l o n f u n k c j i : " ,
n1 < n2 ? n2 : n1 ;
}
char max ( char m1 , char m2)
{ return c o u t << " zwy k la f u n k c j a : " ,
strcmp (m1 , m2) > 0 ? m2 : m1 ;
}
i n t main ( )
{ c o u t << max ( "Ewa" , "Anna" ) << e n d l ;
c o u t << max ( "Anna" , "Ewa" ) << e n d l ;
c o u t << max<>("Ewa" , "Anna" ) << e n d l ;
c o u t << max<>("Anna" , "Ewa" ) << e n d l ;
getche () ;
return 0 ;
}

Po uruchomieniu tego programu mamy nastpujcy wynik:


zwy k la f u n k c j a : Anna

285

286

12. Szablony w C++


zwy k la f u n k c j a : Anna
s z a b l o n f u n k c j i : Anna
s z a b l o n f u n k c j i : Ewa

Wiemy, e funkcja oglna sama dokonuje przecienia. W konkretnych


przypadkach chcemy mie kontrol nad dziaaniem programu i chcemy wykona przecienie w sposb jawny. Takie dziaanie nosi nazw jawne tworzenie funkcji specjalizowanej. Przeciona funkcja oglna przesania funkcj ogln utworzon specyczn wersj. Dla jawnego wykonania funkcji szablonowej wykorzystano oznaczenie <> oraz posta alternatywn <char*>.
Widzimy, e wersja szablonowa funkcji nie dziaa poprawnie. Kod jest le
zaprojektowany. Dobrze jest stworzy specjalizacje szablonu funkcji dla wybranego typu.
Dla przypomnienia, nasz program porwnuje dwa napisy. Zwyka funkcja dziaa poprawnie, wykorzystuje funkcje biblioteczna strcmp().
Prototyp tej funkcji ma posta:
i n t strcmp ( const char s1 , const char s 2 ) ;

Funkcja strcmp() zdeniowana jest w pliku <string.h>. Funkcja zwraca zero


jeeli napisy s identyczne, zwraca liczb ujemna gdy s1 < s2, zwraca liczb
dodatni w przeciwnym przypadku.
Widzimy, e szablon nie dziaa poprawnie dla wszystkich typw danych,
chcemy aby program wymusza wersj zwyk, gdy musimy obsuy napisy. W tym celu musimy posuy si oznaczeniem template <>. W takim
przypadku zawsze wywoana bdzie funkcja zwyka. Specjalizacj szablonu
funkcji dla wybranego typu ilustruje kolejny program.
Listing 12.16. Uycie wzorcw funkcji jawne przecianie szablonw
1

11

13

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
template <typename T>
T max( T n1 , T n2 )
{ return c o u t << " s z a b l o n f u n k c j i : " ,
n1 < n2 ? n2 : n1 ;
}
template<>
char max ( char m1 , char m2)
{ return c o u t << " zwy k la f u n k c j a : " ,
strcmp (m1 , m2) > 0 ? m2 : m1 ;
}

15

17

i n t main ( )
{ c o u t << max ( "Ewa" , "Anna" ) << e n d l ;

287

12.3. Szablony funkcji


c o u t << max ( "Anna" , "Ewa" ) << e n d l ;
c o u t << max<>("Ewa" , "Anna" ) << e n d l ;
c o u t << max<>("Anna" , "Ewa" ) << e n d l ;

19

21

getche () ;
return 0 ;

23

Po uruchomieniu tej wersji programu mamy nastpujcy wynik:


zwy k la
zwy k la
zwy k la
zwy k la

funkcja
funkcja
funkcja
funkcja

:
:
:
:

Anna
Anna
Anna
Anna

Widzimy, e we wszystkich przypadkach wywoywana jest zwyka funkcja.


Naley pamita, e jawna specjalizacja jest stosunkowo now skadni, i nie
wszystkie kompilatory radz sobie z konstrukcj template <>. Naley podchodzi z dua ostronoci do tego zagadnienia i zawsze testowa dziaanie
jawnej specjalizacji dla wybranego typu.
Szablony funkcji s bardzo czsto wykorzystywane w praktyce, Klasycznym przykadem jest szablon funkcji do sortowania dowolnej tablicy. Ze
wzgldu na prosty kod wybieramy metod sortowania bbelkowego (ang.
bublbe sort)
Listing 12.17. Uycie wzorcw funkcji sortowanie bbelkowe
2

10

12

14

16

18

20

22

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
template <c l a s s T>
void bubble (T mm, i n t n )
{ T x;
f o r ( i n t i = 1 ; i < n ; i ++)
f o r ( i n t j = n1; j >=i ; j )
i f (mm[ j 1] > mm[ j ] )
{ x = mm[ j 1 ] ;
mm[ j 1] = mm[ j ] ;
mm[ j ] = x ;
}
}
template <c l a s s R>
void pokaz (R mm, i n t p )
{ f o r ( i n t i =0; i <p ; i ++)
c o u t << " " << mm[ i ] << " " ;
c o u t << e n d l ;
}
i n t main ( )

288

12. Szablony w C++

24

26

28

30

32

{ i n t m1 [ 5 ] = { 1 1 , 3 3 , 2 3 , 1 5 , 7 7 } ;
char m2 [ 5 ] = { d ,
r ,
a ,
e ,
g };
bubble (m1 , 5 ) ;
bubble (m2 , 5 ) ;
c o u t << " posortowane l i c z b y c a l k o w i t e : "<< e n d l ;
pokaz (m1 , 5 ) ;
c o u t << " posortowane z n a k i : "<< e n d l ;
pokaz (m2 , 5 ) ;
getche () ;
return 0 ;
}

Po uruchomieniu tego programu mamy nastpujcy wydruk:


posortowane
11 15 23
posortowane
a
d
e

liczby calkowite :
33 77
znaki :
g
r

Dla testw utworzylimy dwie tablice picioelementowe : tablic typu int


oraz tablic typu char. Obie tablice zostay poprawnie posortowane.
Pokazany szablon funkcji sortujcej jest automatycznie przeciany do
danego typu mamy bardzo sprawny szablon funkcji do sortowania tablic
dowolnych elementw.
Uyteczny moe by take szablon funkcji do przeszukiwania tablicy dowolnego typu. Pokaemy szablon funkcji realizujcy przeszukiwanie metod
poowienia (ang. binary search).
Denicja szablonu funkcji realizujcej przeszukiwanie moe mie posta:
template <c l a s s
i n t BinSearch (T
{ int p = 0 ;
i n t k = n 1;
int s ;
while ( p <=
{s = (p +
i f ( x ==

T>
x , T t a b l i c a , int n)

k)
k) / 2;
tablica [ s ])
return s ;
else i f (x < t ab lic a [ s ] )
k = s 1;
else
p = s +1;

}
return 1;
}

W pokazanym szablonie, T jest typem uoglnionym. Funkcja szuka elementu

289

12.3. Szablony funkcji


o nazwie x w posortowanej tablicy nazywanej tablica, ktra przechowuje n
elementw.
Zdeniowana zostaa take szablonowa funkcja pomocnicza drukTab().
template <c l a s s T>
void drukTab ( T t a b l i c a , i n t n )
{ f o r ( i n t i = 0 ; i < n ; i ++)
c o u t << t a b l i c a [ i ] << " " ;
c o u t << e n d l ;
}

W pokazanym poniej programie najpierw deniujemy tablic (z ustalonym typem, np. int) a potem wywoujemy funkcj szablonow BinSearch().
Listing 12.18. Uycie wzorcw funkcji przeszukiwanie binarne
2

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;

10

12

template <c l a s s
i n t BinSearch (T
{ int p = 0 ;
i n t k = n 1;
int s ;
while ( p <=
{s = (p +
i f ( x ==

T>
x , T t a b l i c a , int n)

k)
k) / 2;
tablica [ s ])
return s ;
else i f (x < t ab lic a [ s ] )
k = s 1;
else
p = s +1;

14

16

}
return 1;

18

20

22

template <c l a s s T>


void drukTab ( T t a b l i c a , i n t n )
{ f o r ( i n t i = 0 ; i < n ; i ++)
c o u t << t a b l i c a [ i ] << " " ;
c o u t << e n d l ;
}

24

26

28

30

32

34

i n t main ( )
{ i n t tab1 [ ] = { 1 , 1 1 , 2 1 , 3 1 , 4 1 , 5 1 , 6 1 } ;
int t1 = 31;
drukTab ( tab1 , 7 ) ;
c o u t << " e l e m e n t " << t 1 << " ma i n d e k s "
<<BinSearch ( t1 , tab1 , 7 ) << e n d l ;
char tab2 [ ] = { a , e , g , h , i , k , x , y } ;

290

12. Szablony w C++


char t 2 = k ;
drukTab ( tab2 , 8 ) ;
c o u t << " e l e m e n t " << t 2 << " ma i n d e k s "
<<BinSearch ( t2 , tab2 , 8 ) << e n d l ;
getche () ;
return 0 ;

36

38

40

42

Po uruchomieniu tego programu mamy wydruk:


1
11
21
31
41
e l e m e n t 31 ma i n d e k s 3
a
e
g
h
i
k
e l e m e n t k ma i n d e k s 5

51
x

61
y

Program dziaa poprawnie, dopasowanie typu przebiegao bez problemu.


Konkretyzacja funkcji dla typu int ma posta:
i n t tab1 [ ] = { 1 , 1 1 , 2 1 , 3 1 , 4 1 , 5 1 , 6 1 } ;
int t1 = 31;
drukTab ( tab1 , 7 ) ;
c o u t << " e l e m e n t " << t 1 << " ma i n d e k s "
<< BinSearch ( t1 , tab1 , 7 ) << e n d l ;

W pokazanym przykadzie tworzymy tablic liczb cakowitych tab1[ ]. Jeeli


chcemy ustali pooenie elementu o wartoci zapisanej w zmiennej t1 to
wywoujemy funkcj BinSearch() w nastpujcy sposb:
BinSearch ( t1 , tab1 , 7 )

Skonkretyzowana dla typu int funkcja szablonowa wymaga trzech argumentw : poszukiwanego elementu, tablicy i rozmiaru tablicy. Podobnie postpujemy dla poszukiwania elementu w tablicy znakowej. Na licie parametrw
szablonu funkcji moemy umieszcza typy klas. W kolejnym przykadzie
zademonstrujemy skomplikowan obsug zarwno zmiennych typw wbudowanych jak i obiektw klasy. Kompilatory zachowuj si bardzo inteligentnie. W naszym przykadowym programie chcemy porwna dugoci
wektorw i wyznaczy mniejszy wektor, oczywicie chcemy mie moliwo
wyznaczenia mniejszej z dwch liczb typu int.
Klasa wek do obsugi wektora 2D ma posta:
c l a s s wek
int x , y ;
{
public :
wek ( i n t xx , i n t yy ) { x = xx ;
i n t getX ( ) { return x ; }
i n t getY ( ) { return y ; }

y = yy ; }

291

12.3. Szablony funkcji


i n t operator

< ( wek & ) ;

// p r z e c i a z o n y o p e r a t o r

} ;

Szablon funkcji min() do wyznaczenia mniejszej zmiennej ma posta:


template <c l a s s T>
T& min ( T& wr1 , T& wr2 )
{ i f ( wr1 < wr2 )
return wr1 ;
return wr2 ;
}

Funkcja min() moe by wykorzystana do porwnania dowolnych zmiennych. W naszym przypadku bdzie skonkretyzowana dla typu wek, bdzie
wyznaczaa mniejszy z dwch obiektw typu wek. Do wykonania poprawnie
operacji porwnania w klasie wek umieszczony zosta przeciony operator <.
Dla testw utworzylimy dwa obiekty klasy wek : w1 i w2.
Listing 12.19. Uycie wzorcw funkcji typ klasy argumentem szblonu
2

10

12

14

16

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
c l a s s wek
{
int x , y ;
public :
wek ( i n t xx , i n t yy ) { x = xx ; y = yy ; }
i n t getX ( ) { return x ; }
i n t getY ( ) { return y ; }
i n t operator < ( wek & ) ;
// p r z e c i a z o n y o p e r a t o r
} ;
template <c l a s s T>
T& min (T& wr1 , T& wr2 )
{ i f ( wr1 < wr2 )
return wr1 ;
return wr2 ;
}

18

20

22

24

26

i n t main ( )
{ wek w1 ( 1 , 1 ) ;
wek w2 ( 2 , 2 ) ;
wek w = min (w1 , w2 ) ;
c o u t << " m n i e j s z y wek tor : " << w . getX ( )
<< " " << w . getY ( ) << e n d l ;
i n t x1 = 3 ;
i n t x2 = 4 ;
c o u t << " m n i e j s z a l i c z b a : " << min ( x1 , x2 ) << e n d l ;

28

getche () ;

292

12. Szablony w C++


30

return 0 ;
}

32

34

36

i n t wek : : operator < ( wek & ww)


{ i f ( xx + y y < ww. x ww. x + ww. y ww. y )
return 1 ;
return 0 ;
}

Po uruchomieniu programu mamy nastpujcy wynik:


m n i e j s z y wek tor : 1 1
mniejsza l i c z b a
: 3

Podczas wywoania postaci :


wek w = min (w1 , w2 ) ;

nastpuje konkretyzacja funkcji szablonu min() dla typu wek. Skonkretyzowana funkcja wyznacza mniejszy obiekt za pomoc przecionego operatora
mniejszoci:
i n t wek : : operator < ( wek & ww)
{ i f ( xx + y y < ww. x ww. x + ww. y ww. y )
return 1 ;
return 0 ;
}

W przypadku porwnywania dwch liczb cakowitych:


i n t x1 = 3 ;
i n t x2 = 4 ;
c o u t << " m n i e j s z a l i c z b a : " << min ( x1 , x2 ) << e n d l ;

tzn. funkcja min() wywoywana jest z argumentami typu int, do porwnania


dwch liczb cakowitych uywany jest elementarny operator mniejszoci a
nie jego przeciona wersja.

12.4. Szablony klas


Na podobnej zasadzie co szablony funkcji mona take deniowa szablony klas (inne okrelenia: wzorce klas, klasy oglne). Naley zwrci uwag
na fakt, e szablony klas (ang. class template) s doskonaym mechanizmem
jzyka C++, Wiemy z praktyki, e niewiele problemw powstaje gdy projektujemy szablony funkcji, to w odniesieniu do klas, szablony s pojciowo
i skadniowo skomplikowane. Z tego powodu szablony klas nie s stosowa-

12.4. Szablony klas


ne tak czsto jak by mona byo przypuszcza. W dalszym cigu obsuga
szablonw w rnych kompilatorach moe rnie by realizowana. Z drugiej
strony coraz wiksze stosowanie biblioteki STL (Standard Template Library) wymusza zapoznanie si z podstawami i logik tej biblioteki. Jdrem
biblioteki STL s oglne kontenery i algorytmy. Wszystkie kontenery STL
to szablony, mona w nich zapisywa dane dowolnego typu. Za pomoc szablonw mona pisa kod niezaleny nie tylko od przekazanych wartoci ale
take od typw tych wartoci. Szablony klas su przede wszystkim jako
kontenery, s to struktury danych zawierajce obiekty.
Deklaracja szablonu klasy (w literaturze przedmiotu spotkamy take nazwy: klasa wzorcowa, wzorzec klasy, klasa oglna, itp.) ma posta:
template <c l a s s T_typ>
c l a s s nazwa_klasy
{
.
.
. // i n s t r u k c j e
.
};

Widzimy, e szablon klasy ma posta podobn do deklaracji zwykej klasy,


jedynie na pocztku musi znajdowa si kod postaci:
template < c l a s s T_typ>

Sowo kluczowe template informuje kompilator, e bdzie deniowany szablon. T typ oznacza nazw typu, ktry bdzie okrelony podczas tworzenia egzemplarza klasy. Zazwyczaj stosujemy jeden typ oglny, ale moemy
stosowa wicej takich typw. W takim przypadku w nawiasach ostrych
umieszczamy list typw oglnych oddzielonych przecinkami.. Egzemplarz
klasy tworzymy przy pomocy konstrukcji:
nazwa_klasy < typ> nazwa_obiektu ;

W tej instrukcji nazwa typ odnosi si do konkretnego typu na ktrym bdzie


operowa klasa. Nowsze implementacje jzyka C++ wprowadziy alternatywny zapis:
template <typename T_typ>

Zastpienie sowa kluczowego class sowem kluczowym typename wydaje si


znacznie logiczniejsze. Podobnie jak w wywoaniu funkcji, podczas tworzenia
obiektu klasy, a wic w momencie wywoania szablonu, w miejsce T typ
podstawiany jest konkretny typ taki jak np. int, double, char, itp. Naley

293

294

12. Szablony w C++


pamita, e podczas deniowania metod szablonu klasy, uywamy typw
oglnych, a denicja metody musi mie zapowied szablonu:
template < c l a s s T_typ>

Gdy deniujemy metod w deklaracji szablonu klasy, wtedy opuszczamy


zapowied szablonu i kwalikator klasy.
Przy pomocy szablonw klas moemy zbudowa kolekcje obiektw dowolnego typu, korzystajc z tego samego szablonu klasy, moemy zadeklarowa i zdeniowa klas dla dowolnego typu. O takiej klasie mwimy , e
jest sparametryzowana.
Omwimy prosty przykad tworzenia wzorca klasy. Bardzo czsto operujemy liczbami cakowitymi i rzeczywistymi. Dla kadego takiego zbioru
musimy pisa oddzielne klasy : do obsugi liczb cakowitych (np. typ int )
oraz do obsugi liczb rzeczywistych (np. typ double). Dziki wzorcom moemy utworzy sparametryzowan klas, ktra obsuy dowolny typ danych.
Listing 12.20. Uycie wzorcw klas prosty przykad
1

11

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
template <c l a s s T>
c l a s s war
{
T n;
// t y p o g o l n y T
public :
war ( ) : n ( 0 ) { }
// k o n s t r u k t o r
void set_war ( ) { c i n >> n ; }
void pokaz ( )
{ c o u t << n << e n d l ; }
};

13

15

17

19

i n t main ( )
// o b s l u g a t y p u c h a r
{ war <char> zn ;
c o u t << " podaj znak : " ;
zn . set_war ( ) ;
c o u t << " wprowadzony znak : " ;
zn . pokaz ( ) ;
// o b s l u g a t y p u d o u b l e
war <double> x ;
c o u t << " podaj l i c z b e r z e c z y w i s t a : " ;
x . set_war ( ) ;
c o u t << " wprowadzona l i c z b a : " ;
x . pokaz ( ) ;
getche () ;
return 0 ;

21

23

25

27

12.4. Szablony klas


Po uruchomieniu tego programu mamy nastpujcy wynik:
podaj znak : d
wprowadzony znak : d
podaj l i c z b e r z e c z y w i s t a : 1 3 . 1 3
wprowadzona l i c z b a : 1 3 . 1 3

W tym przykadzie wprowadzamy z klawiatury warto danego typu, a


nastpnie na ekranie monitora mamy wywietlon warto naszej danej. W
tym konkretnym przypadku testowano obsug znakw i liczb zmiennoprzecinkowych. Implementacja oglnej klasy war ma posta:
template <c l a s s T>
c l a s s war
{
T n;
// t y p o g o l n y T
public :
war ( ) : n ( 0 ) { }
// k o n s t r u k t o r
void set_war ( ) { c i n >> n ; }
void pokaz ( )
{ c o u t << n << e n d l ; }
};

Widzimy deklaracj szablonu klasy:


template <c l a s s T >
c l a s s war

Specykacja szablonu klasy (nasza klasa nosi nazw war) zawiera sowo
kluczowe template a po nim mamy nawiasy ostre <>. Wewntrz nawiasw umieszczamy identykator, ktry reprezentuje typ sparametryzowany,
w naszym przykadzie jest to identykator o nazwie T. Nazwa identykatora moe by dowolna. Wewntrz nawiasw mona umieci wicej ni jeden
identykator, np. :
< c l a s s T, c l a s s W >

Parametry musz by oddzielone przecinkami. Na licie parametrw szablonu mog by dowolne elementy, moemy mie parametry oglne i konkretne:
< c l a s s T1 , i n t n , c l a s s T2 >

Jako parametry szablonu mog by uywane typy wbudowane, typy zdeniowane przez uytkownika oraz wyraenia stae. Klasa war posiada jedn
dan skadow prywatn typu oglnego:
T n;

jeden konstruktor bezargumentowy

295

296

12. Szablony w C++


war ( ) : n ( 0 ) { }

// k o n s t r u k t o r

konstruktor moemy zdeniowa bardziej czytelnie:


war ( )
{ n = 0;
}

oraz dwie publiczne funkcje skadowe:


void set_war ( ) { c i n >> n ; }
void pokaz ( )
{ c o u t << n << e n d l ; }

funkcja set war () wczytuje z klawiatury warto skonkretyzowanego typu,


a funkcja pokaz() wywietla t warto.
Gdybymy nie uywali szablonu klasy, to powinnimy zadeklarowa tyle
klas ile typw chcielibymy obsuy. W przypadku szablonu klas mamy jedn deklaracje, a dla danego typu konkretyzujemy nasz klas. Konkretyzacja
nastpuje gdy uyta bdzie nazwa szablonu klasy. Gdy chcemy utworzy
egzemplarz klasy dla liczb zmiennoprzecinkowych, to denicja egzemplarza
klasy ma posta:
war <double> x ;

// o b s l u g a t y p u d o u b l e

W pokazanym przykadzie identykator x jest obiektem klasy typu war


<double> , dziki czemu moemy obsugiwa zmienne typu double. Nastpuje konkretyzacja typu oglnego i nasza klasa przyjmuje posta:
c l a s s war
{
double n ;
public :
war ( ) : n ( 0 ) { }
// k o n s t r u k t o r
void set_war ( ) { c i n >> n ; }
void pokaz ( )
{ c o u t << n << e n d l ; }
};

W pokazanym programie wykonalimy dwie konkretyzacje klasy szablonu


dla typw char i double. Jeeli chcemy zdeniowa jak funkcj poza szablonem klasy (np. w naszym przypadku funkcj pokaz() ) musimy uy
nastpujcej deklaracji i denicji tej funkcji (pokazano take szablon funkcji):
template <c l a s s T>
c l a s s war
{
T n;
public :
war ( ) : n ( 0 ) { }

// t y p o g o l n y T
// k o n s t r u k t o r

12.4. Szablony klas


void set_war ( ) ;
void pokaz ( ) ;
};

template <c l a s s T>


void war < T > : : set_war ( )
{ c i n >> n ; }
template <c l a s s T>
void war < T > : : pokaz ( )
{ c o u t << n << e n d l ; }

Bardzo czsto stosujemy szablony klas do stworzenia tzw. klasy kontenerowej (ang. container class). Klasa kontenera to taka klasa, ktra deniuje
kolekcj obiektw. Do obsugi tablic, list powizanych czy stosw wykorzystujemy klasy kontenerowe, poniewa moemy napisa uniwersaln aplikacje
obsugujca rne typy danych. W kolejnym przykadzie pokaemy klas
kontenera do obsugi tablic.
Listing 12.21. Uycie wzorcw klas klasa kontenera (tablica)
1

11

13

15

17

19

21

23

25

27

#include <i o s t r e a m >


#include <c o n i o . h>
using namespace s t d ;
template <c l a s s T>
class t a b l i c a
{
int rozmiar ;
T tab ;
public :
t a b l i c a ( int r = 1)
{ rozmiar = r ;
tab = new T [ r ] ;
}
void i n i t _ t a b ( ) ;
void pokaz_tab ( ) ;
~ t a b l i c a ( ) { delete [ ] tab ; }
};
template <c l a s s T>
void t a b l i c a < T > : : i n i t _ t a b ( )
{ f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
{ c o u t << " dane : " ;
c i n >> tab [ i ] ;
}
}
template <c l a s s T>
void t a b l i c a < T > : : pokaz_tab ( )

297

298

12. Szablony w C++


{ f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
c o u t << tab [ i ] << " " ;
c o u t << e n d l ;
}

29

31

33

35

37

39

41

i n t main ( )
{ t a b l i c a <int> t 1 ( 4 ) ;
c o u t << " podaj l i c z b y c a l k o w i t e : " << e n d l ;
t1 . init_tab ( ) ;
c o u t << " e l e m e n t y t a b l i c y : " ;
t 1 . pokaz_tab ( ) ;
t a b l i c a <char> t 2 ( 5 ) ;
c o u t << " podaj z n a k i : " << e n d l ;
t2 . init_tab ( ) ;
c o u t << " e l e m e n t y t a b l i c y : " ;
t 2 . pokaz_tab ( ) ;

43

45

47

getche () ;
return 0 ;

49

51

Po uruchomieniu tego programu mamy wynik:


podaj l i c z b y c a l k o w i t e :
dane : 3
dane : 5
dane : 8
dane : 2
elementy t a b l i c y : 3 5 8 2
podaj z n a k i :
dane : w
dane : a
dane : c
dane : e
dane : k
elementy t a b l i c y : w a c e k

Szablon klasy dla oglnej klasy tablica ma posta:


template <c l a s s T>
class t a b l i c a
{
int rozmiar ;
T tab ;
public :
t a b l i c a ( int r = 1)
{ rozmiar = r ;
tab = new T [ r ] ;

12.4. Szablony klas


}
void i n i t _ t a b ( ) ;
void pokaz_tab ( ) ;
~ t a b l i c a ( ) { delete [ ] tab ; }
};

W szablonie klasy mamy zadeklarowany wymiar tablicy - zmienna rozmiar


oraz wskanik do tablicy tab, w momencie konkretyzacji parametr T bdzie
zamieniany na typ obsugiwanej klasy ( w naszym przypadku int i char).
Mamy rwnie konstruktor, ktry przydziela pami dynamiczn dla tablicy
dla danej iloci elementw take destruktor do zwalniania pamici. W
instrukcji:
t a b l i c a <int> t 1 ( 4 ) ;

konkretyzujemy tablic o nazwie t1 aby moga obsugiwa elementy typu int,


rezerwujemy pami dla czterech elementw typu int. Podobnie w instrukcji:
t a b l i c a <char> t 2 ( 5 ) ;

deniowana jest tablica o nazwie t2 i przydzielana jest pami dla 5 obiektw


typu char.
W klasie tablica zadeklarowane s dwie funkcje skadowe: init tab() oraz
pokaz tab().
Funkcja init tab() suy do wprowadzania wartoci elementw tablicy z
klawiatury.
template <c l a s s T>
void t a b l i c a < T > : : i n i t _ t a b ( )
{ f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
{ c o u t << " dane : " ;
c i n >> tab [ i ] ;
}
}

Jak wida z nagwka tej funkcji:


template <c l a s s T>
void t a b l i c a < T > : : i n i t _ t a b ( )

jest ona metod klasy tablica (wchodzi w skad szablonu klasy), zdeniowana jest na zewntrz klasy (klasa ma tylko jeden parametr typu), nie zwraca
adnej wartoci. Funkcja ta znajduje si w zasigu egzemplarza klasy tablica,
zdeniowana jest z typem oglnym T.
Funkcja pokaz tab() suy do wywietlania elementw tablicy:

299

300

12. Szablony w C++


template <c l a s s T>
void t a b l i c a < T > : : pokaz_tab ( )
{ f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
c o u t << tab [ i ] << " " ;
c o u t << e n d l ;
}

Niejawnie czsto zakadamy, e przygotowany algorytm bdzie dziaa tak


samo dla rnych typw danych, co zreszt lego u podstaw tworzenia szablonw funkcji i klas. ycie programisty jest jednak bardziej skomplikowane
ni to si wydaje. Niekiedy okazuje si, e program nie jest w stanie korzystajc z jednego szablonu klasy obsuy wszystkich danych jakie bdzie
chcia wprowadzi uytkownik.
Aby powsta bardziej uniwersalny program musimy uwzgldni przypadek szczeglny, najczciej dopisa now klas. Jzyk C++ udostpnia
technik specjalizacji (ang. specialization), dziki ktrej moemy konkretny
typ danych obsugiwa w sposb specjalny.
Rozwamy ponownie opisany powyej program w ktrym obsugujemy
tablice rnych typw danych. Program dziaa poprawnie na prostych typach (int, double, char, itp.). W programie mamy szablon klasy tablica.
Klasa szablonu zawiera konstruktor, destruktor, oraz metody init tab() oraz
pokaz tab(). Konstruktor przydziela dynamicznie danym wejciowym przestrze na stercie. Destruktor zwalnia pami przydzielon na stercie. Przy
pomocy metody init tab() wprowadzamy elementy tablicy danego typu z
klawiatury, metoda pokaz tab() powoduje wywietlenie elementw tablicy
na ekranie. Kod klienta tworzy obiekty klasy tablica dla wybranych typw
danych, w naszym przykadzie mamy nastpujce obiekty:
t a b l i c a <int> t 1 ( 4 ) ;

oraz
t a b l i c a <char> t 2 ( 5 ) ;

Obsuga danych typu int i typu char jest poprawna. Gdy uytkownik sprbuje obsuy napis (tablic znakow) w postaci:
t a b l i c a <char> t 3 ( 5 ) ;

pojawi si problem, program nie uruchomi si gdy napisy wprowadza bdziemy z klawiatury. Musimy na nowo opracowa klas obsugujc tablic
znakow, w praktyce oznacza to dopisanie klasy specjalnej, korzystajc z
mechanizmu specjalizacji jawnej (ang. explicit specialization). W programie
musi by zarwno szablon klasy oglny oraz jego specjalizacja. Nie mona

301

12.4. Szablony klas


umieszcza w programie samej specjalizacji szablonu bez szablonu klasy
oglnej. Specjalizacja szablonu jest konkretyzowana przy uyciu tej samej
skadni, co w przypadku obiektu klasy szablonu. Jawna specjalizacja to denicja konkretnego typu lub typw, ktre maj zosta uyte zamiast szablonu
oglnego. Zamy, e mamy zdeniowany szablon klasy tablica :
template <c l a s s T>
// Klasa s z a b l o n u
class t a b l i c a
{
int rozmiar ;
T tab ;
// t a b l i c a danych na s t e r c i e
public :
t a b l i c a ( i n t r = 1 ) // k o n s t r u k t o r p r z y d z i e l a pamiec
// na s t e r c i e
{ rozmiar = r ;
tab = new T [ r ] ;
}
void i n i t _ t a b ( ) ; // metoda do wprowadzani a
// elementow t a b l i c y
void pokaz_tab ( ) ; // metoda do w y s w i e t l e n i a
// elementow t a b l i c y
~ t a b l i c a ( ) { delete [ ] tab ; }
};

Wiemy, e potrzebujemy innej denicji klasy do obsugi tablicy znakw.


Specjalizacja klasy tablica moe mie posta:
template <>
// p u s t a l i s t a s z a b l o n u
c l a s s t a b l i c a <char >
// t y p s p e c j a l i z a c i n a p i s y
int rozmiar ;
// r o z m i a r t a b l i c y
{
char tab ;
// t a b l i c a napi sow na s t e r c i e
public :
t a b l i c a ( int r = 1)
{ rozmiar = r ;
tab = new ( char [ r ] ) ;
}
void i n i t _ t a b ( ) ;
void pokaz_tab ( ) ;
~ t a b l i c a ( ) { delete [ ] tab ; }
};

Skadnia opisu specjalizacji jest poczeniem skadni dla samych szablonw


(z list parametrw szablonu) oraz inicjalizacji szablonu w kodzie klienta
(z list typw rzeczywistych). Jeeli nagwek szablonu klasy oglnej ma
posta:
template <c l a s s T>
class t a b l i c a
{
......

// Klasa s z a b l o n u

302

12. Szablony w C++


to nagwek specjalizacji szablonu klasy moe mie posta:
template <>
c l a s s t a b l i c a <char >
{

// p u s t a l i s t a s z a b l o n u
// t y p s p e c j a l i z a c i n a p i s y

W kodzie klienta specjalizowany obiekt szablonu ma posta:


t a b l i c a <char> t 2 ( 5 ) ;

// o b i e k t t a b l i c y s p e c j a l i z o w a n y

Na kolejnym listingu pokazano program zawierajcy szablon klasy tablica oraz jego specjalizacj dla danych typu tablicy znakowej.
Listing 12.22. Specjalizacja klasy szblonu klasa kontenera (tablica)
1

#include <i o s t r e a m >


#include <c o n i o . h>
#include <s t r i n g . h>
using namespace s t d ;

11

template <c l a s s T>


class t a b l i c a
{
int rozmiar ;
T tab ;
public :
t a b l i c a ( int r = 1)

// Klasa s z a b l o n u

// t a b l i c a danych na s t e r c i e
// k o n s t r u k t o r p r z y d z i e l a
// pamiec na s t e r c i e

{ rozmiar = r ;
tab = new T [ r ] ;
}
void i n i t _ t a b ( ) ;

13

15

// metoda do wprowadzani a
// elementow t a b l i c y
void pokaz_tab ( ) ;
// metoda do w y s w i e t l e n i a
// elementow t a b l i c y
~ t a b l i c a ( ) { delete [ ] tab ; }

17

19

21

};

23

template <c l a s s T>


void t a b l i c a < T > : : i n i t _ t a b ( )
{ c o u t << " dane : " << e n d l ;
f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
c i n >> tab [ i ] ;
}

25

27

29

31

33

35

template <c l a s s T>


void t a b l i c a < T > : : pokaz_tab ( )
{ f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
c o u t << tab [ i ] << " " ;
c o u t << e n d l ;
}
//s p e c j a l i z a c j a

12.4. Szablony klas


37

39

41

43

45

47

49

template <>
// p u s t a l i s t a s z a b l o n u
c l a s s t a b l i c a <char >
// t y p s p e c j a l i z a c j i n a p i s y
int rozmiar ;
// r o z m i a r t a b l i c y
{
char tab ;
// t a b l i c a napi sow na s t e r c i e
public :
t a b l i c a ( int r = 1)
{ rozmiar = r ;
tab = new ( char [ r ] ) ;
}
void i n i t _ t a b ( ) ;
void i n i t _ t a b 1 ( ) ;
void pokaz_tab ( ) ;
~ t a b l i c a ( ) { delete [ ] tab ; }
};

51

53

55

57

void t a b l i c a <char> : : i n i t _ t a b ( )
{ c o u t << " dane : " << e n d l ;
f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
{ tab [ i ]= new ( char [ 2 0 ] ) ;
c i n >> tab [ i ] ;
}
}

59

61

63

void t a b l i c a <char> : : pokaz_tab ( )


{ f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
c o u t << tab [ i ] << e n d l ;
}
//k o n i e c s p e c j a l i z a c j i

65

67

69

71

73

75

77

79

81

83

85

87

i n t main ( )
{ int i l e = 1 ;
c o u t << " i l e elementow t a b l i c y : " ;
c i n >> i l e ;
t a b l i c a <int> t 1 ( i l e ) ; // o b i e k t t a b l i c y i n t
c o u t << " l i c z b y c a l k o w i t e , i l o s c = "<< i l e << e n d l ;
t1 . init_tab ( ) ;
c o u t << " e l e m e n t y t a b l i c y : " ;
t 1 . pokaz_tab ( ) ;
c o u t << " i l e elementow t a b l i c y : " ;
c i n >> i l e ;
t a b l i c a <char> t 2 ( i l e ) ; // o b i e k t t a b l i c y s p e c j a l i z o w a n y
c o u t << " n a p i s y , i l o s c = " << i l e << e n d l ;
t2 . init_tab ( ) ;
c o u t << " e l e m e n t y t a b l i c y : " << e n d l ;
t 2 . pokaz_tab ( ) ;
c o u t << " i l e elementow t a b l i c y : " ;
c i n >> i l e ;
t a b l i c a <char> t 3 ( i l e ) ; // o b i e k t t a b l i c y c h a r
c o u t << " z n a k i , i l o s c = " << i l e << e n d l ;
t3 . init_tab ( ) ;
c o u t << " e l e m e n t y t a b l i c y : " ;

303

304

12. Szablony w C++


t 3 . pokaz_tab ( ) ;
89

getche () ;
return 0 ;

91

Po uruchomieniu tego programu moemy mie nastpujcy wynik:


i l e elementow t a b l i c y : 3
l i c z b y calkowiet , i l o s c = 3
dane :
1 2 3
elementy t a b l i c y : 1 2 3
i l e elementow t a b l i c y : 3
napisy , i l o s c = 3
dane :
Ala Ola L o l a
elementy t a b l i c y :
Ala
Ola
Lola
i l e elementow t a b l i c y : 5
znaki , i l o s c = 5
dane :
W a c e k
elementy t a b l i c y : W a c e k

W naszym programie specjalizacja szablonu klasy zostaa specjalnie opracowana aby poprawnie obsugiwa tablic znakow. W szablonie klasy oglnej
deklaracja tablicy danych oraz konstruktor maj posta :
template <c l a s s T>
// Klasa s z a b l o n u
class t a b l i c a
{
int rozmiar ;
T tab ;
// t a b l i c a danych na s t e r c i e
public :
t a b l i c a ( i n t r = 1 ) // k o n s t r u k t o r p r z y d z i e l a
// pamiec na s t e r c i e
{ rozmiar = r ;
tab = new T [ r ] ;
}
.....................

Specjalizacja klasy ma posta:


template <>
c l a s s t a b l i c a <char >
int rozmiar ;
{
char tab ;
public :

//
//
//
//

pusta l i s t a szablonu
typ s p e c j a l i z a c j i napisy
rozmiar t a b l i c y
t a b l i c a napi sow na s t e r c i e

12.4. Szablony klas


t a b l i c a ( int r = 1)
{ rozmiar = r ;
tab = new ( char [ r ] ) ;
}
.......................

Mamy take rnie zdeniowane metody init tab(). W klasie oglnej mamy:
template <c l a s s T>
void t a b l i c a < T > : : i n i t _ t a b ( )
{ c o u t << " dane : " << e n d l ;
f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
c i n >> tab [ i ] ;
}

Metoda init tab() w specjalizacji klasy ma posta:


void t a b l i c a <char> : : i n i t _ t a b ( )
{ c o u t << " dane : " << e n d l ;
f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
{ tab [ i ]= new ( char [ 2 0 ] ) ;
c i n >> tab [ i ] ;
}
}

Pokazana metoda obsuguje dynamiczna tablic wskanikw na napisy staej


dugoci. Moliwa jest modykacja tej metody tak aby obsugiwaa dynamiczna tablic wskanikw na napisy zmiennej dugoci :
void t a b l i c a <char> : : i n i t _ t a b ( )
{ char temp [ 5 0 ] ;
tab = new char [ r o z m i a r ] ;
f o r ( i n t i = 0 ; i < r o z m i a r ; i ++)
{ c o u t << " dane : " ;
c i n >> temp ;
tab [ i ]= new ( char [ s t r l e n ( temp ) + 1 ] ) ;
s t r c p y ( tab [ i ] , temp ) ;
}
}

Zwolnienia pamici zajmowanej przez napisy naley wykona osobno dla


kadego napisu wedug adresu przechowywanego w elemencie tablicy wskanikw, a nastpnie zwolni pami przydzielon na tablice wskanikw:
f o r ( i = 0 ; i < r o z m i a r ; i ++)
delete [ ] tab [ i ] ;
delete [ ] Tb ;

305

Sownik angielsko-polski

abstrakt data type (ADT)

access specycation

accessor function

address
allocate
application

argument
assertion

A
abstrakcyjny typ danych, zazwyczaj
tym terminem okrelamy typ danych zdeniowany przez programist, typowym przykadem jest klasa jest to formalnie typ danych,
proces opisywania funkcji klasy niezalenie od jej implementacji, nazywamy abstrakcj danych
specykacja dostpu, etykiety private, public oraz protected reguluj
dostp do skadowych klasy (danych
i metod)
funkcje dostpu, s to funkcje (z wyjtkiem konstruktorw), ktre maja
dostp do prywatnych (private) danych klasy
adres, jest to liczba wskazujca
miejsce w pamici,
alokacja, przydzia (pamici)
aplikacja, program komputerowy,
traktowany przez uytkownika jako
jednostka
argument, warto przekazywana do
funkcji
asercja, instrukcja mwica, e element umieszczony w danym miejscu
w programie musi by prawdziwy

308

Sownik angielsko-polski

base class

binding

bit eld
boolean
bug

call
call by reference

call by value

calling constructors

calling function

cast
cast operator

B
klasa bazowa, jest to klasy na podstawie ktrej, tworzona jest klasa
pochodna, wykorzystujca mechanizm dziedziczenia
wizanie, interpretacja wywoania
funkcji w kodzie rdowym w celu
wykonania kodu waciwej funkcji
pole bitowe
wbudowany typ logiczny, wartoci
true lub false
bd w kodzie, nieoczekiwana sytuacja, termin wprowadzony przez dr
Grace Hopper, ktra bya kontradmiraem w U.S.Navy
C
wywoa, wywoanie
proces deklarowania parametru
funkcji jako zmiennej wskanikowej,
w konsekwencji przekazanie adresu
jako argument
proces deklarowania parametru
funkcji jako zmiennej prostej (nie
wskanikowej), w konsekwencji
przekazanie wartoci zmiennej jako
argument
wywoywanie konstruktora, konstruktor jest wywoywany zawsze,
gdy obiekt jest kreowany, jest wiele
sposobw wywoywania konstruktora
wywoanie funkcji, przekazanie sterowania do funkcji, ktra wykona
dane operacje
dosowna, wymuszona, konwersja
typw, np. (double)i
operator konwersji, suy do wymuszenia konwersji, np. zapis int (x*y)
powoduje, e warto wyraenia x*y
jest konwertowana do wartoci typu
int.

309

Sownik angielsko-polski
child class
cin

class

access specier
access specier

declaration
destructor

implementation

inheritance

instance variables

klasa potomna, pochodna klasa


utworzona z klasy bazowej
obiekt cin jest wykorzystywany
do wprowadzania danych, denicja
obiektu cin jest zawarta w pliku iostream, plik iostream opisuje strumie wejciowy, operator wykorzystywany jest do pobrania znakw
ze strumienia wejciowego
klasa, typ zdeniowany przez programist, kluczowy element programowania obiektowego
specykator dostpu (private, public, protected)
konstruktor klasy, jest to metoda
klasy, suy wycznie do tworzenia
nowego obiektu klasy i przypisywania wartoci jego skadowym, nazwa
konstruktora jest identyczna z nazw klasy, konstruktor nie posiada
typu zwracanego
deklaracja klasy, opisuje komponenty klasy: dane i metody,
destruktor, jest to specjalna funkcja klasy, nazwa destruktora jest taka sama jak nazwa klasy, poprzedzona jest znakiem tyldy, destruktor jest automatycznie wywoywany,
gdy obiekt jest niszczony
implementacja klasy, termin oznacza denicje metod klasy, tzn. kod
funkcji skadowych klasy
dziedziczenie, jest to cecha jzyka,
pozwala na tworzenie nowej klasy z
klasy ju istniejcej.
(data members), dane klasy, pola
klasy, zmienne klasy

310

Sownik angielsko-polski
library

members

methods
scope

collating sequence
command line
compiler error
compile-time errors
concatenate
console application

constructor

base class

biblioteka klas, jest to biblioteka testowanych i pozbawionych bdw


klas, z ich interfejsami i implementacjami, zazwyczaj interfejs jest
umieszczany w pliku nagwkowym,
a implementacja jest umieszczana w
oddzielnym pliku z implementacjami (implementation le)
czonkowie klasy, zmienne i funkcje wyszczeglnione sekcji deklaracji
klasy
metody, funkcje skadowe klasy
zasig klasy, generalnie zasig (scope) oznacza zakres widocznoci nazwy w obrbie pliku, dane skadowe
i metody klasy cechuje zasig klasy, te elementy s znane i widoczne
w obrbie klasy , poza ni s niewidoczne
porzdek sortowania
wiersz polece
bd kompilacji
bdy wykryte w fazie kompilacji,
(bdy syntaktyczne)
poczenie, sklejenie wielu acuchw w jeden acuch
proces tworzenia prostych programw na platformach programistycznych z pominiciem okienek
konstruktor klasy, jest to metoda
klasy, suy wycznie do tworzenia
nowego obiektu klasy i przypisywania wartoci jego skadowym, nazwa
konstruktora jest identyczna z nazw klasy, konstruktor nie posiada
typu zwracanego
konstruktor klasy bazowej

311

Sownik angielsko-polski
copy

conversion

default

inline
overloaded
containers

conversion automatic
cout

debugging
declaration

konstruktor kopiujcy wykorzystywany jest do kopiowania istniejcego obiektu do nowo utworzonego obiektu, gdy klasa nie zawiera wskanikowych danych, zazwyczaj nie ma problemw z konstruktorem kopiujcym ( nie s generowane bdne wyniki), gdy w klasie wystpuj zmienne wskanikowe, naley opracowa wasny konstruktor
kopiujcy
konstruktor przeksztacajcy, jest
to konstruktor jednoargumentowy,
ktry umoliwia przeksztacenie
obiektu jednego typu (wcznie z
typami wbudowanymi) na obiekt
danej klasy
konstruktor domylny, jeeli programista nie dostarczy wasnego konstruktora klasy, kompilator dostarczy konstruktor domylny
konstruktor typu inline
konstruktor przeciony
kontenery, zasobniki (podstawowe
elementy STL), kontener jest to
obiekt, ktry moe przechowywa
inne obiekty
proces ujednolicania typw w wyraeniach mieszanych
obiekt cout jest wykorzystywany
do wyprowadzania danych, denicja
obiektu cout jest zawarta w pliku iostream, plik iostream opisuje strumie wyjciowy, operator wykorzystywany jest do wstawiania znakw do strumienia wyjciowego
D
debugowanie, proces usuwania bdw w programach komputerowych
deklaracja, specykowanie typw

312

Sownik angielsko-polski
denition
dynamic allocation

delete operator
derived class

destructor

dynamic binding

dynamic memory allocation

eciency

encapsulation

equality expression
errors
compile-time
logic
programming

denicja, jest to deklaracja alokujca pami


dynamiczne przydzielanie pamici
jest procesem zachodzcym w czasie wykonywania program w odrnieniu od statycznego procesu przydzielania pamici w fazie kompilacji
operator zwalniania pamici, zwalnia zarezerwowan pami
klasa pochodna, klasa potomna,
subklasa, klasa utworzona z innej
klasy (klasy bazowej) dziki mechanizmowi dziedziczenia
destruktor, jest to specjalna funkcja klasy, nazwa destruktora jest taka sama jak nazwa klasy, poprzedzona jest znakiem tyldy, destruktor jest automatycznie wywoywany,
gdy obiekt jest niszczony
wizanie dynamiczne, wizanie pne, wybr odpowiedniej funkcji w
fazie wykonania programu
dynamiczny przydzia pamici,
przydzia pamici w fazie wykonania programu, wykorzystywane s
operatory new i delete
E
wydajno, wydajno programu
oznacza zadawalajc szybko wykonania, majc do dyspozycji okrelone zasoby
enkapsulacja, hermetyzacja, ukrywanie danych i metod, jest to
podstawowa cecha programowania
obiektowego
wyraenie relacyjne rwnoci
bdy
bdy wykryte w czasie kompilacji
bdy logiczne
bdy programistyczne

313

Sownik angielsko-polski
run-time
syntax
typos
evaluate
exit

executable program
exponential notation

expression
extern

external storage class


extraction operator

eld
le
FIFO
ag

ow of control
ush
formal parameter

bdy wykryte w czasie wykonywania programu


bdy syntaktyczne
bdy typograczne
ocena, wyznaczanie wartoci
wyjcie, najczciej jest to zakoczenie wykonywania programu lub jakiej jego czci
program wykonywalny
notacja wykadnicza, jest to sposb zapisywania liczb (np. w notacji
wykadniczej zapis 1.625e3 oznacza
liczb dziesitn 1625, a w notacji
naukowej liczb 1.625x103)
wyraenie
typ zewntrznej klasy pamici, gdy
zmienna jest deklarowana poza
funkcj (zmienna globalna), przydziela sie jej pami klasy extern
zewntrzna klasa pamici
operator pobierania (symbol ), pobiera znaki ze strumienia wejciowego
F
pole, skadnik struktury
plik, jest to obszar, w ktrym przechowywane s dane
rst in, rst out, pierwszy na wejciu, pierwszy na wyjciu
znacznik, aga, rnorodne znaczniki formatu, okrelaj rodzaj formatowania wykonywanego w czasie operacji strumieniowych wejcia/wyjcia, funkcje skadowe setf,
unsetf oraz ags kontroluj ustawienia
przebieg sterowania
oprni (bufor)
parameter formalny

314

Sownik angielsko-polski
forward declaration

friend function

function

function
argument list
arguments structure
array arguments
call by reference
call by value
formal parameter
friend
header
inline

pointer parameter
type specier
virtual

function binding

deklaracja wyprzedzajca, deklaracja zapowiadajca, tej konstrukcji


uywa si w przypadku, gdy w klasie
nastpuje odwoanie do innej, niezadeklarowanej klasy
funkcja zaprzyjaniona, funkcja nie
bdca metod klasy, ktrej przyznano prawo dostpu do danych i
metod prywatnych i chronionych
funkcja, nazwany, wydzielony fragment programu, logiczna jednostka
przetwarzania
funkcja
lista parametrw (argumentw)
parametry typu struktury przekazywane do funkcji
argument tablicowe
wywoanie funkcji z argumentem typu wskanikowego
wywoanie funkcji z argumentem
prostym (nie wskanikowym)
parametry formalne
funkcja zaprzyjaniona
nagwek funkcji
funkcja inline, funkcja wbudowana, funkcja wplatana, s to krtkie
funkcje, ich wystpienia w programie s zastpowane ich kodem ( mechanizm bardzo podobny do tworzenia makr)
parameter typu wskanikowego
przekazywany do funkcji
specykator typu zwracanej wartoci funkcji
funkcja wirtualna, jest to metoda
(funkcja skadowa) zadeklarowana
w klasie bazowej i zdeniowana w
klasie pochodnej
wizanie funkcji, mamy wizanie
statyczne (aktywne w czasie kompilacji) oraz dynamiczne (aktywne w
fazie wykonania program)

315

Sownik angielsko-polski
function template

header le
heap
hexadecimal
high level languages
high-order bit

IDE

implementation
indirect addressing

indirection operator

inheritance

initialization
array
pointer
string
structure

wzorzec funkcji, szablon funkcji,


funkcja oglna, jest to zdeniowana
kompletna funkcja, ktra jest wzorcem (modelem) dla rodziny funkcji,
w denicji wykorzystuje si symboliczny typ, ktry podczas wywoywania funkcji przeksztacany jest na
rzeczywisty typ danych
H
plik nagwkowy (doczany do program)
stos, sterta (typ pamici)
szesnastkowy (system liczbowy)
jzyk programowania wysokiego poziomu
bit najbardziej znaczcy
I
Integrated Development Environment, zintegrowane rodowisko programistyczne
implementacja, pisanie i testowanie
program, take kod programu
adresowanie porednie, jest to
procedura otrzymywania wartoci zmiennej na ktra wskazuje
wskanik
operator dereferencji, operator wyuskania (symbol *), zapis *p oznacza: zmienna, ktrej adres jest
przechowywany w p
dziedziczenie, proces kreowania klasy przez wykorzystanie ju istniejcej klasy, istniejca klasa nazywana
jest klas bazow, nowa klas nosi nazw klasy pochodnej
inicjalizacja
inicjalizacja tablicy
inicjalizacja wskanika
inicjalizacja acucha
inicjalizacja struktury

316

Sownik angielsko-polski
variable
inline declaration
inline function
input/output

input/output stream

insertion operator

instance variables
integer overow

interface
invariant

item
iteration

languages
high-level
low-level
middle-level

inicjalizacja zmiennej
deklaracja funkcji wbudowanej (inline)
funkcja wbudowana, funkcja rozwijalna
wejcie/wyjcie, dane wejciowe s
wprowadzane do programu, dany
wyjciowe s wytwarzanie w procesie przetwarzania
strumienie wejcia/wyjcia, strumie to urzdzenie logiczne produkujce lub pobierajce informacje
operator wstawiania (symbol ) potrzebny jest do wywietlania danych
typw wbudowanych, sam operator
wstawia znaki do strumienia
dane klasy (data members), czonek
klasy
przepenienie, w czasie wykonania
program moe wystpi przekroczenie zakresu np. dla liczb cakowitych, mona otrzyma niepoprawny
wynik
interfejs, zbir deklaracji, potrzebnych do wywoywania funkcji
niezmiennik, element, ktry w danym punkcie programu musi by
prawdziwy
pozycja, skadnik, egzemplarz
iteracja, wielokrotne wykonywanie
tych samych instrukcji

L
jzyki (programowania)
jzyki programowania wysokiego
poziomu
jzyki programowania niskiego poziomu
jzyki programowania redniego poziomu

317

Sownik angielsko-polski
object-oriented
procedure-oriented
library

LIFO
linker

logical expression
low-order bit

machine accuracy

machine language

macro
manipulator

mask

mathematical library
member

jzyki programowania zorientowane


obiektowo
proceduralne jzyki programowania
biblioteka, kolekcja plikw, pliki zawieraj typy, funkcje, struktury, dyrektywy, itp., ktre s wykorzystywane w wielu programach
last in, rst out, ostatni na wejciu,
pierwszy na wyjciu
konsolidator, program czcy, jest
to program ktry konsoliduje inne
pliki i biblioteki z programem obiektowym, w ten sposb otrzymujemy
program wykonywalny
wyraenie logiczne
bit najmniej znaczcy
M
dokadno maszyny liczcej, w metodach numerycznych wygodnie jest
zadeklarowa eps (epsilon maszynowe) jako najmniejsz dodatni
warto zmiennej typu double speniajc warunek, e wyraenie 1.0 <
1.0 + eps jest prawdziwe
jzyk maszynowy ( wewntrzny jzyk komputerowy, skadajcy si z
serii zer i jedynek)
makrodenicji (zdeniowany skrt,
konstrukcje preprocesora)
manipulator, jest to specjalna funkcja, pozwalajca na kontrolowanie
formatu danych podczas operacji
wejcia/wyjcia
maska, jest to staa lub zmienna,
ktra jest wykorzystywana do wyodrbnienia danego bitu w zmiennej,
uywana w operacjach bitowych
funkcje matematyczne zebrane w
pliku (np. <math.h>
element struktury oraz klasy, skadnik struktury oraz klasy

318

Sownik angielsko-polski
member function
memory dynamic allocation

methods

multi-dimensional arrays
multiple inheritance

namespaces

new operator
null character
null pointer value

object

object program

one-dimensional array
operator

funkcja skadowa (metoda klasy)


dynamiczna alokacja pamici, programista kontroluje przydzia pamici przy pomocy operatorw new
i delete
metody klasy, funkcje skadowe, metoda operuje na danych skadowych
klasy
wielowymiarowa tablica
wielo-bazowe dziedziczenie, tworzenie klasy pochodnej jednoczenie z
dwch lub wicej klas bazowych, naley korzysta z tego mechanizmu z
umiarem
N
przestrze nazw, jest to koncepcja
pozwalajca nadawa nazwy blokom sekcji w programie, dziki tej
koncepcji mona stosowa te same
nazwy dla rnych zmiennych
operator new, suy do dynamicznego alokowania pamici
znak zera (symbol \0), uywany np.
do oznaczania koca acucha
wskanik zerowy, wskanik o wartoci 0 i NULL nie wskazuje adnej
wartoci
O
obiekt, podstawowy element programowania obiektowego, modeluje elementy wiata rzeczywistego, obiekty
s tworzone przy pomocy odpowiednio zaprojektowanych klas
program poredni, jest to kod wyprodukowany z kodu rdowego
przez kompilator, potrzebny jest
jeszcze konsolidator aby wyprodukowa kod wykonywalny
tablica jednowymiarowa
operator

319

Sownik angielsko-polski
addition
address
arithmetic
assignment
associativity
bitwise
bitwise exclusive or
cast (type)
comma
conditional
decrement
delete
dereferencing
division
equality
equals
greater than
greater than or equal to
increment
left shift
less than
less than or equal to
logical
logical and
logical negation
logical or
modulus
multiplication
not equals
ones complement
precedence
relational
right shift
sizeof

operator dodawania
aperator adresowy
operator arytmetyczny
operator przypisania
czenie operatora ( z lewej do prawej lub z prawej do lewej)
operator bitowy
operator alternatywy bitowej (symbol |)
operator rzutowania jawnego
operator przecinkowy
operator warunkowy (symbol (?:)
operator dekrementacji
operator zwalniania pamieci
operator dereferencji (wyuskania)
operator dzielenia
operator relacyjny (porwnywania)
operator rwnoci
operator relacyjny wikszy ni
operator relacyjny wikszy ni lub
rwny
operator inkrementacji
operator przesunicia bitowego
operator relacyjny mniejszy ni
operator relacyjny mniejszy ni lub
rwny
operator logiczny
operator logiczny koniunkcji (symbol &&)
operator logiczny zaprzeczenia
operator logiczny alternatywy (symbol ||)
operator modulo (symbol %)
operator mnoenia
operator relacyjny nierwny
operator bitowy dopenienia
priorytet operatora (okrela kolejno wykonywania operacji)
operator relacyjny
operator bitowy przesunicia w prawo
operator rozmiaru

320

Sownik angielsko-polski
structure member
structure pointer

subtraction
unary minus
ostream
output
overow
overloaded function

overloaded operators

parameter
parent class
parametrized manipulator

pass
pass by reference
pass by value
passing addresses
passing arrays
passing le names
passing structures
pointer

operator dostpu do elementu struktury (symbol . )


operator dostpu do element struktury przez wykorzystanie wskanika
(symbol >)
operator odejmowania
jednoargumentowy operator minus
jest to obsugujca operacje wyjcia
klasa pochodna klasy ios
wyjcie
przepenienie (kategoria bdu)
funkcja przeciona, w jzyku C++
ta sama nazwa funkcji moe by
uyta do rnych funkcji, pod warunkiem, e maj one rne listy argumentw
przecianie operatorw, jest to
technika pozwalajca na zmian
sposobu dziaania operatorw jzyka C++
P
parameter, argument
klasa bazowa, superklasa
sparametryzowane
manipulatory,
manipulatory
strumieniowe
s
funkcjami formatujcymi sposb
wyprowadzania danych a take
wykonuj pewne operacje na
danych wejciowych
przekaza (np. argumenty)
przekazanie argument przez referencj
przekazanie argumentu przez warto
przekazanie argumentu w postaci
adresu,
przekazanie tablicy
przekazanie nazwy pliku
przekazanie struktury
wskanik

321

Sownik angielsko-polski
argument
arithmetic

array
assignment
class members
constant
declaration
indirection
initialization
parameter
structure memebers
this

polymorphism

private access rights

program ow
promote

argument wskanikowy (np. przekazywanie wartoci do funkcji)


arytmetyka wskanikowa, specjalny
sposb manipulowania wskanikami
(np. inkrementacja)
tablica wskanikw
przypisanie wskanika (nadawanie
wartoci zmiennej wskanikowej)
wskanikowa skadowa klasy
staa wskanikowa
deklaracja wskanika
wskazanie porednie, dereferencja
zmiennej wskanikowej
inicjalizacja wskanika
parametr wskanikowy
wskanikowa skadowa struktury
wskanik this, kady obiekt ma dostp do swego adresu przy pomocy wskanika this, wskanik this
jest przekazywany do obiektu jako
niejawny pierwszy argument kadej
niestatecznej metody wywoywanej
na rzecz obiektu ( metody nie s kopiowane, s wsplne dla wszystkich
obiektw)
polimorzm
(wielopostaciowo),
podstawowy
paradygmat
programowania obiektowego, dziki
zastosowaniu funkcji wirtualnych
i polimorzmu moliwe jest projektowanie systemu, ktry jest
prosto rozszerzalny, mona pisa
program komputerowy w sposb
uniwersalny,
specykator dostp do danych i metod typu private (tylko metody maj dostp do danych)
przebieg programu
awansowa (typ)

322

Sownik angielsko-polski
protected access rights

public access rights

pseudo random number generator


push

random le access

random number generator


recursion
register
relational expression
reserved word (keyword)
round o error
run-time
run-time terror

specykator dostp do danych i metod typu protected , do skadowych


klasy bazowej zadeklarowanych jako protected dostp maj jedynie jej
metody oraz funkcje zaprzyjanione, a take metody oraz funkcje zaprzyjanione z klasy pochodnej,
specykator dostp do danych i metod typu public, dane i metody s
dostpne dla wszystkich elementw
programu (nie s chronione)
generator liczb pseudolosowych
pooy (na stosie)
R
bezporedni dostp do pliku ( kady
znak w pliku moe by odczytany
bezporednio)
generator liczb losowych
rekurencja, proces wystpuje, gdy
funkcja wywouje sama siebie
sowo kluczowe, klasa pamici register ( rejestr)
wyraenie relacyjne
sowo zastrzeone (kluczowe)
bd obcinania
czas wykonania (czas wykonywania
program)
bd wykonania programu
S

323

Sownik angielsko-polski
STL

scope
sequential access
shift
side eect
signicant gures
simple inheritance
stack
stack operation
standard library
standard template library
statement
static
static Winding
storage allocation
storage class
auto
extern
register
static
static external
string
array of character
constant
end-of-string character
initialization

Standardowa Biblioteka Wzorcw


(ang. Standard Template Library),
koncepcja STL zostaa opracowana przez Alexandra Stepanowa i
Menga Lee z Hewlett Packard, biblioteka wykorzystuje paradygmat
programowania oglnego oparta jest
na trzech skadnikach: zasobnikach
(kontenery), iteratorach i algorytmach, kontenery s uniwersalnymi
wzorcowymi strukturami danych,
zakres widocznoci (zmiennych)
dostp sekwencyjny
przesunicie
efekt uboczny
cyfry znaczce (zapis liczb zmiennoprzecinkowych)
dziedziczenie proste
stos
operacja na stercie (typ pamici)
biblioteka standardowa
Standardowa Biblioteka Wzorcw
instrukcja
typ klasy pamici, statyczna
wizanie statyczne (wczesne),
przydzia pamici
klasa pamici
klasa pamici typu auto
klasa pamici typu ex tern (zewntrzna)
klasa pamici typy register (rejestrowa)
klasa pamici typu static (statyczna)
klasa pamici typu static , zewntrzna
acuch, napis, w argonie - string
tablica znakw, napis, acuch
staa acuchowa
znacznik koca acucha (symbol
\0)
inicjalizacja acucha

324

Sownik angielsko-polski
library function
standard function
struct
subclass
superclass
syntax
syntax error

template prex

this pointer
top down
truncate
truth table
two-dimensional array
type mismatch
type specier
typedef

unary operator
union

variable
virtual functions

void

biblioteka funkcji obsugujcych napisy


standardowa funkcja obsugi acucha ( biblioteka string.h)
sowo kluczowe typy danych (struktura)
klasa pochodna
klasa bazowa (nadklasa)
syntaks
bd syntaktyczny
T
deklaracja wzorca, napis informuje
kompilator, e funkcja wystpujca za tym napisem (np. template
<class T> ) jest wzorcem (szablonem), ktry wykorzystuje uoglniony typ danych o nazwie T,
wskanik this
metoda top down jest jedn z
technik programowania
obci (cyfry znaczce)
tablica, ktra pokazuje wynik dziaania funkcji logicznych (bool)
tablica dwuwymiarowa
niezgodno typw
specykator typu
sowo kluczowe, z jego pomoc deniujemy wasny typ
U
operator jednoargumentowy
unia, struktura danych
V
zmienna
funkcje wirtualne, dziki funkcjom
wirtualnym i polimorzmowi moliwe jest projektowanie systemw,
ktre s w prosty sposb rozszerzane
typ danych, sowo kluczowe,

Skorowidz

#dene, 65
#include, 6
abort(), 237
abstrakcyjne funkcje, 194
abstrakcyjne klasy, 195
akcesory, 50
argumenty domylne funkcji, 11
asercja, 238
assert(), 237
auto, 323
bdy, 230
bool, 23
break, 256
bufor, 231
buforowane wejcie, 237
buforowane wyjcie, 231
catch, 239
cerr, 150
chronione dane, 96
chronione metody, 44
cin, 5
class, 42
clear(), 237
const, 67
cout, 5
dane chronione, 44, 96
dane prywatne, 44, 96
dane publiczne, 44
default, 311
denicja funkcji, 53
deklaracja dostpu, 44
deklaracja funkcji, 33
deklaracja struktury, 31
deklaracja zapowiadajca, 135
delete, 75

destruktor, 58
dynamiczna alokacja pamici, 318
dyrektywy preprocesora, 39
dziedziczenie, 84
egzemplarz, 58
endl, 44
exit, 231
aga, 313
friend, 123
funkcje, 29
funkcje abstrakcyjne, 194
funkcje matematyczne, 317
funkcje operatorowe, 146
funkcje prywatne, 44
funkcje przecione, 13
funkcje publiczne, 44
funkcje skadowe, 47
funkcje statyczne, 179
funkcje wirtualne, 178
funkcje zaprzyjanione, 122
getche, 124
ignore, 236, 237
indeks, 72
inicjalizacja tablic, 72
inline, 48
klasa, 32, 33
abstrakcyjna, 195
bazowa, 84
oglna, 293
pochodna, 84
wirtualna, 208
zagniedona, 215
zaprzyjaniona, 123
klasy zaprzyjanione, 141

326

Skorowidz
kod, 16
kompilacja, 40
koniec acucha, 80
konstruktor, 58
konwersja, 59, 269
kwalikator zakresu, 212
manipulatory, 167
namespace, 24
new, 72
obiekt, 42
operator alokacji dynamicznej, 75
operator przeciony, 144
pne wizanie, 191
pami, 20
polimorzm, 178
private, 44
protected, 44
prywatne dane, 44
prywatne funkcje, 44
prywatne skadowe, 44
prywatne zmienne, 44
przecianie konstruktorw, 272
przecizanie funkcji, 13
przecizanie operatorw, 144
przekazywanie argumentw przez referencj, 7, 129
przestrze nazw, 24
public, 44
referencje, 7
rekurencja, 322
rzutowanie, 207, 223
silnia, 235
sizeof, 146
specykator dostpu, 34
static, 60, 178
statyczne funkcje, 178
statyczne zmienne, 178
std, 24
stderr, 238
STL, 266
struct, 31
struktury, 31

strumienie, 4, 175
tablice, 71, 75
template, 266
this, 67
throw, 239
try, 239
typedef, 324
typename, 278
using, 24
wejcie, 5
what, 260
wizanie pne, 191
wizanie wczesne, 191
wskanik, 7, 45
wskanik do funkcji, 191, 270
wskanik do tablic, 299
wyjtek, 239
zaprzyjanione funkcje, 122
zaprzyjanione klasy, 141
zmienne globalne, 178
zmienne referencyjne, 127
zmienne statyczne, 60
zmienne wskanikowe, 9
znak zerowy, 80
zwalnianie pamici, 181

You might also like