Az operátorok precedenciája és asszociativitása
ACésaC+ + kulcsszavai
A táblázatban elóbb szere p lő operátorok magasabb precedenciával rendelkeznek, mint a kéKulcsszavak felbukkanók. A C+ + szabályai szerint egy kifejezés leg belső zárójelén belül található műve l eteket a program az operátorok precedenciájának sorrendjében vég zi el úgy, hogy and' (&&) a legmagasabb rangú operátorral e lőírt műve l etet hajtja végre előszö r, majd csö k kenő sor- and_e,,' (&,, ) rend szerint folytatja. tv. egyoperandusú plusz (+) és minusz (-) a precedencia-lista maso- .,m dik szintjén szerepelnek. vagyis - teljesen logikusan - mege l őzik az aritmetikai megfele l ői a u to sőbb
ket, amelyek az ölödik szinten kaptak helyet. A második szinten található & operátor a "címe" operátor, míg a g, szinten látható ugyanilyen jel a bitenkénti AND művelet operátora, A 2, szinten látható " a mutató által cfmzett tartalom kiolvasását végzi, míg a 4. szinten lát· ható ugyanilyen jel a szorzás, Amennyiben a műve letek végrehajtásának sorrendjére nem utalnak zárójelek, úgy az azonos precedenciaszinthez tartozó műveleteket a program jobbról balra. vagy balról jobbra haladva hajtja végre a táblázatban megadottnak megfelelöen, Szint 1 (magas)
2 3
'0"
static
friend'
statiC, cast'
goto
struct switch
bool'
"in l ine'
break
'ne
template ' this '
case
long
throw'
mut a ble'
true '
Kiértékelési sorrend
( ) ::
balról jobbra balról jobbra
char
names pace
try'
class'
new'
typedef
campl' (-)
not '
const
not,eq' ( I,,)
typename '
const, cast '
operator'
union
continue
or' (I I )
unsigned
default
or, eq' (I ,, )
us!n g '
delete '
private'
virtual'
do
protected'
void
· [ l
-> ++ --
type i d keyword, typecast * & ! - + ... -- ... aizeof new delete
)
«
17 18 (alacsony)
bitor' ( I)
size, t
Operátorok
,-
16
(&)
s izeo f
float
catch'
4 5 6
8 9 10 11 12 13 14 15
bitand'
false'
jobbról balra balról jobbra balról jobbra balról jobbra balról jobbra balról jobbra balról jobbra balról jobbra balról jobbra balról jobbra balról jobbra balról jobbra jobbról balra jobbról balra
· * ->* • /% »
< <= > >= ='" I ",
••
••
"
?,
= * = /= ... = -= %= «=»;&=A=I= throw , (vessző operator)
balról jobbra balról iobbra
(l)
public' double dynarnic, caet' re gister
type id'
volatUe' wch ar, t
else
reinter -
while
on=
pret, cast'
xor '
explidt'
return
xor, e q'
export'
short
extern
eigned
TIpus
16 bit
32 bit
Tartománv
uneigned short int short int uneiqned long int long int int
2 2 4 4 2
2 2 4 4 4
unsigned int s i ze_ t char wchar_t bool float double l ong d ouble
2 bájtoe 2 bájtoe 1 bájtoe 2 bájtoe l bájtos 4 báj t os 8 bájtos 10 bájt o s
O-tól 65 535-ig -32 768-t6l32 767-ig O-tól 4294967 295-ig -2147483 648-t612147 483647-ig (16) :-32768 - tóI32767 - ig; (32) :-2147483 6 4 8 - tól 2147483 647 - ig (16) : O- tó165 535 - ig; (32) : Q-tól 4 294 967295-ig (16) : 0 - tó165 535 - ig; (32) : Q- tól 4 29 4 9672 9 5 - ig 256 karak ter 65535 karakter Tr u e va gy False 1, 2*1 0·>1 tó13,4"10 " ig 2, 2 *10 -"'- tól 1, 8 * 10'''- i g 3 , 4 *10 ·"" -t6l l, l *10"" -ig
bájtos bájtos bájtos bájtos bájtos
4 bájtos 4 bájtos l bájtos 2 bájtos 1 bá j tos 4 b ájtoe 8 b ájtoe 10 bájtos
(A;; )
, Csak a C++ nyelvnek része, Azok a kulcsszavak, amelyek után zárójel szerepel, a zárójelben található operátor szinonimái.
Adattípusok a C+ + nyelvben bájtos bájtos bájtos bájtos bájtoe
(A)
TARTALOMJEGYZÉK I. rész
Bevezetés a C+ + programozási nyelvbe
l. óra
Kezdő
lépések
E!őkészülctck a programozáshoz ....... . A C++ nyelvjárásai (C++, ANSI C++, ISO C++ és a Windows) A fordító telepítése és beállítása Telepítés a CD-r61 . , ................ . A Borland C++BuilderX fordító beállítása ...... .
Fordít.'Ís a Borland C++BuilderX-szel
A fordítóprog ram és a szövegszerkesztő
Fordítás és linkelés Fordítás az integrált fejlesztői környezetben
linkelés (ősszeszerkeszl(:s) A fejlesztési ciklus. , .. HELLO.CPP -
Az els6 C++ programunk
Fordítási hibák ...... . Kérdések és válaszok
Gyakorlatok
Kvíz Feladalok Válaszok a kvízkérdésekre .
2. óra Mitő!
· 3 .4
.5 . ..... 6 · .. 6 ..... 8
..... 9 ......... 10
.10 .......... . .......... 11 . ..... II
· 13
15 16 16 17 17 17
Egy C++ program részei
lehet jó választás a C++ nye lv. · . 19 Procedurális, strukturált és objektum-központú programfejlesztés . · 21 A C++ rwelv és az objekmm-központú programozás . 22 Beágyazás (encapsulation) . . . . . . . . . . . . . . . . . . . .. . ......... .. .... 23 Öröklődés és újrahasznosítás .................... . . .... 2.~ Többalakúság (polimorfizmus) .... . .. . . ..• . . . .. · . 24 Egy egyszeru program részei .......... . · . 24 Az #indudc utasítás viz.<;gálala .... . .. . .. ...•.. . 25 Elemzés soronként. . ... 26 Megjegyzések ................................ 28 A megjegyzések tipusai ................... .. ... ....•. .. . . ... 28 Megjegyzések használata egy egyszerű programban . 29
I
iv Tanuljuk meg a C++ programozási nyelvet 24 óra alatt Függ"ények . . . . . . . . . . . . . . . . . . . . .....•.....•..... 30 Függvénybívások . . . • ...•..... . .. . . . •..•..... 31 Függvények használata ....... . ........ ............ . 32 Függvényparaméterek használata . . • . . . • . . . . . . . . . • . . • . • . .. . 33 Kérdések és válaszok ....... . .. 34 Gyakorlatok ........ ................. . .. 3S Kviz .. ... 35 Gyakorlatok ... 35 Válaszok a kvrzkérdésekre ... 35
3. óra
Változók és állandók
Mi az a változó? . . . Mem6riafoglalás ..
... .•. .. ..•.. ... . ... , . . . . .. .... . . , .
A'L. egt:s'L. számok (Integer) mérete
..•....
. . •. . . . . •.
. 37 . 38 . .... 39
signed és unsigned ........ ... ..................... 40 Alapvető változ6lípusok .......... . ........ ....... 41 Egy v{lltozó meghatározása . . . . . . ... .•.. . .... 42 A kis- és nagyl>culk eltér6 jelemésér61 ......... ........ . . , 42 Kulcsszavak. . . . . . . . . . . . . . . . . . . . . .. ... .•... .. .. . .... 43 Egynél több változó megadása egyszerre .....•............ .. . . .... 44 ÉrtC:kck hozzá rendelése a változókhoz . . . . . . . . . . . . . ....... 44 typedef . .. .. ... ... ... .. ... ... ... ... .. ... ..•.. ... ... . . .... 45 Mikor használjunk short és mikor long típust ... . . . . . • . . . . . ...... ,47 El6jel nélküli egészek túlcsordulása ...............•.....•............ 47 Az el6jeles egészek körbefordulása .......... , . . . ........ • .. .•.. ...... 48 Állandók . . . . . . . . . . . . . . . . . . . . . . . . . ...•............ •.. , ........ 49 Uterális állandók ........................ ... . ... •. ... • . . .•..•.. ... 49 Nevesített (symboJic) állandók .............•. . ..............•..... 50 ÁIland6 bevezetése a #define ku1cssz6val ..... .. , . . . • . . . • . . . . . . 50 Állandó bevezetése a const ku1csszÓval. ...... . .....•......•.....•..... 51 Felsorolt (enumemted) állandók ............ . . , . . . . , , ..•.. ...... 51 Kérdések és válaszok
................... . ................. 52
Gyakorlatok Kvíz Feladatok . Válaszok a kví'L.kérdésekre ..
4. óra
.. ... . ... . .......... . 53 . ... ... 53 ...... . .. . .. . . .. . 53 .... ...... 54
Kifejezések és utasftások
Utasítások Üres helyek .. Összetett utasítások Kifejezések
.55
.. 56 .. 56 .. 57
Tanuljuk meg a C++ programozási nyelvet 24 óra alatt v Operátorok ..................... . . .. 58 Értékadó operátor ............... . . .... 58 Matematikai operátorok ................................. 59 Matematikai és értékadó operátorok kombinálása . . . . . . . . .. 60 Növelés és csökkentés. . . 60 Előtag (prefix) és utótag (pOStfL'0 operátorok .............. . .... 61 Precedencia, avagy a műveletek végrehajtási sorrendje ........ 63 Egymásba ágyazott zárójelek összetett kifejezésekben. . .... 64 Relációs operátorok ........ 65 Az if utasítás . . . . . 66
..
M_~
~
Összetettebb if konstrukciók ....... . . 68 Kapcsos zár6jelek használata egymásba ágyazott if utasítások esetén .. 70 A logikai operátorokr61 bővebben . . . . . . . . . . . . . ................. 73 Logikai ÉS .. . . 73 Logikai VAGY. .......... . ........ 74 Logikai NEM. . . . . . . .. .. . . . . . . . 74 Relációk precedenciiija .... 74 B ővebb en az igazság természetéren ... .... .•..•... . . . 75 Kérdések 6s v<Ílaszok ....... . . . . . . . . . . . . . . . . . . . . . . .. 76 . Gyakorlatok . . . . . . . . . . . . . .. . ... 76 Kvíz .. . ......... 76 _~~
........
Válaszok a kvízkérdésekre .
5. óra
. . . ......... n
. . . . . . . 77
Függvények
Mi is az a függvény? ................... . . .. . . . . . . 79 A függvények deklarálása és definiálása .81 Függvények deklará lása . .81 Függvények definiálása ... .. . . 83 Függvények megadása: összegzés .. ... . . . .. . ... ..84 Változók használaca függvényeken belül ........... . ........ 85 Helyi változók ............... . . .... . .. ...... 86 ..88 Globális változók ............... ... ...... . . . A függvények argumenrumai ....... . . . . ..89 Függvényhívás mim paraméter ....... . .... . .... . ..89 A paraméterek egyben helyi változók is ..... . .. . . . . . .. . . 90 Értékek visszaadása függvényből ... . ....... .. . . . ... 92 r-üggvények alapértelmezett paraméterei ................ . .. 95 Függvények túlterhelése .. . ...........• . . . . . . . . . 97 Inline függvények .. .. . ......... . . ......... 98 A függvények és a verem . . . . . • . . ....• . . . ... 101 Kérdések és válaszok .......... . .. . .... . . . .... . ........ 103
I
vi Tanuljuk meg a C+ + programozási nyelvet 24 óra alatt 104 · ...... 104
Gyakorlatok ....... . . . Kvíz ... Feladatok Válaszok a kví:t.k~rdésekre ... .... . .
6. óra
. 105 105
Vezérlési szerkezetek
Ciklusszervezés ..... . A ciklusok 6se: :I goto utasítás Miért ne használjuk :I goto utasítást? ...... ... . A while ciklus ....... . ............. .• llonyolulcabb while UUlsítások ........ . A <.:ontinue és a break ......... ....... .. . while(1) - végtelen óklusok A do .. .while ciklus .......... . A for ciklus .. . ......... . lnidalizád6, vizsgálat, léptetés. Összeteuebb for ciklllsok ............ . Halmozott inidaliz:ídó és léptetés .. . Üres UUlsítások a for ciklusfejben .. . Üres for ciklus .... ...... ......... . Egymásba ágyazott ciklusok A switch utasítás ....... . Kérdések és válaszok .... . Gyakorlatok ......... . Kvíz .. Fehldalok ................. . Válaszok a kvízkérdésckre ... .
II. rész
7. óra
107 107 108 108
· ........ 109 . .. 111
.. 113 · . 114 · . 117 · •...... 117
· ....... 119 · ....... 119 . ..... •...... 120 . . . . . .. .. ..... 122
· . . ...... 123 124 .•.. .. ... .. · . 126 . .... 127 · ........ "127 ..• ..... .. 127 . ...... 128
Osztályok Alapvető
Mi a típus? . .
,
. .... ..... .••...• ..... . ... ........ · .......
osztálvok
. .......... . . Új típus létrehozás,l Osztályok és tagok ..... . . Osztályok deklarálása Osztályok nevezékIana .. Objekrum definiálása . Osztályok és objektumok Osztályok Ulgjainak elérése: Privát és publikus hozzáférb Osztálymetódusok létreho~ Konstruktorok és clestru!-..'l( :; ~ ~.lIZ
· ...... 13 ]
· .. ..... . ... . . . ... ... ............... · ......
132 132 133 134
.... . ..... ..134 ...... 13; ......... 135 .... ..... ....... 136 .. ... 138 _- .... létrehozása és türlése .. 140
I
Tanuljuk meg a C+ + programozási nyelvet 24 6ra alatt vii Alapértelmezett konstruktorok ............... . ... . A fordító áJtal biztosított konstruktor .... ... ... .. . ... •. .. . Kérdések és válaszok Gyakorlatok. Kvíz
Feladatok Válaszok a kvízkérdésekre ............. .
8. óra
· .... 141 · .... 141
. ... 144 · . 144 . . ....... 144 · .... 145 · . 145
Az osztályokról - mélyebben
Konst.1ns tagfüggvénye k ........... . Felület és megval6sítás ............
. ......................... .. 147 . ...................• •. .. . .. J48
Hová tegyük az osztály-deklarációt és a metódus-def'iníciókat? ......•...... 149
Helyben kifejtett (jnline) megval6sítás ... .... . ............ • .... , .. 150 Osztályok beillesztése más osztályadattagjaiként ...... .. ..... 152 Kérdések és válaszok ......... ......... . .. .. ... • ....... "1 57 Gyakorlatok. . . . ........... ............... . . 158 . .. ..• ... .. . • . • .. ..... 158 Kvíz ...•..•. Feladato k . . . . . . . . .. . .................. 158 Válaszok a kvizkérdésekre ...... . .. ....... . ... •.• •.. •... .. .... 159
III. rész
9. óra
Memóriakezelés Mutatók
A mutatók és használatuk megértése ........ .... . •. . . •.•. . . •. A memóriadm tárolása a mutató kban
· .... 163 ..167
Mutatónevek .......................................... . · ... . 1<>9 A közvetett elérés operátor, avagy mutatók közvetett használata ... · .. 1<>9 Mutatók , címek és változók. . . . . . . . . . . . . . . .... .. . · .. 170 Adatok manipulálása a mutatók használatával ............ . .170 .172 A mutatókban tárolt címek vizsgálata .. .. . .• . .. •. Miért jó mutatókat használni? .................. . . . . · .. 174 A verem (stack) és a dinamikus memória (heap) .......... . .. . . . .. 175 A new kulcsszó használata · .. 176 .177 A delete kulcsszó használata .. Amemóriaszivárgás elkerOlése . . . . . . •. •.. .•. . . . . . . . .. . . . . .. 179 Kérdések és válaszok ............•. . . .. .......•. ... . • .. .... 180 Gyakorlatok. . ............ ... •. •. ..•.• . .. .. . .. . . ..• . ..... 181 Kvíz · . 181 Feladatok .. •... •.. ...• . . . .. .... •... . •• ..... 181 Válaszok a kvízkérdésekre · .... 182
I
vi Tanufjuk meg a C++ progral'TJOlási nyeMrt 24 óra alatt
10. óra
A mutatók kifinomult használata
Objekrumok létrehozása a memóriában .......... ........... ... Objektumok törlése . ... . . .... . .... .... .... . ..... Adattagok elérése mutatókon keresztül Adauagok a dinamikus memóriában ...... ................. A this mutató . Gazdátlan vagy ~16g6 ~ mutatók ........ . . ...... .. ... .. ...... Konswns mutatók Konstans mut:ltók és konstans tagfüggvények. Konstans this mutatók ......... Kérdések és válaszok ........ Gyakorlatok. . ................ • Kvíz P<:!ladalok Válaszok ti kvízkérdésekre o
o
•••••••
•••••
o
•
o
o
o
•
o
•
o
11. óra
•
o
o
o
o
••
o
o
o
•
o
•
•
o
o
••
o
o
o
o
o
••••••
••••••••
o
o
o
••••
o
•
o
o
•
o
o
o
•
•
o
•
•
o
o
•
o
o
o
o
•
o.
o
o
o.
o
0'0
o
o
,
••
,
•
o
•
o
•
•
o
••
o
•
•
o
•
•
•
o
.,
o
•
••
•
o
o
o.
o
•
••
o
o
•
•••
o
o
o.
o,.
o
,
o
o
o.
o
o
•••
o
o
o
•
o
•
•
o
o
o
•
••
•
•
•
••
o
••
•••
•••
o
•••
o
o
o
o
•
o
••
o
o
•
•
•
•
•
o
o
•
o
0
••
••
o
••
o
•
•
•
•
•
o
o
o
•
•
•
o
•
•
•
•
••••
IM 184 185
186 188
190 191 191 93 193 194 194 194 194 ,
Hivatkozások
Mi :IZ a hiv:ll.koWs (referencia)? ... Hivatkozások létrehozása .......... A wdme~ operátor alkalma7.ása hivatkozásokra A swap() javítása mutatók segítségével A SW:lp() ójabb megvalósílása hivatkozásokkal Kvíz ...... Feladatok . . . .. Válaszok a kvízkérdésekre o
••
o
•
o
o
•
o
o
o
12. óra
•
o
••
o
o
•
••••
o
o
•
o
••••
o
••
o.
o
•
o
o
o
o
•
o
••
•
••
o
o
••
o
••••
o
o
••
••
o
•
•
•
•
•
•
•••
o
o
•
•
•
•
o
•
o
•••
o
o
•••
o
o
•
•
o
••••
o
•
•
•
•
•
•
•
••
•
•••
•
•
o
•
o
•
o
o
•
•
o
o
•
o
•
o
o
•
o
••
o
•
o
o
•
•
o
••
o
•
o
•
o
••
o
•
o
o
••••
o
•
o
o
•
o
o
•
o
••
o
•••••
o
•
o
o
••
o
•
••••••
••••
•••
••
•••
•
•
•••
o
•
o
••
o
o
195
196 197 203 204 210 210 211
A hivatkozásokról és mutatókról - mélyebben
Hmékonyabb a cím szeri nti paraméterátadás .. 213 Mutassunk konstansr.i ........ 217 Hivatkozások, mint a mutatók kényelmes alternatívái. . ...... 219 Mikor használjunk hivalkozásokat, és mikor mutatókat? .. 221 Ne adjunk vissza mcgszCínt objektutnr3 mulatÓ hivatkozást! ............... 222 A dinamikus mcm6rialerületen létrehozott objekUlmra mutató hivatkozás visszaadása 223 Mutató, mutató, kié a mulató? . ............... 226 Kérdések és válaszok .. 227 Gyakorlatok .. 0227 Kvíz 0227 Fcladatok 0228 Válaszok a kvízkérdésekre 0228 o
•
o
•
o
o
•
•••••••••••••
o
o
o
o
o
o
•
o
o
o
o
o
o
o
o
o
o
o
o
••
o
•
o
o
o
o
o
o
•
o
o
•
o.
o
o
•
o
o
•
o
o
o
o
o
•
o
•
•
o
•
o
o
•••
o
•
o
o
•
o
••
o
•
o
o
•
o
o
o
o
o
o
o
o
o
o
•••••••••••••••
o
Tanuljuk meg a C++ programozási nyelvet 24 óra alatt ix
IV. rész 13. óra
Haladó eszközök Afüggvények kifinomultabb használata
Túlterhelt tagfüggvények. · .. . 231 Alapértelmezett értékek használata · ... 234 Hogya n válasszunk a túlterhelés és az alapértelmezett .... 236 értékek h:lsználata között? ..... 236 Konstruktorok túlterhelése .. 237 Objektumok inicializálása .. . . .. . . •.... . .. 237 A másoló konstruktor Kérdések és válaszok ..... . ..... 242 Gyakorlatok. · .. 243 Kvíz · 243 .243 FeJadalok ... .. .. .. . . .. . . . • ..... 243 Válaszok a kvízkérdbckre ..
14. óra
Operátorok túlterhelése
Hogyan terhelhetünk túl operulorokat . Írjuk meg az inkremenuíl6 függvényt ...... ... . A postfix (Ul6tag) inkremenu:i ló operihor tú llerhelése A prefix és a postl1x közöui különbség operator+ ...... . Az operator+ túherhelése . Az operátortú lterhelés korlátai Mit célszerti túlterhelni? operator" Konverziós operátorok Az intO opcdtor . . Kérdések és válaszok Gyakorlatok . Kvíz ... Feladalok Válaszok a kvízkérdésekre .
15. óra
· . 245 247 248 . ........ . .... 248 . 251 . .. 252 · ... 254 .254 .254 .258 .260 · . 261
· .. . 262 . .... . 262 . ....... .. 262 . . .. 263
Tömbök
Mi az a tömb? . ... . .... .. .. •. .. . A tömbök elemei . Tömbök túlírása Kerítésoszlop hibák . Tömbök előkészítése. ObjekUllntömbök ........... . . Többdimenziós tömbök ...... . . . .
....... 265 . . . ... 266 . .. 268 268 269 . ... .. • ..... .... 270 . .. ... . ...... 272
xITanuljuk meg a C+ + programozási nyelvet 24 óra alatt Többdimenziós tömbök inidalizálásll .273 Néhány szó II memórial1asználalr61 ........... . .. ... 275 Mutatótömbök . . .......... .. ... . ........ 276 .... 278 Dinamikus memóriában tárolt tömbök deklarálása Tömb mutatója és mutatók tömbje ...... . .. .... 278 Mutatók és tömbnevek .............. . .. .. .. .... 279 Dinamikus memóriában létrehozon tömbök törlése .. 281 Karaktertömbök ..... . •.•• •.• •.• • ..... 282 Az strcpyO és az strncpyO függvények .......... . . 284 Karakterlánc-osztályok ... . .. .. . .. ..... 286 Kérdések és válaszok . .. . .. ..... 287 Gyakorlatok. .. 287 Kvíz ....... .... . . ...... 287 Feladatok .. • .. . •. ..288 Válaszok a kvizkérdésekrc ........... . ............. .. ..... 21J8
V. rész
Öröklődés és többalakúság
16. óra
Az öröklődés
Mi az az
öröklődés?
... . . . .................... • ..... . ......... 291 és származtatás ....... . .........•. . •.. . . . . ...... 292 Modellezzük az öröklődést az Állatok Országával. . . . . . . • . . . . . • . . . ... 293 A .';zármaztatás szintaxisa ..................... . 294 Privát vagy védett? ......... . ... . ......... 296 Konstnlktorok és destruktorok ...... .. .. . •.. ...• . .. . ...... 298 . ..... 300 Paraméterek átadása a báziskonstruktornak .......... Függvények felülbírálata . . . . . . . . . . . . . . . . .. . ..• .. ... •... •...... 305 Túlterhelés vagy felülírás? ........ .............. . .... 307 A bázisosztály metódllsaimlk elfedése . . . •. . . •. . . . . . . . •. . . • . . . 307 A bázismetódlIs meghívása .... ... .•... ..•... .. . . 309 Kérdések és válaszok ...•... •.... .. .. . ..... 31 1 Gyakorlatok. . . . . . . . . . . . . . . . . ... ..•... .. . 311 Kvíz... ... .. .... ... .. .... .. .. . ... .. 31 1 Feladatok ...... . ...... 311 Válaszok a kvízkérdésekre .............................. 312 Öröklődés
17. óra
A többalakúság és a származtatott osztályok
A vinuális függvényekkel megval6síton többalakúság ...... . ......... 313 Hogyan működnek II virmális függvények ............ . • . . . . . • . . . 318 Nem juthaL,>z innen amoda. ....... . ....... 319 Szeletelés (slicing) . . ......... . .. . .. ........ 320 Virtuális destruktorok .. ... ..•. . 322
I
Tanuljuk meg a C+ + programozási nyelvet 24 6ra alatt xi Virtuális másoló konstruktorok A virtuális függvények kölL5égei ..... • . Kérdések és válaszok ........ . Gyakorlatok. Kvíz rdadatOk Válaszok a kvízkérdésekre .
18. óra
... . 322
... 326 . ....126
..327 ..... 327 ..... 327 . .....~28
A többalakúság kifinomult használata
Problémák az egyszeres örökl6déssel ....... . .. . . .329 Elvont adattípusok. . . . ........... . .....•..... .. 334 Üres virtu,ílis függvények ....... ... ....... . .. . ...... . .. 338 .. .. 342 Az üres viltuális függvények megva16sítástl .......... . .. . 345 Az elvonatkoztatás összetett hierarchiája .. ....... .349 Mely típusok elvontak? 350 Kérdések és válaszok Gyakorlatok. · . 351 Kvíz .... . 351 . ........ 351 FeJadatok Válaszok ti kvízkérdésekre ........ . . . . .. . . .. . . .352
19. óra
Láncolt listák
Láncolt listák és egyéb struktúrák ....... . Esetlanulmánya láncolt listák használatáról A felel6sség átruházása .. Az alkotórészek Mi a láncolt listák tanulsága? Kérdések és válaszok Gyakorlatok. . ....... . Kvíz Feladatok Válaszok a kvízkérdésekre ......... . ..... . .. ... . . .. .
VI. rész
Különlegességek
20. óra
Különleges osztályok, függvények, mutatók
Statikus tagváltozók . Statikus tagfüggvények Egymást tartalmazó osztályok ............... . .... . A tartalmazott osztály tagjainak elérése A tartalmazott osztály elérésének korlátozása
. ... 353 · . 355 .355 ........ 355 364
· 365 366 366 .366 . . . . . . . 366
... 369 . ...... 372
.... 374 . . .. . . . . 380 . . . . . 380
Xli , Tanuljuk meg a C++ programozási nyelvet 24 6ra alatt A tartalmazás költsége ... .. . , . . . . . . . . . . 381 Érték szerinLi és hivatkozás slerinti másolás. . .. ... ... . . , . . . .. 381 Baráti viszonyban levő osztályok ....... . . ......... 381 Baráti viszony füg&'Vények közölt ............... . ..• • . . . . ..... 382 Függvénymutarók ....... , . . . . . . . . . . . . . .... . 382 Rövidített írásmód .. ... .. ... .. ..•.. . .. 386 Függvénymutatók tömbje ................ . . . • . . . .. . .. 386 Függvénymutatók átadása más függvényeknek .. . ..... 389 A type(k:r használata függvénynmtatókkal ........... . ............ 391 Tagfüg&>vényekre vonatkozó mutatók . . . . . . . . . . . . . . . . . . . . . .. 394 Tagfüggvényekre vonatkozó mutatókhól álló tömbök ....... . ..... 397 Kérdések és válaszok .. ............... . ........ 400 Gyakorlatok . . . . . . . . . . . . . . . . . . . . . . . . . . . . . • . . . . .. . .. 400 Kvíz. . . . . . . . . . . ....... . ........•............ . ......... 400 Feladatok ....... ... ... ..•.. .•.. . . •... . ...... 401 Válaszok a kvízkérdésekre . . ..................... . ......... 401
21. óra
Az előfeldolgozó
Az előreldolgozó és a fordító ......... •.. .......... . ................ 403 . . . . . . . . . . . . . . . . . • . . . . . . . . . . . . . . 404 .......................... . .... 404 A . define használata álland6kkal kapcsolatban . . . . . . . . . . . • . . . . . . .. 405 A -*define és az '#ifdcf használata vizsgálatok végzésére. . ........ 405 Az #else direktíva .............................. ..... .... 406 Beszúrás és annak vezérlése .. . . . . . . . . . ........... • .......... 407 Szimbólumok definiálása a parancssorban .......... . .... . .......... 109 A meghat~irozotts{lg rnegszüntetésc .. . . . ................ 409 Feltételes fordítás ......................... . ... . .... . . . ........ 409 Makróként megvalósított fü~>vényck ........ . . . ........ 410 Mi ez a rengeteg zárójel? ..... .•.......... 41 1 Makrók, függvények, sablonok .. . .......... 413 . ............... 414 Karakterláncok kezelése .......... . A szöveggé ala kító (slringizing) operátor. . . . . . .• . ..• . . . . . . . . .414 Az összefúző operátor. . . . . . . . . . . . . . . . . . . . . . . . ... 414 Előre meghalározoa makrók . . . . .. . . . . . . . .. . . . . . .. . .. . .. 415 Az aS5ertO beépített makró . . . . . . .. .......•.... . ..... . .116 Nyomkövetés az assertO makr6 segítségével .418 A makrók mellékhatásai ....... . . ................ 418 Osztályinvariánsok ............ . . ... 420 Köztes értékek kiíratása ...... . . ... ... 425 Nyomkövetési szintek .. ... .... .. ... •. •... . . .. •. . . . . . ... 426 Kérdések és válaszok ...................... .432 Fe1adatok . .... .. ......... ... . . ..... . ...... . .... . . 4.l3 A köztes állapot mentése . A #de nne direktíva használata.
I
Tanuljuk meg a C+ + programozási nyelvet 24 6ra alatt xiii Kvíz , ...... . Gyakorlatok . , ...... . Válaszok a kvízkérdésekre ..
22. óra
........ 433 .. ... .... ..433 . ............ ........... . ........ 434
Objektum-orientált elemzés és tervezés
A fejlesztési ciklus . . . . . . . . . . . ........ , . , 435 E~,')' riaszt6rendszer szimulációja ... ..... . ............ , . . . . , 436 Konce[Ki6lelV ..........• ... •... .. •..• . .. ........ 437 Elemzés és az igények felmérése ........ • ......•..... . ........... 437 Magas és alacsony szinnl teJVezés ....... ••.• .... .. ... •. . .•.. • .... 438 Egyéb objektumok . . . . . . . . . . . . . .•. .. ..•... . . .. 439 Milyen osztály.link lesznek? . . . . . . . . . ............... 4,,9 Hogyan jelezzük a ri:lsztásokat? ........•. , . 440 Eseményhurkok ............ . 441 PostMaster: egyesettanulmány . . . . . . . . . . . .... . . • ' , 443 Mérj kétszer, vágj eb'Yszer . ......... .. ... .. ... .•. .. , ... 444 Oszd meg és uralkodj. . ... ... .. . ..•..•.. , ... 444 Üzenetform;'itum ................. . ........ . , .. , ...... 445 Az osztályok kezdeti telVC ....... . .•...... .. . . , ...... 446 Egy vagy több kiindulópontú hier'dfchia .. . ...•..... . .......... 447 Interfészek tervezése .................. . . . ... ...• , .•. . . ....... 449 Prototípus létrehoz.1S:l ................. ... ... ..•.. . .. 450 A 8O/80-as szabály .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . • . . . . . . . . . . 451 A PostMasrerMessagc os;t.t{ily tervezése ......... . . 452 Az alkahnazásfejle.~;t.tGi interfész. . ..... . ..... . , . .. , .... 452 Progmmozás nagy csoportokban ......... , .... , . • . . . . . . ... 454 A tervezési folyamauaJ kapcsolatos szempontok. . . . . . . • . . . . . . ....... 454 TelVezési döntések. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .... 455 Mikor hozzunk döntéseket? ..... . . . . . . . . . . . . . . . • . . . . . . . 455 . . 456 Munka a vezé rl őprogramokkal . . . . . . . . . . . • . . . • . . • . . . . . • . . . . Kérdések és válaszok .... . •......•..... • .. , . . . 463 Gyakorlatok. ........ . . . . . . . . . . . . . . •. . . . . ..... 463 Kvíz .. . .... , . . . . . . . . . . . . ...•.. , . . . .. ...... 463 Feladatok . . . .. ........ ... .. ..•.. ....... 464 Válaszok a kvízkérdésekre ..... ....• . .. .. . .. , . . . ....... 464
23. óra
Sablonok
Mik azok a sablonok? ........ . . . .. . ... .. .. . .. 465 A sablon példányosítása ....... . ... .. . • ' . . .. . ..... , 466 A sablon definíciója . . . . . . . . . . . . .. . ...•... , . . ........ , 466 .......... . ..... .. . • ' . . . . . . . , 474 Sablontípus használata A szabványos sablonkönyvtár .......... ... ..•. , 480
xiv ITanuljuk meg a C++ programozási nyelvet 24 6ra alatt Kérdések és vála.o;zok
Gyakorlatok ..... . Kvíz . . ...... ........ .
Feladatok Válaszok a kvízkérdésekre
24. óra
.... . .... .. ......... . 481 .. .. .. .. .. .. .. 481 .. .. 481 ..... . . ... 482 ..... " ..... ".482
Kivételek, hibakezelés és néhány tanács
Programhibák, tévesztések, k6dmegromlás . . . . . . . . . . . . . . . . . . . ... 483 Váratlan események kezelése ... ... .. ..•.... .. ... .. . . 485 Kivételek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . • . . . . • . . . . . 485 Hogyan val6sitsuk meg a kivélelkezelést? . . . . . . • . . . . . 486 ..... 491 A try és catch blokkok használata A kivételek elkapása ................ . . . . . . . . . . . . . . . .. . .. 491 Több catch megadása. . . . . . . . . . . . . . 492 Hivatkozás elkapása - többrétúség a kivélelkezelésben ....•. . . •.... .. 492 Hogyan írjunk professzionális minőségű programo kat? . . . 496 Zár6jelek ......................... . . .• . . .•...... 497 Hosszú sorok . . ........ . . .. .. . . • .......... 498 A switch utasítások. . . . . . . . . . . . . •. . .. . . . . •.... . . 498 A program szövege ................. .. 498 Azonosít6nevek . . . . . . . . . . . . . . . . .. . ....• . .. .. . . . . •...... 499 A nevek betO"zése és a nagybetuk kérdése . . . . . . . . . . . . . . . . 499 Megjegyzések ... .. ... .... ..•...•. . 500 Hozzáférés ....... ................ . 501 Osztálydefiníd6k .......... .•.. ... ... ... . 501 Beemelend6 állomá nyok .. ..• .. . .. •.... . ... • .. . ... . . •. ...... 501 assenO . . . . . . . . . . . . . . .................... . . 502 const ... . .. . ... . ........ . . . . . . . . . .. . . . • . .. . . . 502 További források . . . . . . . . . . . ... ... ... .. ... .... .. .. . .. . . . 502 Hol kapunk segítséget és tanácsokat . . . . . . . • . . . . . • . . . . . . .. 502 Ajánlott olvasmányok ....... ... ... .•. ... . .... . 503 Kérdések és válaszok ....... . . . . . . 504 Gyakorlatok. . ........ 504 Kvíz. . . . ..•... . . . ..... 504 Fe1adatok ... ... ... ... ..•.. . . ....... 504 Válaszok a kvízkérdésekre . ... ... ... ...•. ....•. . . .. 504
VII. rész A függelék B függe lék
Tárgymutató .
Függelékek A bináris és a hexadecimális számrendszerek ..... . .. . ...... 507 Gyakran használt kifejezések. . . 519
531
A SZERZŐKRŐl Jesse Liberty számos szoflverfejlesztéssel kapcsolatos könyvet írt már, amelyek között akad néhány kiugróan népszerű is. Ez utóbbiak elsősorban a CH nyelvvel illetve a .~TI technológiával kapcsolatosak. 6 a vezet6je a Ubeny Associates [nc. nevű cégnek, amely egyedi programok fejlesztésével , tanácsadással és oktalással foglalkozik. David B Horvath eCCp)
vezető
tanácsadó Philadelphiában, Pennsylvaniában éL Több
mint 15 éve dolgozik az informatikában illetve
részmunkaidőS
egyetemi tanár néhány
helyi ilIerve távoktatással m(íköd6 egyetemen. Az áilala oktatott tá rgY'd.k el sősorban a C++ nyelvvel, a Unix/Linux operációs rendszerrel, valamint adatbázisokklll kapcsolatosak. Diplomáját szervezeti dinamikából szerezte a University of Pennsylvanián 1998ban. Több nemzetközi szemináriumot és szakmai összejövetelt vezetett már SZ<1kmai egyesületek és vállaltok sz!imára.
David a szerzője a Unix for the Mainframe (Prentice-Hall/PTR) címu könyvnek, Jesse Libeny társzerzojeként pedig részt vett a Sams Teach Yourself C++ for Unux in 21 Days címu kiadvány írásában. Szintén o írt egyes fejezete ket a következo muvekbol: Unix Unleasbed (tőbb kiad~s), Red l-hit Linux Unleashcd (több kiadás), Learn Shell Programm ing in 24 Hours, Linux Unleashed Fourth Edition, Linux Programming Unleashed Second Edition. Emellett számos SZllkcikke jelent meg különbözo magninokban. David írói és oktatótevékenysége mellett aktív közösségi ember és önkéntes feladatokat is vállal különböző szakmai szervezetekben. Ha épp nem a billentyűzet el6u Ol és könyvet ír, akkor kertészkedik, áZlatja magát egy kád forró vízben, vagy nyomdafestéket nem túrő hobbikkal Oti el az icl6t. Idestova 17 éve há zas és van néhány kutyája meg macskája (ezek száma folyamatosan változik). Aki társalogni szeretne vele, az a
[email protected] címen éri el. Reklámleveleket kéretik me1l6zni!
I
xvi Tanuljuk meg a C+ + programozási nyelvet 24 óra alatt
Ajánlás Ajimlom ezt a k6l1yuet Edythe-nek, akilo7 az C/etet kaptam; Staccy-nek, aki megosztja azt ve/em; Robillllak és Nachelllek, akik értelmet adnak neki. - Jesse liberry Ajimlom ezt II k6/1yuettestvéremnek Andy-nek ésfeleségéllek Peg/lck második házassági évford/llójl/k alkalmából (2004. július 13.). Igazából az eskl1vojtiMI1 akll/1am átadlli Ilekik (>gy dedikált példányt, de ellltíz6doll a projekt. - David B. HOIVa/h
Köszönetnyilvánftás Minden könyv kivál6 alkalmal teremt arm, hogy köszönetet mondjunk benne azoknak, akik nélkül garantáltan nem jÖll volna létre. Esetünkben a Staccy, Robin és Rachel Liberty állnak az élen. Ezen kívül szeretnék köszönetet mondani mindazoknak, akik a különböző kiadókSams, Que, O'Reilly, \'('rox - munkatársaiként segítenek abban, hogy eddigi ffitlveim megjelenhessenek. A Sa ms Kiadó szerkesztői valamennyien kiváló munkál végeztek, ám külön szeretném közülük kiemelni Carol Ackermant, Chrisly Fr..mklint és Pau l Striclandel. Szintén hálával ta!lozom Rieh Halpertnek. Végezetül szeretném megköszönni Mrs. Ka1is munkáját, aki 1965-ben, hatodik oszt{llyos koromban megtanítolIa nekem, hogyan kelJ keHes számrcndslerbcn összeadni és kivonni, miközben sem 6, sem mi nem tudtuk, hogy ez pontosan mire is jó. -Jesse liben)'
Mindazok mellett, akiknek Jesse az imént köszönetet mondoLL valójában még sokan lehetnek II Samsnél, akiket kifclejtcttlink. Tőlük cz úton is elnézést szeretnék kérni. Jómagam Songlin Qillval és Lorella Yatcs-Slel dolgoztmn ezen ;1 projekten. Ez a könyv természetesen nem jöhetett volna létre a csa!{Ic.!orn, killönösképpen pedig feleségem Mary támogatása és megértése nélkül. Sajnos a könyvír.'is már csak ilyen. Az embernek leginkább esténként meg hétvégén van ideje és hangulata az alkotáshoz, amikor a csa láddal kellene lennie. Ilyenkor jön aztán a ~bocs de most nem tudok velelek menni, mert a könyvön kell dolgoznom" szöveg. -/)cwid B. Horoath
I
Tanuljuk meg a C++ programozási nyelvet 24 óra alatt xvii
Kfváncsiak vagyunk az Ön véleményére! Mint a könyv olvasója, Ön, kedves olvasó a mi legfőbh kritikusunk. Éppen azért nagyr.! értékeljük az Ön véleményét. Tudni .szeretnénk, ha valamit jól csináltunk, és persze a7.t is, Ita nem. Tudni szeretnénk, milyen más teriilctekr61 olvasna szívesen könyvekct ebben a sorozatban, és termésZdesen örömmel fogadunk minden egyéb megjegyzé.<;l is. A Sams és a Kiskapu Kiadó örömmel meghallgatj:t mindenki véleményét. Kt:rjlik írja meg nekünk, mi az, ami tCtsZett Önnek vagy ami nem tetszett ebben a könyvben , hiSZen ez na!.'Yban segítheti további munkánkat , és azt, hogy egyre jobb minőségű könyveket adjunk ki. Ug)'clllakl...'orjCJ/lIOS lwk r,mjl/k megjegyezni, hogya könyv rmta!múva! kapcsolaros 11/11szaki /...0rdésekbclI álfalá!?clII /leli! rudullk segírelli. A SC/ms Kiad6nak IIg)'l/I/C/kko r U(1/1 egy Felhaszná/ói SzolgáItClfÓ részlegc, amcly (I ÁlÖII)'Vve/ kapcso/alos kérdéseker megkapja, és esetleg II/eguálaszolja.
Ha ír nekünk, kérjük adja meg a könyv pontos dmét és szerz6jét, az ön nevét, e-maii dmét és esetleg telcfonszámát. Amennyiben módunkban áll, me!,'Vizsgáljuk a felvetést vagy problémáI , illetve megosztjuk aZI a szerzővel és a szerkeszt6vel. A könyv eredeti angol ki:tdóját:l
[email protected] címen érheti el. Ha hagyományos levt:lben kívánja a Sams Kiad6t megkeresni, levelét a következő címen várjuk:
Email:
[email protected] Maii: Michael Stephens Associate Publisher Sams Publishing 800 East 96th Street Indianapolis, IN 46240 USA Magyar kiadással kapcsolatos megjegyzéseit kérjük a kiadoElkiskapu . hu címre küldje. A Sams egyéb kiadványaival kapcsolatban kérjük látogasson el www.samspublishing.comcímre. Ha ezzel a konkrét könyvvel kapcsolatban keres valamit a SAMS webhelyén , a keresőmezőbe kérjük az eredeti kiadás ISBN számát (0672326817) gépelje be. A magyar kiadás weboldalát és letölthelő anyagait a Kiskapu Kiadó webhelyén (www.kiskapukiado.hu) találja meg.
BEVEZETÉS Ennek a könyvnek az a célja, hogy 5egítségévcl
dó alkalma7..ásokat mdjon fejleszteni. A leckékben számos a tananyagot illusztráló kódrészlet találhat6, :lmelyeknél mindig megadtuk azt is, hogya prog'.lrn futtatásának mi a kimenel. A fontos szintaktikai elemeket külön is igyekeztünk kiemeh,i, hogya könyv utólag, referendaként is használható legyen. Valamennyi óra anyaga néh{my kérdéssel és gyakorlattal végz6dik, ezzel is segítve :L tanultak gyakorlatba v,116 átemelésél. A korábbi kiadásokb61 hi{myzott az ebben a kiadásban minden lecke végén felbukbn6 kvíz, amelyhez természetesen a helyes válaszokat is megadjuk.
Kinek érdemes elolvasni ezt a könyvet? Ebből
a könyvből az is megtanulhalja a C++ nyelv has:miilat{ll, akinek semmiféle korábbi programozási tapaszmbta nincs. Mi mindenl az clcj6r61 kezdünk elmagyaclzni, vagyis nem csak magát nyelvet tanítjuk meg, hanem a C++ nye l vű programozással kapcsolatos koncepcióbl, alapelveket is. A szintaxist mindig példákkal illusztrn ljuk, a bemutatott k6dok míiködését pedig szükség esetén akár soronként elmagyarn z7.uk. Éppen ezért gondoljuk úgy, hogy ez a könyv nagyszeni társ lehet egy a C++ nyelv birodalmában tell utazáshoz. Akár rendelkezik az olvasó korábbi programozási tapasztalatokkal, akár teljesen kezdő, bízunk benne, hogy e kötet világos szerkezete megkönnyíti majd számára a tanulást.
Nem kellene el6bb megtanulnom a C nyelvet? Ez a kérdés b'Yakorlatilag elkerülhetetlenül merül fe l, hiszen a C++ bevallottan nem más, mint a C nyelv továbbfejlesztése. Kell-e tehát ismernünk magát a C nyelvet a hhoz, hob'Y megéruük a ~javított vá ltozatot"? Stroustroup, a C++ nyelv alkotója és számos más szakértő is egyetért abban, hogy nem csak hogy nem kell ismernünk a C-l, hanem bizonyos helyzetekben kifejezetlen előny is, ha nem tudunk róla semmit. MegfigyelbelŐ ugyanis, hogya korábban C-ben fejlesztő progl'"dmoz6knál kialakulnak bizonyos "rossz szokások", amdyeknek C++-ban már nem tudnak "hódolni", és amelyek kezdetben erősen gátolják őket az átáJlásban. Ennek megfelelően a könyv megírása során mi sem
)o(
ITanuljuk meg a C+ + programozási nyelvet 24 óra alatt fcJtételeztük az olvasór61, hogy ismeri a C nyelvet Persze ha valaki mégis, akkor sincs semmi veszve. Nekik azt ajánljuk, hogy fussák át az e1s6 néhány fejezetet, csak hogy képbe kerüljenek, aztán kapaszkoc\janak meg valami szilá rd objektumban, mert hamarosan az általuk ismert nyelv olyan kiterjesztbeit fogják látni, ame\yekr61 eddig nem is :ílmoc1tak.
Miért érdemes megtanulni a C+ + nyelvet? Ez megint egy igen gyakori kérdése, 561. áll!lláb-.tn olyasvalanú szokott utána jönni hogy Vé&tiI is az X nyelv lJ laglÍjabb és legnalJyszen7bb, és hamarost/Il úgyis leváltja ti C++-I". Erre a rövid vábszunk körülbelül ~lImyi, hogy .Nem addig van az!". Rengeteg olyan nyelv vol! m:lr, amir61 egy id6ben mindenki tudta, hogy le fogja váltani a COBOl-t, mégis ebben a pillanatban is töhh millió sornyi olyan COBOl kód van, anút "komoly" szervezetek rendszereiben aktívan használnak. (Mi több, foly:mlatOsan újabb sorokat is irnakD Körülbelül ugyanez a helyzet a C++-szal: kódsorok millióit írták ezen a nyelven, 86t, még mindig írják 6ket. A másik nyomós indok, amiért mindenk6ppen érdemes megtanulni l C++-t az, hogy bár vannak . újabb és javított~ nyelvek, az(:rt ezek bevallOlwn sokat merítettek a C++-ból. Mind :1 Jav:1 mind a CI:t sz{unos .. C++-szal eb'Ycz6 nyelvi elemet tartalmaz, viszont ezeknek a nyelveknek az els.1játítása több ponton jóval nehezebb. H
Aki tehát ismeri az új trónkövetelők 6sét, az könnyebben boldogul majd a leSZ:.'Írmazottakkal is. De akár tovább is mehetünk: a C++ ismerete nem csak a Java vagy a C.. elsajátítása során válhat el6nyünkre, hanem az awk, a Perl na és persze a C tanulása közben is. A legfontosabb lényez6 ezen a téren az, hogya C++-sza l olyan hordozható kl:pességeket sajálíthatunk el, amelyeket aztán mindenféle platformon, és az informatik:li piac legkülönböz6bb területein kamatoztalhatunk, legyen lZ PC, valamilyen nagy teljesilmén}'Ű Unix rendszer, vagy egy egész adatcentrumot megtölt6 mainframc. Lehel, hogy a kód, amit írtunk nem lesz hordozható, vagyis nem Tudjuk változtatás nélkül átvinni egyik rcndszcrr61 a má~ikra , de a rudás, amit állllla megszerezlünk, biztos:'ln az lesz.
A könyvben használt jelölések A könyvben a kövctkez6 speciCIlis jelöléseket fogjuk használni.
Az ilyen jelzésú szövegdobozok olyan információt tartalmaznak. amelyek birtokában hatékonyabb kódot tudunk írni, vagy mi magunk hatékonyabban tudunk dolgozni.
I
Tanuljuk meg a C++ programozási nyelvet 24 6ra alatt xxi
Figyelem!
Az ilyen jelzésű keretes részekben olyan problémákra vagy mellékhatásokra hívjuk fel a figyelmet, amelyek bizonyos helyzetekben gyakran felmerülnek. TudIa hogy...? Az. ilyen dobozokban tippeket, trükköket mutatunk be, illetve az aktuális témához kapcsolódó kiegészítő információkat közlünk.
Helytele. Az ilyen táblázatokban tömören összefogla ljuk mindazokat a tanácsokat, aranyszabályokat, amelyek az é ppen tárgyalt témához kapcsolódnak. Ilclyes tehát, ha odafigyeJü nk ezekre.
Helytelen, ha figyelmen kívül hagyjuk ezeknek a felsorolásoknak a tartalmát.
Ha ezt a szimbólumollátjuk, akkor a melletre [ott program kimenetét tartalmazza.
levő
szöveg vabmilyen éppen bemuta-
Ez a jel mUlat ja az éppen tárgyalt példakód elemzésének kczdetC:t. A könyvben ezen kívül különféle berutipusokat is használunk: • Hogy könnyen meg lehessen különböztetni a folyószövegec a kódoktól, utóbbiakat speciális monospace betúkészlettel szedtük. Az olyan szavakat és karaktereket, amelyek a kód részét képezi, de hivatkozás végett a foly6szövegbcn is szerepelnek ugyanennek a betútípusnak a dőlt változatával írtuk. • Az új vagy fontos fogalmakat szintén egy speciális betűtípussal jelöljük. • A könyvben találhat6 valamennyi kódban megszámoztuk a sorokat I-Ia tehát egy sor elejéről mégis hiányzik a sorszám, az annak a jele, hogy átl6g oda az e l őz6 sor, vagyis amit látunk, az csupán egy sor folytatása. Ilyenkor a két sort begépelésnél természetesen egyesíteni kell, vagyis egy sorba keU őket gépelnünk.
I
xxii Tanuljuk meg a C++ programozási nyelvet 24 óra alatt
A CD tartalma A könyvhöz mellékelt CD-n a
következőket
laláljuk:
• V,llamennyi a könyvlX!n szereplő fomlskódol. • Egy szabadon használható integrált fejlesz16 i környezetet ODE) és fordítóprogramol,a Borland C++BuilderX-el. Bár a könyvben sLereplő valamcnnyi kódot kipr6bálluk ezzel a fordft6programmal, fontos megjegyezni, hogy maga a kötet nem e rről a termékről sz61. A bemutatott kódokat bármely stabil C++ fordít6valle lehel fordítani. Szintén fomos hangsúlyozni, hogy nem minden fordítóprogram kell6cn friss, vagy hű az érvényben [évei szabványokhoz, így egyes k6dokkal helycnként és id6nként gondunk lehet, miközben a többi kifog5swlanul maködik. Mivel a piacon számtal:m fordít6program és azoknak mcgszámlálhatatlan változata 1(;!111CtÓ fel, természetesen nem v511alkozllatunk ezek teljes áttekintésére. • Az 6rák végén szerepl6 gyakorlatok közül számosnak a megoldásál.
I.
RÉSZ
Bevezetés a C++ programozási nyelvbe 1. óra
Kezdö lépések
2. óra 3. óra 4. óra 5. óra
Egy C++ program részei Változók és állandók Kifejezések és utasítások Függvények
6. óra
Vezérlési szerkezetek
1.
ÓRA
Kezdő
lépések
Ebben az 6rában a következő kről lesz sz6: • Hogyan telepítsünk egy fordítóprogramot és hogyan haszn:í ljuk azt • A C++ nyelvt1 programfejlesztés lépései • I !ogyan gépeljük be, fordítsuk le, és linkeljük első működé CH programunkat
Elókészülelek a programozáshoz A C++ nyelv filozófiája - akárcsak a löbbi objektum -orientált nyelvé (ilyen például a Java iS) - , megköveteli, hogy a munka megkezdése előtt tervezzünk. Az olyan egyszeru fe!ada tok persze, amilyenekkel a könyv első néhány fejezetében találkozhatunk, nem igényelnek túl nagy előkészületeket. Az olyan összetett feladaloknál azonban, ;'l!nclyekke1 egy igazán profi programozó nap mim nap találkozik, a tervezés már létfontosságú. Minél részletesebb a terv, annál zökkenőmentesebb lesz a feladat megvalósítása a megadott határidőt és a kölL<;égkeretet is figyelembe véve. Egy jó terv ezen kívül segít a hibalehetőségek rninimalizálásában és megkönnyíti a későbbi karbantartást is. Becslések szerint a szoftverfejlesztés költségeinek 900/o-át a hibakeresés és a karbantartás adja . A megfelelő tervezés nagy mértékben csökkenti ezeket a költségeket, amely aztán a teljes projekt költségvetésén is meg fog látszani.
41J. rész · Bevezetés a C+ + programozási nyelvbe Egy program tervezésekor az első kérdés, amit fel kdl tennünk a következő; Mi az a probléma, amil meg szeretnénk oldani? Minden probléma megoldásához megfelelően behatárolt célt kell kitűzni, mint ahogy ezt a könyv legapróbb példáinál is megtesszük. A második kérdés, amit minden valamire való progmmozó feltesz magának: MegoldhafÓ ez a/dadal eg)'edi program írá.sa nélkül is? Egy régi program újrah'lsznosítása, átdolgoz,ísa, vagy egy már meglév6 kereskedelmi szoftver sokszor jobb megoldást nyújthat egy problémára, mim mindent teljesen elölről kezth:::ni. Aki ilyen alternatívákat kínál ügyfelei sz:hnára, sose mamd munka nélkül. Egy köllSéghatC:konyabb megoldás ma kulcs lehel egy később elénk !.áruló ajtóhoz, ajánlólevél t:gy jövőbeni mt:gbízáshoz. Hl! mégis új szoftver írásába vágjuk a fejszénket, a költségek akkor is jelemősen lefamgllat6ak, amennyiben felhaszn:'iljuk a már korábban megírt fi.lggvénykönyvtárakat és oszt(tlyok:tt (objektumdefiníci6k könyvtÍlra). A fejlesztés sebessége ezekkel radik,íHsan növclht:t(5, amely részben magyarázat cl C++ népszerO"ségére is.
Tudta hogy, ..?
IBM - OeveloperWorl
Noha az IBM DeveloperWorks portál- http://www.ibm.com/developerworks/ - elsősorban a Java-hoz nyújt segítséget, mutat néhány remek példát a függvénykönyvtárak megosztásával kapcsolatban is, Feltételezem, hogy az olvasó megértette a megoldandó probitmái és az mindenképp új szoftvert igényel. Vágjunk hát bele a tervezésbe.
A C++ nyelvjárásai (C++. ANSI C++. ISO C++ és a Windows) C++ egy programozási nyelv. A Windows, a Unix (mint például AIX, Solaris, HP-UX, stb.), a Linux , és II Mac OS X ezzel szemben operáci6s rendszerek. Bizonyára ;IZ olvasó úgy szeretné megt:tnulni a C++-t, hogy késo"'bbi programjai architektúrát61 és operációs rendszertől függetlenül működjenek. A TlnLlljuk meg a C++ programozási nyelvet 24 óra alatt dmű könyvben ennek megfelelően nem tételezünk fel sernrnit a használt operáci6s rendszert illetően. A könyvben az ANSI/ISO C++-t - vagy más néven a szabványos C++ nyelvet - tárgyaljuk. Ez egy nemzetközileg elfogadon változat, meJy bármely architektúrán és bármely fejlesztői környe7.etben használható.
Aprop6
Mi az ANSI? Mi az ISO?
Al ANSI az Amerikai Nemzeti Szabványügyf Intézet (American National Standards Institute) rövidítése, míg az ISO a Szabványosítás Nemzetközi Szervezetéé (International Organization for Standardization). Ahogyanevükb ől sejth ető, a szab-
l, 6ra • Kezd61épósek
15
ványosításért felelősek az Amerikai Egyesült Államokban és az egész világon.
A szabványokon nyugvó nyelv használatának nagy előnye, hogy mindenki ugyanazt a leírást és ugyanazokat a szabályokat követi. _-\ könyv példaprogramjai az. ANSI/ISO C++ szabványt köve tik és csakne m bármelyik fordíl6 programmal le kell tudnunk fordítani őket. Keveset hivatkozunk majd ablakok1"3, listadobozokra, grafikára és ehhez hasonló dolgokra, I::zck ugyanis nagy mértékben ruggnek az operációs rendszert61 és a használt fejleszt6i környezeuől. A végeredményt mindig az alapértelmezett kimeneten kapjuk. Hogy ez mOködjön, valószim11eg közölnünk kell a fordítóval, hogy kamkteres felületre kívánunk alkalmazást írni. Ez a helyzet a Ha rland C++l3uildcrX-szd is. Néhány fordító - többnyire a grafikus felü le tre írtak (\Vindows, Mac, stb.) - ezt gyors (quick window) vagy egyszeru (simple window) ablaknak nt:vc:.::i, esetleg egys:.::eníen csak konzolos alkalm:lZ.áské nt hivatkozik tiL A könyvben mi mindvégig l11:lgát a fordít6p rogr:l mot (compi ler) hasznCIljuk. E:.:: a z a progmm, ami lefordítja az emberek számára olvashat6 forráskódot gépi kócinI. A végeredmény egy tárgykóclú állomány (object file) lesz. A linker szir,t{:n I::gy program, mely a tárgyk6dú ál!om:'inyokat összef(izve hozza létre magát a futtatható prognunot. A könyvhöz tartozó CD-n találunk egy ilyen fordítóprogramot. A következ6 részben Jé· pésr61-1épésre bemUIatjuk, hogyan kell telepíteni a Borland CH Builde rX-eL Ez egy Windows alapO integrált fejlesztői környezet (Integr:lted Development Environment IDE), mely szolgá ltatásaiv
A fordftó telepítése és beáUftása A könyvhöz mellékelt CH BuilderX integrált fejleszt6i kö rnyezet Microsoft Windows operudós rendszer alatt futtatható. Amennyiben ne m ilyen környezetben dolgozunk, nyugodtan ugorjuk át ezt a részt. Ebben az csetben ke resnünk kell egy, az operációs rendszerünkhöz elC:rhet6 fordítót. Az operációs rendszer dokumentációja vagy a szállító cég további részletekkel szolgá lhat a használható fordítóprogramo kkal kapcsolatban. Ha Windowst használunk , telepítsük a Borland C++ fordítót és a hozzá tartozó fejlesztői környezelet. A könyv ábráinak készítéséhez mi is ezt használtuk. Maga a gmfikus felület sokaknak ismerős lehel, hiszen találkozhattunk vele Borland Compiler, Borland CH BuilderX, CHBuHderX, vagy C++BuilderX IDE néven is, dc ez valójában mind ugyanaz a te rmék.
s i l. rész· Bevezetés a C++ programozási nyelvbe
Telepítés a CD-rol Helyezzük be a CD-t a meghajtóba és várjunk, amíg elindul az automatikus telepítés. Ha el nem történne meg, indítsuk el a CD-n találhat6 install . exe programot. Az install. html és a readme . txt további információkkal szolgál a program legfrissebb kiadásáról. Amint elindult a te1epít6program, kövessük az alábbi lépéseket: Kattintsunk a Next gombra. Fogadjuk el a Fclhaszn:íl :ísi feltételeket. Kattintsunk a Next gombra (hagyjuk alapértelmezeuen). Válasszuk a Full Install Sel pontot (Teljes telepítés), és kattintsunk a Next-re. Válasszuk ki a telepítés cél könyvtárát (az alapértclmczctl val6színl1leg megfele16) és kattints~l/1k Next-re. 6. Válasszuk ki az ikonok helyét (aL alapértelmezett valószínű l eg itt is megfelel6 lesz) és kaltintsunk a Next-re. 7. Végül pedig kattintsunk az Install-ra. Várjuk meg amíg a telepítés be fejeződ i k. l. 2. 3. 4. 5.
Természetesen a progr-drnunkat még regisztrál ni is kell. A Borland-tól egy állományt kapunk, melyet be kelJ másolnunk a program könyvtá rába (egyszen.íen kövessük az emailben kapott egyszen.í lépéseket). Ezzellulajdonképpen máris használható a program, de cé lszerű még elvégeznünk rajta néhány beállítást.
A Borland C++BuilderX fordftó b.állftása Tndílsuk el a C++BuikJe rX-szct, .1 köveLkez6képpcn: kattintsunk a Start gombra, majd a Minden program pontra. ILL keressük ki a Borland C++ 13uilderX pontot majd legvégül kattintsuk a program ikonjfl r-.I. Ha mindent jól csináltunk, akkor elindul a fejlesztői környezet. Most az alapbc{llIításokkal fogunk foglalkozni, amelyeket természetesen bármikor módosíthatunk :1 menüsorban található Tools menüponlra kanimva. Az l. l -es ábdra pillantva látható, hogy a menÜS{)f alatt két sorban helyezkednek cl gombok. Ez al eszköztár. Az egénnutatót az egyes gombok fölé húzva rövid leírást kapunk az adott gomb f\Jnkciójll r61. Az fels6 sor els6 hét ikonj{lVal az alábbi mtiveleteket érhetjük el balról jobbra haladva: új fájl létrehoz{)sa (iu készíthetünk új proje kteú, [5 jl megnyitása (létező projekt megnyitása), fájl ism(:telt megnyitása (a lemezen található állomány beolvasása), forrás bezárása, aktuális fájj mentése, az összes fájl ment{!sc, illetve a nyomtatás. Az 10101 feliratú fájl ikonra kaUintva fordítha tjuk le a forrást és készíthetiink futtatható állományt, a zöld színű nyíl pedig elindítja az elkészült progr.amot. A gombok megFelel6it a menüsorban is megtalálhatjuk rövid keresés után.
Számos ikonnál szerepel a projekt szó. A projekt egy csomag, amely összefog több egymáshoz taltozó ~!lományt. A C++BuilderX .cbx ki te~es7.téssel menti a projektfájlokat. A legtöbb a könyvben szerepl ő projekt csupán egy állományból áll, ami kissé ér-
I
1. 6ra • KezdI lépések J Lelmctlcnnck tt1nhet. Ugyanakkor több tucat vagy akár több száz forráskódállománnyal rendelke:d:i projekt esetén igen hasznos ez a szolgáltatás, tehát érde mes mcgtanulni a használatát.
;
~··~Il~"K
...
-•
[lDo<:uo><"" .od"''''~,k'p", .. ,,/C.... ''l~pllr,''l
t~~;[d< :;0.0'" !"" [Tolo
o~et·~·OA ~
11:-..""",-:ld-· -
~~.,~-~.ol ~~- ..
..... ~
,- ,,-
,
-
~
'II
M.
"'. ~-
..
ii]FioI,'L-
•
~ ,-
~~ L.'" ~' -
i
--,..,,,'"~.
- l -,T
-
-
",
.
~
,.
• o. •
1.1 ábra , \ C++BuilderX hllcgráll fejlesztői Mrnyazel (IDE)
A C+ +BuilderX~ben alapértelmezésként be van ka pcsolvn a hibakeresés lehetősége. Mi eZL a könyvben csak később fogjuk haszná lni, de addig is érdemes vele megismerkedni. A Borland fejlcszrókörnyezete beépített szövegsze rkesztővel rendelkezik. Kattintsunk az Új fáj l ikonra, majd pedig a Source File (forrásfáj D fülre. Ez a szolgáltatás egy fájl szerkesztésére szo lgál. Több állományesetén szinté n az Új fáj l ikonra kell kattintanunk, azonban e kkor a Console Application-t (szöveges alkalmazás) kell választanunk, melye t a Projekt fülön találunk. Ha használjuk a varázslót, akkor az is elkészíti számunkra a ke retrendszert. Ha azonban kikapcsoljuk, akkor egysze rű forráskódszerkesztó ablakot kapunk. Mentsük a projektet és minden hozzá kapcsolódó ál!ományt egy külön könyvtárba. A projektállomány . cbx kiterjesztésű, míg a CH forráskódot taltalmazó állományok végződése. epp. Érdemes megjegyezni, hogya szerkesztő mentéskor automatikusan . cpp kiterjesztéssellátja el a forrásáIlományokat. Az éppen szerkesztett fájl mentéséhez kauintsunk az aktuális fájl mentése ikonra.
a l I. rész· Bevezetés a C+ + programozási nyelvbe
Fordítás a Borland C++BuilderX-szel A projekt va!,')' egyedi fájl fordításához és a bináris állomány összeszerkesztésf:hez katlinL'mnk a Make Projekt ikonra. Néhány fordító ezt a OliTveletet két lépésben o ldja meg:
d6bb fordítanunk kell aztán szerkeszteni. A legtöbb integrá lt környezet ugyanakkor eZt már egy lépésben hajtja végre. A program - fejlesztői környezeten belüli - futtatásához kallinLsunk a Run Project ikonm, hibakereséshez haszn;íljuk a Debug Project ikont. Ha egy programot hibakercs6vcl fUllalunk , akkor az 1.2 ábrához hasonl6 kép fogad minket. Ebben az esetben a hetedik sorra á!lítot!am be és egészen odáig lefutott a program.
r.,: t
. ~"'I~.,~ C
tH"e:i - '!il."ilio ....
(It""'" ..... ~" /\1, ID !!!I-..."'" I-",~ .iit..
-
'"
8:::.
••
_
..,..... ,-., .x·...., ... ; ----...-. .. ._ '._<
1000;....,"" . "' Iio "I"""""""kbp,.lt
I~"":--~!I-~'"
..
,
~
~.
... -c
--
>ot .....
.-
••.
"";'~~
, . .s 'a
'ol
,
' 1'
~
"'. . c_. . ,. . ,. .___. . '. . . ,
••
-
._-_., -
~
,,'
"1'11 1
... ..... 0'
_
~
• . - II
.
~
~.:.J'-'I
li
~-
« .......,.
",
-"
.. . .
'."
• . ... L...
...... _ • .....0
. ;,
h@!E
---
1.2 ábra A Borland C++BuildO/X ltilxlA,'(Jr(!Sője
Am int a képerny6nkön megfigyc1het6, a barna csík jeh:i, hogy melyik le.5z a következő programsor, a piros kör pedig maga a töréspont. A Löréspontnál ideiglenesen megáll a program futá sa. Hibakeresés esetén érdemes már a legelső gyanús sornál töréspomot elhelyezni. TöréspontOl :IZ adott sor bal szélére kattintva helyezhetünk el. A 5tep ikon segítségével egy sol1léphetünk előre a futtatásban , míg a Continue ikon folytatj .. a program futtatását a következő töréspontig vagy a program végéig. A többi ikon segítségével más·más szemszögből vizsgálhatjuk a programunkat. Ezek az ikonok az ablak a ljánál találhatóak, Hasznos szolgáltatása ennek a környezetnek az automatikus fordítás és szerkesztés, amennyiben ezt futtat.'ís vagy hibakeresés e16tt nem tettük meg.
,. óra • Kozd6 lépések A Help menü Aboul menüpo ntjában található a fejlesztői környezet verziószáma, mely 1.0.0.1786. A Help menü Help Topics (Súgó témakörök) menüpontjában kimerítő C és C++ refer enciát ta lálunk. Érdemes kö rülnélni itl is.
Tudta hogy...?
Kömyezetfüggó ság6
A bemutatotl fejlesztői környezet egyik hasznos tulajdonsága a környezetérzékeny súgó. Kattintsunk egy kulcsszóra és nyomjuk meg az F1 gombot. Egy új ablak nyílik
meg információkkal az adott parancsról.
A fordft6program és a szövegszerkesztó Egy fordít6progmm gyakran saját beépített szövegszerkesztővel rendelkezik. C++ állományok szerkesztC:séhez használhatjuk eZt, vagy bármely ker<:!skedelmi szerkesztőt, dc akár egy irodai programcsomagot is. Mim .I%t már korábban említellük, a Borland fordítója rendelkezik beépített szerkesztővel. 13ánn ilyen szövegs;:.erkeszt6t is vá lasztunk, fontos, hogy tudjon formázás nélküli, egys;:.eru s;:.öveget menteni. Garantáltan haszmí lható szerkesztő például a Windows jegyzeuömbje, a DOS-os EdiL parancsa, a Brief,
Figyeleml
Legyünk 6vatosak
Amennyiben irodai szövegszerkesztöt használunk, fontos, hogy formázás nélküli állományként mentsük a kódot, mert bár mi el tudjuk olvasni, a fordítóprogram nem tud majd vele mit kezdeni.
A szövegszerkeszt6vel készített állományok forráskódok és C++ nyelvben. cpp, . exx, . ep vagy. e kiterjesztéssel bírnak. A könyvpéJdáiban szerepl ő állományok kiterjesztése . epp, hiszen el az alapértelmezett a Borland fordítójába n.
Apropó
Forrásk6dok kiterjesztései
A legtöbb c ++ fordító nem törödik a kiterjesztéssel, de amennyiben nem állítottunk be mást, alapértelmezés szerint jó néhány . epp-t fog használni.
19
10
II. rész · Bevezetés a C+ + programozási nyelvbe
Használjunk egyszeru szöveg szerkesztésére képes szerkesztőt vagy a fordító beépített szerkesztőjél. Használjuk ki a szerkesztőp rogramok nyújtOll
Ne használjunk olyan szövegszeramely speciális formázókaraktereket is ment. Ha mégis szeretnénk ilyent használni, akkor mindenképp ASCII szövegként mentsük a forráskódot. kesztőt,
Fordftás és linkelés Noh:1 ,I forrás kód számos ember szi'lmára kicsit kínaimik tűnhet, azért tagadhatatlan, hogy emberi szem számára oh'asható szöveg. A forráskód tehát nem program, közvetlenül nem futtatható.
Apropó
Fordftás a parancssorból
Hogya rorrásk6db61 ruttatható programot készItsünk, szükségünk van egy fordítóra. A paraméterezési m6d fordftónként változó, igy részletekkel a program dokumentádója szolgálhat. Ha parancssorból kívánjuk futtatni a fordítót a fejlesztői környezet helyett, akkor a használt programtól és operációs rendszertől függ ően az alábbiakat kell begépclnünk: Borland fordító esetén - bcc32
-o GNU esetén - gxx -o Cygwin fordftónál- gH <[ájlnév> -o Linux alatt - 9H -o UNIX alatt - CH - o
Fordrtás az
integrá~ tejleszt6i
környezetben
A legtöbb modern fordít6 integrált fejlesztői környezetet is nyújt. Egy ilyen környezetben a Build vagy Compile menüpont kiválasztásával vagy a megfelelő billentyűkombi náci61enyomásával érhető el az alkalmazás fordítása. A C++BuilderX-ben egyszeruen kattintsunk a Make Project ikonra.
1. óra • Kezdő lépések I II
Linkelés (összeszerkesztési Miután lefordítottuk a forráskódot, létrejött a tárgykód. Ez még mindig nem fuUatható . Ahhoz, ho.gy futtathatóvá tegyük, le kell futtatnunk a linkert. Egy tipikus - C++-ban írt - futtatható program egy vagy több tárgykódú állomány és egy vagy több függvénykönyvtár összeszerkesztésével, úgynevezett linkelésével jön létre. Függvénykönyvtár származhat a fordítóprogram készít6jétól, külső forrásból, de írhatunk ilyent akár mi magunk is. Maga a fü ggvénykönyvtár hasznos függvények, eljárások és osztályok gyűjteménye, amelyekre hivatkozhatunk a programunkban. A függvény egy olyan kódrészlet, amely valamilyen konkrét feladatot valósít meg. Összeadhat példáu l két számot, vagy kiírhat valamit a képerny6re. Az osztály egy új típus definídója. Egy osztályban az adat és a rajta végrehajtható műveletek egyaránt jelen vannak. A könyvben később mHjd részletesebben megismerkedünk az osztályokkal. Egy futt<1tható állomány létrehozásának tipikus lépései a
következők:
1. Forráskód létrehozása és elmentése . cpp kiterjesztéssel. 2. A forráskód lefordítása tárgykódd{t 3. A t,írgykód és b{trmely más - a futtatásl10z szükséges - fuggvénykönyvtár összeszerkesztése futtatlmt6 állománnyá.
A Borland fejlesztői környezetében a Make Project fordít és linkel egy lépésben. Egyegy modul kü lön·kÜJÖn is lefordítható, ehhez kattintsunk a jobb egérgombbal a projekt ablakban a modul nevére, majd a Build Tools-n<'il válasszuk ki a BCC32-t (mely maga a C++ fordító). Amennyiben a Iinkelést szeretnénk saját kezűleg végrehajtani, kattintsunk jobb egérgombbal a projekt nevére, majd a ·13uild Tools-nál válasszuk ki az ILINK,,2-t (ez a fejlesztői környezethez mellékelt linker)
A fejlesztési ciklus Ha minden program első próbálkozásra tökéletesen működne, akkor a fej lesztési ciklus egyszeruen így nézne ki: programírás, a forráskód lefordítása, linkelés és futtatás . Sajnos azonban csaknem minden prognlm - még a legegyszerűbbek is - tartalmazhatnak és tartalmaznak is hibát. Néhány probléma már a fordításnál jelentkezik, egyesek a linkelésnél, de olyan is szép számmal akad, amelyekre csak a program fu ttatásakor deru l fény. nSyntax error" (nyelvi hiba) jellegű hiba fordításkor jön ehi Ez azt jelenti, hogy hibát vétettünk a nyelvtanban. Találkozhatunk ezen kívül "warning" (figyelmeztetés) je llegű hibával is . Ilyenkor elképzelhető, hogy tényleg hibáztunk, de a fordító el tudta készíteni a tárgykódot, men valahogyan értelmezte a hibás kódot is, csak épp gyanúsnak ta-
12 11. rész • Bevezetés a C++ programozási nyelvbe lálta. Linke lési hibaüzenete t leggyakrabban hiányzó függvénykönyvtá rak va!,'Y elg6pelt függvénynevek okozhatnak, Azok a hibák, ame lyekkel a program fuUatása közben találkozunk, logikai hibák, Teljesen lényegtelen, hogy milyen jellegű hibába botlunk, ki kell javítani a forráskódot, utána pedig újra le fordítan i, újr.! kelllinkelni és újra le kel! futtatni az alkalmazásunkaL Ezt a folya matot mutatja be az 1.3-as ;íbra. L1that6, hogy in egy ciklusr61 van szó. Nagyon ritka az a program , amelyik elsőre hibátlanul fordítható, linkelhető és ráadásul még hibátlanul is fut.
Start
Igen Fordftási hibák?
Igen
'----'-<.
Futási hiba? N,m
Kész
1.3 ábra A C++ program/ej/esz/és lépései
1. óra· Kezdő lépések 113
HEllO.CPP - Az elsó C+ + programunk A C-vel rokonságban álló programozási nyelveket oktató könyvek körében már hagyomány, hogya Hello World szavak kiíratása az első program. Ezt a hagyományt - melyet Kernighan és Ritchie C nyelvről szóló könyve indított el - mi scm kívánjuk megszakítani.
Apropó
Új program létrehozása
C+ +8uilderX-ben új projekt létrehozásához kattintsunk a New Source File-re (új forrásáIlomány létrehozása). A könyv példáihoz mindig a Console Application-t (szöveges alkalmazás) válasszuk. A projekt létrehozása után elkezdhetjük a munkát a szerkesztőablakban. Gépeljük be dZ első prog,dmot betűről hetűre a szövegszerkesztőbe. Ha meggyőződé sü nk szerint nem vétettünk hi b~t, mentsük, fordítsuk le, linkeljük össze - amennyiben a fordít6 ezt nem teszi automatikusan - és pr6báljllk meg elindítani. Képernyőnkön II J Jell0 World szavak jelennek meg. )elenesetben nem maga II program a lényeg, hanem az, hogya fejlesztési ciklust és a fordítót megismerjük. Pár lecke után teljesen világossá v{tlik majd a program m űködése.
Apropó
Néhány szó a könyvben található forráskódokról
Az alábbi forráskódban a sorok az adott sor számával kezdődnek . Ezek csupán hivatkozások megkönnyítése végett vannak ott. Az 1.1-es lista O. sora például így néz ki begépelv"e: #include
A sorszámok tehát megkönnyítik a magyarázó szöveg értelmezését. Egyes fejlesztői környezetekben - ilyen például Borland is - szintén találkozhatunk sorszámozással, de ezek nem részei a forráskódnak. A könyvben minden program sorszámozása a o. sorral kezdődik, mert maga a C++ nyelv is O-ról kezd a számozást. A korábban bemutatott fejlesztői környezetben a számozás 1-től kezdődik, de ha ezt valaki zavarónak találja, nyugodtan kikapcsolhatja. Ezt a lools menü Editor Options menüpontjában található Une Numbering jelölőnégyzet kikapcsolásával tehetjük meg. A könyvben található összes forráskód a mellékelt CD lemezen is megtalálható, mégpedig a programot bemutató blokk fejlécében szereplő néven.
14 11. rész· Bevezetés a C+ + programozási nyelvbe
\.\ usta - hallo.cpp. az eII6 C+ + _ _ o: 8include 1. 2 , int main()
,. ,. 4: 5:
std : : cout « ret.urn O,
"Hello World!\n";
Gy6z6djünk meg róla, hogy hibátlanul gépcltük be a kódot, különös tekintettel az speci{llis ír.isjdckrc. A 4. sorban találhat6 « egy átidmyítást jelképez. Angol kiosztású bi!lcntyuzcten nyomjuk le egyszerre a Shiftet és a v(.!ssz6t. (Magyar kiosztás esetén hasznMjuk az AltGr és a J ~ billenty{ikombináci6t.) A 4. sort pomosvesszO:vel fejezzük be, figyeljünk rá.
Apropó
Nevek és névterek
Az std : : cout parancs két részból áll: std és cout.. A cout -tal frath atunk ki va· lamit a képernyőre. Ez része az alapcsomagnak. Minthogy azonban sok ember ír új
fü ggvényeket és objektumokat a C++-hoz, könnyen
előfordul,
hogy ugyanazt
az azonosltót használják más célra. Hogy ezeket megkmönböztessük, meg kell ad-
nunk a használni kivánt névteret vagy változatot is. Jelen esetben tehát Igy közöl· jük, hogy az std csomagban
szerep lő
cout objektumot szeretnénk használni.
Fontos, hogy pontosan a fordítóprogram dol.:umentáci6jának megfelel6en járjunk el. A legtöbb fordító - így a Borlandé is - aUlomatikusan összeszerkeszti a t.'ÍrgykMotvllgy tárgykódokat - futtatbató álJománnyá, de ha más fordítót használunk, mindenképp olvassuk el a leírásál. Ha hibaüzenetet k:lpunk, nézzük meg alaposan, ho&')' ugyannt gépdtük-c be, mint ami a korábbi l.l-es progmffi k6djában látható. lIa nem a l3orhll1d fordít6ját használjuk, további hibaüzeneteket k:tphatunk. I [a O. sorra a rendszer a ~CannOl find iostream hibát adja fordításkor, akkor II fordító nem talál ja a megfelelO: fejlécállornányt. Ennek a helyét a fordító konfigurádós á!1ományáhan vagy környezeti vá ltozók segítségével állíthatjuk be. Ha a fordító II 2. sornál nem találja a main () fi.lggvény prototípusát, akkor egy int ma i n () ; sort kelJ a második sor elé beszúrni Ebben az esetben ezt a könyv összes példájánál meg kell ten nünk. A legtöbb fordító nem igényli ezt (a 130rland sem), de néhány igen. H
A módosítás után így néz ki a programunk:
o:
tinclude
1. 2 : int main() ; 3 : int main!) 4: {
5: 6: 7.
std :: cout « return O;
"Hello World!\n';
1. óra • Kezdő lépések 115 Próbáljuk meg lefordítani a hello. cpp állományt és fu ttassuk le. Ha mi ndent jól csi náltunk, kiírja a képernyőnkre hogy: He l lo Wo rl d!
lia sikerülL, akkor gratulálok, sikeresen begépeltü k, lerordítottuk és lefuttattuk első C++ nye1vű programunkat. Szinte hihetetlen, de csaknem minden profi C+ + programozó s7:ámára ez volt az első program.
Fordftási hibák Számtalan oka lehel a fordítási hibáknak. A leggyakoribb az e!gépelés vagy más apró hiba . Néhány fordító nemcsak a hibára mut.1t rá, de arra is, hogy az a forráskódban hol van. Néhfiny akár még javítiisi tanáccsal is szolgálhat Nézzük meg, hogy reagá l a fordít6tlk egy sZ{lI1dékosa.n elhelyezelt hibám. Ha a HELlO.CPP hibátlanullefutot! , (Igy n(:zzük meg, mi történik, ha töröljük a 6. sorbrm található kapcsos zárójelet. A programunk az 1.2 programlism mása kell, hogy legyen.
1.2 Ua.. - Példa a forditáai hibára (hallobad.cppl o : #include 1. 2 : int main() 3. 4: std : : cout « "Hello World! \n' ; 5: rctllrn O;
Fordítsuk újra a programot. Az alábbihoz hasonló hibaüzenetet fogunk kapn i: "hellobad . cpp· : E2134 Compound statement mi s .s i ng } in function main ( at line 7
A hibaüzenet - noha kissé rejtjclesnek tűn ik - közli, hogya probléma a forráskód végén található. A 7-es sorra hivatkozik, hiszen az lenne a következő sor a szerkesztőnk ben . Sokszor a hiba közvetlen a probléma közelében található. Amennyiben a fordító pontosan azonosít nlinden problémát, megpróbálja kijavítani forráskódot.
Apropó
EmlékeztelÓ a könyv forráskódjaival kapcsolatban
Ne feledjük, hogy a könyvben a forráskódok sorai előtt található sorszámozás csu· pán referencia. A számozás - akárcsak néhány fordító esetén - , O-ról indul. Más fordítók - így a Borland is - viszont l -től kezdik a számozást.
1s l l. rész· Bevezetés 8 C+ + programozási nyelvbe
Kérdések és válaszok Kerdés: Mi (l különbség az egyszefü szövegszerl..'eSZfŐ és az irodai szőIK!j!,szerh.'SZt6 Mzött? Vá/asz: Az egyszeru szövegszerkesztőve l egyszenl szövegfájlt készíthetünk. Semmi olyan speciális jel vagy forrn~Í7..ás nem kerül bele, amely az irodai szövegszcrkeszt6nél igen. Az egyszenl szövegfájlok nem tartalmaznak automatikus sortörést, félkövér, illetve dall betUket stb. Természetesen az irodai szövegszerkeszt6kkel is menlhetünk egyszen1 szöveg formátum az esetek nagy részében, de körtiltekint6en kell eljárnunk.
Kérdés: Afordít6m beépftcfI sz6vcgszerkeszfövel rendeU.tazik. llasználjam inkább az t? VúfC/~'z:
Csaknem minden fordító képes szinte bá rmilyen szövegszerkeszl{Svl::1 írt forr.1s-
kód lordítúsár:1 . A beépített sze rk esz tő előny e többnyire az, hogy felgyorsít ja a fo.::jlcszt~si ciklust. Komolyabb fordítók összkomfortos szerkesz16felületle! re ndelkcwck, mely a fejlcszt6i környezet elhagyása nél kül segít megoldani ;_ fordítási és tárgyk6dszerkesztési problémák:lt, de sokszor találunk gyors segítséget is. Természetesen a kü lö nálló szerkeszt6nek is megvmumk az el6nyei. Rendelkezhet példáu l olyan kiegészítésekkcl , amellyel a fcjleszt6 i környezet nem. De az is el6fordu lhat, hogy már rnegszoktuk és hatékonyabb a 111lJnkavégzés vele, mint a beépített szerkcszt6vel.
Kérdés: FilJye/men kíVlllhagyhatom a fordít6 fi8yellllczleléseit? Válasz: Nem . Swkjuk mcg, hogya figyehneztet6 i.lzent:tek is hibflk. A C++ fo rdítója minden esetben szól, H111ikor o lyan dolgot cselekszünk, amely nem ajánlott. Tegyi.lk meg a szükséges lépéseket, hogya figye lmcztctések is e1ttlnjenek. A hibaüzenet azt jelenti, hogya fordító a forráskMu nkat nem ILldja gépi k6ddá alakít.ani. A figyelmeztetés azt jelenti, hogy sikerült ugyan lefordítani, de nem biztos, hogy úgy, ahogyan mi szerettük volna. Kérdes: Mil jeleni az, hogyfordirás; idö? Válasz: A fordítási id6 az az id6, am ikor a fordító fut. A Iinkelési - vagy szerkesztésiidő
a<:, amikor a linker fuI. A fu tási
idő
pedig amikor maga a programunk
működik.
Gyakorlatok Ebben a szakaszban o ly:m kvízkérdéseket és gyakorl6feladatokal gytljtönünk össze, melyek segítenek megszilárdít.ani az elsajátíton tudás!.
l. óra·
Kezdő
Kvfl 1. Miért fomos projektet használni? 2. Mi a hibakeresés célja? 3. Miért érdemes olyan fordítót használni, me!y támogatja a nemzetközi sZábványokat? 4. Milyen segédprogramokkal $zerkeszthet6 a forráskód?
Feladatok A feladatuk
1t::hetóv~
teszik az önálló gyakorlást. Nincs egyetlen helyes megoldásuk.
1. Pr6báljuk ki a fejlesztő környezet gombait és menüit. Mit csinálnak? Könnyebbé teszik az életet? Vagy éppen megnehezítik? 2. Pr6báljuk meg lefordítani, összelinkeini és lefull:!lni a fejlesztői környezethez mellékell példákat. Vessünk egy pillantást a forráskódra, hogy kitaláljuk miért és
hogyan keletkezik a végeredmény. 3. Pr6báJjuk ki a Borland - vagy bármely más - fordító parancssoros változatát. Könnyebb szerkeszleni, fordítani, linkelni, futtatni más eszközökkel? Vagy ~pp az integrá lt fejlesztői környezet könnyíti meg a helyzetünket?
Válaszok a kvfzkérdésekre 1. A projekt összefogja a futtatható ,11lomClnyhoz szükséges komponenseket. Egy aJk,llmazás sok-sok különböző f:íjJt ig~nyelhet , de projektbe összefogva együtt kezelhetóek, útl{IlJmtóak. 2. A hib:lkeresés célja természetesen a hibák (bug) fe lkutatása. ilyenkor olyan logikai hibákat keresünk, melyek a program fordítását nem befolyásolták ugyan, ele az mégsem aZlleszi, amjt a programozó elképzelt. 3. Az egyszeru válasz: követi a szabályokat. A fordítótól elvárható, hogy minden esetben ugyanúgy viselkedjen. Talán nem is gondolná a kedves O lvasó, de még a benzinkutakról és az autók benzintankjár61 is szabvá ny rendelkezik . Ha minden gyártó saját feje után menne, akkor nem lenne biztos, hogy A gyártmányú autóba B gyártmányú benzinkúton mdunk tankolni. 4. Bármilyen programot használhanmk, amely képes egyszerű ASCll szöveget menteni. Használhat juk az operációs rendszerhez melléke[t szerkesztőt (DOS alatt az edit, Uni..Vlinux alatt a vi és az EMACS, Windows alatt a Jegyzettömb), kereskedelmi alkalmazást (mint például a BrieO, integrált fejleszt6i környezetet vagy akár egy irodai szövegszerkesztőt is. A lényeg csupán az, hogy tudjon formázás nélküli szöveget menteni.
17
2. ÓRA Egy
C++ program részei
Ebben az órában a következőkról esik majd szó: •
Mikor megfelel6 választás a C++ nyelv
• Milyen részekból áll össze egy CH program •
llogyan al kotnak egészet a részek
• Mik a függvényck , és mire használhat6k
Mit611ehet jó választás a C+ + nyelv M;:mapság il legtöbb profi szoftverfejlesztő C++-ban dolgozik. Ennek elsősorban az az oka, hogy ezen a nyelven gyorsan működé, mégis kis méretG programokat írhatunk, ehhez pedig egy robus:ltus és különböző platformok között könnyen hordozható környezet áll rendelkezésünkre. A ma használatos fejlcszt6eszközök segítségével kifejezetten összetett és hatékony kereskedelmi alkalmazásokat írhanlOk viszonylag egyszeruen, ám ennek ára van: meglebetósen alapos ismeretekkel kell rencleJkeznünk a C++ nyelv szolgáltatásir61.
20 II. rész • Bevezetés a C++ progfllmoz.ási nyelvbe Mfls programozási nyelvekhez képest a C++ viswnylag fiatalnak számíl. Persze az is igaz, hogy maga a programozás is mindössze hatvan éves múltra tekinthet vis.<;za, de a C++ még ezen a skáltin scm számít réginek. Az első nyelv megszületésc óta a progrdllloti'ls módszertana igen jelentós fejl6désen esett át, amelynek egyik lépfse éppen a C++ volt. Ez a nyelv ugyanis történetileg a C nyelv továbbfejlesztésének tekinthető. Mivel pedig maga a C is minJössze 30 éves, világos, hogy a C++ sem lehet ennél iJŐsebb. programozók kez<.!(!llx:n ,l létező legegys7.eruöb gépi urasításokkal, vagyis gépi kódban dolgoztak. Ennek a nyelvnek az utasításai egyes(!k és nullák hosszú sorozataiból állnak, ami érthető módon nem túl könnyen kezc1hel6. Hamarosan megszOlettek tehál az els6 assemh1cr(!k, amelyek ember számám is olvasható reprezenlflciókat, úgynevezett nmemonikokat rendeltek a gépi utasításokhoz. Ilyen volt például az ADD vagy a MOV, amely(!k egy-egy konkrét gépi k{x.!ú mOvc1elnek fele l teth etők meg. Id6vel aztán ki
A fordítóprogramok (compiler) ezzel szemben elóbb végigulv:lssák a teljes forráskódot, és abh61létrehozn:1k egy úgyncvczctlt~rgyk6dú 511om(tnyt (objecl code). A feldolgozásnak ezt :lZ első lépését nevezik fordításmk (compiling). A fordító tehát egy l:írgykódú állományt (object file) állft elő, amit ti második 1 6p~sben a szerkesztő (linker) alakit full:ltllató progmmm:i. A futtatható progmm az az :illúmány, amit aztán az open'\ci6s rendszer segítségével lefutlatharunk. (Zárójelben talán érdemes megjegyezni , hogy aZ itt említett lárgyk6dnak (object code) semmi köze sincs a könyvben késoób tárgyalandó objektumokhoz.) Mivel az interprcterek közvetlenül végzik II gépi kódra alakítást és a végrehlljtást, a programozó számára rendkívül kénye\mesen használhatók. fl. fordítóprogl1lmo k ezzel szemben elsőre kissé k6nyclrnetlenek, hiszen segíL~égükkel csak több l6p6sben ~I lítható e l ő a futtatható állom{my. H,l ;lzonban jobban megvizsgáljuk a dolgot, kiderül, hogya kényelem elvesztése jelentős haszonnal is jár: az így létrehozott gépi kód sokkal gyorsabban fut, mint amilyen sebességgel az incerpreterek működni képesek. A programozók legfőbb célja hosszú éveken át az voll, hogy kis méretú, gyorsan futó programokat alkossanak. A programnak azért kellett kicsinek lennie, mert drága VOll a memória, a sebesség és hatékonyság pedig azért volt alapkövetelmény, mert a h6skorban a számítási teljesítmény sem volt különösebben olcsó. Aztán ahogya számítógépek egyre kisebbek, gyors:tbbak és olcsóbbak lettek, és ahogya memóriaárak is visszaestek, a progranúejle~ztéssc\ kapcsolatos prioritá~ok is megváltoztak. Manapság
2. óra •
C++
részei 21
a programozó munkabére sokkal de sokkal magasabb, mim az ót foglalkoztató vállalkozás által használt bármelyik számítógép ára. Ennek megfelelően ma az igazi értékel a jól megírt és könnyen karbantartható programkód jelenti, nem a hardver. A .jól megirt" jelzőt bizonyára nem kell magyarázni, a könnyű k~lrbantarthatóság pedig :IZt jelenti, hogy ha a szoftver szolgált:llásaira támaszkodó vállalkozás igényei megváltowak, akkor a programot különösebben nagy beruházás vagy erőfeszítés nélkül át lehessen írni, vagy szolgáltatásainak körél ki \chessen terjeszteni.
Procedurális,
strukturá~
és objoktum-kÖ2pontú programfojlesztés
A procedurális vagy djár-ds központú fejlesztés alapelképzelése szerint a progr.un nem egyéb, lrlint a bemenő adatokon végrehajtandó műveletek sorozata. A stmkturált programozás nagy találm:1nya ezeknek a műveletcknek a rendszerbe szervezése, szisztematikus megközelítése volt, mégpedig azz
Kés:dtsilnk egy !istál, ami tartalmazz.1 az összes dolgozó bérét. Számoljuk meg, hány alkalmazottja van a cégnek. Adjuk össze :l fizetéseket. Osszuk cl egymással az imént kapott két számot.
A vállalatnak nyilván V:ln valamiféle adatbázisa, amelyben a dolgozók adatait tárolja, tehát a fizetések összegzéséhez a következő részlépéseket kell végrehajtanunk: l. Nyissunk meg egy adott dolgozóhoz tartozó bejegyzést. 2. Emeljük ki az adacszerkezetból a fizetés értéké\. 3. Adjuk hozzá a kiolvasott értéket annak a változónak a tanaimához, :Imely az összeget fogja tartalmazni. 4. Nyissuk meg a következő dolgozó bejegyzését.
22 11. rész • Bevezetés a C+ + programozási nyelvbe A dolgozói adatok kiolvasásának sekre bontható:
művelete
teljesen hasonlóan a következ6 részlépé-
1. Nyissuk meg a dolgozók adatait tarta lmazó fájlt. 2. Ugorjunk a kiv{llasZLoU dolgozónak megfelelő rckordra. 3. Olvassuk föl az adatokat a l emezről.
Összességében elmondható, hogya strukturált programozás nyújtotta megközelítés a mai napig rendkivül fon tos és hasznos paradigma az összetett feladatok megoldásával kapcsohttban. Ezzel egy(iu azért marddLak még jócskán megoldandó problC:mák. Ahogy növekszik a feldolgozandó adatok mennyisége, egyre nehezebb és nehezebb lesz mt!gbirk6zni az adatok és az őket kezdő eljárások szétválasztásával. Minél többféle műveletet akarunk végezn i egy adathalmazzal , annál zavarosabb lesz :l végeredmény. A procedurális megközelítést alkalmaz6 fejleszl6k időről időre azon kapják maguklH, hogy már megint kitaláltak egy (lj megoldást ugyanannak a régi problémának a kezelésére. Erre szokták fejlesztői körökben azt mond,mi, hogy az ilyen programozók wújra fölfedezik a kereket", és ez az, aminek az ellentéte az újrahasznosítható kód. Az új ....J.hasznosítás alapötlctc mindössze annyi, hogy a programokat lehet6ség szerint olyan ismert tulajdonságokkal rendelkez6 általános épit6elemekb61 kell összerakni , amelyeket szükség eset.én egyszeruen lx:emelhetünk a .saját kódunkba anélkül, hogy újra mcg kellene azokat írmmk, vagy akár csak meg kellene vizsg:'ilmmk a belső szerkezetüket. Természetesen ez a megközelítés sem tlj, hiszen a val6s objektumokkal dolgozó m6rnökök is pont ezt a módszert alkalmazZ{Ik. Ha egy villamosmérnöknek egy készülék elkészítéséhez szüksége van egy tranzisztorra, nem azzal fogja kezdeni, hogy fölfedezi a tran· zisztort, hanem bemegy a raktárba, lecmeli a pokr61 a megfelel6 alkatrészt, vagy ha nagyon mus7-áj, akkor módosít rajta egy kicsit. Fu rc.sa kimond.mi , de az objektum-központú prog,dm07-ás fölta lálásáig ezt a megközelítést a programozók nem alkalmazták. Az objektum-központú paradigma lényege mindössze annyi, hogy az adatokat és a velük dolgozó eljárásokat egy egységnek, egy objektumnak tekintjük, amelynek nevet :lclunk, és amely különt"éle tulajdonságokkal és viselkedésformákkal rendelkezik.
A C+ + nyelv és az objektum-központú programozás A C++ nyelv teljes egészében támogatja az objektum-központú paradigma alkalmazását, IniVel tartalmazza. azt 3. három alapszolgáltatást, amelyekre épícve ez megvalósítható. Ezek a beágyazás (enclIpsulation), az örökl6dés (inheritance) és a többalakúság (polimorfizmus; pulymorphism).
2. óra • Egy C+ + program részei
123
Beágyazás (encapsulation) Amikor egy villamosmérnök egy új eszközt épít, gyakorlatilag összeforraszt néhány készen kapott 'Ilkatrészl. Az új eszköz bizonyosan tartalmazni fog például ellenállásokat, konden7.1itorokat és tranzisztorokat. A tranzisztor egy eszköz, amelynek vannak bízonyos tulajdonságai és viselkedésformái. A mérnök ezt az eSlközt munkája során anélkül használhatja, hogy pontosan ismernie kellene annak a bels6 felépítését, vagy mű ködési mechanizmusait. Egyszeruen csak annyival kclllisltában lennie, hogy az illető alk:ltrész mire jó. Ahhoz, hogy ez a dolog ebben a formába n működjön, ;1 tranzisztornak mint alkatrészLípusnak tökéletesen meghatározott visclkedésformákkal és rulajdonságokkal kell rendelkeznie. Egy bizonyos, jól körülhalárolható funkciót kell megval6sítania, de azt teljesen. Ha egy dolog egy funkciót teljes egészében képes ellátni, azt röviden l:x::;igyazásnak nevezzük. Az objektum-központú metodológill szóhasználatával élve a lran zisl'.tor tulajdonságai be vannak ágyazva a tranzisztor objektumba. Ezek a bizonyos Lldajdnnságok és funkciók teh:h nem szanaszét hevernek vala hol az áramkörök dzsungelében, hanem a szerkezet egyetlen helyére összpontosu lnak. Pontosan ebből adódik aztán az az elf5ny, hogy tranzisztort használni az is tud, aki ncm is ismeri annak a bels6 működését vagy relépítését A C++ nyclv a beágyazás mcgvalósítását II fel h:lsznál6 által definiált típusok, az úgynevezett osztályok használatán kcreszl'Ülteszi lehetcívé. Egy j61 átgondolt és felépített osztály miután létrehoztuk pontOSan úgy működik, mint egy teljesen beágyazott egyed. Egyetlen egészként haszmíl1mtjuk, de nem kell tudnunk, hogy mi van benne pontosan, és az hogy működik. A működési mechanizmus a felhasznál6 el6tl ált.1lában teljesen rejtve maradh:ll, C&1k aZI kell tudnia az H1etőnek, hogy pon{osan mire való az adott elemtrpus, és hogyan kell azt használni. Az osztályok létrehowsár6l először II 7. órJ anyagában lesz sz6 részletesen.
Öröklődés és ~jrahasznosftás Az 1980-as évek végén aCiLibanknál dolgoztam, ahol az volt a feladatom, hogy fejlesszek ki egy eszközt, amivci az ügyfelck otthonról intézhetik banki ügyeiket. Mivel gyors:1n pi:1crn akamtnk kerülni ezzel a szolgáltatússal, nem a scmmib6l kezdtem cl megtervezni a készüléket, h,mcm egy már létező dologból, nevezetesen a tdcfonbó! indultam ki, és annak a képességeit fcjlcsztctLem továhb a bank igényeinek megfcleWen. Úgy is fogalmazhatnék, hogy amit végül megcsináltam, az egyfajta telefon volt, de néhány extr-.!. szolgáltatással kiegészítve, hiszen a megval6sítás során végig támaszkocltam a már meglév6 funkciókra, például a Iúvások kezelésére, viszont ahol kellett, ott továbbfejlesztettem azokat. Kicsit tömörebben újrahasznosítottam II telefon meglév6 funkcióit. Ugyanez! a dolgo! a C++ is tudja, hiszen az öröklődésen keresztül támogatja a k6dok újmhasznosítását Egy új típust, vagyis egy új osztályt ugyanis létrehozhatunk egy már
24 11. rész· Bevezetés a C+ + programozási nyelvbe meglév6 alapján is. Ilyenkor azt mondjuk, hogy az új alosztály t a régib61 származtatjuk, az általa képviselt fe lhasználói típust pedig származtatott típusnak (derived lype) nevezzük. Ezzel a S7..6ha.sználattal élve tehát az én .felspécizen telefonom nem egyéb volt, mint egy a közönséges telefonok osztályából származtaton új telefontípus. Származtatásnál az új osztály örökJi a régi ntinden tulajdonságát, de annak funkcióit újakkal is kiegészíti. Az örökl6désr61 és annak használatáról a 16. órában lesz majd szó részletesen. H
Többalakúság (polimorfizmus) Egy okos telefon ugyanabban a helyzetben néha máshogy viselkedik, mint egy közönséges "bula~ készülék. Ha hívás fut be, akkor a hagyományos telefon egyszen1en megcsörren. /\7. okos készülék ezzel szemben bekapcsolja a képerny6 világítását, ma jd emberi lmngon közli "lIív{\sod van kedves gazdám". A dologban a legérdekesebb n, hogy mindehhez a leJefontársaságna k semmi köze sincs. Hívúskor ponlOs~tn ugyanaz a jt'l fUl be a buta 0s ~lZ okos telefonba, csak az erre :IdOlt v51as7.Uk tér el egym(\stól. Az egyik csöng, ;1 músik beszél, vagyis mindkett6 éppen a "megfelela dolgot" esiníi lja a külső jelre. Az cffi:le ~hclycs viselkedésformák megvalósítását :l C++ is támog!ltja n úgynevezeu többalakú.súgon (polymorphism) keresztül. A progr.unokban függvények és osztályok egyaránt mUlathmnak többalakíságOl, ami nagy vonalakban annyit jelent, hogy egy prug. . ..unban lIgyan:......z~11 a névvel egy függvény vagy osztály több különböz6 megval6sílfisa is létezhct, 6s ezek minden helyzetben éppen a megfelel6 dolgot csinálják. A több:Ilakúsúgról, és annak kifinomult használatáról a 17. és 18 órában lesz szó b6vebben. H
Egy egyszeru program részei Az ds6 ór.íban múr lútlunk egy egészen egyszeru C++ progmmol. Ez volt a he110.epp, amelynek cgyszcrGsége dlenére súmos érdekes épít6eleme van. Ebben a szakaszban közc1ebbr6l is megvi"_'>gúljuk a program felépítését. A progmm k6dja, amit ill csak a kényelem végett isméuek meg a 2.1 Listában látható.
2.1 Lista - A hel10.COD Droqram ellYSZerúséae ellenére k1válóan alkalmu IHIV C+ + program épft6elemeinek bemutatására o:
~i.ncludc
"2 :
int main ()
3:
t
4: std :: cout « 5 : return O; 6,
'Hello World!\n';
2. óra • Egy C++ program részei 125
Hello
World!
A O. sorban beemeljük a program k6djába az iosLream n evű fájl tal1almál. A fordítóprogram szempontjából ez gyakorlatilag ugyanolyan, mintha begépeltük volna a hello.epp fájl elejére az iostrcam nevlÍ fejlécá llomány teljes taltalmát.
Az #include utasftás vizsgálata Amikor elindítjuk a rordítóprogramoL, az el6ször is meghív egy másik programot, az úgynevezett előfeldolgozót (preprocessor). Az el6fddolgoz6t soha nem kell kézzel indíta nunk, ezl a fordítóprogram mindig megteszi helY(;Hünk. A O. sor első karaklere egy kett6skeresZl (#), ami nem m,ís, mint egy az eI6felc1olgozónak szóló jelzés. Az el6feldolgozó ugyanis els6 közelílésben semmi egyebet nem tesz, mim végigolvass,1 a fo rráskódot, és olyan sorokat keres benne, amelyek kellős kereszltel kezdődnek. Ha wlá l ilyent, akkor al utána szerepl6 utasításoknak megfelelően módosíuisokat végez a fornískódon. Amit tehát a fordítóprogram ténylegesen megkap, az már ez a módosított változat lesz. Az előfeldolgozó tehát olyasmi, mint könyvnél a szerkeszt6, csak ez fordít;\si id6ben működ i k,
6s forráskódot szerkeszl. Ezt az aUlOl11atikus szerkesztőt a fent említett el6feldolgoósi direktívákkal (preprocessor directive) vezérelhetjük. /\ kellóskereszt után következő szó, az include egy ilyen, az e16feldolgozónak szóló utasílás, amely aZl mondja: "Ami most következik, at; cgy fájlmk a neve lesz. Keresd meg ezt a fáj lt, vedd a teljes tartal mát, és szúrd be ide, ahol én vagyok." Ha a fordít6progJ
Aprop6
Afejlécállományok kiterjesztéseiról
Gyakori, hogy az új ANSI szabványnak m egfelelő fejlécálJományoknak semmiféle kiterjesztésük nincs. Ugyanakkor a "történelm i hagyományok" miatt a legtöbb fordítóprogram kétféle változatot is tartalmaz ezekből. Az egyiknek a régi, a másik már az új szabványhoz igazodik. Ha például megvizsgáljuk, hogy az imént említett
26 11. rész • Bevezetés a C+ + programozási nyelvbe i ostream nevű állománnyal mi a helyzet, igen nagy valószínűséggel két változatot is találunk majd beléle. Mi ebben a könyvben mindig az új szabványnak megfeleló változatokat fogjuk használni, hiszen a másik garnitúra elavultnak számít. Ugyanakkor ha valaki egy régebbi kiadású fordítóprogramot használ, akkor kénytelen a hagyományos írásmódot alkalmazni, vagyis ebben az esetben a O. sor helyes alakja a kővetkezó: ~include Az i ostream Onput-output-stream) fejlécállomány bcemel~ére
:I COUl használata miaU van szüks(:günk. Ez az az utas1"t{ls, amivel a képerny6re tudunk írni dolgokat.
Figyeljük meg, hogya cout-ot megelőzi egy s td : : jeJz6, ami a fordítóprogram számára azt jelenti, hogy a szabványos be/ kimeneti könyvtá ml kell használnia. Hogy ez a dolog pontosan hogyan is m('iködik, és mi is az, ami ilyenkor a háttérben történik, majd az elkövctkez6 6r{lk során fog kiderüJni. Egyelőre maradjunk annyiban, hogy az std , : cout az lIZ objektum , ami a szabványos kimenetet kezeli, testvére {X..odig, az std : : cin, ugyanezt teszi, csak abemencttel. Amint azt már említettem, H nulladik sorban tal;~lhar6 i ncl ude direktíva hatása pontosan ugyanolyan, mimha a k6cl elejére bcgépeltük volna az iost erarn állomány teljes tartalmát. A.mikor a fordítóprogram megkapja a forrásfájll, ;lbban már Olt van ez a t::ut:lloln.
Elemzés soronként A program ténylegesen a 2. sorban kezdődik, ahol rögtön egy main () nevli függvé nyt IMunk . Minden C++ programban kell lennie egy ilyen ncvl.1 függvénynek. A rüggvény úgy általában véve nem egyéb, mint egy k6drészlet, :unely egy van több m('iveletet hajt végre. A fllggvénybe n foglalt műveletek végrehajtását szokás a fí.iggvény végrehajtásámik vagy meghívásíinak is nevezni, és talán az sem túl meglep6 , hogy függvények is mcghívhatnak más függvényekel. A main () mindazonáltal a függvények közölt speciális helyet foglal el , mivel ez a program belépési pontja. Ez azt jelenti, hogy amikor elindítunk egy lefordíton programot, akkor a rendszer automatikusan a main () függvény végrehajtás;íval kezdi a művel et ck sorát. A main () megadásánál - akárcsak az összes többi függv(>nynél- előre meg kell mondanunk , hogy az milyen típusú ért(:ket fog visszaadni. Ezen a ponton a main () megint különlegesnek számít, mivel kötelez6en egész (integer) értékkel kell visszatérnie. Ezt a típust a CH nyelvben az int kulcsszó jelzi, de err6! majd csak a harmadik órában lesz szó. Ami pedig a visszatérési értékeket illeti , ezekről először a 4. órában tanu lunk. majd a kifejezések és utasítások kapcsán.
2. óra· Egy C++ program részei 127
Tudta hogy...?
Értékek visszaadása?
A main () függvény által visszaadott egész értéket maga az operációs rendszer veszi át. Ez azért hasznos, mert ez az egyetlen szám főlhasználhat6 bizonyos hibaállapotok }elzésére. Ez amúgy teljesen áttalánosan használt módszer a termelési vagy kötegelt feldolgozást végző rendszerekben. A dolog a gyakorlatban úgy néz ki, hogya program által az operációs rendszernek visszaadott hibakódol egy másik program vagy szkript ki tudja olvasni, és meg tudja tenni a megfelelő lépéseket, hogy értesítse a kezelóket. (Ha
például egy kifejezetten nagy jelentőségű adatfeldolgozási folyamatról van SZÓ, akkor üzeneteI küld egy személyhívóra, természetesen szigorúan az éjszaka kellós közepén.) Minden függvény törzse egy nyitó kapcsos Lár6jellel kezd6dik, és - nem túl meglep6 módon - ennek
28 11. rész· Bevezetés a C+ + programozási nyelvbe
Megjegyzések Amikor írunk egy progmmot, rendszerinlteljcsen világos és magától értetőd6, hogy mit is akarunk megvalósítani egy adon kódrészleltel. Az emberi természet azonban mulatságos dolgokat képes produkálni. Ha megnézzük ugyanazl a kristálytiszta kódot egy hónap múlva, gyakr-.m megesik, hogy már nem értjük, mi is folyik ou, vagy legalábbis hoss7..an kell gondolkodnunk, hogy megértsük a saját alkotásunkal. Ez a jelenség pedig teljesen által:ínosnak mondható, hiszen minden valamire való kódban ott van valahol ez a bizonytalanság. Persze azt nem lehet el5re megmondan i, hogy hol lesz, de hogy oU lesz valahol, az biztos. Pontosan ennek a kezcll:sl:re találták ki a megjegyzéseket, amelyekkel nem csak magunkat segíthetjil k meg, hanem mások számár;] is könnyebben értheté'ívé tehetjük az általunk írt kódokat. A megjegyzés egyszeruen o lyan szövegrész, amit a fordítóprogram figyelmen kívül hagy, e amelynek tartalma alapján könnyen elclönthet6, mi is folyik éppen a programnak azon a részén.
A megjegyzések tipusai A megjegyzés tehftt olyan rész a forráskódban, amelyalefordított program mO"ködését nem befolyásolja, viszont mankót jelenet annak, aki meg akarja érteni a kódot. A C++ nyelv kétféle megjegyzés használatát tesz lehet6vé. Az egyiket két perjel (J / ) előzi mcg. A továbbiakban ezt a típust C++ stílusú megjegyzésnek fogjuk hívni. Ennél a típusnál a két perjel ami utasítja a fordítóprogramot, hogy az adott sorban :1 jel után hagyjon mindent figye lmen kívül, egészen a sor végéig. A másik típllSÚ megjegyzés kezdetét egy perjel és egy csillag Ct") jelzi, mégpedig ebben:l sorrendben, a végét pedig egy csillagot követő perjel ( .. t) mutatja. Az ilyen megjegyzésnél a fordítóprogram minden, a két említett jelzés közé esó szövegrészt figyelmen kívül hagy. Ezt a megjegyzéstípust a továbbiakban C stílusú megjegyzésként fogjuk em líteni, mivel valójában a C nyelv hagyatékának tckinthet6. Ha ezt a típust használjuk, akkor ügyeljünk rá, hogyaforráskódban minden nyitó ; .. jelnek meg kell legyen valahol a záró" I párja. Számos C++ programozó csak a C++ stílusú megjegyzéseket haszná lja, a C stílusút pedig kizárólag akkor, ha a forráskód egy blokkját átmenetileg üzemen kívül akarja helyezni. Ezt a módszert szokás .kikommentezésnek" is ncvezni. Fontos megjegyezni, hogy az így hatástalanított kódrészlet tartalmazhat C++ stíl usti, vagyis két perjelicI kczd6d6 megjegyzéseket is, de C stnusúakat nem.
2. óra • Egy C+ + program részei 129
Figyelem!
Kódrészlet üzemen Idvül helyezése
Igyekezzünk körültekintően eljárni, ha C stílusú megjegyzés segítségével akarunk átmenetileg hatályon kívül helyezni egy blokkot a forráskódban. Ha ugyanis ez a kódrészlet más C stllusú megjegyzéseket is tartalmaz, akkor megeshet, hogy csak egy kisebb részletet sikerül kiiktatnunk, nem pedig azt a blokkot, amit eredeti· leg szerettünk volna. Főszabályként elmondható, hogya C stílusú megjegyzések nem ágyazhatók egymásba. Hiába van ugyanis a kódban két nyitó I " jelünk, a fordítóprogram belső logikája szerint a legelső *j bezárja az összes megnyitott megjegyzést. Az ilyen hibák elkerülésében nagy segítségünkre lehet egy jó grafikus fejl e sztői környezet, amely más színnel jelzi a megjegyzésbe tett részeket, fölhíwa így a figyelmet az esetleges tévedésekre. Szintén jó hír, hogya ,.fölösleges" záró" 1 jeleket a legtöbb fordítóprogram hibának tekinti, és megáll.
Megjegyzések használata egy egyszenl programban A megjegyzések semmiben sem befolyásolják a lefordílott k6dol. A megjegyzésekt61 sem a program, sem annak fordítása nem les:! lassabb, hiszen a rordítóprogmm egyszeruen figyelmen kívül hagyja 6ket. A megjegyzések használatára nllltat példát a 2.2 tistában látható egyszerű program .
2.2 Usta - Megjegyzésolc hasznélsta (comment.cpp) o:
~includc
1. 2 : int
main~)
3: { 4 : 1* Ez egy C stílusú megjegyzés 5 : ami addig tart, amig a fordító 6 : meg nem ta lálja a záró csillag-perjel kettőst * I 7 : std : : cout« "Helló Világ ! \n"; 8 : /1 Ez egy e++ stílusú megjegyzés . ami a sor végéig tart 9 : sto. : : cout« "rtt a megjegyzés vége!";
10 : 11 : /1 Egy sor állhat egyetlen kettős perjel l el inditott mcgjegyzésb6l is 12 : 1* Ugyanez igaz a C sti l usú megjegyzésekre is */ 13 : r"eturn O; 14 :
Helló Világ! I t t a meg j egy z és vége !
I
30 L rész • Bevezetés a C+ + programozási nyelvbe
A 4. és 6. sorokban l:tlható megjegyzéseket a fordítóprogram teljes egészében Ilgyelmen kívül hagyja. Ugyanez: igaz természetesen a 8. , ]"t. (:s 12. sorokban láLhat6 szövegrészekre is. A 8. sorban induló megjegyzést a sor vége árja, vagyis ennek jelzésére nincs szükség. Ugyanakkor a 4. és 12. sorokban láthatók régi stílusúak, tehál itl szükség van az explicit lez.árásrd. A megjegyzések írá~ olyasmi, antivel kapcsolatban a progmmozóknak csak egy kis csoportja tekinthet6 ~mesternck ". Általánosságban elmondható, hogy bár az "olvawközönségünk" bizonyára ismeri magát a C++ nyelvet, vagyis elvben el tudja olvasni, amit írtunk, azért
Függvények Bár a main () is egy függvény, azért ,I függv(:nyck egy meglehet6sen swk
2. óra· Egy C++ program részei 131 zát, visszamegyünk a helyünkre, aztán ott folytat juk a rajzolást, ahol abbahagyruk. (Na ja, az arcképünkön marad egy kósza vonal ott, ahol a hegy kiLöröLt, de ezt m ajd egy másik füru,'\'ény elimézi.)
Függvényhlvások A függvényeket lekimhetjük amolyan speciális szolgáltatásoknak. Amikor a f6programnak szüksége van egy ilye n szolgállatásra, akkor meghívja a megfelelő függvényt, majd annak le futása után o tt folytatja a végrehajtást, ahol a z abbamamdt. Ezt a működési logikát szemlélteti a 2.3 Lista.
r2.3 um - függvényhlvú bemutatá.. lcallfunc.cpp)
l
o : *i nclude 1.
2 : II Fűggvényhivás bemu t atá sa 3 : II Ozenet mcgjclenitése a képerny6n 4 : void DemonstrntionFunction{) 5: {
6 : std : : cout «
"A
DemonatrationFunction foggvénybcn vagyunk\n ";
7: )
B. 9 : II A main() fQggvény - kiír egy Qzenetet , majd 10 : II meghivja a Demon~trati.o nFunction nevű függvényt , 11 : II pedig kiír egy második üzcnc t et 12 : int main{)
végűl
13 : (
14 : ~td : : cout « "A fMin fOggvényben vagyunk\n" 15 : DcmonstrationFunction() ; 16: std : : cout « "Ismét a main-ben vagyunk\n"; 17: return O; 18 :
lCImiinII
J
A main függvényben vagyunk A Demonstrat ionFunction függvényben vagy\mk Isrn6t a main-ben vagyunk
-
-.
A Demons trationFunction (l függvé ny kódját a 4-7. sorok tartalmazzák. Ez a kódrészlet a program elkülönült része, vagyis le van benne írva , mit kell csinálni, de amíg meg nem hivják a függvé nyt, addig nem csinál senunit. A hívás a 15. sorban látható, melynek hatására a függvény kiír a képerny6re egy tájékoztató szövegel, majd visszatér a hívóhoz.
32 11. rész· Bevezetés a C++ programozási nyelvbe A progr.Hn tényleges kezdete a 12. sorban látható. A 14. sorban a main () függvény kiír a képernyőre egy üzenetet, amelyben közli, hogy most éppen a main () végrehajtásánál tart a program. A következő lépésben, vagyis a 15. sorban a main () meghívja a DemonstrationFunction () függvényt. Ennek hatásár.! lcfutm.k a DemonstrationFunction () törzsében leírt utasítások, ami jelen esetben mindössze egyetlen k6dsort (6. sor) jelent. Ez kiír egy újabb üzenetet, amelyben jelzi, hogy a végrehajt1~s most épp a meghívott függvény belsejénél tart. A 7. sorban aztán a Demonst rat ionFunct ion ( ) véget is ér, és a vezérlés visszakerül a hívóhoz, vagyis esetünkben a main () függvényhez. A végrehajtás a függvényhívást közvetlenül követő soron, vagyis esetünkben a 16. sorban folytatódik, ahol a main () kiírja az utolsó üzenetet, és maga is véget ér.
Függvények használata Egy függvény vagy egyetlen adatta l lér ViSSZ:l, vagy vissz,nérési értéke void, ami azt jelenti, hogy nem ad vissza semmit a bívónak. Ha példáu l írunk egy függvenyt, ami két egész S7.rl1l1Ot tud összeadni, akkor
int Sum (int a, int
b)
TiSZlázandó az elnevezé.~eket a függvényparaméter megadha cSllp:'in egy deklaráció, amelyben azt írjuk e16, hogy milyen típusú adatot kell a hívó félnek :'itadnia. A függvény hív{\sakor ténylegesen átadott érték neve argumentum. Ezt a két fogalmat számos progmmoz6 szinonimaként használja, míg mások kínosan ügyelnek a helye.~ elnevezésekre. Mi ebben a könyvben az első - kétségkívü l könnyebb - megoldást fogjuk választani, vagyis hol par.lmélelt, hogy argumentumot írunk m
2. óra •
C++
részei 33
H~ny
futá!;át, akárhol legyen is a k6djában. l-la egy függvényben egyáltalán nem szerepel ez az utasítás, akkor automatikusan void lesz a visszatérési értéke. Ügyeljünk rá, hogy a retur n után valóban olyan típusú érték szerepeljen, mint amilyent a deklarációban visszatérési típuském megadtunk.
Függvényparaméterek hasmálata A 2.4 Listában bemutatunk egy egyszerű függvényt, amely kél egész számot vesz át a hívőtől bemenő paraméterként, összeadja azokal, majd az eredménnyel - amely szintén egész é rték - té r vissza a hívóhoz.
2.4 Ua.. - Egy bemen6 paraméterekkel randalk..6 agyazer4l1lggvény llune.eppl o: ninc l ude < i ostream~ 1.
2 : int Add (int x, int y l 3: { 4 : std :: cout «
"Itt az Add ( ) függvény, a kövctkcz6 értéke ket kaptam " _ « x « • és « y « ' \n ' ; 5 : return (x+y) ;
6. 7.
8 : int mai n() 9: (
10 : s td : :cout« 11 : std :: cout« 12 : st d :: cout« 13 : std :: cout« 14 : std : : cout « 15 : rcturn O; 16 :
"A main() fo.ggvé nyben vagyu n k ~ \ n " ; " \nMeghivjuk az hdd() fü ggvényt\ n'; "A visszaadott érték : " «Add (3 , 1) ; " \nIsmét a ma in() függv é nyben vagyun k. \n" ; "\nKilépünk ... \n\n" ;
monat A
main () fü ggv ény b e n v agyun k!
Meghivjuk a z Add() függvé nyt It t az Add() függvé ny , d következő értékeket kaptam 3 és 4 A vissz aadott ér t é k: 7 Ismét a mai n () függ"vényben vagyunk.
Kilépünk . .
kezdődik. Látható, hogy két cgésL': értéket vár bemenő paraméterként, majd egyetJen, szintén egész értékkel tér vissza. Maga a program a 10. sorban k ezdődik , ahol kiír egy tájékoztató üzenetet.
Az Add () függvény meghatározása a 2. sorban
34 11. rész · Bevezetés a C+ + programozási nyelvbe A 12. sorban a main () egy üzenet kíséretében kiírja az Add () függvény által visszaadott értéket. Ez az érték az Add (3 , 4 J hívás hatására keletkezett, ami szintén a 12. sorban látható. A végrehajtás e rről a pontról ágazik el úgy, hogya hívás után végrehajtott első művelet a 2. sorban látható. A függvényen belül a neki átadott két értéket az x és y változók reprezentálják. A kód ezeket összeadja, majd az eredményt az S. sorban visszaadja a hívó félnek.
Ez az a visszatérési érték (7), am i a ] 2. sor hatására a képernyőn is megjelenik. A mai n () ezután a 1.~. és 14. sorokban kiír egy-egy üzenetet, majd maga is kilép, visszaadva ezzel a vezérlést az operációs rendszernek.
Kérdések és válaszok Kérdés: Mi a szerepe az #include utasítéisllak?
Válasz : El. egy az clőfeldolgozónak (preprocessor) szó[ó direktíva. Az el6feldo lgozó egy olyan program, .l mi! a fordító hív meg automatikusan minden egyes fordítási megkezdésekor. Ennek a múködésér lehel vezérelni ezckkel a direktívákkal. Konkrétan az Itinclude aZl jelcnti , hogy az e[6feldolgozóna k be kell sz(u·nia az adott helyre az #include lll,ín megadotl fájl lcljes larl~l l m,Íl ugyanllgy, mintha azt mi magunk gépdtük volna be oda. Kérdés: Mi az alapvető külóllbség a II és a 1 * lípllSIÍ meg;egyzéseM Mz6tt? Válasz: A keu6s perjellel indított megjegyzés a sor végén automatikusan véget ér. A perjel és csillag kombimídóva l jelzett ezzel szemben a záró p{lfáig, vagyis az els()-; karakrcrkettősig tart. Ne felejtsük el, hogy az ilyen típusú megjegyzésnek "mag{ltól" soha nincs vége, az még a függvényeket lezáró kapcsos zár6jelen is {ltnyúlhal. Mindenképpen ki kell írnunk tehát a záró -; kombináci6t, különben fordítási hibát kapunk. Kérdés: Mi különbóz lel Illeg egy jó es egy rossz megjegyzést?
Válasz: Egy jó megjegyzés azt mondja el a kód olvasójának, hogy miért pont az tönénik az adott helyen, anti, vagy általánosabb esetben információt s7.o1gáltat arról , hogy az adott kódblokKnak mi a célja, mi az a feladat, amit meg akalUnk vele oldani. Rossz megjegyzés például az, amelyik azt próbálja megmagyarázni, hogya k6d eb')' adott som mit csinál. A k6dsorokat úgy kell megírni, hogy azok magukért beszéljenek, és a megértésükhöz ne kel!jen semmilyen további magyarázat. Az igazság az, hogya forráskódok dokumentálása amolyan önálló művészeti tevékenység, amit magához a nyelvhez hasonlóan tanulni kell.
2. óra · Egy C++ program részei 135
Gyakortatok Most, hogy már tudunk ezt-azt a C++ nyelv ről, álljon itt néhány megvá laszolandó kérdés és megoldandó feladat, amelyekkel megszilárdíthatjuk a fordítóprogrammal kapcsolatos eddigi tudásunkat.
Kvfz l. Milyen típusú a main () függvény visszatérési értéke? 2. Mire valók a bpcsos zá rójelek' 3. Mi a különbség a fordítóprogram (compiler) és a parancsértelmező (interpreter) között? 4. Mién o lyan fontos il kódok újrahasznosítása?
Gyakorlatok 1. Vegyük H 2.1. Listllban bemutatott kódot, és bontsuk ketté a 4. sorllba n hítható művel etet úgy, hogy az egyik sorban a Helló a másikban a Világ sZót írassuk ki. Történt valami változás a kimenet form<Ítum(lbHn? Hogyan o ldhatjuk meg, hogy a két szó kü lön sorba n jelenjen meg? 2. Próbti ljuk meg lefordítani saját rendszerü nkkel a 2.1 Listában látható kódot de úgy, hogy az üinclude dostream> direktívllt átírjuk I/inc1ude alakúr.L Ezzel kideríthetjük, hogy az álta lunk haszn{llt fordítóprogram elfogadja-e mindét alakot, vagy ha nem, akkor melyikkel hajlandó mű ködni. Ennek ,lZ információnak később még nagy hasznát vehetjü k. 3. Pr6búljunk meg lefordítani egy kódit az integrált fej l esztői környezet (IDE) segítségével, majd parancssorból is. Van-e bármi különbség a keletkezett bináris állományok kÖlött?
Válaszok a kvlzkérdésekre l . A mai n () függvény mindig egész (int) értékkel tér vissza . 2. A kapcsos zárójelek egy programblokk elejét és végét jellik. Ilyenek közé kell zárni példáu l egy függvény törzsét, de léteznek más fajta blokkok is. 3. A fordítóprogram (compiler) a teljes forráskódot lefordít ja gépi k6dra, m i előtt eJkezdené végrehajtani azt. A parancsértelmező (interpreter) ezze! szemben soronként halad végig a kódon, és minden utasítást azonnal végrehajt. 4. Nos, mién használunk föl többslör bizonyos e1képzeléseket az élet más területein, nem csak a programozásban? Azért, mert ez így sokbl könnyebb, gyorsabb és bizonyos helyzetekben olcsóbb is, mint újra és újra megírni ugyanazt a kódot, vagy megcsinálni ugyanazt. Az az igazság, hogy az információ megosztását és a helyes gondolkodást is tanítani kell.
ÓRA
Változók és állandók . . lisz szó ebben az órában: .gyan vezethetünk be új változókat illetve ;í.lland6k:it.
• •
H ~-an adharunk éJtékeket a változ6knak t!s hogyan v:íllozlitLhatjuk meg. ~'3n
•
írhatju k ki egy változó értékét a képernyóre.
a változó? ~:;:;
I 1I
lZÓ néz6pontjából a változ6 egy Lerület a számítógép Jnf;!móriájtlban, flhol u.rolhawnk, amelyeket később visszaolvashalunk.
",:..~k, ez pontosan mit is jelent,
:
először
meg kell ismernünk v:damennyirc
:~~~-'P mem6riájának mŰködését. A számitógép memóriáját úgy tekinthetjük,
__ '-
38 11. rész • Bevezetés a C+ + programozási nyelvbe A változóknak azonban nem csak címük, hanem nevük is van. Létrehozhatunk péld,íul egy myAge nevű változót. 1\ változónk nem más, mint az egyik cel1árd hivatkozó címke, amely alapján a cella könnyedén fellelhető anélkül , hogy ismernénk a mcmóriacímet. A 3.l.ábm ennek az elképzcJésnek a vizuális vázlata. Ahogy az ábrán látható, bevezettünk egy myVariabl e nev(} váJtozót, amely a 103-as memóriacímen kezd6dik.
myVariable váltoronév RAM
elm
rnmm '00
'" '"
'03
",
'" '"
3.Ubra A mcm6rl# Il/z/lálls ml1Rfelcl6Jc
Aprop6
Mi is az a RAM?
A RAM a Random Acces Memory (véletlen elérésü memória) rövidftése. Ez az az elektronikus memória, amelyet a számftógép a program végrehajtása során használ. A számítógép kikapcsolásakor a RAM-ban tárolt minden információ elvész. Amikor a programunkat futtatjuk, az a merevlemezen található fájlból a RAM-ba töltődik. Ugyanfgy a változók is a RAM-ban jönnek létre. Amikor a programozók a memóriát említik, valójában a RAM-ra gondolnak.
Mem6riafoglalás Amikor C++-ban meghatározunk egy változ6t, a fordítóval nem cs:.k a nevét kell közölnünk , hanem azt is, hogy milyen cípusú információt fogunk :.bban tárolni: egész sz,ímm, kamkt.ereket, és így tovább. Ez a változó típllsa. A másik kifejezés, amellyel ezzel kapcsoJmosan találkozhatunk, az adaffípus. A változó típusa meghatározza a fo rdító számára, hogy mekkont ten:.iletet foglaljon le a memóriában a vá ltozó ertékéIlek tárolásához. Mi ndegyik cella egy bájt mérení. Ha a létrehozoct változó kél bájt mérení, két bájtm van szüksége a memóriában, azaz két ceUára. A vállQZó típusa (például: int) határozza meg a fordító számára hogy mennyi memóriát (há ny memóriacellát) kell lefoglalnia a változó s;dI1llára. Mivel a számítógép bitek és bájtok formájában fejezi ki az értékeket, és amemória méretét is bájtokban méri. Nagyon fomos megértenünk ezeket a fogalmakat és otlhonosan mozognunk közöttük.
3, óra • Változók és állandók 139
Az egész számok (Integer) mérete Egy char típusú változó (karaktNck tárolásilm használjuk) szinte mindig egy bájt hosszúságú Egy short int a legtöbb számítóg6pen két bájtosj egy l ong i nt általiíbiln négy bájtos, az im trpusú vá1t07-ó (short és l ong előtagok nélkül) pedig kctt6 vagy n é~,.y bájt hosszú lehet. Ha Windows 95/98, Windows XP vagy Windows NT/2000/2003
rendszereken dolgozunk , négy bájtos inl mérettd számolhatunk, feltéve , hogy
kell őe n
új fordítóprogramoL használunk.
Ám ezt nem szabad készplmznek venni, mivel nem biztos, hogy minden rendszeren így működik. Csak annyit tudhatunk biztosan, hogy l!gy short int mérete kisebb vagy ugyanakkora mim egy int mérete, valamint hogy egy inL mérete kisebb vagy ugy,makkor mint egy long int mérete . A l ebegőpontos adatok kérdése már egy egészen más történet, l:rr61 is hamarosan szót e jtünk. A 3.1 Lista segítségévcI mcgCIJlapíthatjuk ezeknek a tipusok nak a pontos méretét az adott számítógépen, az adott fordítóprogramot h asználva.
3,IIilIta - A ....611.pItja • _PUSH . . - • o:
~include
oz6m~ptIak6n
'siz.',.pp)
"
2 , int main () ) ,
4, 5, 6, 7,
8, 9,
10: ll : 12 :
13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 :
{
std : : cout std : : cout std : : cout std : : cout std : :cout std : : cout std : : cout std : : cout std : : cout std : : cout std : : couL std: : cout std : : cout std : : cout return O;
« « « « « « « « « « « « « «
"Egy int t ipusú változ6 méretc :\t \t" ; sizeof(int) « , bájt.\n"; "Egy short tipusú változ6 méretc : \t"; sizeof(short) « , bájt . \n"; "Egy short tipusú változó mérete : \t"; sizeof(long) « " bájt.\n'; 'Egy char tipusú változó mérete : \t\t"; sizeof(char) « " bájt.\n' ; 'Egy 0001 t ípusú vált.ozó mérete : \t\t"; sizeof(bool) « , bájt . \n' ; 'Egy fl oat tipu sú változó mércte : \t\t'; sizeof(float ) « bájt.\n' ; "Egy double típusú változó méret e : \t"; s1zeof (double) « bájt . \n ' ;
. .
40
II. rész· Bevezetés a C+ + programozási nyelvbe Kimenet Egy
int tipusú változó mé r ete:
Egy short tipus\Í változó mérete : Egy sho rt tí pusú vá l tozó mérete :
Egy Egy Egy Egy
char ti pusú változó m6retf! : bool tipusú változó mé re te : float tipusú vá l tozó mérete: doub le tipusu vál tozó mé ret e:
Apropó
4 báj t . 2 bájt. 4 bájt. 1 bájt. 1 bájt. 4 bájt. 8 bájt .
A futási eredmény eltérő lehet
Az olvasó saját számítógépén futtatva a programot eltérő eredményt kaphat.
A 3.1 Lista legtöbb eleme azonnal élthet6. Az egyetlen újdonság ti kódban az 5. és 15. sor köúitt találhmó sizeof () függvényhívások sorozata. Ezt a függvényt a fordít6progr:.lmunkkal k.apotL könyvtár tartalmazza és annak az objekwmtípusnak a mérct6L adja vissz,!, amelyet. pHraméterként átadunk neki. Példaként az 5. sorban az int kulcsszót adtuk át a sizeof () függvénynek. A sizeof () használatávalmegállapítotluk, hOh'Y az adott gépen az int eh'Yenl6 hosszúságú a long típussa l, mivelmindkett6 négy bájtos.
signed és unsigned Az összes típusnak kél fajtája van: el6jeles (signed) és el6jel nélküli (uns i gned). id őn ként szükségünk van negatív számokra, időnként nincs. Az egész tipusokat (short és long) az unsigned kulcsszó nélkül el6jelesnek tekinti a fordító . A s igned egész típusok lehetnek negatívak és pozitívak egyaránt. Az unsigned cgés:.::ck mindig pozitívak. Ne feledjük: a signed az alapértelmezett az eg~sz típusoknáL Mivel a változó bájtokban (ennélfogva bitckben) meghatározott mérete mind a signed, mind az uns igned egészek esetében ugyanakkora, egy előjel nélküli egész típusban kétszcr akkora értéket tudunk tárolni, mint egy előjeles egészben. Egy unsigned short típusú változó Oés 65535 között tudja ábrázolni a számokat. Egy signed short ripusú változó által ábrázolt számok ICle negatív, ennélfogva II signed short ~32768 és 32767 közötti számokat tárolhat.
Tudta hogy...?
Bitek a signed és unsigned típusok esetében
Az előjeles egészeknél egy bitet magához az előjel tárolásához használunk. Előjel nélküli típusok esetében ezt a bitet is számok tárolására használjuk. Ha megfigyeljük, akkor látható a signed és unsigned esetekben a tárolgató értékek száma azonos, a különbség tehát csak az ábrázolás módjában van.
3,6ra • Változ6k ás álland6k 141
Alapvető
változótfpusok
Számos egyéb változótípus van még a C++-ban, amelyek nem .:;orolhatók az eddig tár-
gyait egész típusok közé. llyenck például a
lebegőpontos
és karakter típusú változók
A lebegőpontos (float) válLOzók l(lrtszámok formájában kifej ezhető értékeket, azaz valós számokat tárainak. A karakter (char) típusok egy bájtot foglalnak magukban, és a 256 elembő l álló ASCII illetve kiterjes .... tetL ASCII karakterkészlet karaktereinek tároIásárd haszn~Hjl1k őket. Az ASCll karakterkész/el számítógépes l1asználalra szabványosítoLl karakterek halmaza, Az ASCII mozaiksz6 az American Standard Code for Information Interdlange rövidíté-
se. Csaknem valamennyi operációs rendszer támogatja az ASC II kódk(:s:.dct h
r3,1 Táblázat - Vához6tlpu.ok Típus unsigned short int short int u!1signed long im long inl int. unsigned int char bool float
double
Móret 2 bájt 2 bájt 4 bájt 4 bájt 4 bájt
I Értókek
0-65535 32768-,32768 0-4294967295 214748.~648-2147483647
214748364&-2147483647 0-4294967295 256 féle ka rakter igaz vagy hamis 1.2e-38-3.4e.~8 2 . 2e-308-1.8e:~08
Bár sokan úgy tartják, hogy ez rossz programozói gyakorlat, dc a char típusú változókat használhatjuk nagyon kis egész számok tárolására is.
42 11. rész · Bevezetés a C+ + programozási nyelvbe
Egy változó meghatározása Vához6t annak típusának megadás.'ival, majd egy vagy több sz6közt követően a változó nevének leírásával, ponlosvesszővel lt:z{irva ké.szíthelünk, illetve határozhatunk meg. Egy v,1ltozónév csaknem bármilyen betűt tartalmazhat , de szőközt nem. Érvényes változónevek például a következ6k: x, J23qrsnf, myAge. A jó változónév kifejezi, hogy azt a v;íltozót !lure is használjuk. A megfeleléS m:veket használva könnyebbé válik a progmm folyamatának megértése. A következO: utasítás egy myAge nevú egész típuSli változ6t határoz meg: int myAge; Ne feledjük , hogya CH érzékeny a kis- és nagybetuk közli különbségre, azaz a myAge és a MyAge két különböző változót jelöl. Általános progr.:unoz6i gyakorlatként pr6báljunk olyan v,lItoz61l\.!veket :Idni, amely llta! arra, hogy milyen célra használjuk az adott változót. A eletKor és a mcnnyiAzAnnyi váltowneveket könnyebb megértenünk és eml(:keznünk rájuk, mintha xJ4, v
De nézzünk egy hosszabb példát Pr6báljuk meg kitalálni az els6 pfir sor alapján, bogy mit csinálnak az alábbi kódrészletek. 1. példa: main() (
unsigned !:lhort X; unsigned short y ; unsigned int z ; z = x * y;
2. példa: main() (
unsigned short Szelesseg ; unsigned short Hosszusag ; uns i gned int Terule t; TerOlet = Szelesseg * Hosszusag ;
A kis- és nagybelÚk eltéró jelentéséről A CH érzékeny a kis- és nagybetúk közti külö nbségre. Magyarul a kisbetűket és a nagybetűket a fordítóprogram megkülönbözteti. Az age és
3. óra •
Apropó
Vá~oz6k
és állandók 43
Ne zavarjátok betúimet!
Néhány fordító megengedi a kis- és nagybetúk közti különbségtétel kikapcsolását. Ne essünk. ebbe a kisértésbe; a programunk nem fog működni más fordító kkal, és a többi C+ + programozót is összezavarja a forráskódunk.
Sok programozó szeret csupa kisbctűs váltowneveket használni. Ha a név két szóból áll (például my car), kél elterjedt konvenció szeri nt nevezbetjük el őket: my_car vagy myCar. Ez utóbbit teve jelölésnek (carne! notatioo) nevezzük, mivel a nagybetúk úgy néznek ki, mint a púpok. Tekimhctjük úgy is, hogy ezek a névadási szokások .Uni."(" és .Microsoft" strlusok. Azok a progmmoz6k, akik Unixon tanujlak, és hosszan dolgoz-
tak Unix környezetben, az első stílust választják. Ezzel szemben a Microsoft rendszeren nevelkedett fejleszt6k igyekeznek a második változatot használni. A fordít6progr:lmot mindez persze nem igazán érdekli, csak legyenek a változ6neveink követke7.ete.~ek.
Kulcsszavak Néhány szó foglalt II C++ számára , ezek nem h,lsználhatók változ6nevl;!kk6nt. Ezek azok a kuksszavak, :Imelyeket a fordító használ a progmlllunk vezérlé~ére. Kukssza~ vak pékl(lul az :l1{lbbiak: if, while, for és main. Általában véve minden értelmes változónév bizonyosan nem kulcsszó. A változóink L:lrtalmazh:lmak kulcsszavakat a nevük részeként, de nem áll hatnak csak abból. Az o lyan változónevek mint például main_nag vagy forEver megengedettek, de a main vagy a for nevek nem.
[ HoIyn Ilalároz7.uk meg a vállOZÓl a típusá~ nak majd nevének megadásával. HasznMjunk beszédes változ6neveket. Ne feledjük, hogya C++ érzékeny a kis- és nagybe tűk közti különb~ ségre! DeríL<;ük ki, hogy sa ját rendszenJn~ kön az egyes változók mekkora memóriaterület foglalnak, és mekkora értékeket tárolhat unk bennük.
I Ne has;m:íljunk v:íltoz6névkém C++
ku1csszavakall Ne használjunk el6jeJ nélküli
kat negatív számokhoz!
vállOZÓ~
44 11. réSZ · Bevezetés a C++ programozási nYeNbe
Egynél több változ6 megadása egyszerre Egynél több azonos típusú változ61 is létrehozhatunk egyetlen UI~sílással, ha a változó típu.!:ia után vessz6ve\ elválasztva írjuk le az egyes változóneveket. Íme egy példa: unsigned int myAge, myWeigh t : long area, width , l ength :
Ilkét el6jel nélküli egéG 2 változó Ilhárom long típusú változó
L<1that6, hogy a myAge és a myWeight változókat egyaránt cl6jel nélküli egészkénl határoztuk m ég. A má.!:iodik sor három ö náll6 long típusú V{lltozót határoz meg arca, w idh t, length nevekkel. A típus (long) mindhárom változóhoz hozzárcndel6dik, azaz nem keverhetjük a típusokat a változ6k meghatá rozásánál.
Aprop6
Hogyan frjunk hasznos megjegyzéseket
A magyarázó megjegyzések elhelyezése a változók meghatározása után ugyanazon sorban jó gyakorlat. Nemcsak minket emlékeztet a változó céljára, hanem támpontot nyújt a többi programozó számára is ezzel kapcsolatban még akkor is, ha neve erre esetleg nem utal.
Értékek hozzá rendelése a változ6khoz A változóknak a hozzá rendel6 operátor (=) segítségével adhatunk értéket. Ennek megfelel6en a Widht n evű vá ltozónak az alábbi módon adhatjuk az 5 é rtéket: uns i gned short Widht ; Wid th - 5:
Összekapcsol hat juk a két lépést és kezd6értéket adhatunk a v:í ltozónak 7.áskor így: unsigned short wi dth
c
II
meghatáro-
5;
A kezd6én ék megadása nagyon hasonlít az egyszeru értékadás r-.J., és az egész típusú változ6knál alig va n különbség a kenó kÖZÖLt. Később , ami kor az állandókról fogunk beszélni , látni fogj uk, hogy néhány elem esetében kötelez6 II ke zd őérték megadása, mivel kés6bb má r nem tudunk értéket hozzárende1ni. A 3.2 üsta bemutat egy fordításm kész programot, amely egy téglalap terü letét számítja ki, majd kiírja a vá l ~s zt a képerny6re .
3.2 Lista-A _ _ ak _
(lII8VIIr.cpp)
0 , II A vá lato26k használ atának bemuta tása l , l i nclude 2,
3. óra • Változók. és állandók. 145 3 : int main() 4, 5, unsigncd short int Width 5, Length ; 6, Length" 10 ; 7, 8: II kés zi t egyelo j e l nélkül i egész vál to z6 t , k ezdoértékként 9: I I a szél esség é s a hosszúság szorz atával fel tö l t v e 10 : un signed short int Ar e a " Wi d th * Leng t h: 11 : 12 : std: : c ou t « .. Sz é le s ség : • « Width « • \ 0 · ; 13 : std : : cou t « 'Hosszú s ág : • « Le ng t h « s td : : end l : 14 : std::co ut « ' 'l'erü let : « Area« std: : e nd l ; 15 : re tu rn O; 16 :
menet Szé l esség : 5 Hosszú ság : 10 Terület : 50
Elemzés Az első sorban beemeljük a cout utasítás haszmílat{jhoz szükséges iostream függvénykönyvtárat az inclllde utasitás segitségével. A program a 3. sorban kezdődik. Az 5. sorban a wi d th nev(!' vá ltozót előjel nélküli egésl.ként határoztuk meg, kezcl őér tékét S-re áJlítottuk. Egy másik egész típust is meghat{lroallnk Lengt h néven, cic nem adtunk neki kezclőé rté ket. A 6. sorban rendeltük hozzá a ]0 értékel. A tizedik sorban al. Area nev(!' egészet határoztuk meg, kezdőértékkém a Width és Height v{lltozók szorzatát adtuk. A 12-14. sorig a változó értékeit írtuk a kimenetre. Megemlítjük még, hogy az endl kulcsszó sortörést vált ki.
Apropó
Sor lezárása
Az endl kifejezés a sor lezárásáért felel (end-L és nem end-egy). Általában így ejtjük: "end-ell".
typedef Jelővel fárasztóvá, unalmassá és ami ennél is fontosabb lehetséges hibaforrássá válik az unsigned short int folyamatos leírogatása. A typedef kulcsszó használatával szinanilllát készíthetünk egy már llleglév6 típusra, amelyet a változó típusának Illeghatározásánál használhatunk.
46 11.rász • Bevezetés a C+ + programozási nyelvbe Fontos megkülönböztetnünk az új típus l6trchozását61. (Ezt a 7. 6ráb'ln az alaposztályok tárgyalásánál fogjuk megismerni.) II typede f használata úgy tört(:nik, hogy a t ypedef kulcsszó után írjuk a meglévő típust, majd a típus új nevét. Például a typedef unsigned short int USHORT
utasítás egy új tipusnevel hoz létre, amelyet mindenhová leírlJatunk, ahol unsigned short int-nek kellene szerepelnie. A 3.3 Lista a 3.2 Lista kódjának olyan átalakítása, amelyben típusmeghatároz{lsként az USHORT nevet h
3.3 Ulla - Atypedef ....... _ _ 'typedef.eppl 1 : II A typedef kulcsszó használat4nak bemutatása 2 : tincludc 3,
4 : t ypede f unsigned short int USHORT :
I ltypede f megha t ározás
5, 6 : int mainO 7: {
USHORT width 5, USHORT Length; Lengt h = 10 ; OSHORT Arca • Wi dth * Length; öt d : :cout « " Szóleaség : ' « Width « "\ n ' ; std : : cout « " Ho~~zúság : ' « Len'ijlt h « std : :endl ;
8, 9, 10 : 11: 12 : 13 : 14 : 15 : 16 :
!:ltd : : cout «
"Terület : " «
Area «
std : : endl ;
return O:
ICiIllll1ll Szélessóg : 5 Hosszúsúg : 10 Terület : 50
Apropó
Nem lényegtelen, ha szükségünk van rájuk
A fenti kódot lefordítva néhány ford ító figyelmeltetö üzenetet dob "konverzió során lényeges számjegyek veszhetnek el" felkiáltással. Ez azért van, mert a 11. sorban létrehozott két USHORT típusú változó szorzata nagyobb lehet, mint amekkorát egy
unsigned short tárolni tud, és ha ezt a szorzatat az Area változ6ba tesszük, az a szám csonkításával járhat. A fenti esetben ezt az üzenetet nyugodtan figyel-
-
men kívül hagyhatjuk.
A 4. sorban a Lypedef segítségéve! meghalároztuk az USHORT lípust, amely az uns ig ned short int típus szinonimája. A program egyébként megegyezik a 3.2-es Listában bemuratottal, és a kimenet is ugyanaz.
.3. óra • Vá~ozók és állandók 147
Úgy is gondolhatunk a typedef -re, nlintha az az alapjául szolgáló meghatározásI (unsigned short int) helyeuesítené bárhol, ahol leírjuk (USHORT).
Mikor használjunk short és mikor long típust A kezdd C++ programozóknak gyakran okoz fejtörést, hogy !lukar kell egy változóI shon illetve long típusúként meghatározni. A szabály, miután megénettük, egészen egyszeru. lia a legcsekélyebb esély van arra , hogy a kérdéses változóban táro lni kívánt énék túl nagy lesz a v31lozó típusához képest, használjunk nagyobb mé re n1 típust. Ahogya 3.1. Táblázatban láthaltuk, az unsigned short lípusCJ egészek, feltételezve hogy 2 bájl mérctt1ek, 65535-ig tudják ábrázolni a szflmokat. A signed short típusúak ermek a feléig . Az unsigned long egészek elképeszl6cn nagy számokalludnak tárolni (4294967295), de még mindig eléggé korlátozottak. lia ennél is nagyobb sz{irnok tárolásá ra van szükségünk, használjtmk float vagy double típusokat, ezeknél viszont elveszítjük a pontosság egy részé t. A lebeg6pol11oS és dupla hosszúságú lípllsok hatalm,ts számok tá rolás1írn képesek, de csak az első 7 illetve 19 számjegy lényeges a legtöbb rendszeren. Ez azt jelenti, hogy az ezek utáni sz,ímjegyeket ;t sz;'i mítóg(:p le ke re kíti.
Előjel
nélküli egészek túlcsordulása
Az a tény, hogy az unsigned long típusú egészek nem képesek tárolni bizonyos é nékeknél nagyobbat , csak ritkán o koz problémál. De mi van akkor, ha kifutunk a memóriaterületl:>ól? Amikor egy el6jel nélküli egész eléri a maximális értékét, körbefordul, és újrakezdi, hasonlóan mint az autók kilométe rórája. A 3.4 Lista azt mutatja , mi törtétlik , ha túl nagy érté ket pfÓb:1lunk egy short tipusú egész változ6hoz rendelni.
3.4 llIta - Bemutatja. mi t6rt6nlk. h8 1ú1nagy értéket pnlb6l••k _rendoIni oI6jo1 n6lkll11 og6u riltoz6h.. (toobiau.cpp) o:
#includc
l, 2: 3, 4: 5: 6, 7, 8,
9, 10 : ll : 12 :
int main() unsigned shor t int smallNumber; smallNumber ~ 65535 ; !ltd : : cout « • small number: • « smallNumbcr « smaIINumber++ ; std : :cout « • small n~r:' « smallNumber « smaIINumber++ : std : : cout « 'smaII number : • « smalINumber « return 0,
std : : cndl; std : : cndl; std : : endl;
1
48 11. rész· Bevezetés a C+ + programozási nyelvbe
small number: 65535 small number: O small number: l
A 4, sorban a smallNumber nevű változót short im-ként határoztu k meg, ami a fultató sz:'unítógépcn 2 bájt hosszúságú. Ez azt jelenti, hogy legfeljebb O és 65535 közötti számok tárolására alkalmas. Az 5. sorban a maximális értéket rendeltük a változóhoz, amelyet a 6. sorban ki is írattunk. A 7. sorban megnöve[tük a smallNwnber változó értékér, azn hozz1iadtunk l-et . A növt:l6 opedtor a ++ (úgy millt a C++ nevében: inkrementálisan SZ~\T'111azik a C-b(1). A smallNumber változó értékének most elvileg 65536-n:lk kellene lennie, de az unsigned short egészek nem tudnak 65535-nél nagyobb értéket t[lrolni. A válToz6 taltalma így körbefordul, vagyis O-ra álL Ezt ímttuk ki a 8. sorban. A 9. sorban a smallNurnber érlékét újból megnöveltOk, az új értéke 1 lett, amelyet szintén kiírartunk.
Az előjeles egészek kömefordulása Az el6jeles egész abban különbözik az el6jel nélküli egésztől, hogy a l ehe~ges értékei nek fele negatív. A hagyományos kilométer6m helyet képzeljünk el egy olyat, :Imely előre forog a pozitív- , hátrafelé a negatív számok esetén. A nullától mért egy kilométer távolság lehet l és -1 is. Amikor kifulunk a pozitív számokból, rögtön a legnagyobb negatív számf:1 ugrunk, és innen számolunk visszafelé nulláig, A 3.5 Lista azt mutatja, hogy mi történik, ha hozzáadunk l -et az unsigned short egész típusú vá!(Oz6nk ImximáJis értékéhez.
3.5 Us" - B......tja, ml történik, ha túl nagy értéket pnlbálunk hozz6rendelni al6101.. ogÓlZ vához6hoz jtoobigs.cpp) o:
Jlinclude
1. 2:
5:
6: 7: 8: 9:
int main()
short int srnallN~r : smallNumber = 32767 : std: :cout « 'smal l nWllber:' «
smallNumber «
std : : end!;
smallNumber «
std: :endl:
smalINumber~~ :
std: :cout «
'small number:' «
smalINUmber~~ ;
3. óra • Változók ás állandók ( 49 10 :
std : : cout «
ll:
re turn O;
"smal l number : '
«
sma l lNumber «
std: : endl;
12 :
smal ! number : 32767
smal l number : - 32768 small numbe r : - 32767
r A 4. sorban a s mallNumber neVÍÍ változ6t ezúttal signed shan típusú egészként határoztuk meg, (Ha külön nem adjuk meg, hogy előjel nélkülit szeretnénk, automatikusan el ő jeleset kapunk.) A program ub'Yanúgy folytatódik , mint az el őző esetben, de a~ eredmény egész más. I !ogy j61 megértsük II fenti eredményt, tudnunk kellene, hogy egy elő je les egbz ponlosan hogyan is ábrázo16dik b it szinten, vagyis k6L bájtot elfoglalva . A lényeg mindazonáltal az, hOh'Y habár pont úgy llHlködik, mint egy előjel né lküli egész, az előjeles egész a legnah'Y0bb pozitív énékéh61 a legkisebb m.:gmív értékébe fordu l át. A -32767-r61 csökkentjük az értékél, -32768-at kapunk, tovább csökkenlve 32767-et, 32766-ot, és igy tovább.
Állandók A válloLÓkhoL hasonlóan ,lZ <Í lland6k is adaltároJ6 memóriateriiletek. Ám amíg a vá lto-
zó k értéke V[tllOzhal, addig, ahogy m{tr bizonyára rájöttünk, aL áll,mdóké nem. Meghalározáskor az állandónak kezdőérlékel kell adnunk, k ésőbb aztán m,ír nem rendelhetünk hozzá új értéket. Miutá n egy {tllandó megkapta a ke zdőé rtékét, n evébő ! ered6en az éltéke állandó. A C++ kétféle állandót ismer, ezek: nyelvi állandó (litera!) és nevesített állandó
(symbolic).
Literális állandók A literális állandó egy olyan érték, amit egyenesen a programk6dba írunk, ahol arra szükségünk van. i nt
myAge "
39 ;
A myAge egy int tipusú változó, a 39 pedig egy nyelvi állandó. A 39-hez nem rendelhetünk értéket, és nem is változtathat juk meg azt.
50 II. rész· Bevezetés a C++ programozási nyelvbe
Nevesített (symbolic) állandók A nevesített állandók olyan állandók, amelyeket a nevük azonosít, akárcsak a változókat. A változókkal ellentétben azonban az értékük a bevezetést követ6en nem válLaztatható meg. Ha van egy programunk egy tanul o k és egy oszt alyo k nevő vállOz6val, az osztályok sz.,"ímának segít.<;égével ki tudjuk számítani , hány lanulónk van, ha tudjuk, hogy minden osztályba 15 tanuló jár: tanulok
~
Apropó
os z talyok * 15 ;
Az első operátorunk
A • operátor a szorzást jelöli. Az operátorokról később
bővebbe n
szót ejtünk.
A fent i példában a 15 nyelvi konstans. A kódunk könnyebben karb:tntarthat6 és olvasható volna , ha ezt az értéket egy nevesített állandóv:tI helyeuesítenénk. t~nulo k
=
oszt ~ lYOk
~
oS7.talylet$zam :
Ha a későbbi ekben úgy dönten&nk, hogy megváltoztatjuk a tanulók számát az egyes osztályokban, megtehetjük azon a helyen, ahol bevezettúk osztaletszam állandót, anélkül, hogy a programban [m1shol bármit is vt'tltoztatnunk kellene. igy ni ncs az a kockázat, hogy esetleg valahol elfe le jtjü k átírni , ami azt.'Ín nehezen kinyomozható logikai hibát eredményez.
Állandó bevezetése a #define kulcsszóval Ha a régimódi, veszélyes, részben helytelen módon szeretnénk álbndót bevezetni, így tehetjük meg: ndefi ne oszta l ylet8zam 15
igy határoztuk meg az állanclókat a régi C változatokban. AztflO az ANSI szabvliny bevezette const ku1csszót a C nyelvbe is. Figyeljük meg, hogy a osztalyleL'i7..am állandónak nincs rögzített típusa (int, cha r stb.). A #de fi ne egy egyszeru szövegbehelyettesítést csinál. Valahányszor az el6feldolgoz6 a osztalylctszam szóval talá lkozik, a 15 értéket teszi :il helyére a k6dszövegben. Mivel az el6feldolgozó a fordítóprogram el6tt fur, a fordító soha nem ta lálkozik az állandónkkal , a 15-ös számot látja hdycne.
3, 6ra • Változók és
Állandó bevezetése a const kulcsszóval, Hab,ír a IIdefine is mt1ködik, a C++-ban van egy jobb, kevésbé egészségtelen és sokkal ízlésesebb módja a konstansok bevezetésének: const unsigncd short int
o~ztaly let szam
= 15 ;
A remi példa szimén egy osztalyle tszam nevű nevesített állandót vezet be, dc ebben az esetben az álland6nak típusa is van: unsigned short int.
Tovább tart ugyan begépelni , de a használata s7..ámo.~ d6nnyel jár. A legfontosabb különbség, hogy az állandó típussal rendelkezik, így a rordító kényszerít bennünkel, hogy a típusának megfelele> módon haszn~Hjuk.
Felsorolt (enumerated) állandók A felsorolt álland6k ii llandók halmaz.át jelentik. Meghatározh:llunk például egy felsoro lást SZIN néven, majd meg:ldhatjuk, hogy erulek ötféle értéke lehet: PIROS, KEK, ZOLD, FEHER és FEKETE.
Nyelvtanilag 3 felso roiL állandók bevezetése a következ6 képp néz ki: használjuk az enum kulcssz6t, II típus nevét, majd kapcsos zárójelek közt: írjuk vesszővel elválasztva a lehetséges énékekcl. íme egy példa: en\lm SZIN ( PIROS , KEK,
ZOLD,
FEHER ,
FEKETE ) ;
A fenti utasítás két feladatot hajt végre egyszerre: l. Egy felsoro lást vezet be SZIN néven , azaz létrehoz egy új típust. 2. Létrehoz egy PIROS nevű nevesíten állandót O értékkel, egy KEK nevű nevesített álland6t 1 énékkcl, egy ZOLD nevű nevesített állandót 2 értékkel, és így tovább. Minden felsorolt állandónak megfeJel tehát egy egész érték is. I [a külön nem adjuk meg, akkor az első ,\llandó a O ért.éket kapja, a többi pedig mindig eggyel nagyobb az e16zŐnél. Az állandóknak persze adhatunk egy meghat:írozott értéket is. Ilyenkor ha vala melyiknek az értékét nem határozzuk meg explicit m6don, akkor az az el6tte lev6nél eggyel nagyobb értéket kap. Lássuk mindezt egy péld:ín keresztül: cnurn S zin { PIROS=lQO, KEK,
ZOLD=500,
FEHER,
FEKETE=700 J;
A PIROS értéke 100 lesz, a KEK értéke 101, a ZOLD értéke 500, a gül pedig II FEKETE nevúé 700.
FEHER
értéke SO l , vé-
Ermek a megoldásnak az az előnye, hogy névvel hivatkozhatunk az amúgy jelentés nélkiHi sZámokrd, jelen esetben 1 vagy 700 helyett azt írhatjuk, ho&'Y FEKETE vagy FEHER.
51
52 11. rész · Bevezetés a C++ programozási nyelvbe Bevett szokás, hogy mind a hagyományos, mind ~t felsorolt állandók neveit csupa nagybetűvel írjuk. Ez első látásra is nyilvánval óvá teszi, hogya nb, amit olvasunk, egy állandó, és nem valamilyen változó, amelynek megváltoztathatnánk az értékét. A fordítóprogram maga különben nem sokat tö rődik azzal, hob')' következelesen nevezzük-e el a változókat és az áIJandókat, ezzel tehát csak a saját és embertársaink életét könnyítjük meg.
Kérdések és válaszok Kérdés: Hu ti short illf lípusok tlilcsordu/halnak. mié/111em hasz ná/unk nlÍlIdig /ollg típl/sli (xUtozókal? Vá/asz: Mind a short mind a long egész tipusok tú1csordulhatnak, de n long típusú egészek ezt sokkal nagyobb számok esetéhen teszik csak. Ugyanakkor a legtöbb gépen a hosszú egészek kétszer annyi memóriát fogb lnak. Nyilván, ez ma már kisebb probléma, mim régen, mivel a legtöbb személyi s7!tmítógép memóriája több milliónyi (ha nem milliárdnyi) bájllJÓl áll. Kérdés: Mi /ór/éllik, ha lelx!gfipon /os típllS helyett ep,C-szhcz ref/ddtil/k Ifzedcsjc8gyel nmdelkczó szá mot, //Iint pé/dálll az alábbi sorba II :
int aNumbcr
z
5 .4;
Válasz: Egy jó fordító progr::un kűld ugyan egy figyelmeztetést az efféle megoldásokra , de az érté kadás ilyen módj.t megengedett. A hozzá rendelt számot a fordító ilyenko r egésszé csonkolja. így teMr ha az 5,4 értéket rendeljük egy egész rípusú változóhoz, annak értéke egyszt!ruen S lesz. Ügyeljünk rá , hogy ez a művelet adatveszréssel jár, vagyis kés6 bb hiába próbáljLIk meg az egé.~z típlts(t változónk érték61 cgy lebt!g6p()nIOS lípusúba áthelyezni, annak az értéke is 5 lesz. Kérdés: Miért ne haszlláljllnk Ilye/vi OiterálisJ állwuJókati miét1 kell nevesftelt ál/clI/dók bevezclésével töltentlllk a drága idŐn/..'(ft? VálCIsz; lia sok helyen használjuk ugyanazt az értékeL egy progmmban, akkor a neve-
sítdt kons\;\nsok segíL<;égévcl az összes előfordulásl megvá itoZlalltaLjuk ügy, hogy egyetlen helyen árírjuk az érlC:ket. A nevesílett állandók ráadásul elárulják magukról, hogy mksodák. Általában például nehéz lehel rájönni, hogy egy számo t miért is szorozumk mt!g éppen 90-nel, dt! ha azt olvassuk valahol a kódban hogya derekSzog sokkal könnyebb dolgunk van .
3. 6ra • Vá~oz6k és állandók 153
Gyakorlatok Most már kip róbálhatjuk a változók és állandók műk ödését. Válaszoljunk erre a néhány ké rdésre, és csin álju nk felada to kat, hog)' meger6sítsük a ford ító p rogram mű kö désével ka p csolatos tu dásunkat.
Kvíz 1. 2. 3. 4.
Miért célszerű előjel nélküli változ6kat használni előjelesek helyett? Mi a különbség a kezdóérték megadása és a változó meghatározása között? Azonosak-c II DOG, dog, Dog, és a doG változ6k? Mi a különbség a IIdefine és a const ut.1sítások között?
Feladatok 1, Egészítsük ki rt 3.1. Listában sze replő programot úgy, hogy az összes, a 3.1. tllblázalbar1 szereplő változ6típusr tarta lmazza. A kapolt értékek e l térőek lehetnek azoktól, amelyek a táblázatb,lll szerepelnek! 2, Gondolkozzunk el azon, hogy mílyen számokkal és egyéb információval tal<1lkozunk mindennapi életünk során, Milyen típusú változ6kban lenne a legjobb ezekcllárolni? Milyen beszédes ne veket adhatnánk ezeknek a vllllozóknak? 3. Ha van tapasztalt programfcjlesa6 ismcr6sünk, kérdezzük meg t6le, hogy milyen változ6elnevezési stratégilll haszn:ll? Ha elég sok ismer6sünk van, tapasztalni fogjuk , hogy különböz6 módszereket alkalmaznak. Kérdezzük meg 6ket, hogy miért. Lehet, hogy meglep6dünk majd a válaszokon,
1. Az előjel nélküli egészek nagyobb pozitív sámok tárolására alkalmasak, mint az el őjelesck . Ugyanakkor nem vehetnek fel negatív értékeket. Programozóként a mi feladatunk eldönteni , hogy melyik a l(:!gjobb választás egy konkrét probléma megoldására. 2. A meghatározás (definíció) az, amikor adunk egy típust és egy nevet egy később hasznáhli kívánt változónak. A kczd5érték megadás.1 (jnicializáci6) ezzel szemben az első érték hozzá rendelése a7. adon változóhoz. A meghatározás és a kezd6érték megadása szerepelhet ugyanabban az utasításban is. 3. Nem, nem azonosak. A C+ + különbséget tt!sz a kis- és a nah'Ybetúk közőtt, így mindegyik írásmód küJönböz6 vá ltozókat jelent :1 fordít6progmm sz.imám. Azt, hOh'Y milyen elnevezési konvenciót használunk , nekünk, vagy a Illunkacsoporrunknak kell eldöntenie. 4. A IIdefinc egy
4.
ÓRA
Kifejezések és utasítások Ebben az órában a következ6króllesz szó: • •
Mi az utasítás? Mi a kifejezés?
• •
Mtlveletek operátorok kal Ig:lZ vagy hamis - logikai
műveletek
Utasrtások Utasításokbl idnyítjuk a program fu tását, értéke1hetünk ki kifejezl!sckct, vagy speciális esetben (null) nem hajtunk végre :;emmit. C++-ban minden utasítást pon tosvessző zár. íme egy példa egy nagyon egyszeru utasításra: x = a
1"
b;
A matematikával ellentétben ez nem azt jelenti, hogy x egyenl6 a és b összegével, hanem azt, hogy a és b összegét rendeljük x-hez. Röviden: a és b összegét helyezze el a program x-ben. Noha látszólag két műveletet hajtunk végre ezzel az utasításS<'lI, még-
=---==='--'-====_ _________
56 11. rész· Bevezetés a C++ programozási nyelvbe __----=-1.::-
is csak egynek számít, ezért csupán egy pontosvcssz6vel zárjuk le. A hozzárendelési operátor a jobb old:llon található kifejezés eredményét hozzárendeli a balolda lon található elemhez.
Űres helyek A szóköz, a vízszintes tabulátor és az újsor karaktereket úgynevezett üres karJkterek (whitespace). A fordító automatikusan fib'Ye\me n kívül hagyja az felesleges üres helyeket, íb'Y egy szóköz helyére nyugodtan írhatunk tabul<1tort vagy újsor karaktert is. Az üres karakterek els6.sorTh1n az olvashat6ságot javítják, a fordító nem foglalkozik velük. A korábban bemutat.ott él1ékadásunk:
írható akár így is: x •
.a b
Noha a második megoldás is hibátlan, mégis butaság. A tabulálás kétélű fegyver. Megfelelő alka lmazása eseté" megkönnyíti a fo rráskód olvasását és karbanlartását, ha nonban ÖSS:lCviSS7';:1 használjuk teljesen érthetetle" zagyvaság lesz az eredmény. Maga a C++ amúgy a józan íté l6 képességünkrc bíZ"';:1 a dolgot. Pontos megjegyezi, hogy az üres kamkle rek lényegesek egy változó, vagy egy fü ggvény nevében, dc ne m LlgyHnazt jelentik, mint Cgy közönséges kamklerláncban. A secondsPerMinute például teljesen m:1st fog jelenteni, ha seconds Per Minute formában írjuk le, (!kkor ugyanis a fordító három különböző változókénl fogja kezelni.
Összetett utasftások Minden olyan helyen, ahol szerepelhe t egy egyszeru utasítás, használhatunk összetett utasítást (compollnd staternent) is. Az összetett utasítás nyitó kapcsos zár6 jelle l n{" kezd6dik, és záró kapcso.~ z:1r6jellel "}" végződik. Minden utasítás pontosvesszővel zárul, dc maga az összetett utasítás nem, amint az a következő példában is láthatá: temp'" a ; a bb " temp;
Ez a kód az a és b változók tartalmár cseréli fel.
4. óra • Kifejezések é. uta. ltá. ok 157
I Minden nyitó kapcsos zár6jelnek legyen záró pá rja . Minden utasítás pontosvessz6vel zárul. Forrásk6dunkban pr6bálju k meg értelmesen használni
Összetett utasításaink sose It::gyenek túl hosszúak, hogy átláth:rt6 marddjon a kód.
Kifejezések A kifejezés olyan ul:lsÍ\.1s a C++-b:lll , amely é rtéket ad vissza. Minder' kjf~j cz(:s ulasíl:í s teh5t, de o{:!m minden ut,lsít:1s kifejezés. Sok kódrészlet mcglcp6 mó
példa: 3.2 PI
SecondsPerMinutc
/1 visszaad ja a 3 . 2 értéke t /1 lebeg6pontos á l landó , mely a 3 .1 4 et adjo vissza II egé~ z t ipusú ko ns t ans , maly a 60-a t adja viss za
Tekintve, hogya PI egy konstans, amely 3. 14-gyel egyenl6 , illetve a SecondsPe r Mi nut e szintén konstans és 60-al egyenlő, mindhárom utasítás egyben kifejezés is. Az x = a i' b nem csupán összeadja az a-t és b-t, valamint hozziÍrendeli az e redményt xhez, dc visszaadja az x értéké! is. Ez :L művelet igy szi nté n kifejc:d:snek számít. Mivel pedig kifejezés, szerepelhet egy é nékadó operátor jobb oldalán a következ6képpen: y _ x = a
i'
b;
A hozzá re nd e l ~s i vagy C:rtékadó operátor (=) a baloldalon a jobb olda lon Található kifejezés énékével.
sz erep l ő
értékel fe lülírja
Az o pcl1U1dus egy matematika i kifejezés, ami azt az objektumot jelenti, amelyen az operátor segítségével valamilyen műveletet hajtunk végre. Az imé nt bcmutHoTt kódsor például az alábbiak szerint hajtódik végre: • A program összeadja a és b énékét. • A kifejezés eredményét hozzárendeli x-hez. • A hozzárendelés Ilunt kifejezés eredményét hozzárende li y-hoz.
58 11. rész • Bevezetés a C+ + programozási nyelvbe Ha a, b, x és y egészek vallak és a énéke 2, b értéke pedig 5 VOll, akkor x és y értéke egyaránt 7 lesz. A 4.1 Listában bemutatjuk néhány összeteU kifejezés használatát.
O:
,j nclude
"2 :
int main()
3:
{
int a=O, b"O, x..,O, y ~ 35: std : :cout « 'beforc a : • « std :: couL« • x: . « x«
4: 5:
6: 7: 8: 9: 10:
a « • b : • « b; • y : ' « Y« std :: endl ;
a " 9; b " 7;
y _ X " a+b; std : : cout « 'after a: std : : cout « • x: " « return O;
11: 12 :
' « a« x «
b :' « « y «
b; !:ltd : : endl:
13 :
beforc a ,
° ° ° b:
x:
y : 35
after a: 9 b: 7 x : 16 y : 16
A 4. sorban deklar~iljuk és inicializáljuk a változókat. Az 5. és 6. sorban kiíratjuk az éc* tékeiket. A 7. sorban az a ért6két 9*re v{lltoztatjuk, a 8. sorban a b értékét pedig 7*re. A 9. sorban kisz:í.moljuk a és b összegét és hozzárendeljük azt x*h(!z. Miután az x=a+b kifejezés kiértékelése megtörtént, ennek eredményét hozzárendeljük y*hoz.
Operátorok Az operátor olyan jel, amely utasítja a fordítót a megfeJel6
műve let
elvégzésére.
~rték.dó operátor Az értékadó operátor baloldalán található kifejezésI balél1éknek (I*va]ue), a jobb oldali operancJust pedig jobbénéknek (r*value) nevezzük.
Apropó
Konstans csak jobbérték lehet
A konstans jobbérték, fgy nem szerepelhet értékadás baloldalán. x ,, 35 ; 35
=
x;
/1 így helyes /1 ez helytelen , konstans nem s zerepelhet baloldalon
4, óra • Kifejezések és utasftások 59 Abalérték (l-value) a kifejezés bal oldabn szerepelhet, míg ajobbérték (r-value) a kifejezés jobb oldalára kerül. Jegyezzük meg, hogy ntinden balérték szerepelhetnek egy kifejezés jobb oldalán is (vagyis lehet jobbérték is), de fordítva már nem fel tétlen igaz az állítás. Például az x =5 ; helyes, míg az 5 =x; helytelen.
Apropó
Miért figyeljünk a bal- és jobbértékekre?
Bizonyára felmerült az olvasóban a kérdés, miért kell megtanulnunk efféle furcsa kifejezéseket mint balérték meg jobbérték? Nos, az egyik ok az, hogy a fordítási hibaüzenetek megértéséhez ez is fontos. Ha nem értjük e két fogalom közti különbséget, néha nem fogjuk tudni, hogy mi is a probléma oka. Azt is fontos átlátni, hogy nem fogalmazhatunk meg akárhogyan egy adott kifejezést. Vannak helyes és helytelen alakok, amelyek elsö olvasásra logikailag egyformának tűnhetnek.
Matematikai operátorok Összesen öt matematikai openhor van: összeadás (+). kivonás C- ), szorzás C*), osztás CJ), és maradékos osztás (%). A C++ a C-hez hasonlóan azon kevés nyelvek egyike, ahol nincs beépített hatványoz6 operátor az Y-adik IHltványon). Ehelyett függvényt kín:íJ a felad::lt megolclflsára.
ex
Az összcad{ls, a kivonás és a az osztás.
szo rzá~
úgy viselkedik, ahogy várjuk. Nem így azonban
Az egész számok közti osztás kicsit különbözik a matematikában mcgszokottóJ. Norm{tlis esetben ha a 2"]-et elosztjuk 4-gye!, akkor egy valós számot kapunk (a v,116s sz-tlln egész- és tőrtrészből áll) . Az egész típusú v,íltoz6k azonban nem rendelkeznek törtrésszel, így ebben az esetben a C++ egyszerűe n levágja a Jőlősleget". így aztá n a 21/4 kifejezés C++-ban dolgozva 5-öt ad eredményül. A mamdékos osztás vagy modulo operátor Ct) a fenti egészosztás maradékát adja vissza . 21 % 4 például l-et ad, hiszen 21/4 =5 és a manldék 1.
Apropó
Amaradékos osztás ról
,,21 % 4"-et huszonegy modulo négynek olvassuk. A modulo az a műve l et, amelyet a maradékos osztás operátora végez el. A modulo művelet végeredménye az osztás maradéka (modulus), ami példánkban 1. Meglepő
múdon a mamdék meghatározása igen hasznos lehet. Jó példa erre, ha egy adOlt egy másik művelet ntinden tizedik futásám akarunk c.~ak végrehajtani.
műveletet
Világos, hogy minden 1O-zel oszthatú szám lO-es maradékos osztás esetén nulla maradékot ad. Így például nullát ad eredményül a 20 % 10, a 30 % 10, és így tO\;ább.
60
II.rész· Bevezetés 8 C+ + programozási nyelvbe A lebeg6pontos osztás ugyanúb'Y viselkedik, mintha 7_-;ebszámológéppel hajtanánk végre, vagyis a C++-ban ez az "igazi" osztás. Ezzel a múvelettel helyreáll a világ rcndje, vagyis 21/4.0 - 5.25. Ha bármely operandus lebeg6pontos, akkor az eredmény is lebeg6pontos lesz.
Matematikai és értékadó operátorok kombinálása Gyakr::ln cl6fordul, hogy egy változó értékéhez hozzá szeretnénk adni egy másik számot és az eredményt az eredeti változóba visszaírni. Például Im a myAge változót szcretnénk kCH6vel növelni, akkor azt így is írhatnánk: int myAqc ,. 5 ; int temp; tcmp • myAge + 2 ; lny Age .. temp ;
II összeadjuk az 5-öt 65 a 2-t majd ' mentjo.k " II a temp változ6ba II a melyet aztán itt vis.nahelye:r.ul1k a myAge változ6ba
Lássuk be, ez elég nyakatekert és helypazarl6 megold(ls. C++-ban Szerencsére egy vá ltozó egyszerre szerepelhet a hozzárendelési operátor mindkét oldalán: myAge
~
myAge
!
2;
Ugye mennyivel szebb?! Matematikában ez a kifejezés értelmellen, de C++-ball azt jelenti, hOgy növeljük meg kett6vel a myAge változó értékét, majd rendeljük azt hozzá magához a myAge változóhoz. Meg egyszerűbb - noha kezdetben kevésbé átlátható - írásmód
II
köverkez6:
myAge += 2 ; Az önhozzárendel6 összeadás operátord (+=) (self-assigned addition) hozzáadj:1 a jobb oldalt :l balhoz és az eredményt hoz7.árendcli a baloldalhoz. Szóban ~ plusz-eb'Ye nI6~ oper:.'itornak hívjuk, vagyis jelen példánkat myAge plusz-egyenl6 kettőne k olvassuk. lia például a myAge értéke 4 volt, akkor a fenti múvel!;:t után 6-r;\ vá ltozik. Önhozzárende l ő
pezhető:
operátor egyébként bármely matematikai operátorból hasonlóan kékivonás ( -=), szorzás (*=), osztás CI,;), valamint a mamdékos OSZlás (t=).
Növelés és csökkentés Leggyakmbba n egyet adunk hozzá egy változóhoz vagy vonu nk ki bel6le. Az eggyel növelés az inkrementálás, az eggyel tönén6 csökkentés pedig a dekrementálás. A C++-b:1I1 ezek a műveletek saját operátorral rendelkeznek. történő
Az növe l ő operátor (H) eggyel növeli a változó értékét, a csökkentő operáto r (--) ped ig eggyel csökkenti. Például ha a c változót növei ni szeretnénk, azt a követk ezőkép teheljük meg: C++ ;
II a c é rtékét megnöveli eggyel
_ _ _ _ _ _ _ _ _ _ _ _ __ _ _ _ _ _---'4::..:ó:r=a -·"Kife = je"zó", "=k."ós,-ut """s"ltá",::ok.,l__s,-l- - ---C'. A fenti sor végeredményét tekintve
eh'Yenértékű
ezzel
c '" c + l;
illetve mint ahogya korábbi példák mutatják akár ezzel is: C
+= 1 ;
Tudta hogy...?
Honnan szánnazik a C+ + elnevezés?
Miért is hfvják C+ +-nak azt a nyelvet. amit éppen tanulunk? A C+ + nem teljesen új nyelv, hanem a C nyelv továbbfejlesztése, egy szinttel val6 HnöveléseM,
Elótag Iprefix) és utótag Iposlfix) operátorok Mind II növelő CH), mind pedig a csökkentő (--) operátor ha~zn;ilhal6 d6tagkEnt (prefix) és ut6tagként (poslflX) egyaránt Prefix esetén (a ~p re" jelentése: el6) a z operátort II változó elé írjuk (példáu] ++mYAge), míg postfix haszn51al eselén ( a ~pOSl" jelentése: utó) az operálorr II vállOZÓ neve után írjuk (példfíu] myAgc++). Egyszerűbb
utasításoknál általában mindegy, hogy melyik alakot h:lsználjuk. Ha azonban az inkrcme ntál6 vagy de krementál6 kifejezés egyben értékadás része, már egyáltaI:ín nem mindegy, hogy .elöl" vagy . hárul" van az operátor. A prefix operiítor ugyanis a hozzárendelés el6u ért é kelődik ki , míg a postnx csak hozz:'írendelés után . A dolog logikája röviden: prenx esetén előbb növeljük a változ6t és utána eZI használjuk az értékadásnál, pűSlfi x esetén viszont elóbb kiolvassuk az é rtéket, elvégezzük az értékadást és csak utána növeljük a változó tartaimát eggyel. Elóször L-waros lehet, de nézzük csak az alábbi példát, ahol x eredeti é rtéke legyen 5: int a ,. ++ x;
A fordító a lln1velet hatására előbb növeli az x-et (6 lesz) és utána eZl kapjuk az a változóban. Végül te hát mindkét változó értéke 6 lesz.
m~g
Ha viszom így pr6bálkozunk: int b '" x++ ;
akkor ill a fordító fogja az x értékét, beleteszi a b változ6ba, majd pedig növeli t!ggycl az x-el. Következésképp a b értéke 6 lesz, az x értéke viszont 7. A 4.2 Listában jelen van egyszerre mindkét típus.
62 11. rész· Bevezetés a C+ + programozási nyelvbe
4.21i1ta -
o: l: 2:
3: 4. 5: 6: 7. 8. 9. 10 :
11 : 12 : 13 :
14: 15 : 16 : 17:
18 : 19 :
20 : 21 : 22 :
prefix ól POIIfiX . . . . -
t.....-x.cpp)
II 4.2. Lista II Operátorok p r e fix és postfix használatának II bemutatása 'include
int maln() f int myAge : 39 ; II é r té ke k beállitása int yQurAg e : 39 ; s t d : : cou t « " I am: \ t" « myAge « "\tyears old . \n" ; std : : cout « "You are : \t" « you rAge « "\tyears old\n "; myAge+ +; II postf ix növelés !+y ou rAgc ; II p ref ix n Ov elés s t d : : oD u t « "On e y ear p asse~ ... \n"; s td: : oou t « "I a m:\t" « myAge « " \tyear s old. \ o'; s Ld : : oout « "You a r e : \t " « y ou r Agc « " \tycars old\ n "; o t d : : oout « "Ano ther y ear passes \ o" ; std : : oDut « " I am :\ t " « myAgc+ + « "\tyears o l d .\ n "; std : : oout « "You a r e : \ t" « ++ yourAQe « "\tyears o l d\n "; st d: : oout « " Let ' s print it aga in. \ n " ; sLd : : cout « " I arn : \t " « myAge « "\tycars old . \n" ; std : : oout « ' You are : \t " « yourAge « " \tyeilrs old\n" ; return 0 ,
23 :
I arn 39 years old You arn 39 years old One year passes ... I am 40 yea r s old YOU are 40 years old hnother year passcs I am 40 y ears old You are 41 yea rs o l d Lct' s print i t agai n 41 y ca r s o l d I am 41 yea rs o l d You ure
A 7. és 8. sorban két egész változót deklarálrnnk és beállítottuk mindkett6 értékét 39re. Él1ékeiket a 9. és 10. sorokban írat juk ki. A Il. sorban
prenx módon növeljük a
myAge változ6t, a you rAge változ6t a 12. sorban
pedig postfL'{ módon. A végeredményeket a 14. és 15. sor itatp ki.
Mindkettő
egyaránt 40.
4. óra • Kifejezések és utasftások 163 A 17. sorban a myAge változó értékét növeltük a kiíratás alatt postflx módon, emiatt szintén 40-e t írt ki és csak később növekedell az értéke. Ezzel szemben a 18. sorban prefLx növelést hajtottunk végre a yourAge változón, a képernyőre kerüld érték ennek megfelelően 41 lett. Végü l a 20. és a 21. sorokban ismét kiíratmk a két változó énékét. Mindkét változó eselén megtörtén a növelés mindkét esetben így a myi\ge és a yourAge is 41 letl.
Apropó
Ha háromféleképp csináihatom ugyanazt, melyiket válasszam?
C++-ban az elmondottak alapján háromféle módon növelhetjük egy változó értékét eggyel: A=A+l , A+=l vagy AH . Mármost joggal kérdezheti az olvasó, vajon melyiket és miért az érdemes használni. A processzor felépítése miatt jó ideig az A+= l hatékonyabb volt az A=A+l-nél, ilIetve az A++ hatékonyabb volt az A+ =l-nél. Egyes helyeken ez va lószínűle g ez még ma is igaz, de az optimalizálást végző fordítók manapság igen hatékony gépi kódot álHtanak elő. Én személy szerint a t- + operátort szoktam használni, ha nyilvánvalóan eggyel szeretném növeini egy változó értékét. Ha valami más értékkel szeretném növelni, akkor a += operátort használom, míg több változó összegzése esetén a = és t operátorokat alkalmazom a "klasszikus" módon. A különböző helyzetekhez igazodó különböző megoldások számomra átláthatóbbá teszik a forráskódot.
Precedencia, avagy a műveletek végrehajtási sorrendje A precedencia a
műveletek
végrehajt,ísi sorrendjét jelenti.
Egy olyan összetett utasítás, mint az x=5+3
~
8 ;
felveti a kérdést: az összeadást vagy a szorzásl hajtjuk végre először? Ha az összeadlíst, akkor az eredmény 64 lesz, ha pedig a szorzáSl, akkor 29, vagyis a dolog egyáltalán nem mindegy. Minden opt!rátor re nclelke:lik precedencia s:únttcl. (A részletes lista a könyv elején található.) Esetünkben a szorzás például magasabb rangú művelet , mint ai összeadás, így a helyes válasz a 29. Ha két operátor ugyanolyan precedenciával rendelkezik, akkor balról jobbra haladva hajtjuk végre a megfelelő műveleteket. Például az x = 5 + 3 + 8 * 9 + 6 * 4;
eserén a 57:0fzások értékekXlnek ki
először:
8"9-72, 6*4-24.
64 11. rész· Bevezetés a C+ + programozási nyelvbe Ezután a kifejezés így néz ki: x - 5 + 3 + 72 + 24 ;
Balról jobbra haladv;t ,tZ összeadásokkal: 5 + 3 ... 8, 8 + 72
=
SO, 80 + 24 ... 104,
Figyelem! Néhány oper{ILor esetén - ilyen például a hozzárende1és is - jobbról balld történik a kiértt':kcl6dés. Mi a helyzet, ha a precedencia sorrend nem megfelel6 számunkra? Nézzük eZt a peldát; TbtalSeconds - NumHinutesToThink
+
NumHinutesToType
~
60
Ennél a kifejezésn(Ol nem a NumMinutesToType ~rté két szeretnénk hatvannal megszorozni, majd hozzáadni a NumMinutesToThink értékél, hanem el6bb elvégezni HZ összeadást, majd pedig a szorL.ást. A z{jrójelezéssel il yen esetben felülbírálható a precedencia. A zárójelben !év6 elemek bármely matematikai operátorr.tl szemben els6bbséget 6[veznek. A mi példánk ennek megfelel6en így néz ki; TotulSeconds
Tudta hogy... ?
=
(NumMinutesToThink
+
NumMinutesToType) • 60
Zárójelezés
Bizonytalanság asaMn bátran zórójelezzünk. A program nem lesz lassabb és a fordl-
tási idö növekedése is elhanyagolható.
Egymásba ágyazott zárójelek összetett kifejezésekben Összetett kifejezéseknél el6fordllllm , hogy egy m ~sba kell ágyaznunk zár6jelpárokat. előző példánknál m:tr.ldva tegyük fel , hogy nem csa k a másodperceket kell kiszámolnunk.
Az
TotalPcrsonSeconds (People l nTheOffice
+
( ( (NumMinu tesToThink PeopleOnVacation) )
+
NumMinutesToType )
~
60)
~
Ezt a bonyolult kifejezést belü l1'61 kifelé haladva értékeli ki a fordító, Először a NumMi nutesToThink váiL07:ót adja hozzá a NumMinutesToType-11OZ, mert ez a legbelső zárójelpár. Az összeget megszorozza 60-nal. Ezután a Peopl e lnTheOffice értékét hozzáadja a PeopleOnvacation-hoz. Végül pedig az emberek számát összeszorozza a másodpercekkel. A példa megint felvet egy érdekes kérdést. Ezt a kifejezést a számítógép könnyen fel tudja dolgozni, de emberként nehéz átlátni, megérteni vagy módosítani. Az előző kifejezést így is írhauuk volna néhány ideiglenes változór bevezetvc ;
4. óra •
és utasftások 65
TotalM inut es : NumMinutesToThink + NumMinutesToTypc; TotalSeconds : TotalHinutes • 60 ; TotalPeople : Peoplel nTheOffice + PeopleOnVacation; TotalPersonSeconds = TotalPeople * TotalSeconds; Ez a p&lda hosszabb és több ideiglenes változót haszná l, mint az előző , de sokkal könnyebb megérteni. A 60-at szimbolikus konstansra cserélve &s az egészet egy magyaráz6 megjegyzéssel ellátva könnyen érth ető és ka rbantartható forráskódot kap unk.
Tudta hogy...?
Az igazság természete
AC++ korábbi változatai az igaz és hamis értékeket egészként ábrázolták, az ISO/ANSI szabvány azonban bevezetett rájuk egy új típust, a bool-t. Ennek két értéke lehet: IGAZ vagy HAMIS. Minden kifejezés kiértékelhetö logikailag. Azok, amelyek matematikailag nullát adnak hamisak, minden más viszont igaz. Régebbi fordítók long int-ként kezelték a bool típus!, melyek így 4 bájtot foglal tak. Az. ANSI kompatibilis fordítók a legtöbbször már csupán 1 bájton tárolják a bool típusú változ6t.
Relációs operátorok Relációs operátorok segíts6gével két számról eldönthetCS, hogy egye n lőek vagy az egyik kisebb a másiknál. Minden reláció I-et (igaz) vagy O-át (hamis) ad vissza. Relációs operátorokról a 4.1. Táblázat nyújt áttekimést. Ha a myAge értéke 39, a Y0 \.lrAge ped ig 40, akkor:lZ hogy vajon egycnl6ek-e?
egyen l őség
operátorr:l l elc.lönthc-
tő,
myAge == yourAge ;
II ugyanaz a rnyAge és a yourAge értéke?
A kiértékelés végén nul1:'it - vagyis hamisat- kapunk, men a két változó értéke nem egyenlő.
myAge > yourAge;
II d myAge nagyobb a yourAge-nél?
szi ntén nullát - vagy hamisllt - ad .
Figyeleml
Hozzárendelés vagy egyenióség?
Számos kezdö C+ + programozó összekeveri a hozzárendelö operátort (=) és az egyenlöség operátort (= =), amely aztán sok alaUomos hiba forrása lehet. A gondot az jelenti, hogy a relációs operátor végeredményét hozzárendelhetjlik egy változóhoz! Teljesen szabályos például, ha a = b > c vagy a o=c b == c jellegű kifeje zéseket használunk. A fordító figyelmeztethet. ha például hozzárendelést irtunk, amikor az egyenlöségnek több értelme lenne. Ilyenkor gondolkodjunk el, mert nem biztos, hogy tényleg azt írtuk, amit végre szereUűnk volna hajtatni.
56 11. rész • Bevezetés a C+ + programozási nyglvbe I lat relációs operátor van: egyenlőség (= "'), kisebb «), nagyobb ( », kisebb vagy « =), nagyobb vagy egyenlő (>-=), illetve a nem egyenlő (! =). A 4.1 Tábhízat ezekről ad áttekintést, használatukat is bemutatva. egyenlő
4.1 T6b1Ú81- _ _Operátor
Név egyenl6seg oem
egyenl ő
!-
nagyobb
>
nagyobb vagy egyenlő
>-
kisebb
<
kisebb vagy
egyenlő
<-
Péld a
A példa kiértékelése
100 - - 50, 50 -- 50,
hamis
t OO!- SO; 5O!- SO; 100 > 50; 50 > 50; 100 >- SO; 50 >- 50; 100 < 50; 50 < SO; tOO <- SO; 50 <- 50;
igaz igaz
hamis igaz hamis igaz ign
hamis hamis hamis igaz
Az ff utasftás Alapesetbe n a programunk sorról ~rra fut , vagyis .lbban a sorrendben hajtja végre az utasítfIsokat, ahogyan azok a fOmlskódban szerepelnek. Az if utasítás azonban lehetCívl: teszi felt.ételek vizsgálatát - példáu l két vá ltozó egyenlő-e - és a kiénékel€!stő\ függ6en ágazik el és fut lovább a program. Egy
egyszerű
példa az i f pal".mcsra:
if (kifejC2.ésl utasitás :
A zár6jclen belül bánnilyen kifejezés lehel, dc leggyakrabban relációs kifejez6s1 ta lálunk. [la a kifejezé.~ értéke O, akkor hamisnak minősü l és a számítógép nem fu ttatja le az utasításr. Ha nem nulla az értéke, akkor ig:lznak minősül és lefut az utasítás, Nézzilk a következő példát: i t (bigNumbc r > smallNumbcr) bigNumber " smallNumber :
Ez a kódrészlet összehasonlítja a bigNumber-t éS:il sma llNumber-t , Ha a bi gNumber nagyobb, akkor a mflsodik sor fut le, így a bigNumber énékét a s mallNumber értékével tesszük egyenlővé.
4. óra • Kifejezések és utasítások 167
------------------------------~~~~~~==~-~----~l·
Az else ág Feltételvizsgálatkor sokszor használunk az igaz ág mellett egy hamis ágat is. A korábban bemutatott módszerrel külön-külön vizsgálhatnánk a feltételeket. Noha működik , mégis kicsit kényelmetlen megoldás. Az else kulcsszó használatával lényegesen olvashatóbb a forrásk6dunk: i f (kifejezós)
utasí tás; e lsc utasítás;
A 4.3 .Lista az else kulcsszó használatát mutatj<1 be.
4.3 usto - Az 01.. ""_
6 használatának bemutatása (H.....cppl
o: 1:
114.3. Lista - Az else ku lcsszó haszná l ata fflnclude
2. 3:
int main()
,.
13 :
int first Number • secondNumber ; secondNumber) std: : cou t« '\nThanks !\n '; e l se std :: collt« '\nOops . The sccond is bigger ! " ;
14 : 15 :
rcturn O;
S: 6: 7:
8: 9: 10 : 11 : 12 :
16 :
Pl ease e nt e r a big number : 10 Pl ea se ent er a smal1er n umber. : 12 Oops. The second i s bigger !
M,ís számókkal más eredményt kapunk: Please ent e r a b ig number : 12 Please enter a smaller number: 10 Thanks!
A 10. sorban található i f utasítás kiértékelése után, ha az igaz, akkor a ll., ha viszont hamis, akkor a 13. sorban fo lytatódik a program futása. Ha a 12. sorban található else
ss l l. rész • Bevezetés. C+ + programozási nyelvbe ágat kivesszük, akkor a l,). sor mindig lefut, függetlenü1 a reláció igaz vagy hamis voltától. Ebben az esetben az if uLasítás a 11. sorban véget ér. I [a nincs az el se ág, akkor a 13. sor fut le következőnek. A parancsokat kapcsos zár6jel párok közé foglalt kódblokkokkal is helyeucsíthetjük.
Megismerkedünk egy újabb utasMssai Az std : : cin utasrtás segftségével olvashatunk be C+ + alatt a billentyűzetről adatokat egy változó ba. i f (kHejczés)
utasítás; kövctkez6 utasítás ; Ha a kifejezés igllZ, akkor ldut az utasítás majd pedig <1Z i f uL{tni utasítás. H a hamis, úgy az cJs6 nem fui le, hanem egyb61 a következő sorra ugrik
Ne feledjük, ;\ pontosvessz6vellczá rt utasítás helyére kapcsos z.'irojelekkel határolt,
összetett ulasítássorozatot is írh.ltnánk.
Összetettebb if konstrukciók Érdemes megjegyezni , hogy bármilyen kifejezés szerepelhet U!asításkent az if és az else ~gban , akár egy m~sik .i f vagy else is. A kövelkcz6 példában bemutanmk egy összctett if szerkezetct: i f (kiCejczés l ) (
iC
(kHcjezés2) utasításl:
el!lc (
if
(kifejezés3) utasitás2 ;
elflc utasitás3 ;
else utasitás4 ;
Ez a kissé ormótlan kódrészle t a kövelkez6t jelenti: ha a kifejezésI és kifejezés2 is igaz, akkor az utasitásl-ct hajtjuk végre. Ha a kifejez ésl igaz, a kif ej ezés2 hamis és II kifejezés3 igaz, akkor az utasitás2 fuI le. Ha kifejezésI igaz, de a kifejezés2 és kifejezés3 hamis, akkor az utasitá s 3-at fUlIatjuk. Végiil pedig ha
4. óra •
_sok 69
az első kifejezés hamis, akkor az utasitás
4.4 Liota -lIIIZOIOiIO ogym6sba ágYUOIt H- . . . I-H.cpp) O, l :
II 4 .4 Lista - Össze t ett, egymásba ágyazot t if utasitások
~i ncl ude
2,
3:
int mai n ()
4,
5: 6: 7:
8:
9: 10 : 11: 12 :
13 : 14 :
15 : 16 : 17 :
II Beolvasunk két számot Illetároljuk 6ket a bigNumber és littleNumber változ6kba II Ha a bigNumoor nagyobb, mint él littleNumber , II megnézzük, hogya kc t t6 mara dék nélkül osztható-e egymással I I ha i gen , megné;o:;o;ük , hogy ugyanaz-e él két szám int f i rstNumber, sceondNumber ; atd : : eout « "Enter two numbcrs . \nFirst , "; std " cin » firstNumber ;
std , : eout « "\nSecond : . ; std , : cin » sccondNumber ; std : , eout « " \n\n";
18 :
if (firstNumbcr >_ secondNumber)
19 : 20 :
{
if ( (firstNumber 11 secondNumbcr) ::= O)
21 , if (firstNumber == sccondNumber) std :: eout« "They are the same!\n" ; else i::ILd : : eout « ' They are evenly divisible!\ n ' ;
22 :
23 : 24 : 25 : 26 :
27 :
else std :: cout«
28 :
"They ara not evenly divisible!\n" ;
29 : 30 :
else
31 : 32 : 33 :
std :: cout« return O;
-
Enter two numbe rs . First: 10 Second: 2 They arc evenly divisiblc !
"Hey! The second one is largcr!\n' ;
70
II. rész · Bevezetés a C++ programozási nyelvbe Más számokkal más végeredményt kapunk: Enter two numbers . First, 2 Second : 2 They arc the same!
Enter two numbers . First : 3 Second : 2 They arc not evenly divisible !
•
S
A két bekér! számOl összehasonlít juk. Az első - 18. sorb;lfl (a!;llh:ltó - j f ut::lsítássa l el· len()rizzi.ik, hogy az első szám nagyobb·e, mint mási k. Amennyiben nem, ügy::l 30. sorban található ebe ágm ugrik a programunk, Ha igt!n , akkor a 19. sortó l folytatódik a programunk. A másod ik i f utasítás a 20. sor· ban ell enőrz i , hogy az első szám oSZlhal6·e a másodikkal mamd(!k nélkül. I-Ia igen, ak· kor a 22. sorlxlI1 találl1:l lÓ i f utasítással eldöntjük, hogya két szám egyenI6·e, és :Hl· n:lk megfel el6cn kiírmjuk a megfelelő üzenetet. Ha nem teljesü l a 20. sor feltétele, ::Ikkor 27. sorb:m található else ágra ugrik a vezérlés.
Kapcsos zárójelek hasmálata egymásba ágyazott
~
utasftások esetén
Amennyiben csak egy utasítást kívánunk végrehajtlmi, ügy az i f után elhagyhat6 a kapcsos zárójel: i r
(x > y)
i f (x < z)
)( = y ;
/1 Ha x nagyobb y-nál /1 és x k isebb mint z /1 akkor x-et értékét hozzárendeljük y-hoz .
Nagy és összetett egymásba ágyazott utasítások esctén ugyana kko r ez II megold:ís je· megnöveli :l hiba lehetóségét. Ne feledjük, az üres k:uakterek használata könnyebbséget jelent a programoz6nak, de a fordító figyelmen kíviJ l hagyja, Zár6jele· zés hiányában könnyen előford ulhat, hogy nem a m egfelelő H·hez írjuk az else ágat, amely így logikai hibát fog eredményezni. Erre láthatunk pék!;1t a 4.5 Listában. lentőse n
I
4. óra • Kifejezések és utasítások 71
4.5 Usta - Hoavan teszik ádáthatóvá a kaDCSOS Zlir6ie1ek döntési szerkezeteket (bracosü.cpp l /1 4 . 5 program - Bemu tat juk, hogyan t es zik átlátha t6vá /1 a kapcsos zárójelek az if múve l eteinket . Hi nclude
0,
1, 2, 3.
i nt main()
4,
5. 6: 7, 8, 9, 10 ,
int x ; std : , cout « "Enter a nunIDer less than 10 or greatet" than 100 , "; s t.d " cin» x; std " cout« " \n ";
11 : 12 : 13 , 14 : 15 :
i f (x> 10) i f (x > 100)
std : , cout. « else st.d :: cout. «
16 : 17 : 18 :
"More t.han 100 , Thanks! \n " ; / / nem megfelelő helyen "Less than 10 , Thanks ! \n" ;
lévő
else
return O;
Kimenet Enter a number less than 10 or greater than 1 00 , 20 Less than 10, Thanks!
-
A program egy lQ-nél kisebb vagy 100-n{11 nagyobb számot vár. Amennyiben megkapja, úgy megköszöni. De a jelek szeri tlt a programunk nem ezt csiná lja. Ha a 11. sorban igaz a kifejez6sünk, akkor a 12. sorban foly tatjuk a futtatásL Ekkor a kapott s:lám lO-nél nagyo bb. Te nnés:wtesen a 12. sorban is található egy feltérelvizsgálat, ami igaz értéket ad, ha lOO-nál nagyobb a megadott szám. Ebben az esetben a 13. sortól folytatódik ,l program futása. Ha a megadott szám kisebb lO-nél vagy egyenlő azzal, akkor az első feltétel hamis, tehát a 16. sor f"lH Ic. Ha a megadott szám kisebb lO-nél, akkor az alábbi kimenetet kapjuk: Enter a numb er less than 10 or great e r than 100 , 9
A 14. sorban található else ágat a 11. sor feltételvizsgálatához szerettük volna használni, valójában azonban a 12. sor t"eltételvizsgálatához tartozik. Ez bizony egy alattomos logika i hiba a programunkban.
n II. rész· Bevezetés a C+ + programozási nyelvbe A fordító nem jelez fordítási hib,lt, hiszen hibátlan C++ progmmunk, de mégsem csinálja azt, amit szerettünk volna. Ráadásul mindaddig nem is jelentkezik a hiba, amíg 100-nál nagyobb számot adunk meg. Összességében tehát sikerült egy igen faramuci hibát előállítanunk. Maga a kód nyelvi szempontból helyes, ezért fordít ási hibát nem kapunk. Ugyanakkor részben mégis azt csinálja, amit akartunk, vagyis ha valaki teszteini kezdi, akkor el6ször csak az csetek kis hányadiiban tapasztalhatja a hibás ffillködésl. Amíg nem adunk meg WO-nál nagyobb számot, minden a legnagyobb rendben van .
Apropó
Nekünk fontos az átláthat6ság, nem a gépnek
Jegyezzük meg: üres helyeket a saját érdekünkbe érdemes használni, nem a fordító miatt. Afordító nem foglalkozik vele, hogy szép-e az i f utasítás. Olyan formátumot kell használnunk, amely könnyen megérthetö, átlátható számunkra. Persze kicsit bosszantó lehet, ha a programunk nem úgy viselkedik, ahogy azt a behúzások alapján várnánk. A 4.6 Listában javítotluk a hibát a megfele16 helyeken dheJyezell kapcsos zár6 jelekkel.
• Iul..... ,6r6ie101cet f f _
4.8liata - H..... h_náliulc _ (propolbra.os••pp)
o, 1:
2:
/I 4.6 program - bemutatJ uk a kapcsos zárójelek helyes használatát II if utas.itÓssal *.include
J, 4,
5, 6: 7:
B:
9: 10:
int main!) int x; std : : cout « ' En t er u number less tha n 10 or greater than 100 : ' ; std : , ci.n » x; std : : cout « '\n";
11 :
Lf(x>lO)
12 : 13 :
( i f (x > 100) std :: cout«
14:
15: 16 : 17 : 18 :
else
II mos t
std: : cout « return O;
' More tha n 100 , Thanks!\n' ; működik
"Less than 10, 'I'hanks ! \n" ;
19 ,
Enter a number less than 10 or greater than 100 , 20
4. óra • Kifejezések és utesftások 173 ----------------------------~~-=~~~==~-~---- -
A 12. és l;. sorban elhelyezett kapcsos zárójelekkel e1éniik, hogya 16. sorban található else ág most már a 11. sor if-jéhez kapcsolódik, épp ahogy szerettük volna. lia a felhaszná ló 20-al gépel be, akkor a 11. sorban igaz lesz a feltétel, viszont a 13. sor fe ltétele hamis, így nem ÍI.nunk ki semmit. Tökéletesebb lenne a progmffiunk, ha egy else ágat helyeznénk el a 14. sorban, így tájékoztatva a felhaszná l61 :1 hiba okáróL
Apropó
Oefenzfv programozás
A könyv példaprogramjai mindig egy-egy - éppen tárgyalt - példára világrtanak rá. Egyszerűek, hiszen nem állt szándékunkban a felhasználói hibák teljes k örű kezelése, vagyis ezek a k6dok nem "bombabiztosak", Egy professzionális programnál azonban minden hibalehet6ségre fel kell készülni és megfelelöen kell azokat kezelni.
A logikai operátorokról b6vebben Gyakr.U1 nem csak egy reláció igaz vagy hamis mivolt:'íl",J vagyunk kíváncsiak, hanem többre. Példáu l ig
4.2 T6bIúat-LotIlkII ~ Operátor
Jelölés
ES (AND) VAGY (OR) NEM (NO'I)
&& II
Példa kifejezési && kifejezés2 kifejezés l I I kifejezés2 !kifejezésl
Logikai ~S A logikai ÉS két kifejezést értékel ki. Ha mindkettő igaz, akkor az ÉS végeredménye is igaz lesz. Például ha éhesek vagyunk ÉS van pénzünk, AKKOR meg tudunk ebédelni. if ( (x
=~
5) && (y __ 5) )
74 11. rész· Bevezetés a C++ programozási nyelvbe Az előző példa akkor lesz igaz, ha x és y is öttel
egyenlő,
minden más esetben hamis,
Az ÉS kifejezés csak akkor ad igaz értéket, ha mindkét kifejezt:s igaz.
Jegyezzük meg, hogya logikai ÉS kifejezésnél két & jelel kell írnunk!
Logikai VAGY A logikai VAGY szintén kél kifejezést értékel ki. Ha bármelyik igaz, a kifejezésünk végeredménye is ign lesz. Például ha van pénzünk VAGY hiteJkártyánk, AKKOR ki tudjuk fizetni a számlát. Nem szüks{:ges egyszerre a pénz és :\ hitelkártya, persze az scm je lent probJémát, ha mindkclt6vel rendelkezünk. i f ( (x ==5)
II
(y"'''' 5)
)
Ez pélcb ig:lt~ , h~ ~ k:í r az x, akár az y értéke 5. Gyakorlatilag hi! a fordító sosem vizsgá lja az y-l.
llZ
x éltéke 5, akkor
Fontos: a logikai VAGY kifejezésnél kél I jelel írunk!
Logikai NEM A logikai nem igu3t ad, ha a kifejezés hamis és h:lIniS31, ha a kifejezés ig:lZ. if
( !(x =,.5)
)
Ez igazat ad, ha az x éltéke nem öt. Teljesen úgy viselkedik, mintha ezt írnánk: i f { x! =5)
Reláci6k precedenciája A relációs és logikai operátorok kifejezésként viselkednek a C++-ban. l -et (igaz) vagy O-{tl (hamis) adnak eredményül. Akárcsak a matematikai operároroknál, itt is a precedencia határozza meg, milyen sorrendben értékel6dnek ki. Ezt fontos ismerni péld{tul az alábbi utasítás helyes megfogalmazásához: if
( x > 5 && Y > 5 I ( z > 5)
A példában a programoz6 cl6bb az ÉS kapcsolatO{ szerene volna kiértékelni, utána pedig a VAGY kapcsolatot. V:lgy esetleg épp fordítva? Ha például x értéke 3, y és z értéke 10, akkor az el56 értelmezés szerint IGAZ az állírás (z nagyobb lO-nél, ezén az figyelmen kívül hagyja a fordíró x és y értékeit). Ezzel szemben a második esetben HAMIS az ál1ftás. (x nem nagyobb 5-nél, vagyis teljesen min de~,'y , hogya VAGY feltélel teljesül-e).
4. óra • Kifejezésok és utasftások 175
Habár a precedencia határozza meg a kiértékelésl, zár6jelezéssel megváltoztatható a sorrend és az utasítás is álláthat6bb lesz: if
(
(x :>
5)
&&
(y :> 5
II z > 5)
)
A korábbi éltékeket behelycltesítve HAMIS lesz a feltétel, hiszen x nem nagyobb 5-nél, így az ÉS nem lehet IGAZ. Az ÉS-nél mindkét oldalnak igaznak kell lennie. Hiába szeretünk egy ételt, ha nem jó az íze, nem esszük meg.
Apropó
Csoportosflás zár6jelezéssel
Érdemes plusz zár6jelekkel csoportositani a kifejezéseket. Ne feledjük, a cél egy olyan program létrehozása, amely működik , könnyü olvasni és átlátni. Bővebben
az igazság természetéről
C++-ban a O reprezentálja a hamis értéket, minden más pedig igaz logikai értéknek felei meg. Számos C++ programozó ki is használja ezt az i t-es szerkezeteiben, Íme erre egy ~szép" példa: II ha x igaz (nem nulla)
i f (x)
x
O;
=:
EZl tehát valahogy úgy ken olvasni, hogy "ha x értéke nem nulla, akkor állítsuk nullá!"'",", Itl persle kicsit csaltunk, és valójába a következől al:lkot kellett volna használnunk: i f (x ! = O) x =: O;
II ha x nem nulla
Igazából mindkét megoldás helyes, de az utóbbi talán könnyebben átláthatÓ, Érdemes inkább logikai ki€=nékel€=sre használni a CH ezen tulajdonságát, semmint egy változó nem nulla én€=k€=t ellenőrizni vele, Az alábbi két sor jelentése szintén azonos: if if
( !x ) (x
=:=:
O)
II ha x hamis (nulla) II ha x nulla
A második utasítást ezzel együll va lamivel könnyebb megérteni és hiszen sokkal kifejezőbb .
7s l l. rész • Bevezetés a C+ + programomsi nyelvbe
Kérdések és válaszok Kérdés: Mié,-t haszlláljakfe!esleges ziú'Óje!ezést, lia úgyis a precendia határozza meg az operátorok kié,tékelésél? Válasz: Noha 3 fordítóprogram természetesen ismeri a precedenciáját és a progmmozó megtal111lhatja azt, megfele16 zárójelezéssel azért sokkal könnyebben tarthat juk karban a programjai nkat Kérdés: Ha a relációs operátorok él1éJ.>e l-et llagy O-át ad, a t6bbi éltéJ.'Cl lIlié/t tekillfji/k igaznak? Vú/asz; Relációs operátorok l-et vagy O-ál adnak vissza, de minden kifejezés ad vissz:! értéket, így akár az if ut:lsításban is ki én(! kelhelő. íme erre egy példa: if (
(x .. fl
...
b) =" 35 )
Ez egy szabályos CH utasítás_ A bels6 zárójelben szere pl ő hozz::'irende1és még akkor is lefut, ha maga a feltéle1viugálat hamis eredményt ad. Kérdés: II tabuláto/; a szóköz és az 1íjsor milyen. hatással V(lIl a p rogral11unkra? Válasz: A tabulátor, a szóköz és az újsor - ezeket nevezzük üres karaktereknek - nem befolyásolják a program múködésél, csupán olvashatóbbá teszik a forrásk6dllnkat. Kérdés; A negatív számok igaz vagy h(,llllis logikaI é/1éket jelentel/ek? Válasz: Minden nem nulla szám - legyen az pOZitív vagy negatív - igaz értékként viselkedik.
Gyakorlatok MOST, hogy már rudunk egyet s mást a kifejezésekr6l és az utasításokr61, válaszoljuk meg a kvízkérdéseket és a !"'Yakorlatok segítségével mélyítsük el a megszerzett tudást.
Kvfl l. Mi a különbség az XH és a HX között? 2. Melyik más nyelvekben gyakori operátor hiányzik a CH -ból? 3. Mi a különbség a 001- és jobbérték között? 4. Mit csinál a modlllo operátor?
4. óra • Kifejezések é, uta'fiá,ok! 17
Feladatok 1. Írjunk egy programot, amely a három tanult módon (a "'3+1, 3+ =1 ét; aH) növeli egy változó értékét. Fordítsuk [e, linkeljük és futtassuk. Más leLt a méretük? Észleltünk a futási sebességben különbséget? 2. Gépel jünk be egy matematikai kifejezést az integrált fejlesztői környezetben. Pozicionáljuk a kurzort a valamelyik matematikai operátorra és nyomjuk meg az Fl billentyűt. Mennyire hasznos a kapott információ? 3. Írjunk egy programot, amiben egyszerre növelji.ik prefix és pOSlftx módon egy
változó értékét egy lépésben (++x++). Lefordul? Van értelme? Mi történik, ha egyszerre használjuk a növelést és a cs6kkentést?
Válaszok a kvfzkérdésekr. 1. Az első ut6tagkénl való (posrfix) növelés. Ekkor e l őbb kiolvassuk
5.
ÓRA
Függvények I,\ir6llesz szó ebben az órában: • • • •
Mi a függvény és milyen részekból áll Hogya n deklarálhatunk és definiálhatunk függvényeket Hogyan adhatunk át paramétereket egy függvénynek Hogya n adhatunk vissza értéket egy függvényb6!
Mi is az a függvény? llggvény ahtpvct6cn egy alprogrdlll, amely bizonyos Iln1veleteket tud végezni adatoés e nnek eredményeként egy értéket tud visszaaclni. Minden C++ programban van gaJább egy függvlmy, nevezetesen a main () . Ez az a pont ugylmis, ahol a program ~g:rehajtása elkezcl6dik. A main () természetesen maga is hívhat más függvényeket, az általa meghívott fiiggvények is hívhatnak újabbakat, és így tovább. 'o
'flden függvénynek egyedi neve van a programon belül. Ha a végrehajtás elérkezik :: ilyen névhez, akkor a múve1el'iOfOn egy elágazás keletkezik: a végrehajtás a függ_~n leín e l ső művelette l folytatódik, majd innen egészen addig tart , amíg el nem
I
III I. rész· Bevezetés a C++ programozási nyelvbe ér egy return pa rancshoz, vagy a függvény k(x1jának végéhez. Amikor a függvé ny visszatér a hívóhoz, a végrehajtás a függvé nyhívást követő els6 parancson folytatódik. Ezt a végrehajtási sémát az 5.1. ábrd szemlélteti. Egy jól megtervezett függvény egyetlen, jól körülhatárolható feladatot hajt végre. Ez röviden aZl jelenti, hogya jó függvény egyetlen dolgot tud, de azt nagyon jól, aztán ha ezzel végzett, akkor visszatér a hívóhoz. Az összetett feladatokat előbb fel kell bontanunk egy-egy függvt:ny formájában megvalósílható alfelacbtokra, aZI,ín ezeket az elemi függvényeket keH cgyenkér1t a megfelelő sorrendben meghívni. Ez a tervezési módszer a kódot könnyebben érthetővé és "igy könnyebben k:lrbantanh,lIóvá teszi. Ebhcn a könyvben nagyon gyakran fogom emlegetni a könnyű karbantanhatóságoL Ennck az az oka, hogy - amint azt korábban is említeltem - rmmapság a progr::lmfejlesztésben az igazán nagy köIL<;{:get nem is ;mnyira az alkalmazás megírása, hanem annak a karbantartása, folyamatos frissítés€: jelenti.
Main O { UtlsfIM;
tunel
tunel (): Utasités: func2
O;
IJI.IsItH;
lune4 (): } Utasitás:
5.1. ábra AmiJ.'Or a program e,gy j/lggocnyhílXjs/lOZ érkezik, a végl"l!Ilajtás a jl1gg/Jélly kódjállak e{só sorára ugrik, ml/jd a lli.sszatéTris utáll a függ/Jéll)'lIíuás Iltálli első so"al folytatodik.
Itt szeretném föl hívni a figyelmet arra az eg{:sz egyszen1 megállapításra is, mely szerint a kódolással kapcsolatos problémák igen nagy hán}'"".Jda egyszerű stiláris probléma (ilyen például a sorkezdetek megfelelő ménékű beljebb wirisa). Maga a fordító ugyan nagyon hajlékony, de könnyen olvasni a szépen szerkt::sztett k6dot lehet.
5. óra • Függvények I SI
A függvények deklarálása és definiálása :ld6u meghívharnánk egy függvényt,
előbb
deklaráln i majd definiálni kell azt.
függvénydekhuáci6ban megadjuk a fordítóprogramnak a kérdéses függvény nevét, t:>SZatérési értékének, valamint bemenő paraméte reinek típusát. A függvény deklaráci~ szokás prototípusnak (prototype) is 1úvni, mivel ez csak egy formai leírás, ami fUggvény törzsét ne m tartalmazza. defuúció ezzel szemben azt í rja le, fiÚként kell il függvénynek működni e, milyen mú-eteteket kell végrehajtania . Ahhoz, hogy egy függvényt bármely más függvényb61 .eghívhassunk, legalább deklarálnunk kell azt va lahol az első hívás el őtt, ám m~. gá l -.-égrehajtandó kódot csak a dcfinídó tartalmazZa.
Fiiggvények deklarálása , rordít6progmmmal maga a gyártó is szállít számos e l őre megírt f'Üggvényt. Ezeknek • o.1eklariiciÓi természetesen szintén készen vannak azokban a bizonyos fcjlécállomáT.'Okban, amelyeket az ti nc1ude paranccsal mdunk beemelni a saját k6dunkba. Egy füru,>vény prototípus:! egy olyan pontosvessz6vel végz6d6 utasítás, amely t:lrtal1ll3zza a függvény nevét, valamint visszatérési érté ké ne k típusá t és bemenő par.:unétereinek listáját. A paraméte rlista - elnevezésének megfel elően - ,I bemenő par
paramétertrpus / uoslgoed short iot
"Y/éter neve
FlndArea
(lot length. int width) ;
'--VJ VIsszatérési érték típusa név
~I
~
paraméterek
pontosvessz6
•.2. ábra q:yfriggvimydeklanici6 részei
.\ függvény definíciójának és deklaráci6jának pontosan meg kell egyeznie a név, és felsoroll paramétertlpusok te kintetében . Ha nem ez a helyzet, akkor fordításkor hibát .bpunk. Érdemes ugyanakkor megjegyezni , hogy egy függvény prototípusának nem
2
82 11. rész· Bevezetés a C+ + programozási nyelvbe föltérlen kell tanaimaznia a bemenő paraméterek neveit, csak a típusok megadása kötelező. Ennek meg felel ően a következő függvénydeklaráció teljesen helye~: long FindArea{int, int) ; Ez a protOTipus egy FindAread ( ) nevű függvényt deklarál, amely long t!pusú é rtékkel tér vissza, bemenetként pedig két egész paraméter v;ír. Bár a függvény ilyetén megadá~ sa szintaktikailag he lyes, ne m valami jó öuet, hiszen ha megadjuk a paraméte rek neveit is, az adott esetben sokkal világo~abbá teheti , hogy pontosan miről is van szó. Egy szintén helyes de átgondoltabb de klaráció tehát a következ6 képpen nézhe t ki: long FindArea(int lcnqth . int widthI ; Így sokkal egyértelmabb, hogy mi is ez a függvény, és milyen paramétereket kell ne ki megadnunk milyen sorrendben. Figyeljük meg, hogya függvé nyeknek a legtöbb esetben van v is~zatl:rési énékü k is. Az 5.1 Listában bemutatunk e gy olyan programo t, amely hivatkozik a Fi ndArea () függvény pro to típusára.
I o : /1 Az 5 .1. Lista a függvények prototipusának használatát szemlélteti 1 : linclude 2,
3 : inL FindArea (int length, int width) ; II prototipus deklarálása 4,
s : int main() 6, 7: int lengthOf\'ard ; int wiclthOfYélrd ; 8: 9: int areaOf'lard ; 10 : 11 : std : :cout « "\nHow wide is your yllrd? ". 12 : std : :cin » widthOfYard; 13 : std : :cout« '\nHow long is your yard? "; 14 : std : :cin » lengthOfYard ; 15 :
16:
arcaOfYard:
Find~rea(lengthOfYard,widthOfYard) ;
17 :
18 : std : :cout « '\nYour yard is ' ; 19 : std : : cout « areaOfYard : 20 : std :: cou t « " square feet\n\n" ; 21 : return O; 22 : 23 : 24: int FindArúa(int l, int w) 1/ function definition 25:
26 : 27 :
roturn l ..
WI
5. óra • Fúggvény.k
183
How wide is your yard? 100 How long is your yard? 200 Your yard is 20000 ~quere f eet
A FindArea () függvény prototípusát a 3. sor tartalmazza. Összehasonlítva ezt a 24. sorban kezd6dő dcfinfci6valláthatjuk, hOh'Y mind a név, mind II paraméterek típusa megegyezik a két helyen. Ha
különbözők
lennének, fordítási hiba kelelkezne. Ami azt illeti, formai szempontból
nincs sok különbség a deklaráci6 és a definíció között. Az el6bbi nek pontosvessz6vcl
kell végződnie, és nem tartalmazhat ja a függvénytörzsct, cic amúgy mindenben egyezik az utóbbival FigyeljLik meg, hogy II prototípusban a length és a width neveket adtuk a bemen6 paramétereknek, a definíció azonban már az l és w nevekkel dolgozik. Ebből világosan látszik, hogy a fordító semmire sem használja a deklal"ációban megadott neveket. Ezek csupán a programol6 számára jelentenek h
Függvények definiálása Egy függvény definíciója két részre osztható: tartalmaz egy fejlécet és egy törzseL A fej· léc rulajdonképpen nem egyéb, mint a deklaráció elismétlése. Annyi különbség persze azért van, hogy itt már kötelező megnevezni a paramétereket, illetve nincs a sor végén lezáró pontosvessz6.
84 11.rász· Bevezetés a C++ programozási nyelvbe A fü ggvény törzse vagy teste a végrehajtandó utasítások sorozata, amit kapcsos ~dróje lek között adunk meg, /\ :I fejléc és a függv (;nyrörzs szerkczetét szemlélteti az 5.3. ábra .
visszatérési érték típusa
paraméterek
~
~
unslgned short InI
(inIlengIh, inI wId1h)
nyitó kapcsos zárójel I/Utasftások relum (length. wIdIh);
\kUICSSlÓ~
visszatérési érték
záró kapcsos zárójel
5.3. ábra /igy fl188V1'}II), fcjlt."ociilwk és ItJrzséllck szeM'Zcl
FOggvények megadása: összegzés ... Egy függvény prototípuSlI ami való, hogy által mt:gmondjuk II fordítóprogramnak , mi a függvény neve, milyen típusú értéket ad vissza, és milyen líplISÜ bemenő adatoklH vár milyen sorrendben. Ez fo rmaitag a köverkez6képpen fest: visszat6réai_tipus faggvény_neve( [tipus [paraméter_nevelI ... ) :
A függvC:ny definki6ja ezzel szemhen azt tudatja a fordítóprograrnrnal, miként kell az adOlt függvénynek működnie. A formai leírás a következőképpen fest: visszatérés~típus
ftlggvény_neve( [tipus [paraméter_nevcll ...
(
utasítások;
Bár a függvény prototípusa a visszatérési énéket, a nevet és a paraméterek listáját adja meg, nem kell minden függvénynek paraméterekkel rendelkeznie, illetve ha vannak is neki paraméterei, a deklarációban akkor sem kötelező felso rolni ezek neveit, csak a lípusukat. A prototípus mt:gadása mindig pontosvessz6vel végz&iik. A függvény ddiníciójának meg kell egyeznie a deklarációval a visszatérési l:nék és a bemenő pamméterek típusában . Meg kell neveznie valamennyi para mélen, a függvény törzsét pedig kapcsos zár6jelek közé kell zárni. A függvény törzsében szereplő
5. valameflllyi utasítást pontosvessző követ, de magát a függvényt nem zárja ilyen jeL A definíció végét a záró kapcsos zárójel jelenti. Ha II függvény ad vissza valamilyen értéket, akkor a törzsében az utols6 végrehajtott utasítás egy r e turn lesz. Ez az utasítás amúgy a törzsben bárhol felbukkanhat, vagyis nem kell a sz6 .'i7.Oros értelmében az ulolsónak lennie. Minden függvénynek van visszatérési típusa. Ha a függvény egyáltalán nem ad vissza értéket a hív6nak, akkor visszatérési típusa v oid. Nézzünk néhány példát függvények prolotipusának megadására: long FindA rea (lo ng l ength, l ong width ) . II l ong értéke t ad vinn za és II két bcmcn6 paramétare van void PrintMessage (int messageNumber) ; / I nem ad Vi::Hi z a 6rtéket , egy II bemen6 paramétere van int GetC hoice ( ) ; II agész érték e t ad vi!;5za , ni n cs b e mc n 6 p u ramétere BadFun ction(} ; II egés z értéke t ad vi ssza , n incs bemen 6 p a ramétere
:-;ézzünk né hány példát függvé nyek meghall'íro zásám is: long Az:-ea(long l , long
w}
(
return l
• w:
void Pr intMessage(int whichMsg) {
i f (whichMsg
::
o)
atd : : cout « " Hello . \n "; i f (whichMsg -- li std : : cout « "Goodbye . \ n "; i f (whichMsg > II atd : : cout « "I ' m confuaed . \n " ;
Hogy hány és milyen típust] utasítás lehet egy függvény törzsében, arra gyakorlatilag nincs semmiféle korlát. Ugyanakkor a jól megtervezett függvényck álmlában rövidek. II legtöbb tulajdonképpen nem hosszabb néhány sornál.
Változók hasmálata függvényeken belül A változók függvényen belüli kezelésének több különböző módja is van. Amint már többször említeuük, vannak a bemenő paraméterek, amelyeket a hívó ad át a fü ggvénynek. Ezek mellett magának a függvénynek is lehet saját változókészlete, amelynek a tagjai a függvényre nézve lokálisak, és megszlÍnnck létezni a törzsön kívül. Ugyanakkor a függvények használhatnak globális, vagyis a program egészéból e1érhetó változókat is.
85
86 11. rész • Bevezetés a C+ + programozási nyelvbe
Helyi vá~oz6k Egy függvénynek nem csak kívül ről adhatunk át paramétereket, hanem a törzsén belül is deklarálhaul11k újabb v;íltoz6kal. Ezek a lokális vagy helyi válLOzók, amelyek - ne· viiknek megfelelően - csak a függvényen belül léteznek. H<1 a függvény visszatért a hí· vóhoz, ezeknek a változóknak a tartalma elvész, azokhoz a továbbiakban semmilyen módon nem lehet hozzáférni. A lokális változókat pontosan úgy kell deklar:1lni, mint
5.2 usta - ...,..."".... ás helyi o : linclude
ri_
""""' =;..Iata ;..;...:;. lloca ;..;..Ivo _ ,_.cpp = , _ _ _ _ _.....
"
2 : float Convert (float.) ; 3, 4 : inL main() 5: {
6, 7, 8, 9, 10 : IL
float 'I'empFer ; flo a t TempCel ;
std : : cou t « " Plea~e e n ter the ternp en~ tu re i n Fahre nhe i t : "; std : : ci n » TempFer : TempCcl = Convert(TempFer) ; std : : cout « "'nHere's the temperature in Celsius : . ; std : : cout « 'I'empCel « std : : endl; return O;
12 : 13 : 14 : 15 : 16 : 11 : float Convert(rloat TempFer) 18 : {
19 : 20 : 21 :
float 'I'empCel : 'I'empCel = «('I'cmpFer retur n TernpCe l;
32)
* 5)/9 ;
22 :
Pl ease e nt er th, t emperature in Fahrenheit: 212 Here's the temp erdt.ure in Celsius : 100 Please enter
5.6ra • függvények I S7
..... kimenet a program három egymás utáni futtalásának eredményét mutatja. Ez egyes fu natások során rendre a 121, 32 és a 85 értékeket adtuk meg neki bemenetként. A 6. 6 -. sorban egy·egy float IÍpusú változ6t deklar:álunk, melyek közül az egyik a Fah· ~eitben kifejezett h6mérséklelet tárolja, mig a másikban ennek a Celsius-skálán ki· fc:,ezen értéke ta lálható. A felhasználótól a 9. sorban bekérünk egy f ahrenhcitbcn kire>ezen h6mérsékletet, majd ezt az értéket átadjuk a Convert () függvénynek. .-' Yégrehajlib ekkor a Convert ()
első
sorára, vagyis a 19. sorra ugrik, ahol ckkhm'í-
IUnk egy TempCel nevi! helyi v{lltoz61. Bár egy ugyanilyen n cvű vfl ltm:6 már a 7. sor'.ln is szerepelt, a kettő nem azonos, A most deklarált TempCel ugyanis :\ Convert ()
rugg\'ényre nézve 1ok{llis, v'lgyis csak eze n bclLillétezik. A param6lerk6nt lÍTndott -.;.~pFer vá ltozó szinl(:n lokális, v;\gyis csupán m5solnt a main () függvényben hithaló ruggvényhívásnál áwdotl (:rtéknek. A függvény par.:lméterl:t ennek rnegfelel6en hívharruk volna akár FerTemp-nek is, :1 he-
nl . .iltoz6t pedig Cel Temp-nek, ugyanolyan tökéletesen ml1ködne. Aki nem hiszi, akár -nódosíthatja is a kódot, és rncgnézheLi, hogy lefordítás után ugyanúgy ml1ködik-e . .\ helyi TempCel változ6b;1 egy olyan énék kerül, ami úgy keletkezik, hogya pnr.lmé~dként átadott 'remFer i!nékl:b61Ievonunk 32-t, az eredményt megszorozzuk 5-tel, "'I'U;d elosztjuk 9-cel. Ezt az énl:ket aztán vissz
S.3 Lista - Holvlrilm6k 60 DORIIIIétorok haunélata 6ov. ho.. a malnll60 a Convertll fGggrinyak rilloz6lt .... ugyanúgy hlvják (locaIvor2.cpp) ' include _ . float Convert (float) ; ~ :
int main () (
float TempFer;
sa lI. rész · Bevezetés a C++ programozási nyelvbe 7: 8, 9:
10 : ll :
12 : 1] :
14:
fl oat TempCel ; s td : : cout « • Pleas e ente r the t emperatur e in Fahrenheit : .; std : : cin » Temp Fe r ; Te mpCel = Convert( TempFe r) ; std : : c o ut « • \ n Here ' s t h e t empcra Lure i n eel sJ us : . ; s t d : : cou L « TempCe l « std :: end l ; ret u rn O;
15 : 16 : 17 : float Convert (float Fer) IS : { 19 :
20 : 21 : 22 :
float Cel ; Cel" ({Fer - 32) r eturn Cel ;
• 5) / 9 ;
l Ia minden iga z, ugyannt az ered ményt kell kapn unk , mint az iménL Minden változónak van érvé nycsségi köre, ami meghatározza , hogy mikor és a program mely része in hozzáf(:rhet6. Azok a változók, amelyekct egy programblokkon belí.il ad unk meg, arra a blokkra nézve lokálisak. Csak ezen a k6drészleten belül hozzMérhcl6k, ha pedig a v~grehajtá s kikerült innen, akkor egyszen.1en megsz\1nnek létezni. A globális változó k ezzel szembe n a program bármely ponljáról houáférhet6ek.
Apropó
Ahol csak akarodl
Új változ6t bármely programblokk bármely pontján dsklarálhatunk. Ha úgy akarjuk, akkor akár egy i f -es szerkezet kapcsos zár6jelei között is. Ugyanakkor ne felejtsük el, hogy ezek a változók csak az adott blokkon belül léteznek.
Globális vá~ozók Azok rt változók, amelyeket minden függvényen kivü l dekla rállunk globálisak lesznek, v
5. óra • FQggvény.k 89
A függvények argumentumai Egy függvény argumentuma inak természetesen nem kell azonos típusúnak lenniük, Ha éppen arra van szükségünk egy probléma megoldásához, akkor teljesen ésszerű az a függvény is, amelyik mondjuk egy egészet, két long típusú paramétert, meg egy :-\;J.raktert vár !:>emenetként. Fúggvényhívflskor argumcnlumké nt bármely a C++ nyelvben értelmes kifejezést meg.dharunk. Ebbe bcll:tartoznak :t konstansok, a matematikai és logikai kifejezések, illel\-e egy bármely mis függvény ;Utal visszaadott érték is. Tegyük fel például, hogy van eg}' li1ggvényünk, melynek a deklarációja a következőképpen fest: nt MyFunctioll(int thclntegerParam. bool theBoolean) ;
Ezt számos különbö7.6 módon hívhat juk meg, amely le het6sC:gckb61 i l l csupán hármat mutatunk be: _nt
II deklaráljuk az argumentun,ként ha9znál t /I változ6kat : " MyFunction(x,y); II egy int és egy bool változ6t adunk át MyFunction (32, true) ; II két álland6t adunk át ;: " MyFunction(23.9, tOO>5); II a:;o: els6 érték 32, a másik igaz :;0: ,
X
,.
3 , y " 5:
\z utolsó hívás kirnenelelél lekinlve leljes<::n megegyezik a másodikka l. Végezetül T".Jk 3 kövctkez6 két függvényt:
n ~z
_:lt MylntFunction(int x, int y): 001 MyBoolFunction(int x, int y): :\
~ Function- l
ezek után akár a kövctkez6képpen is meghívhatjuk:
: " MyFunction(MyTntFunction13 , 5) , MyBoolFunction(2,4»: I:t rulajdonképpen az történik, hogya MylntFunction(J,S) hívás által vissz:1:1doll ~ész . és a Myl3oolFunction (2 .4 ) hívás á ltal szolgáltatott logikai értékel adjuk át p3!""l.méterként a MyF'unct ion függvénynek.
függvényhfvás mint paraméter B.:ír nyelvileg teljesen helyes, ha egy függvény paramétereként cgy másik függvény ál:3.l \·iSS7..aadott értéket használunk fel, a7.ért a 7. igazat megvallva ezz<::! a m6clszerrel elég ,rusza kódot lehet alkotni. Az ilyen progf"".J.mat aztán ig<::n nehéz nyomköveIni és kar:Unrartani. Példaként tegyilk fel, hogy a doubleJ: (), tripleJ: (), squaJ:e () és J. :::ube () függvénye k valamennyien egy-egy értéket adnak vissza. Ebben az esetben =\-elvileg teljesen helyes a következő függvényhívás: .'_""C.5wer " (doubleJ:(tripler(square(cube(myValuc ) ) ) :
90 II. rész· Bevezetés a C++ programozási nyelvbe Hanem hogy itt mi is történik pontosan, azon az ember kénytelen kissé elgondolkodni. Vesszük ugyebár a myValue változó él1ékét és ezt paraméterkém átadjuk a eube () függvénynek. Ennek :l visszatérési értékét megkapja argu mentumként a square () nevú függvény , amely szimén kisz.'lmol valamit. Ezt a vala mit kapja bemenetkém a tripler () függvény, amelynek visszatérési értékél végül a doubler () dolgozza fel. Az. Answer tehát a myValue köbe négyzetének megháromszorozott majd megkét5zerezett értékér kapja ért(:kül. Mindezt nem csak leírni nehéz, hanem átgondolni is. EIs6 látásra például sokak nem lesz triviális, hogyelabb szorozl\mk hárommal és csak aztán emeltünk négyzetre, vagy éppen fordítva. Ha pedig hibás a végeredmény, ember legyen a ta lpán, aki megmondja, hogy a négy függvény közül V'djon melyikben kell keresni a hibát. Sokkal átl{lthatóbb, ha létrehozunk néhány átmeneti váltOZÓI, és a lépésre kljLjuk végre, valahogy így: unsigned unsigned unsigncd unsigned unsion~d
long long long lon g long
myValue = 2; cubed = cube(myValue); II squared = square(cubed) ; II tripled .. tripler (squured) ; II Answer = doublerltriplcd) ; II
műveleteket lépésről
cubed • 8 squared ol 64 tdpled = 192 Answer : 384
Ezzel a módszerrel lehet6ségü "k van ci , hogy szükség esetén b5rmely köztes eredményt mcgvizsgftljLlnk, és azo" sem kell gondolkodni, hogy milyen sorrendben hajtódtak végre
A paraméterek egyben helyi vá~oz6k is A függvl:nynek átadOft argumentumok a továbbiakban lo kálisak a filggvény re nézve. Ez azt jelemi, hogy :l rajtuk végzen változtatásoknak nincs semmiféle hatása a fliggvényen kívüli kódra. Ezt a szabályt a programozók érték szerinti átadásnak (passing by value) nevezik, ami tehát szemlélctcsen azt jelenti, hogy a függvé ny saját argumencumainak csak a másolatával dolgo:lik, nem az ,eredeti példánnya l". A helyi másolatokat aztán a függvény k6djában már ugyanúgy kezeJhetjük, nlinl bármely más helyi válto· zót. Az 5.4 List.-'lban bemutatoll k6d ezt a sz:tbályt hivatott bemutatni.
5.4 UIIII- Az _ _ 6Iodá
o:
-1,"'IIy".,."",
II 5 .4. Lista ~ Az érték szerinti átadás szeml éltetése l : #include 2. 3 : void swap(int x, int y); 4.
5: int main () 6: { 7. int x = 5 , Y
10;
5. óra • Függvény.k 191 std : : cout «
B. 9. ~O :
swap(x,y) ;
ll:
std : : cout «
:2 : :3 :
return O;
"Main . Before swap, x : « y:
«
x y «
'\n' ;
'Main. After swap, x: • « x « • Y' • « y «
'\n" ,
<<:
:4 : :5:
:6 , void swap (int x, int y) :"7:
:9 ,
int temp: std : : cout «
20 : 21 :
temp = x,
:8 :
22 :
x = y;
23 : 24 : 25 :
y
« «
x y «
' \n ';
·Swap . After 9wap , x : " « x « • y: " « y «
• \n' :
= temp ;
std : : cout «
-
·Swap . Before swap, x : « • y:
26 :
!{ain . Before swap. x: 5 y : 10 S;;ap . Before swap. x: 5 y : 10
:wap . After s wap. x : 10 y : 5 ".ain . After s wap . x : 5 y : 10
Ez a program a main() filggvényen belüllérrehoz kél válloz6t, majd argumentumként mdja azokat:! s wap () függvénynek. Ez - nevének megfe l e l ően - felcseréli a két érté..e{. Ami kor 3zo nban a main () függvt:nyhez visszatérve megvi7..sgáljuk a helyzetet, azt tapasztaljuk, hogy semmi sem változott, \-áltozók a 7. sorban kapnak értéket. Ezeket a 8. sorban meg is jelenítjük a képerr:;-6n, aztán a 9. sorban meghívjuk II s wap () függvényt, amely megkapja ők et bemenetkém .
.J,.
program végrehajtása tehát a s wap () k6djával fo lytatódik. Itt a 19. és a 20 , sorban iskiíratjuk a kél értékct, amelyek cb'Yelőre ugyanabban a sorrendben vannak, mint ~etileg a ma in () függvényben, A 21-23 sorokban megtörténik a csere, amit a 24, és :--. sorokban egy újabb kiíratással ellen6rzünk. Látható, hogy amíg a swap () kódját) bdül . tartózkodunk", addig a két érték tényleg helyet cserél.
•J,.
~
fu_!lem a füru"",ény végrehajtása után a program a 11. sorban folytatódik, ami már isr:.er. a main () része, és a két 6rték megint a régi . Semmi nem változott.
92 11. rész· Bevezetés 8 C++ programozási nyelvbe Mindebból világosan látszik, hogya swap () függvénynek átadott két változó átadása csak érték szerint törtém meg, vagyis a függvény két másolatot kapott, amelyek a swap () ~m nézve lokálisak. A 2] -23 sorokban igazából ennek a két helyi változónak az értékét l'seréltük meg, amelynek így a külvilágra semmiféle hatása nincs. A main ( ) továbbra is az eredeti áll apotol látja. Egy későbbi fejezetben be fogunk mutatni egy o lyan megoldást is, amivel a ma i n () számára is fclcserélbet6 a két érték egy mégpedig egy függvényhívás segítségével.
Értékek visszaadása függvénvból Egy függvény v.~gy egyetlen értéket ad vissza, vagy a visszatérési típUS<1 void. A vo i d kulcsszó igazából csak egy jelzés a fordít6prograJtUlak, amely arra utasit ja, hogya kérdéses függvényb61 nem kívánunk semmiféle értéket visszaadni. lia egy függvényen belül értéket szeretnénk visszaadni , nincs más dolgunk, mint a return kulcsszó után leírni a visszaadni kivánt változó nevét, egy kifejezést, vagy cgy konkrét értéket. fme néhány példa: return 5 ; return ( x > 5) ; retur n (MyFunction cl) ; rdtéve hogy a MyFunct ion ( ) olyan függvény, amely maga is ad vissza értéket,
mind~
három femi művelet helyes. A második esetben (x>s) a hfv6nak vissza,ldorr érték ha-
mis lesz, ha x értéke nem nagyobb mint S, eHenkező esetben pedig természetesen igaz. Ügyeljünk rá, hogy ilyen esetekben a kifejezés értéke adódik vissza, nem pedig az x változó tartalma. Amikor a prognlm elérkezik egy r eturn utasításhoz, akkor a hívó fél visszakapja az utána megadott kifejezés l:rtékét, a végrehajtás pedig a függvényhívás utáni soron folytatódik. A r e t urn után szerepl6 műveletek ilyenkor egyáltalán nem futnak Ic, V;lgyis a r et urn ga ....,lntáltan a függvényből va ló kilépést jelenti. Ez egyben:lZt is jelenti, hogy egy függvény kódja több r eturn utasítást is tartalmazhal különböz6 helyeken. Csak azt nem szabad elfelejteni, hogy amint a program ;lZ els6 return utasítást eléri, a rüggvénynek vége. A több return utasítás haszná lalát szemlélteti az 5.5 Listában bemutatou kód.
5.5 lista - T6bb lIlIUm ....Itú egyeUen fOggvHyben !manyqm..... ppl
o: II 5. 5 . Li s t a - Töb b r eturn utas i t ás egye t l en füqgvényben 1: Il s tatcments 2 : tinc 1ude 3, 4 : i nt Double r (int Arno untToDouble ) ;
5. óra • Függvények 193 5: 6 : int main() 7:
(
int r esult = O;
3: 9:
int input ;
:0 :
std : : cout « 'Enter a number betwee n O and ' « " 10 , 000 to doub l e : ' 0 std : : cin » input ;
:1 : :'2 : :3:
..
--' .
:'5 : :6 , :,7 , :'8 , :'9 , 20 : 21 : 22 : 23 : 2~ :
std : : cout « ' \nBcfo r c doub l e r is c a lled . . ' ; std : : cout « ' \ninput : • « inp u t « • doubled : • « resul t « ' \0 ";
25 :
ret urn O;
result .. Doub ler(inpu t ) ; std : : cout« std : : cout « «
" \nBac k f rom Do ubl e r ... · ; " \n i np u t : • « input doub l ed : ' « res ult « ' \0\0" ;
26 : 27 :
2B : int Doubler (int original) .;9 :
-O : ~l :
{
i f (original <= 10000)
rcturn original ' 2 ;
32 :
else
"] ,
return - 1. std :: cout « "'(ou can't get here ! \o' ;
~4 ;
- 5:
Enter a number bctwccn O and 10 , 000 to double : 9000 Before doubler is called .. .
• oput : 9000 doubl ed : O Eack trom doubl er .. . : nput : 90 00 doubled : 180 00 :::nter a number bc twccn O a nd 10 . 000 t o dou bl e : 1100 0 3efore doubler is ca l led . , :nput : 11000 doubled : O 3ack from doubler ... : nput : 11000 doubled : -l
94 11. rész • Bevezetés a C++ programozási nyelvbe
A 11-13 sorokban bekérünk a felhasználó ró l egy számot, majd a 16. és 17. sorokban kiírjuk azt az eredmény tárolásárd használt helyi váltoroval együn. A Doubler () függvényt a 19. sorban hívjuk meg, bemenetkém pedig a fel haszná lótól bekért értéket adjuk át neki. Az eredmény a már említe tt result neV1.1 helyi változ6ba kerül, majd az értékeket a 21-23 sorokban ismét kiíratjuk. A 30. sorban - amely a Doubler () függvény k6djához tartozik - megvizsgál juk, hogy a paraméterként átado tt érték nem nagyobb-e mint 10000. Ha nem, akkor a függvény a szám kétszereséveltér vissza. I-Ia azonban a megadott szám tízezernél nagyobb, akkor a visszatérési érték -1 lesz, amit jelen esetben hibak6dnak is felfoghalunk. A 34 . sorba n l{uhat6 kódra a végrehajtás soha nem ke rü lhe t, mivel akár nagyobb ;j paraméterkém átadott ért6k mint 10000, akár kisebb, a függvény a 3"1. vagy a 33 . sorban mindenképpen visszatér a hívó fé lhez, vagyis a 34. sorig gar.mtá lt.an soha nem jut el. Ez annyira így V' In , hogy egyes fordítóprogral1lok az ilyen helyzetre még Ogyelmeztet6 üzenetet (warning) is küldenek.
Apropó
Fordft6i ilzenetek a manyretums.cpp fordftása közben
A manyreturns. cpp program fordftása során egyes a következő hibaüzenettel találkozhatunk: "manyreturns.cpp"; W8066 Unrcachable code in function Ooubler(int) at line 34 "manyreturns. cpp ": w8070 Function should retum a value in function Doubler(int) at linc 35 Az első üzenet arra figyelmeztet bennünket, hogy az s td : : cout művelet soha nem hajtódik végre, mivel a Doubler () függvény mindenképpen visszatér a hfvóhoz. mielőtt elérné azt. A második üzenet szerint a fordítóprogramunk kicsit kényelmetlenül érzi magát a kialakult zúrzavar mian, mivel az utolsó return utasítás után de még a záró kapcsos zárójel előtt talált végrehajtható kódot, és ez határozottan nyugtalanítja. Ezek csupán figyelemfelkeltő üzenetek (warning), amelyek felbuk· kanása azt jelenti, hogya fordítás rendben lefutott, a gépi kódú program elöálIt, ellenben futtatáskor nem biztos, hogy azt fogjuk kapni, amit szerettünk volna. Ez ese· tünkben természetesen nem probléma, mi ugyanis éppen azt kapjuk, amit szerettünk volna: azt akartuk bemutatni, hogy Hügyes szelVezéssel~ eJhelyezhetünk egy programban soha végre nem hajton részleteket.
5. 6ra • Függvények
195
Függvények alapértelmezett paraméterei Ha egy függvény prolorípusában és definíciójában szerepel egy paraméter, akkor a hí,·6 fél köteles ennek a hívás soctin értéket adni. Ráadásul az átadott értékek olyan típusúnak kell lennie, mint ami a deklaráci6ban szerepd. Ha tehát van cgy a következő módon deklarált függvényünk long myFunction{int) ;
akkor annak meghíváskor tnindenképpcn át kell adnunk egy egész értéket, legyen az egy váho7..6 vagy egy áUandÓ. Ha fl függvény definíciója eltér a deklarációtól, vagy a hívás során nem a megfelelő értéket adjuk át, akkor fo rdítiÍsi hiba keletkezik. Ez al6l egyet.len kivétel van , mégpedig ha a függvény prOlotípusában (dekla rki6jában) megadunk egy p:mun(:ternek alapértelmezett értéket. Ez az érték akkor lép érvénybe, ha :1 hívó nem rendelkezik a kérdéses paraméter éltékéról. Ennek ismereté ben az imént bemut.nott cleklarációt a következ6képpen al:tkíthatjuk ál: long myFunction (int x " 50) ;
Ez a dekl:lrádó azt mondj,l, hogy .1myPunction () neVI.Í függvé ny long értéket ad vissza, és egy egész t:néket vár pamméterkénL Ha pedig a hívó ez ut6bbit nem adja meg, akkor II p:lmméler alapértelmezett értéke legyen 50, Mivel :1 deklarációban nem kötelező nevet adni a par< uuétereknek, a fenti sort akár a kövctkez6képpen is írhauuk \'olna: long myFunction
(int = 50) ;
Atl61, hogy :t dekbrád6ban alapéltelmezett értéket adunk meg egy paraméterhez, a függvény definíciója még semmiben nem változik. Esetünkben tehát a rnyFunction konkrét megadása a következ6képpen kezd6dhet: long myFunction ( int x)
Ha a hívó fél nem :tdja meg az x pa r-.J méler értékér, akkor a fordít6progr.un az alapértelmezett 50-es értéket rendeli hoz7.á. Ez továbbra sem jelenti azt, hogy II definicióban és a deklaráci6ban a par.Jméternek azonos néven kell szerepelnie, az alllpénelmezctt énék hozzá rendelése ugyanis pozíció é.~ nem név alapján történik. .-\lapértelmezett értéket megadhatunk egy függvény egyes paramétereihez, vagy akár 3Z összeshez is. Ez egyetlen korlálO?ás ezzel kapcsolatban amit be kell tartanunk az, hogy ha .1 paniméterlistában eb'}' elemnek nincs alapértelmezett értéke, akkor nem lehet előtte olyan paraméter, aminek van, Tegyük fe l, hogy egy függvény deklarációja a
következőképpen
long myFunction ( int Paraml. int Param2,
fesl:
int Param3);
•
96 11. rész· Bevezetés a C++ programozási nyelvbe Ebben az esetben a Param2 pamméLernek csak akkor lehel alapénelmezeu énéke, ha Param3 -nak is adunk. Hasonlóan Par amI-hez csak úgy adhatunk meg ilyen értéket, ha ugyanczt meglcsszűk a másik kettővel is. Az alapértelmezett pammélerértékek használatát az 5.6 listában mutatjuk be.
na.. I o: II 5 . 6. Lista A~ alapértelme~ett paraméterértékek 1 : II használatának bemutatása 2 : .include J, 4: int AreaCube(int length, 5, 6 : int main()
'7 : 8: 9:
int width
~
25 , int hcight • 1 ) ;
11 :
int i nt int int
12 : 13 : 14 :
area" AreaCube(length, width, height ) ; std :: cout« "First time area equals ' «arca«
10 :
length 100 ; width .. 50 : height = 2 ; area : ol
'\n ":
15 :
16 : 17 :
area • AreaCube(length , width) ; std : : cout « 'Second time area equals' «
area «
'\n';
18 :
19 : 20 : 21 : 22 :
arca" AreaCube(length): std : : cout « 'Third time area equals' « return O;
area «
'\n';
23 :
24 : int AreaCube(int length, int width. int height) 25 :
26 :
reLurn (length ' width' height) :
27 :
First area equals : 10000 Second t ime e rea equals : 5000 Third time area equa ls : 250 0
A 4. sorban lálható az AreaCube () függvény prototípusa, mely szerint ez a függvény három egész típusú paramétert vár a hívótól. Ezek közül a két utolsónak a lapértelmezett értéke is van. Maga a függvény egyébként egy téglatest felszínét számolja ki , a paraméterek pedig ennek az élhosszúságait tartalmazzák. Ha nem adunk meg szélességet (width), akkor
5, óra • Függvények 197 az automatikusan 25 Jesz, ha pedig a magasság (height) hiányzik, akkor a progr.lm az 1 értékkel számol, Megtehetjük persze azt is, hogy csak a magasságot nem adjuk meg. Ilyenkor természetesen csak erre fog érvényesülni az l-es alapértelmezen érték, a 2S..öl a függvény nem használja. Ennek a fordítonja ugyanakkor nem lehelSégcs, vagyis nem adhatunk meg magasságot anélkül, hogy meg ne adnánk a szélességet is. A 8-10 sorokban beáilítjuk:t magasságot, szélességet és hos,.o.;zúságot tároló változók h.;idt.h, height, l ength) értékét , majd ezeket paraméterként átadva a 13. sorban meghívjuk az AreaCube () függvényt. Ez kiszámít ja a felületet, amit a 14. sorban kiiratunk a képerny6re. A végrehajtás t!zlltán a 16. sorban folytatódik, ahol ism(:t meghívjuk 37. AreCube ( ) fü ggvényt, de ezúttal nem adunk meg In:lgasságot. A függvény ekkor az alapértelmezett értéket fogja hasznMni, ami akiíratott eredményben világosan lútszik. A \9. sorban ismételt függvényhívás következik, de ekkor már sem II szélességet sem a magasságot nem adjuk meg. A végrehajtás tehát immár harmadszor a 26. so rm ugrik, 3hol két alapértelmezett érték felhasználásával a függvény ismét kiszámítja a felszínt. Ezt aztán megint kiírltjuk a képernyőre .
Függvénvek túlterhelése :\ CH nye lv lehet6vé teszi , hogy ugyanolyan névvel akár több függvényt is létrehozzunk. Ezt a:.! dj{jrást nevezik a függvény túlterheléséne k (overloading). Az azonos névvel rende lkező függv ényekne k azért a pamméterlistájukban különoozniük kell , vagyis legalább egy paraméterüknek más lípusúnak kell lennie, vagy eltérő számú \xlrlméterrel kell rendelkezniük. Esetleg mindkettő. Lássunk rögtön egy példát: ::'nt myFunction (int , int) ; _:lt myFuncti on (long , long) ; :nt myFunction (long) ; :\ :nyFunct i on () függvényt itt három különböző paraméterl ista megadúsával terheltük rul. Az első és a második változat a para méterek típusában tér el egymástól, míg a harmad iknak eltérő SZ;lmú p:1ramétere van. Hogy a C++ fordítónak melyik változatot kell használnia az egyes hívások :1lkalmával, azt ezek az dtcrések, vagyis a hív,ís konkrét módja dönti cl.
•
Túlterhelt fuggvényeknél a visszatérési érték típusa lehet azonos de akár eltérő is, amíg .i paraméterek listája különbözik. Ugyanakkor arra is lehetőség van, hogy a túlterhelés ...b.pja a visszatérési érték típusa legyell. A fuggvények túlterhelését swkás többalakúságnak (polimorfizmus, polymophism) is nevezni. Az elnevezés magáért beszél, így ra;in nem igényel további magyarázatot.
98 11. rész· B8V8Z8tés a C+ + programozási nyelvbe A függvények többalakúsága a nyelvnek al. a képessége, amely lehetővé teszi, hogy egy adott nevet túllerhelve a függvénynek több jelentést lulajdonítsunk. Ha ugyanazzal a névvel, de eltérő paraméterlistával hozunk létre két vagy több függvényt , maga a fordító akkori is képes lesz automatikusan eldönteni, hogy melyik helyzetben melyikről van szó a kódban, hiszen a paraméterek száma és típusa alapján azonosítani tudja a megfelelő változatot. Ha például az a feladat , hogy egy függvénnyel számítsuk ki sz;imok átlagát, akkor fl túlterhelés segítségével nem kell külön nevet adnunk minden egy adattípust kezelő változatnak, vagyis nem kel1lélrehoznunk külön Average I nts (), AverageDoubles () és ehhez hasonló nevíI függvényeket. Hívhatjuk valamennyi egyszeruen Average () -nek, a fordító pedig mindig tudni fogja , hogy melyik az igazi. Tegyük fe l, hogy van egy függvényünk , ami bármilyen bemenetet is kap, megslOrozza azt kettővel. Csupán a numerikus típusoknál mamdva ennek a függvénynek nyilván képesnek kell lennie az int , long, floa t és double típusok kezelésére. Ha nem lenne a C++-ban Többalakúság, eZl csak négy különbözi) ncv(j" Cüggvénnyel tudnánk megoldani, valahogy így: int DoubleI nt (int) ; long DoubleLong(long) ; float DoubleFloat(tloat) ; double DoubleDouble(double) ; Ugyanez a túlterhelés segíL'iégével így fest: int Doubl e( i nt) ; l ong Double (long) ; fl oat Double (fl oat ) ; double Doubl e (double) ; Az ilyen kódot nyilv:'i.n sokkal egyszerűbb utólag mcgl:rteni v,lgy használni, hiszen nem kell azzal foglalkoznunk , hogy mikor melyik függvénytípusl hívjuk meg, a fordító ezt automatikusan elintézi helyettünk.
Inlino függvényok Amikor megadunk egy függvényl, a fordítóprogram általában csak e~,'Yszer hozza létre a memóriában az ennek megfelelő gépi kódot. Ha meghívjuk a függvényt, a program végrehajtásában elágazás keletkezik, a processzor:t megfelelő memóriaterülefre ugrik, és végrehajtja a függvény kódját. Amikor a függv{my visszatér, a végrehajtás is visszaugrik a hívás helyére, és az azt követő utasítással folytatódik. Ha tehát egy függvény tízszer meghívunk, az tíz ilyen ugrást fog jelenteni a végrehajtásban. A függvénynek egyeden példánya van, de azt tíz.<;zer hajtjuk végre. Az efféle ugrálásnak persze van némi számításigénye, vagyis általa némiképp csökken progmmunk teljesítménye. Ugyanakkor vannak egészen kis függvények is, :tmelyek
5. óra • Függvények 199 akár egyetlen soroyi kóclból állnak. Nyilvánvalóan a tel jesítmény növekedését vonja maga után, ha egy vagy kél utasítás végrehajtásáért nem kell ezt a bizonyos ugrási mű veletet elvégezni. Amikor a programozók teljesítménYfÓl beszélnek, általában a sebességre gondolnak, vagyis a függvényhívások kikeriilésével programunk futási sebessége fog növekedni. Ha egy függvény deklarációjában szerepeltet jük az inline kulcssZÓl, akkor a fordítóprogram valójában nem hoz létre függvényt, hanem a megadott kódul bemásolja minden o lyan helyre, ahol eZI a látszólagos függvényt meghívjuk. Ez egyben azt is jelenti, hogy a végrehajtásban semmiféle ugrás nem lesz, a progmm úgy viselkedik, mintha a fOggvé nyben szerepl6 utasításokat a hívás helyére gépeltük volm. be. Mindez igazár1 szépen hangzik, ám nem árt fölhívni a figyelmet az inline függvények haszná latá nak leheL~~ges árnyoldalaira sem, El őször is ha egy inline függvényt tízszer meghívunk egy másik függvényb61, akkor a k6dja tízszer bemásolódik a nnak a k6djába. Ez pedig azt jelenti, hogy bár a sebesség bizonyára növekedni fog, a kód mérete is ugyanez teszi, mégpedig az inline függvény méretétő l függ6en esetleg olyan mértékben, amiért már nem is éri meg ezt a megoldást, használni. Ráadásul előfordu lh at, hogya sebessl:gnövekedés is csak szép álomnak bizonyul. A manapság használatos fordítóprogr-.tmok igen okosak, rengeteg optimalizálást elvégeznek maguktól is, ami szinte minde n esetbe n szükségtelenné teszi az inline függvények alkalmazását. Ami viszont még ennél is fontosabb, az a k6drnéret és a teljesítmény összefüggése: II nagyobb kód gyakran lassabban fut, mert nehezebben kezeli az operációs rendszer.
Aprop6
Helycsere a #define direktfvával
Az inline függvények tulajdonképpen ugyanaz a szerepet töltik be, mint a C nyelvben a IIdefine segrtségévellétrehozott makrók. Ezek után joggal kérdezheti az olvasó, hogy - különösen kezd6ként - mégis milyen amnyszabályokhoz tartsa magát. Ha van egy kifejezeuen kis fuggvényiink , amely mindössze egy vagy két utasításból áll, az . potenciális jelöl[~ arra, hogy inline-kénc deklaráljuk. Ha azonban bármilyen kétségünk merül fel ennek a hasznosság:lVal kapcsolatban, egyszeníen ne éljünk a lehet6séggcl. Az inli ne függvl'!nyek haszná latára mut:H példát az 5,7 Lista.
o: II 5 .7. Lista - Inline függvény használatának bemutatása 1: finclude 2, ] : inline 4,
int Doubler(int);
100 I. rész • Bevezetés a C+ + 5 : int main () 6: ( 7: 8,
int target ;
9:
std : : c ou t « 'Enter a number to work wi th : std: : cin » target ; std :: cout« '\n ';
10 : 11 ,
' ;
12 : 13 , 14 :
target " Doubler(target ) ; otd : : cou t « "Targ e t : • «
t arget «
target " Doublp.r(target) ; std :: cout« 'Target : • «
target « s t d :: endl ;
std , , end l;
15 : 16 :
17 : lB : 19 : 20 :
target " Doublcr(target) ; std : : cout « 'Target : • « rcturn O;
21 :
ta eget «
std : : cndl ;
22 : 23 :
24 : int DoubIer(int targe t ) 25 : (
26 :
return 2 *target ;
27 :
Enter a Targel,. : Target : 'I'arget :
n u mber to work with : 20
40 BO 160
A 3. sorban" Dou bler () függvénylolyan inline-kém deklaráljuk, amely egyetlen eg(!sz értékel vár bemeneIként és szintén egész é rtékkel tér vissza. A deklaráci6 am(lgy pontosan ú ~,.y néz ki , mim bármely másik, eltekintve persze a visszatérési típus előtt ál16 inline kulcsszótól. Enől
kezdve mindazokon a helyeken, ahol a
t arget = Doubler ( ta rgct} ;
kódsort
leí~uk
olyan gépi kód keletkezik, mintha a
következő
állna a forráskódban:
target " 2 * ta rget ;
Ez azt jelenti, hogy amikor a programol elindítjuk, már minden a helyén van, vagyis nincs szükség ugrásokra ahhoz, hogya függvény lefusson. Az eredmény gyorsabb, de nagyobb mérelú program.
5. óra • Függvények 1101
Apropó
Csak agy tanács ...
A modern fordítóprogramok az inline kulcsszót valójában csak laza iránymutatásként és nem parancsként kezelik. vagyis ha a fordító egyéb szempontok alapján úgy dönt, akár figyelmen kivül is hagyhatja ezt az óhajunkat, és közönséges függ-
vényhívást hajthat végre helyette.
A függvények és a verem Amikor meghíYl.Jnk egy függvényt, a progr.Hn végrehajtásában elágazás keletkezik.
A hívó fél átadj:l a paramétereket, majd a függvény törLSt!vel folytatódik a futCIs. Amikor a függvény véget ér, egy értéket ad vissza a hív6nak (hacsak nem void li visszat(:rési típusa), a végrehajtás pedig visszatér a hívás utáni első utasításm. Na dc hogyan is mű ködik ez az egész pontosan? A fordítóprogram v:116j::íban minden egyes programhoz létrehoz egy úgynevezett vermet (stack) , Ez nem más, mint egy speciális célr'.1lefoglalt mem6riaterület, amelyen keresztül a függvények képesek egymással kommunikálni. Veremnek azért nevezik, mert érvényes rá
5.4. ábra A verem nem egy6b, mim rá/"JI(!~)'ek ByITjtc/luJI/)"c.
Az ~uto1s6nak be els6nek ki" elv röviden annyit tesz, hogy amit uLoljára tettünk a ve-
rem tetejére, azt kapjuk vissza el6ször a kiolvasás során. Vannak persze másféle kiszo lgálási sarok is. A legtöbb például úgy működik, mint a színházi sarok, vagyis aki dsónek jön, az jUl be először, az hagyja el elsőnek a sort. A verem ezzel szemben olyan, mint az egymásra ídkotl pénzérmék. Ha egymásra rakunk 10 darab érmét az asztalon, majd párat leemelünk a rakásr61 , akkor az első három leemelt érme az utolsó három fel helyezen pénzdarab lesz.
102 11. rész· Beveie1é. a C++ programozísi nyelvbe Amikor adatot ~tolllnk" (push) a verem tetejére, akkor a rakás növekszik. Amikor lehúzunk (pop) onnan valamit, a verem csökken. Ha nem akarunk katasztrófát, akkor nem tudunk úgy leemelni a veremról egy elemel, hogy le ne emelnénk az összes fölÖ(te lev6L is. A programozással fog lalkozó könyvekben á ltalában a k1nyérhalmoLszokás a verem alleg6riájakénl em líteni, amivel nincs is különösebben nagy baj azon kívül , hogy alapvel6en hibás. Valójában sokkal helyesebb amolyan függ61egescn el rendezett Oóksornak e lképzelni a vermet, amelynek a teleje ott van, ahova a veremmutat6 (stack pointer) é ppen mutat. Minden egyes t1óknak va n egy sorszáma, a vcrcmrnutató pedig mindig ezek közül :1 sorszámok közül tárol egyel. Minden, ami ez alatt a fiók alan - va~,'yis a verem teteje alatt - helyezkedik el, a veremhez tartozik, és éppen tá rol vala mit. Minden , ami fölötte van, üres, nem tartozik a veremhez, a tartalma pedig érvénytelen. Ezt az elképzelést szem lé lteti:az 5.5. ábm. Verem 100
theVarlable
60
101
l
My","
50
Veremmutató
A vermen kr- 11021 } vüli tartomány
02 <:0
l 03 l 04 105
YourAge
37
l 06
A verem tartalma
107
l 08 l 09 110
5.5. ábra A veremm lllllfú
Alnikor adatot tolunk a verem tetejére, az a veremmutat6 fölötti első fiókba kenil, a vcremmutató pedig eggyel (vagy a szükséges hosszal) feljebb mozdul. Ha adatot veszünk Ic a veremről , akkor ennek é ppen a fo rdítonja játszódik le, vagyis a veremmmató lejjebb tolódik. Ezt az egyszeru játékszabályt szemlélteti az 5.6. ábra .
5.6ra • Fuggvónyek 1103 Verem Veremmutató
100 Ihe Varlab le
eo
MyA,.
50
~
10 1 102 103
Avermen krvűli
10'
tartomány
105 YOI.IrAge
37
106 107 108Q
:~} Averem tartalma 5.6. ábra A l'Crem ll/ultl/6 mozga/ása
Amikor progmmunk meghív egy függvényt, létrejön egy ~vcremkcrel~ (513Ck frame), amely egy olyan, a veremb61lefoglalt megfelelő méret\! terület, amely elegendő a kérdéses függvény par.:lmétereinek kezelésére. Ez egy viszonylag összetett folyamat , ráadásul a részletei különbözhetnek is a kü l önböző fe!építésCf gépeken, de a lényege a következő: 1. A rendszer elhelyezi a verem tetején a függvény visszatérési dmét. Ez az a me-
m6riacírn, ahova a végrehajtásnak vissza kell ug'J.nia, amikor a függvény befejezle mtIködését.
2. A rendszer lefoglal a vennen egy akkor'.! helyet, amely e l egendő a vissza(~r~si érték tárolására. 3. A hív6 fél elhelyezi a verem tetején a függvény valamennyi paraméterét. 4. A végrehajtás a függvény k6djára ugrik. 5. A fiiggvény helyi változói deklarálásuk sorrendjében létrejönnek a vermen. Visszméréskor ugyanennek a
művd etsornak
a fordítonja zajlik le.
Kérdések és válaszok Kérdés: Miért nem használlInk kizárolag globális változókat? Válasz: Volt idő, amikor a programokban valóban kizárólag glohális válLozókat használLak . Aztán ahogy növekedett a szoftverek összetettsége, egyre nehezebb volt megtalálni bennük a hibákat. Ennek pedig alapvet6en az volt az oka, hogy a globális adato-
104 11, rész· Bevezetés a C++ programozási nyetvbe kat elvileg bármely függvényelronthaua, hiszen a program bármely pontján módosíth,ltó volt bármi. A hosszú évek gyakori ali tapasztalata azt.'Ín meggy6zte a fejleszt6ket, hogy az adatokat ami lyen mértékig csak lehet, lokálissá kell tenni, vagyis a lehető legkisebbre le keU szűkíten i azoknak a blokkoknak a számát, amelyek egy-egy változóhoz hozzáférhetnek. Ké,.dé.~:
Miél1 /Icm befolyásolja a liívó fCl adatait az, ha egy fiiggvél/Y módosítja a neki
árudoll fXo'améterel..'cr,?
Viilasz:'A filggvények paraméterei érték szerin{ adódnak át. El vény valójában csak az eredeti értékek másol:Hával dolgozik. Kérdés: Mi t(JI1éllik akJ.'OI; ha dcklarálom a
/.>6vet!':ező kél
lIl(
jelenti, hogya függ-
függvény:
int Area (int width , int length = l); inL Arca (int size) ; Tckilllhető ez Wlterhelésl/ck vagy sem? Eltérő ug)'an a pamméte1'ck száma, dc az első esetbell az egyiknek vall alapél1clmczett él1éke is.
Válasz: A helyzet érdekes, ugyanis maga a deklaráció rendben van, de ha az Areél függvényt bfirhol egyetlen par:ltnéterrel meghívjuk, fordítási hiba keletkezik, mivel a rendelkezésre álló lIdatQk alapjfin nem dönthető el, hogy melyik formá"l gondoltunk (:unbiguity between AreaCint, int) and Area(int».
Gyakorlatok Most, hogy már tudunk mcgtanultunk néhány dolgot a fi.lggvényekr61 , álljon in néhá ny megvfil:lszolandó k6rdés és megoldandó feladat , amelyekkel megszilárdíthatjuk a eddig megszerzelt tudásunkat.
Kvfz 1. Mi az abpértelmeLetl paraméter-átadCI.<;i m6d függvényhivás esetén?
2. Honnan tudja II C++ fordító , hogy melyik változatát hívja meg egy túlterhelt fOggvé nynek? 3. Mitől olyan hasznosak a függvények? Miért nem írunk helyettük egyetlen monolitikus programot? 4. Hány értéket lehet viSS7.aadni egy return paranccsal?
5. óra • Függvénvek 1105
Feladatok 1, Pr6báljuk meg kikommenlezni az 5.5, Listában bemutatott kód (manyreturns.cpp) utolsó std : :cout hívását. Mi változik a fordítá:> során? Mi történne, ha bete nnénk egy return pamncsot az std: : cout urán? 2. Hogyan kellene kiegészíteni az 5.2. listában bemutatott programot (local var . cpp) úgy, hogy Celsius skálár61 is tudjon Fahrenheitre ál$zárnítani? Pr6báljuk megva lúsltani az ötletet! .,. Egészítsük ki úgy az 5.2. Listáb:m b(!mutalau programot (local var . cpp), hogy egész és lebegoponlos értékeket is álludjon váltani Fahrenhcitrol Celsius skálára. Használjuk a függvények túlterhelésének lehctoségét!
Válaszok a kvfzkérdésekre l. Az alapértelmez.en m6dsi!cr az énék szeri nti átadás ( p ;lS5 hy va lue). Ez azt jck:nti, hogy.! függvény csupán a paraméterek másolatát knpja meg, vagyis az általa
rajtuk végzett módosít:1soknak a külvilágra senunifélc hatása nem lesz, 2. A fordít6program a filggvény meghívása kor átadott p:U"
6.
ÓRA
Vezérlési szerkezetek Ebben az 6rában a következőkról lesz sz6: • •
Milyen ciklusok vannak és hogy,ln érdemes őket használni Hogyan szervezzünk különféle ciklusokat
•
Egy :t1Lernatíva a mélyen cgybeágyazolt i f ... else utasítások helyett
Ciklusszervezés Múlt órá n :l vezérJóutasítások első változatáról vOll szó, vagyis az if ... else utasításokról. Sok programozási fe ladat úgy oldható meg, hogy ugyanazokkal az adatokkal tevékenykeclünk újr.! meg újm. EZl az ismételt tevékenységet iterációnnk nevezzük. Az iteráció megval6sitásának legalapvetőbb módszere a ciklusszervezés.
A ciklusok 6se: a goto utasftás A swmításrudomány hőskorában a ciklus egy címkéből, néhány ulasításból és egy ugrásb61állt.
A C++ nyelvben (mint ahogy a C-ben is) a címke egy név, melyet: (kenósponc) köveL El lehet helyezni címkét bármely utasítás elé (batoldaIÚ, és ugráskor erre a címkére lehet hivatkozni a goto utasítás után.
1Da l l. rész • Bevezetés a C++ programozási nyelvbe
Miért ne használjuk a goto utasítást? A C++ programokban riLkán látunk goto-t, és ez nem véletlen. A goto utasítással el lchet ugrani a forrásk(xl bármely részére, el5re vagy hátm. A g oto utasítás válogatás nélküli alkalmazása szörnyű gubancos, olvashatatlan programokat eredményezett: ezt hívjuk spagetti k6dnak. A go t o használatának elkerülésére kifinomult, szigorúan szabályozott cikluss7.ervcz6 utasítások születtek: f o r, while , do ... wh ile. Ezek használata sokkal érthetőbbé tCszi a prognuum, és általáb,lIl fe leslegessé teszi a goto használatát. A globális változó k használatát is hason ló okokból kerüljük: nehéz 5kel S7iimon tartani és nyomkövctni.
A while ciklus A 6.1. Listában bemutatott while ciklus hatásánl a program bizonyos utasítások sorozatát ismétli mindaddig, amíg a kezdeti fe ltétel ign marad.
o: 1/ 6 . 1 Lista 1 : II A wh i le ci kl us 2 : lI includc ]
,
4 : int main() 5: (
6,
int counter = O: /1 feltétel ben l év6 változó inicializációja
7,
8: 9: 10 : l l: 12 :
whi l e(coun t er < 5) /1 ellcn6r zés : i ga z-e még a fe l tétel {
counte r ·H· ; II A ci klu s magja std :: cout« "S7.áml ál6 : " « counter«
" \n ":
13 : 14 :
15 : 16 :
std :: cout« rctu r n O;
"Kész . A számláló értéke , • «
Számláló , l Szám1á16 , 2 Számláló : 3 Szám1ál6 : 4 Számlá ló : 5 Kész. A számláló é rté ke : 5
counter «
" . \n" ;
6.6ra • Vezértési szerkezetek 1109
Ez az egyszeru program bemutatja a while ciklus tényegét: ha fe nnátt egy feltétel , végreh'ljtódik a dklusmag. Esetünkb<:!n a program a 8. sorban található feltételt vizsgálja meg: kisebb-e a számláló 5-nél. Ha igen, akkor végrehajtódik a ciklusrnag. A 10. sorban növekszik egyet a számláló, melynek értékét a program a ll. sorban ki is írja. Ha nem teljesül a 8. sor feltétele (azaz a s7.1Ímtál6 már nem kisebb 5-néD, a program nem hajtja végre a 9- 12. sorban l evő ciklusmagot, hanem a 14. sorra kerül a vezérlés.
Bonyolultabb whil. utasftások A while ciklus e lején vizsgált f<::ltételben bármilyen összetett C++ kifejezés áJthat. Ez tartalmazhat logikai operátorokat is (és: &&, vagy: I I, tag:ldás: O. A 6.2 Lista egy kissé összetettebb while utasítást mutat be.
6.2 lj... - Egy ÖlSZetottobb whilo ciki.. fcomploxwhilo.cppl
o: II 6.2 LiAtl.l. 1 : II Osszetettebb whi le ciklus 2: ~includc
,.
int main () 5: { unsigned short small ; 6. unsignod loog large ; 7. const unsigoed short MAXSMAIJIJ",65535 ; 8. 4:
,. 10:
ll: 12: 13: 14 : 15 : 16: 11 :
18 : 19 : 20 : 21 : 22 :
std: : cout « 'írjon be egy kisebb számot: '. std : : cin » small; std :: cout« 'írjon be egy nagyo bb számot : ' ; std : : cin » l arge ; std: : cout «
28 :
small «
' .. ' .
II minden iteráci6s lépésben három feltételt Vizsgál while (!lmall < large && large> O && smull < Mi\XSMlILL)
if
(small 1; 5000 == O) II 5000 soronként kiír egy pOttyOt std : : cout « • . • .
23 : 24 : 25 : 26 : 21 :
'khIebb: • «
small+ +;
large-:2 ;
110 II. rész· Bevezetés a C+ + programozási nyelvbe 29 : 30: 31 :
std: :cout « "\nKisebb : " « small « " Nagyobb: " « large « std: : endl; return O;
32 :
írjon be egy kisebb számot: 2 írjon be egy nagyobb számot: 100000 Kioebb : 2 ..... Kisebb:33335 Nagyobb : 33334
• EZ;J program egy egyszen1 játék. Beírand6 egy kisebb és egy nagyobb szám. A kisebb egyesével növekszik, a nagyobb kettesével csökken. A játék célja annak kitalá lása, hogy hol találkoznak. A 10-13. sorban írhatjuk be:il számokat. A 'IH. sor egy olyan ciklusfehételt tal'talmn, mcly esak az alábbi három feltéte l egyidejű teljesülése eserén engedi meg a ciklusmag kfutásál: • a sma11 nem n:!gyobb a large-nál • a large pozitív • a sma11 kisebb MAXSMALL-n{11 (ami a kis egészek
m~rethal1lrd)
A 21. sorban II small SOQO-es maradékát sz.1mítja ki :il program. Ez csak akkor nulla, ha a small egész számú többszöröse 5000-nek. Ne feledjük , hogy ez:! viz..<;gál:!t nem válloztatj:! meg :il sma11 énékét. Amikor az 5000-es maradék nulla , egy pont (.) íródik ki a képerny6rc, hogy lássuk a folyamat haladását. A 24. sorban n6 a sma11, a 26. sorban pedig kettesével csökken ,I large énéke. Ha a h:'irom fe ltétel valamelyike séIiiI, véget ér a ciklus, és a progmm futása a ciklus végi z,h6jel utáni 29. sorban folytatódik . Elképzelhető,
hogy az ~előjeles nu1!ánk" miatt az
"complexwhile.cpp·: WB012 Comparing signed and unsigned values in functio n main() a t line 18 (E16jcles és e16jel nélküli értékek ósszehasonlitása a main () függvény 18. sorában)
Kényszeríteni lehet az előjel nélküli konstans használatát egy u bettl ooaillesztésével (jelen esetben például Ou használatávaD.
6. óra • V_ési sze,bzetek 1111
A continue és a break Bizonyos esetekben szeretnénk visszatérni a dklusfejhez (a ciklus kezdetéhez), még mielőtt a további Ul,lsltások végrebajtódnának. A continue (jelentése: Jo(ylasd) utasí~ tás visszaugrik a ciklus elejére . .\1á5 esetekben pedig még azelőtt szeretnénk kilépni a ciklusból , hOb'Y a kilépéshez szükséges feltételek fennállnának. A break (jelentése: kit6rés, megszakílás) utasítás azolmal elhagyj3 a ciklust, és a cikjus végi wr6jel után folytatódik :l progr
o: II 6.3 Lista 1 : II A oreak és a continue bemutatása 2: hncIude 3: using namespace std:11 E fájlban e16kerOl a !Jtd : : cout , 4: II std :: cin, std :: endl, sto. 5, 6 : jnt muin() 7: { 8: unsigned short small ; unsigncd long large; 9: 10: unsigned long skip; 11 : unsigned long t arqet ; 12 : connt unsigned short MAXSMALL ~65535 ; 13 : 14 : cout«· írjon be egy kisebb számot : "; 15 : cin » small; 16: cout « "írjon be egy nagyobb számot : " ; 17 : cin » large ; 18 : cout « 'Adjon meg egy szorz6számot : " ; 19 : cin » s kip; 20 : cout « 'Adj a meg a tippelt célért6ke t : "I 21 : cin » target ; 22 : 23 : cout « "\n" , 24 :
,~zá m
a já-
112 11. nlsz • Bevezetés 8 C+ + programozási ny.Ml. 25 : 26 : 27 :
II a ciklus 3 fe l tételé nek megadása whil e (sma ll < l a rge && larg e> O && small < MAXSMALL) (
28 ,
small++ :
29 : ]0 :
i f (smaH % skip """ 0 )11 ki mara dj o n a csökkentés ?
31 : 32 : 33 :
{
cout « 'kimarad : • « continue ;
34 : 35 :
if (la r ge
36 : 37 : 38 : 39 :
==
small c< endl ;
t arge t ) /1 Pon tos an egyezik acélér t ék k el ?
{
cout « break;
'Célba értiInk ! ' ;
40 : 41 :
42 : <13 : 44 : 45 : 46 ,
large =2 ; II a while ciklus vége
coul «
" \nKiscbb : • «
emaIl «
• Nagyobb : " «
large «
endl ;
return O;
47 :
írjon be egy kisebb számot : 2 í rjon be egy nagyobb sz ámot : 20 Adjon meg egy szorzószámot : 4 Adja meg a tippelt célértéket : 6 k i marad : ", k imar"d : 8
Kisebb : 10 Nagyobb : 8
Ebben a játékban a jálékos veszített; a kisebb szám azel őtt haladla meg a nagyol, mielőtt az elérte volna a 6-os célértéket. A 26. sorban helyezkedik el az összetett ci klusfeltétcJ. Ha a sma ll továbbra is kisebb
ma.dd a large-nál, a l arge nagyobb mrlmd nullánál, valamint a sma ll nem lépi túl a maximális előjel nélküli rövid egész számok méretét, akkor a program belép a ciklusmagba. A 30. sorban vizsgáljuk meg azt, hogya szorz6számnak többszöröse-e sma ll értéke. Ha igen, akkor a con t i nue parancs hatására a vezérlés a 26. sorra kerül, ezzel elmarad acélérték elérésének vizsgálata és a l arge csökkentése.
6. 6ra • Vezérlési szertezetek 1113 A 36. sorban vizsgáljuk meg azt, hogya nagyobbik szám egyenl6-e a céJé rtékkel. Ha igen, győzött a játékos. Megjelenik az elis merő üzenet és elé rjük a b r eak utasítást. Ezáltal azon nyomban ~kitö rünk ~ a whi l e ciklustJÓl, és a progmm futá sa a 45. sorban folytatódik. Óvatosa n használjuk a cont i nue és a brea k utasításokat. Nehéz megérteni .tzokat a programo ka t, amelyek egy·cgy ciklus be lsejé ből hirtele n kilé pne k, és egy kicsiny kis while ciklust is olvashatatlanná lehet tenni néhány szabadosan használt continue és a break utasítással. Aprop6
Névtarak használata
Ez a programpélda egy újabb kulcsszót is becsempészett a látóterünkbe: usi ng narnespace, azaz: névtér használata. Ez arról értesíti a fordftóprogramot, hogy amennyiben függvényhívást talál (mint amilyen például a cin vagy a cout ), akkor képzelje oda a megadott elótagot (esetünkben ez a std :: ). Ez csökkenti a begépelendő betük mennyiségét. Mivel sok fordítóprogram nem ismeri még ezt a direktíváI, ebben a könyvben nemigen fogjuk használni. A Borland C+ + fordító ismeri.
while{ 1) - végtelen ciklusok Egy while ciklusfejben vizsgált feltétel bármilyen összete u C++ kifejezés lehet. Amíg é né ke igaz ( true) , fo lytatódik a ciklusmag fl,l(ra tása. így olya n ciklust is lelre le het hozni, mely soha nem é r véget, ha az 1 szá mot használjuk a vizsgá landó feltételben. Mivel az 1 mindig "ig:lZ", a ciklusb61 soha nem lé pünk ki (hacsak nem éri.ink el egy b reak utasítást). Ezt ~végt e len ciklusnak" hívjá k. A 6.4 Lista ezt a módsze rt használja, melybcn tízig s71i molunk el.
6.4 Um - V6gte1en ciklus a while(1} legftsltg6vel (whilaforaver.cpp)
o: II 6 . 4 Li sta 1 : II A ' while iga z ' ciklus b emutat6ss 2 , 'incl ude 3, 4 : in t main ()
5, ( 6:
7, 8: 9: 10 : 11 :
12 : 13 : 14 : 15 :
16 :
i n t coun t er
o:
O;
while (1) { count e r ~~; i f (c ount.er > 1 0) bre ak ;
st.d : : cout « re t u rn O;
'Számlá ló , • «
count er «
'\n ' ;
114 11. rész • Bevezetés a C+ + programozási nyelvbe
Számláló: 11
A 8. sorban olyan rel tétel van megadva, amely sosem [esz hamis . A ciklusmag II 10. sorban megnöveli a számláló (counter) változó értékét, majd a ll. sorban megvizsgálja , túlhaladta-e már a tízet. Ha nem , akkor tovább folyik az iteráci6. Ha viszont II számláló nagyobb tíznél, a 12. sorban található break véget vet a ciklus fuLásának. A programvezé rlés .1 14. sorr.:l kerül , ahol II végeredmény kiíródik. A program
működik,
de nem szép. Kitún6 példája annak, amikor alkalmatlan eszköz-
zel okjunk meg egy fei
ne k e lJe n6rzést:t oda tesszük, ahová való: a ciklusfej feltételébe.
Figyeleml
lefagyások
A while (1) -hez hasonló végtelen ciklusok a számlt6gép lefagyását eredményezheti, ha a kilépési feltétel soha nem teljesül (vagy nincs is). Csak nagy körültekintéssel használjunk ilyet, és teszteljük alaposan! A C++ szá mos utat tesz lehet6vé ugyanazon cél eléréséhez. Az igazi tudás azl jelemi, hogy kell ő érzékkel megta láljuk az adott feladathoz leginkább ill őt
HoIyeo Ila szn~ljunk while ciklust olyan ireráci6hoz, 1l1cly egy fe ltétel ign voltától függ.
Gyakorolunk önmérsékletet a continue és a break ut:lsít~sok terén.
Ne tegyünk pontosvesszőt közvetlenül a while () után, mert ez ciklusmag nélkül zárja le az utasítást (azaz nem fog működnO.
Győz6djünk meg arról, hogy ciklusunk végü l valóban véget é r.
A dO...while ciklus Lehetséges, hogy egy wh ile ciklus magja soha nem fut le. A while ciklusfejben a mag futtatása előtt megtörténik az e ll e nőrzés , és ha hamis a feltétel értéke, a kkor az egész ciklusmagot átugorja a program. A 6.5 Lista eZl illw>Zlrálja .
6. 6,. • Vezérlési szsrl.ezetek 1115
6. 5 Usta - A whilu ciklus magjának átugrása (bodyskip.cpp)
o:
II 6.5 L is t a
1: 2: 3: 4, 5: 6,
II A whi l e cik lu s magj ának ki hagyá sa, II ha él f~ lt é t e l ha mis IIincludc int main()
int counter;
7: 8:
std : : cout « "Hány hell o legyen? " ; std : : cin » counter; while (counter:> O) {
9: 10 : ll :
12 : 13 : 14 : 15 : 16 :
std :: cout«
' Hello!\n" ;
counter~- ;
std : : cout « return O;
'A számlá l ó állállél : • «
countcr ;
17 :
Kimenet Hány hello legyen? 2 Hello !
Hello ! A ~zámlá16 állása : O FUU;lssuk lIjr.1 il prognulloc és adjunk meg nulWt. Jlyenkor ezt Játjllk:
Hány hello legyen? O A sz&mlá16 állása : O
A fdhasználót ar,d szólílja fel a program a 8. sorban, hogy adjon meg egy kezdóértéket, melyet a számláló (counter) neVtl egész változóban tároL Enne k érté ké r a 10. sorban elle n6rzi, t:s a cikl u..,magb'l1l csökkenti (a 13. sorban) . Az el ső fultatíi sko r a számlálót 2-re állítnlluk be, így kéL<;7.er le is fu tott;:l ciklusmag. A második alka lommal azonban nullát adtunk meg. A 10. sorbeli vizsg:'i lat eredménye hamis volt; a szá mláló nem volt pozitív. A ciklusmag tehát nem futot! le, és egyetlen Hello sem le tt kinyomtatva.
Mi a teendő akko r, ha legalább egy Hello-t mindenképp szeretné nk megjeleníteni? A while ciklus nem képes erre, men a feltt:telvizsgálat még a zelő!! megröné nik, hogy bármi is megjelenne. Kikényszerithetó a do log egy i f vizsgíilattal is, még a ciklustYd való belépés el6tt: i f (countcr < l ) II Egy mi nimális érték kénysze ri tése couu Let:" = l ;
116 11. rész· Bevezetés a C+ + programozási nyelvbe A programozók ezt megoldásnak.
~belonlepkének~
Ckludge) hívják; csúnya és nem elegáns
A do ... whil e ciklus magja a feltételvizsgálat előlt fm le, így legalább egyszer mindenk6ppen végrehajtódik. A 6.6 Lista az előző program újrafogalmazása a do ... whi le
öklussal.
6.6 usta - Pálda a do..wbile ciklus alkalmazására Idowhile.cpp)
o: II 6 . 6 Lista 1 : II A do ... whi1e bemutatása 2 : 'inc1ude 3, 4 : int ma i n() 5: { 6: 7:
8: 9: 10 : 11 :
i nt cou nter ; std : : cout « "Hány hallo legy en? "; std : : c in » counLer ; do {
std : : cout « "Hello\n" ; counter - ; while (counter >0 ) ; std : : cout « 'A számlálÓ állása : • « return O;
12 : 13 : 1 4: 15 :
counter «
std : : endl ;
16 :
Kimenet Há ny hello legyen? 2 Hello
Hello fo. s zámláló á l lása : O
Fuuassuk újra a programm és adjunk meg nllllát. Most ezt látjuk: Hány hello legyen? O
Hello A s zá mláló állása :
l
A felhasználót arrJ. szólítja fel a program a 7. sorban, hogy adjon meg egy kczdóértéket, melyet a számláló (counter ) változóban tárol. A do ... wh ile ciklusban a ciklusmag a Feltételvizsgálat előtt lefut, ily módon garantált, hogy legalább eb'Yszer végrehajtódik. A 11. sor kinyomtatja az üzenetet, és a 12. sorban csökkentett számláló értéke a 13. sorban kenil e l lenőrzésre. Ha a Feltétclteljesül , akkor a vezérlés a ciklus eJej6re kerül (a 11. sorra); különben pedig rácsorgunk a 14. sorra.
6, óra • Vezérlési szarbzetak. 117 A do .. . while ciklusban ugyanúgy használható a continue és a break, mint a while ciklusban. A while és a do ... while ciklusok közü egyeden különbség;t fcJtérelvizsgálat helye.
A for ciklus A while ciklusok írása közben gyakran azon találjuk magunkat, hogy beállínmk egy számláló változót, vizsgálgatunk rá valamilyen feltételt, és fokozatosan növeljük (vagy más módon változtat juk), val:lhányszor lefut a ciklusmag. A 6.7 Lista erre mutat példát.
6.7 ülla - M6tI OtIY piRa"".a wIllI. clkl.1nI (whil••uain.cppl o : 116.7 LisLa l: II while ciklus újra 2 : #lncludc 3. 4: int muin() 5: ( 6: int cou ntel:' " O;
,. 8:
9: 10 : 11 :
whilc(countcr < 5) ( countcr++; std :: cout« 'POrgOk ! " ;
12 : 13 : 14 : 15: 16 :
std: : cout «
• \nA számlálÓ : " «
counter «
• . \n";
rcturn O;
POrgOk! POrgOk! POrgOk! POrgOk ! POrgOk! számlálÓ : 5.
A
A számláló (counter) v{i!to:.:óértékét nu lbíra ;;!lítjuk a 6. sorban, majd a 8. sorban megvizsgáljuk, kiscbb-e még 5-nél. A '10. sorban mcgnövcljük az ért.ékét. A 11. sor kiír egy egyszerű üzenetc!; bizonyiíra cl tudja képzelni a kedves Olvasó, hogy ennél hasznosabb feladatot is elláthat a program a számláló minden egyes növekedésekor.
Inicializáci6, vizsgálat, léptetés A for ciklus egyctlen utasícásba gyűjti az inicializációt, a reltételviz,>gálalot és a léptetést. A ciklusfejben a for kulcsszó után (egy záróje\páron belül) pomosvcssz6k áhal határoltan három utasítás áll.
118 11. rész • Bevezetés a C++ programozási nyelvbe Az els6 utasítás az inicializád6. Bármilyen C++ utasítás ideírható, de alapvet6en egy számlál ó változó létrehozása, inicializálása szokott itt állni. A második utasítás a feltételvizsgálat - bármely helyes C++ kifejezés szerepelhet beIU1e. Ugyanarra szolgál, mint a while ciklus feltételvizsgálata. /\ harmadik utasítás a léptetés. Á1t:llában egy számhíl6 növelése vagy csökkentése a feladata , de itt is elképzelhető bármilyen C++ Ul,lsítás. Megjegyze ndő, hogy az első és a harmadik helyen tetsz6leges C++ utasítás állhat, a második helyen viszont egy kifejezésnek kell állnia - egy olyan C++ utasításnak, amely visszaad valamilyen f!rlékel. A 6.8 Lista bernUlat egy fo r ciklusl.
6.8 Lista - A for ciklus használata (fortoop.cpp)
o:
II 6 . 8 Lista 1 : II A (or ciklus 2 : 8include
3, 4: i n t mai n ( ) 5: ( 6: int cou n ter l 7, for (counter., O; counter < 5 1 8: std : : cout « ·POrgOk ! ' ;
countcr ++)
9,
std : :cout « ret urn O;
10 , 11 :
' \nA számláló : " «
counter «
•. \n" ;
12 :
menet POrg:Ok! POrgO k!
POrgOk l POrgOk ! PO r gOk !
A szám1616 : S .
•
Ó
A f or ciklus a 7. sorban egyesíti a számlál6 iniciali7.áci6ját, aIlliak vizsg{,lalát, hogy ez kisebb-e 5-nél , valami nl a számláló növelését. A ciklus magja a 8. sor. Természetesen blokk is használható ciklusl11agkénl.
Figyeleml
Gyakori for hibák
Gyakori hiba avesszó (,) használata pontosvesszó (;) helyett a for utasításban. A fordítóprogram ezt jelzi is. Egy másik gyakori hiba, hogya f or zárójele után pontosvesszővel (;) zárjuk a sort. Ez egy ~ semmit sem csináló cikJust~ eredményez. Vannak esetek. amikor ennek értelme lehet. így a fordítóprogram erre nem ad hibajelzést.
6. 6ra • Vezérlési szerl<02lI'.k 1119
Összetettebb for ciklusok A for utasítások igen efÓS és rugalmas eszközt adnak a kezünkbe. A három független utasíliis (az inidalizáci6, a vizsgálat és a léptetés) igen sok l ehetőséget hordoz. A for ciklus az alábbi sorrendben hajlja végre a lépéseket: L elvégzi az inidalizáció mGveleteit 2. kiértékeli a feltételt 3. ha a feltétel teljesül, végrehajtja a ciklusmagot és A ciklus további lépéseiben a 2. és a 3. lépés
II
léptetést.
ismétlődik.
Halmozott inicializáci6 és léptetés Nem ritka, hogy több váilozót is inicializál unk, esetleg cgy összetettebb logikai kifeje-
zést vizsgálunk, valam int, hogy egyszerre több léptető ulasíl{,,~l is végrehajtunk. Az inicializáci6 és a léptetés helyére több C++ ulasÍlás is odaírható, vesszővel clválaSZI\'3 . A 6.9 Lista két vállol.ó inidalil.áti6ját l!s ll!ptC::tésél mutatja.
6.9 Ulla - Halmozott utaltiaolc • for ciklulfejben lformulti.cpp) o : II 6 . 9 Lista l : II bemutatja, hogy hogyan lehet tObb utasitást elhelyezni 2 : II a for ciklusfejbon
3: 'includc 4, 5 : int lMin() 6: 7: 8:
9:
{
for (int i=O, j=O ; i<) ; i·H, j·H) s t d : : cout « " i : • « i « " j : • « ret u rn O;
j
«
std : : e ndl ;
10 :
Kimenet i : O j: O i : 1 j: 1 i: 2 j: 2
A 7. sorban kél változ6, az i és a j kap O kezdóértéket. A kiértékcIt feltétel (i<3) teljesül, így lefut a tiklusmag és kiírásm kerülnek az értékek. Végül a ciklusfej harmadik részében á1161éptetések is megtörténnek, megnövclve az i és a j értékét.
120
II. rész • Bevezetés a C+ + programozási nyelvbe A 8. sor végrehajtása után újra és újra kiértókel6dik a feltétel , és amíg igaz marad, megtörténnek a léptetések (az i és a j értéke tovább n6), valamint a ciklusmag is ismételten lefut. Ez mindaddig folytatódik , amíg meg nem bukik a feltétel. Ekkor a vezérlés kilép a ciklusból, és a léptetés is elmar.!d.
Üres utasftások a for ciklusfejben A for ciklusfej bármely (a kár az összes) lltasítása elmaradhat. Ezt úgy valósílhatjuk meg, hogy nem írunk semmit a pontosvessz6k által kijelölt helyre. Ha o ly.m for ciklust szeretnénk írni, amely pontosan úgy mllködik, mint egy while ciklus, akkor hagyjuk el az e1s6 és a harmadik lIIasHást. A 6.10 Lista eZl illusztrálja .
&.10 Lista - NIda a lor _fajben oIhoIyazIoet6 O............ (fomuII.cpp)
o:
II 6 . 10 Lista II ürea utas itás a k El f or cikluöfejben 2 : #include
1 :
] :
4: int main() 5:
6:
(
int eount cr = O:
7,
8: 9:
10 : 11 :
for( : eounter < 5: ) { counter++ : std :: cout« "POrgOk!
' :
12 : 13 : 14 :
15 :
std : : eout « return O:
• \nA számláló : ' «
eountcr «
•. \n' ;
16 :
POrgOk t POrgOkt POrgOk! POrgOkl POrgOkl A számláló : 5 .
Bizonyár.! felismeri a kedves o lvasó, hogy ez ponlosan úb')' működik , mint egy korábban bemutatott while ciklus. A 6. sorban a számláló (counter) változó kezd6értéket kap. A 8. sorbeli for utasítás nem inicializál semmit, ám megvizsgálja, hogy a számláló kisebb-e ötnéL Nincs léptct6 utasítás sem, így ez a cik!usfej teljesen úgy viselkedik, minlha ezt írtuk volna: while (counter < 5)
6. ó... Vezériési szer1
6. t 1 lista - Egy üres for ciklusfej (forempty.cpp)
o: /1 6 . 11 List
,,
4: int main() 5, ( 6: i nt.. counter",O : II initi1'llli7.t'ltion 7: int max: 8: std : : cout « 'Hány hello legyen? '; 9: std " cin » max ; 10 : (or ( ;; ) II a for loop thett doesn ' t cnd 11 : 12 :
{
if (counter < max) II test { std : : cout « 'Hello!\n'; countcr++; /1 increment
13: 14 1 15 : 16 : 17 :
else break ;
18 :
19 : 20 : 21 :
return O;
Kimenet Hány hello legyen? 3 Hello! Hello! Hello!
A fo r ciklusfej a végsőkig ki leLL üresítve. Mind a ke:cdőérték megadása, mind a vizsgálat és a léptetés át lett helyezve máshová. A 6. sorban történik meg :lZ inicializáci6, még a for ciklus el6lt A feltételvizsgálat egy kOlön:í116 if-ben zajlik a 12. sorban, és ha ez sikeres, akkor a lS. sorban eggyel megnő a számláló értéke. Ellenkező eselben a 18. sorban l evő break hatására elhagyjuk a ciklusmagot. Bár ez a program valahol elég bizarr, vannak esetek. amikor pontosan egy for ( ; ; ) vagy egy while (1) ciklusra van szükségünk A switch utasítás t.1rgyalásakor látunk majd egy ésszen1 példát ezekre a furcsa ciklusokra .
122 11. rész · Bevezetés a C++ programozási nyelvbe
Üres for ciklus Annyi minden elvégezhető a for ciklusfejben, hogy néha már semmi teendő nem marad a ciklus mag számárA. Ilyen esetben tegyünk egy üres utasítást (;) a ciklusmag helyére. A pontosvessző lehet egy sorban a for utasítással , de ezt A 6.12 Lista rámutat, hogy hogyan érdemes ezt megírni.
könnyű
szem elól tt:veszleni .
6.1 2 Lista - for cikiul Orel 8 ciklusmaggal (fomullbody.cppt
o:
II 6.12 Lista
1 : II Az Qrell uLasitá!'l
2 : II elhelyezése ciklusmagként 3 : 'incluae
4, 5:
i n t ITIuin()
6: 7:
{
tor (int i - O; i<5; sta : : cout «
' i:
• «
i ++ «
std : : e nd l. )
8, 9:
10:
L
"L
"
-
rcturn O;
o
1
2
3
l , 4
A 7. sorb:m található for ciklus három utasítást tartalmaz. Az inicializációlétrehozza és beállítja az i változ6c A feltételvizsgálat ellenőrzi, hogy i kisebb-e ötnél, végül a léptct~si utasítás II kiírás után megnöveli iértékét. Nincs semmi egyéb teendője a for ciklusmagnak, így al ün..!s utllSítást (;) használjuk. Megjegyzendő, hogy ez nem valami csinosan megteJVezett for utasítás, mert a léptetési rt:sz messze túllépi feladatkörét. Szebben nézne ki a cikllIs az :L!{lbbi formában: 8: 9:
for (int i = O; i<5 ; std : : cout «
"i:
1++)
" «
i
«
endl;
Bár mindkét progr.tm ugyanazt csinálja, ez utóbbi példát egyszenibb megérteni. Azt is tartsuk szem e l őtt, hogy a ciklusfejben lélrehoZOll változók csak :I cikluson belül léteznek; a ciklus lefutása után megswnnek.
6. óra • Vezérlési
Egymásba ágyazott ciklusok A ciklusok egymásba ágyazhatóak, azaz lehet ciklus egy másik ciklusmagban is. A belső ciklus mind;mnyiszor végigpörög, ahányszor csak a külső ciklus magja lefut. A 6.1 3 Lista bemutatja, hogy hogyan lehet egymásba ágyazott ciklusokkal egy mátrix elemeit végigjárni és kiírni.
6.1 3 Usta - Egymásba ágyazott for ciklusok (fomested.cpp)
o: II 6.13 Lista l: II E9ymásba ágyazott for dklusok 2: ~incluae ]
,
4 : int m"in ()
S: { 6: 7:
8: 9 : 10 :
ll : 17. : 13 : 14 : 15 :
16 : 17 :
18 :
int rows , column s ; char theChar ; std :: cout« "Sorok száma? " ; std : : cin » rO\ll9 ; std : : COUI: « 'Oszlopok száma? " ; sta: : cin » columns; Gtd :: coul:« 'Milyen karakter legyen? ' ; std: : cin » theChar; for (int i = O; 1
tor (int j = O; j < eolumns ; jH) std : : eout « theChar ; std :: cout « ' \ n " ;
19 :
20 :
return O;
21 :
Sorok száma? 4 Oszlopok száma? 12 Milyen karakter legyen? x
xxxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxx
A progrn m felszólítja a felhasználóI, bogy adja meg a sorok és oszlopok kívánt számát, valamint a nyomtatandó karaklert. AJ. első for ciklus a 14. sorban lélrehozza és értékkel látja el az i számlál6t, s már futhat is a külső ciklus.
113
aC++ A 16. sorban va n a külső ciklus' magjának első som. lu indít juk a másik for ciklust. Egy másik ciklusváltozó (j) is inicializálódik, majd indulhat a bels6 ciklusmag. A 17. sorban kiíródik a kiválasztott karakter, majd a vezérlés visszatér a belsO' for ciklusfejhez. Észrevehetjük, hogy a belső ciklus csak egyetlen utasítás (a kiválasztott karakter kiírása). Megvi zsgá lj~lk a feltételt (j kisebb-e az oszlopok megadott számánál, columns-náD. Ha ez igaz, akkor kiíródik a következő karakter, és n6 egyet j értéke. Ez mindaddig folytlltódik. míg j értéke egyenlő nem lesz az oszlopok számával. Anukor a belső ciklus vizsgálata sikertelenül zárul, akkor mjr 12 db x kinyollltaL'lSra keruit; a vezérlés a 18. sarm kerul, ahol egy újsor kamkter íródik ki. A vezérlés ekkor a külső ciklusfejhez tl:r vissza, ahol a megfelelő feltéteJvizsgáJat zajlik (i kisebb-e a sarok megadott számánál, rows-ná1). Ha ez igaz-kén t értékeJ6dik ki , akkor újra lefut a ciklusmag és megnő i l:n l:ke. A küls6 ciklus második iterációs lépésében is elindul a a O kezd6értékel (t), és újra lefllt a teljes belső ciklus.
belső
ciklus. Itt j li jm Celveszi
Az egymásba ágyazott ciklusoknak tehát az a lényege, hogya belső ciklus annyiszor fm végig, ahány iter.ki6s lépése van a külső ciklusnak. igy a megadott kamktcr ~oszl o pszá m "-szor íródik ki minden sorban.
A switch utasftás Az if és else ... if utasítások kombinációja igen zavaró lehet b izonyos mélységű' egybeágyazáson túl. A CH ajánl is egy alternatívát. Az if-fe l szemben, amely csak egyetlen vizsgálatot végez, a switch (jelentése: kapcso/6) Ichct6vé teszi, hogy egy váltOZÓl tetsz6leges számú énékkel vizsgáljunk meg. A switch utasítás általános alakja a következ6: switch (kifejezés) {
case elsoÉrték :
utasítás ;
case másodikÉrték :
break; utasítás ; brci.lk;
case utols6Érték :
default :
utasítás ; break; utasítás break;
A kifejezés bármilyen helyes C++ kifejezés lehel, l!s az utasítás bármilyen C++ utasítás (vagy al.ok b\okk)a) lehet. J\. swit.ch kiértéke\i. a kife)C7J"!St, és al. eredményt sorban összeveti a case értékekkel. Megjegyzendő azonban, hogy a kiértl!kell!s csak egyenlO'séget ta rtalmazhat, relációs kifejezést vagy logikai mű've l eteket nem. Ha a case
6. óra • Vezérlési szel1
Apropó
A programozó hibamentesltése (de-faultozása)
Mindig érdemes default ágat használni a s witch utasrtásokban. Ha más miatt nincs is rá szükség, a fe ltételezetten lehetetlen érték tesztelésére és egy hibaüzenet kiírására mindig alkalmas; ez óriási segrtség lehet hibakereséskor. Bár nem kötelező, a legtöbb programozó a default esethez is tesz egy break·et a következetesség, hordozhatóság kedvéért. Érdemes tudato.sítani, hogy .unennyiben egy case ág végén nim:s break ut:lsítás, akkor a vezérlés r.'lcsorog a k övetkező sorra , a következő case ágra. Ez néha jól jön, de általában hiba. Ha úgy dönt a programozó, hogy engedi továbbcsorogni a ve7,krJést, érdemes ezt egy megjegyzéssel is dokumentálni, hogy nem pusztán elfelejt6dött a break . A 6.14 Lista bemutatja a switch utasítás használatál.
6.14 LiI1a - Aswl1ch ...."'. hum"... (.witchor.cppl
o: II 6 .14 Lista 1 : II A switch bemutatása 2 : ~include 3, 4 : int main () 5: { 6: unsigned s ho rt :i nt number : 7: std: : cout « "Adjon meg egy számot l és 5 között: 8: std : : cin » number: 9: switch (number) 10: ( 11 : case O: 12 : std :: cout« ' Sajnos ez túl kicsi!": 13 : broak ; 14 : case 5 : 15: std :: cout « " Remek dOnt és!\n" : II átcsorgunk 16 : case 4: 17 : std : : cout « ' J ó válasz tás ! \n' : / / átcsorgu nk 18 : case 3 : 19 : std : : cout « 'Kitun6 ! \ n"; II átcsorgunk 20 : case 2 : 21 : std :: cou t « 'Mesteri! \n" ; 1/ átcsorgunk
126
.rész · 22 : 23 : 24 : 25 :
26 : 27 : 28 :
case 1: std : : cout « break ; default : std : : <;:out « break ;
29 : 30 :
std : : cout « retllrn O;
"Hihetet.len! \n· ;
"Túl nagy! \n' ;
"\n\n" ;
31 :
-
Adjon meg Kitun6 ! Mesteri !
egy szá mot l és 5 között:
3
Hihetetlen! Adjon meg egy számot 1 és 5 között : 8 Túl nagy !
A progl1un felszólítja II felhaszn{1161, hogy adjon meg egy számol. EZL kapj:1 meg switch utasítás. Ha a szám nulla, akkor a s .....itch 11. sora ilh:szkedik a kereseti értékre, éS:l ~Sajnos ez túl kicsi! ~ üzenet jelenik meg, majd egy break utasítás vet véget a switch utasításnak. Ha a megadon énék 5, a program a 14. sorra . kapcsol ~, ahol egy üzenet kiírása után rácsorgunk a 17. sorr-.. , ahol egy újabb üzenet megjelenítése áll , és így tovább egészen a 24. sorig, ahol egy break utasíLásba botlik a vcz(:rlés.
:I
Az a végs6 hatása ezeknek a programsoroknak, hogya 2, 3, 4, 5 szá mok esct(:n több
üzenet is megjelenik. lia a sz:1m nincs a 0 . .5 intervallumban, akkor nagy, (:s a 25. sor default utasítása hívódik meg.
fe lt C:te l ezhctőcn
túl
Kérdések és válaszok Kérdés: Mi alapjánlJálasz/ az if .. . else és
II
switch J...'Óz t"ll?
Viilt/sz: Ha egy-két else ágnál löbb szerepel, és mindben ugyanazt a változót vizsgáljuk, akkor megfontolandó a switch használata. Ha nem egyenlőséget , hanem valamilyen reládót vizsgálunk (például a > b), akkor nem lehet a switch utasítást használni.
6. óra • Vezérlési szerkezetek 1127 Kérdés: Mi alapjáll választ a while és a d o ... while /Wzlll?
I "á/asz: Ha a ciklusmagnak legalább egyszer mindenképp le kell fULnia, akkor a do ... wh ile az ajánlott, egyébként :1 whil e használandó. Kérdés: Mi alapján /Jálaszt a while és a fo r Mzi/I?
la/asz: Ha inicializáini kell egy számláló változÓI, és minden alkalonmlal meg kell nö\"el ni, akkor a for ciklus használata javasolt. De ha már inicializálva van a változó, és nem kell minden lépésben növelni, a while ciklus jobb választás lehet. Kérdés: Melyik
CI jobb,
a while (l) vagy a for ( ; ; ) ?
I'á/asz: Nincs köztük sz:hnottev6 különbség.
Gyakorlatok Ezen az ó rán megismertünk néhány összetett vezérlési szerkezetet. Tudásunk elmélyítc.!sc végett válaszoljunk meg néhány kérdést és végezzünk el néhány gyakorlatot!
Kvfz I. Milyen adattípust érdemes használni a for dklusban? 2. Mi a különbség a break és continue között? 3. A 6.13 Lista egymásba ágyazott for ciklusokaI mut.Holt be. Egymásba lchel-e ágyazni whil e vagy do ... while ciklusokaI is? 4. Mit csinál a break parancs a s witch utasításon belül?
Feladalok l. Módosítsa úgy a f orl oop. epp programot, hogy lei:>eg6pontos (floa t) számot
haszniiijon ciklusváltoz6kénl, és nyomtassa is ki az értékét a ciklusmagba nl A léptett!skor 1 helyett növelje az értékét Ü,l-del. 2. Módosítsa úgy a fornest ed. cpp progmmot, hogy használjon for helyett whBe dklusokat! Előszór csak az egyik f or-l cserélje ki while-ra, majd a másikat is1 Ebből az is látszani fog, hogy különböző lípusú ciklusok is egybeiigyazhatóak. 3. Módosítsa úgy a swi teher . epp programot, hogy az egyik ca se ág tartalmazzon egy ciklust! Lefordíthat6-e és fut-e így a program?
128 1r. rész· Bevezetés a C+ + programozási nyelvbe
Válaszok a kvfzkérdésekre 1, A legtöbb progmmozó csak egész számot használ for ciklllsváltoz6ként, de ez nem a nyelv korlátja ; lehet lebegőpontos számot, szöveget vagy más adattípust is használ ni. 2. A break parancs hatására véget ér a ciklus, és a vezérlés a ciklust követő következő utasításra kerül. A continue parancs hatásárd a ciklusmag hátrdlevó részét átugorja a program, de a ciklusból nem lép ki a vezérlés (hacsak a kilépési fe ltétd nem teljesül). 3. Semmi akadálya! Maga a C++ nyelv bels6leg következetes. Lehel for <:iklusL h'lsználni while ciklusan belül, ezt akár do ... while cikluson belül stb ... a programozó szabad kezet kap e téren. 4. It. ~witch utasíthbeli break parancs hatására a progl"
II.
RÉSZ
Osztályok 7. 6ra 8. 6ra
AJapvetö osztályok Az. osztályokról - mélyebben
7. ÓRA Alapvető
osztályok
Ebben az órában a következl5krollesz szó •
Mi a tíPllS?
•
Mi az oszL!ily és :lZ objektum?
•
Ilogyan hozhatunk létre új osztályokat és azokba lllltoz6 objektumok:J.t?
Mi a tfpus? A típus egy kategória. Az egyik olyan léllyeges mlajdons:'Íg, amiben az t!mocr különbözik az tillatvilágt61 az, hogy képes katcgorizálni a dolgokat. Nem formákat tálunk sz:ízszámra a sZ:lVannán, hanem á llatokat és fákat. És nem csupán állatokat, hanem azon belül is gazellákat, l'.si ráfokat, elefánlokat, vízibiva Jyokat és így lOvább. Az ember rendszertanok:lt :llkotott, különböző bcsorol::'isokat, csopoltosításokat, fe[oszLíÍsokat és osztályoka\. Röviden: nü emberek természetünkb61 adódóan típusokban gondolkodunk. A narancs egy citrusféle, a dlrus egy gyümölcs, a gyümölcs növény, a növény pedig élőlény.
Hasonlóan besorolhatjuk az autókat, a házakat, a .személyeket vagy
13Z I II. rész' Osztályok Egy CH programozó igény szerint bármilyen típust létrehozhat. Ezek a C++ beépítet( típusaira épülnek - ilyen például az int, a long, vagy a double - és rendelkeznek azok minden képességével és lchclőségéveL Progf'.lmokat legtöbbször hétköznapi problémák megoldására írunk Ilyen például a munkatársak nyilvánt:lft<ísa va~,'y egy fűtési rendszer szimulálása, Noha egyszeru adattipusokkal - egész vagy kar:akter - is megoldhatunk összetett problémákat, igazán komplex feladatokat sokk.11 cgyszelUob objekrumokkal megoldani. Más szavakkal: sokkal e~,'y s7.eruób a dolgunk egy fűtési rend'izer szi.mulálásakor, ha különböző egyedi adattipusokal hozunk létre a szobára, a hőmér6kre, a termoszlátokra b; a fútőteslekre. Minél jobban modeUezzük a való életet típusokkal, annál könnyebben írhatjuk meg a programot
Új tfpus létrehozása Kor~lbban már bemutatwk a C++ beépített típusait, mint például az int (egész) és a cha r (karakter). Egy változó típusából sok minclCl1re kövctkeztethetünk. Például ha a Heigh t (magasság) és wi d Lh (szélesség) változókat előjel nélküli rövid egészeknek deklaráljuk, világos,m kiderül , hogy O és 65535 közötti értékeket vehetnek fel (feltételezzük, hogy az előjel nélküli rövid egész 2 bájtot foglal).
A mérete mellett a változ6 képességeire is kövctkeztethetünk a típusból. Például:1 rövid egészeket összeadhatjuk. igy ha a HeighL és Width változókat előjel nélküli rövid egészként deklaráljuk, akkor az összegüket értékül adhatjuk egy harmadik változ6nak. A változó tiplls:ib61 tehát :1
k övetkezők
derül nek ki:
• Amemóriaigénye • Milyen jel1egll információt tárolhanmk benne • Milyen mavclctcket végezhetünk rajta A CH 1chetóv(: teszi s:ljáttípusok létrehozását is a problémák megoldására. Új típusokat osztályok létrehozásával deklarálhatunk. Az osztály tehát nem más, mint egy (lj típus defmíciója.
Osztályok és tagok Osztályok létrehozásával deklacilhatóak Llj típusok. Az osztály változók - gyakran különböző típusúak - és velük kapcsolatos függvények összessége. Gondoljunk például úgy egy aut6m, mint kerekek, ajtók, ülések, ablakok, stb. összessége. Másképp is megközelíthetjük persze a dolgot: egy autó képes mozogni, lassítani, gyorsítani, megállni és így tovább.
7. óra • Alapvetó osztályok 1133 Az egységbe7..árás (encapsulation) nem más, mint egy dologgal kapcsolatos inf0n11áció--
iknak, és a kérdéses dolog képességeinek egy objekrumba
tön~n6 összegyűjtése.
Az egységbezárás - ha csak az autós példára gondolu nk - programozói szcmszögb6l igen hasznos. Minden adat egy helyen van, így igen könnyen hivatkozhatu nk rájuk, másolhatjuk vagy módosíthatjuk őket. Egy osztály ügyrelei vagy kliensei olyan oszt:ílyok vagy függvények , melyek az adott osztályt h:lsználják. Az e!,rységbe71írns lehet6vé teszi a kliensek számár::~ , ho!,'Y a műkö dési elv ismerete nélkül is használhassák az osztályt. Vezethetünk autót p61dául anélkül , hogy é rtenénk, hOb'Yan is mllködik egy bels6égésű motor. Ugyanígy a kliens osztály is használhatja az adolt osztályt anélkül, hogy belelátna annak működésébe. Azt kell csupán tudnia, hogy mit csinál , de azt nem, hogy hogyan. Az oszt:íly különböző változ6típusok és más osztályok tetszőleges kombirlációjáb61 állhat. Az osztály változóit tagv:í ltozóknak vagy adattagokn:lk hívjuk. Az Car osztály például rendelkezhet változókkal, mely az ülések számára, a r.ídi6 upusára, vagy a kerekekre utal és í~y tovább. A tagváltoz6k, vagy másképpen az adatlagok az osztályunk változói. A tagváltozók éppúgy réSzei az osztálynak , mint a kerekek és a motor az autó nak. Egy os zt~ly függvényei jellemz6cn a tagv:.'íltozók mód osítás{tr::t szolg!iln:lk. Rendszerint tagfüggvénynek V:lgy mctódusnak r,evezzük 6kel. A Car osztályban lehet például II Start () és a Br ak e () függvény , melyekkel a sebesség énékét állítlultjuk be. A Cat osztályb:tn hasonlóan tárolhaljuk az :'illat éJclkorfh és súlyá[, a mct6dllsok között pedig a Sleep(), II Meow () és a Ch aseMice () szerepelhet. A tagfüggvények az osztály függvénye i. A tagmggvények éppannyira reszei az osztálynak, mint II tllgváltozók, meghatározzák , mire képes az adott osztály.
Osztályok deklarálása Egy osztályt II cl a ss kulcssz6val deklarál link, nyitó és záró kapcsos zárójelek közön soroljuk fel a tagváltozókat és II tagrüggvényeket. A záró kapcsos zárójel után pontosvessz6t rakunk. így néz ki például a Cat osztály deklar:.'ici6ja: class Cat (
public : unsigned int i t sAg e; uns i gncd int i tsWeight ; )
,
Meow () ;
134 111. rész • Osztályok A eat osztály deklarálásával még nem foglalunk le memóriát, csupán közöljük a fordítóval, hogy milyen adatokatlárolunk (itsAge és az itsWeight vállOz6k) és aZl, hogy az osztály tagja i mire képesek (Meow ()). Ez azt is megmondja a fordítón ak , hogya eat osztály létrehOl.ásá hoz mennyi memóriára van szükség. Jelen esetben - ha az egész típus 4 bájlnt foglal - a eat osztály 8 bájt mérett1!esz: 4 bájtOl igényel az itsAge és 4 bájtot az itsWeight. A tagfüggvények - jelen esetben a Meow () - nem fog lalnak külö n tárhelyel.
Osztályok nevezéktan. A programozónak természetesen valamennyi osztá lyt, tagfüggvényt és tagváJtoz6t cl kell neveznie. A 3. fejezetbe n (Változók és konslansok) említettük, hogy ezeknek könnyen érthelC1eknek kell lenniük é.'i utalniuk kell a vá ltoz6 céljaira is. A Cat ,
Rectangle (tégla lap) és az Employee (alkalmazott) például megfelelő oszlálynevek. Meow ( ), H ChaseMicc () éS:l StopEngine () szintén megfelel6 függvénynevek, hiszen ut3lnak a fü ggvér1yek rendeltetésére. Számos progra mozó its (övé) e I6t:lggall(llja el a tagváltozóit, mir1t például: itsAge, i tsWeight, itsSpeed. Ez a megoldás segít megkülönböztetni 6kel azoktól a vállozókt6 1, melyek nem tagváltoz6k. A C++ kü lönbségel lesz .. kis- és n agybet űk közön , l=ppcn ezért minden oszt{llyt cél-
szem hasonlóan elnevezni. Ha így teszünk, nem kell azon törnünk a fejünket , vajon Rectangle, rectangle vagy RECTl\NGLE néven dekla rá lnrk-e az oszt[llyt? Néhány programoz6 például hizonyos el6taggal látja el az osztályok neveit ( példáu l eCat vagy ePer son), megint mások csupa nagy vagy csupa kisberút használnak. A könyv példáiban minden oszt(tly nevét nagybetűvel fogjuk írni. H:lsonlóképp bevett gyakorlat, hogy számos programoz6 a függvények neveit n:lgybea változók neveit kishel"űve l kezdi. Több sz6ból á1l6 változ6nevek esetén vagy aláhúzásjelet használunk (például ChaseJlice) vagy Illinden sz6 kezd6betűjét nagybetü"vel írjllk (példálll ChaseMice vagy DrawCircle). tűvel,
Fontos, hogy bármilyen konvenciót is követünk , az váljék szokásunkká 6s a programjainknál ragaslkodjunk hozzá. Id6vcl ki31aklll az egyéni stílusunk, nemcsak ~, nevekre, de például a behúzásokra, a kapcsos zárójelek helyére és a megjegyzésekre vonalkozóan is. Sz,ímos cég és fejleszt6i csoport rendelkezik hivatalos vagy ajá nlott kódolás! stílussal. Tanuljllk meg 6kel.
Objektum definiálása Az új típusunk egy objektumát - példányát - hasonl6 képp definiáljuk, IllÍnt például egy egész típusú változ6t;
unsigned int GrossWeight ; Cat Frisky ;
II definiálunk egy e16jel nélküli egészet
II defini áljunk egy Cal objektumot
Ez a kódrészlet definiál egy előjel nélküli egészet GrossWeight (bruttó tömeg) néven. Megad továbbá Frisky néven egy a eat osztá lyba tartozó, vagyis ilyen típusú objektumot.
Osztályok és objektumok A kedvencünket sosem hívjuk macskának, minden macska saját nevet kap. Ezzel emeljük a nappaliban htmyél6 kedvencünkct a macska általános iclcája fölé. C++-ban ha sonl6k6pp gondolkodunk. A eat osztály maga az idea, míg az cgyes példányok az osztályba tartozó objektumok. Frisky tehát a Cat osztály egyik eleme v.tgy példánYd, mely rendelkezik el6jel nélküli egész típusú Grossweight
Az objeknun egyszeruen az osztály egy tagja , amikor pedig egy osztály alapján lélrehozunk egy objektumot, :l zl az oszUíly pé ldányosításának hívjuk.
Osztályok tagjainak elérése t1'lilltán definiáltunk egy a eat osztályba taltoz6 objeklumol- jelen esetben a Frisky-l -a pont operátorral 5ivatkozhatunk az adott osztály tagfüggvényeire és tagváltoz6i ....J.. 1la például Frisky itsweight változójá t be szeretnénk állítani 50-re, akkor ezt a köve tkezőképpen tchetjük meg: Friaky .i tsWeight - 50 ; i lasonl6képp kell eljárnunk, ha meg szeretnénk hívni a Mcow () függvényt:
Frisky .Meow();
C++-b:lll sosem típ~l s hoz rendelünk értéket, l1:lnem változ6hoz. Az alábbi sor pékMul helytelen , amit a fordít6 jelezni is fog, hiszen :lZ egész típusho? nem rendelhetjük értékként :IZ 5-öt: int .
5; II helytelen
lia azonban már definiáltunk egy egész típusú vá ltoz6t, ahhoz ahhoz hozzárendelhetjü k az 5-öt: int
X;
x = 5;
II definiáljuk az x- et egészként II az x értéké t beá l litjuk 5-re
Röviden: tároljuk az 5-öt az x len a következő értékadás is:
nevű
változ6ban , mely egész típusÚ. Ugya nezért helyte-
Cut.itsAge=5 ; Elóbb létre kell hoznunk egy Cat típusú objeknunot, aminek a tagváltoz6jához aztán miír hozzárendelhetjük ,IZ 5-öt: Cat Frisky ;
Frisky . itsAge
=
5;
II akárcsak az x-nél; II akár csak x = 5;
Privát és publikus hozzáférés További kulcsszavak is előfordulhatnak egy osztály deklaráci6jában. Ezek közül a két legfontosabb public és a private.
,I
Minden adanag és tagfüggvény alapértelmezés szerint privát eléresú. A privállagok csak és kizárolag az adott osztályból érhet6ek el. A publikus eléresú tagokat ezzel szemben bármelyik osztályból e lérhe tjük. Ez a megkülö nböztetés famos, bár zavaró lehet. l logy világosabbá tcgyük a helyzetet, nézzük meg ismét a fejezet egy korábbi példáját: clafls Cat
unsigned int itsAge; unsigncd int itsWeight; Maow() ;
Ebben:] deklaráci6ban az i t sAge, az itsweight és :1 Meow () pr/vM láthatósággal bírnak, hiszen eZ :lz alapértelmezett. Ez azt jelenti, hogy ha másképp nem jclczzí.lk, minden lag privát.
•
Ha ennek ellenére megpr6báln5nk kívülrő l e lérni az itsAge adaltagol, a fordító hibát fog jelezni. Az al:'ibbi pélcb tehát működésképtelen: eat Boots; Boots.itsAgc:S; II hiba! nem férOnk
ho~~á
a~
adattagho~
Az osztálydefiníci6val gyakorlatilag utasítouuk a fordítót, miszerint az itsAge, itsWeight l!s a Meow() tagokat csakis a Cat osztály lagfilggvényei érhessék e l. Ennek ellenére az elGzG példában mégis megpr6bálruk kívülről elérni. Az, hogy Boats egy Cat típusú objektum, még nem jelenti azt, hogy Boats privát részeit is e lé rhetjük. Hogya Cat osztály tagjait
kívülről
elérjük, deklaráljuk
őket
publikusként:
class Cat public : unsigned int Iln!ligncd int Meow() ;
Most már az i tsAge, az itsWeight és a Meow () is publikusak. Éppen ezért a Baat. i tsAge=S sor is hiba nélkül le fog fo rdulni. Alapvető tervezési s7..abály, hogy azt adattagak lehetőleg mindig privát Játhat6s.'íggal rendelkezzenek. Éppen emian olyan publikus eljárásokat is létre kell hoznunk, melyek elérik (kiolvassák és módosítják) a privát aclattagokal.
7. 6,. . Ezek a függvények lehetóvé tt!szik, hogy különválasszuk az adattárolás módját és az adatok felhasználását. Ennek köszönhetően ha bármi megváltozik az adattárolásban, nem kell újrairnunk azokat a függvényeket , melyek ezekkel az adatokkal dolgoznak. Az elérhetóséget public és pr i vat e ku1cSSZ Cat public :
unsigncd int Age : unsigned int Wcight ; vo i d Meow () ; },
eat Frisky ; Frisky . Age "'" 8 ; t'risky .weight = 18 ;
Frisky . Meow{) ;
•
Vagy nézzük ezt: class Car {
public :
II a kOvatkez6 5 tag publikus
vo i d Start () ;
void Accelerat e( ); voi d Bukn() ;
void SetYear(int year); int GetYear () ; private , int Year ; Cha r Model [255] ; }
,
Car OldFa ithfui ; l nt bought; OldFaithf ul . SetYear(84) ; bought = OldFaith fu l. GetYaar() ; OldFaith f ul . St a rt() ;
II az utolsó kett6 privát II az ontá l ydeklarác i ó vége II 8 Car osztály egy példánya II lokális e16jelcs egész II értékadás II a bought változó 84 lesz II meghi vjuk a !l t art me tódust
A private kulcssz6val tehát korlálOzhatjuk az adauagok elérését és azt, hogy mikép-
pen módosítharjuk 6 kel.
137
38 II. rész •
Osztálymetódusok létrehozása Minden deklariUt osztálymetódust definiálni is kell.
Apropó
Tagmetódusok
A tagmetódusok (vagy tagfüggvények) definfciója az osztály nevével kezdődik, majd két darab kenósponttal, a függvény nevével, végül annak paramétere ivel folytatódik. A tagmetódusok (vagy tagfüggvények) deflníci6ja az osztály nevével kezdtXlik, majd kéc darab kelli5sponttal, a függvl:ny nevévd, végül ann:1k parnml:lcrcivel folyt:1t6dik. A 7. ] Listában be mutaljuk az egyszeninek mondható eat osztály teljes deklarációját, :lZ adalwg-hozzMérési függvényeket, ilklve egy általános célú tagfüggvl:nyt.
1.1 Liata - Egy egysz8ni _ly oIjúáui llimpl........,pl Q. 1,
1/ Bomutntjuk egy osztály deklarációját II és az oszLály eljárásaineok definiálását
2,
3, 4, 5: 6, 7: 8: 9: 10, 11 : 12 : 13 :
'includc
II oz az std : : cout miatt szükséges
class Cat II az osztály deklaráció kezdete { publi c : 1/ a publiku s rész int GetAge() ; /1 adattagelér ési függvény void SetAge (int agc); II adattagelérési függvény void Meow() ; II általános célú függvény private : II privát 9zukasz int itsAge; II tagváltozó J;
14, 15: /1 GetAge , publikus adattagelérési függvény 16 : II visszaadja az itsAqe tag értékét 17: int Cat : , GetAgc() 18 ,
(
19 : return itsAge ; 20 : 21 : 22 : /1 a SetAge publi kus def i níciója 2] : /1 publikus adattagelérési függvény 24 : II mellyel az it sAge tagot módosíLhatjuk 25: void Cat : : Set;a.ge ( i nt age) 26 : 27 :
28:
29 : 30 ,
31 :
II beáll í t juk az itsAge változót II melyet az age vál to zó bó l kapunk itsAge : age ;
7. óra • ]2 : / 1 a Meow met6dus de finici 6ja /1 nincs visszatérési értéke ]4: / I ni ncsen paramétere J] :
35 : / 1 kiirja a képerny6re . hogy "mcow ' ]6 : ]7 :
void Cat : :Mcow() (
std : :cout « ·Meow. \n'; ]9 : 40 : 4 1: /1 lét r ehoz za a macska egy példányát, beállítja az életkorát 42: II nyávog egyet , megadja az életkorát és újra nyávog 43 : i nt maln() 44 : { 45 : Cat Frisky; 46 : Frisky.SetAge(5); ]8 :
47 : 48 : 49 :
50 :
51 : 52 :
Frisky.Hcow{) std : : cout « std :: cout« Frisky.Meow() return O;
; 'Prisky i a a cat who is • ; Frisky. Get Age() « ' years old . \n' ; ;
Meow. Fri s ky is a cat who is 5 years old . Meow.
Az 5. sorról a 13. sorig taltiljuk a Cat osztá ly definícióját. A 7. sorba elhelyeztük
a public klllcsszót, mely jelzi a fordítónak, hogy publikus tagok következnek. A 8. sor a Ge tAge () ada tcag-cl~ rési függvé ny deklar:lcióját tanalmazza. En nek segítségével é rhetjük el az itsAge tanaImát, melyct a 12. sorban deklaráltunk. A 9. sorban helyet kapou a SetAge () . mely a megadon paraméterrc beállítja az itsAge értékér. A 11. sorban kezd6clik a Cat oszlály privát e l érésű része, mcly csupán a 12. sorban deklará lt itsAge tagváltozót tartalmazza. Az osztá lydeklarációt záró kapcsos z:hójellel és ponrosvessz6vel zárjuk le. A 17. és a 20. sorok között definiáljuk a GetAge () tagfüggvényt. A metóc!us nem rendel kezik pamméterrel és egész értéket ad vissza. Ne feledjük: a metóc!usok neve előtt szerepel az osztály neve és kél darab ken6spom. Ez jelzi a fordító számám, hogy a Cat oszlály függvé nyét definiáljuk. A fejléc kivételével a Get Age () függvé ny definiálása megegyezik a többi függvényéveL A GetAge () függvény csupán egy sarbói áll, és az itsAge é rtékét adja vissza. Ne fek..ujük: a main{) függvényb61 nem érhetjük el a Cat osztály privát elérésű itsAge, vi-
139
140 III. rész • Osztályok szont elérhetjük a publikus GetAge () metódust. Mimhogy ct GetAge () a Cat osztály tagfüggvénye, így eléri az itsAge vá1toz6L Ezzel már a main () függvény számánl átadható az i t sAge értéke. A 25. sor tartalmazza ct Set Ag e (l definícióját. Egész típusú paramétert Váf és ez alapján beállítja az itsAge értékér a 29. sorban. A Se tAge () ct Ca t oszLály tagfüggvénye, (gy tdjes joggal kezelheti az itsAge tagváltoz6t. A Meow () metódus implementációja a 36. sorban kezd6dik. Ebben az egysoros függ-
'lényben csupán kiír.ujuk ct Meow szót egy soremeléssel a képerny6re. (Emlékszünk ugye, hogy a \n új soremelést eredményez?) A 44. sorban kezdődik a már jól ismert main () függvény. Ennek nincs bemenő paramétere, és üres (void) a viSSZatérési értéke. A 45. sorban létrel10zunk a Cat osztályból egy példányt Fr i s ky néven. A -i6. sorban beállítjuk a Se t..Age () függv~ny segítségével az it s Age v1illozóját 5-re. Az elj{u"ást a példányazonosltóval, egy pontral és a metódus nevével hívjuk meg. Eh.hez hasonlóan bármely más met6clusl meghívhatunk az osztályb61.
A 47~ sorban meghívjuk a Meow () tagf"Üggvényt, :l 48. és 49. sorokban kiolvassuk ~s kiíratjuk az i t sAge értéké t a GetAge () adattag-elé rési függvé ny segílSégével. Az 50. sorban újra meghívjuk a Meow () függvényl.
Konstruktorok ós destruktorok, avagy az objektumok létrehozása és törlóse Kétféleképpen definiálhatunk egy egésllípUSLI vá ltozót. Definiálhatjuk elóbb magát a vlÍltozót úgy, hogy é rtéket csak később adunk neki: int Weight ;
II változ6 definiá lása II programk6d Weight = 7 ; II értékadás
Ugyanakkor definiá lás után akár azonnal is inicializálhat juk: int Weight - 7, II definiálunk és inici alizálunk egyszerre Ez a m6dszer egyesíti a definíciót és a kezdeti értékadásl. A v:í!tozó később persze szabadon módosítható, az inicializálás azonba n garantá lja, hogya változónk nem fog értelmetlen értéket tartalmazni. Mi a helyzet egy osztá lyadattagjaival? Az osztályok példányosít.ásakor lefut egy speciális tagfüggv~ny , a konstruktor. Ennek a feladata az osztály egy érvényes példányának létrehozása. A gyakorlatban ez azt jelenti, hogy az adattagokat induló értékekkel l:'ítja el. A konstruktor eljárás neve megegyezik az oszrályéval , de nem rendelkezik visszatérési értékkel. Bemenő pammétereket viszont kaphat, ahogyan bármely más metódus is az osztályon belül.
7.6ra • Ha van konstnLktorunk, ajánlott cleslnlklort is deklarálni. Ahogy a konstruktor létrehozza és inicializálja a példányt, úgy a destruktor az objeb.w m megszűnésekor eltakaritja az objektumot és az esetlegesen foglalt memóriát felszabadítja. A destruktor neve u&'Yanaz mim az osztályé de - (tilde) karakterrel kezdődik. A destruktornak nincs bemenő paramétere és visszmérési é rtéke sem. A Cat osztályhoz például így kell destnlktort írnunk: -Cat cl ;
Alapértelmezett konstruktorok Ai': alábbi sorral: Cat Frisky(5);
rulajdonképpen a Cst osztály konslr1lktorát hívjuk meg, amely jelen esetben egy paramétert kapott (S). r la paraméter nélkül hívjuk meg, úgy clhagyhat6 a zárójelpár és az alapértelmezett konstruktor fulle. Cat Ft"i s ky; Az alapértelmezett ktmstruktor nem kap paramétereket.
Afordlt6 által biztositott konstruktor Ha egyáltalán nem deklaráltunk egy osztá lyhoz konstnLkton, úgy a fordító autom:uikus.1 n létre fog hozni egy alapértelmezcner. (Ne feledjük, az alapértelmezett konstnLktor nem fogad paramétert.) A fordító által kínált alap&rtelmezett konSlnlktor nem csinál semmit. Mi is deklarálhanmk ilyent, hiszen nem kell mást tennünk m.im üresen hagyni :I konstnLktor törzsét. Ehhez :lzonban [:utazik két fontos megjegyzés: 1. Az alapénelmezeu konstnLktor nem kap paramétereket. Definiálharjuk mi magunk is, vagy rábízhatjuk ennek létrehozását a fordítóra is. 2. Ha létrehozunk bá rl1li~)1C1I sajál kOnstnlktort (paraméterekkel vagy anélkül) a fordító nem ad a lapértelmezett konstmktort. Ebben az esetben ezt is nekünk kell definiálni.
A dCSTnlktor hiánya esetén hasonlóképpen jár el a fordító. Ha nem adunk meg destruktort, akkor kapunk egy üres törzsűt, amely nem csinál semmit. Ha azonban deflniáltunk bármilyen konslmktort, :Ikkor mindenképpen definiáljunk destrukton is, még akkor is ha üres és valójában nem csinál semmit. Noha az alapértelmezen destruktor is tökéletesen teszi a dolgát, a saját destmklor átláthat6bbá teszi a k6dunkat, egy üreset megírni pedig igazán nem nagy munka.
141
142 111, rósz' Osztályok A 7.2 listában a Cat oszlály kapou egy konstruklon, mely a Cat példányokat hivalaU inicializálni úgy, hogy beállítja az általunk megadotl életkort. Destruktor nélkül nem lenne teljes a program, így bemutat juk azt is.
7,2 Lista - Ko..truktorok Ú destruktorok (_.Optl) o: 1/ Bemutat juk konstruktor és destruktor 1: 2: 3: 4, 5: 6: 7, 8, 9, 10 : 11 : 12 : 13 :
/ I deklarációját il Cat osztályban #inc!ude II az std :, eout miatt using std : : cout; 1/ mindig az std: :cout-ot használjuk e forrásban
class Cat {
II az osztálydeklaráci6 kezdete
public:
II publikus rész 1/ konstruktor II destruktor
Cat(int initialAge) ;
14 :
-Cat () ; int GelAge () ; 1/ adattagelérési függvény void SetAge(int age) ; II adattagelérési függvény void Meow ( ) ; private: 1/ privát rész int iteAge; II tagváltoz6
H:
):
16 : 11:
18 :
II a Cat konstruktora Cat : :Cat( i nt initialAge)
19,
{
20 : itsAge • initialAge ; 21 : ?2 : 23 : II destruktor , al1\ely nem cs inál senunit 24 : Cat : : ~Cat() 25 : { 26 : 27 : 28 : 29: II GetAge, publikus adattagelérési tüggvény 30 : II visszaadja az itsAgc tag értéké t 31 : int Cat : :GctAge() 32 : 33 : rúturn ltsAge; 34 : 35 ,
36 : 37 :
38 : 39 :
40 : 41 : 42 : 43 : 44 : 45: 46 :
II a publikus SetAge definíci6ja II a dattagelérési függvé ny void Cat : : SetAgc {int age) { /I beállítja az itsAge értékét /I a paraméterul kapott értékre its1'lge = age;
II a Mcow met6dus defin íc i 6ja II visszatérési értéke : üres (void)
7. óra • 47 : 48 : 49 :
II paramét erek : n incsenek II mit cs i nál: kiirja a képel,"ny6re , hogy meow
void Cat : :Mcow()
50 : cout «
51 :
52 : 53 : 5<1 : 55 : 56 : 57 : 58 : 59 : 60 : 61 :
62 : 6] :
64: 65 : 66 : 67 :
](j
'Meow.\n';
1/ létrehozzuk a macs kát, beáll!tjuk az életkorát 1/ nyá vog egyet, lekérdezzük a z é let korát , majd i.!; mét nyávog int main()
Cat Frisky(5); Frisky.Meow() ; cout « 'Prisky is a cat who is • ; cout « Frisky . GetAge () « • years old . \n' ; Frisky . Mcow ( ) ; Frisky.SetAge(7) ; cout « 'Now Frisky i s • ; cout « f'ri sky . Go tAge (J « • yoars old . \n " ; return O;
•
Meow, Frisky is a cat who i s 5 yeal,"s old . MCQw. Now Frisky is 7 yenrs old .
A 7.2. List:1 hasonlít a 7.1-esre, kivéve, hogya B-as sorban bÓvÍletrük egy konSlruktorml. mely egész értéket vár, illetve a 9. sorban egy destruktorml, mcly ncm fogad pamméterekeL A destruktorok sosem kapnak paraméterekct, illetve se a konstruktor, se a destruktor nem ad vissza értéket, még üreset (void) sem. A 18. és 21. sor között megfigyelheljük a konstruktor implementádóját, mely nagyon hasonló a Set Age () adaltag-elérési függvényhez. A 24. és 27. sor kÖ7.ötl megfigyelhetjük a -Cat () destruktort. A függvény nem csinál semmit , de muszáj megadni , amennyiben az osztálydeklaráci6nál megadtuk. Az 58. sorban példányosítjuk a Cat osztályt Frisky néven. Frisky konstruktom 5-öt kap meg paraméterként. Nem kell meghívnunk a SetAge () függvényt, hiszen Friskyt lmÍf eleve úgy példányosíljuk, hogy az itsAge változóba 5 kerül, bizonyíték erre a 61. sor kimenete. A 63. sorban Frisky itsAge változóját m6<.losítjuk, melyet a 65. sorban kérdezünk le újra.
143
144 , II. rész •
OSZtálYaI<
Ausing kulcsszóról bővebben Ebben a példában találkoztunk a us ing kulcsszóval. Ez (namespace módosító nélkül. amelyet az előző órában mutattunk be) közli a fordítóval, hogya cout függvényt mindig az std függvénykönyvtárban keresse. Ha más, szintén a szabványos könyvtárból származó függvényeket is használunk (ilyen például c in), akkor ezt vagy mindenütt std: : cin fonnában kell megadnunk. vagy hasonlóképpen kell eljárnunk, mint a 3. sorban.
Kérdések és válaszok Kérdés: MilyeJ/ Hagy egy osztálypéldány? Vá lt/sz: Egy osztály egy péklányának helyfoglalása mindig az adanagok helyfoglalásá-
nak az összege. A t:lgfüggvények nem az objektuman belil! foglal ják a memÓri[tt. Nl!h:íny rordító 2 bájlos változó esetén 2 bájtnál valamivel többet foglal Je. Err61 további részletekkel
Kérdés: Mlc'!I1/1em szabad pub/ikllsnak definiálni az adaffagokal? Válasz: Az adauagok privát elérése anélkül teszi lehet6vl: az osztá ly felhaszná l6ja számára !lZ adatok használatát, hogy ismerné az osztályon belüli ad:ntárolás és adaúeldolgozás mikéntjét. Például ha a e a t osztály rendelkezik egy GetAge () met6dussal, akkor :I Z osztály kliensei lekérdczhetik az életkort anélkül, hogy nJdnák, miképp tárolja :IZ az életkort, v:lgy hogyan számolja ki azL A publikus adat ezzel szemben olyan, mint :I globális változó. Bármely parancs hozzáfér, ha pedig megváltozik, akkor hosszú és kellemetlen hibakeresés következik.
Gyakorlatok Most, hogy már lLldunk egyet-s-mást az objeklumokr61 és osztáJyokr61, válaszoljuk meg a kvízkérclés€:!ket a gyakorlatok segítségéve! pedig mélyíL'iük cl a megszerzett tudást.
Kvfz 1. Mi a különbség az osztály és az objekmm között? 2. Mi a kü lönbség a public és a private elemek között egy osztályon belül?
3. Mit csinál egy konstruktor? 4. Mit csinál egy destruktor?
7. óra •
Feladatok l . Módosítsuk a simpleclass . cpp-t úgy, hogy létrehozunk egy második macskát Spot néven. Nyávogásm tudjuk bírni? 2. Mi történik a simpleclass . cpp-hen, ha megpróbáljuk megváltoztatni az i t sAqe változót a main () függvényból? Működik az itsAqe++ vagy a Frisky. itsAge ++ kifejezés? Milyen következtetéseket vonhatunk le a publikus és privát elérésekkel kapcsolatban? 3. Módosítsuk II simpleclass . cpp-t úgy, bogy az it!}lIqe publikus változó legyen. Most hajtsuk végre újr:... a 2. feladatot! Ezután a main fiiggv(:nyb61 is 110zóférünk Fri sky adataihoz, viszont így sokkal nehezebb megmondani, hogy hol rontottuk el (:s miért kapta a változó ;IZ aktuális (;rtékét.
Válaszok a kvfzkérdésekre 1. Az osztály egyfajta leírás vagy definíció, mely nem foglal memóriát. Az objektum
ennek az osztálynak egy megvalósítása (példánya). 2. A privát adatok és metódusok (függvények) csupán az adott osztályból érhet6ek el. A publikus adatok és metódusok (függvények) az oSltályon kívülr61 is elérhet6ck. Gyakorlatilag érdemes az osztályadatokat privát cléréslívé, a metódusokat pedig publikllss{\ tenni. 3. A konstruktor létrehozza az objektumot az osztálydefinki6 alapján. Konstruktor létreho7ltsát rábízhatjuk a fordító"l is, amennyiben nem bonyolult a progmmunk, dc összetett objC;! ktumn ~1 megírhatjuk mi is. A saj:ít konstmktorok készítésével nemsokárn részletesen foglalkoZl.lnk. 4. A d/;!struktor végzi :1 takarítl'isl, ha egy objckl"umrn már nincs szűkségünk. Konstruktor létr/;!hozásác rábízhmjuk a fordítóra is, amennyiben nem bonyolult a programunk, de összetett objektumnál megírhatjuk mi is. A saját destnJkLorok készítésével hamarosan részletesen is foglalkozunk.
145
8.
ÓRA
Az osztályokról - mélyebben Ebben az órában a következő kről lesz sző: • Mik azok 3 konstans t.agfüggvények • Hogyan különíL"ük el az osztá ly keze16 felülctét a megva16sítását61 • Hogyan kezeljük osztályainkat, és hogyan vegyük rá a fordít6program ot, hogy segítsen megtalálni és elkerülni a progmmhibákal
Konstans tagfüggvények Ha egy osztály tagfüggvényéL const -nak deklaráljuk, ezzel megígérjük, hogya metódus nem fogja megváltozt:ttni semelyik osztály tag értékét Úgy deklarálhatunk egy osztálymetódust konstansként, hogya const ku!csszót a zárójelek urán (a pontosvessző elé) írjuk. Példaként deklaráljuk a SomeFunction () konstans tagfüggvényt úgy, hogy ne legyen egyetlen argumentuma sem, és csak az üres típust adja vissza. Ez a következőképp néz ki: void SomeFunction() const ;
148 1 ll, rész • Osztályok A hozzáféfŐ függvényeket ("gel accessor" vagy "geffe!" metódusokat) h'Yakmn konstans függvényekként de klar:ílják a const módosító segíL'iégével. A Ca t o5zt.álynak két hozzá férő
függvénye van:
vo i d SctAgc(int anAgel ; int GetAge () ;
A SctAge () nem lehet konstans, me rt megváltozt3tja az iLslI.ge tagváltozót. A GetA.ge () viszont már lehet konstans, és érdemes is annak választani, mert nem vál(oztat a7. osztályon ; egyszen1cn csak visszaadja az ItsAge tagváltoz6 aktuá lis értékél. fgy ezen függvények deklarációja az alábbi módon tönénhet: void SctAgc(int anAge) ;
int GetAge () const;
!la cgy függvényt k()nst~Hlsként deklarálunk, majd a függvény megval6sításába belekerül valamely objektum (a;wz pékl[1U1 valamely mgválloz6 é rté ké nek) megváltoztatása, akkor ezt a fordít6prognull hibiin:lk könyveli el. Ha példúul úgy írnánk meg a GetAge ( ) -et, hogy tal1s.1 sZ:lmon, bogy il Cat húnyszor kérdezte le a korát ("' get age), akkor ez fordít<'isi id6beli hibát gcner.i1na. Ez ugyanis változtaln:l a Co!It objektumon.
Ha csak lehet const Erdem,,, konstanst használni, amikor csak lehet. Deklaráljuk konstansként a tagfüggvényeket, ha azok nem változtathatnak az objektumon. Ezáltal a fordftóprogram több hibát szürhet ki, ami gyorsabb és olcsóbb, mint ha a programozó foglalkozna ezzel.
Jó programo:dsi gY:lko rl:lt, ha
a le hc16 1egLÖhh met6dust ko nst:lnskénr clekl:lrfLljuk. Vala hányszor így teszünk, felh:l!:l lm:lzzuk a fordít6progra mol, hogy téveszt(:seinke l k or~n fe lfedezze; még mielőtt azok futás i időbeli progmmhibá kká U:pnének elő. Ugyano lyan okból érdemes konstans függvé nyeket használni, mint amil!rt konstans változ6k:lt.
Felület és megval6sftás Ahogy korábban tanultuk, az ügyfelek a prognlm azon összetevői, amik létrehozzák és használják osztályunk objektumait. Úgy is elkl!pzelhetjOk osztályunk felületét - azaz :lZ osztály-deklar:'idót - mint egy s zerződést ezekke l az ügyfelekkel. A szerződés felsoro lj:l osztályunk felhasználható adatait, meghatárm:za osztá lyunk viselkedését. A eat osztály uckla r:k i6jában például olyan ~ze rz6dést írtunk, amely szerint minden macska (Cat) tudhatja és kinyerheti saját korát; valamint, hogy létrehozáskor inicializál ható ez a kor, és később beállítható vab'Y kinyerhető , és hogy minden macska tud nyávogni: Meow () . Ha a GetAge () -ct konstans függvényként hozzuk létre (ahob'Y azt érdemes), akkor a sze rződés arm vonatkozóan is ígéretet tesz, hogya GetAge () nem fog változtatni a macskán, ami meghívja
Tudta hogy... ?
Miért használjuk hibakeresésre a fordítóprogramo!?
A programozás véres valósága azt mutatja, hogy egyikünk sem ír hibátlan kódot. Nem az különbözteti meg a profi programozót a kezdőtől, hogy az előbbi nem vét hibát; hanem, hogy ő még azelőtt megtalálja azokat, mielőtt tennékét vízre bocsátja. A fordítás idejű hibák - melyek a programfordítás alatt deriilnek ki - sokkal jobbak a futási idejű hibáknál, melyek a program futásakor ütköznek csak ki. A fordítási idejű hibákat nem lehet nem megtalálni. Viszont számtalanszor le lehet futtatni egy programot úgy, hogy annak vezérlési útja nem fedi le a teljes forráskódot; így a futási idejű hibák meglehetősen sokáig rejtve maradhatnak. A fordítási idejű hibák rögtön a fordításkor kiderülnek , így könnyű azonosítani és javítani őket. Aminőségi programozásnak pontosan az a célja, hogy ne maradhassanak futási idejű hibák a kódban. Ennek kipróbált és jól bevált módszere a fordítóprogram meg· felelő kihasználása; segítségével a tévesztések még a fejlesztés korai fázisaiban fel· térképezhetőek.
Természetesen elképzelhető, hogy programunk hibamentes, csak épp nem azt teszi, amit várunk tőle. Ezért szükségünk van minöség-ellenőrző, tesztelő csapatra is.
Hová tegyük az osztály-deklarációt és a metódus-definfciókat? Oszt:1lyunk minden egyes deklarált függvényéhez meg kell írnunk a megfelel6 definíciót. Ezt más néven a függvény implementáci6jának, megvaI6sítás.in:lk hívjuk. Mint minden más függvénynek , az Osztálymet6dus definíci6jának is van fej része és törzse. A dcfiníci6n:lk olyan fájlhan kell lennie, melyet a fordítóprogmffi megtalál. A legtöbh C++ fordít6 azt várja, hogy ennek a fáj lnak. c vagy. cpp kiterjesztése legyen. Könyvlinkben a . cpp-t fogjuk használni , de érdemes megnézni :I kedves olv:l."ó álL:!1 hasznúlt fordítóprogmm elvárásait. Apropó
A forráskód állományainak kiterjesztése
Sok fordítóprogram azt tételezi fel, hogya. c kiterjesztésű állományok C nyelvű prog· ramot tartalmaznak, a . epp kiterjesztésűek pedig C+ + nyelvűt. Valójában használha· tó bármelyik, de csökkenti a zűrzavart, ha . cpp·t használunk C++ programjainkhoz. . cpp állományokat fel kell venni a projektfájlba v:lgy a make file-ba. Ennek konkrét módja a használt fordítóprogramtól függ. H:I integrált fejlesztői környezetet használ (TDE-ú, olyasféle menüpontot érdemes keresni, hogy jájlok hozzúadása a projekthez". A progr..lmhoz tartozó minden .cpp állományt hozzá kell adni a projekthez, melyről azt szeretnénk, hogy bekeriiijön (fordítás és linkelés után) a végső furratható fájlba. Ezekcll~
Bár megvan a szabadságunk arra, hogy a deklarációt a . cpp forrásfájlba illesszük , ez nem túl jó programozásbeli gyakorlat. A legtöbb progmmozó által elfogadott közmeg-
150 I II. rósz· Osztályok
egyezés szerint a deklarációkat fcjléc:íllományokba érdemes tenni, melyek hasonl6 nevet viselne k, ("sak a kiterjesztésük . h j . hp vagy. hpp . Könyvünk ben. hpp-t fogunk e rre a etIm használni, de érdemes megnézni fordit6programunk beállításait. A Cat osztály deklarációja például a cat . hpp állományba kerülhet, míg az osztálymetódusok deAnícióit a cat. hpp állományba. Úgy junathatom a fejlécillomány tartaimát a cat . cpp állományba, hogy ennek el ső soraiba beírom: llinclude · cat . hpp·
Ez arra utasítja a fordít6programot , hogy enn(;!k a sornak a hcly6re emelje be a cat . hpp állo mányt; mintha csak erre a helyre le nne beírva annak tartalma. Na dc akkor miért is kell elkülöníteni ('iket, ha a végén ögyis össze leszne k párosítva? Ügyfélprogntmjaink:it a legtöbb cselben nem érdeklik a z egyes függvényck konkré t megval6sírás,ínak apró részletei. A fejlécállománybó l mindent megrudnak, amire szükségü k van; felesk:ges lt:nne a megva lósítási fájl okkal foglalkozniuk.
Apropó
Osztályok deklarálása
Egy osztály deklarációja mondja meg a fordítóprogramnak, hogy mi is ez az osztály, milyen adatai és tagfüggvényei vannak. Az. osztály-deklarációt más néven az osztály kezelófelületének (interfésll is hIvják, mivel ebból derül ki a felhasználó számára, hogy mit tehet az adott osztállyal. Ezt általában egy . hpp fájlban, azaz fejléc állományban tároljuk . Egy függvény definíci6ja Irja le tészletesen, hogy hogyan muködik az adott függvény. Ezt más néven az osztálymet6dus megvalósításának hívjuk, és egy . cpp fájlban tároljuk. Az. osztály megvalósításának részletei csak az osztály megalkotójára tartoznak. Az. osztály ügyfélprogramjainak - azaz a program azon részeinek, ame· Iyek használják az adott osztályt - nem kell (és nem is érdemes) tudniuk, hogy hogyan valósul meg egy-egy függvény. Ennek a megközelltésnek az a fő előnye, hogy könnyen meg lehet osltani osztályokat; azaz mások példányosíthat ják az általunk tervezett objektumokat, és mi is használhatunk mások (munkatársaink vagy kereskedelmi eladók) állaliéirehozott osztályokat.
Helyben kifejtett (inline) megval6sftás Ahogy egy normál Függvény esetében is utasíthatjuk a fordít6progrnmor, ho1;,')' helyben kifejtett fi.\ggvényként ford ícsa Ic azt, ugyanígy t e h ető helyben kifejtetté egy osztálymetódus is. Ehhez a visszatérési 6rték elé in inline kulcsszót haszn:'i lhatjuk. A GetWeight () Függvény helyben kifejtett megva l6sítása például így n(:z ki: inlinc int Cat : : GetWcight () {
rcturn itsWeight; II Vissza adj a az adattag súlyát (Wcight)
8. óra •
Úgy is helyben kifejlellé lehet6 egy függvény , hob'Y az osztály-dekJarációba írjuk a dcfmícióját. Például: class Cat {
publ i c : int Get Weight ( ) const { return i t sWeight ; ) II i nline void SetWeigh t(inL aWeight) ; Figydjük meg a GetWeight () dd'lníciójának szintaxis:ít. A helyben kifejtett függvény lörLSe rögtön az osztálymctódus de klarációja után kezd6dik, a7..az n incs pontosvessz6 a zárójelek után. Mint bármely más függvénynél, in is kapcsos zárojelek között szerepel a definíció. Ahogy már megszokhaltuk, a térköz karaklerek nem sz5mítanak, azaz mind ez így is írhar6 : class Cat {
public : int Getweight() const {
return itsWeight ; II inline
void Sctweight(int aWeight);
A 8 .1 és 8.2 Lista is létrehozza a eat osztályt; külön kerül a d eklarádó (cat . hpp) és függvények megvalósítása (caL . cpp). A 8.1 Lisla II hozzáfér6 függvényeket és ti nyávogó (Meow (» függvényt is belyben kifejletté teszi.
:I
8.1 Usta - A Ca. osztály daklaráció;a a cat.hpp fájlban
o:
tinclude
"2 ,
class Cat
3,
10, 11 :
public , Cat (int initialAge) ; -Ca t () ; i nt Ge tAge() ( retu r n i t sAge ;) void SetAge (int agc) { itsAge '=' age ; } void Meow() { std :: cout« "Meow. \n" ; } private : int itsAge;
12 ,
};
4: 5:
6:
7,
s, 9,
II inline ! II i nlinc ! II inBne !
8.2 usta - A Cat osztály megvalósitása a cat.cpp fá~ban
o:
/1 Helyben kifejtett (inIinc) faggvények bemutatása
l: 2:
/I és a fejlécállomány(ok) beemelése hnc l ude 'caL hpp ' II Ne feledj ak a fejlécállomány beemel ését!
3,
152 111. rész' Osztályok 4: 5.
Cat : : Cat (int initialll.ge )
6:
it sAgc
~
initialAge;
7.
8. 9:
Cal: : -Cal( )
10 :
{
ll : 12 : 13 :
} II Létrehoz egy [J\(Icskát (cat) ; beállítj a a korát , /I megnyávogtatja, lekérdczi a korát. majd új r a ny á vogtatja . int main{)
H : 15 :
16 : 17 :
Cat Frisky(S) ;
18 :
Frisky . Meow() ;
19 : 20 :
std : : cout « std :: cout«
2l: 22 : 23 : 24 : 25 :
Fris ky .Meow(); Frisky . sc t Agn (7); s t d : : OOIJl. « ' Frisky most. már' std : , cout «
" Frisk y egy • ; Fris ky. Gct Age() «
Frisky . GetAge() «
• é v es macs ka . \ n ' ;
éves . \n ";
return O;
26 :
m
Mcow .
Frisky egy 5 éves ma c9ka . Mcow. Frisky most már 7 éves .
A GetAge () -el a R 1 Lisu 7. sorában dcklaráljukj in nonna! gondoskodunk a helyben kifejtett megvalósltásról is. A 8. és 9. sorok további helyben kifejtett függvényeket adnak meg; ezek megvalósítása ugyanaz marad, mint a korábbi .távolabb kifejtett" megvalósításokban . A 8.2 Lista 2. soráhan a #i nclude "cat . hpp· beemeli a caL . hpp tartaImát (am i viszont a cout számára szükséges iost r e am-et emelte be :lZ ;. sorban).
Osztályok beillesztése más osztályadattagjaiként Nem ritka, hogy egy bonyolultabb osztályt úgy építünk fel , hogy egyszerűbb osztályokat használunk fel h07.711; azaz deklarációikat beilleszrjük a bonyolultabb osztály deklarációjába. Egy Autó osztály felépülhet például Kerék , Motor , Sebességvá l tó stb. osztályoklxll. Ezeknek a deklarációknak rész jellegük van, azaz az autó részei a kerekek, a motor és a sehességváltó.
8. Vab'Y tekintsük a következő példát: minden téglalap szakaszokb61 áll. A szakaszokat két \'égpontjával jellemezhetjük. A rOnloka! x és y koordinátáik halározz.'ik meg. A 8.3 Lsta a Rectanglc ( Téglalap) osztály teljes deklarki6ját mutatja ( rect . hpp). A téglalapot a négy csúcsát összekötő négy szakasz jeleníti meg; minden pont egy koordinátapárml van megadva, ezért először a Point ( Polll) oSltályt kell megadnunk (mc1y :mnak x és y koordinátájál tartalmazza). A 8.4 Lista mindkét osztály deklarficióját bemutatja.
8.3 Ulta - A rect.hpp egy teljes osztály deklarációját mutatja (fejléc).
o: 1:
II rect. hpp kezd6dik lincludc
3:
clöss Point /1 Tartalmazza az x,y koordinátákat
4:
{ II nincs konstruktorl az alapértelmezés has~náland6 public : void Setx ( int x) ( itsx '" x ; ) void SctY(int y) ( i t sY '" y ; ) int GetX()const { return itsX ; } int GetY()const { return itsY;} privllte: int itsX; int itsY; I; II II • Point· osztály deklaráci6jának véoc
,.
5: 6: 7:
8: 9: 10: 11:
12: 13:
14, 15, 16 : 17 :
cll'HI~
18 :
{
19 : 7.0 : 21 : 22 : 23 : 24 , 25: 26: 27 : 28 : 29 : 30 :
oubl ic : Rectanglc (int top . int left, int hottom . int right) ; -Rectungle (l (l
31:
32 , 33 : 34 :
35: 36 : 37 :
38 : 39 : 40 : 41: 42 :
Rectangl e
int int int int
GetTop() const { rcturn itsTop; l GetLeft() const ( return itsLeft; GetBottom() const ( return itsBottom; GetRight() const { return itsRight; }
Point Point Po i nt Point
GetUpperLeft{) GetLowerLe[t() GetUpperRi.ght () GetLowerRight ()
const const const const
( return itsUppcrLcft ; ( rcturn itsLowerLeft ; ( r eturn itsUpperRight ; { return itsLowcrRight;
void void void void
SeLUpperLeft (Point Location) ; SetLowerLeft(Point Location) ; SetUpperRighL(Point Location) ; SetLowerRight(Point Location) ;
void void void void
SetTüp{int top) ; SetLeft (int left); SetBo t tom (int hottom) ; SetRi gh t (int right) ;
154 111. rész • OSllályok int GetAn:!il () cons t;
43 :
44, 45 :
private :
46:
Point Point Point
47:
48: 49: 50: 51 :
Point int int int int
52 :
itsUpperLeft; itsUpperRight; itsLowerLeft; itsLowerRight ; itsTop;
itsLeft ; itsBottorn; itsRight;
53 : 54 :
J,
55:
/I rect . hpp vége
8.4 Usta - A rect.epp egy bonyolultabb osztály megvalóslUidt mutatja.
o:
,. 1:
3: 4: 5: 6:
II rect . epp kczd6dik #inc l udc Hrect .hpp· Rectangle : : Rec tanglc(int Lop, int left, int bottom , int.. righ t) ( itsTop .. top; itsLeft - left ;
itsBottom
7: 8:
,.
z
bottom ;
itoRight '" right ;
10: ll : 12 : 13: 14: 15 : 16 : 17 :
itsUpperLeft , SetX(lcft) ; itsUpperLeft . SetY(top); itsUpperRight.SetX(right) ; itsUpperRight.SetY(top) ; itsLowerLeft , SetX(left) ; itsLowcrLeft , SeLY(bottom) ;
18 :
19: 20 : 21 : 22 : 23 :
void Rectangle : : SctUpperLeft(Poi nt Location)
24. :
(
25 : 26 : 27 : 29 : 29 : 30 :
itsLowerRight.SctX(right) ; itsLowerRlght . SetY(bottom) ;
itsUpperLeft = Locdtion; jtsUpper Right . Se tY (Location .Ge t Y() ; i tsLowerLcft .SetX(Location GetX( ») ; itsTop = Location.GetY( ) ; itsLeft = Location.GetX();
31 :
32 : 33 : 34 : 35 : 36 :
void Rectangle : : SetLowerLe ft (Point Location) itsLowerLeft =' Location ; itsLowerRight . SetY(Location . GetY()); itsUpperLeft . SetX(Locati on . GetX()) ;
8. óra • Az 37 : 38 : 39 : 40 : 41 : 42 : 43 : 44 : 45 : 46: 47 : 48 : 49 : 50: 51 : 52 : 53 : 54 : 55 :
56 : 57 : 58 : 59 : 60: 61 :
62: 63 : 64 : 65 : 66 :
67 : 68 : 69 : 70 : 71 : 12 : 7) :
74 : 75 : 76 : 77 : 78 : 79 : 80 : 81 : 82 : 83 : 84 :
it.sBott.om = Location . GetY () ; itsLeft = Location . Gf'!t.X () ;
void Rectangle : : SetLowerRi ght(Point Loca tion) { itsLowcrRight = Location ; itsLowerLeft . Set.Y(Loc~tion . GetY(»
void Recta ngle : : SctUppcrRight{Point Loca t lon) ( itsUppecRight = Loca t. ion; i t~Upper Lef t. SetY(Loca t ion . GetY{»
;
i toLowe r Right . SctX(Location .GetX (»; i ts Top '" LOCtlt.ton . GetY() ; itsRight _ Location.Cc t X() ;
void Rectnngle: : SetTop(int. LOp) ( itsTop • top ; itsUppcrLeft.SetY{top); itsUpperRight.Set.Y(top) ;
void Rectangle: : SetLeft(int 1eftl ( itsLeft '"' left.; itsUpperLe[t.SetX(1eft); itsLowerLeft . SetX(left.) ;
void Rectang1e :: Sct Bot tom (int bottom) ( it~Bot t om m bottom ; itA Lowerl.eft . SetY(bottom) ; i ts LowcrRight . SetY(bot t om) ;
void Rectü ny l e :: SetRi ght (in t rig h tl itsRight " right ; itsUppcrRight . SctX(right) ; itsLowerRlght.SetXCrlght) ;
85 :
86 : 87 : 88 :
;
itsUpperRight.SelX{Locat ion .Ge t X(» ; itsBottom::. Location . Ge t Y() ; itsRight • Location .GetX() ;
int Rectangle : : Get.Area () const
156 111. rész • OSZ1ályok 89: 90 :
91 ,
int Width
~ i tsRight - it sLeft; int Height '" itsTop - itsBottorn; return (Width · Height);
92 : 93 : 94 : II kiszámitjuk a téglalap teruletét a sarkok koordinátái alapján úgy, 95 : II hogy meghatározzuk a szélességét, magasságát , és Osszeszorozzuk 96: int main() 97 : ( 98 : / I A helyi Rectangle változ6 inicializálása
99 : 100 , 101 :
Rectangle MyRcctangle (100 , 20, 50, 80 ) ; int Arca", MyRectangle.GetArea{);
102 : 1 03 :
std :: cout«
"Terület:
104: 1 05 : 106: 107,
std : : cout « std : : cout « retur n O;
"Bal fels6 pont x koordinátája: "; MyRectanglc.GetUppcrLeft() . Ge tX{);
• «
Arca«
"\n\";
Tero.let: 3000 Bal fa!s6 pon t x koordinátája
20
A 8.3 Lista 3-14. sorában deklarálruk a Point osztályt, amely egy sokszög valamely pontján:lk x és y koordinátáit hivatO!{ tárolni. A programban nemigen kerül el6 a Point osztály, de a mjzoló metódusok használják.
A Point osztály-deklaráci612. és 13. sorában kél tagvá ltoz6t deklarálunk (itsx-et és itsy-t). Ezek hordozzák a koordináta-információkal. Az x koordináta növekedésekor jobbra ll10zdulunk a koordinálasíkon, az y növekedésekor pedig felfelé. Más ábrázolások csetén ez másként is lehet, például egyes ablakkeze\6 programok esetén az y növelésével lefelé mozdulunk a képernyőn. A Point osztály deklaráci6jában (a 7-10 sorban) helyben kifejlett hozz,Mér6 függvényekkel é~ük el és állítjuk be az x és y koordinátákat. A Point-ok az alapértelmezett konstruktort és destmktort használják, azaz kénytelenek vagyunk beállítani a koordinátáikat. A 17. sorban kezd6dik a Rectangle osztály deklarációja. A téglalapol a négy csúcsa (négy pom) határozza meg.
A Rectangle konstruktora (20. sor) négy egész számot vár, melyek a következők: top. left, bottornés right (azaz/ent, bal, lent és jobb). A konstruktornak átadott négy paraméter négy tagváltozóba kerül, majd létrejön a négy Point.
A hozzáCér6 füg&'\'ényeke n kívül a Rectangle-nak van eb')' GetArea () nevű függvénye is, melyet a 43. sorban deklarálunk. A terület változóban való tárolása helyett a GetArea () kiszámít ja a területet a 8.4 Lista 89-91. sorában. Ehhez a téglalap szélességét és magasságát számolja ki, majd ezeket összeszorozza téglalap bal felső pontja x koordinátájának kinyeréséhez el kell érnünk az Upper Left pontot és ki kell nyernünk ennek x koordinátáját. Mivel a GetUpperLeft () a Rectangle saját metódusa, közvetlen hozzáférése van annak s."lját adataihoz, köztük az itsUpperLeft-hez is. De mivel a itsUpperLeft egy Point , és a Point-ok itsX értéke saját (plivát) változ6, a GetUpperLeft () nem tudja ezt közvetlenül elérni. Ehelyett a nyilvános GetX () hozzáfer6 függvényt kell használnia az adat kinyeréséhez. A
A 8.4 Lista 96. sorában kezdődik :t tenyleges programtörzs. A 99. sorig nem foglalunk le helyet a mem6riában, és voltaképpen semJ11i nern történik. Pusztán aZI közöljük a fordít6 prog~ m m a l , hogy hogya n kell létrehozni Point-ot és Rectangle-t, ha valaha szükség lesz rá.
A 99. sorban viszont megadunk egy (vízszimes/függ6Ieges oldahlkkal
rendelkező)
Rectangle-t annak felső, baloldali, alsó és jobboldali szélének koordináta-értékével (top, left, bottom és right). A 101. sorban létrehozunk egy Area ( /en"llel) new helyi egész változ61. Ez t.1rolja
imént létrehozott Rectangle területének értékét, mely a Rectangl e saját GetArea () függvényével inicializíi l6dik .
:IZ
A Rectangle klie nsprogramja tehát anélkO! t\Jd létrehozni Rectangle objektumot (és
tudja lekérdezni a területét), hogy bármit is tudna a GetArea () megval6sításár61. Puszdn a fejlédlllomá nyt látva (mely tartal mazza a Rectang1e osztály deklaráci6ját) a programozó tudja, hogy a GetArea () egy egész számot ad vissza. Hogy ez konkrétan milyen bO"vészmutatvánnyal történ ik, az nem a Rectang1e oszt:'i ly fel használójának a dolga. A Rectangle osztály progr-
Kérdések és válaszok Kérdés: Ha a 1..'Ollslansfiiggvények osztiilymódosittisi kísérle/e hibajelzést adfordittisi időbeli, m iért 'Icm érdemes egysze1l7en elhagyni a const módos;rószóf a hibajelzések elkell"ilésére? Válasz: Ha a szóban forgó tagfüggvé.nynek elvileg nem kell módosítania az osztályt, akkor a const kulcsszó használata jó módszer a buta hibák felderítésére a fordítóprog-
158 111. rész ' Osztályok mm rl!vén. A GetAge () -nek például semmi oka nincs arra, hogy változtasson a Cat osztályadatain, ha azonban a megvalósítás programk6dja az alábbi sort tartalmazza: if
(it~Age
_ 100) std::cout «
"Hé! Már 100 éves vagy?!\n";
akkor a konstansként deklarált GetAge () hibát dob a fordításkor. A prog.... moz6 feltételezhet6cn azt szerette volna ellenőrizn i , hogy az itsAge egyen16-e százzal, de ehelyett véletlenül értékül adta a százat az itsAge-nek. Mivel ez az é rtékadás módosítani szerelnI! az osztály adatait, hololt korábban azt ígértük, hogy nincs ilyen szándékunk, a fordít6progr-.tm hibát jelez.
Ezt a fajta hibát e lég nehéz pusztán végignézéssel megtaJálni - a szem gyakran azt látja, amit szeretne. A U:nycg: a program látszólag normálisan fog futni , de az it sAge értéke egy hibás szfl mértékrc lesz átállítva. Ez előbb-utóbb gondot fog okozni. Kérdés: VCI II -C! él1C!l lIIc struct slmM/I.í rá/ hasz /lálni C+ + progl"alllban? VálCIsz: Több C++ programozó csak olyan osztályok esetében használja a struct kulcsszót, amelynek nincsenek függvényei. Ezzel a régi idők C sLnlktúráihoz nyúl nak vissza, amelyek nem tartalmnzhattak függvényeket. 6szint(:l' szólva czt nem tanom jó programozási gyakorlatnak. Ami ma egy metódus nélküli struklÚI'".!, az a jövőben igényelhet metódusokat. Emiau áL kell majd írni a struktúrát osztályl'".! , v!l.gy metódusokat is tartalmazó struct struktlmlt használva fel kelllÚgni a követeU szabályt.
Gyakorlatok Ebben 32 6r~iban további ismereteket szereztünk az osztályokr61. Tudásu nk elmélyíté$éhez válaszoljunk meg néhány kérdést és végezzünk el néhány gyakorlatot!
Kviz 1. Miért szokás az osztály-deklaráci6kat külön fájlban e lhelyezni? 2. Mi a szerepe a ca t . c pp-bcn szereplő konstruktornak? 3. Mi történne a rect . cpp-ban és a rect . hpp-ban, ha a Point osztályt nem def!nHllnánk? 4. Honn:ln tudja a fordítóprogram, hogy hol keresse a beemc!end6 állományokat?
FeladatoK 1. M6dosítsuk úgy a rect. hpp programot, hogya Point osztály deklarációja kerüljön át egy másik állományba, és ezt az új állom.1nyt emelje be a rect . hpp-be.
Változik az eredmény? 2. M6closícsuk úgy a cat . hpp progmmol, hogy a GetAge () legyen konstans metó-
dus. Kell még valami máson is változtalni?
3. Módosírsuk úgy a cat. cpp progr
Válaszok a kvflk-érdésekre 1. Hogy könnyen megoszthatóak legyenek másokkal (és mi is használhassuk má-
sok objektumait). 2. Inicializálja az itsAge változót, arra az értékre állítva be, amit az objektum példányosításakor megadnak. Ez.í ltal mcgspóroljuk, hogy az objektum deklarálása után még külön meg kelljen hívni a SetAge () -el az életkor bcállításám. 3. Ha ól Point oszt.ályt sehol máshol sem adjuk meg, akkor a fordítóprogram hibát jelez meghatározatlan hivatkozás miatt. Ha valahol máshol került deflni:ílásrn, akkor a megfelelő fájlt be kell emelni (pl. po i nt . hpp-ként). Gyakori megoldás, hogy egy osztály egy mCtsikra t{11l1aszkodik, abból építkezik. 4. Ha abeemelendő állom{UlY neve idézőjelek (") közt áll, akkor a fordítóp rogram ugyanabban a könyvt:1rban (ogj;t keresni, mint ahol az aktuális forr.'Ísfájl is van. Azt is közölni lehet a fordítóprogrd1llmal, ha más könyvtárakban kell keresnie az állományt: idézőjelek közt megadva az elérési utat, csak oU keresi a fájlt a fordítóprogram. Ha azonban az állomány neve kacsacsőrök (o) között szerepel, akkor a szabványos rendszer-fejléckönyvtárban tönénik a keresés.
III.
RÉSZ
Memóriakezelés 9. óra 10. óra 11 . óra 12. óra
Mutatók A mutatók kifinomult használata Hivatkozások A hivatkozásokról és mufatókról - mélyebben
9.
ÓRA
Mutatók Mirőll.sz sz6 • •
•
ebben az 6rában:
Mik :'Izok a mutatók Hogyan vezethetjük be és hogyan használhat juk a mutató kat
Mi a dinamikus memória (heap), és hogyan kell
műve l eteket
végezni ezen
a mem6riaterOlctcn
A mutatók és használatuk megértése Egy C++ programozó számám a létez6 1eghatékonyabb eszköz a mutató, amdy a me-
mória közvetlen clérésének lehel6ségét biztosítja. Ugyanakkor a mutat6kr6\ azt is tart~ ják, hogy a programot leginkább összekuszá16 elemek. Épp ezért mlgyon fontos , hogy megért,-;ük . mik is valójában a mutatók. Ebben :lZ órában a mutatók működéséróllesz szó. Ám ne feledjük, hogy csak a könyvben továbbhaladva fogjuk teljesen megéneni a mutatók szükségességét. A lilII/alÓ egy olyan váltózó, amely memóriacímet tartalmaz.
164 1111. rész· Mem6riakezelés Álljunk is meg, és olvassuk el újra. A mutatá egy változó. Azt már tudjuk, hogy mi az a válLozó: olyan nyelvi elem, amelyben értékeket tárolhatunk. Egy egész változó számokat tárol, egy karakter lípusú változó em' belŰL tanalmazhat A mutató pedig olyan vállozó, amely memóriacímettárol. Rendben , de mi is az a memóriacím? Hogy eZl megértsük, ismernünk kell valamennyire a sz..ímítógép mem6riájának múködését. Ne ijedjünk meg, annyira nem bonyolult. A számítógép mem6riája az a hely, ahol - többek közötl- a változók tárolódnak. A mem6ria sorszámozott mem6riacellákra van bontva , a memóriacímek pedig ezek a bizonyos sorszámok. Mindegyik változó, legyen az bármilyen típusú, egy egyedi címen tárol6dik a memóriáb<1!l . A 9.1. ábrán a theAge nevU előjel nélküli hosszú egész vállOZÓ tárolásának módja láth<1!ó vázlat.os ábrázolásban. Memória A theAge változó
,
I
I 10110101
01110110
101
102
103
11101110
I
I
I
100
11110110
104
105
106
I
107
108
109
110
111
I
112
113
Minden memóriacella 1 bájt hosszú A theAge nevű el6jel nélküli hosszú egész 4 bájt, azaz 32 bit hosszú A theAge változónév az elsó bájtra mutat. A theAge memóriaclme: 102
9.1. lb.. " tf/eAge uáltoz6 flbrt1zolt'i:;a /láz/a/osall
A"L egyes számítógépek e l térő de általában elég összetett sémák S"Lcrint címezik a memóriár. A programozóknak általában nem kell ismerniük a ponlos címét minden egyes változómik, mivel ezeket a részleteket a fordítóprogram kezeli. Ha szükségünk van erre az :.datra, :tkkor használhat juk a címe (address of) operátorl (&), amelynek múködésél a 9.1 Usta mul:ttja bc.
9.1 Usta - A vátlozók memóriacimének lekérdezése (addressdemo.cpp)
o:
II A 9 . 1 . k6drészlet az address of (cim) operá t orL 1 : II lokális változók esctóbcn 2 : # i n c lude
3,
6, 7, 8, 9,
10 , 11 : 12 : 13 : 1<1 : 15 : 16 : 17 : 18 :
unsigned short shortVar=5 ; unsigncd long longVar~65535 ; long sVar = -65535; std , : cout std : : cou t !;td : : c:out std , , cou t ctd : : c:out sto : : cout
« « « « « «
"shortVar : \t " « shortVar ; "\tA shoctVar memóriacime : \t" « &shortVac « " \n" ; "longVar : \t" « longVar ; "\tA lo ngVar memóriacíme : \t' « &longVar « '\n ' ; 'sVar : \t\t' « sVar ; ' \tA sVar mcmóriacíme : \ t " « &sVar « " \n ";
return O;
Apropó
Különleges karakterek a C++ nyelvben
A \ t a 9.1. kódrészletben egy TAB karaktert illeszt a kimenetbe. Ily módon egyszerűen készfthetünk oszlopokat (egyszeru ugyan, de messze nem tökéletes). A \n új sorra viszi a kurzort (ugvanazt eredményezi. mint az std : : endl függvény) A \ \-t akkor használhatjuk, ha \ jelet szeretnénk kifrni (karaktersorozatokban) A \ " karakterekkel id ézőjelet [rhatunk ki (karaktersorozatokban) A \ ' karakterekkel egyes idézőjeleket frhatunk ki (karaktersorozatokban, vagy karakterként) A fenti összefüggésben használva a \ (backslash) karaktert escape karaktemek nevezzük, mivel megváltoztatja az öt követö karakter jelentését, s nem nyomtatható karakterként viselkedik (a \n új sort eredményez. és nem egy n betűt).
menet short Var : 5 longVar : 65535 ~Var : -65535
A shortVar memóriacimc : 12 45066 A longVar mcffiÓrlacíme : 12 4506 0 1245 056 A sVar mem6riacíme :
Saját számítógépünkön másként nézhet ki a kimenet, lIlivel minden futás során más címeken jönnek létre a változók, au61 függ6en , hogy mi egyéb található éppen a memóriában, és mennyi szabad hely áll rendelkezésre. A kimenet így is kinézhet: shortVar : 5 lo ngVar: 65535 -6553 5 sVar:
ll. shortVar mcmóriacime : Ox8fc9 : fff4 A longVar mem6riacíme : Ox8fc9 : ff:f2 A sVar memóriacimc : Ox8fc9: ff 00
166 1111. rész • Memóriakezelés
Három változót vezettünk be, majd kezdóértéket adtunk nekik. Egy short típusüt a 6. sorban, egy unsigned long típusút a 7.-ben, és egy long típusút a 8.-ban. Az értéküket és a címüket a 10. l!s 15. sorok között íraljuk ki, utóbbit a cím ( &) operálor segítségéve!. A shortVar változó értéke a vártnak megfelel6en 5, mem6riacíme 1245066 volt az Intel x86 alapú sz.-'inútógépcmen futtatva , Borland fordítóval készített biná riS5."l!. Ez a nem túl egyszeru cím számítógépfügg6, és minden futás során változhat kicsit. Ugyanakkor soha nem vállozik az, hob'Y a két első változó címe közötti különbség négy bájt, amennyiben a számítógép néh'Y bájt hosszú cgészckct használ. A 9.2. ábra szemlélteti, hogya fenti program váltOl:ói hogyan tárolódnak il memóriában. (Ne feledjük, hogy néhány számítógépen mindkét vállozó között négy bájt lesz a kü lönbség, atl6l függ6en, hogy il fordít6 nkal hogyan MJítottuk be.) Egyel6re a dolognak nem !'iok értelme látszik, elvégre minek nekünk tudni :~ z egyes változók memóriacímének aktuális értékét. Ebben a pillanatban tehát elég annyit megjegyezn i, hogy mindegyiknek van címe, és hogy mindegyikhez a megfelelő mem6riamennyiség van hozzárendelve.
T
". I
00000"10'
~ 000000000000_
" "1111'"'"11'ooo_
00000000" ""1111111111
9.2. ábra A Ilálfozók fáro/ásának lJcnluwfásC/
Honnan rudja a fordító, hogy mennyi memóriát kell rendelni az egyes válloz6khoz? Nos, ezt mi magunk adjuk meg neki, amikor deklaráljuk a típusukal. l Ia például unsigned long lípusú változ6t vezetünk be, a fordító rudja , hogy négy bájt memóriát kelllefoglalnia, mivel minden egyes unsigned long négy bájton tárolódik. A fordító dolga a megfelel 6 teliilet hozzárende1ése.
9. 6ra • Mutatók 167 Amikor egy mulató létrejön, :l fordító a hardverfelépítéstól és operációs rendszertől függ6en annyi memóriát rendel hOZ7..á, hogy dférjen benne egy változó mem6riacíme. Egy mutató mérete nem feltétlenül akkora, mim egy egész tipusú változóé. Semmiképp se vonjunk le következtetéseket a vá ltozótípus alapján a muml6 méretére vonatkozóan.
A memónacfm tárolása a mutatókban Minden változónak van címe. És még ha nem is ismerjük czl pontosan, akkor is elhelyezheljük egy mut:Cltóban. Vegyünk például egy howOld nevú egész típusú változ6t. Ennek a cim(:t eltárolhatjuk a pA'iJe nevú mutatóban, amit a következőképpen deklarálhatunk *DAge = NULL
i nt
Ez egy pAge néva int típustl változ6t címz6 mutOlt61 vezet be, azaz a pAge meghatározás.'l szerint egy int eimét fogja t.írolni. Ne feledjük, hogya pAge ugyar'olyan v[lltoz6, mint a többi. Amikor bevezetünk egy t::gész (int típusú) v5 Jtozót, akkor:lZ egy egész érték tároJásiira lesz [elkészítve. Amikor egy, a pAge-hez hasonló mut;lIót készítünk, az egy mem6riacím tárolására lesz felkészítve. A mutató egy különleges típusú változó, amely a memóriában talá lható elemek címének tárolására alk
II
mulatók kezd6értékkel egyOn történ6 bevezetésének egy másik módja:
int ·pAge " O
Ennek eredménye ugyanaz kelj legyen, mint ha NULL ércékel adtunk volna neki, de technikailag a O egy egész típusú állandó, a NULL pedig ennek az állandónak a memóriacímél lartalmazz3.
168 1111. rész · Mem6riakezelés
Apropó
Kezdóértéket a mutatóknak!
Programozzunk biztonságosan, adjuk kezdóértéket a mutatóknak! Ha a mutat6t O vagy NULL kezdőértékkellátjuk el, külön hozz,'! kell rendelni a howOld eimét a pAge mutatóhoz. A következő példa ennek mikéntjét mutatja. int hoWOld = 50 ;
int .. pAge", O; pAge
ol
&howOld;
II változó létrehozása II mutató létrehozása II a howOld címének h07.zárendelése a pAge-hez
Az els6 sor létrehozza a howOld nevG változ6t, ::unelynek típusa unsigned short int, és az 50 kezd6értékel rendeli hozzá. A második SOr hozza létre a pAge nevű mUlatót, ;l!ncly szintén unsigned short lnt típusú , (:s előszö r O kczd6értékkel rendelkezik. A pAge mutató voltát a v,lItoz6 típusa és a változó neve közé irt· k,lraktcr jelzi. A harmadik és egyben utols6 sorban hozzá rendeljük a howOld memóriacímét a pAge neVli rnut<1tÓhoz. A hozzá rendelést az address of (&) operátor teszi l e hetővé. H<1 az address of open~tort kife!ejtenénk, a howOld mcmóriacímc helyctt annak értékét rendelnénk hozzá a nmtat6hoz. Ez az érték vagy valós, vagy egy nem lét ező mem6riacím, de még ha valós is, az teljesen a véletlen mGve, és biztos, hogy nem az a hely, ahol b,1rmifélc műveletet szerelnénk végezni azzal a mUl,Hóval.
Apropó
Mutatók és nem mutatók
Nem mutató értékét rendelni egy mutatóhoz viszonylag gyakori hiba. Annyira az, hogya fordítónak ezt elvileg jeleznie is kell fo rdítás közben. A pAge vá ltoz6 értéke jelenleg a hoWOld változó memóriadme, magának howOld-nak az értéke pedig 50. Ezt az állapotot az alábbi pár lépésben is megvalósíthattuk volna: unsigned short int howOld 50; /1 változó készitése unsigned shor.t int • pAge = &howOld ; 1/ a howOld ra irányitott mutató II bevezetése
A pAge tehát egy olyan mutató, amely a hoWOld változó mcm6riacímét tarta lmazza. A pAge ha sznábHával kiolvashatjuk a howOld értékt:L, vagyis azt a bizonyos 50-ct. A howOld változ6nak a pAge mutatón keresztültörtt:nó elért:s(!t k6ZveICII elérésnek (indirection) nevezzük, mivel nem közvetlenül (a nevén át) f(!rünk hozzá a howOldhOL, hanem a pAge-en vagyis a memóriacímén keresztül. K ésőbb, de még ebben a lec+ kében látni fogjuk , hogyan alkalmazzuk a közvetett elérést egy változó értékének kiolvasásához. A közvetett elérés azt jelenti, hogy a mutatóban található mem6riacímen e l é rhető értéket olvassu k ki. A mutató tehát röviden e.gy közvetett módsLert ad az általa táro lt cimen található érték megszer.lésére.
9. óra • Mutatók 169
Mutatónevek A mutató kkal kapcsolatban ugyanazok az elnevezések érvényesek, mint a változók esetében. Könyvünk azl a konvendót követi, hogy minden egyes mutat6t p octave! kezdünk, mint például a pAge és a pNumher mu lalókal ta rtalmazó példá kban.
A közvetett elérés operátor, avagy mutatók közvetett használata A közvetett elérés open'iton ( *) másként v;ssztlh iulItkozús(dereference) operálornak is nevezzük. Amikor egy ffiutatóval visszahivalkozunk, az általa hiva tkozott rnern6rbdmen található énéket olvassuk ki. unsigned short int howOld '" 50 ; unsigncd short int yourAge; yourl\ge '" h(')wo l d ;
A muta16 közvetett dér(:st b iztosít aLOn vá ltozó é rté kéhez, a rndynek mem6riacimére mut:lt. A hoWOld változó é nl:kének {ttadása a yourAge változónak a pAge mu lató közbei ktatásával például az alábbi módon tön énhet:
II howOld bevezetése &howOld ; II a howOld-ra mutató pAge I I bevezetése unsigned short int yourAge; II egy újabb változó bevezetése yourA",e = ~pAge; II a pAge által mutatott érték (50) II hozzárendelése u yourAge v61tozÓhoz . unsigned short int howOld unsigned short int ~ pAge
50;
K
A közvetett elérés oper.'itor (*) a pAge neV\l mUlató előtt valami ilyesmit jelem: .:I Z ezen a címen tá rolt éJték ~. A hozz.árcndclés tehát a követ kezőt mondja: .VegYÜk a pAge mutatóban találhat6 mem6riacímcn tárolt értéket, és rendeljük hOZ2.1 :1 yourAge n evű váhozóhoz. ~ Egy mfLsik megközelítésben a dolog a következ6képp néz ki : ~Ne foglalkozzunk a mutatóval, fogla lkozzu nk a mUlató áltHl hiv:Hkozott címen la lálható e l em mel. ~
Apropó
Közvetett eléréssel kapcsolatos műveletek
A közvetett elérés operátort (*) két esetben használjuk: változók deklarálásánál és hivatkozásnál. Amikor egy mutatót készítünk, a csillag jelzi, hogy ez egy mutató lesz, és nem közönséges változó. Például: unsigned short * pAge = NU LL ; II mutat6 unsigned short típusú I I változ6hoz
Amikor a mutatón keresztül visszahivatkozunk, a közvetett elérés operátora azt jelzi, hogy a mutatóban tárolt memóriacímen található értékhez szeretnénk hozzáférni, és nem a memóriacímhez magához. ·pAge = 5 ;
II az 5 érték hozzárendelése a pAge II memóriaterületóhez.
Ne feledjük továbbá, hogy ugyanezt a karaktert (*) hasmáljuk a szorzáshoz is. Afordító a kódkömyezetb61 állapítja meg, hogy éppen melyik felhasmálási esetről van szó.
170 1111. IÓSZ
•
Memón.koz.lés
A közvetett elérést a mindennapi életben is használjuk. l ia a helyi pizzériát hívjuk, hogy vacsorát rendeljünk, de nem tudjuk a telefonszámát, megnézzük a telefonkönyvben, vagy a wehen. Az információforrás nem a pizzérifira magára vonatkozik, de tartalmazza 3IU1ak "eimét" Üelt:fonszámáO. Amikor ezt tesszük, valÓjában közvetett elérést val6sítunk meg,
Mutatók, címek és változók Nagyon fontos, hogy különbséget tegyünk a mUlatÓ, és a mUlató által tárolt mem6riacím között. Ez a forrása a legtöbb összcvisszaságnak a mutat6kkal kapcsolatban. Nézzük a
következő kódtőredéket:
int theVariable : 5; int * pPointer = &theVariable
A theVariable nevű váltOZÓI egész típusúkénl határoztuk meg, és az 5 kezd6értéket rendeltük hozzá. A pPoinler-t egy egész vMlozóra mulató hivatkozásként dekbráltuk, kezdóértt:!kként a thevariable változó Illernóriadmét kapta. Tehát a pPointerben tá rolt melllóriadm megegyezik a theVariable változó címével. A pPointer által hivatkozott Illelllóriacílllt::n az 5 értéket tároljuk. A 9.3. ábra a theVariable és a pPointer változókat szemlélteti.
A.datok manipulálása a mutatók használatával Miután a mulatóhoz hozzárendeltük egy változó mern6riadmér, a mutal6n keresztül hozzáférhetünk a változó értékéhez. A 9.2. Lista bemutatja, hogyan rendelhet jük eh'Y lokális változó eimét egy mUl3t6hoz, és hogyan válloltathatjuk meg a változó é rtékét a mulatón keresztül. pPointer
IheVariable
l,
Változó név
0000
0000
j
0000 0101
,
0000
,
0000
0000
'------v----'
'"
0000
1~1
I
'02
0110 0101
'-----y-----'
;
H"
0000
0000
'Ol
Címkiosztás
9.3. ábra A mell/ória l!áz(atos áhrázolása
104
105
". '"
'08
'09
9. óra • Mutatók 171
9,2 Um - Adatváltottatál mutat6k használatával {pointeruse.cpp)
o: II 9 . 2 . Li s ta - Mutatók h as ználata 1 : linclude 2 : using s t d: : cout ; /1 a z std : : cout k önyvtár haszná lata
,.
4 : int main() 5: ( 6: i nt myAge ;
,. ,.
7:
II e gy v á l to zó i n t " pAge .. NULL ; / I e gy mu tató
myAge
~
5,
10 :
pAge
11 : 12 :
cout « cout «
'myAge : • ~pA9'c :
14 :
cout «
"'pi\(Je •
15 : 16 :
" p Age = 7 ; cout « '''pAge : cou t « ' myAge :
~
&myAge;
« «
/I a myAge címének hozzá rendelése a pAge-hez myAge « ' \n " ; "pAge « ' \o\n" ;
1) :
17 :
7 \ 0" ; II JI. 7 ért ék h02zá rcn d e l ése a myAge- hc z « " pAge« " \n" , «myAgC« ' \n\n ";
18 : 19 , 20 ,
21 : 22 : 23 : 24 :
cout« myAge = cout « CDu t «
'myAge = 9\n '; 9; 'myAge :
• · pAge :
« «
myAge «
" \n ';
" pAge «
' \n ";
ret urn O;
25 :
myAge :
5
" pAge : 5
· pAge = 7 . pAge : 7
myAge : 7 myA<;le :
9
myAgc : 9 " pAge : 9
A fcnti program két vállOZ6l vezet be: egy myAge nevú egészet és egy pAge nevú mutat6t, amely egy int típusú változóra hivatkozhat, és amely a myAge címét tarta lmazza. A myAge a 9. sorban az 5 értéket kapja, ezt ellenőrizzük a ll. sorban történ ő kiíratt\ssal. A 10. sorban a pAge-hez rendeljük a myAge címét. A 12. sorban visszahivutk07.unk a pAge-re, és kiíratjuk, bemutatva, hogy a címén található érték val6lr.m 5, amelyet
'72 1111. rész' Mtmóriakezelés myAge változóban tárolunk. A 15. sorban il 7 é rtéket rendeljük a pAge memóriacímén található memóriaterülethez. Ez a myAge változ6 é rtékér is 7-re változtatja, amelyet a 16. és 17. sorban található kiír6 művelctekkel igazolunk.
A 20 sorban, a 9 értéket re ndeljük a myAge változ6hoz. Ezt az énéket aztán kiolvassuk közvetlenül a 21. sorl:Ydn, és közverYc - visszahivatkozva a pAge mutat6ra - a 22. sorban.
A mutatókban tárolt cfmek vizsgálata A mutatók lehctővé teszik a mem6riacímekkel történő mlíveleteket anélkül , hogy pontosan mdnánk mi is van a mutatókban. Ha végigolvasluk ezt a leckét, életük hátralev6 részében vakon bízhalunk abban, hogy amikor egy változó mem6riacímét hozzárendeljük egy mut.1t6hoz, a kkor a mutatóban valóban annak a vált.oz6nak a címe szerepd. Egyszer az életben :Limnban bizonyosodjunk meg róla, hogy ez tényleg így van, A 9.3 Lista éppen ebben segít ne künk.
9.3 Lista - Mi il van egy 1IMIlIt6ban... (pointemore. o:
1:
/ / 9.3 Lista: Mi ls van egy mutatóban . . ftinclude
2,
3: 4: 5, 6, 7. 8: 9:
10 : ll : 12 ;
13 : 14:
15: 16: 17 : 18 : 19 : 20 : 21:
22 : 23 : 24 :
int main ()
{ unsigned short int myAgc : 5, yourAge unsigned short int * pAge • 'myAge;
la;
std : : cout « std :: cout« std : : cout « std : : cout «
'myAge : \t" « myAge; '\t\tyourAge : \t' «yourAge« " \n "; '&myAge:\t ' « &myAge ; " \t&you r Agc : \t' « &yourAgc «'\n ',
std :; cout« std :: cout«
'pAge:\t' «pAge « ' \n'; '''pAge : \t"« "pAge« '\n\n',
pAge = &yourAge; std : std : std : std :
: cout : cout : cout : cout
« « « «
'myAge : \ t ' « myAge ; '\ t\tyourAqe : \t ' « yourAge « "\n" ; 'ócmyAge ; \t' « &myAge ; '\t&yourAge: \t' « ócyourAgc «'\n';
std :: cout « std " cout «
'pAge : \t' « pAge « '\n'; ""pAge : \t " « "pAge « " \n\n';
std :: cout« re t urn O;
'ócpAgc : \t ' «ócpAge«
25:
26 : 27:
28 :
/ / a mutató
' \n' ;
9. 6,. • Mutat6k 173
Kimenet you rAge :
myAge:
5
&myAge :
12 45066
p Age :
12(\5066
*pAgc : myAge:
5 5
&myAge :
12 45066
pAge :
1245064
· pAge:
10
&pAge :
1215060
10
&yourAge :
124506 4
yourAge : &yourAgc :
10 124506 4
Ne feledjük, hogy saját rendo;zerükön 'L kimenet ismét eltf:rhet az itt látható eredményt61, mivel minden számítógép killö!1böz6 címeket tárolja a különböz6 változókat, attól függ6en, hogy mi van még a memóriában, és mennyi memória áll rendelkezésre. A kimenet a következ6képpcn is kinézhet: myAge : &myAgc : pAge :
Ox355C
~ pAge :
5
myAgc :
5 Ox ]S5C
&myAge : pAge :
5
you r Age :
10
&yourAge : OxJ55 E
Ox 355C
· pAge :
Ox 355E 10
&pAge :
Ox355A
yourAge :
10
&yourAge : Ox355E
Az 5. sorban a myAge és yourAge változókat unsigned short egész típusúkénl vezeltük be. A 6. sorban 3. pAge mUlat6t egy unsigned short egész típusü cím tárolásához deklarálruk, és kezd6értékként a rnyAge változó címét adtuk meg neki. A 8.-11. sorokban a myAge és your Age változók értékét és dmét lt.1ltuk ki. A 13. sorban íratruk ki a pAge tart:l lmát, amely a myAge címe. A 14. sorban aztán a pAge-re történő vissza hivatkozás eredményét ki.i1dtük a kimenetre, amely a pAge címén ral:'i1hat6 értéket, a myAge értékét, azaz 5-öt adott eredményül. Ez a mutatók lényege. A 13. sor mutatja, hogy a pAge a rnyAge változó címét tart·ahnazza , majd a 14. sor mutatja , hogy hogyan érhetjük el a myAge értékér a pAge mut.116m történő visszahivatkozással. Fontos, hogy megénsük eZl, mielőlllovább folYlalnánk. Tanulmányozzuk a kódot, és nézzük át újra a kimenetet.
174 1111. rész • Memóriakemlés A 16. sorban a pAge-bez ezúttal a yourAge változó címét rendeltük, majd újra kiíratnlk az értékeket és a címeket. A kimenet azt mutatja, hogya pAge most a yaurAge változó címét tartalmazza, és a visszahivatkozás során a yourAge értékét kaptuk. A 26. sor a pAge sa ját címét írja ki. Mint az összes többi változónak, ennek is van címe, amely szintén e1t,í ra lható egy másik mutatÓhan . (A mutató címének másik mutat6hoz törté n ő hozzá rendelésére hama rosan részletesen is kitérilnk.)
Használjuk a közvetett elérés operálort (*) a mutat6ban lévő címen tárolt 6rték eléréséhez. Minden mutat6nak adjunk val6s kezdóértékct, vagy null-t (O). Ne feledjük II különbséget a mutatóban lévő cím, és :lZ azon a címen lévő érték között.
Ne pr6bálkozzunk memóriadm tárolásával olyan változókban, amelyek nem lllutatÓk.
Miért jó mutatókat használni? Most tehát már pontosan tudjuk, hogy hozh:Hunk létre muta tót, és hogyan helyezhetjük cl benne egy változ6 címét. Most már csak az H gond , hogy magunktó l soha n ~m jut.na eszünkben ilyesmit csiná lni. Végül is mi a csud5nak bajlódjunk mUlalókkai , amikor megvan a változónk, amelyen keresztül hozzáférhetünk a memóriában tároli értékhez? Nos, az eddig bemutatott ~mutat6s ada tváltozt;ltások~ basználau{nak egyetlen értelme tényleg cS:lk a mutatók működésének a bemumtása. llaneIllmOSl, hogy már ismeros számunkra a mutatók használata , valami hasznosat is kezdhetünk velük. A mutatókat le&!,'}'akr.lbban lZ alábbi három feladatr" használjuk: • A dinamikus mem6ria (heap) kezelésére • Osztálypéldányokban Cobjektumokban) tárolt vá ltozók és tagfi.i8bTV~nycik c1ért!sére • Vá ltozók fü ggvényeknek történ6 refercnciakénti átadásához A fejezet hátralévő részében a dinamikus memóriában ( heap) tönénő adatkezelést és az oszt.álypéldányok adatainak és tagfüggvényeinek elérését mutatjuk be. A 11. fejezetben foglalkozunk m:ljd a változók hivatkozással törté nő átadásával.
9, óra • Mutatók 175
A verem (stack) és a dinamikus memória (heap) A programozók áiLa[ában az alábbi öt mcm6riatcruleten végeznek ml1veleteket: • • • • •
Glob{ilLo; névtér A dimmikus memória (heap) Regiszterek Kódlér A verem
A lokális változók él függvényparaméterekkel egyült veremteriiklen vannak, A kód rer~ mészetesen a k6dtérben található, a glob,ílis változók pedig n globális névtérben. A re~ gisztereket használjuk az olyan bels6 álbpotkczeló funkciókhoz mint például a verem~ kezelés, de ill ta lálható például az ulasí1.ásszáml:íl6 is. Majdnem az összes fen nmaradó memória a dinamikus:1n kezcIt mem6riaterület (heap), amil néha egyswrúcn .szabad tárterű [elnek" is hívunk. A lokális változókkal az a problém,l, hogy nem maradandók. Atnikor a függvény visszatér, a lokális változó megsemmisül. A globális változ6k megoldják ezt a prob[émát, csak épp a programból bárhonnan hozzáférhet6ck, amil:>61 aztán a Icgkülönfélébb problémák eredhetnek. Az ilyen kódol állalában neMz megérteni és karbantartani. Az adatoknak a dinamikus memóriáb:m történ6 elhelyezése mindkét nehézségeI megoldja. E[só közelrtésben a dinamikus rnem6ria egy j6 migy memóriatcn]lcl, ahol ezrével sorakozmlk a sorszámOZOlt mem6riacellák, csak arm várva, hogy adatokkal töltsük meg. Azonban ezeket a cellákat nem címkézhet jük fel egyszeruen változ6bcvezctéssel, ahogyan a veremtefÜleten lévőket. Itt először kérnünk kell egy mem6riadmct, amelyen lefoglalhatunk egy területet, aztán czt a dmet egy OlutatólYdn kell biztons.4gba helyeznünk. Lássunk talán egy példát, amin kereszrül jobban megérthetjük a dolgot. Egy barátunk megadja nekünk azt a 800-as tclefonszámot, amellyel a Mindentgyflrtó Amerikai Válla!attól (ACME) rendelhctünk poSlai utánvéttcl. Miután hazamegyünk, felvesszük ezt a számot a telefonunkba, majd eldobjuk azt a darab papírt, amire a tclcfonszárnot e l ő zőleg felírtuk, Atllikor később tárcsázunk, a telefon kicsöng valahol , és az ACME csomagküldő szolgálat válaszol a hívásra. Nem emlékszünk már a tclefonszámra, és azt sem tudjuk, hogy a hívOll telefonkészülék hol van, de a gyorshív6 gomb segÍlségével akflrhányszor újra elérhetjük az ACME csomagküldó szolgálatot. Az ACME csomagküldó ellől kczdve olyan, mint egy adat a dinamikusan kezelt memóriában (heap). Nem rudjuk merre van, de azt igen, hogyan érhetjük el: a címén keresztül , jelen esetben a a telefonszám segítségéve!. Még csak nem is kell ismernünk eZl a számot, elég ha eltesszük egy muratóba, eseTÜnkben egy gyorshívó gombhoz társítjuk A mutató hozzáférést biztosít az adatainkhoz anélkül, hogy a részletekkel kellene bajlódnunk.
176 1111. rész • Mem6riakezelés A veremterület automatikusan törlődik, amikor a függvény visszatér a hívóhoz. Ezzel az összcs a függvényre nézve lokális változó látókörön kivülrc kerül, és eltűnik a verembőL 1\ dinamikus memóriát azonban nem takarítja ilyen automatikusan a rendszer. Az ott tárolt dolgok megmamdnak, amig a progr.lmunk be nem fejezi a futását. Ez persze egyben azt is jelenti, hogy ami felelősségünk, hogy a használat vt!geztt!vcl felszabadítsunk minden olyan memóriaterületet, amclyet előzőleg lefoglaltunk. I la ezt elfelejtjük, és otthagyjuk magunk után a már nem használt elemeket, akkor úgynevezett memóriaszivárgás (leak) keletkezik. Erről a tl!már61 később még szót ejtünk ebhen a leckében. A dinamikus mernória legnagyobb előnye, hogy a lefoglalt tenHet egészen addig hozzáférhető mamd, amíg célzottan fel nem szabadít juk. Ez az állíuís feltétel nl:lkül igaz, vagyis h" egy függvl:nyből fogblunk le dinamikus memóriát, az i.~ elérhető marad azután is, hogy II Cüggvl:ny visszatért a lúvóhoz. A mem6ria ilyen módon tö rténő használata azzal :IZ clőnnye l jrlf a globális válrozókhoz képest, hogy az adatokat csak :Izok a függvények érik el, amelyek ismerik a hozzájuk vezető mutat6t. Ez a módszer tehát egy szigorúan ellen6rtötl fdületet nyújt az adatok eléréséhez, és megszüntcti azt :l problém5t, hogy az egyik függvény vámtlan ~S el6re nem láthat6 módon változtatja meg az adatainkat. Ehhez azonoon musz. 'ij mUL'Itókat létrehoznunk és kezelnünk. A következ6 részben megmUl:.lljuk, hogy:ln tehetjük ezt meg.
A new kulcsszó használata A C++ programnyelvben a new kulcsszó segítségével foglalhatunk dinamikusan memóriát. A new kulcsszót annak a változónak/objektumnak a típusa követi, lItllcly számára a memÓri:HefÜletellefoglalni szeretnénk. Ebb61 tudja a fordít6progflun , hogy mennyi memóri{1l kcll biztosítani:l. A fentiek éneimében a new unsigned short int deklarnci( példáu l két bfijtot fogbl a dinamikus memó riában, míg II new long négyct. A new visszatérési értéke egy memóriacím, amelyet cgy mutat6hoz kell llozzárendeJnünk. Egy unigned sort típusú dinamikusan kezelt változ6 létrehozás:1 .1 követke ző képpen történik: unsigned short int * pPointer; pPointer ~ new unsigned short int; Természctesen a mutatónak a bevezetés során is adhatunk kezd6énékct: unsigned short int * pPointer
=
new unsigned short int;
A mutató mindkét esetben a dinamikusan kezelt memóriában található unsigned short int típusú adatra Illlltat , amelyhez a mulatón keresztül a következőképp rendelhetünk értéket: *pPointer = 72; Ez II következőt jelenti : ..tedd a 72 értéket a mutató által címzett teriiletre vagy a 72 értéket a dinamikus memória azon pontjára, ahová a pPointer nmtat ". H
,
Ha a new utasítás nem tudja lefoglalni a szükséges véges erófortiÍs), akkor egy kivételt dob.
Apropó
menny iségű
~ tedd
memóriát (a memória is
Kivétel generálása
A kivételek hibakezeló objektumok, amelyekkel a 24. leckében foglalkozunk majd bövebben.
Nélliiny régebbi fordító ilyenkor nul lmut:ltóval tér vissza. Ha ilyent 11asználunk, föl tétlen e ll e nőri zzük. hogy null-c a 1llutatónk értéke a new végrehajtása után. Minden újabb fordító esetében kiv(:tclck dob!lsára számíthatunk.
A delete kulcssz6 használata :\liután végeztünk a memóriaterületünkön l évő adatokkal, ki kell adnunk a delete utasítást a memóriadmellartalmaz6 1l111latÓra. A delete . viss7.llcsatolja" a memóriát a szabad tetillethez. Ne feledjük, hogy maga a mutaLó - ellentétben lIzL11 a mem6riaterüleltel, ahová mutat - lokális változ6. Miután a függvény, amelyben bevczcuünk, ViSSl.:ltér a főprogra mhoz, a mutató látókörön kívülre kerül, és elveszik. A new kulcsszóval lefoglalt memóriareriller, nem szabadul fel automatikusan, te hát ilyenkor az a LCrület c1érhetetlenné válik, ezt a helyzetet nevezzük memóriClszivárgásnak (memory leak). Azért hívják szivárgásna k, mivel a program futásának végéig nem férhetünk hozzá, azaz o l y~m, mintha a szabad memória elszivárogna a számítógépünkból Hogy visszaadjuk a memóriatcriilctet a rendszernek, a delete utasítást mindig ki kel t adnunk. Nézzünk cgy példflt: delete pPointer ; Amikor töröljük a mUlaL6t, valójában azt a memóriatcriiletet szabadít juk fel, amelyre a mutató hivatkozott, valami ilyesmit kétilnk: ~Csatold vissza a a szabad területhez azt a memóriadarabkát , amire a mUlató hivatkozik." A mulató még ezután is mutató ma· rad, azaz szükség esetén újra mcmóriadmet rendelhetünk hozzá. A dinamikus lárban történő mcmóriafoglalásr.! a 9.4. LiSla mutat példát. Láthatjuk a létrejött változó használatát, majd törlését is.
178 1111. rész • M.móriakezelés
Figyeleml
Mutatók törlése
Amikor meghívjuk a dele t e utasítást egy mutatóval, a hivatkozott terület felszabadul. Ha újra törölni próbáljuk ezt a mutatót, a programunk összeomlik. Ennek kivédésére ha törjünk egy mutatót, állítsuk az egyben null értékre, a delete meghívása egy null mutat6ra ugyanis garantáltan biztonságos. lássunk egy példát: AnimaI * pDog .. new AnimaI ; de l e t e pDo g ; Ilmem6~i a fels?abaditás3 pDog '" O: IImutat6 nu ll-ra állítása II . ..
delet e pDog; r/veszélytelen
Ne aggódjunk, ha a fenti kódot nem értjük teljesen. Itz objektumok dinamikus keze-
léséról számára majd a következő leckében lesz szó. A módszer természetese ködik az egyszeru adattípusokra, például az int-re is:
mű
int *pNurnbcr = new in t; dele t e pNumberl //mem6ria fels~ftbaditása pNumhcr = Ol //mutat6 null-ra állitása
/ I . .. deletc pNumberl /lvesz61ytelen
9.4 Us" - Mem6riafogl.lás60 mutat6k tö~60. (poIntarhHp.Cpp) O: 1: 2:
II 9. ~ Lista: 1/ Mem6riafoglalás és mutat6k t6rlése .include
3. 4:
5: 6: 7: 8:
9: 10 : 11 : 12:
int main () { int localV"riable :: 5; int * pLocal= &localVariable; int * pHeap = new int ; if (pHe,lp "".' NULL) { std :: cout« "Error! No memory for pHcap!!"; return l;
13 : 14 : 15 : 16 : 17 :
18 : 19 : 20 :
21 : 22 :
23 : 24 : 25 : 26 : 27 : 28 : 29 :
'*pHcap::: 7; std :: cout« "localVariable : • « localVariable « std :: cout « · ·pLoca l : " « " pLocal « "\n '; std :: cout« ' ''pHcap : " « "pHaap« ' \ n "; delet a pHeapl pHeap " new int ; i f (pHeap == NULL) {
std :: cout« r eturn l :
' Error! No memory fo r pHeap!!" ;
"pHeap = 9 ; std ::cout« '''pHea p: delet e pHeap; rcturn O:
'«
*pHeap «
'\n' ;
"\n ";
9. 6ra • Mutat6k 179
localVariable: 5 *pLocal: 5 *pHe ap: *pHcap:
7 9
A 6. sorban kezd6értékkellátjuk el a bevezetett lokális vá1tozól. A 7. sorban egy muta· tónak adjuk kezd6értékkéOl a lokális változó címét. A 8. sorban egy újabb mutalól vezelünk be, amelynek énékéül eh'Y new int utasítás ViSS7..atérési értékét adjuk, amely di· namik~lsan foglal memóriát egy int számára. A 14. sorban a 7 értéket tesszük az újonnan foglalt mem6riatelÜleLre. A 15. sorban kiimtjuk a lokális változó értékét, mlljd a 16. sorba n a pLocal {tll::l ] hivatkozott értékeL A vánnak megfelel6en a k é l érték megegyezik. A 17. sorban a pHeap által hivatkozott értéket íratjuk ki. A kimenet:lZ1 a tényt igazolja, hogya 14. sorban áradott érték hozzáférhet6. A 18. sorban a 8. sorban fogla lt memóriát felszaood ítjuk a delet e lltasítás meghívfisfival. Ennek hatás.1ra a felszab:lduló memóriaterület címe is törl6dik a mutatóból. A pHeap ennek következtében szabadon újra felh:'lsználható. A 19.-25. sorokban újr:.! mem6riacímet rendelünk hozzá, majd a 26. sorban kiír:.lljuk az eredményl. A 27. sorban újr-.t visszacsatolju k a szab;ld területhez a lefoglalt memóriát. Ugyan a 27. sor redundáns (mivel a program végén a lefoglalt memóriaterülelek úgyis fe\.szabadulnak), mégis ajánlatos a memóriateriiletet a progmmoz6nak felszabadítania. Ha a program megváltozik, vagy hozzáínmk még valamit, el6nyös lehel, ha már eI6z(5leg gondoskodtunk az ilyenkor szükségessé vá ló lépésekr61.
A mem6riaszivárgás elkerülése A másik gyakori oka a mem6riaszivárgás jelenségének, ha m6g azcl6tt új címet rendeIünk a mutatóhoz, hogy felszabadítoltuk volna III általa hivatkozott memÓriaterületel. Nézzük a következő k6drészletet: unsigncd uhort int * pPointer = new unsigncd short int; *pPointer '" 72; pPointer '" new unsigned short int ; *pPointer '" 84;
A fenti kódrészlet cls6 sorában egy pPointer nevú mulat6l hozu nk létre, majd hozzárendelünk egy a dinamikus memóriához tartozó címer. A második sorban erre a területre a 72 értéket helyezzü k. Eddig a dolog rendben is volna. A 3. sorban azonban ugyanehhez a mutal6hoz immár egy másik dmet rendel ünk, a 4. sorban pedig a &1 értéket helyezzük el ezen az új területen. Az erede[Í memóriaterület, ahová cl6z61eg a 72
180
Im. rész • M.m6riak.,.~s értéket írtuk, most már nem hozzáférhető , mivel a mut:lt6t, amely az oda vezet6 utat mutatta, időközben fdülínuk. Mivel pedig nincs semmilyen mód az eredeti terülel elérésére, felszabadílani sem mdjuk azt a progmm futásának vége előtt. A fenti kódrészlet tehát helyesen a következ6képpcn rest: unsigned short i nt * pPoint er '" new unsigneo short int; " pPoi n t er = 72 ; delete pPointer : pPointer '" new unslg ned short i n t ; *pPointcr -
84 ;
Az újabb k6drés zletbe n .. zl a memóriaterületet, amire a pPointer hivatkozik, immár fels7Abadítjuk a 3. sorban , vagyis kiküszöböltük a fenl vázolt hibál.
Tudta hogy... 1
Kövessük a mutatókat
Minden egyes alkalommal. amikor egy new utasftást kiadunk, ki kell adnunk hozzá később egy delete utasítást is. Fontos követnünk, hogy melyik mutat6hoz van memóriaterület hozzá rendelve, és mindig győződjünk meg róla, hogy a lefoglalt memóriaterületet visszaadjuk a rendszemek, miután már nem használjuk.
A malloc és free Bizonyára találkoztunk már régebbi programokban malloc () és free () függvényhivásokkal. Ezek a függvények hosszú ideje a C nyelv részei, és ugyanúgy a dinamikus memóriakezelésre szolgálnak, mint a newés a dele t e utasítások. A leglényegesebb különbség a régi C és az új C++ módszer között az, hogyamal loe számára pontosan meg kell mondanunk, hogy mennyi memóriát foglaljon. Nézzük meg a fordít6prog ramunk súgóját vagy kézikönyvét, ha többet szeretnénk tudni róluk. Mi természetesen a C+ + nyelvi lehetóségeit fogjuk használni a továbbiakban is.
Kérdések és válaszok Kérdes: Miél1 olyan jOllfosak a mulalób?
t'á/asz: Amin t ez ebb61 a leckéb61 is kiderült, a 1l11.1t:l!6k azért olyan fOnlosak, mert a segíL<;égükkel a dinamikusan kezelt memóriában is tudunk objektumokat elhelyezni, illetve lehetővé teszik a refe rcnciakénti paraméterátad:.íst is. Ezen kívül a 13. órában majd látni foguk, hogy hogyan használhatjuk a mul,Hókal a töhb szerepkörben felhasználható osztályok esetében .
Kérdés: MiiJT1 J•.'e/l egyáltaláll bajlódllllllk a db/ali/ikm memóliakeze/essel? Válasz: Egyrészt a dinamikusan kezelt memóriában létrehozoll objektumok azután is megmamdnak, hogyafüggvénylink visszatért a hívóhoz. Ezen kívül az objektumok tárolásának ez a módja lehetővé teszi, hogy futásidőben dönLo;ük el, mennyi objekrumra lesz szükségünk, ahelyett, hogy előre lefoglalnánk valamennyinek a helyet. Ez utóbbi lehetőséget majd a 10. órában ismerhetjük meg mélyebben.
Gyakorlatok Most, hogy megismerhettük a mutatók használatát, válas7.0ljunk néhány k6rd6src és oldjunk mcg pM felad:not, hogy meger6síLSük eddig megszerzett ismereteinket.
Kvfz ']. Mi a különbség a O és NULL mulató kezcl6értékek között? 2. Mennyi memóriát foglal egy egészhez létrehozott mutató? Mennyi memóriát foglal egy lebegé5pontos változól címző mulató? 3. Mi az. a mem6riaszivárgás? 4. Hogy:1Il tudjuk felszabadítani a new kulcsszóval lefoglalt memóriát?
Feladatok l. MódosítSlIk a pointeruse. cpp (9.2. Lista) programot úgy, hogy a pAge mlltat6t
O kezd6értékkel lássuk el NULL helyet!. Változott bármit is a futási eredmény? 2. M6dosítsuk a pointergtore. cpp (9.3. Lista) programot lll. alábbiak szerinl: szorozzuk össze a yourAge és pAge változókat, majd tároljuk ezt az értékeket egy új vált07.óban. fmssuk ki az új változó értékét. Gondolkodjunk el, vajon honnan tudja a forditó, hogy II " karakter mikor jelent szorLást és mikor a pAge mutatóra történő hivatkozást. 3. Végezzünk újabb módosítást a pointerstore . cpp (9.3. Lista) progmtnon: oldjuk meg, hogya "pAge mutatót használva módosíLSa a myAge vagy yourAge v;,\ltozők tartaimát. Megváltozik ett61 a pAge-ben tárolt mcm6riacírn?
182 1111. resz • M,m6ri.k,zelés
Válaszok a kvízkérdésekre Mindkettő a nulla címel rendeli a l11utat6hoz. A NULL nyilvánvalóan egy mulató, míg a O inkább úgy néz ki , mint egy egész, de a jelentésük végeredményben azonO$. KódoláSt stílus kt!fdése, hogy ki m<::1yiket használja. 2. A legtöbb rendszeren a válasz az, hogy ugyanakkorák. Amúgy viszont progr..!moz6ként egyáltahin nincs szükségük erre az információra. Elég annyit tudnunk, hogya mutatók mérete biztosan elegendő a hhoz, hogy dférjenek bennük a gé-
l.
pünkön használatos memóriacímek.
3. A mem6riaszivárgiís akko r lép fel , amikor dinamikusan foglalunk Ic memóriát, de nem szabadítjuk fel azt, antikor már nem használjuk. A program ilyenkor tovább fogalja a memóriát, így egyre kevesebb szabad hellyel gazdálkodhat 4. Használjuk a delete kulcssz6l. A legjobb :lZ, ha azonnal töröljük azt II mutal6t, :lmely áhal dmzett adatra már nincs szükségünk.
10.
ÓRA
A mutatók kifinomult használata Ebben az órában a következőkr61 lesz szó: • Hogyan hozzunk létre objektumokat a dinamikusan kezdt memóriában • Hogyan haszn:'ilhatjuk igazán hatéko nyan a mutatókat • Hogyan cl6zhctjük meg :il memó riagondokat a mutatók használata közben
Objektumok létrehozása a memóriában A CH programozó egyik leghatékonyabb eszköze a mutatók használata: segítségükkel közvetlenül hozzáfé1'11Clóv(: v{l]ik a számítógép memóriája. Ahogy létre lehet hozni egész szflmra hivatkozó mutatót, ugyanúgy bármi lyen objek· [umra rámutathatunk Ha deklarál unk egy Cat típusú objektumot, akkor deklarálható erre az osztályra hivatkozó mutató, és máris példányosítható egy Cat objektum a dína· mikusan kezelt me m6riában, mint ahogy az a veremben is meglehető. A szintaxis megegyezik az egészeknél használnal: Cat *pCat = new Cat ;
184 1111. rész • Memóriak8lBlés
Ez az alapértelmezett konstruktort ruvia meg, amely nem kér paramétert. A dinamiku s memóriában vagy a vereremben konstruktorrallehet objektumot létrehozni.
Objektumok törláse Ha egy mutatón keresztül törlünk (delete) egy objektumot a memóriából, akkor az objektum destruktora hívódik meg a memóriaterület felszabadítása el őtt. Ez lehető séget ad arra, hogy az osztályunk után kitakarítsunk, hasonlóan ahhoz, mint ahogy ez a verem esetén is megtön6nik. A 10. 1 Lista bemutatja, hogyan hozhatók létre és hogyan törölhetók objeknllnok a mcmÓriáb6l.
10.1 lma - ObioktumDk I_ozáu 60 tö~.... di....lkuIa. kezolt mom6ri6llan (h•• pcreate....)
o:
II 10 . 1 Lista 1 : II Objektumok létrAhozása a mem6riáb;:ln 2: #include ]
,
4: 5: 6:
7: 8: 9: 10:
11: 12 : 13 : 14 : 15: 16 : 17 :
class SimpleCat ( public: SimpleCat() ; -SimpleCat() ; privata: int itsAge; }; SimpleCat : : S ilnpleCat () ( std: : cout « ' Constructor called ." « itsl\ge ol 1;
std : : endl ;
18 : 19 :
20 : 21 :
SimplozCi!lt: : -SimpleCa t () { std: : cout « "Dcstructor called." «
std: : endI;
22 :
23 : 24 :
int main ()
25 : 26 : 27 :
{
28 : 29 :
30 : 31 : 32 :
33 : 34 : 35 : 36 : 37 :
std: : COIJt « ' SimpleCat Fr isky . SimpleCat Frisky ;
«
std : : end.1 :
std : : cout « "SimpleCat *pRi!lgs z new Simplecat ... " « SimpleCat * pRags ; new SimpleCat; std: : cout « "delete pR3gS ... • « delete pRags; std: : cout « return O;
std : : endl;
std :, endl ;
" Exiting, watch Frisky go .. . " «
std : :endl :
10. óra· A mutatók kifinomult használata 185
Kimenet SimpleCa t Frisky . . . Constructor called . SimpleCat * pRags = new simpleCat ... Constructor called. de1ete pRags ... Dcstructor ca1led. Exiting, watch Fdl;ky go . Destructor ca1led.
s A 4-ll. sorban deklaráljuk a lecsupaszított simpleCat osztályt. A 27. sorban Frisky létrejön a vermen, melynek hatására lefut a konstruktor. A 30. sorban pRags lllutatóv;l l létrejön egy másik simp l eCat, de ez már a dinamikus memóriában (,I konstll.1ktor itt is meghív6dik). A 33. sorban delet e-lel töröljük a pRags mutat6t, ezzel a dcslruklorl is meghívjuk. Amikor a f6program függvénye véget ér, Frisky a hatókörén kívül kerül , így a destruktor munkába 16p.
Adattagok elérése mutatókon keresztül A helyileg (veremben) 16trdlozott Cat objekl'umok esetén
jektumok eléréséhez fel kelloldanunk a mulatót , és a pont oper:.'lIort kell meghívnunk a mutatott objekrumra. így például a GetAge () tagfüggvényt így érhetjük el: (·pRaQ's) .GetAgc () ; A zárójel biztosítja, hogy a pRags felold:'ísa még t.örténjen.
<1
GetAge () függvény meghívása
előtt
Mivel ez igy elég kényelmetlen, a C++ nyelv rendelkezésre bocsát egy rövidített operátort a közveten eJéreshez, a ~ mlllar opecltort (->, kÖl6jel + ~ nagyobbn jel). A C++ eZl egyeden szimbólumként kezeli. A 10.2 Lista mutal egy példát a memóriában létrehozott objektumok adattagjainak és lagfüggvényeinek elér~sére. 10.2 Lista - Dinamikusan létrehozott obisktumok adanaaiainak az elérése
(heapaccess.cpp)
o:
II 10.2 Lista 1 : II Objektumtagok elérése 2: linclude 3 : using std::endl; 4: class Simp1cCat
a
dinamikus memóriában
S:
(
6:
public : simp1ecat() {itsAge - 2; }
7:
III. rész · MemóriakeZ8lés -Simp1eCat{l II int GetAge(l const return itsAge ; } void SetAge{int age l { itsAge = age ; } privatc : int it sAge :
8, 9, 10 :
ll : 12 :
13 : 14 :
};
15 :
int main(J
16 : 17 : 18 :
SimpleCat • Frisky : new SimpleCat : std : :cout « "Frisky • « Frisky->GetAge(l « • éves' « endl;
19 :
20 :
Frisky->SetAge{5l : std : :cout « 'Frisky • « Frisky >GetAge() « • éves ' « endl :
21 : 22 : 23 :
24 :
25 : 26 : 27 :
dc1cte Fr i s ky; return O;
Kimenet Frisky 2 éves Frisky 5 éves
A 17. sorban egy SimpleCat o bjektumpé ld{my jön lé tre a me móriában, me lynek é le tkonn az alapénelmezett ko nstruktor 2-re á llítja be. A lR. sorban a GetAge () tagfüggvé nyt hívjuk meg. Ez most egy mutató; ilyenkor a • lilii/a f operátorral tudjuk elérni :1 hivatkozon tagfüggvC:nyt. A 21. sorban a SetAge () metódus, a 22. sorban pedig újra a GetAge (l hívódik meg.
Adattagok a dinamikus memóriában Az osztályo k egy vagy több adatlagja rámutathat a mc m6ria egy-egy objekllllnára . A szükséges memóriate rü let lefoglalása tönénhc t az osztály konstruktorában vagy valamely metódusában, a fe lszabadítása pedig a dcstruktorában, ahogy az a 10.3 Listában is látható .
10.3 Lista - Adattal sl8r.pét játszó mutatók (datamember.cppl o : /I 10.3 Lista 1 : II Adattag-mutat6k 2 : iinc lude 3,
4: 5,
class SimpleCat
10. óra · A mutatók kifinomult használata 187 6, 7, 8,
9, 10 : 11 :
public : S i mp leCat ( ) ; -SimpleCat{ }: int GetAge~() cons t return ~ it sAge ; l v o i d SetAge(int a g e ) ( *itsAge " a ge ; )
12:
int GetWci9ht() con st { return *it sWcight ; v oid setWeight ( i nt weight) ( *i t sWeight = weight ;
13 :
14 : 15 : 16 :
17 : 18 : 19 : 20:
21 : 22 : 23 :
p riva t e : int ~ itsAge ; int * itswci9ht ; );
SimpleCat : : Simp1cCat (J ( i t sAge = new 1n t(2) ; i t s Weight • new i nt ( 5) ;
24 : 25 : 26:
SimpleCat , : -SimpleCat ()
27 ,
(
28,
29:
delete itsAge : delete itsWeight;
30 : 31 : 32 :
33 : 34 :
35:
36 :
int main() ( SimpleCat *Frisky = new SimpleCat ; std : : cout « "Frisky " « Frisky->GetAge() « • éves\n' ;
37 :
3B : 39 : 40 :
41: 42:
43 :
Frisky->SotAge(S) ; std : : cout « " Fr i s ky , « « • éves\ n';
Frisky->GetAge ( )
d elete Frisky ; return O;
44 :
Frisky 2 éves Frisky 5 é ves
A Simp1eCat osztálynak két tagváltoz6ja van, mindkettő egészeket címez. A konstruk-
tor (a 20-24. sorban) inicializálja ezeket a mutat6kat úgy, hogy a dinamikus mem6 riaterületre mutassanak, és a változ6khoz hozzá rendeli a megadou kezd6érrékekcl.
188 1111. rész • Mem6riakezelés A destnIktor (a 26-30. sorban) felszabadítja a lefoglalt memóriaterOletekcl. Minthogy ez a destruktor, nincs értelme nullázni a mutatókat, hiszen ezek a későbbiekben már nem lesznek elérhetőek. Ez egy bizlonságosan vállalható kivétel azon szabály alól, hogy a törö lt mutatókhoz érdemes null értéket rendelni ; persze nem okoz gondor a szabály követése se m.
A hívó függvény - jelen esetben a main () - mit sem rud arról , hogy az itsll.ge és az itsWeight a dinamikus memóriára irányuló mutatók. Egyszenle n meghívja a GetAge () és a GetWeighL () függvényeket, mint eddig is; a mem6 riake zelés részlet.ei el vannak rejtve az osztály megvalósításában - ahogy annak lennie kell . Amikor F'risky törlődik a 42. sorban, egyből meghív6dik a destruktOJ:l. A deSlmklor minden adattag-mutatót töröl. Ha ezek kimutatnak egyéb objektumokra is, melyek a felIl:lszn{116 {tltal definiált oszt51yok példányai, akkor azok destruklorai is meghfv6dnak. Ezen a p6kl{tn jólláthat6, hogy miért érdemes saját destruktort írni (a fordít6 progr.:tn1 állal fclaj{tnlotl alapértelmezés helyett). Alapértelmezetten a 28. és 29. sorban láthat6 törl(:sek n~m tört(:nnének meg; a programozómik kell ezeket ll1<:!gírnia. Ezek híjáll a 42. sor törl(:se csak magám a F'ris ky objektumm vonatkozna (és a hozzátartozó mutatók .....,i), de a dinamikus memóriában létrehozott bejegyzésekre nem. A destHlktor nélkü l memóriaelszivárg:'isunk lenne.
A this mutat6 Az osztályo k minden tagfuggvénye tartalmaz egy rejtett paJ:lmé tert: a this ( ez) mutatót. Ez muta! magára az obje ktumpéldányra. Így minde n GelAge (J vagy Set.i\ge () hlvásnál az obje ktum this mutatója rejtett paraméterké nt jele n van. A Lh is mutató fe ladata rámutatni arra az objektumm, melynek metódusát meghívtuk.
Általában nem használjuk; egyszeruen meghívjuk a tagfüggvl!nyckel l!s beál1ítglltjuk
a tagválLOZókal. Alkalmanként azonban szükségünk leheL arm, hogy magát az objektumot is elérjük (például a rá hivatkozó mutató vissza3dása miau). Ezen a ponton válik igaz5n hasznossá II this mutató. Általftban nem kell a this mutatót haszná lni ahhoz, hogy egy objektum tagváltozóit elérjük ugyHnazon objektum tagfüggvényein belül. Azonban meghívható kifejezenen a t hi s mutató is, hH a programozónak úgy tartja kedve. A ] OA Lista bemutatja, hogy ez miként használható.
10.4 Usta - A this mutató használata lusingthis.cpp)
o:
II 10 . 4 Lista l : II .i\ th i s mut at6 használata 2: .include 3 : using namespa ce std;
10.6ra • A mutatók kifinomult 4: 5: 6: 7:
8: 9:
10 : ll :
12 : 13 : 14:
15 : 16 : 17 : 18 : 19:
20 : 21 : 22 : 23 : 2 4: 25: 26 : 27 : 2 8: 2 9: 30: 31 : 32: 33: 34: 35 : 36 : 37 : 38: 39 : 40 : 41 :
class Rectangle { pu blic , Rec t a ngle () ; -Rectangle() ; void SetLength(int length) ( this - >itsLcngth = length ; int GetLength() const ( return this->itsLength; void SetWidth(int width) ( itsWidth = widt h ; ) int GetWidth() const { return itswidth ; } private: int itsLength; int itsWidth ; ); Rectang1e : : Rectang1e ()
( i t s Wid th = 5 ; itsLength;; 10 ;
Rectangle : : -Rectang1e() () int main() ( Ractangle t haRect ; cout « "Tógla 1apom « • láb cout « "Téglalapom « " láb
theRect . $et Length(20) ; theRect . SctWidth(lO); cout « "Tégla!apom • « thcRect . GetLcngth(l « " láb hosszú . " « endl ; cout « "Téglal a pom " « t h cRect . GetWidth() « • láb széles . " « e ndl ;
42 :
43: 44 :
" « theRect . GetLength() hosszú ." « end1; • « theRect . GetWidth() széles . " « endl;
return O,
Téglalapom Tégl alapom Téglalapom Tégl alapom
10 láb hosszú . 5 láb széles . 20 láb hosszú . 10 láb széle s .
190 III. rész • Mem6riakezelés
-
A SetLength () és GetLength() hozzáfér6 fü&!,,,,ények kifejezetten a this mutatót használják a Rectangle objektum tagváltozóinak elérésére, ellentétben a SetWidth (J és GetWidth () hozzáfér6 függvényekkel , melyek másként dolgoznak. Nincs különbség a viselked ésükben, csak abban, hogy a this nélküli metódus kódja talán olvasl1at6bb. Tudta hogy...?
Mire is val6 tehát ez a this mUlat6?
Ha csak ennyi értelme lenne a this használatának, nem lett volna érdemes megemlfteni. A this mutató hordozza az adott objektum memóriacfmét - ez igen hatékony eszköz lehet! A könyv egy késő b bi (14.) fejezetében láthatjuk majd ennek a gyakorlati felhasználását az operátorok túlterhelésénél. Egye l ő re elég annyi is, ha tudunk a this mutató l é tezéséről, és arról. hogy magára a szóban forgó ob jektumra mutat. Semmi dolgunk vele, nem kell létrehoznunk vagy törölnünk - ezt elvégzi helyettünk a fordítóprogram.
Gazdátlan vagy "lógó" mutatók A programhibák egyik forrása a gazdátlan mutatókb61ered, melyeket nehéz és kellemetlen felkutami. Gazdátlan mutató akkor keletkezik, amikor törjünk egy mutat6t (deleteleO - ily m6clon felszabadít juk a hivatkozott memóriaterillelet - és kés6bb anélkül kisércljük meg újra használni ezt a muratót, hogy bármit is hozL1rendeltünk volna. Olyan ez, mintha egy cég elköltözne telephelyér61, és egy ügyfelük a régi sti'imon próbálna telefonálni nekik. Lehet, hogy semmi különös nem történik - csöng egy telefon egy elhagyott irodaházban. De az is lehet, hogy ezt a számot már valaki más használja, aki esetleg v~gigdoJgozta az éjszakát, és ez a telefoncsörgés ébreszti legszebb álmából. Röviden: ne használjunk olyan mutatókat, melyeket e16z6leg töröltOnk. A mulató továbbra is a mem6ria egy bizonyos területére mutat, de ;1 fordítóprogramnak joga van oda más adatokat tenni, így ennek a mtJtatónak a használata II progl"J.m összeomJásához vezethet. Ennél is rosszabb, ha a program vígan fut tovább, és a hiba csak néhány perccel kés6bb következik be. Ezt időzített bombának hívják, és nem túl vicces. A bizlonslig kedvéélt érdemes NULL-ra állítani (és ezzel lefegyverezni) a használaton kívül helyezett mutat6kat.
Apropó
K6bor mutat6k
Az. gazdátlan mutatókat kóbor vagy lógó mutatóknak is hívják.
10. óra • A mutatók kifinomult használata 191
Konstans mutatók Mutatók esetében a const kulcssz61 ti típus előtt , után, vagy mindkét helyen hat juk. Az alábbi deklarációk mind helyesek:
használ~
const int • pOne ; int • con s t pTwo ; c onst int • const pThree ;
Ezek különooz6 mutatókat eredményeznek. pOne ch'Y konstans egészre mutat. A hivatkozou értéket nem lehet megváltoztarni a mutatón keresztül, azaz nem m űködik az alábbi sor: ' pOnc = 5
Ha eZI kís6reljük meg, hibát ad a forJít6 progmm.
pTwO
konstans mutató egy egészre.
A hivatkoZQII szá m értéke megváltoztathal6, de a p'I'wo nem mutathat sehová máshová.
Ko nsWns mutat6hoz nem lehet más változót rendelni. Azaz nem ~p'rwo
..
működik
a következ{}:
&x
pThree konstans mutató egy konstans egészre. A hivatkozott szám értéke sem válloztatható mcg, és a pThree sem mutathat semmi másra. Húzzu nk egy képzeletbeli függ61eges vonalat :l csillag jobboldal án. Ha a cons t szó a vomtlt61 ba lra esik , akkor az objektum konstans, és ha jobbr-.l, akkor pedig a maga mutató változtathatatlan const int· pl ; II A hi vatko.,;ott egé!lz konstans i nt • const p2; I I p2 ko nsta ns , ne m mu tat hat semmi másra .
Konstans mutatók és konstans tagfüggvénv.k A 7. 6rán tanultunk az osztályok alapjair61. Oli volt szó arrol, hogya const kulcsszót lehet tagfüggvényekre is vonalkOZL1.tn i. l ia egy függvényt konstansként dekladlunk, a fordíl6progr-.un hibajelzésselm3sít viSsza minden kísérletet, amellyel az adon függvény változtatni akama az objekrumon. Ha konstans objcklumra hivatkoz6an deklará lunk egy mutatól, akkor ezt csakis konstam; mctódusokkallehet használni. A 10.5 Lista ezt illusztrálja.
10.5 Usta - const objektumot cimz6 mutatá használata (constptr.cpp) o : !! 10 . 5 Lis ta l : I I ko n s tans objektumot c i mzo mu tat6 ha s.,;ná l ata 2 : 'incl ude 3, 4 , class Rec t angle 5: { 6 : public ,
192 1111. rész • M.móri.koze~s 7: 8: 9: 10 : ll : 12: 13 : 14 : 15 :
16 :
Rectangle() ; -Rectangle() ; void SetLength( int l e ngt h) ( itsLength = leng t h ; int GetLength() const { re t urn itsLength ; } v o id SetWi d th {int wi dt h) l itsWid th - wi dth; i nt Get Widt h() c on s t { retu r n i t s Wid th ; )
private : int itsLength ;
int itsWidth ;
17 : 18 : 19 : 20 : 21 : 22 : 23 : 24 :
25 : 26 :
}; Rectangle: : Rectangle () : itsWidth(S) , itsLength (10) (l
Rcc t angle : : _Rectn ng l e () {}
27 : 2B :
int main()
29 :
30 :
31 : 32 :
Rectang!a* pRect = new Rectanglc; const Rectangle * pConstRect • new Rectangle ; Rectangle • const pConstPtr = new Rectangle ;
33 :
34 : 35 : 36 : 37 : 38 : 39 : 40 : 41 : 42 , 43 : 44 , 45 : 46 : 47 :
48 : 49 : 50 , 5 1: 52 :
std : : cout «
'pRect szélessége : " pRect ->GetWidth()« láb"« std : : endl ; std : : cout « "pCon s t Rect szélessége : « pCo n stRect->Cctwidth() « ' láb' « std , , endl; stó : , cout « ' pCo n stPt r szé l essége , « pConstPtr->CetWi d th () « ' láb ' « std :: endl ;
«
pRect->setWidth(10) : II pConstRect->SetWidth(lO) ; pConstPtr->SetWidth(lO) ; std : : cout «
'pRect szélessége: pRcct->GetWidth()« láb'« std : : endl ; std , , cout « 'pConstRcct szélessége : « pCo n s t Rect->Get Width() « • l á b " « std :: cndl , s td : : cout « 'pCon s tP t r szélessége : « pConstPt r - >Ge LWidth() « • láb' « s td : : endl ; return O; «
pRect sz élessége : 5 láb pConstRec t szélessége : 5 l áb pConstPtr szélessége : 5 l á b pRect szélessége : 10 láb pConstRcc t szélessége , 5 láb pCo n s t Pt r s zé l essége : 1 0 l áb
10, óra • A mutatók
A 4- 18. sorokban deklarálunk egy téglabpot. A 13. sorban a GetWidth () tagfüggvényt konstansként deklaráljuk A 30. sorban cgy mutatóva! hozzuk IéIre a pRect téglalapot. A 31. sorban deklarált pConstRect egy konstans téglalapra hiv:ltkozó mulató. A 32. sorban létrehozott pConstPtr pedig egy téglalapra hivatkozó konstans mulató. 11.34-39. sorok kiírják a három szélesség-értéket. A 41. sorban a pRect segítségévcl álállitjuk az els6 téglalap szélességét 10-fe. A 42. sorban a pConstRect használatával tennénk u~,'yaneZlJ de ez II mUlató egy konstansként felveti téglalapra hivatkozik, melynek é rtéke nem változtatható, és nem hívható meg rá nemkonstans L:tgfüggvény. Ez a sor tch:1t megje,gyzéssé van alakitv:-I, A 32. sorban 3 pConntPtr konstans mutatóként lett lélfehozv
Konstans this mutat6k Amikor konstansként deklarálunk egy obje ktumot, ezzel a hozzá tartozó this mutatól is konsla nSr:l hivatkozóként hal:1rozzuk meg. A konstans this mutató viszont csak konstans tagfüggvényekkel használhat6. A kom:t:lOs objC;!klumok és konstans mulatók II következő órán új!"'..! el6kerülnek, amikor a konstans objektumokra vonatkozó hivatkozásokat tárgyaljuk
Kérdések és válaszok Kérdés: Mié11 érdemes konsUII/ske1lf dek/arálllom egy objektumot, ha ezzel korlálozom a jelhasz1lálltalóságál? Vá/asz: Progmmozóként az az egyik célunk, hogy magával a forditóprog!"'dmmal minél több hibátlelepleztessünk. Egy nehezen fe!derithet6 komoly hib
Kérdés: Mi é11elme azza! baj/ódllom, hogy bármit is a dillamikllst/ll kezelt memóriában dek/aráljak? Válasz: A memóriában deklarált objektumok megmaradnak a függvények visszatérése
után is. Ezek ráadásul dinamikusak iSi vagyis annyit lehet belőlük létrehozni, amennyire csak szüksége van egy adott alkalmazásnak vab')' egy bemenő adath
194 ( 111. rész • Mem6riakezelés
Gyakorlatok Az elmúlt órán a mutatók használatinak sokféle l ehetősége tárult föl az olvasó elön; nldásának elmélyítéséhez válaszoljon meg most néhány kérdést és végezzen el néhány
gyakorlatot!
Kvfz 1. Milyen C++ kulcsszavakkal tudunk dinamikusan mem6riatelÜletet foglalni és felszabadíta ni? 2. LeheL~ges-e törölni egy objektumOl (azaz meghívni a destruktorát) anélkül, hogy nem használjuk a törlési (delete) p:mmcsot? 3. Melyik
Feladatok L Vegye ki a megjegyzésb61
2. 13iwnyára észrevette, hogy r16hány programkód \n-et használ, más helyeken std : : endl nJnik fel. Helyenként std : :cout és std : : endl szerepel, másuLt pedig a using utasítással együn használt cout és endl. Próbálja meg ezeket cserélgetni a fejezet programjaiban. Melyiket jobb használni, a \n-et vagy az end1et? Melyik az egyszenlbb, a std : : elCítag vagy a using utasítás? 3. Kilszőbölje ki a " mulaf' operátort a heapaccess . cpp programban (10.2 Lista)! Melyiket könnyebb olvasni, az eredeti vagy a módosítolI változatot?
Válaszok a kvfzkérdésekre 1. A new utasítással lehet memóriaterilletet lefoglalni a dinamikusan kezelt terüle-
len; a de1ete utasítással lehet ugyanezt fe lszabadítani. 2. Amikor a vezérlés kilép egy objektum hatóköréból, az objeknllll automatikusan
törl6dik. Ha egy objektu mot létrehozunk, de nem törJü nk a main () függvényben, akkor a main (I-ből való kilépéskor hívódik meg a destmkror, ahogy eZL a 10.1 Lista kimenetének utolsó sora is mutatja. 3. Az egyik lehetőség a (·pRags) . GetAge (), a másik a pRags - >GetAge (). A ~ m utat " ( - » operátor jobb, mivel ránézésre is nyilvánvalóan mutatja a programozó szándékát. 4. Gazdátlan mutatóról akkor beszélünk, ha azután kíséreljük meg amemória használatát egy mutató révén , miután már azt a mulatót töröltük. Ilyenkor nem tudhat juk, hogy az adott memóriaterület milyen szerepben áll!
11.
ÓRA
Hivatkozások Ebben az órában a kővetkezókróllesz szó: •
Mik azok a hivatkozások
• Miben különböznek a hivatkozások a mutatóktól • Hogyan lehet hivatkoz.'isokat létrehozni és használni • Milyen korlátai vannak a bivalkozásoknak • Hogyan lehet a függvényeknek értékeket és objeknllnokal átadni és t510k átvenni hivatkozások segítségével
Mi az a hivatkozás (referencia)? Az elmúlt két órán áttekintettük a mutatók használatár; hogy segítségükkel miként lehel a dinamikus memóriában lévő objektumok kal bánni, és hogy hogyan lclu;!( ezekre az objektumokra közvetett módon rámutatni. A hivatkozások, melyről ezen az órán sz6 lesz, a rnutatókhoz hasonlóan hatékony eszköZT adnak a kezünkbe, csak jóval egyszeníbb szintaxissal.
196 1111. rész • Memóriakezelés A hivatkozás egy alternatív név ( alias). A hivatkozást egy másik objektum, a célobjektum nevével lehet inicializálni. Ettől a pillanattól kezdve a hivatkozás úb.'Y viselkedik, mintha maga a célobjektum lenne; bármi , amit a hivatkozással teszünk, megtörténik a célobjektummal is. Ennyi az egész. Néhol úgy emlegeti a hivatkozásokat, mintha azok mutató k le nnének, de ez nem pomos. Bár gyakran tényleg mutatóként valósítják meg 6kel, ez csak a fordítóprogmmok gyártóira tartozik. I'rogmmozóként el kell mdni különíteni a két fogalmat.
mUlatók olyan változók, melyek egy másik objekmm memóriacímét tárolják. A hivatkozások ezzel szemben egy másik objektum névváltozatai.
A
Hivatkozások létrehozása Hivatkozást úgy hozh:nunk létre, hogy megadjuk a <:élobjcklum típusát, majd a ~ hlva l koz tí:," operátort (&), végül :1 hivatkozás nevét. Ez utóbbi bármi lehet, de könyvünkben kis r-rel fogjuk kezdeni a hivatkozások neveit. II .. például van egy somelnl. ncvlT egész változónk, akkor az .. Iábbi módon hozhatunk létre egy hivatkozást rá: int &rSomeRef • someInt:
Ezt (Jgy olvassuk, hogy ~rSomeRef hivatkozás egy egészre, melyet a someInt-re hivatkozva inicializ:ílnmk ~. A '11 .1 Lista mutatja be a hivatkozások létrehoz.'lsát és haszn:ílatát.
Apropó
Ahivatkozás operátora
A "hivatkozás operátor" !&) megegyezik a "elme" operátorral. Példánkban a deklarációban fordul eló. Emlékezzünk vissza, hogyamutatóknál is hasonló volt a helyzet ha a deklarációban szerepelt a csillag (,,) jel, az azt jelentette, hogya szóban forgó változó egy mutató. Ha utasftásban szerepelt, akkor a .közvetett' operátor! jelentette (amennyiben mutatóval együtt jelent meg), vagy matematikai kifejezésben a HSlOrzás" operátOr!. 11.1 Lista - Hivatkozások létrehozása és használata (cre8tereference.cpp) 0 , II 11 . 1 Lista 1 : II Hivatkozások (referenciák) létrehozása, használata 2 : Jlinclude 3, 4: 5,
6, 7,
int main ()
int intOne; int &rSomeRef
B, 9:
intO ne '" 5 ;
intOne ;
11. óra • Hivatkozá,ok 1197 10 , ll : 12 :
std: :cout « std : : cout «
'intOne : • « intOne « std: : endl; 'rSameRef : • « rSorneRef « std: : endl ;
13 :
rSomeRef = 7;
14 : 15 :
std : : cout «'intOne: «intDne« std : :cndl; std : :cout « 'rSomcRcf : • « rSomeRef « std : :endl;
16 :
return O;
17 :
intOne: 5 rSomeRef : 5
intOne: 7 rSomeRef : 7
A 6. sorban tleklm'álunk egy egész változóI (intOne). A 7. sorb:m hivatkozást (rSomeRef) deklarnlunk eb'Y egészre, melyet al intOne-r::t hivatkozva iniciali7...'Ílunk. Ha initiali:r.álás
nélkül akarunk deklarálni egy hiv;Ltkozásl, akkor rordítási id6ben hibát kapunk. A hivalkozások:lt inidalizálni kell . A 9. sorb:m a7. intOne-nak értékü] adjuk az 5-öl. A 10. és ll. sorb:1O kiír::tljuk az intOne és az rSomeRef értékeit, melyek természetesen megegyeznek , hiszen
az rSomeRef egy egyszeru hivatkozás az intOne-rd. A 11. sorban az rSomeRef-nek értékül adjuk a 7-el. Minthogy ez egy hiv:ltkoz..ís, az intOne alternatív neve, a 7 tulajdonképpen az intOne változólxI kerul , ami a A 14. és 15. sorban ki is derül a kiímtáskor.
A.eime· operátor alkalmazása hivatkozásokra [la hivatkozás eímére kérdezünk rá, akkor a eé!objeklum címét kapjuk vissza. Ilyen a hivatkozások Lennészele - pusztán egyallernatív névként viselkednek. A 11.2 Lista ezt mutatja be.
ll.2Usta - A hlvatkoz'lOk cfm6nak meghaUtrozása {addressreference.cpp)
o: II 11.2 Lista 1 : II A hivatkozások és az "&" használata 2: jinclude 3, 4: int main!) 5, 6: int intOne; 7: int &rSomeRef = intOne ; g,
9:
intOne "" S;
198 1111. rész • Mem6riakezelés
.
std: : cout « std : : cout «
'intOne : « intOne « std : : endl; 'rSomeRef : « rSomeRef « std : : endl;
13 : 14 : 15 :
std : : couL « std: : cout «
• &rSomc.Re ( :
16 :
re t urn O,
10 : ll :
12 :
' &intOne :
.
.
« «
&intOne « std : : end1 ; &rSomeRef « std: : e nd l ;
17 :
JGmenet intOne : 5 rSomeRef : S &intOne : 1245064 &rSomeRef : 1245064
A kapott kimenet terlll(:szelesen nem fog ezzel megegyezni, hiszen minden szá mító· gép más-más címen t{lfolja a változó kat, aItól függ6en, hogy még
Iru minden van a me-
mó riáhan és hogy mekkora az dérhel6 me mó ria me nnyisége. Ha nem Borland fordító! használunk, akkor így is festhet a lulás eredménye: intOne : 5
rSomeRcf : 5
&intOne: Ox0012FF7C &rSomeRef : Ox0012FF7C
s. III is n intOne-ra hivaLkozvH in icializáljuk az rSomcRe f -et. Ez a lkalommal a változók me mó riadmét is kiír.ltjuk, ami szinté n megegyezik. A C++ nem ad lehet6s6gcl arra, hogya hivatkozások té nyleges memóriadmét lekérdezzük, hiszen annak nincs é rte lme, szemben a mutat6kkal vagy egyéb változókkal. A hivatkozások lé trejöllü kt61 fogva a célohjeklUm szinonimáiként visclkednek, még a ~címd' operátor a lkalmazásakor is, Nézzünk egy másik példát. Legyen egy President osztályunk, melyet az alábbi módon példányosítsunk :
Lé lrehozhatunk és inidaliz:i lhatunk egy hivatkozást a President-re:
Csak egyetlen President van; mindkét név ugyanazon osztály egyazon objektumára utal. Bármi, amit Dubya-n e lkövetünk, George_W_Bush-on is végrehajtódik. Gondosan különböztessük meg a 11.2 Lista 7. sorába n található & jelet a 13-1 4. sorban talá lhat6akt61. Az e l6bbi egy egészre utaló hivatkozást deklarál rSomeRef néven , míg az utóbbiak lekérdezik egy egész számnak (és a rá vonatkozó hivatkozásnak) a mem6riacímét.
~ba n
nem szokás hivatkozások mcmóriacímével dolgozni, hanem egyszeruen ...-é1objektum helyett használjuk a hivatkozást, ,thogy ez a 11. sorban is lál'izik.
T.. pasztalt CH progmmozók is zavarba jönnek, ha szóba kerül, hogy mi történik ak.._ha egy bivatkozáshoz megpróbálunk valami más változ6t hozzá rendelni. Azt tud~_ hogy a hivalkozásokat nem lehet újminicializálni, s hogy mindig is az eredelileg kiSZt.'TTlelt célobjeklu m szinonimái lesznek. A 11.3 Lista futásának tanús.'1ga szerim az újr.a·hozzárendelést (Igy tekinthetjük, mim értékadást a célobjektum számára. 11.3 Lista - Hivatkozás sz6mára történ6 értékadál (assignrefareace.cpp) II l l. 3 Lista II Hivatkozás újrainicia l izálási kisérle t e
, : l inc lude using name space std : II hüsználjunk most std :: obj e ktumokat 4: 5: int main() 7: ~ :
int intOm! : int &rSomeRef • i ntOne :
9, .. C: : 1:
l2 : ... 3 : l4 :
intOne • 5 : cout ' intOne : \t ' i nt One end1 ; ' rSomeRef :\t" rSomeRef « end l : cou t cout « "&intOne , \t" "« &in t One « andI ; cout « '&rSomcRef , \t' « &rSomcRef « cndI ;
" "
"
"
: 5: 16 : 17 :
18 : 19 :
20 : 21 : 22 :
23 : 24 : 25 :
i nt intTwo '" 8 ; rSomeRef = intTwo : /I nem az lesz , mint amit vár hat nánk cout « " \ n intOne , \t ' « intOne « endl : cout « ' intTwo : \t' « intTwo « endl ; cou t « ' r SoIlleRcf ,\t ' « rSomeRcf « endl : cout « ' &intOne : \t" «&int One« endl : cout « ' &intTwo : \t" «&intTwo« endl ; aout « ' &r SomeRe f : \t' « &rSomeRef « end l : re tu rn O;
i ntOne : 5 rSome Ref : 5 &int One : 12450 64 &rSomeRef : 1245064 intOne , 8 intTwo : 8 rSomeRef: 8 &intOne : 1245 06 4 &intTwo , 124 5056 &rSomeRef , 1245 06 4
200 1111. rósz ' Memóri.kezelé, A kapon kimenet nem ugyanígy fog kinézni, hiszen minden számítógép más-más címen tárolja a váltm:6kat, ;Illól függ6en, hogy mi van a memóriában és hogy mennyi az elérhető memÓria. Ha más fordítót használunk, akkor ehhez hasonlót is kaphatunk: intOnc: 5 rSorneRe f:
5
&in t One : Ox0012FF7C
&rSomeRef: Ox0012FF7C intOne: 8 intTwo: 8
rSomeRef : 8 &intOne: Ox0012FF7C &intTwo: Ox0012FF74 &r.-SomeRef : Ox0012FF7C
ltt is cgy egész S:dlrnOl és egy rá utaló hivatkozást inicializálunk a 7-8. sorban, majd az egész sz.ámnak érté kűl adjuk az S-öt CI 10. sorb'Ln. A változók értékeit és címeit CI 11-
14. sorban íraljuk ki. A 16. sorban egy (lj változó, az intTwo jön létre és B-as kezd6értékkel inidaliz./j I6dik. A 17. sorban a programozó (lj hozzá rendelést kísérel meg az rsomeRef-hez; legyen 6 ezentúl az intTwo hivatkozása. Ám nem ez történik. Az rSomeRef továbbra is az intOne szinonimája mar:ld, így az ominózus értéka d~s az alábbival egyenénékll: intOne = intTwo ;
Erre elég bizonyság az rSomeRef és az intOne értékének kiíratása (18-20. sor) - ezek az intTwo értékével egyeznek meg. Az is látszik, hogy az rSomeRef elme továbbra is az intOne elmével egyezik meg, nem pedig az intTwo-éval.
HoIyeo Objektumok szinollimájaként haszn{lljuk a hivatkozásoka!. Jnicializáljuk a hivaLkozásokm.
Ne rendeljünk új objekmlllol egy,
már l ét ező hivatkozáshoz. Ne tévesszük őssze a "címr!' operátort a "hivatkozáS' operátorral.
Mire lehet hivatkozni? Minden objekmmhoz rendelhet6 hivatkozás, beleértVe a relhasználó által létrehozott objeknunokat is. Érclemes tudatositani, hogy objektumm lehet hivatkozást létrehozni, nem pedig osztályrd vagy adattípusra, mint amilyen például az egész. Nem ml1ködik tehát az alábbi sor: i nt
&
r lntHe f " i n t ; II rossz
11. 6ra • Hivatkozások I z01 Csak egy bizonyos egész változónak az álneve lehet a hivatkozás; int howBig = 200; int & rIntRef = howBig ;
Ugyarugy lehetetlen a eat osztályf'd hivatkozást állítani: CAT & rCatRef - CAT ; II rossz
Az rCatRef-et csak egy bizonyos eat objektummal inicializ.'ílhatjuk: CAT frisky ;
CAT & rCatRef = frisky;
Az objektulllokm mulató hivatkozások ugyanúgy használh:lt6ak, mint maguk az objektu mok. Az adallagok v,Lgy me l6dusok a megszokolt osztálytag-kiválaszt6 operátorral (. ) é rhet{5ek e l. A be6pítell típusokhoz hasonlóan az objektumoknak is szinonimákal reremLt!llck a hivatkozások
Null mutatók és null hivatkozások Ha egy mutat6 t tö röloc:k vagy ne m iniöalizálnak, a null é rtéket kell t:utalm3znia. A hivatkozásokkal nem így áll a helyzet. Hivatk07..á-; nem lehel null értékű, Az :l progra m, amelyik null é rtékű hivatkozásl tartalrnaz, éJVénytelennek tekinthct6, ekkor pedig akármire ké pes. Futhat normá lisan , v:lgy éppen tö rölheti az összes állományt merevle· mezünk ről. ÉlVénylc1en progl"'J.mtól minden kitelik. A legtöbb fo rdít6progr::lrn nem panaszkodik sokat, ha null értékű hiva tkozást lalá l, és csak akkor omlik össze, ha megpróbáljuk valamiképp használni ezt :1 hiv:ukozást. Nem érdemes azo nban ilyesmit haszn.ílni, me rt ha másik számítógépre vagy másik fordít{r progmmnak pr6báljuk átadni a progl"'d munkal, titokzatos hibák jelenhelnek meg null éné kű bivatkozásainkból ered6en.
Cím szerinti paraméterátadás Az 5. órnn a függvényekn é l tanu ltuk, hogy van két korlát juk: a paramétereknek csak az értékét veszi 51 a függvé ny, a ret urn utasítással pedig csak egyetlen értéket lehel visszaadni. A dm szerimi pa,.!méLcrátadással mindkét korlátozáson felül lehet emelkedni. C++·ban kéúéle módon lehe l eZl megvalósítani: mulatóval vagy hivatkozással. A szintaxis eltérő, de a hatás ugyanaz: a függvény hat6körében éJVényes másolat helyett maga az e redeti objekmm keni! átadásra. Ezzel lehet6vé vá lik, hogya függvény mcgváltoztassa a hivat· kozott objektumot. Mielőtt eZl megvi7_sgálnánk, tanulmányozzuk a 11.4 Listát, melynek swap () függvénye érték szerint kapja meg pa'dmélereil.
202 1111. rész' M.m6riakezelés
11.4 lista -~"" _ _ 6boModás
o: 1: 2, 3: 4,
5: 6: 7:
l
/1 11.4 Lista /1 trték szerinti paraméterátadás Unclude void 5wap(int x,
int Y)i
int main () ( int x = 5. Y '" 10;
8, 9,
std : : cout «
• «
"Main . 8efore swap, x:
«
10 : 11 :
y:
x «y«
"\n ";
swap(x,y) ;
12 : 13 :
s t d : : cout «
14 :
roturn O;
"Ma i n . After swap , x: «
' «
• y:
x '
«
«
'\n ";
y «
• \n" ;
y
15 : 16 , 17 :
void swap ( int x. int y)
18 ,
(
19 : 20 : 21 : 22 :
23 : 24 : 25 : 26, 27 : 28 : 29 : 30 : 31 :
int tomp; std : : cout «
·Swap. Before swap,
« temp
x y
.
.
". y , «. «x
."
• y,
temp;
std : : cout «
Main . Before swap . Swa p . Before swap . Swap . A f ter swap . Main. After swap . "
" " "
·Swap. After swap, «
. «.
". y ,
x « y «
• \n";
5 y , 10 5 y , 10 10 y , 5 5 y, 1 0
A program két változót inicializál a main ( ) -ben, amelyeket átad a swap () függvénynek. Ez arm lenne hivatott, hogy a két paraméter értékél felcserélje. Azonban a main () -beli ismételt kiíratásból az derul ki, hogya változ6k értékei változatlanok!
203
11 .Ó!a"
A probléma abból ered, hogy csak érték szeri nt kapta meg a swap () függvény 3Z x és y paramétereket; azaz csak helyi másolatok készültek a függvény számára a változ6kr61, 1\ megoldást a dm szerinti paraméterátadás jelenti. C++-ban két út kínálkozik a probléma megoldására. A swap () -nek átadhatunk (az eredeti változókra utaló) mUlató pa,dmélereket, vagy ugyanezekre ól változ6kra való hivatkozásokat.
A swapO javltása mutatók segitségével Amikor átadunk egy mutatót, akkor az objektum tényleges címélludatjuk a fi.iggvénnyel, ami ezek után rud vá ltozUllni az adott mem6riacímen leviS értéke n. Hogy a megjavítolt swap() ténylegesen tudjon cserélni, használjunk két egb;z mUlatót paraméterként. Ezen mutatók feloldása ul:Ín már ténylegesen felcserélhelő x ~S Y, amim azt a 11.5 Lista is szemléltt:li.
11.5 UI1II- CIm szerinti paraméterátadú mU1lt6kkallpanllyplJ. pI
o:
/1 11.5 List~ - Cim szerinti paraméterátadás 1 : 'include 2, 3:
4:
,. 5:
7, 8, 9,
void swap(int "x, int *y); II the "*" saya that the function expects pointers int main() int x • 5, Y = 10 ; std :; cout «
10 ; 11 , 12 : 13 :
s wap (&x . &:y) ; s td: : cout «
14 :
return O:
15 : 16 : 17 : 18 :
19 : 20 : 21 : 22 :
mut~t6kka1
"Main . Bcfore 9wap , x: " « x « « y « Y' "Main. After s wap , x: « x « " y, " « y «
"\n" ;
"\n";
void swap (int *px, int *py) int temp; s td : : cout «
·Swap. Bcfore s wap , "px: " « *px « " *py : " « .py «
• \n' ;
23 : 24 , 25 : 26 :
t e mp = *px ; ·px *py : .py = temp ;
27 : 28 : 29:
30 :
std: , cout «
" Swap. After s wap, «
" px: " « *px • *py: • « "py «
"\ n " ;
204 1111. rész· Memóri8kezelés
lIImenet Main. Before swap . x: 5 y : 10 Swap. Before swap. ·px : 5 * py : 10 Swap . After swap . *px : 10 *py: 5 Main. After swap . x : 10 y : 5
Sikerült! 1\ 3. sorba n a swap () deklarációját úgy alakitouuk, hogy kél egész szám helyett kf:l egészre mutató par.lméten várjon. A swap () függvény hívásakor (a 11. sorban) az x és y változ6k címeit adjuk át paraméterként. 19. sorba n egy helyi változót deklarálunk a swap ( ) -en belül (temp) . Ennek nem kell mutat6nak lennie, hiszen csak a *px értékét fogja hordozni (;lZ:U~ a hív6 függvénybeli x ~rtékét) a függvény élettartam,l során. A függv ény visszatérése után már nem lesz szükségünk a tempore. fl
A 24. sorban a temp-hez hozzá rendeljük a px címen t:.tlá lh,lt6 értéket. A 25. sorban a px címen áll6 értéket felülírjuk a py címen wlálható értékkel. A 26. sorban a tempben ideiglenesen tárolt értéket (azaz a korábbi px értékét) beírjuk a py mem6riacímre. Mindezek eredményeként a hívó függvény kél változ6jámtk értéke, melyekkel meghívta a swap () függvé nyt, ténylegesen fe1cserél6dik.
A swap() újabb megvalósítása hivatkozásokkal Az cl6z6 program mG'ködik, de a swap () szintaxisa két okból is nyögvenyel6s. Egyrészt a swap () meghívása kor kétszer is ismételt címfeloldás hibalehet6ségekel hordoz, és nehéz is olvasni. Másrészt az a kényszer, hogya szóban forg6 változ6k címeit kell megadni a hív6 fOggvényben, felfedi a swap () megva!6sításának technikai részleteit a felhasználók szá mára. A C++ egyik célja, hogy a függvények használ6it megkímélje a konkrét mGködés megi.~m e résé n e k fáradságá tól. Mutatók átadásának a megkövctdésc a hívó függvény fel használ6jál1l tesz többlet terhet, amit pedig nem az 6 dolga viselni; így a hívó félnek kell fejben tartania , hogy a swap () számára a felcserélend6 változók címeit kell átadni. A címzés-szemantika megértésének terhét a swap () megalkot6jának kell hordoznia. Ezt hivatkozások használatávallehet megoldani. A 11.6 ü sta ennek megfelel6en fogalmazza át a swap () függvényt. A hívó fél most csak az objektumokat adja ál, de - minlhogy az elvárt paraméterek hivatkozásként vannak deklarálva - ezek hivatkozásként értelmez6dnck. A hívó függvénynek semmi különösel nem kell tennie.
11.6", • Hivatkozások 1205
11.6 Lista - Az újra/rt sw,pO immé, hivadc02ésokkal (passbyraf.cpp)
o: /1 11.6 Lis ta 1: / / eim szer.i.nti szép paraméterátadás - hivatkozásokkal l 2 : *include cios t ream> 3. 4 : void s wup (i nt &x,
int &y);
5 : /1 az ' &' jel mutatja, hogy a függvény hivatkozásokat vár 6 : int main () 7: 8:
,,
( int x =5,y=10;
10: 11:
std : : cout «
12 ,
6wl.lp{X , y) ;
13 :
std : : cout «
14 :
15 : 16 : 17 : 18 , 19 :
20 :
'Main. Before swap, x: •
'\n';
"\n';
return O; void swap (int &rx, int "ry) ( int temp;
21 :
22 : 23:
std : :cout «
·Swap. Before swap, rx : • « rx « • ry : • « ry «
'\n' ;
24 :
25 : 26: 27 : 28 : 29: 30 : 31:
Main . swap . Swap . Main .
tcmp " rx ; rx ry ; ry = temp ;
std : : cout «
· Swa p . After swap, rx : • « rx « • ry : • « ey «
'\n';
Before swap , x: 5 y : 10 Betore s wap, rx : 5 ry : 10 After s wap, r x : 10 ry: 5 After swap , x : 10 y : 5
Ahogya mutatókkal megval6síton példában, in is deklarálunk két változ6t a 8. sorban, melyeket ki is írarunk a 10-11. sorban. A 12. sorban hívjuk meg a swap () függvényt, melyben maga az x és y kerül átadásF.l, nem a címeil.... A hívó fél egyszeruen magukat a válLOzókat adja át.
206 1111. rész· Memóriakezelés Amikor a swap () meghívódik, a vezérlés a 18. sorra kerül, ahol a változók hivatkozásként értelmez6dnek. Kezdeti értéküket kiírjuk a 22-23. sorban. Fontos, hogy semmilyen operátort nem használunk az elérésükhöz, hbze n ezek az eredeti változók szi nű nimái, így ö nmllgllkban is használhatóak. A 25-27. sorblln fclcscrél6dnek az é rtékek, melyeket a 29-30. sorban ki is nyomtatunk. A. vezérlés visszakerül a f6programba, és a 13-14. sorban a main () is kiírja :l változók énékeit. A swap () pamméterei hivatko7..áskéntlette k deklarálva, így a main () -heli értékt:!k átadáSa is így történt; emilltl értékváltozásuk a main () -ben is megm;wddt. A. hiv:ltkozások kényelmet és könnyű használhatóságol kölcsönöznek a normál változ6knak, II mutatók cím szerinti paraméterátadási ké pességének le he t6ségével és e rejével felvértezve.
A függvények fejlécei és prototfpusai Hivatkozásokat is elfogadó függvényekkel könnyebb az élet, és a kód is olvashatóbb, de honnan tudja a függvény meghívója, hogy érték szerinti vagy cím szerinti paraméterátadás történik? A swap () -et használó programozónak tudnia kell , hogy ez a függvény ténylegesen hozzá fog-e nyúlni a p;munéterekhez. Ez a függvény deklarációjából (prototípusából) derül ki , amely többnyire a fejlécállományban található a többi deklarációval egyOtt. Az ou szerep l ő paramétereket megtekintve egyb61 látszik , hogy az adott függvény cím szeri nti par.Jméterátadással dolgozik; így meggy6z6dhel arról a programozó, hogy tényleges cserét várhat II swap ( ) -t61. Ha a swap () egy osztály tagfüggvénye [enne, akkor az osztály-deklarációból (amely szil1tén a fejlécállomá nyban található) lenne kinyerhető ez az információ. C++-ban a z osztá lyok ügyfelei (azaz valamely más oszt::ily tagfüggvényei, melyek az adott osztá lyt használni sze retnék) bízhatnak abban, hogy a fejlécál1ományokban mindent megtalálnak , amire szükségük van . Ez jelenti az osztály vagy a függvé ny keze16felületé1. A tényleges megva lósítás el van rejtve az ügyfélprogramok elől. Ez segíti a programozót abban, hogya lényegre koncentrálhasson, és anélkül haszná lhassa az adott osztályt vagy függvényt, hogy ismerné II pontos megvalósítását.
Több érték visszaadása Ahogy korábban emIíteuük, a függvények csak egyetlen visszatérési értékkel rendelkezhetnek. Mit tehetünk akkor, ha két értéket szeretnénk visszakapni egy függvényt61? A probléma úgy orvosolható, hogy két paramétert adunk át neki cím szerint. Ezeket a fü ggvény [eltöltheti a megfelelő értékekkel. A cím szerÍllt paraméterátadás felruházza
11. 6ra • HivatXozás<>k I W7 a függvényt azzal a képess6ggel, hogy ténylegesen több értéket adjon vissza. Ebben a megközelítésben a függvény eredeti vissz,n6r6si értéke más szerepbe kerül; használható pt:!ldául hibajelzés küldésére. A dm szerinti paraméterátadás tehát mUlató vagy hivatkozás segítségével történhet. A 11.7 Lista eb')' olyan függvényt mutat be, amelynek három visszatérési értéke van; két mlLral6 paraméter plusz II Chibajelzésre használt) klasszikus visszatérési érték.
11.7 Usta - VisszatérNi értékek többsz6r6zltse mutatókkal (returnwithptr.cpp) 0, 1111.7 Lista 1: II FOggvények tObbszörös visszatérési értéke mutatókkal 2: linclude
3,
4,
short Factor (int, int", i nt*) ;
5,
6,
int main!)
7,
8,
9,
int number, squared, cubed; short error;
10: ll:
12 : 13 : 14 :
std : : cout « 'Enter a numbar std : : cin » number;
(o
-
20) :
.,
error = F'actor(number, &squered . &cubcd);
15 : 16 :
i f (!errorl
17 :
{
std: : cout « std: : cout « std: : cou t «
18:
19: '"O :
"number: "square: ' cubcd :
...
« « «
number « • \n" ; squared « "\n' ; « • \0' ; cubed
21: 22 : '-3 , 2 4: 7.5 :
e l se std::cout« return O;
"Error encountered !! \n";
26 : 27 : 2S : 29: 30 : 31: 32: 33 : 34 :
short Factor(int n, int *pSquared , int *pCubed) short Value = O; if
(n >
20)
value = 1 ; else
35 :
·pSquarcd = n*n: *pCubed n*n*n:
36:
Valuc '" O;
37 : 38 :
39 :
re t urn Value;
1
20e III. rész • Mem6riakezelés
Enter a number (O-20) : 3 number: 3 square : 9
cubed: 27
A 8. sorban lélrehozzuk a number, squared és a cubed (szá m, négyzete, k6be) egész változókat. A number--nek a felha sználó bemenő adata ad értéket. Ezt a számot, valamint a squared és a cubed címét átadjuk a Factor () függvénynek. A Factor () megvizsgálja az érték szerint áladolt els6 p;mun éterl. Ha ez nagyobb húsznál (v:lgyis a maximumnál, amit ez a függvény még kezeln i ~ llld "), akkor II visszatérési vá ltoz6t (va.lue) egy egyszelű hibajelz6 értékre állítja. rigycljük meg, hogy a Factor () visszatérési értéke vagy ez a hibajelz6 érték, vagy nulla (ami azt jelzi, hogy minden rt..:ndben zajlott). A 38. sorban kerill visszakoldésre a Value ért6kc. A két ténylegesen várt ~v isszatérési érték~, a szá m négyzete és köbe nem a return révén jut vissza a fl5program ba, hanem a fuggvénynek átadott mut..tók révén lehet6vé vált közvetlen C:n6kvá ltoztatással. A 34. és 35. sorban bekerülnek a kiszámított értékek a mutatók álUl! hivatkozott mcm6riacímekre. A 36. sorban a visszatérési énéket (value) sikeres állapotra állíljuk, és a 38. sorban vissz..küldjük. A progmmot továbbfejleszlhetjOk azzal, hogy felvesszük az al ábbi deklar.'lci6t: enum ERROR-VALUE ( SUCCESS, FAILURE): Ezek után már nem O vagy l értéket kellene visszaadnia a t"üggvénynek, hanem SUCCESS-t (sike,t) vagy FAILURE-t (híbtit). Ahogy korábban láthattuk, II fcl sorolásos típus első eleme (SUCCESS) O, második eleme (FAILURE) l értéket vesz föl.
Több visszatérési érték használata hivatkozásokkal Bár a 11 .7 Lista programja működik, könnyebb olvasni és karbantartani, ha mutatók helyett hivatkozásokkal dolgozunk. A 11.8 Lista mutatja az átín programot, amely már hivatkozásokat használ, valamint szebb hibakezelést valÓSít meg az ERR-CODE révén.
". ó.. • Hivatkozások 1209
11.81.is1a - A 11.71.is1a úlnlrúa: _ _ ""'"'" _
... h i t _ a l
(-..withnof.cppl o : /I 11. 8 Lista l ; 1/ FOggvények felruháloása 2 : II tObbszOrös visszatérési értékkel hivatko zások révén
3:
•• 5:
tincludc enum ERR-CODE ( SUCCESS, ERROR ) 1
6.
7: ,.
ERR-CODE Factor(int , int&, int&) ;
9:
int m",in() {
10 :
int number , squared , cubed : ERR-COOE res ult :
ll : 12 : 13 ; 14 :
std : : cout «
15 : 16 :
std : : cin »
17 : 18 ,
re!lult • Factor(number , squared , cubed) ;
19 : 20 : 21 : 22 : 23 : 24 :
i f (reGult
25 :
else
26 : 27 :
return O,
,
"Enter a number (O - 20) :
number ;
..
SUCCE$S)
std : : cout «
"number :
std : : cout «
·square : ·cubed:
Btd : : cout
«
std :: cout«
.
« «
number « • \n "; squared « • \n" ;
«
cubed
«
• \n';
"Error encountered!!\n" ;
28 :
29 : 30 :
3L
,
ERR.....CODE Factor (int n, int &rSquared, int &rCubed)
32 : 33 : 34 : 35 :
36 : 37 : 38 :
i f (n > 20) re t ur n ERROR; else
,
r Squared :: n *n ; r Cubed '" n"n*n ; return SUCCESS ;
39 : 40 :
Enter a number (O-20) : ] nwnbe r : ]
square : 9 cubed : 21
II simple error code
210 1111. rész • Mem6riakeze~s
A 11.8 ü sta megegyezik a 11.7 Listával , kél kivételle1. Az ERR-CODE felsorolásos típus átlálhat6bbá teszi a hiba jele ntéseke t (33. és 38. sor) , valamint a 19. sor hibakezelésél.
A jelentősebb vá ltozás azonban abban áll, hogya Factor () függvényt most úgy deklaráltuk, hogy mutatók helyett hivatkozásokal vár a squared és a cubed helyére. A pamméterek kezelése íh'Y sokkal egyszerűbb és érthetőbb.
Kérdések és válaszok Kérdés: Miért IJtlsználjl/llk hivatkozásoka" Ita CI
cl
mutat6k mindarnl képesek, amire
hfvatl.lozások?
Válasz: A hivatkozásokat könnyebb használni és megérteni. A címfeloldás rejtenen tör-
ténik, nincs szükség isméte lt címfeloldó operáto rokra . Kérdés: Aná,., hasz/lálj/.m k akkor /IIuUllókat, ha egyszerl1bbek CI lIiv(l lkoz!üok?
Válasz: A hivatkoz{!sokat nem lehet nu llázni, sem pedig újra felhasználni. A mutatók rugalmasabbak, de valamivel nehezebb a használatuk.
Gyakorlatok Most, hogy megismerkedfÜnk a hivatkozások használatával , válaszoljunk meg néhány kérdést és végezzünk el néhány feladatO[ Uldásunk e l lenőrzésére!
Kvfz 1. Mi az a hivatkozás? 2. Milyen operátorral hozunk létre egy hivatkozást? 3. Mi egy hiv"koz's mem6"ocime' 4. Mi a függvények alapértelmezett paraméterátadási módszere a C++-ban? Milyen módon lehet ennek korlátait átlépni?
Feladatok 1. Egyesítse a passbyptr. cpp és a passbyref. cpp (11.5 és 11 .6 Lista) programjaiban használt módszereket úgy, hogy az egyik é rtékel mutató, a másikat hivatkozás segítségével adja át a swap () függvénynek! Ez jól mutatja, hogy e kél módszer teljesen átjá rható .
l1.6ra • HivatkOLls.kl 211 2. Módosírsa úgy a returnwithptr. cpp programot (11.7 Lista), hogy mutatók helyeu hivatkozásokat használjon! 3. Bontsa három részre a returnwithptr. cpp programot Cl 1.7 Lista) oly módon, hogy HZ eredeti néven maradjo n meg a főprogram (returnwithptr . cpp), a Factor () függvény megv.d6sítása kerüljön át egy külön fájlba (factor. cpp), és legyen egy fejlécállomány is a Factor {} függvény proTolípusával (fa ctor . hpp). Vegye fel a factor. cpp-t a projektállomá nyok közé, (:$ fordítsa le a progmmot! Ez azt szemlélteti, hogy hogyan lehet megoszta ni a fü ggvény- és osztálykönyvtárakat. A függvény lefordítható és a gépi nyelv{[ progmmkód a fejlécállománnyal (bclUle a ruggvényprototípussaD együtt közzélehe16 a termelékenység javítására.
Válaszok a kvfzkérdésekre 1. A hivatkozás valamely váltOl6 vagy obje ktum szinonimája, névvá ltolat.1, 2. Al ~és" jel (&) használatávallehec hivatkozást deklarálni. A hiv:ltkozásokat inidalizálni kell a deklaráláskor. Nem lehet nu ll hivatkozásu l,k, csak le nulIázott mutat6nk. 3. A hivatkozás címe annak a változónak vagy objektumnak a címe, amelyre utal. Ha :1 . cím(/' operátorl alkalmazzuk egy hivatkozásra, akkor (a mutat6kkal ellentétben) úgy tűnik , hogy semmi helyet sem foglal el a memóriában. 4, Az é rté k szerinti pamméter:'uadás. Ilyenko r az átadott vállOZó másolatával dolgozik a függvény, így nem lehet felülírni az átadott változ61. Ezen kétféle m6don le het felülemelkedni: egyrészt a mutatók használatával, ugyanis ilyenkor az obje ktum címe kerül átadásra. Másrészt hivatkozások átadásával, hiszen ezzel az eredeti változó névvá llozatál kapja meg a filggvél1y.
12.
ÓRA
A hivatkozásokról és mutatókról - mélyebben Ebben az 6rában a következőkrőllesz sz6: • • • •
llogyan tehetjük hatékonyabbá programjainkat cím szerinti paramétecitadással Mikor érdemes hivatkozásokat használni, és mikor mutatókat Hogyan Icgyiln k úrrá a mcmóriagondokon a mutatók ha.~ználata közben Hogyan kerüljük el a hivatkozásokka l kapcsolatos csapdákat
Hatékonyabb a cfm szerinti paraméterátadás Az érték szerinti paramétcrát:tdáskor másolat készül az objeklumr61. Ha a függvény visszatérési értéke egy objektum , akkor megint egy újabb másolat keletkezik. Nagyobb mércrú, felhasználó általlétrehozou objekrumok esetén ezeknek a másolások a száITÚtásigényc már igen számottevő is lehel. A szükségesnél több memóriát használ a program, és végs6 soron sokkal !assabban fut.
214 1111. rész • Memóriak.,.lé. Egy, a felhasználó által definiált objektum mérete a veremben az egyes tagváltozók méretének összege. Ezen tagvállozók akár nagy objektumok is lehetnek. Egy efféle gigantikus struktúra átadása (azaz a verembe másolása) igen sokba kerülhet memória és futási idő vonatkozásában. Másféle költségek is fellépnek ilyenkor. Az oszt{llyok pélclányosításakor készül róluk t'b'Y ideiglenes másolat; ilyenkor a fordít6progmm meghív egy speciális konSlnlktOI1: a másoló konstruktort. A 13. órán, a Ixmyolulwbb függvényeknél megtanul juk, hogy hO!''Yan működnek ezek a másoló konstnlktorok, és hogy hogyan készíthetünk magunknak ilyeneket; most azonban elég annyit tudnunk, hogy egy objektum ideiglenes másolatának a verembe másolásakor meghív6<.lik a másoló konstruktor. Amikor :1 függvény kilépésekor véget ér ennek az ideiglenes másolatnak az éleuartama , az objektum destll.lktora hívódik meg. Ha egy objektumot érté~ szerint adunk vissza, akkor ennek az objektumnak II m{lsobtát létre is keH hozni, majd törölni is kell. A konstruktorok és dCSlruktorok meghívása nagy objektull1okkal költséges lehet a sebesség és a memóriah
o:
II 12 .1 Lista l: 1/ Obj ektum mutat6j ána k az átadása 2 , winclude
3, 4, 5: 6: 7: 8: 9:
class sjmpl eCat { public : SimpleCat () ; II constructor SimplcCat (Simp l€Cat&) ; II máso l 6 ko n strukt o r -Simp l eCat() ; II des truktor 10 : J ; ll : 12: SimpleCat : : SimpleCa t () 13 : { 14 : std:: cout « "Si mpl e Cat Constructor .. . \n"; 15 : 16 :
12. 6ra • A hivatk.omsokfól és mutat6kr6117 , 18 , 19 : 20 , 21 , 22 , 23 , 24, 25 , 26, 27 : 28 , 29 : 30, 31 , 32 : 33 : 34: 35 , 36 : 37 : 38 : 39 :
SimpleCat , , SimpleCat(Simp1eCa t &) ( std , , cout « "Simple Cat Copy Constructo r ... \n ";
SimpleCat , , -Simplecat () { std , , cout « "Simple Cat Destructor. .. \n" ;
Simp!eCat Punctionane (SimpleCat theCat) ; simplcC~t· FunctionTwo (SimpleCat *theCat) ; int main() std , : couL « "Making a cat. .. \n" ; SimpleCu t Frisky; atd : : cou t « "Calling FunctionOne ... \n "; FunctionOnc(frisky); std , : couL « "Calling Function'l'wo ... \n" ; FunctionTwo(&Frisky); return O;
40, 41 : II FunctionOne. érték szerinti paraméterátadással 42 : SimpleCat ~'unctionOne(Simp1eCat theCat) 4] :
(
std: : couL « 'Function One. Returning ... \n"; return theCat;
44 : 45: 46: 47 : 48 : 49:
II functionTwo, passes by re ference Simplecat* FunctionTwo (Simp1cCat ·theCat)
50 :
{
51 : 52 : 53 :
1: 2: 3: 4: 5: 6: 7: 8: 9: 10 : ll :
std :: cout « 'Punction '!'wo . Returning ... \n" ; return thcCat ;
Making a cat ... Simple Cat Constructor . Cal l ing FunctionOne ... Simple Cat Copy Con s t ructor . Function On e . Returni ng ... Simple Cat Copy Constructor . Simple Cat Destructor . . Simple Cat Destructor .. . Call ing PunctionTwo .. . Function '!'wo. Rcturning . . simple Cat Destructor ...
216 1111. rész· Memóriakezelés
Apropó
Sorszámok akimenetben
Az itt látható sorszámok nem jelennek meg a képernyőn, csak az elemzés megkönnyítése végett tüntetjük fel őket.
Egy tdjesen lecsupaszított SimpleCat osztályt deklarálunk a 4-10. sorban. A konstruktor, a másoló konstmktor és a destruktor mindössze egy-egy tömör üzenetleJ jelzi a meghívását. A 32. sorban a main () értesít a macska létrehoz..'isáról , melyel a kimenet 1. sorában láthaTtlnk. A 33. sorban val6ban létrejön egy simpleCat péld{IllY, mégpedig a konstmktor révén, melynek üzenete a kimenet 2. sorában olvashat6. A .,4. sorban a main () jelzi, hogy kthizül a FunctionOne () meghívásÍIra (ez a kimenet 3. sora). Mivel itt érték szerinti pamméterátadás rörtónik, a simpleCat mÍlsolatát keH létrehoznia a programnak a veremben, hogy legyen a fi.lggvt!l1ynek egy saját, helyi objektmna. Emiatt ismét meg1Lívódik a másoló konstruktor (ez a kimenet 4. sor:a). A vezérlés a 44. sorra keri.il, :1 hívott függvény tönsébe, ahol egy nyomjelz6 üzenet író· dik ki (a kimenet 5. sorában). A függvény kilép, és érték szerint ad vissza egy SimpleCat objektumot. Emi:llt ismét meghív6dik a másoló konstruktor (:1 kimenet 6. sorában). A FunctionOne () visszatérési értéke nem rendel6dik hozzá semmilyen változóhoz, így a (vissz.1térési értékként) ideiglenesen létrejön objekrumot el kell dobnunk , :unit a destmktor végez el (ez a kimenet 7. sora). Ekkor a Funct ionOne () egyúttal ki is lép, így a számáro. készült helyi objektummásolat érvényét veszti, és ismét munkába lép a destruktor (a mit a kimenet 8. sara mutat). A vezérlés visszakerül a fóprogmmba, és (a kimenet 9. sora szerint) meghív6dik a FunctlonTlo.>o (), melynek paramétere már cím szerint adódik át. Nem készül másolat, így nincs is err6l üzenet a kimenetben. A FunctionTlo.>o{ ) hírt aci saját rutásár61 a kimenet 10. sorában, majd visszaad egy SimpleCat objektumot cím szerint (így most sincs nyoma konstnlktof vab'Y destruktor hívásának). Végül II program kilép, Frisky érvényét veszti és meghívja a sorsát destmktort, amit a kimenet utolsó sora nyugtáz.
megpecsételő
Összességében elmondható, hogy a FunctionOne () hívása az érték szerinti paraméterátadás és visszaadás miatt két konstruktor- és két destruktorhívást igényelt, ezzel szemben a FunctionTwo () hívása egyet sem.
12. 6ra • A hivatkozásokr61és mutat6kr61- mélyebben 1217
Mutassunk konstansra Bár kétségkívül hatékonyabb dolog mutal6t átadni FunctionTwo () *nak, ez nem teljc* st:n veszélytelen . Ettől a függvényt61 ugyanis nem várjuk az átac10tt SimpleCat () ob* jektum megv{lltoztatását, mégis át.adjuk neki a dmét. Ez Ichetőv6 tesz akár tiltott vá[* toztatás! is, és meghiúsítja az érték szerinti param~leráladás kínálta védelmet. Az érték szerinti pamméterátadás hasonlít ahhoz, mint amikor valaki réltve őrzöu múkincse helyett annak fényképét adja a múzeumnak. I-Ia a vandálo k meg is rongálják, az eredetit semmi károsodás nem éri. A dm szerinti paraméterátadás ezzel szemben olya n, minth:1 :1 l:lkcimünket küldené nk el a mÍlzeumnak azza l a biztatással, hOb'Y irányítsák csak hozzánk nyugodtan a látogatókat, hogy mcgnézhessék az eredeti kincset. J-Ja egyszerre szeretnénk élvezni az é rték szerinti paraméterátadás biztonságát és a cím
szerint.i paraméterátadás haté konyságá t, konstans mutat6val kelJ átadnunk a Sil1\ple Cat objektumot. Ez meggátol nlinden nemkonstans metódushívásI, így ke1!6 védelemben részesül SimpleCat objekrunmnk. A 12.2 Lista ezt mutatja be.
o:
II 12 . 2 Lista l : II Objektum konstans mutAtójának az átadása 2 : .include
3,
4:
class Simpl eCat
5:
(
6: 7: 8: 9:
public : SimpleCat () ; simpleCat(SimpleCat&) ; -S impleCat();
10 : ll : 12 , 13 : 14 :
int GetAge () const return itsAge ; } void SetAge(!nt Age) ( itsAge = age; )
15 ,
private : int itsAge;
16:
};
17 : 18 :
SimpleCat: : SimpleCat ()
t9 :
{
20 :
std : :cout« itsAge = l;
21 :
·Simple Cat Constructor ... \n" ;
22 : 23 :
24:
SimpleCat : : SimpleCat(Simplecat&)
25 :
{
26 : 27 :
28 :
std : :cout«
"Simple Cst Copy Cons tructor ... \n ";
218 1111. réSZ· M,m6ri.k",lés 29 : 30 : 31 : 32 : 33 : 34 : 35: 36: 37 :
SimplcCat : : -Simpl eCat () ( std :: cout« 'Simple Cat Destcuctor ... \n' l
const Simplecat * const F'unctionTwo (cons t SirnpleCa t * const theCat); int main()
38 :
39 , 40 : 41 : 42 : 43 : d4 : 45 : 46 : 47 : 48 : 49 : 50 : 51 : 52 : 53 :
54 : 55 : 56 : 57 : 58 : 59: 60: 61: 62: 63:
std : : cout « 'Making a cat .. . \n" I Sirnpl eCat Fr isky l std : : cout « 'Frisky is "I std :: cout« Frisky .GetAge() « "ycars old\ n"1 int age" 5 ; r·cisky . SatAge(age) ; std ,: cou t « 'Frisky i s 'I !:Itd :: cou t « Fri s ky . GetAgc( ) « • yca r s o l d\ n"1 otd : : cout « "Call i ng Fun ctlon'T'wo ... \n " ; FunctlonTwo(&Frisky) ; std :: cout « " Frisky is "; std : :cout « Fris ky . GetAge() « ' years old\n" ; return OI
II functionTwo, konstans mutatót kap és ad vissza const SimpleCat .. const FunctionTwo (const Simplecat * const theCat) { std : :cout« "Functlon Two . Returning ... \n" ; std: : cout« "Frisky is now " « theCat->GetAge()I std : :cout « ' years old \n' ; II theCat->SetAge(8); const! return thecat;
Making a cat ... Simple Cat Constructor ... Fris ky i s 1 years o l d Fris ky i s 5 years o l d Calling Fun ct i onTwo ... Function Two . Returni n g . Fris ky i s now 5 years old Frisky is 5 years old Simple Cat Destructor . .
12. 6ra' A hivatkozásokr61 és mutatókr61 - mély.bb.n 1219
A SirnpleCat számára két hozzáfér6 függvényt deklaráltunk: a GetAge () -el a 11. sorban, amely konstans függvény , és a SetAge () -et a 12. sorban, amely nem az. Van egy saját tagváltozója is, a 15. sorban deklarált egész itsAge. A konstruktor, a másoló konstn Jktor és a destrukwf úgy lett megírva, hogy kiírja a saját nyomje\z6 üzenetél. A másoló konstruktor azonban sohasem kerül meghívásn:I; prog-
ramunkban ugyanis csak dm szerinti paraméterátadás tönlmik, és ehhez nincs szükség másolat készítésére. A 4Q. sorban létrejön egy macska; az alapértelmezeu életkorát ki is nyomtat ja a 41-42. sor. A 44 . sorban a SetAge () segítségével átá!1íljuk a korát (it~Age) 5-re, és az en..>(lmf:nyt megjelenítjük a 45-46. sorban. A FunctionOne ( ) -l nem haszn:íljuk ebben a programban, csak a FunctionTwo () -t, amelyet egy kicsit átírtunkj a 34. és 35. sorban láthatjuk visszatérési értékét és átadand6 paraméter6t: mindkett6 egy konstans objektumra utal6 konstans mutató.
Minthogy az átadandó pamméter és a visszatérési érték is cím szerint kerUl ,ítad:'isl"""d , nem készülnek m:'isolatok, és nem hív6tlik meg a másoló konstmktor. A FunctionTwO () -beli mutató azonban konstans, így nC:!m lehet bel6le meghívni a nemkonsta ns SetAge () -et. Ha az ezt megkísérl6 61. sort kivesszük a megjegyzésb61, nem fordul Ic a program. Érdemes megIIgyeini, hogy a main () f6programban létrehozott Frisky objektum nem konst:ms, így például átáJlítható a kora, meghívhat6 rá a Setll.ge () . Ennek a nemkonstans Frisky objektumnak a eimét kapja meg a FunctionTwo (), és mivel ez úgy lett dC:!klarálva, hogy konstansra hivatkozó ll1utat6ra számít, az objektumot konstansként fogja kezelni!
Hivatkozások, mint a mutatók kényelmes altematfvái A 12.2 Lista megoldja a felesleges másolás (és ezzel a másoló konstmktor és destruktor indokolatlan meghívásá nak) problémáját. Konstans objektumra mutató konstans mutat6t használ, így nem kell tartanunk att61, hogya függvény jogosulatlanul megválloztatja a paraméterként átadott objektumo{ka)t. A módszer azonban még mindig nem tú l elegá ns, mivel mégiscsak mutatókat kell átadnunk a függvénynek. Mivel azt is tudjuk, hogy az átadandó paraméterek nem lesznek null értékűek , könnyebb lenne az élet, ha mutatók helyett hivatkozásoka t adhatnánk át. A 12.3 Lista ennek szellemében korrigálja a 12.2 Listát.
220 1111. rész· Mem6riakezelés
o:
II 12.3 Lista 1: II Objektum hivat ko zásának az átadása 2: ftinclude 3, 4: class SimpleCat 5: { 6 : public : 7: SjmpleCat() ; 8: Simp leCa t (SimpleCat&) ; 9: -simp1eCat() ;
10 : int GetAge () co n st void SetAgc( int age)
ll: 12 :
retur n itsAge : } { i tsAge • age; )
13 : 14 :
15 : 16 :
private : int itsAge:
);
17 : 18 :
simpleCat : :Simp1eCat()
19 :
(
7.0 : 21 : 22:
std : : cout « itsAge = 1 ;
'Simple Cat Constructor ... \1'1 ';
23: 24 :
SimpleCat: :Simplecat(Simp1eCat&)
25 : 26 : 27 : 28 : 29 : 30 :
(
31 :
std: : cout«
"Simple Cat Copy Constructor ... \n";
Simp1eCat : : -Simplecat () { std : : cout « 'Simple Cat Destructoc .. . \n' ;
32 : 33 : 34 : 35 : 36 :
37 : 38 : 39 : 40: 41 : 42 : 43 : 44: 45: 46 :
const SimpleCat & Func tion'l'wo (const SimpleCat & theCat) ; int main()
( std :: cout« 'Making a cat ... \ n '; Simpl eCa t Fr i s ky; std : : cout « "Frisky is ' « Frisky . CetAge() « ' years 01d\n' ; int age", 5; Frjsky.SetAgc{age) ; std : : cout « 'Frisky is ' « Fri sky . GetAge() « ' years 01d\n' ;
47 :
48: 49 : 50 :
std : : cout « 'Ca11ing FunctionTwo ... \n" ; FunctionTwo(Frisky) ; std : : cout « "Frisky is " « Frisky.Geti\ge()
12. 6ra • A hi_omokról és mutat6król- mé~ebb,n 1221 • years old\n" ;
«
51: 52 :
return O;
53 : 54 : 55 : 56 : 57 : 5B : 59 : 60 : 61 : 62 :
1/ functio nTwo.
const SimpleCilt
konstansra utal6 hivatkozást kap és ad vissza FunctionTwo (const SimpleCat (. theCat l
&
(
std :: cout«
' Punction Two. Returning ... \n' ;
std : : cou t «
'Frisky is now ' « « • years old \n "; /1 thecat . SetAge(B); const! return theCat;
theCa t . GetAgc()
63 :
Making él ca t . .. Simple eat constructor ...
Frisky lS 1 yoars o ld Frisky is 5 years old Calling FunctionTwo FunctionTwo . Returning ... Frisk.y is now 5 years old
frisky is
~
years old
simpl e eat Destructor ...
A kimenet megegyezik a 12.2 Listáéval. Az egyetlen emJílt!sre mélt6 külónbség az,
hogy most a FunctionTwo () függvény konsta ns objektumra utaló hiv,ltkoz{lsl vár és ad vissza. Ezen II példán is láthatj\lk, hogy hivlltkoz.'isokkal könnyebb dolgozni, mint mutat6kkal ; a ko nstansok által elérhct6 nyereség, hatékonyság és hizlonság azonban itt is megval6su1.
Mikor használjunk hivatkozásokat, és mikor mutatókat? A C++ programozók általában sokkal szívesebben használnak hivatkozásokat, mint mutat6kat, mivel ezek átláthat6bbak és hasznáJhat6bbak. A hivatkozásokhoz azonban nem lehet új objektumot hozzárcndeJni. Ha a program futása közben változtatni kell II célobjektumon, kénytelenek vagyunk mutatót használni. A hivatkozás nem lehet null értékű; így, ha van esély arra , hogy acélobjektum nul1áz6dik, akkor sem tudunk hivatkozást használni, csak mutatót. Ha a dinamikus memóriából szeretnénk területet foglalni, akkor is mUlatókra van szükség, ahogy arról az előző órákon már szó volt.
222 1111. rész' Memóriekezelés
Ne adjunk vissza hivatkozást!
megszűnt
objektumra mutató
Ahogya C++ programozók megtanulják a eim szerimi par.lméterátadásl, vérszemet kapnak, és id6nként hajlamosak túlkompenzálni ifjúkori lévedéseikel. Ne lévesszük szem eid!, hogya hivatkozás csupán egy névvá llOZ
létező
objektum hivatkozá-
12.4 Lista - Nemlátezli objektum hivatkozáiának vi81zaadás. (returnref.cpp) 0,
II 12. 4 Lista l : II Már nem létezo objekLwnra mutat6 2: 1/ hivatkozás visszaadása
,3:,
'include
5:
class SimpleCat
6: 7, 8: 9: 10: ll: 12 : 13 : 14 : 15: 16 : 17: 18 : 19: 20 :
{ public: SirrrpleCat (int age, int weight); -SimpleCat () (l int GetAqe() ( return itsAge; ) int GetWeight() ( return itsWci9ht; private : int itsi\ge ; int itsWeight ; ): SimpleCat :: SimpleCat(int age, int weight) : itsMe (age), itsWeight (weightl {) SimpleCat &TheFunct ion() ;
21 : 22:
23 : 24: 25 : 26 : 27 :
int main() SimpleCat &rCat = TheFunction() ; int age = rCat . GetAge() ; std :: cout« "rCat is • «age « rcturn O;
28 : 29 : 30 : 31 :
32 : 33 :
34 :
SimpleCat &ThcFunction () (
SimpleCat Pr isky (5 ,9 ) ; rcturn Frisky;
• years old!\n" ;
I és mutatókról
A 12A Lista rordításakor az alábbi hibaüzenenel szembesülünk "returnref.cpp· , E2363 Attempting to return a reference to local variable 'Frisky' in function TheFunction() at line 3]
(Azaz; " returnref.cpp~: E2363 A 'Frisky' helyi változóra utaló hivatkozás visszaadási kísérlete a 111cFunctionO füru,'v6nybcn, a :$3. sorban) Figyelem!
Intelligens fordft6programok
Vannak intelligens fordft6programok, melyek észreveszik a nemlétező (null ) objek-
tumra utaló hivatkozást, és hibát jeleznek, mint ahogy ez itt is látszik. Más fordít6k azonban ilyenkor is lefordftják a programot, és elöállítják a futtatható kódot. Fontos tisztázni, hogy itt az alkalmazott programozási gyakorlat a rossz, vagyis nem érdemes olyan fordítót keresni, amelyikkellefordul az ilyen program. A Borland és a g+ + például okos fordft6programok. Hibát jeleznek, ahogy az fent is látszik.
Az 5-IS. sorban deklaráljuk a SimpleCat osztályt. A 24. sorm.k az lenne a feh.data , hogy létrehozzo n egy o lyan hivatkoz;'ist, amelyet a TheFunction () visszatérési érték€:"el inici
A dinamikus memóriaterületen létrehozott objektumra mutató hivatkozás visszaadása Szeretnénk kijavítani a működésképtele n returnref . epp programunkat (12.4 LiSL1). Csábít6 lehetóség, hogya TheFunction () segíL'iégével a dinamikus mem6riaterületen hozzuk létre F'risky mllcskát, hiszen ily módon életben marad a függvényból val6 visszatérés után is. Ezzel a megközelítéssel élve azonban felmerül a kérdés, vajon ki gondoskodik majd
224 1111. rész • M.m6ri.k".~s a Frisky által lefoglalt mem6riaterületról, ha már nincs rá szükség. Ezt példázza a 12.5 Lista.
O, I I 12. 5 Li s t a 1:
2: 3. 4: 5: 6:
7: 8, 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 , 18 : 19 : 20 : 21 : 22 : 23 : 24 : 25 : 26 : 27 , 28 : 29 : 30 : 31 : 32 :
II
A mem6riaelszivárgás javí tá sa
II includ c clas s SimpleCat { public : SimpleCat (int age, int weight) ; -SimplcCat () () int GetAge() { rctu rn itsAge ; } i n t GetWeight() ( return itsWeight;
pr i vate :
i nt itsAge ; int itsweight ;
);
SimpleCat : , SimpleCat(int age, int weight) : itsAge(age) , itsweight(welght) fl SimpleCat" TheFunction() ;
i nt main () (
Simp lcCa t i nt age = std : : cout std : : cout I I How do
= Th e Function (); rCat . GetAge( ); « "reat i s • « age « • years old ! \n "; « ''-rCat : • « &reat « std : : endl ; you get dd of that memory? &. r eat
SimpleCat • pCat = &rCat ; delete pCat ; II Huha , ezek után mire utal az rCat?? re t urn O;
33 : 34 : 35 : 36 : 3 7: 38 : 39 : 40 :
SimplcCat &TheFun c ti on () { SimpleCat * p Fris ky " n e w S impleCat (5 , 9) ; std : : cout « "pFrisky: • « p Frisky « s td : : end !; return *pFrisky ;
pFrisky: 8861880 Az rCat 5 éve!; ! &rCat : 886 1880
12. óra • A hivatkozásokfól és Az olvasó által kapott kimenet másként fog kinézni, hiszen minden számítógép eltérő címen tárol ja a változókat, attól függ6en, hogy mi van a memóriában és hogy mennyi az elérhető szabad hely. Ehhez hasonlót is kaphatunk tehát:
pFrisky : Ox00431CAO Az rCat 5 éves!
&rCat: Ox00431CAO
Figyeleml
Gondoljuk át, mit törlünk!
Afenti program szépen lefordul és látszólag jól műkö d ik . Csak éppen egy idózített bomba ketyeg benne...
A TheFunc t ion () függv~nyt rm.:gvállOZI:ltluk ; immár nem egy helyileg létrehoi'.oLt ob-
jektum hivatkozását adja vissz
Figyelami
Nemlétező
objektumok
Nem lehet eléggé hangsúlyozni, hogy a ne m létező objektumra hivatkozó program érvénytelen, és futásának kimenetele megjósolhatatlan.
226 1111. rész • Mem6riakezelés Valójában kél megoldása is van a problémának. Az egyik az, hogy a függvényt61 magát a (megfelelő mem6riacímre utaló, 37. sorban létrehozott) mutat6t kérjük el vissz.mérési énékként. Ezután a hívó program törülheti a mutatót, ha nincs rá többé szüksége. Ennek véghezviteléhez változtassuk meg a TheFunc t ion () függvé ny visszatérési énékét (hivatkozás helyett) mut;ltóra, és a feloldott mut:ltó helyett adjuk vissza magát a rnuta16t: Simp l eCat
· TheFunction()
(
Simp!eCat • pFrisky = new SimpleCat(S , 9) ; std : : cou t « 'pFrisky : • « pFrisky « std : : e nd !; re t urn pFrisky ; 1/ Magát a mutat6t adjuk vissza
A másik (elegánsabb) megoldás: a macska objektumot még a hívó függvényben hozzuk létre, és cím szerint adjuk ál a TheFunction () -nak. E megoldás szépsége, hogy a hívó függvény foglalja le a szükséges memóriaterOletet, ~s az ő fele l őssége mar-J.d en ~ nek felszabadítása is. A következő sorokb61 kiderül, miért kívánatos ez így.
Mutató, mutató, k.ié a mutató? Amikor progr-dmunk lefoglal egy szeletet a dinamikus memóriából , visszakapunk egy erre hivatkozó mutatót. Musz:'tj megtartanunk ezt a mutató!, mert ha elvész, a m e m6ria~ szelel többé nem szabadítható föl; mem6riaelszivárgás keletke zik. Amikor a függvénye k egymásnak adogatnak egy memóriaszeletet, valamelyik függ~ vény a mUlató lulajdonos;'\nak tudhatja magál. Általáb:m cím szerint keri.11 átadásm a memória tartalma, l!s annak a függvénynek a dolga lesz felszabadít:lni a mem6rb szeletet, amelyik lefoglalta. Ez azonban csak egy javasolt sZ:lb;'\ly, nem bárm i {lTon kötelező érvényű.
Meglehetósen veszf:lyes valamely függvény által lefoglalt memóriatetiilet felsz."1badítá~ sát egy másik függvényre bízni. Ha nem egyértelmű , hogy melyik függvény~ a mutató, ez két csapdát is rejthet: elfelejtjük törölni vagy többször is töröljük. Mindkettő komoly gondokat okozhat a programban. Biztosabb úgy megírni a függvényeinket, hogy :lmeIyik lefoglalja a memóriaeimct, az legyen a felszabadílója is. Ha olyan függvényt ír az olvasó, amelyben létre kell hozni valamit a dinamikus mC1l16riaterOleten, majd ennek eimét vissza kell küldenie a hívó függvénynek, fontolja meg a kezelöfelület megváltoztatását. Legyen a hívó fél fe ladata amemória lefoglalása - ez adja át a megfelelő eimpar.lmélert függvényünknek. Ezzel a memóriakczclés fcleI6S5é~ ge a hívon függvénye n kfvülre kerOl; ahhoz a függvényhez, melynek egyébkt!nl is nemes feladata a felszabadítás megszervezése.
12. 6ra • A hivatXozásokról és mutat6kr61- mélyebben 1227
Helyes
HelyteIon
Akkor használjunk érték szerinti paraméterátadást, ha musz.1j. Akkor adjunk viSS7.a értéket egy függvényMI, ha muszáj.
Ne használjunk cím szerinti átadást, ha nem nyilvánval6, hogy létezik a hivatkozou objektum. Ne használjunk nemlétező objekmmra utaló hivatkozást.
Kérdések és válaszok Kérdés: /vfit!r' használj/mk lIIutatókat, ha egyszen7bbek CI hiva/kozások?
Vá lasz: A hivmkozások nem utalhatnak nemlétező objektumra, és nem lehet6kr.:t újF.! felhaszn::j lni. A mutatók n.lg:llm:LS;Lbbak, dc valamivel nehezebb a hasznábtuk.
Kérdés: Miél1 adju nk ,lissza bá/'ll/iI ls énék szedI/! egy faggvéll)'oo/? Válasz: Ha helyi objektumot kell visszaadnunk, akkor egyszerűen kénytelenek va·
gyunk az értékét visszaadni, mert egyébként adnánk vissza.
nemlétező
objekrumra utal6 hivatk07.ást
KérdC"S: lia ilycn veszélyt"S a Ililxlfkozás visszaadása, mién nem használj/lk mindig az él1ék szerimi visszaadásI? Válasz: Sokkal hatékonyabb programokat lehet írni hivatkozás visszal.ldásával. Keve-
sebb mem6ria kelt hOZ7.á, és gyorsabban fut a program.
Gyakorlatok Most, hogy mélyebben megismerheLtük a mutatók és hivatkozások haszná latftl, vála· szol junk néhány kénlésre és o ldjunk meg néhány feladatot ludásunk mcgszitárdítás:l végeU!
Kvíz 1. Miért jobb a cím szerinti paraméterátadás na!,'Y adalmennyiség esetén? 2. Miért lehet érdemes const mutatót használni a cím szerinti paraméterátadáskor? 3. Miért lehet jó hivalkozás helyett mutatót használni a dm szerinti paraméterátadáskor? 4. Lehet-e hivatkozást készíteni eb'Y mutató típusú változ6ra?
228 1111. rész • Mem6nakezelés
Feladatok 1. Bontsuk három rés7:re :l passobjectsbyref. cpp progmmot 02. 1 Lista) úgy, hogy maradjon meg eredeti néven a főprogram és a fülli,'Vé nyek megval6sítása (Passobj ectsbyref. cpp), az osztálymet6dusok kerüljenek át egy külön fújiba (SimpleCat. cpp), és legyen egy fejlécállomány is az oSltály-deklarációkból (SimpleCat. hpp). Vegyük fel a SimpleCat. cpp-l a projektállományok közé, és fordítsuk le a programot! Ez azt szemlélteti, hogy hogyan lehet megosztani az osztálykönyvUírakat. A tagfügf,rvények leFordíthat6k és a gépi nyelv(!' programk6d a rcjlédllománnya l (benne a függvényprolotípussal) egyiin közzétehető a termelékenység javíL'isára. 2. Módosítsuk Clgy lcaky. cpp progmmOl 02.5 Lista), hOh')' az mutatókat használjon a TheFunction () hívásakor, majd törölje a mUI,Hókal a memóriaelsúvárgás kivédése é rdck6ben! 3. MódosíLo;uk úgy a pUI:lDobjectsbyref . cpp és a passre[toobj. cpp (12.1 és 12.3 Lista) programokat úgy, hogy azok nyorntassaák ki a változók memóriacimeit a függvényhívások el6tt és után! Ezzel betekintést nyerhetünk a Iamlit módszerekbe.
,t
Válaszok a kvfzkérdésekre L Az alapértelmezett al. érték szerint par.lrnéter.'itadás. Ekkor m{tso!atot kell készí-
tt::ni al. átadandó objcktumokr61. Nagy objektl.lmoknál ez számottev6 ideít és memóriát igénydllcl. 2. A const kulcsszó alTa utasítja a fordít6programot, hogy tiltsa meg a bíVOIl függvénynek a mulató által hivatkozott érték megváltozt3tását. Így a músolás költségei nélkül is érvényesül az érték szerinti paraméterátadás biztonsága. 3. Ha a hívott függvény dinamikus memóriát foglal le, ennek cime viss7.aküldhet6 a hívó félnek egy mulatÓ segítségével. Nem szabad azonban később elfelejteni a lefoglalt memória fc1sl.abadítását, hogy kiküszöböljük a memóriaelszivárgást. 4. Miért is ne lehetne? Azonban érdemes megfontoltnak lenni e téren, mert egy ilyen hivatkozás sok zavart okozhat, Gondoljl.lnk ugyanis bele, hogy már magl.lk a mutatók is elég fejtörést okozhatnak.
IV.
RÉSZ
Haladó eszközök 13. óra 14. óra
A függvények kifi nomultabb használata Operátorok túlterhelése
15. óra
Tömbök
13.
ÓRA
A függvények kifinomultabb használata Ebben az órában a következőkrőllesz szó: • • •
•
Hogyan k:hCl II tagfüggvényeket lúlterhelni Hogyan írjunk o lyan függvényeket, amelyek dinarnikusan kezelt változ6kkal rendelkező osztályokat is támogatnak Hogyan inicia lizáljunk különréle objektumokat Hogyan lehet másoló konstruktorokat írn i
Túlterhelt tagfüggvények Az S. órában láthattuk, miként lehet megval6sítani a függvények löbbalakús<Ígát (azaz a függvénytúlte rhe lést) az.1hal, hogy több fÜgb'Vényt hozunk létre azonos néven, különböző pammétcrekkcl. Az osztályok tagfügf,,,,ényeil is hasonlóképp lehet túlterhelni.
232 1 IV. rész· Haladó eszközök A 13.1 Listában szerepl6 Rectangle osztálynak két DrawShape () függvé nye is van. Az egyik nem ké r paramétert; ez az osztály pillanatnyi adatainak megfelel6en rajzol egy téglalapoc. A másik két paramétert kér: a szélességet és a hosszúságot, és - eltekintve az osztály a kmális adataitól- ezek alapján rajzo lja meg a téglalapol.
13.1 Usta - Tagfüggvánvak túharheláse (overloadfunctions.cpp)
o: II 13.1 Lista II Egy osztály tagfOggvényeinck túlterhelése 1 : tinclude 2,
II Rectan9lc osz tály-deklaráció 4 : class Rectangle
3:
5: ( 6 : public :
7 : II KonsLruktor 8 : Rectangle(int width . int height); 9 : -Rcctangl e () ( J tO : 11 : II Túlterhelt tag füg gvényOnk : a DrawShape 12 : void OrawShapc() con~t ; 13 : voId OrawShape(int aWidth , int aHeight) const ;
14 : IS : 16 : 17 : 18:
priV/lltl".! : int itsWidth; int itsHeight ; };
19 : 20 : 1/ A konstruktor mcgva16sitása 21 : Rectangle: : Rectangle(int width, int heightl 22 : (
23 : itsWidth : width; 24 : itslleight - height; 25 : J 26 :
27 :
II Az egyik túlterhelt DrawShape nem kér enni '-9 : II Az osztály pillanatnyi értékei szerint rajzol t églalapot 30 : void Rectangle: :DrawShape() const 28 :
31 : (
32 : OrawShapc( itswidth . itsHcightl ; 33 : ) 34 : 35 : 36: 1/ A másik túlterhelt DrawShape két paramétert kér 37 : II A két a daL alapján rajzolj a a Légldiapol 38 : void Rectangle: : DrawShape (int width, int heightl const
39 : 40: 41: 42 : 43 : 44 :
for (int i '" O; i
for (int
j
'" O;
j< width; j++l
{
std : : cout «
".';
13. ő... A
kifinomultabb használata
45 : std : :cout «
46 :
"\n" ;
47 : 48 :
49 : 50 : 51 : 52 : 53 :
1/ A túlterhelt függvények bemutatása a foprOgramban
int
~in()
( /1 30 ; 5 - re inicializáljuk az egyik téglalapot
54 :
R~ctanglc
55 :
std : : cout «
56 :
theRect . DrawShape () : std :: cout « "'nDrawShapc{40,2): \n" ; LheRecL.DrawShape( 40,2) ;
57 :
58 : 59 : 60 :
thcRcct(30,5); "DrawShape() : \n ";
reCurn O;
KImenet DrawShape ( ) :
............................... ••••• * ••••••••••••••••••••••••
DrawShapc(4.0, 2) :
A fenlÍ lista lt!gfonlosabb k6drészlele a 12~ 13. sorban találhat6, ahol a DrawShape () függvényt lúlwrhdtnck deklaráljuk. Túlterhelt osztályfüggvényeinket a 28-48. sorban valósítjuk meg. Figyeljük meg, hogya paraméter nélküli változat cgyszeníen meghívja a kétparaméteres váltoZ:ltOL a7. o~zLály aktuális tagváltozó-értékeivcl. Érdemes keményen taltani magunkat ahhoz, hogya két függvényben ne ismétclji.\nk k6drészJetet, mert úgy igen nehéz a későbbi váltm:LaLáliokal öSlizehangolni, és ez számtalan hiba lehct6séget tartogrtl. Az 50-GO. sorban látható főprogram létrehoz egy Rectang le tégla lapot, majd meghívja a DrawShape () függvényt, e l őször paraméter nélkül, majd két egész számmal. A fordítóprogram dönti ej a paraméterek száma és típusa alapján, hogy melyik met6dust hívja meg. Elképze l hető Icnnc ch')' olyan DrawShape () függvény is, amely szintén két paraméter kérne: egy hosszúság jellegűt és egy felsorolásos tipusüt, amely rt felhasználó kívánságának megfelelően lehetne szélesség vagy hosszúság.
234 l iV. rész • Haladó asz!<özök
Alapértelmezett értékek használata Ahogyanannál függvényeknek is lehet alapértelmezett paraméterilk, ugyanez a helyzet tagfüggvényekkel is. Megadhatunk alapértelmezett értékeket a dcklaráci6kor, amint azt a 13.2 Lista bemutatja.
13.2 UsI. - Alap6rtalmazett értékek megadása (usingdefautts.cpp) o: / I 13 . 2 Lista / I Alapértelmezett érték ek a tagfüQ'l1Vényekbcn
l : #include 2.
): II Rectangle osztály-deklaráci6 4 : class Rect.angle 5:
(
6 : public : 7 : II Konstruktor 8: 9:
Hectanglc(int width , int h eight) ; -Rec t angle () (}
10: ll : 12:
void DrawShape(int aWidth, int aHeight . bool UscCurrentVals = false} const ; pdvale :
l) ,
int itsWidth ;
int itsHeight ;
14: J;
15 : 16: 11:
II
A konstruktor megva16sitása
18 : Rectangle : : Rect.anglc(int width, int heightl : 19 : itsWidth(widthl , II inicializálás 20 : itsHeight(height) 21 : (j 110res fUggvénytOrzs 22 : 23 : II A 3 . paraméter helyén álló érték 6rtc1mezése 24 : void Rectangle : : DrawShape{ int width, 25 : 26 : int height , 27 : bool UscCurrentValue 28 : ) const 29 : 30 : int printWi dth ; int printHe ight ; 31 : 32 :
33 :
iC (UseCurrentValue
34:
(
"':o
true)
35 : printWidth", i t sWidth ; II Használja az osztály pillanatnyi értékeit 36 : printHeight :o itsHeight; 37 :
)
38 : else 39 :
40 : printWidth", width; II Használja a megadott paramétereket 41: printHeight = height ; 42: j 43:
13. 6ra • 44 : 45 : 46: 47 :
for
(int i = O; i
48 :
49 : std : , eout «
50 :
"\ n';
51 : 52 :
53 : 54 : II A túlterhelt fUggvények bemu tatása a foprogramban 55 : int main() 56 : {
57 : II 30;5 - re inicializáljuk a z egyik tégla1apot 58: 59 : 60 :
Rectangle theRect(30,5); std :: cout« 'OrawShape(O,O,truQ) .. . \n" ; theRect . OrawShape(O , O. truQ) ; !:ltd : : cout «"Or-awShape(40 , 2 ) ... \n"; theHect. DrawShape (40,2) ; return O;
6l : 62 : 63 : 64 :
menet OrawShape(O,O , true) ... ~ ••••••••••• • * •••• ** •••• *** •••
• _•••••••• * •••••••••••••••••••
••••••••• ••••••••••••••••••••• DrawShape(40, 2) ... ~ •• •••• • * ••••••••• *.* •••• ** •••• * •••••••••••••••• o • • • • • *. 0 •• *
A 13.2 lista a DrawShape () függvény túlterhelése helyen
I
236 IV. rész • Haladó eszközök
Ha ellenben a UseCurrentValue hamis - akár azért, mert nincs is Cés hamis az alapértelmezés), akár azért, mert a felhasználó azt adta meg - akkor a kél első paraméter alapján állít juk be a printWidth és a printHeight értékét. Figyeljük meg, hOb'Y ha a UseCurrentValue értéke igaz, akkor senkit nem érdekel a másik két paraméter értéke.
Hogyan válasszunk a túlterhelés és az alapértelmezett értékek használata között? A 13.1 és a 13.2 Lista ugyanazt a feladatot oldja meg, de az el.sőbe n haszná lt túlterhelt függvényeket egyszeníbb megérteni és kézenfekvőbb használni. Ha esetleg egy harm:ldik függvényvá llozal igé nye is felmerül (például a felhllszná16 szeretne egy olyat is, mclybcn csak a szélességet vagy csak a hosszúságot adjuk meg, de nem mindkeu6t), :Ikko r is könnyebb kiterjeszteni a túilerhelt függvényeket. Az alaphtdmezett t'! rtékek használata hamar elbonyo l6dhat az utó lagos hozzátold(lsokkal,
Hogy,ln lehet eldönteni, hogy tLllterhelt fü ggv~nye k et vagy érdemescbb-c használni? Ime egy gyors iránymutatás:
al:lp~ rtelmezcll l!rté kekel
Használjunk függvl: nytúlterhelést, ha • • •
Nincs {:sszerú al:l pél1elmezeu érték. Különböz
Konstruktorok túlterhelése A konstruktorok, mint minden más tagfüggvény, túlterhelhet5ek. Ez nagy hatású és rugalmas eszközt ad kezünkbe. I [a péld{IU[ kétféle téglalapkészít6 konstruktorunk vanj az egyiknek ál lehet adni paraméterül a hosszllsá glsz~ le.sség adatpárt, :I másiknak viszont nincs szüksége rá, akkor a fordítóprogram "szerint hoz létre ilyen v:lgy oly:ln téglalapot, ahogy II megadott paraméterek száma és típus diktálja (mint bármely más túlterhelt függvény esetében). A konstrukto rokkal ellentétben a destnlktorokat nem lehet túlterhelni. A destruktorok paraméter-szignatúrája (deklarációja) mindig ub'}'an3Z: az osztály neve, el6tte egy hullám(-) jellel, és nem adható meg pamméter.
13,6ra •
használata 237
Objektumok inicializálása Mostanáig az objekrumok tagváltozóit a konstruktor törzsében inicializáltuk. A konstruktoroknak azonban kél része van: a kezcl6értéklista és a konstruktor tör.lSe. A legtöbb változó bármelyik részben inicializálható, azonban világosabb és gyakmn hatékonyabb is a kezd6értéklistában inicializáini a tagváltozókat. Az alábbi példa eZl
mutatja be:
•
CAT () , II a konstruktor neve és a paramétGrek itsAge(S) , II kezdoértéklista i t sWeight(8) ( l II a konstruktor törzse A konstnlktor par:améterlistáj{mak zárójele után egy kett6spont áll~ ez utá n köve tkezhet
a tagvá ltoz6 neve és egy zlirójelpár. [bbe frandó az a kifejezés, amelyet kezd6értékOl szeretnénk adni a tagvá ltoz6nak. Ha több tagváltoz6t is inicializálunk, vessz6vel kell elv~!asztanunk 6kt!t. Figyeleml
A konstansok nem vákoznakl
Ne felejtsük el, hogya hivatKozásokat és a konstansokat inicializálni kell, és utána már nem lehet öket megválloztalni. Ha adat1ag hivatkozásaink vagy konstansaink vannak, ezeket a lenti m6don, a kezdöértékJistában kell iniciaHzálni.
Egyszerűbb - és azt kell mo ndjam, hogy hatékonyabb - a tagváltoz6kat a kezcl6érté klistában inicializál ni, mintsem kés6Db értéket rendelni hozzájuk. Ennek megértéséhez el6ször meg kell ismerkednünk a másoló konstruktofídl
A másoló konstruktor Az alapértelmezett konstruktoron és destmktoron kívül a fordítóprogram még egy alapértelmezett m:ísoló konstruktort is rendelkezésünkre bocsár. A másoló konstnlktor mindannyiszor meghívódik, ahányszor csak másolat készül az objektumról. Ha érték szerint adunk vagy kapunk objektumot egy függvényt61 , ideiglenes másolat készül r6la. Ha egy felhasználó által defmiált objekrumról van szó, akkor az oszt{llyhoz tartozó másoló konstruktor hívódik meg. Minden másoló konstruktornak egyetlen pammétere van: a sz6ban forgó osztá ly egy példányám utaló hivatkozás. Ezt nyugodtan írhatjuk konstans hivatkozásként, hiszen a másol6 konstruktornak nem kell változtatnia al átadon objekrumon. Példáu l: ClIT(const CAT
&
thecat) ;
IV. rész • Haladó eszközök ln a CAT konstruktor egy (már létező theCat) macskám utaló konslans hivatkozást vesz áL A másoló konstruktor célja másolatot készíteni a theCat objektumr61. Az alapértelmezett másoló konstnlktor egyszen.1en lemásolja a paraméterként kapoll objektum nunden tagvá ltoz6ját, és így hozza létre az új objektum tagváltoz6it. ELL felszíni (vagy tagonkéntO másolásnak Cshallow copy) hívják, mivel jól mt1ködik a legtöbb tagváltozóra, de nem kezeli jól a dinamikus memóriatcrületre utaló mutatókat. A fe lszíni vagy tagonkénti másolás tehál az objekrum tagváltoz6inak pontos m(lsolatát viszi át egy másik objekrumba. Ez aZl is jelenti, hogya mutatók ugyanarra a mcm6riacímre fognak mutatni mindkét objektum esetében. A mélységi másolás (deep copy) ezzel szemben képes arra. hogy a dinamikus mem6riateJilleten lev6 értékeket is megduplázza. lIa a CAT osztálynak van egy itsAge tagv:íltoz6ja, ;1rnely egy a dinamikus mem6riában lev6 egész sz:ímrJ mulat, akkor az alapértelmezett m{lwl6 konstruktor a paraméterként kapott CAT objektum it sAge tagváltoz6ját, mintmulat6t másolja ál az (Lj CAT objektum itsAge nevű l:!8válloz6jába. A kél változ6;1 mem6ri:l egyazon területére mutat, :!hogy azt a 13.1 ábm is mutatja.
Free Slore
old CAT
,---"":'::A,,,,,-...Jf-'
New CAT
I\sAge
13.1 ábra Az a/t/{Jértc/mczell m{/so/ó kol/slmklol" hasz1lálara
Ez kataSZlrnfához vezet, ha bármelyik macska megszúnik létezni. Ekkor ugyanis meg· hívódik a destruktor, amely meg fogja kísérelni a lefoglalt memória felszabadítását. Esetünkben, ha például az eredeti macska halálozik el, akkor :!nnak destruktora felszabadítja a dinamikusan lefoglalt memóriát. A másolatbeli i tsAge ugyanakkor még min· dig ugyanoda fog mut:!tni, így, ha a program megpr6bálja használni ezt a mutat6!, összeomlik. A 13.2 ábm illusztrálja a katasztrófát.
I
13. óra • A függvény.k ki1inomultabb h,sznál.ta 239
Fr86 Store
NewCAT
.'
13.2 ábra Gazdál/llll IIIIIUIIÓ IélrcllozóslI
Úgy oldhat juk meg ezt a felszíni másolásból fakadó problémát, hogy megírjuk gólját másoló konslruklorunk:n, és lefoglalunk a másolat számára egy új mem6riacímcl.
A mély másobt készít(:sc lehet5vé teszi, hogy a már létez6 értékeket is átmásolju k :IZ
új memÓrialerületrc. EZL mutatja be:\ 13..' Lista.
13.3 Lista -A _ _ ktur oHIk6d6Ie I_ _ .epp)
o:
/1 13.3 Lista l : /1 máaoló konstruktorok 2 : linclude 3, 4: class eliT S:
(
6 , public :
7 : CAT(); 1/ alapértelmezett konstruktor a: eAT (const eliT 'l; 1/ másoló konstruktor 9 : _CAT(); 1/ destruktor int GetAge() const ( rc tu rn *itsAge ;
10 : ll :
int GctWeight() const ( return *itsWeight ; { *i tsAge = age; }
12 :
void SetAge(int age)
13 : 14 : 15 : 16 :
private : int * itsAge; int *it sWeight ;
17 :
};
18 : 19 :
CAT: : CAT()
20 : 21: 22 : 23 : 24 :
{
25 : 26 :
itsAge = new int; itsWe ight = new int; *i tsAg e = 5 ; * i tsWei ght = 9;
I
140 IV. ré" • Haladó aszközök 27:
CAT , : CAT(const CAT & rhs l
28 : 29 , 30 :
(
31 :
itsAge '" new int; itsWeight '" new int; *itsAge '" rhs ,GetAge () ;
32 :
" itsWeight "- rhs .GetWeight() ;
33 : ]4 : 35 :
CAT ,: -CAT()
36 :
{
37 : 38 : 39 :
delete itsAge ; itsAge '" O; delete itsWeight ;
40 :
itsweight • O;
41 : 42 : 43 : 44 :
45 : i1 6 : 47:
int mai n (}
CAT frLs ky; std :: cout « std :: cout «
"frisky ' s age : • « frisky.GctAge() « 'Setting frisky to 6 ... \n';
'\n' ;
48 :
frisky . SetAge(6) ;
d9 , 50 : 51 : 52 : 53: 54: 55: 56 ,
std :: cout « ' Creating boots from frisky\n'; CAT boots(frisky) ; std :: cout « "frisky's age : • « frisky .GetAg e{) « "\n' ; std :: cout« "boota' age: • «boots.GetAge() « "\n"; std : : cout « 'setting frisky to 7 ... \0"; frisky.SetAge(7) ; std :, oout« "frisky's age , " « frisky.GetAge() « "\n" ; std : :cout « 'boot ' s age : • « boots.GetAgc() « "\n"; return O;
57 :
58 ,
friaky' a age , 5 Setting frisky to 6 ... Crea t ing boots from frisky frisky ' s age : 6 boota ' age , 6 sp. t ti ng frisky t o 7 ... fri sky ' s age : 7 boota ' age , 6
A 4-17. sorban deklaráljuk a CAT osztályt. Figyeljük meg a 7. sorban az alapénelmezett konstruktor, a 8. sorban a másoló konstruktor deklarációját. A lS. és 16. sorban két tagváltozót deklarálunk, mindkettő egész számra mutat. Általában nem használunk egész mutatókat egy Q'jztály ta&",áltozóiként, de így lehet a legegyszerúbben illusztrálni a dinamikus memórialerületen definiált tagváltozók kezelését.
Az alapértelmezen konstruktor a 19-25. sorban helyet készit a dinamikus memóriában ki!t egész számnak, majd az értékadást is elvégzi. A másoló konstruktor a 27. sorban kezd6dik. Figyeljük meg az rhs nevű pammétert. Gyakran nevezzük így a másoló konstruktornak átadott paramétert, amely a rig!Jf1/CI lld-side(jobboldalt) rövidítése. Ha vetünk egy p illantást a 31. és 32. sorba n található értékadásokra, megf1gyelhetjük, hogy :LZ átadott paraméter áll az egyenl6ségje1 jobb oldalán. Vizsgáljuk ezt meg részletesebben: • A 29. és 30. sorban lefoglalunk egy-cb'Y egész számnyi helyet a dinamikus memóriában , melyeket a már létez6 CAT objektum megfelel6 é rtékei alapján töltünk fel a 31. és 32. sorban. • Az rhs eb'Y macska (CAT), melyet konstans hivatkozáskénr adunk át a másoló konstruktornak. Az rhs . GetAge () tagfüggv&ny azt az értéket adja vissza, melyre az rhs sajíit itsAge tagváltoz6ja mutat. Mint igazi CA'! objektumnak, rhs-nek megvan minden szüksl!ges tagváltoz6 ja. • Amiko r a másoló ko nstruktor mcghív6dik egy ct j macska létrehozására, kapnia kell egy (már létez6) macskát par.!méterként. A 13.3 ábra felvázolja, mi történik itt. A már létez6 macsk .. változ6i álta l mutatott értc:!kek átmásolMnak az új macska számá r.! lefoglalt memóriaterÜlctckrc. Free Store
r-
5
5
NewCAT
oIdCAT
ItsAge
1-
I--'
itsAge
13.3 ábra A mély mdsolás bemutatása
A 45. sorban létrejön egy frisky nevű m,lCska (CAT), majd kiír6clik az életkora, melyet a 48. sorban gyorsan át is állírunk 6-m (természetesen ezt is dokumentáljuk). Az 50. sorb-.lO kl6nozunk egy új macskát (boots néven) egy másoló konstruktorral, melynek frisky-t adjuk át pamméterként. Ha bármilyen más függvénynek adnánk át érték szerint frisky-t pardméterként, akkor is ugyanezt a másoló konstruktort hívná meg a fordítóprogmm.
242 1IV. rész · Haladó as2l<özök Az 51. és 52. sorban mindkét macska életkorát kiíratjuk Elég meggyőz6en látszik, hogy hoots életkora nem az alapénelmezen 5, hanem a frisky-161 megörökölt 6. Az 54. sorban frisky életkorát 7-re állítjuk, és ezt rögtön ulána ki is írat juk. Ekkor frisky életkora már 7, boots azonban megmaradt fiatal 6 évesnek, jól nlUtatva, hogy kora különálló memóriatcrülctcn lárolódik. Amikor macská ink kimúl nak, destruktoraik automatikusan meghív6dnak. A CAT destruktor mcgval6sítása a 35-41. sorban látható. Mindkét mutat6t (it s Age és itsweight) töröljük egy-cgy delet e paranccsal, fe lszabadítva ezzel az általuk lefoglalt dinamikus memóriát, és a biztonság kedvéért le is nullázzuk a mutatókat.
Kérdések és válaszok Kérdés: Miél1 haSZ/lálnánk afapértelmczcu é/1ékekkel dolgozó jüggvényeket, Ita lellet jllggvénytúlterhelést is alkalmazul? Válasz; EgyszenThb egy függvényt karbantanani, mint kettót, és sokszor könnyebb megérteni egy alapénelmezett énékekkel definiált függvé nyt, mi nt két függvény törzsét végignézni. Szintén fontos szempont, hogya tapasztalatok szeri nt gyakori hiba, hogy az egyik fOggvény kijavítása után elfelejtkezünk a másikr61. Kérdés; Ha ilyen problematikus afi."lggvéllyek flílterltalése, miérlllem használu lIk heIyetfl"/k mindig alapél1elmezett érlékeket? Vála..tt: A rüggvénytúlterhelés olyan lehetőségekkel is szolgál, amelyek nem állnak rendelkezésünkre az alapértelmezett értékekkel dolgo7..6 függvénycknl:1. A paraméterek számán túl ezek típusával is befolyásolhat6 ugy.mis a túlterhelt függvényck működése. Kérdés: Egy osztály komtrllktoriinak megírásakor mi alapjáll dóntlletó el, mit érdemes az kezdoo'1éklisttibcIII e/vegez/l i, és mit ajiiggvényt6rzsbell? Válasz: Egy egyszenl alapszabály az, hogy am il csak lehet, intézzünk el a kezd6értéklistában; azaz minden tagváltoz6t inicia lizá jjunk ill. Az egyéb dolgok, mint a számítások vagy kiírások a konstruktor törzsébe valóak. Kérdés: Lelle/-e Ilíllerhell függvéllynek alapérlelmezelf paramétere? Válasz: Természetesen. Semmi sem liltja, hogy ezeket a hathatós eszközöket egyszerre használjuk. A túlterhelt függvények közül egy vagy több tarta lmazhat saját alapértelmezett énékeket a szokásos szabályok szerim, mint ahogy ez így van bármely más fü ggvénnyel is.
13. 6ra • A függvények kifinomultabb használata 1243
Gyakorlatok Ebben az óráblln a füg&yvények kifinomultabb használatával ismerkedtünk meg. Válaszolju nk néhány kérdésre és végezzünk el né hány feladatolludá suok ellenőrzése és megsziJárdít;ísa végett!
Kvfz 1. Honnan tudj:l a fordit6progmm, hogy a túlterhelt függvényválto7..alOk közül melyiket kell meghívnia? 2. Lehet-e ala pénelmezelt paraméte rekkel ellátni tllltcrhelt függvényváltoz31okat? 3. Miért érdemes túlterhelni egy konstruktort? 4. Mit csinál a másol6 konstruktor?
Feladatok 1. Módosítsa úgy az over l oadfunctions . cpp programot 03. 1 Lista), hogy legyen egy olyan változata is a DrawShape () függvénynek, amelynek két alapértelmezett egész paramétere van. SikeriI llefordítani és futtatni? Milyen következtetést tud levonni ez alapján a túlterhelt függvények és az alapértelmezett paraméterlisták összekapcsolását illet6en? 2. Módosítsa úgy a copycons truc t ors . cpp programot (13.3 Lista), hogy azután m6dosítsa boot s korth, milltfin f ris ky életkora már megvá ltozott! I [alássa! vane bOOLS életkora f dsky-érc? Azt már láttuk, hogy f ris ky életkorának váltoWItása nem befolyásolja boots-él. 3. Goodo!kodjon el a copyconstruc t ors . cpp program 03.3 Lista) alapján azon, hogy mi történne akkor, ba az itt láthat6 destruktor helyen az alapértelmezettct használnánk? Mi (vagy pontosabban mi nem) történne?
Válaszok a kvfzkérdésekre 1. A megadott para méterek számából és típusából. 2. Ige n, mindaddig, amíg a különböz6 függvényváltozalok paraméterlistái egyediek ma radnak, hiszen csak ebb61 tudhatja a fordít6 progmm, hogy mikor melyiket kell hívnia. 3. A konstruktorok t'Últerhelhet6sége rugalmassá teszi az objektumok létrehozását. Létre lehet hozni paraméterek nélkül is egy objektumot (például egy alapértelmezeIlet vagy üreset), és egy másik konstruktor segítségéve! egy olyat is, amelynek kezd6 paramétereit megadhatjuk; vagy akár lehet több konstruktor is többféle paraméterlistával.
244 1 W, rész ' Haladó aszközök 4. A másoló konstruktor akkor Iúvódik meg, amikor másolat készül egy objektumr61; például amikor egy objektumot kifejezetten le szeretnénk másolni, v3b'Y amikor egy függvény számám érték szerinti pamméterMadás történik. A fordítóprogram rendelkezésünkre bocsát egy alapértelmezett másoló konstruktort, de a bonyolultabb objektumok esetén ez nem biztos, hogy igényeinknek megfele-
l6cn
működik.
14.
ÓRA
Operátorok túlterhelése Ebben az 6rában a következ6kr61lesz sz6: • • •
Hogyan terhelhetOnk túl tagruggvényekel Hogyan terhelhet jük túl a hozzárendelési operátort mem6riakezelés céljáb61 Hogyan írhatunk olyan függvényeket, melyek támogatják a dimtmikus vá1Lozókal hlsznál6 osztályokat
Hogyan terhelhetünk túloperátorokat A C++ s:dunos beépített típussa l rendelkezik. Ilyen például az i nt, real, char, stb, Mindegyik típushoz tartozik számos beépített operátor is, mint például az összeadás Ct) és a szorzás (*). A C++ lehet6vé teszi, hogy ilyen oper.í.torokat saját osztályainkkal kapcsolatban is fel használjunk. Az operáto r túlterhelés bemutatásához a 14.1-e5 listában létrehozunk egy Counter osz-
tályt. A Counter osztályt számlá lásra fogjuk fel használni - minő meglepetés - például ciklusokban és olyan helyeken, ahol egy számO[ növelünk, csökkentűnk, vagy bármi egyéb módon vállozlaljuk az értékét
246 1W. rész· Haladó eszközök
0 , II 1 4. 1 Lista 1 : / 1 Coun ter os ztá l y 2: tlinclude 3.
4: 5,
c lass Coun tcr
6:
public :
7: 8: 9: 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 :
Countcr() ; -Counter(){} int GetItsVal()const { return itsVel ; void SctItllVal (int xl {itsVal • x : }
private :
int i tsVal ; };
Count er: : Counter{) : itsVal(Q)
fl
19 : 20 : 21 :
int main{)
22 :
Counter l i
23 : 24 : 25 : 26 :
std : : cout « "The val u e of i « std : : cndl : re t ur n O;
The value of i is
is • «
i . GetItsVal()
Q.
Ebben a formában nem sok haszna van ennek a z osztálynak. A definícióját a 4- 14 sorokban láthatjuk. Egyedüli tagvá ltolója egy egész típusú változó. Az alapértelmezett konstruktor - amelyet a 7. sorban deklaráltuk és a 18, sorbnn imple mentáltunk - bcíillítja az i tsVal értéké! Q-ra. A valódi, beépített, mondhami vérbeli int típusú változ6val szemben a counter típusú objektumunkat se növelni, se csökkenteni nem tudjuk Nem tudunk se hozzáadni, se elvenni bel61e, nem tudjuk máshoz hozzárendelni , OOt semmilyen egyéb módon sem tudjuk manipulálni. Cserébe még az é rtékét is problémásabb kiíratni.
14. 6ra •
f~uk meg az inkrementáló függvényt Az operátor túlLcrhelés révén lehetőségünk nyílik osztályainkat (például a fenli Counter l is) számos olyan hasznos ké pességgel bővíteni , amelyekkel amúgy nem rendelkezik. Operátor túlterhe1ésról beszélünk, lm ch'Y operátorr egy osztályban implementálunk. A 14.2 Listában láthatjuk, hogyan terhelhetjük túl az inkrcmentálás operátorát.
14.2 Lista - Az inkremantál6 oper., túherhel6sa (overloadincrementcppl 0, /I 14.2 Lista l: /1 Az inkrementá16 operátor túlterhelése 2: hnclude
3. 4: 5. 6: 7: 8: 9: 10, 11: 12, 13: 14 :
class Counter publ i c :
Counter() : -Counter(){} int GetltsVal () const { return i tsVal; void SetItsVal(int xl (itsVal = Xi ) void Incremcnt() { ++itsVal; l const Counter& operator++ (l; privatc :
int üsVal ;
15 : 16 : 17 : 18 : 19 :
Counter : : Coun ter() : itsVa1(0)
20 :
()
):
21: 22: 23:
const Counter& Countcr : : operator++ ()
24 :
++itsVal ; return - this :
25 : 26 : 27 : 28 : 29 : 30 : 31 : 32 : 33 : 34 :
35 : 36 : 37 : 38 :
39 : 40 :
int main(} {
Coun ter i; std : : cou t « 'The « s t d :: endl ; i .lncrement() ; std : : cout « 'The « std : : endl; ++i; std : : cout « 'The « s t d : : end!; Counter a = -+ -+i; std : : cout « ' Th e
value of i
is ' «
i. GctltsVal(}
value of i
is ' «
i .GetltsVal ()
value of i
is ' «
i.GetltsVa1()
value of a : ' «
a . Getl tsVa1 () :
24l1 1IV. rész • Haladó aszközök 41:
std :: c o u t « · and i : .«
42:
rcturn O;
l.GetltsVal()«
std :: endl ;
43:
The The The The
value value value value
"" "' "
of i of i of i of
O 1 2
3 and i : 3
A 36. sorban meghívtuk az inkrcmentá!6 opcrátor1: ++1 ;
A fordító ekkor valójában a 22-26 sorokban megva16sítoTt operator++ függvényt hívja meg, mely növeli az itsValue tagváltoz6 értékét majd visszatérési értékként az aktuális objckuHllot címz6 (l his) mutat6n keresztül mag{ll az objektmnot adja vissza. Ez egyben lehet6vé teszi azt is, hogy a Counter objekwmot hozzárendelhessük a-hoz. l ia a Counter típusú objektumok dinamikusan is roglalnának memóriát , akkor kötelez6en felül kellenI.! bír{llnunk a konstruktort is. Jden esetben azonban az :l lapértelmezelt másoló konstruktor is megleszi, Figyeljük meg, hogya példában hivatkozásként adjuk vissza a Counter objektumot
A postfix (utótag) inkrementáló operátor túlterhelése Mi a helyzet abban az esetben, ha a postfIx inkremcntáló operátOlt szeretnénk túlterhelni? Ez problémás kérdés a fordító szemszögéből nézve, hiszen elsőre nem világos, hogyan tehetünk különbséget preftx és postfLx operátorok között? Megegyezés s;.;erint czt egy egész típusú változó segítségével jelezzük az operátor dekJarád6jában , A paOJ.métcr értéke lényegtelen, csupán jelzés értékkel bír: közli a fordít6programma l hogy postlIx operátorr61 van szó.
A prefix és a postfix közötti különbség Mielőtt
megírhatnánk S<"lját POStfIX operátorunkat. meg kell értenünk, miben különbözik a prefix változatról. Emlékeztetőül: a prefIX operátor előbb inkrementál. majd kiolvas, a postfix pedig el6bb kiolvas, csak aztán inkrementál.
túltemelése Amíg prefIX operátor esetén egyszeruen csak növeljük az értéket és visszaadjuk magát az objektumot, addig postfIX esetén el6ször kell értékel visszaadnunk és csak utána növelhet jük meg. Ehhez feltétlenül létre kell hoznunk egy ideiglenes objektumot. Ez az ideiglenes objektum fogja tárolni az eredeti énéket, miközben az eredeti objektumot megnöveljük. Ezt az ideiglenes objektumot fogjuk aztán vis.<;7..aadni, hiszen a postflX operátomái az eredeti értéket várjuk és nem a már inkrementáltat.
Apropó
Nézzük át még egyszer
Tegyük fel, hogy a programunkban szerepel a következő sor: (l
=
X++1
Ha x értéke 5, akkor az utasrtás végrehajtása után a értéke 5 lesz, de x értéke már 6. Az x értékét kiolvassuk és hozzárendeljük a-hoz, csak aztán növeljük meg. Amennyiben x egy felhasznál6i objektum, úgy postfix növelés esetén el kell tárolnunk az eredeti értéket (5) egy ideiglenes objektumban, aztán megnövelhetjük x értékét 6-ra, végül pedig visszaadjuk az ideiglenes objektumot, amelyet azután hozzárendelhetünk a-hoz. Figyelembe véve, hogy ideiglenes objektum tartalmazza a visszatérési értéket, ncm haszná l1mtunk hivatkozássa l történ6 átadást, hiszen amint lefut 3 függv6ny, 34 ideiglenes objektumot nt:m tudjuk többé elérni. Éppen ezért érték szeri nti atadást kell használnunk. A 14.3. Lista bemutatja, miként használjuk a preflX és a postfIx operátoroka!.
14.3 Ulla - Prefix és po.lfix opo,6torok hosznállla (pr...dposlfix.cppl
o: II 14.3 Lista 1 : II A this pointer 6rt6kének visszaadását mutatjuk be 2 : jinc1ude 3, 4: class Counter 5:
(
6: 7:
public: Counter(): -Counter{) () int GetltsV",l(lconst { return itsVal; void SetltsVal(int xl {itaVal x;} const Countcr& opera t:or++ (l; II prefix const Counter operator+ + (int) ; /1 postfix
8:
9: 10 : ll :
12 : 13 : 14 :
15: 16: 17 : 18: 19: 20 :
21 :
private: int itsVa1; ); Counter : : Counter{) : itsVal (O) fl
I
250 IV. rész • Haladó eszközök 22: 23 , 24 :
const Counter& Counter : :operator++ ()
/1 prefix
(
++itsVal: return *this;
25 : 26 :
27 : 28 : 29: 30 : 31 : 32 :
const Counter Counter : : operator++ (int)
II post fix
Counter temp(*this); ++itsVal; return temp;
33 : 34 :
35 : 36 :
int main()
37 :
Counter i-
38 : 39 :
std :: cou t « 'The value of i i s • «i.GetltsVa1! ) « s t d ::end l;
40 : 41: 42: 43 : 44 : 45 :
i ++ ;
std : : cout « 'Thc valuc of i « std : : endl ;
is • «
LGctltsValO
+Ii ;
std :: cout« "The value of i « std : : endI ;
46 :
Counter a
47: 48: 49 :
I:Itd :: cout « std : : cout « II .. i.+;
50 :
std : : cout « std :: cout « return O;
51: 52:
~
is • «i.GetItsVal()
. . "' . .'The value. of« ., . «
++i; "The value of « a . GetItsVa,l(); and i: « i .GetItsVal () « std : : endl;
and i :
a . GetltsVal(); i .GetltsVal () « std: :cndl;
53 :
The The The The The
vllIue vaIue vllIue vaIue vaIue
of of of of of
i i i
"
O i. l i. 2 3 and i: 3 a , 3 and i: 4
"'
A 12. sorban deklarált postfIx operáton a 28-től a 33-ig sorig ccrjcd6en implementálmk. ÉszrevchetjOk, hogy a 43. sorban sze replő prefix operálornál nem használtuk az egész lípusú jelz6l (x).Postfix operátor esetén viszont így jelezzük, hogy poscfixr61 és nem prefixr61 van szó. Természelesen az x énékét nem használjuk semmire.
14.6ra •
túltemelése 251
operator+ Az inkrementál6 operátor egyopemndusú (unary), tehát csupán egy operandusr vár 16-
Jünk. Ezzel s7.emben az összeadás operátora (+) kétoperandusú (binary), két operandust kell neki megadnunk (például a + b). Magától adódik a kérdés: hogyan terhelhetjük fÚ] a -+- operátort a Counter osztályunk esetében?
A célunk tehát az, hogy kél Counter típusú változót össze tudjunk adni az alábbi módon:
Counter varOne, varTwo, varThree ; varThree = varOne -+- vurTwo l
Ismétlem: nyugodtan írhatnánk mondjuk egy Add () nevCí függvényt, amely kél Count er típUSlI paramétert vá r és egy Counter típust ad vissza eredményként. EZl :1 megközelítést mutatja be a l4A-es lista.
14.4 Ulla - Az Add(l függv6n, laddlunction.eppl
o:
/I 14 .4 Lista 1 : II Add fOogvény 2: linclude
3, 4: 5:
6: 7:
8: 9: 10 : 11 : 12:
13 : 14 : 15 : 16 : 17 :
class Counter ( public : Counter{); Counter{int initialValue) ; -Counter(){} int GetltsVal()const ( return itsVal; void SetItsVal(int x) {itaVal " x; } Counter Add(const Countcr &); private: int itsVa1 ; };
18 : 19 : 20 : 21 : 22 : 23 : 24 : 25: 26: 27 : 28 : 29 :
30 :
Counter: : Counter (int initia1Va l ue) : HsVal (initia1Va1ua) () Counter: : Counter() : itsVa1(O) {} Counter Counter : : Add(const Counter return
Counter(itsVa1~
l<
rhs )
rhs.GetItsVal(»;
252 1 IV. rész • Haladó ,s2i<özök 31 : 32 : 33 :
int main ()
34: 35 36 37 38 39
Countcr varOne(2) , varTwo(4) , varThree; vdrThree = varOnc .Add (varTwo) :
: : : : :
std::cout« ' varOne : • «va rOne.GetltsVal()« std: : endli std : : cout « 'var'l'wo : «varTwo.GetltsVal()« std : : cndl ; std : ,cout « ' varThree : • « varThree.Gctlts Val() « s t d :: endl ;
return O:
40 : 41:
va rOne : 2 varTwo : 4 varThree : 6
AZ Add () filggvéllyt a 12. sorban deklaráljuk. Cím szerint átadjunk egy Councer típusú konstan!;t, mcly aZl a l:rtékct tartalmazza, melyet hozzá szeretnénk adni az aktu:'ilis objekrumhoz. Counter típusú objektumot ad vissza, melyet a 35. sorban hozzárcndclünk a balo ldalhoz. Összefoglalva: varOne az objektum, varTwo az Add ( ) -nak ~Íladott paraméter, varThree-be pedig elhelyezzük a végeredményl. Ahhoz, hogya varThree-t l6trehozhassuk inicializ:ilás nélkül , alapértelmezett konstmktort kell írnunk hozzá. Az alapértelmezett konst!l,lktor inicializálja az itsVal értékél O-ra, ahogy azt a 23-25 sorok között is megfigyelhctjük. Nem nulla értékkel inicializáljuk varOne és v.!IorTwo objektumokat, ezért hozzájuk másik konstruktorra van szükség, melyet a 19-21 sorokban találunk. Másik leheL'>éges megoklása ennek a problémának, ha alapértelmezésként o-ára á11ítjuk a konstruktorban (8. sor) az értéket.
Az operator+ túltemelése A 14.4-es lislában az Add () függvényt a 27-30 sorokban val6sÍlottuk meg. Noha a megoldás működ i k , eléggé ~Ie rmészetellenesnek " tűnik használat közben . Az operator+ túlterhelésével sokkal természetesebbé tehető a Counter osztály használata . Ezt a 14.5ös listában mutatjuk be.
0 , 1114 . 5 Lista 1 , lj Az összeadás operátor 2 , lIinclude 3,
4,
class Counter
(+ ) túlterhelóse
14.6... Oporátorok túttemelés. 1253 5,
6: 7: 8,
public: Counter () ;
Counter(int initialValue) ;
9:
_Cou nter() {J
i n t Ge tltsVal()const { ret u rn i tnVa l ;
10 :
void Set ItsVal(int xl
11 :
12 : 13 : 14 : 15 :
16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 :
(itsVa l
'" x ;
)
Counter operator+ (const. Counte r &); pdvate : int itsVal ; );
Counter : : Counter(int initialvalue) : itsVal{initialValuel ()
Countcr : : Count er() : itaVal ( O) ()
24 :
25 :
Counter Counter : : operat.or-+· (const Counter " rhs)
26:
(
27 : 28 :
return Counter(itsVal + rhs.GetltsVal() ;
29 :
30 : 31:
]2 :
3] : 34 : 35 : 36 : 37 : 36 : 39 :
int main() (
Countcr varOne(2) , varTwo(4) , varThrec; varThree = varOne + varTwo ; s td: : cou t « 'varOne : • « varOne . Getl t s v al( ) « s td: : cnd l ; std : : cout « ' varTwo : • « varTwo.GetltsVa!() « std : : e ndl; std : : cout « 'varThrec: ' « varThree.GetltsV51() « std : : endl ; return O;
v5rOnc : 2 varTwo : 4 varThree : 6
Ezek után meghívhatjuk az összeadás operátorál úgy, ahogy azt a 33. sorban is [esszük: varThree : varOne + varTWO;
A fordít6 ilyenkor úgy értelmezi, mintha CZl írtuk volna: varThree = varone ,operator+(varTwo) ;
..r.,
254 1 W. rész • Haladó ,,",özök Ennek megfelelően meghívja a 12. sorlY,m deklarált és 25-28 sarokb,1n megval6síton metódusL Összevetve a korábbi Add { ) függvény deklarációjával és definíciójával szinte nincs is különbség. Ami al alkalmazást illeti, ott viszont szembeötl6 az e ltérés. Men hiszen mennyivel tennészetesebb a varThree
~
varOne + varTwo ;
mint a varThree = varOne.Add{varTwo) ; Nem nagy változt.atás, de ahhoz épp e lég, hogy könnyebben átlátható és használható programot kapjunk. Látható tehát, hogy operátorok t(JlterheJéséveltulajdonképpen explicit függvényhívásokat helyeuesíthetünk.
Az operátortúlterhelés korlátai /I. beépíLCU típusok opcrálom il (ilyen például
Mit célszerű túlterhelni? Az operátortúlterhelés le het6sége a c++ egyik olyan terillete, melyet kezdő programozók gyakra n feleslegesen használnak s6t visszaélnek a lehct6sl!ggel. Csábító lehet6ség ugyanis újsze n1 és érdekes módon felhaszná lni a legzavarosabb operátorok31, azonban ez á ltalában zagyva és nehezen értelmezhet6 kódot eredml!nyez.
Egy ideig persze vicces lehet a + operátorral kivonást, a ., operátorral meg összeadást végeztetni, dc profi programozók soha nem tesznek ilyel. Veszélyforrás az is, ha valaki jó szándékk~d , de egyéni logik:lval használja az operálorokat - pé ldául + operi'tlo rL használ betúk összefúzésére, vagy j - t karakterláncok relbontáslíra. Fel le he t pe rsze sorakoztatn! az cff6le megoldások mellett s:.:6l6 érveket is, dc minde n eselbe n körülLekint6en kell eljárnunk. Ne feledjük: az operátoltúlterhelés célja a progl
operator= Ugye e mlé kszünk, hogy a fordít6 biztosít alapértelmezeu konstruktort, destruktort és másol6 konstruktort. A negyedik és egyben utolsó függvény, melyet a fordító biztosítamennyiben nem adunk meg sapt vált07..atol- a hozzárendelési operátor (operator= (l).
14. 6ra •
tútterhelése
Ezt az operátol1 hívjuk meg, valahányszor értéket adunk egy objektumnak Például: CAT catOne (S , 7); CAT catTwo(3,4) ; II .. a program többi része 0
catTwo '" catOne;
Példánkban létrehoztuk a catOne-t és a megadott pamméterekkel (5,7) inidalizáltuk az itsAge és az itsweight tagváltoz6kat. Ezután létrehOlllIk a ca tTwo-t, amit ismét a megadott paraméterekkel (3,4) inidalizáltunk. Az utolsó sorban azt{m színre lép az alapértelmezett hozzárendelési operátor (operator=): catTwo '" catOne;
A hozzárendelés eredményeképpen a catTwo objektum itsA<;I'e és itsWeight változói felveszik a catOne megfelel6 értékeit. Az utasítás lefurtatásll után a catTwo itsAge értéke tehát 5 lesz, a catTwo. i t s Weight értéke pedig 7. o
Figyeljük meg, hogy példánkban nem hívnlk meg a másoló konstruktort. A catTwo ugyHois már létezett, nem kellett azt létrehozni, í!,'Y a fordító intelligens módon a hozzárendelési operátort hívta meg. A 13. órában megtárgyaltuk 3 felszíni Clagonkénti) és a mély (közvellen) másolás kÖZli különbséget. A felszíni másolás csak a tagokat másolja és dinamikusan kezelt tagokn{d mindkét objektum ugyanarl"..t a címre mutal. A mély másolás ezzel szemben lefog;lalja a szükséges memóriaterületel. Az alapértelmezett másoló konstruktor m(íködéséről már láthaUunk korábban egy ábrát 03.1. ábra), lapozzunk vissza, ha szükségesnek érezzük. A hozzá rendelési opecltornál meglehet6sen hasonl6 a helyzeT, mint a m{\soI6 kons!· ntktomál. Persze itt van egy kis csavar a dologban: a cat'J'wo objektum már létezett, te· hát tartozott hozz<1. lefoglalt tárterület. Hogy elkeri.iljük a memóriaszivárgást, törölnünk kell ezt a terü letet. A hozzárendelési operátor implementá lás.1 során tehát első dolgunk a mutatóhoz tartozó memóriaterület [örlése, kitakarítása. De mi is történik, ha a ca tTw"o-L önmagához rendeljük, va lahogy így: cat TWo = catTWo ;
Persze senki nem csinál ilyesmit szándékosan, de a programnak akkor is le kell mdnia kezelni. Van valami, ami viszont még ennél is fontosabb: az efféle értelmetlenek túnd hozzárendelések véletlenül, rejten módon is bekerülhetnek a kódba , például úgy, hogy a hozzárende1ést mutatókon keresztül végezzük, amelyek elfedik 3 háttérben történő eseményeket.
25S I IV. rész' Haladó eszközök Ha ezt a problémát nem kezeltük körültekintóen, a catTwo törölheti saját memóriafoglalását Aztán amikor a hozzárendelési operátor oda érkezik, hogy ténylegesen rnásolná a mem6ria tartalmát, akkor szembesül csak a nagy problémával: nincs is mit másolni. Ezt
elkerülendő
ellen6riznünk kell, hogy a hozzá rendelési operátor jobb o ldalán szeobjekcum nem ugyanaz-e? Ezt a this mUlató vizsgálatával teheljük meg. A 14.6 Listában egy osztályt mutarunk be, amely rendelkezik hozzárendelési operdlorral. replő
14.6 Uata - Houárandelési operátor (assignoperator.cppl
o:
/I 14.6 Lista
1 : II Másoló konstruktorok 2:
#include
3,
4 : c lass CAT 5: { 6 : public : 7: II alapértelmezett konstruktor a : II cl mnsoló konstruktort és destruktort kihal]Ytuk 9: int Gethge() const { return *i tshge; } 10 : int GetWaight {) const { return *itsWeight; ll: void SetAge(int age) ( ' itsAge = age ; ) 12 : CM' operator:c(const CAT &) ; 13 : 14 : private : 15 : int *itsAge; 16 : int " itsWeight; 17 : ); 18 : 19: CA'!' : : CAT() 20 : ( 21 : itsAge = new int ; 22: itaWeight = new int ; *itsAge • 5; 23 : 24 : *itsWeight = 9 , 25 : 26 : 27 : 28 :
29 : 30 : 31 : 32 : 33 : 34: 35 : 36 : 37 : 38 : 39 : 40 : 41 :
CAT CAT: : oper ator=(const CAT & rhs) ( i f IthiEl == &rhs) return " th i s ; delete itsAge; delete itsWeight; itshge = new int; itsWcight = new int; *itsAge = rhs .GetAge{); " itsWeight = rhs . Ge tWeight () ; return *this ;
14.6ra · 42:
int rnain()
43 : 44:
CAT frislcy: std : :cout « "frislcy's age: • « frisky.GetAge() « std: : end1 ; std :: cout« 'Setting frislcy to 6 .. . \n' ; frisky . SetAge(6) : CAT whiskers: std: : cout « "whiskers' age: • « whiskers.GetAge() «std: : cnd1 ; std : : cout « 'copy ing frisky to whiskers ... \n' : whiskers ~ frisky : std: :cout « 'whiskers' age : • « whiskcrs.GetAge() « std : : end,l; reLurn O;
45 :
46: 47: 48 : 49: 50 : 51: 52 : 53 : 54 : 55 :
56 : 57 :
frisky' s age: 5 Sctting fri!;ky to 6 ... whiskcrs' age: 5 copying frisky to whiskera .. whiskcrs' age : 6
A 14.6 Listában ismét viszomláthatjuk a már jól ismert CAT osztályt, (gy mern helytaka. rékoss.1gból kihagynJk a másol6 konstruktort és destruktor!. A 12. sorblln dekbráltuk a hozzá rendelési opcrátort, melyet kés6bb a 2s..39 sorokban definiáltunk. A 30. sorba n n aktu ális objektumot, amelyhez hozzárendelünk, megvizsgáljuk, hogy nem egyezik-c azzal, amelyet h ozzá szeretnénk rendelni. Az elle nőrzést az rhs mem6· riacímének ~s a this memóriacímének összehasonlításával végezzük el. Természetesen az egyen lőség operátort (,,"') is túlterhelhetjük, ennek köszö nh etően sa· ját kritériumok alapj{tn dönthetjük el, mikor "egyen lő " két objekntmunk.
Apropó
Kezdjünk tiszta lappal
A 32·töI35·ig terjedő sorokban töröljük, majd újra létrehozzuk a tagváltoz6kat a memóriában. Noha ez nem kötelező, jó és korrekt programozói szokás, mert megkfmél a mem6riaszivárgás okozta problémáktól, amikor olyan változó hosszúságú objektumokkal dolgozunk, melyek nem rendelkeznek saját hozzárendelési operátor· ral.
25S I IV. rész • Haladó ,szI<özök
Konverzi6s operátorok Mi történik ha egy beépített típust - legyen az egész vagy el6jel nélküli rövid egész kívánunk egy felhasználói osztály példányához rendelni? A 14.7-es listában a Counter típusú objekru mhoz kísérelünk meg int tipusú váltoZÓI hozzárendelni.
FIgyelem!
Ne lep6djünk meg
A 14.7. Lista kódját nem lehet lefordítani . A hibaüzenetet az elemzés elolvasása után fogjuk csak megérteni.
o: /I 14 . 7 Li!;ta 1 : II Ez a példa nem fog leforduln! ! 2: ,.
j include
4:
class Counter
5: 6:
( public :
7:
Counter{) ;
8: 9: 10 : ll : 12 :
13 : 14 : 15 :
~Counter(){}
int GetltsVal{)const { return itsVal ; void SetItsVal(int x l litsVal = x : } private: int itsVal; l;
Counter : : Counter() :
16:
itsVI!Il(O)
17 : 18 :
()
19 : 20 : 21 : 22 : 23 :
int /'IIlIIin()
24 : 25 : 26 :
(
int theShort = 5; Counter theCt r = the Short ; s td : : cout « "theCtr : • « theCtr.GetItsVal() « std: : e nd l ; r eturn O;
" counterconvert.cpp " : E2034 Cannot convert 'int' to 'Counter' in ... function main() at line 22
14. óra •
tú.emelése 259
A 4~13 sarok között deklarált Count er osztály csupán alapertelmezctl konstruktorral rendelkezik. Nem rendelkezik eb'Yooi metódllssal arra vonatkozólag, hogyan alakíthat át int típusú értéket Counter típusú objektummá, így fordítási hibát kapunk a 22. sorban. A fordító nem mdja kitalálni - hacsak nem közöljük vele - hogyan kell hozzárcndelnie egy i nt típusO vá!toz6t az itsval-hoz, mely az osztály tagváltozója.
A 14.8. Listában megfelelő konverzi6s operátor létrehozásával javítjuk a hibát: a konstruktor al. int típusO változó alapján létreholza egy Counter objektumot
14.' Lista - int étalakItása Counter..,é o : II 14. 8 Li sta l : II Konstruktor , mint konvcr% i 6s operátor 2:
#include
3. 4: 5: 6:
class Counter ( public :
7: 8: 9:
10 , ll : 12 :
13 : 14 : 15 : 16 : 17 : 18 :
Countcr() ; Counter ( l.nt val) ; -Counter() { l int Gc t ltsVal()conS L { rn turn itsVal ; void SetItsVal(int xl (itsVal = X; l private : int itsVal : l; Counter : : Count er( ) : itsVal {Ol ()
19 : 20 : II Konstruktor túlterhelés 21 : itsVal (val) 22 : (J 23 :
24 : 25 : 26 : 27 : 28 : 29,
int main() ( int theShort " 5 : Counter thcC tr = t heShort ; std: : cout « " thcCtr , • « thectr.GetltsVal() « re tu r n O,
30 :
thectr : 5
std : : end1 ;
I
260 W. rész • Haladó eszl(özök
A lényeges változás a 8. sorban lálható, ahol is il konslruktolt terhe1tük túl úgy, hogy j nt típust fogadjon. A mcgv'116sílás a 20-22 sorokban ](llkHfJ, A módosítások eredményeképp ez a konstruktor az int típusból Counte r lípust hoz létre. Ezután a fordító már meg tudja hívni a konstruktort, amely in t típust vár argumentumként. De mi történik, ha például megfordítjuk a hozzárendelést az alábbi módon? 1 : Coun t.er thcCt.r(5) ;
2 : int theShort ~ theCtr ; 3 : cout « " theShort : • «
theShort «
endl ;
Ez ismét fordítási hibát fog eredményezni. Noha a fordító m{l r tudja, hogyan hozzon lé tre Count er lípusú változó! i n t típusb61, nem tudja megfordítan i a hm:zárendelést.
Az intO operátor I logy megoldjuk a femi problé m{u - vagyis hogy miként csinÍl lhatunk Counter típusból int típust - és az e hhez hasonlókat, használjuk a C++ konverziós o peráto rnil. Ezc k a konve rziós operátorok ké pessé tcszik osztá lyunkat arrd , hogy implicit módo n konvcrtá ljunk objektumokat a beé pített típusokra. A 14.9. Listában mutatjuk be ezl. Egy megjegyzés cSllpfln: a konverzi6s operátoroknlil nincs megadva visszatérési típus annak e llenére, hogy az átalakított értéket visszakapjuk.
14.9 Ulta - Countar tfpust intO típust alakftjuk Icounterconvamntcpp)
o: II 14. 9 Lisla 1 : II konverzi6s operátor 2: linclude ]
,
4:
class Countar
5,
6:
7: S:
9: 10 , 11 : 12 , 13 : 14 :
15 : 16 : 17 : lS , 19 :
public : Count ar(j ; Co un ter ( i nt v a l j ; _Coun t ar(j { j int GetItsVal()con st { return itsVa 1 ; v o i d SetItsVa1(int xl ( itsVal =: x ; ) operator unsigned short C) ; private : int itsVal ; ); Counter : : Counter() : itsVa !(O) ()
20 : 21 : 22 ; 23 ;
Counter : :Counter(int vall ; itsVal (val) {}
24 ; 25 : 26 ; 27 : 28 :
Counter : : operato r uns i gned short () { return ( int (itsVal) ) ;
29 : 30 :
31 ; 32 : 33 :
34 : 35 ,
int main() Counter e t r(5) ; int theShort • etr ; std : : eout « " theShort : " « return O;
theShort «
std : : endl;
36 :
theShort : 5
A konver.d6s o peT"átort a 12. sorban deklarlilmk. Figyeljük meg, hogy nincs visszatérési értéke. A függvény implementki6ját a 25-28 sorokban t;:tláljllk. A 27. sor adja vissza az i tsVal értékét i nt típusra alakitva. A fordít6 most már tudja, hogyan kell egész (int) és Counter típusú objektu mokat egymásba átalakítani oda-vissza, vagyis a hozzárendelésekkel kapcsolatban teljes a szabadság.
Kérdések és válaszok Kérdés: !H/él1 definiálunkfe/ül egy operátol1, hll tagmelódllsl is lelrehozhall/./Ik? Válasz; Túlterhelt operlitorokkal könnyebb dolgozni, feltéve persze, hogy j61 ismerjük a viselkedésüket. Lehetővé teszik osztályaink számára, hogy a beépített típusokhoz hasonlóan viselkedjenek. Kérdés; Mi ti különbség II másoló konstluktor és a hozzá/"CI"Ide/ési operátor i...'Özött?
Válasz: A másoló konstruktor új objektumot hoz létre, amelynek értékei megegyeznek a meglévő objekcuméival. A hozzárendelési operátor ezzel szemben egy már meglév6 objektumot módosíl, mely felveszi egy másik objektum értékeit.
262 1IV. rész • Haladó 8S2I<öZök
Kérdés: Mi förlélllk azzal az int típusú pammélerrel, amit
cl
postfix
operátorok lúlter-
lIc1éséllél haszlIáftmkl \Iá/asz: Semmi. Az az int csupán annak jelölésére használjuk, hogy postfix opedtort kívánunk túlterhelni.
Gyakorlatok Most, hogy már ismerjük az operátorok tú ltcrhelésével kapcsolatos módszereket, válaszoljuk mcg a kvízkérdéseket és ;1 gyakorlatok segítségéve! mélyítsük el a 1l1egszerzelL tudást.
Kvfz 1. Miétt nem hozhatunk létre új operátorokm, mim például a **-ol hatványm.áshoz? 2. Miért nem definiálhat juk felül a már meglév6" operátOrl úgy, hogy hatvánY0zásként viselkedjen? 3. Miért különbözik a szintaxis prefIX és postfix inkrcmcntálás/dekremcntálás esetén? 4. Mil csinálnak a konverzi6s operátorok?
Feladatok l. A 14.6-05 Jistát (assignoperator . cpp) írjuk át úgy, hogy az c&'YenI6ség operátort (==) terheljük túl. Használjuk az operátol1 frisky és whisker életkorának
ÖSSzchasonlítására. 2. Irjuk át a 14.3-as példát (Preandpost fix. cpp) úgy, hogy al inkrernentál6 operátor hatására csökkenjen az itsVal értékc. Ha megcsinfiltuk és Iln1köc\ik, tegyük félre egy kis időre (egy órúm vagy akár egy napr::l). Aztán nézzük meg újra a programot. Még mindig egyértelmúnek tűnik, hogy hogyan viselkednek az operátorok? E.'ielleg némi zavart érzünk, mikor a progmlllot fu ttat juk? Ebból talán kiderült, miért nem szabad a túherheléstleljesen új fu nkciók kialakítására használni. 3. Módosítsuk a 14.5. Listában bemutatott kódot (pl usoperator . cpp) Ú&'Y, hogy amínusz operátort is túherhcljük. Úgy írjuk meg a kódOL, hogy az csupán egy egyszeru kivonást hajtson végre, majd futtassuk tc néhány különböz6 bemeneltel. Számít az objekrumok sorrendje az utasításokban?
14. 6ra • Operátorok Memelése 1263
Válaszok a kvízkérdésekre 1. A ** nem része a C++-nak, így magát a fordítóprogramol kellene átírni, hiszen nem tudja, hogyan kell azt kezelnie.
2. Megtehetnénk és talán lenne is értelme. Azonban más programozók 7A v;lrónak ta lálnák - 561 akár akár mi magunk is néhány év múlva, A nyelvben semmi sem akadályoz meg abb:m, hogy ezt megtegyük, mindazonáltal ha egy m6d van rá, (,lián ne nehcótsünk meg a sajálll1unkánkat. 3. A két operátor viselkedése teljesen különbözik Prefix jelölés esetén előbb végrehajtjuk a műveletet és csak azután OIV:lssuk ki az értéket. Postfix használat esetén ezzel szemben előbb kiolvassuk az értéket, csak aztán hajtjuk végre a műveletet. A túlterhelést használó programnak utánoznia kell eZl a viselkedést. Pontosabban nem kell, de ha nt:.:m szeretnénk magunkat megutáltatni a kollégáinkk:tl, akkor kövessük ezt a szokást! 4. A konvertáló ope~torok segítségével objektumot alakíthatunk beépített típussá. Ebben az órába!' a counter objektumot tanulmányozluk és a végeredményt egyszenl egészkém kívántuk megkapni. A cél eléréséhez II konverziós o perálorokat használtuk.
15.
ÓRA
Tömbök Mir611esz szó ebben az órában: • •
Mik
•
A tömbök és mutllt6k kÖzölti összefüggések
•
Hogyan használhal'Unk
mulmóműveleteket tőmbökön
Mi az a tömb? A tömh a7.onos típusú objektumok összessége. A tömböt ennek mcgre1e16en akár lIdattárolásra szolgáló rekeszek sorozataként is elképzelhetjük. Minden egyes rekesz a tömb egy-egy elemének felel meg. Tömböt úgy deklarálhatunk, hogy megadjuk a benne tárolt adatok típusát, leírjuk a tömb nevét, majd közvetlenül ez után szögletes zárójelek között megadjuk a fl.ltóindexének Csubscripú felső határát Ez utóbbi rulajdonképpen nem más, mint a tömbben tárolható elemek száma szögletes zárójelek közé írva. lássunk. C&'Y pl!ldát: long LongArray [25] ;
I
266 IV. rész • Haladó ,S2!<özök E deklaráció szerint a LongArray nevű tömb 25 hosszú egész (long) típusú elemet képes tárolni. Amikor a fordítóprogram e hhez a sorhoz ér, lefoglal annyi memóriát, amelyben az előírt mennyiségű adat elfér. Mivel esetünkben egy long típus 4 bájtot foglal, a fordító egy 100 bájtnyi összefüggő mem6riateruletet fog lefoglalni. Ez látható a 15. L ábrán.
~ 100 byte s
15.1. ábra l;gy 16mb deJdarálÚ$a és tm/lak lia/ása
A tömbök elemei Ha hozzá akarunk férni a tömb egyele mé hez, csupá n meg kell adnunk a tőmb neve után annak indexét, mégpedig ugyanúgy szögletes zárójelek közé zátva, mint a dekJarációban. Ügyeljünk rá, hogyatömbeleme nullától számoz6dnak, vagyis az els6 elemre a tOmbNeve[OJ formában hivatkozhatunk. E16z6 pé ldánknál maradva teh:ít longArray [O I jelenti az imént létrehozott 2S demű tömbönk első elemét, longArray [ll a másooik:u és így tovább. A swmozásnak ez a módja els6re sokak si'..ámára zavaró. A SomeArray (31 dekla ráció azt jelenti, hogya someArray nevű tömbnek 3 eleme van: SomeArra [O l , SomeArray [ll és SomeArray [2 ] . Általánosságban tehát a SomeArray [n] deklaráció mindig egy n e l emű tömböt mkar, amelynek awnban az e l ső eleme Somell.rray [O 1, az utolsó eleme pedig SomeArray [n- l l. Megint visszatérve saját példánkhoz a 2S elemű longArray tömb elemei loangArray [O) -tóllongArray [24 J-ig számoz6dnak. A lS.l. lista egy olyan kódot mutat, amelyben lérreho zunk egy 5 egész értékMI álló tömböt, és egyenként feltölt jük az elemeit.
15.1. ÜS.. - Eg6szekb6I AI16 tömb lótni_II és blllZllálata limnay. pl
o: 1115.1. Lista - Tömbök 1 : *i ncl ude 2,
3 : int main () 4,
5 : int myArray[51: 6 : for (int i=O ; i<5; iH) II O- 4 7: (
8 : std : :cout « ·Valuc for myArray [" « 9 : std : :cin » myArray[il ;
i «
• J : ";
10 : )
ll : for (int i = O; i<5 ; i++) 12 : std : : cout« i« ": " «myArray[i] « 13 : return O:
"\ n";
14 :
ValuE:l Va l ue Valuc va lu e Va lue
[oc fo c foc for foc
myArray [Oj : myArray [l J : myArrny[2 1' myArraytJ j: InyArray [ 4] :
3 6
9 12
15
0, 3 6 2, 9 3 , 12 4 , 15
"
-
Az ;. sorban tétrehozunk egy myArray nevtl tömböt, amely öt eb'Yszeres pontosságú egész értéket képes tárolni. A 6. sorban kezdődik egy ciklus, amelybe n a ciklusválloz6 o-t61 4-ig megy, vagyis é ppe n megfelel egy 5 elemű tömb indexei nek. A felha sznál6t61 minden egyes elemnek bekérilnk egy értéket majd beírjuk az a megfele l ő töm belem be. A tömbök számozásának megfelelőe n tehát az első bekért érték myArray r OJ -ba fog kerülni, a másod ik myArray [lJ -be és így lovább. " második ciklus kiírja II bekért értékeket a ké perny6re.
Apropó
A C++-ban a tömök indexelése nullával
kezdődik
A tömbök számozása nullával és nem eggyel kezdődik. Ez sokak számára elsőre elég furcsa, Igy a kezdők által elkövetett programozási hibák igen jelentős hányada ennek a "nyelvi érdekességnek" köszönhető. Ne felejtsük tehát el, hogy ha van egy 10 elemből álló tömbünk, akkor arra csak TömbNeve [Oj -tól TOmbNeve [91 -ig hivatkozhatunk. TOmbNeve (10 J elem egyszeruen nem létezik. Ha tehát a fenti példaprogramunk 6. és 11. sorában megengedtük volna az egyenlőséget is a ciklusfeJtételben « =5), akkor túlszaladtunk volna a tömb végén.
I
26S IV. ró" • Haladó eszközök
Tömbök túlírása Amikor felül akarunk írni egy tömbe!emet, a fo rdítóprogram a tárolt adatok mérete és a tümbindex alapján számítja ki a megfelelő eltolási é rtéket. Ha például a LongArray [5 ] elemel akarjuk módosítani programunk valamely pontján, akkor a fordítóprogram veszi a long típus hossz{n (vagyis 4 bájtoü, megszorozza azt az index énékével (az eredmény 20), majd a tömb elejét61 ennyi bájt távolságra beírj~_ a megadott értéket. Mármost ha azt kérjük ól fordíl6p rogmffitól, hogy írja felül a LongArray [50 l elemet, az akkor is pontosan ugyanezt fogja csinálni, függet le nül 31tó l, hogya kf:rdéses tömbnek nincs is ilyen sorszámú eleme. Kisdimítja, milyen messzire van a hivatkozou elem a tömb elejétől - esetünkben 200 bájtnyira - aztá n beírja oda a megadott értéket. Csakhogy azon a mem6rÍ3!.erületen gyakorlatilag bármi lehetett, "mit felOllrva <'lZ eredmény egyszeruen megj6solh:llatlan. Ha szerencsénk v,ln, a progL'"munk azonnal leáll valamilyen misztikus hibával. Ha viszont nincs, akkor a program tovább fu t, kiszámol mindenféle őrü lt eredményeke t, mi pedig hosszan törhetjük :l fejünket, hogy hol is le het a problém:L A tömbök kezelésével kapcsolatban tehát
a fordítóprogram úgy viselkedik, mint a vak
ember, aki elindult hazulról egyet sétálni. E l őször e lmegy az e lső házig, vagyis a F6Utca (O] e lemig. Eddig a dolog rendben is volna. Ha most ilt azt mondjuk neki, hogy menjen el :1 FG utca hatodik házához, "kkor azt mondja magában: most <'lZ els6 háznál állok, tehát men nek kell még öt Mznyit. Minden ház négy lépés hosszúságú, tchát mennem kell összesen még húsz lépést. Ez egészen addig rendben is va n így, amíg van a Fő utcának o lyan háza, amire hivatkoztunk. Ha viszont azt mondjuk a vak embernek, hogy menjen e l II FC; utca 1OO-ba, de a F6 utc:{lban összesen csak 25 ház van, akko r is menni fog 400 lépést, men 6 ezt nem tudja. Po ntosabban csak akarna menni 400 lépést, mert szinte biztos, hogy még mielőtt odllérne, beleesik valami gödörbe, vagy elüti egy busz. Szóva l ha egy mód van rá, lcgyünk óvatOSlIk, hogy hova küldjük a szerencsétlent.
Kerftésoszlop hibák Az egyik leggyakoribb programozási hiba az, amikor pontosan egy elemmel ínLnk túl egy tömböt. Ami azt illeti , annyira gyakori, hogy külön neve is van: ezt nevezzük kerítésoszlop hibának. A név egy az emberi gondolkodásmód nucsaságát kihaszná ló talá165 kérdésból ered. Ha megkérdezünk embereket, hány kerítésoszlop kell egy tíz méter hosszú kerítéshez, ha méterenként egy oszlopot akarunk leásni, akkor a legtöbben azt fogják válaszolni hogy tíz. Pedig valójában tizenegy, hiszen a végére is kell egy zár6 (lásd a 1;.2. ábrát).
15. óra·
15.2. ábra Hány osz/op I..'CII egy tíz méter lIosszrí kerlléshez? ..
Ez az eggyel való túlszaladásos probléma rengeteg programozó életét keseríti meg. Id5vel aztán megszokja az ember, hogya 25 elemű tömb utolsó eleme a huszonnegyedik, men a számozás nullát6l indul, de addig elköveti néhányszor II kerítésoszlopos butaságot. CA C++ programoz6k egyébként gyakran nem értik, miért nincs az épülctekbel1 nulladik szint. Olyannyira nem, hogy egyesek tényleg a négyes gombot nyomják mcg a liftben, ha az ötödikre akarnak menni. Ezen aztán a többiek jól szoktak sz6mkozni.)
Tömbök előkészítése Ha egy tömb elemei egyszeru beépített típusok (például egészek vagy kaT"J.kterek), :ik-
kor a deklarfici6val egy időben inidaliz..i lhatjuk is. Ehhez nem kell egyebet tenni, mint a neve után egy egyenl6ségjelet írni, majd kapcsos zárójelek között fe lsorolni az elemek énékét. L-',ssunk egy példát: int IntegerArrey[51 = ( 10 , 20 , 30 . 4 0 , 50 J ; Esetünkben az IntegerArray öt egész számot tanalmaz. Az I ntegerArray [O J elembe tőnénetesen 10 kerül, az IntegerArray [1) -be 20, és így tovább. Ha a deklar:'id6ban nem adjuk meg a tömb méretét, akkor II fordítóprogram akkora helyet fog lal le neki, amennyiben az inidalizációs értékek elférnek. Ha tehát azt í~uk hogy i nt Integerl\rray l ] = ( 10, 20, 30, 40 , 50 ); az eredmény pontosan ugyanaz lesz, mim az imént, amikor explicit módon megadtuk, hogy öt elemb61 álljon a tömb. Ha kíváncsiak vagyunk egy tömb méretére, meghatározhatjuk azt a már ismert sizeo f () függvény segítségévcl a következ6 módon: cons t int Integerl\rrayLength ~ sizeofClntege rAr rayl Is izeo f ClntegerArray[O } l ; Példánkba az I nt egerArrayLengt h változó értéke a tömb hosszának, és az els6 elem hosszának hányadosa lesz, ami értelemszerűen nem más, mim a töm elemszáma.
269
I
170 IV. rész' Haladó aszközök Ha a deklarucióban megadjuk a tőmb méretét, akko r az inicializáció során nem ad h a~ tunk meg ennél több elemet. Így például a következő deklaráci6m fordítói hibát fogu nk kapni, hiszen hat elemet próbálunk elhelyezni egy öt elemből álló tömbben: int IntcgcrArray[S] '" { 10, 20, 30,
40,
50,
60};
Ugyamikkor kevesebb elemel adhatunk meg az inicializációban, vagyis a következő forma helyes: int IntegerArray[Sl = ( 10, 20);
Objektumtömbök B:'lrmely objektumot - legyen az bc{:pítetllÍpus vagy a felh:lszná l6 álta l definiált - tárolhan,mk tömbben. Amikor tömböt deklarálunk, tulajdon képpen csa k két információt adunk meg a fordít6progf
Apropó
Az alapértelezett konstruktor
Itt szeretnénk emlékeztetni arra, hogy az alapértelmezett konstruktort nem föltétlen a fordftóprogram hOlla létre automatikusan. létre jöhet [gy is, de megfrhatja maga a fejlesztő is. A lényege mindössze annyi, hogy nem lehetnek b emenő paraméterei. Egy tömbben tárolt objektum valamely adanagjához val6 bozzá(l':rl':s mindenképpen kétlépésl foglal magában. Először is meg kell adunk a r l operátorml, hogy az objektl.lmtömb h:'inyadik eleméről van szó, ntán pedig a tagoperátor (.) segílségével hozz{\férhetünk a kérdéses adattaghoz. A 15.2. Listában beml.ltatjuk, hogyan hozhatunk létre egy öt CAT objektu mból álló tömböt.
15.2. Lista - Objektumtömb létrehozása (objarray.cpp)
o:
II 15 .2 . Lista - Ob jektumok tömbje
1:
#include
,, ,,
3: class CAT
5: 6: 1: 8: 9:
public : CAT() ( itsAge = l; itsWeight =5; ) II Az alapértelmezett konstruktor ~CAT() {} II A destruktor int GetAge{) const I re t urn itsAge; } int GetWeight() const { return itsWeight;
15. 6ra· Tömbök 1271 1 0 , vo id SctAgc{in t agc)
{ it s Ag e = age;
}
ll :
12 : private : 13 , i nt i tsAge ; 1 4: int itsWcight ; 15 :
};
16 : 17 : int mainO
18 , 19 : 20 : " 1: 22 : 23 : 24 : 25 : 26 : 27 : 28 :
CAT Littcr[5] ; II Létrehozunk egy 5 obj ek twnból .1116 tömböt int l ; f or (i :: O; i < 5 ; i ++) Litterr i l . Set Ag e(2 ~ i +1) ; f or (i:: O; i < 5 ; i ++ ) s td:: C':Q u t « · eat ~ . « i +1« . : « Li t t er r i ) . GetAge () « s t d ,; e nd l ; r e tu rn O;
oat U : l c ot 3 cat '3 : 5 cat H : 7 9 cat
'2 :
'5 :
A CAT osztály d eklarációja a 3· 15. sorokban látható. Ahhoz, hogy CAT lípuSlI o bje ktumokat tömbben lehessen tárolni a CAT osztálynak mindenképpen rendelkeznie kell a lapérteJezelt konslruklorl".ll. Ennek a deklarációja látható a 6. sorba n. Ne fclcj lsí.ik cl , hogy ha hozunk létre bármilyen más konstruktort, akkor a fordít6progr::un állal biztosítoct alapértelmezett konstruktor nem jön létre, vagyis azt is nekünk kell megírnunk. A 19. sorba n lefogla ljuk a megfelelő nagyságú mem6rüít egy o lyan tömbnek, amely 5 CAT típusú objektum tárolására képes. Ennek a tömbne k Litt er lesz a neve. Az első fo r ciklus (2 1. és 22. sorok) beállítja az öt CAT objektum korát. /\ másod ik ciklus (24-26. sarok) újra egyenként végigmegy a tömbdemeken , és valamen nyinek meghívja a GetAge ( ) metódusát Lálható, hogy ez ut6bbi esetben mindig e lóbb a Litter [ il konstrukciót haszná ljuk, amivel kike ressük a megfe l elő sorszámú tömbelemet, majd ezt követi a pont (.) operátor, amivel a lagfüggvényhez férü nk hozzá .
I
272 IV. rész· Halad6 eszközök
Többdimenziós tömbök Egy tömb tulajdonképpen nem egyéb, mint adatsor. Adatokat azonban nem C!iak sorba
lehel rakni. Elképzelhetjük például őket rácsba szervezve, ahol vannak soruk és oszlopok is. Ezt a felépítményt nevezzük kétdimenziós tömbnek. Az els6 dimenzió felel meg a soroknak, a második az oszlopoknak. Innen persze már csak egy lépés a három dimenzió, vagyis a kocka. Itt a kiterjedések a széles.ségnek, magasságnak és a hosszúságmIk felelnek meg. Mi több, akár háromnál több dimenziós tömbökkcl is dolgozhatunk, ha éppe n erre van szükség. Ezeket persze már sokkal nehezebb elképzeln i, vagy valós, térbeli objektumokkal kapcsolatba hozni, de néha tényleg hasznosak. Amikor deklarálunk egy tömböt, minden megadott index egy-egy tömbdimcnzi6nak fc lcl mcg. A kétdimenziós tömböknek tehát két futó indexe van, a h{lromdimcmd6soknak három és így tovibb . A C++-ban egy tömbnek gyakorlatilag lctszO"legcsen sok dimenziój" Jehel, de l megoldandó fejadatok lermészetéb61 adódóan a legtöbbször egyvagy kétdimenziós tömböket használunk. A kétdimenzi6s tömb használltára kiváló péld,l a sakktábl:!. A tömb egyik kiterjedése a nyolc sornak, a másik a nyolc oszlo pnak felel meg (lásd a 15.3. ábrát).
."
15.3 ábra A sakkIábia és a kéIClimellziós 16mb megfeleltetese
Tegyük fe l, hogy van egy SQUARE (mezŐ) nevű osztályunk. A sakktáblának megfelelő tömb deklarációja ezzel a következőképpen nézhet ki: SQUARE Board [8) [8) ;
l5.óraUgyanezt az adatmenyiséget természetesen tárolhatnánk cm' 64 menziós lömhben is a következőképpen:
elemből
áll6 egydi-
SQUARE Board[64l
Ugyanakkor ez a megoldás nem áll szerkezetileg olyan közel a valós, leírni kívánt objekturnhoz, mint az előző. A játék kezdetén a király az első sor negyedik mezőjén áll. Figyelembe véve, hogy a sz.í lllozás most is nullától indul, ez a pozíció a következő képpen írható le: Board{O]
[3) ;
A fenti kód persze tartalmaz még egy fe ltételezést is, nevezetesen hogya sorokat az els6 index reprezentálja. A helyes indexelést és a mezők helyzetét szemlélteti a 15.3. ábra.
Figyeleml
A tömbök helyfoglalásár61...
A többdimenziós tömbök félelmetes gyorsasággal képesek elfoglalni a teljes memóriát. Ne felejtsük el, hogy a szükséges tárhely számításánál az indexértékek itt szorzódnakl
Többdimenziós tömbök inicializálása Az egydimenziós tömbök höz hasonlóan természetesen a többdimenziós tömbök deklarációjában is megadhatunk kezdóértékeket. Ilyenkor az elemeket úgy kell felso rol ni, hogya tömb utolsó indexe változ.zon a leggyorsabban, vagyis miközben az utolsó növekszik, addig !lZ összes többi legyen állandó. Nézzünk egy példát! Legyen egy tömbünk, melynek deklarációja a következő: int theArray[Sll31
Ha ebben a dckl:iráci6ban kezd6értékckct adunk meg, akkor az első három a theArray [O) indexlÍ sorba keru I, a második három a theArray [1) sorba és így tovább. Nézzü k például a
következő
deklarációt:
int theArray[5l[3 ] = { 1, 2 , 3 , 4, 5 , 6 , 7 , 8 , 9 ,1 0 ,1 1,12,13 , 14 , 15
Ha áuáthat6bban szeretnénk leírni az inicializációs részt, akár azt is megtehetjük , hogy kapcsos zár6jelekkel csoportosítjuk a felsorolás elemeir valahogy így:
k i egészítő
int thcArray[5] [3] .. ( {1,2 , 3} ,
{4,5 , 6}, {7 , a , 9},
{lO,11.12}, {13,14 , 15} );
I
274 IV. rész • Haladó 'szKözök A fordítóprogram automatikusan figyelmen kívül fogja hagyni II bels6 kapcsos zárójeleket, vagyis ezek csupán a programozó számára teszik olvashatóbbá a kódot, hiszen világosabbá teszik, hogy melyik érték melyik tömbelembe kerül. Az inicializáló é rtékek felsorolásában minden egyes számOl vessz6vel kell elválasztani a szomszédaitól, mégpedig függetlenül att61, hogy alkalmaztunk-e bels6 7.1ir6jelezést. Ezen kívül II teljes inicializációs szakaszt kapcsos zárójelek közé kell zárni , a deklaráciÓl pedig szokás szerint pontosvessz6 zárja. A 15.3. listában egy kétdimenziós tömböt hozunk létre . Ennek az els6 dimenziója II O-4 indexeket, míg II második II 0-1 értékeket veheti fel. Az alapértékeit úgy állítjuk be, hDb'Y minden l -es indexű elem a O-s indexíi
kétszerese legyen.
o: /I l.lstl ng 15.3 - Creati ng A Mul t idime ns i ona1 Ar ray 1 : *inc l ude
,,
3 : int mai n ! ) 4:
(
5 , int SomcArray [5 1 [21 .,. ( (O , O), {t , 21, (2 ,4 ), (J , 6), {4, 8}} ; 6 , for (int l = O; 1<5 ; i+ + ) 7 : for (int jeD ; j<2 ; j ++) 8, ( 9 , std : , eout « "SomeArray[" « i « ° l ( 0 « j « o ) ,o; 10 : ~td :: eout « SomcArray[il [jl« std : : end1 ; 11 , J 12 : return O; 13 :
SomeArray[OI [Oj : SomeArray[O) [l) , Somehrray [ ll [Ol : SomeArray [ ll [ll ' SomeArray [2 ) {O l : Some Array[ 2 1 (l ): Some Array (3 1 [0 1 , Some Array {3 ] [l ]: Somc Ar ray [ 4] [O ], SomeArray[ 4] [l ]:
O O 1
, 2 4
3 6
4 8
Az 5. sorban szerep l ő deklaráció szerinl SomeAr r a y egy kétdime nziós tömb. Az első kite rjedése öt , a második kél elem hosszúságú. Ezr rehát egy 5x2-es rácsnak megfelelő adalSzerkezel (lásd a 15.4. ábrát).
15. 6ra • Tömbök 275
, 2
,
3
o
2
3
o some Array [5[ [2[
15.4. ábra L;iV' 6tszór J..'Clles rács megfelelóje a mell/óriálxIII
A tömb elemeit páronkt:nt inicializ;íljLlk, bár ami azt illeti , akár ki is számíUlatnánk őket egy m egfelel ő k6drészlettel. A 6. és 7. sorban két egymásba :'igy:l zott ciklLlS kezd6clik. A külső ciklus az első, míg :1 bels6 a második tö mbindexen halad végig, ,1111int az :1 ki· írásból is lálható, hiszen SomeArray [ Ol [Ol-át SomeArray [O J [l J követi. Az első tömbindex aktuális értékér természetesen csak akkor inkrementálja a progrdm, amikor a második leheL
Néhány szó a memóriahasználatról Amikor deklarálunk egy tömböt, pontOsan megmondjLlk a fordítónak, hogy hány cIc· met szcretnénk benne tárolni. A fordítóprogram ennek htttÍls{]ra lefoglalja a szükséges memóri atenlletet, amiL akkor is lefogla lva tan, ha a tömb egyes részeir soha nem is használjuk. Ez a stratégia nem jele nt különösebb problémáI abba n az esetben, ha előre meg lehet mondani, hogy a tömbnek legfeljebb hány eleme kell legyen. Egy sakktábla például mindig pontosan 64 mezőből áll , dc az is sejthető, hogy egy macskának (CAT objektum) eb'Yszerre körülbelül 1-10 kölyke lehet. Vannak ugyanakkor esctek, amikor fogalmun k sincs, hogya program futása sor{m hány adon típusú elemre lesz majd szükségünk. Ilyenkor kifinomultabb adaLo;Zt;'rkezctckct kell haszn:\ lnunk. Ebben a könyvben éppen ezért szó esik még a mutat6tömbökről, a dinamiklIs memóriában lefoglalt te rül etek tömbkém való használatáról, illetve néhány egyéb adattá rolási szerkezctr61 is. A 19. órában például az úgynevezett láncolt listákróllesz majd szó. Ugyanakkor a még e nnél is kifinomultabb adaLo;zerkezctck, amelyek kifejezetlen nagy mennyiségIl adat hatékony tárolására használllatÓk már túlmutatnak e könyv keretein. Errol a témáról szólnak azok a bizonyos ,.Algoritmusok és adatszerkezetek" címú tankönyvek, amelyekb61 számos programozó tanul a különböz6 cgyelcmi és főiskolai kurzusokon. A programm..ásban amúgy az a szép, hogy soha nem lehet a végére érni. Mindig van még mil tanulni , íb'Y mindig van még másik könyv is, amil el kellene olvasni. Az út végtelen.
I
27S IV.
resz • Ha~d6 eszközök
Mutatótömbök Az eddig tárgyalt tömbök közös vonása, hogy valamennyien a vermen (stack) kapnak
helyet. Mivel azonban a veremteriilet a rendszerek többségénél meglehetósen korlátos, a nagyobb tömböket cé l szerű a dinamikus memóriában létrehozni . Lehetőségünk van például arra, hogy a tömbbe szervezett objektumokat ténylegesen :t dinamikus memóriában hozzuk létre, magában a tömbben pedig csak egy-egy őket dr11i:6 mutat6t t:íroljunk. Ez OL módszer önmag{tlxlll drasztikusan csökkenti él verm~n tárult adatmennyiségeL A 15.4. Listában átírrl.lk a 15.2. Listába n bemutatott kódot llgy, hogy ell a kombinált módszert használja. Mivel így sokkal kevésbé kell aggódunk altól , hogy elfogy a rendelkezésre álló mem6ria, a tömb méretét S-r61 500-ra emeltük és álneveztünk Litter·r61 Family·re.
15,4. Lista - Tömbelemek tárolása a dinamikus memóriában (arravonheap.cpp) 0, /I 15.4 . Lista Objektumok eimét tároló tömb l: .include 2, 3 , class CAT 4:
(
S : public : 6 : CAT() ( itsl'lge '" l: i tsWeight =5 : ) II alapértelmezett konstruktor 7 : ~CA'I'() {} II dest.r uktor 8 , int GctAge{) const ( rct u rn itshge: ) 9 : int GetWeight{) const ( return itsWeight; 10 , void SetAgc(int age) ( itsAge = <.Ige; ) ll : 12 , privata : 13 : int itsl'lge ; 1 4 : int itsWeight; 15 , ); 16: 17 : int main() 18 : 19: CAT • Family[500); 2 0 , int 1; 21 : CAT • !;leat ; 22 : for (i ." O; i < 500; i H) 23 : ( 24 : pCat = new CAT; 25 : pCat->SetAge (2 *i +1) ; 26: Family[i) = pCat ; 27 :
)
28 : 29 : for (1 = O ; i < 500; i+ + ) 30 : std : : cout « "Cat ff' « i +l « " : 31 , « Family[ i]->cctAge() « s td, : end l ; 32 : 33 : for (i = O; i < 500 ; i ++ )
34 : {
15. 6ra • Tömbök 35 : delet e Family [i l ; NULL ; 36 : Family[i ] 37 : ) ~
]8 :
39 , return 0 , 40,
menet Cat fl, Cat c.t
l
'2 : 53 '3 :
Cat M499 : 997 Cat ~500 : 999
A CAT osztályt a 3-15. sorokban deklaráljuk. Ez a form .. amClgy mindenben megegyezik
a 15.3. Listában bemutatott:!!. Ugyanakkor ez alkalommal a 19. sorban talá lható tömb neve m!lf Family, és a dekl:lrációja szerint 500 darab CAT típlJSÚ objektumot dmz6 mulató! képes tá rolni. A nyitó ciklusban C22-27. sarok) 500 új CAT objektumot hozunk létre, amelyek a dinamikus memóriában kapnak helyet. Valamennyi új ohjektumban beállírjuk az életkort is, mégpedig a sorszám kétszeresl:nél eggyel nagyobb értékre. Az els6 CAT objektumnál tehát az é rték 1 lesz (a sorszám nulla!), a másodikná13, a harmadiknál 5 és így tovább. A dklusmag Ulols6 tömbt:le mbe n.
műveletével
az objektu mot
címző
mulatót elhelyezzük a
megfele l ő
Figyeljük meg tehát, hogy ebben az esetben nem maga az objeknnn (eAT) kerül bele a tömbbe, hanem csa k egy azt címző mutat6, ami a tömb deklarációja alapján pcrsze nem is lehet másként. A második ciklus (29·3 1. sorok) kiírja az egyes eltároh é nékeker. Ehhez ebben az esetben kétlépésrc van szükség. El őször kivesszük a megfelelő objektu m dmét a tömbból (Family lil), majd ezen kereszCi.iJ meghívjuk a Ge tAge () tagfüggvé nyt, ami visszaadja a kívánt é rté ket. Példánkb-.1O a Family tömb és n abban tárolt valamennyi mutat6 a vermen kap heIyel. Ugyanakkor az 500 darab CAT objektum a dinamikus memóriában van. Ennek a módszernek :~ verem tehermentesítésén fÚl megvan az az el őnye is, hogy megfelelő progmmszervezéssel csak annyi memóriát kclllefoglalnunk a CAT objekollnok számárn, nmennyit ténylegesen használunk is. Igaz ugyan, hogya fenti k6dban nem ez történik, hanem egyszeruen egy dklussallétrehozunk 500 ilyen obje ktumot, de a dklusváltozó felső határát akár a fe lhasználótól is bekérhellük volna. Bizonyos esetekben cz nyilván sokkal gazdaságosabb módszer, mint eleve létrehozni adott számú objektu mot.
278 1 IV. rész • Haladó eszközök Végezetül egy harmadik ciklusban töröljük a memóriából mind az 500 CAT objektumot, a Family tömb elemeit pedig null értékre 5l1íljuk
Dinamikus memóriában tárolt tömbök deklarálása Arra is
lehetőségünk
van, hogy a teljes tömböt a dinamikus memóriában [ároljuk, ne csak a bCl1ne elhelyezett objektumokat. Ehhez a new operátorL kell használnunk de úgy, hogy aZI kombinMjuk a tömbindex megadásával. Ennek a műveletn ek az eredménye egy mulató lesz, amely :17.1 ól memóriateriilelet címzi, ahova ti [őmb került. Lássunk egy példát: CAT *Family = new CAT15001;
Ez a deklaráció azt mondja, hogy Family egy muLatÓ, amely egy 500 darab Cl\T típusü objektumot t'iro16 tömböt dmez. Másként fogalmazva Family nem egyéb, mint a Family[OJ elem dmI:!.
,I
Ennek megoldásnak az egyik nagy előnye az, hogy ettől kezdve Inumtóaritmetikát is használhatunk a tömbelemek dmzésére. Lássunk erre is egy példát: C1\T "Family = new ClIT(5001 ; CAT *pCaL = Family; II pCat a Family[OI e l emre mutat pCat->Setllge(lO ); II F«mily[O ] érLékét lD-re á ll it juk pCat++; II advance to Family [l ] pCat->SetAge(20); /I Pamily(ll értékp.t 20-ra állit juk A femi els6 művelet létrehoz egy 500 CAT típusú demet tároló tömböt a dinamikus memóriáb:m és egy mutatÓl, amely ennek a tömnek az cls6 elemét címzi. A harmadik sorban ezen a mut:1tón (pontosabba n a másolatán) keresztül hívjuk meg az első ilyen CA'l' objektum SetAge () nevű tagfüggvényét, és heállítjuk vele a 1Q-es értéket. A mlllalól ezután inkrementáljuk, melynek hal.ís{ira immár a következő CAT objektumot címzi. EZl ismét a már látott módon használjuk, vagyis megint meghívjuk az aktuálisan címzett objektum Set1\ge () tagfü~'Vényét, de immár a 20 értéket adjuk neki bemenelkénl.
Tömb mutatója és mutatók tömbje Vi:t.Sgflljuk meg a következ6 három deklarációt: 1 : Cat FamilyOne[500l 2: CAT • FamilyTwo[SOO]; 3: CIIT • Family'l'hree = new CAT[500] ; FarnilyOne egy 500 CAT objektumot tároló tömb. FamilyTwo 500 olyan mutatót lárol, amelyek CA'r típusú objt!ktumokat címeznek. FamilyThree maga egyetlen mulató egy olyan tömbre, amely 500 CAT objektumol tárol.
15.6ra • Tömbök A három de klaráció közti eltérések drámaian befolyásolják a három dolog múködését. Ami talán még e nnél is meglepőbb az az, hogy flÚg FamilyThree rulajdonképpen FamilyOne valamiféle variá nsának is felfogható, addig FamilyTwo·tól gyökeresen különbözik. Mindezzel el is jutottunk ahhoz a kérdéshez, hogy miben is egyezik illetve tér cl egymástól egy tömb és egy mutató. FamilyThree egy mutató, amely egy tömböt címez, vagyis tanalma nem más, mint e tömb e l ső elemének a címe. A FamilyOne esetében szó szerint ugyanez a helyzet, vagyis maga a tömbnév nem c!''Yéb, mint a tömböt címz6 mutató.
Mutatók és tömbnevek C++-ban a tömb neve ncm egyéb, mint egy konstans mutató, amely,) tömb els6 elemét d mz!. N(:zzük a következ6 deklnrációl: CA'r Family l S0 1 ;
lu Family ncm egyéb, mim egy mmaló, mégpedig egy olyan mm:Hó, amelynek t:utalmaz az a cím, amit a &Family 101 mllvelettel is megkaphatn5nk. Kicsit egyszenlbben : a tömb nevét leírva ml::~kapjuk a tömb c1s6 elemének címét. A C++-b:m teljesen elfogadott, ha egy tömbnevet állandó nllltatókénr használunk , vagy épp fordítv:l. A Family + 4 forma tehát ebben a nyelvben ugyanazt jelent i, mint a Family r41, ugyanúgy hivatkozhatunk vele bárhol a tömb ötödik demére. Ha egy mulalÓ értékéhez hozz:'iadunk egy számoL, inkrememáljuk, v:tgy dekrementáljuk aZl, az ehhez szükséges Imlvelete a fordít6progra m végzi el. EZl azért fon tos hangsúlyozni, mert a Family + 4 forma természetesen nem egy olyan dmet jelent, ami 4 bájtnyira van a Family tömb e1ejét61. Ha például minden, a tömbben tárolL objekmm 4 bájt hosszlIságú, a kkor a Family + 4 cím 16 bájtnyirn lesz a tömb kezdetét61. Ha viszont a Family tömb CAT típusú elemeket tárol, és minde n CA'!' 20 bájtnyi tárhelyet igényel, :lkkor Family ~ 4 jelentése egy a tömb elejét6180 bájt távolságra nmtaeó CÍm lesz. A 1S.5. Lista e~y o lY'1n k6clor mutat be, amelyben a dinamikus me móriában hozunk lélre egy tömböt.
15.5. Lista - Tömb létrehozása 8 new operátorral (oewarrav.cpp)
o:
II 15.5 Lista - TOmb a dinamikus memóriában 1 : Hinclude 2, 3 : cla.ss CAT 4: {
5 : public :
1279
280 l iV. rész · Halad6 eszközök 6: CA'!'() { itsAge = 1; itsWeight=5; } 1/ alapértelmezett konstruktor 7: ~CAT() ; /1 destruktor S : int GetAge() const ( retllrn itsAge ; ) 9 : int GetWeight() const { return itsWeight; 10 : void SetAge(int age) 11 : 12 : private : 13 : int itsAg-e :
{ itsAge '" age ;
}
14 : int itsWeight ; 15 : J ; 16 : 17 : CAT : : ~CAT() 18 : ( 19 : /1 std " eout«
"Destructor called!\n' ;
20 : ) 21: 22 23 24 25
: int main() : : Cl\T * Fl.lmily .. new CAT [ 500j ; : int i;
26 : CAT • pCat ; 27 : for (i '" O; i < 500 ; iH) 28 : {
29 : pCat = new CAT ; 30 : pCat->SatAqe(2 * ]
+1};
31 : Fumily[ll ,. ' pCat ;
32 : delete pCat; 33 : l 34 : 35 : for (i .. O; i < 500; iH) 36 : std :: cout « 'Cat ." « 1+1 «
': •
37 : «Family[ij.GetAge() « s t d :: endl ;
38 : 39 : deletc [I Family; 40 : 41: return O; 42 :
Kimenet Cat U: 1
Cot 1/2 : 3 Cot B: 5 Cat "499 : 997 COL 8500: 999
A 24. sorban deklaráljuk a F'amily tömböt, amely 500 CAT típusú objektumot képes tárolni. A new CAT[SOOI utasítás hatására ebben az esetben az egész tömb a dinamikus memóriába kerül.
Apropó
Egy mGszaki részle!...
Technikai értelemben a 24. sorban tulajdonképpen egy a dinamikus memóriában le· vő névtelen tömböt deklarálunk. A Family mufafóba csupán a tömb első elemé· nek d me ke rűl. Ennek ellenére általiIban azonosnak teki ntj űk ezt a mutatót magával a tömbbel, ami azért nem helytelen, mert maga a C++ fordító is ~így gondolkodik". Amikor létrehozunk egy tömböt -legyen az akár a vermen, akár a dinamikus memóriában - annak a neve tulajdonképpen nem más, mint egy mutató, amely az elsó elemét clmzi. A tömbbe kerül6 CAT objektumokat szintén a dinamikus memóriában hozzuk létre (29. sor). Figyel jük meg ugy,makkor, hogy I:!bbl:!n az I:!setben nem az új objektumok mUlat6i kerülnek a tömbbe, hanem m:Lguk az objl:!ktumok. Ez a tömb tehát nem mUlalótömb, amelynek ekmci Cl\T típusú objektumokat címeznek, hanem CA'I' objektumok tömbje. Áltah'iban a [ l operátor( helyeltesíthetjük a * operátorra l Gndirekdó) is. A Family [3 J tehát cbben a helyzeten ugyanazt jelenti, mint a * (Family + 3) forma.
Dinamikus memóriában létrehozott tömbök törlése A Family név tehát nem egyéb, mit egy mutató, amely egy a dinamikus memóri!ib:u, talál ható, CAT objektumokat tarta lmazó tömb els6 elemét címzi. Amikor a 15.5. Lista 31. sorában használjuk a pCat mlllatót, akkor az garantáltan egy II dinamikus memóriában létrehozou CAT objektl.lmra mOIat, ami most bekerül a megfelelő tömbelembe. ( Eddig ugye semmi meglepö nincs 3 dologban, elvégre pont arról van szó, hogyan kell a dinamikus memórifib:m objektumtömböket kezelni .) Csakhogy a pCat mutatót ti ciklus következ6 iterációjában is feihasználjlIk. Nem jár ez azzal a veszéllyel hogy az el6zó CIIT objeknllnra már nem mutat semmi, és soha többé nem fildjuk felszabadítani az {t1t:da lefoglalt memóriát? Ez valóban nagy gond lehetne, de valójában nem kell t61e tartanunk. A fordítóprogram elég okos ahhoz, hogyemlékezzen az össze dinamikusan kezelt objektumra, így biztosak lehetünk benne, hogy az objektumtömb valamennyi eleme törölhető, és programunk vissL1kapja az általuk lefogla lt memóriát. Hogy megbizonyosoclhasSlmk err61, módosítsuk egy kicsit a 155. Listában bemutatott kódot. Először is változtassuk meg a tömb méretét 500-r61 IQ-rc a 24., 27. és 35. sorokban. Aztán vegyük ki a megjegyzésjelct a 19. sorban láthat6 cout utasítás elől. Amikor a program futása a 39. sorhoz érkezik, a tömb már nem létezik, a rendszer meghívta az abban tárolt valamennyi CAT ohjektum deslruktorál. Amikor a dinamikus memóriában létrehozunk. egy objektumot a new operátorral, azt a delet e utasítással lehet onnan törölni, amit az elem neve követ. Ehhez teljesen ha-
282 l iV. rész • Halad6 eszközök son!óan ha a new [l utasítással hozunk létre egy objekmmtömböt, akkor azt a delete [J utasítással lehet törölni. Az üres :z.'lr6jelpár jelzi a fordítónak aZl, hogy in egy egész tömb törlésé ről van szó. Ha véletlenül kifdejtjük a zárójeleket, akkor a mcrvelet csak az objektumtömb első elemét fogja törölni. EZl szintén beláthatjuk ~ kísér1elileg~ is, ha a 15.5. Lista 39. sorábó] eltávolítjuk a megje~,'yzésjeleL Ha ezzel párhuzamosan átszerkesztjük a 21. sort is úgy, hogya destruktor a képernyőn jelezze, ha lefut, akkor láthat6, hogy csak egy ilyen jelzés jelenik meg, vagyis csak cb'Y CAT objektum tÖrl6dÖtt. Sz6val gr..ltulálunk! Épp most sikerültl étrchozni cgy mcmór;akczclési hibát (memory leak).
Helyes
Helytelen
Ne felejtsük e l, hogy egy n elemű tö mb elemei O-lól n-J-ig sz5moz6dnak. Tö mbö k indexelését csa k olyan mutatókon keresztül végezzük, amelyek valóban az adott típusú tömbre Ilmtatnak.
Ne próbáljunk egy tömb vége után írni, vagy onnan olvasni. Ne keve rjük össze a tömböt dmző muta tót a mlllat6 tÖmbbe l.
Karaktertömbök A kar.lktcrl:'inc ne m egyéb, mint karakterek sorozata. Eddig C.'i upán névvcl nem rcndelkező karakterláncokkll ltalálkoztunk, általában a eout utasítással kapc.'>Ohnhan. íme egy példa e rre: cout «
"hel lo world. \n";
C++-ban a kal"oIkterlánc tulajdo nképpen egy karakte rtö mb, amelynek végét egy null karakte r jelzi. (A null karakter jelö lésére - megkülönböztetend6 a NULL értékU mutatótól- a '\0' használ:1tos.) Ennek megfelel6en egy kal"oIktcrláncot deklar.'ilhatunk és ini· dalizá lhntu nk is ugyanúgy, mint egy tömböt. Helyes tehát a kövctez6 forma: char Grccting [l .. { ' H', ' e ', ' W' , ' o ', ' r' ,' l ' , ' d ', ' \0 ' } ;
'l' ,
'l ',
'o ' ,
'
A sort záró i \0 ' karakter az a bizonyos null kllrakter, ami alapján a C++ függvényei meg tudják mo ndnni, ho gy hol van a karakterlánc vége. Bár a fe nti módszer működő képes, sem kényelmesnek, sem biztonságosnak nem nevezhet6. Sz{unos helyen el le· het gépelni, arTÓI nem is beszélve, hogy egy szöveget betfinként, vesszők kel elválaszt· va begépelni inkább gépír6i bravúr, mint értelmes mcgoldás. Éppen ezért a C++ biztosít egy sokkal kézenfekvőbb lehetőséget a karakterláncok megadás."ira : char Greeting[) "
"Hello World" ;
15, 6ra • Tömbök 1283
Ezzel a szintaxissal kapcsolatban két dologra szeretnénk fölhívni a figyelmet: • Az előző módszt:rlől eltérően itt nem egyszeres, hanem keltős idézőjeleket kell használni, a vessz6k és a kapcsos zár6jelek pedig nem kellenek. • Nem kell külön kiírnunk a záró null karaktert, az II fordítóprogram automatikusan beilleszti a karakterlánc végére. A "Hello World n karaktedánc 11 bctűb61 áll , tehát 12 bájt hosszúságú. A Hello 5 b{ljtot tesz ki, II sz6köz egyet, a World újabb ÖlÖl, a záró null kardkter pedig egy bájlOS.
Deklarálhatunk inicia1i7..áci6 nélkül is kar:.lktertömböket. Ezeket szokás puffereknek is nevezni. Mini ffiindtm lömbnél, természetesen ill is ügyelnünk kell arra , hogy nem a karjuk több karaktert elhelyezni egy ilyen lárban, mint amekkora a mérete. Az inici:llizáció nélkü ! dck!arálL karaktertömbük használatára mU lat példát a 15.6. Lista.
15.6, Usta - Egy karaktertömb feltöltése (arrayfilled.cpp) O; /1 15 . 6 . Lista KaraktertOmbOk pufferkánt való használata l ; linclude
"
3; 4, 5; 6: 7: 8: 9: 10 :
i n t main () { char buffer[801 ; std : : cout « "Enter the string ; ' : std : : cin » buffer : std : : cout « " Here ' s the buffer , • « return O; }
buffer «
std : : endl;
Kimenet Enter the string : Hello Wo r l d Here ' s t he buffer : Hello
Az S. sorban létrehozunk egy puffert, amely 80 karAkte rt képes tárolni . Ez azt jelenti,
hogy ebben a tömbben egy legfeljebb 79 bettls szövegel helyezhetünk el, hiszen kell egy bájt a lezár6 null karaktcrnek is. A 6. sorban bekérunk a felhasznál6tól cgy szövegel, amit a 7. sorban el is helyezünk imént kialakított pufferben. A cin automatikusan elhelyezi a karakterlánc végét jelz6 null karakte r, ezzel tehát nem kell tör6dnOnk.
37..
A 15.6. Listában bemutatott kóddal két probléma is van. El6ször is nem tudjuk, hogy mit fog gépelni a felhasználó. Ha 79 karakternél többet ad meg, akkor a puffer túlcsor-
284 1IV. rész' Haladó aszközök dul, hiszen a c in nem érzékeli a végél. A másik probléma, hogy ha a felhasználó szóközt is gépel, akkor a cin azt fogja a bemenet végének tekinteni , és a maradékot be sem írja a pufferbe.
Ezeknek a problémáknak a megoldásra kicsit át kell alakítanunk a programot: a cin egy speciális metódusát, a get () -et kell használnunk. A c i n . get () három paramétcn vár bemt!nelké nl: •
A kitöltcnd6 puffer nevét
• •
A beolvasni kívánt karakterek legnagyobb számát Azt a jck:t, amit a bemenet végének kell tekinteni
Az alapértelmezett "bemenet vége jel az újsor. A módosított kód a 15.7. Listában látható. 15.1. Lista - Karaktertömb feltöltése (második változat) larrayfilled2.cpp) o : 111 5 .7. L i!l t ól A ci n . get (} met6dus ha szná léltv. l : #i nc l udc 2. 3 : int ma in() 4: 5: 6: 7: 8: 9:
I char buffer[80] : std : : cout « " Enter the st r ing : "; std :: cin . get!buffer, 79) : II get up to 79 or newlinú /ltd : : cout « "Here ' s the buffer : • « buffer « std : : endl ; return O;
10 :
menet Enter the sLri ng : Hello World Here ' s the buffer : Hello Wo r l d
A ci n osztá ly ge t () rnetódusát a 7. sorban hívjuk meg. Ennek az első argu rnenmma az a puffer, amit az 5. sorban deklarálrnnk. A második bemenő paraméter a bekérhet6 karakterek maximális száma . Esetünkben ez 79, mivel a pufferben így még é ppen elfér a záró null karakter is. A bemenet végét jelző karakte r megadására itt most nincs szükségünk, mivel az alapértelmezett újsor erre a célra éppen megfelel. A cin-ről és annak mindenféle különlegességér61 majd a 21. órában sz6lunk részletesebben.
Az strcpyO és az stmcpyO függvények A C++ örökölte a C nyelvtől a karakterláncok kezelésére szolgáló függvényeket. A számos lehetséges mO"velet közül iti csak kettőt szeretnénk kiemelni, amelyek karakterlán-
lS.6racok másolásra szolgálnak. Az c&'Yik az strcp () fijg&'Vény, a másik az s t rncpy ( ) . Az strcpy () működése viszonylag egyszenT: veszi a megadott kamkLerláncot, és a teljes tartaImát átmásolja a szintén megadott pufferbe. Használatát a 15.8. Lsta szemlélteti.
15.8. Lista - Az strcpyll függvény használ... (..ingstrepy.epp)
o:
1/ 15 . 8 . Lista - Az s trcpy() fo.ggvény használata l : #include 2 : #inc l ude
)
.
4: int main ()
5: { 6. chnr Stringll] = "No man is an island"; 7: char String2lBOJ ;
,.
9 . strcpy (String2 , Stri ngl) ; 10: « ll : s td : :cout « · string1 :
12: std : : cout « l): return O; 14:
"String2 :
«
Stringl « Stri.ng2 «
std :: endl ; std: : endl ;
JGmanot Stringl : No man iy an island String2 : No man is a n island
A 2. sorban beemeljOk a STRING . H fejlécállományt, amely tartalmazza az strcpy () függvény prototípusát. Az strcpy () bemenetként két karakte rtö mböt vár, melyek közü l az első il másolás c;(:lja, és a második a forrlIs. Ügyeljünk amI is, hogy ha a forrns
több adatot tartalmaz, mint nmennyi a célpufferben elfér, akkor az strcpy () en túlírja azt.
egyszerű
Ennek a lchel,>éges hibának a kiküszöbölésére a szabványos könyvtár tartalmaz egy strncpy () nevű változatot is, amelynek a másolni kívánt kamkte rck maximális számlIt is mcg lehet adni. A fü ggvé ny az első null karakterig másol, vagy nmíg el nem é ri ezt az e l ő re rögzíLeu számot. Az strncpy () függvé ny használa tM szemlélteti a 15.9. Lista .
15.9. Lista - Az strncpyO függvény ha""lata (usingstrncpy.epp)
o: II 15 . 9 . Lista Az strncpy() fo.ggvény használata 1 : öinc1ude 2 : *lnclude ) .
4 : int main() 5: {
6 : const int MaxLength : 80; 7 : char Stringl [] : "No man is an island";
I
286 IV. rész • H~ad6 eszkÖ2Ök 8 , char String2[MaxLength+l1; 9.
10 : strncpy(String2,Stringl,MaxLength); ll: 12 : 13 : 14 :
String2[strlen(Stringll] std : : cout «'Stringl : s t d :: cout« 'String2 : return O;
' \0'; II add a null to the end String! « std : :endl; «String2« std : : endl;
<<:
15 :
Stringl : No IMn is an island
String2 : No man is an island
A 10. sorban az strcpy () -l lecseréltlik egy s t rncpy () -rc, amelynek ezért t!gy h,moadik parnmételt is meg kelJ adnunk: a másolni kívánt karakterek maximális számát. A String2 nevt1 puffert MaxLength .. ! hosszúság(mak dekl:trálruk. Az a bizonyos plusz egy hely lermészelest!n megint a záró null karaklemck kell.
Karakterlánc-osztályok Az ANSI/ISO szabványnak 1ll<:!gfelcl6 C++ fordít6programokhoz minden esetben tarlO-
zik egy szabványos könyvt5r, amely rengeteg adatkezelés! és adattárolási osztályt tartalmaz. Ennek kötelez6 eleme egy String nevű osztály is, amely nevének megfelel6en a karakter!áncok kezc::lésérc szolgál. A C++ ugyan örökölte a C nyelvt61 a null karakterrel 7Jirt karakterláncokat, illerve azokat a könyvtári függvényeket , amelyek ezek kezelésére szolgálnak, ám al. objektumorientált keretrendszerbe ezeket nem emelte át. Ehelyeu a String osztály az, amely az objektumközpontú szemléletnek megfelelően elrejti magukat a tárolt adatokm a felhasználó e161, és csak a megfelelő met6dusokon keresztül engedi kezelni azokat. Ha az olvasó nem azt a fordítóprogramot használja, am it ehhez a könyvhöz melléke\tek, akkor megeshet, hogy az általa használt rendszer nem tartalmazza a String osztályt. Mi több az is megeshet, hogy megtalálja ugyan, de az nem a szabványoknak megfelelően működik , így kénytelen újm megírni. Afféle szolgáltatási minimumkém a String osztálynak képesnek kell lennie áthidaini a karakterláncok tömb je ll egéből adódó problémákat. Ilyen probléma például hogy a karakterláncok, mint minden tömb statikusak: meg kell mondanunk a méretükel. Aztán ha megmondruk, akkor azt a helyet mindenképpen lefoglalják a memóriában, akkor is, ha nem Ls használjuk. Ha pedig túlírunk egy tömb végén, az eredmény kamsztr6fa.
15. óra • Tömbök 287 Nos, ezek azok a dolgok, amiket egy jól megírt. String osztály biztosan kiküszöböl: csak a ténylegesen szükséges tárhelyet fogalja le, azt viszont mindig, akármekkora karakterláncot akarunk is létrehozni. Ha pedig egyszeruen nem lehet lefoglalni a szükséges memóriát, az osztály jelzi ezl a felhaszmil6nak.
Kérdések és válaszok Kérdés: Mi törten/k, Ita be/cirok egy 25. elemel egy amIigy 24 elemi_I tömbbel Válasz: Igazából nem lehet megmondani. Annyi biztos, hogy olyan mem6riatcriiletre írunk, ami nem a tömbhöz tartozik, az eredm(:ny pedig katasztrófa is lehet. Kérdés: Mil nevezül1k il/icializólat/al1 1ö/llbe/emnek? Válasz: Ez egy tömbnek egy olyan eleme, amely még soha nem kapmt értékel. ilyenkor is van benne persze vallmi , de azt nem lehet megmondani, hogy mi. Ami éppen aZon a mem6riateri.iJeten VOll, amelyen a tömb helyet kapott, az lesz az inicializálás d6u az elemekben. Ha tehát kezdeti érték megadása nélkül kezdünk használni egy tömbclemet, annak beláthatatlan következményei lehelnek.
Kérdés: wllet tómhóket kombillállli, vagyis egymásba ágyaz/li öket? l 'á lasz: Igen. A közönséges tömböket az 6ket címző mutatók segítségévellehel összeegy új, nagyobb tömbbé. Kardkterláncok esetében az összefűzésre akár kö nyvliíri függvé nyeket is használhatunk. Ilyen például az strcat (). fűzni
Gyakorlatok Eltöltöttünk tehát egy teljes órát azzal, hogya tömbök kel ismerkedjünk. ItI aZ ideje, hOb'Y tudásunk ellen6rzése és gyakorlatba való átemelése végett megválaszoljunk néhá ny kérdést és megoldju nk néhány feladatot.
Kvfz 1. Mekkora a legkisebb és legnagyobb használható tömbindex egy adott tömbnél? 2. Mi történik, ha egy tömbben több ad,ttot próbálunk elhelyezni, mint amennyi a deklarációja szerint elfér benne, vagyis a maximálisnál nagyobb indexe t használunk? 3. Milyen el őnyökkel jár a tömbök használata? 4. Honnan szerezhetünk több információt a C++ String osztályával kapcsolatban?
288
[V,
rész • Haladó eszközök
Feladatok 1. Módosítsuk úgy a 15.4. Listában bemutatott kódot (arrayonheap ,cpp), hogy nem kelljen benne használnunk a pCat muuuót. 2. Módosítsuk úgy a 15.4. Listában bemutatott kódot (arrayonheap . cpp), hogy minden olyan helyen, ahol fölbukkan benne az 500 mint konstans, on használjuk az ugyanerre az értékre beállított MAXSIZE állandót. Próbáljuk ügy is lefunalni a programot, hogy csökkenljük ezt a számot. Mi történik, ha eltávolítjuk a const ku1cssz6t? A C++-han a tömbök lefoglalása nem dinamilmsan történik! 3. Mi történne, ha a 2. gyakorlatban MAXSIZE értékeként 200-at adnánk meg, de nem írnánk át 3i'. 500-as érték minden egyes eloforclulását MAXSIZE:-ra? -1 . MódosÍlsu k a 15.9. Listában bemutatott kódot (usi ngstrncpy . cpp) úgy, hog)' a 10. sorban a MaxLength értéke 5 legyen. rordítsuk Je és futtassuk a programol, majd hasonlítsuk össze a kimenetét az eredeliével. Ez a változtatás kiválóan mutatja az strncpy () függvény igazi erősségét: képes ti teljes kamkterláncnál kevesebbet is másolni.
Válaszok a kvízkérdésekre 1. Vtlllmenny; tö mb indexeléS(! nullával kezd5clik. Az utolsó elem (v:lgyis az utol-
só használható index) é rtéke mindig eggyel kisebb, mint a tömb elemszáma. Ha tehát egy tö mb dekhlrációjában az [50J szerepel, akkor az utolsó használható index a 49. 2. Nehéz megmondani. Ha szerencsénk van, akkor pont egy o lya ll memóriaterületre fogunk rászaladni, amit az operációs rendszer ellenőri z, így azonn:ll valamilyen hib:llizenetet k:lpunk. Ha nem, akkor megvá lrozhat például egy másik változÓ értéke, ami rendszcrinr nehezen felderíthet6 hibához vezet. Én egyszer egy teljes szombmomat eltöltöttem azzal, hogy egy ilyen hibát keresgéltem, és még csak nem is a saját k6dom volt.. 3. A tömbök használatának legfobb c10nye az, hogy általuk egyetlen néven keresztül érhetjük el logikailag összetartozó adatok cgy egész h:ll mazát. 4. Az első hely, amit érdemes megnézni tlZ általunk ll:l.~ znált fordítóprog ram súgója. Ha a könyvhöz mellékelt Borland fordít6l használjuk, kattintsunk a Help menüpontra, majd válasszu k onnan a Help Torks pontot. Krmints unk a Reference opci6ra, majd keressük ki a pontot. (Ügyeljünk r5, hogy ez ut6bbi nem azonos a ponttal, am; a C szabványos kö nyvtárában található, :l k:lrakterláncokkal kapcsolatos dolgokat írja le.).
v. RÉSZ ..
Oröklődés 16. 17. 18. 19.
óra óra óra óra
és többalakúság
Az öröklödés A többalakúság és a származtatott osztályok A többalakúság kifinomult használata láncolt listák
16. Az
ÓRA
öröklődés
Ebben az órában a következ6kr611esz szó: • Mi az az örökl6dés • Hogyan származIassunk egy osztályt egy másikból • Hogyan lehet elérni az alapmetódusokat a származtatott osztályokból • Hogyan !ehet felOlírni az alapmct6dusokat • Mi az a védett elérés, és hogyan lehet használni
Mi az az öröklődés? Az emberi értelem :dapvcl6 igénye, hogy a kü l ö nböző foga lmak közt kapcsolatokat keressen, felismerjen és létrehozzon, Különböző hierarchikus, táblázatos, hM6zatszcn1 é,~ egyéb intellektuális mcx.lcllckct építünk fel, hOh'Y megértsük, hogyan működnek egyült a dolgok. A C++ nyelv az örökl6dési hierarchiában próbálja mindezt lTlegr::tgadni. Mi az a kutya? Amikor kedvenc háziállatunkra nézünk, mi jut eszünkbe? Az állalolVos különféle szervek összehangolt há16zatállátja, a fizikus különböző atomok és erők köl· csÖnhatás.1r
292 V. rész • Örökl6dés és többalakúság Bennünket most a biológus nézőpontja érdekel; az Ő szempontjából egy kutyafélével van dolgunk, tágabb énelemben egyemlóssel, ami e.gy állat. A biológus felosztja az élőv ilágm, és a maga szempontjai szerint minden élől ényt besorol az ország, [Ör7$, osztály, rend, nemzeL<;ég, nem, faj és alfaj valamelyikébe. A biológus által felállítoLL hierarchiának minden kapcsolata ~részl.'!' jeJlega, A kutya emlősá llat, Látjuk a háttérben a része relációt: A Toyota az egyfajta gépkocsi, amely egy jánnú. A Tibi csoki egyfajta édesség, amely élelmiszer. Mit is értünk az alatt, hogy "valami egyfajta másvalami»? Azt, hogy annak a másvalarnim:k a speciális esete. Azaz a gépkocsi a járm űvek egy sped{ili.<; esete. A gépkocsik is, az autóbuszok is j:í rmúvek. Van valami specia litásuk, amely gépkocsivá vagy autóbusszá teszi őket, de mindkét típust egyformán megilleti a .já rmű" besorolás. Osztoznak ebben a jellemzőjükhen.
Öröklődés és származtatás A imtya örökli (azaz automatikusa n megkapja) az eml6sáll atok valam<:!nnyi nllajdonságát. Emlősálbt VOIt.,1 [lál fogva képes a hclyváltoztató mozg{lsra és a l e v egő beléJegzésére - minthogy minden e m16sá lbt definíció szerint képes ezekre . A kutya speeia litása mtg HZ ugatás, farokcsóválás stb., úgyhogy ezek II tulajdonságok még hozz:'ijönnek az előző ddlníci6hoz. A nkutyas.1g» specializált jellemz5, míg az nem16sség" által:mos,ln igaz minden eml6sállatra. A kuty{lkat kisebb esoporlokra is feJoszth:lljuk, például vaclászkulyákra és terrie rekre stb.; a terriereket is csopon osítl1atjuk: vannak Yorkshire Terrierek, Dandie Dinmont T<:!rnerek stb. A Yorkshire Terri<:!r egyfajta terrier, így egyfajta klllya is, emiatt e ml ősá llat is, azaz állal, és ily m6don é l ől é ny . A hierarchiát a 16. 1 ábra mutatja.
16.1 ábra Az álla/ok hir:m,., hiája
16. óra • Az A CH úgy pr6bál meg efféle hierarchiákat leképezni , hogy az egyik osztá lyt a má.<;ik~ ból származtatja. A származtatás j61 kifejezi a . része~ reláci6t. Az emlos osztályból s7.ár-
mazhat az új kutya osztály. Ekkor már nem kell kifejezetlen megmondani , hogy a ku~ tyák tudnak mozogni, hiszen ezt a tulajdons{lgukat már az emlősöktrn öröklik Mivel a kutya-t az eml os-b61 származtattuk, automatikusan képes a mozgásra. Azt mondjuk, hogy ha egy oszt'ii.ly új jellemzőket ad egy másik osztály tulajdonságaihoz, akkor azt cbb6l származt:lttuk. Az eredeti osztályt hívjuk az új osztály bázisosztályának, vagyalaposztályának. Ha a kutya osztály az emlo~-b61 szá rmazik, akkor az emlos a kutya osztály bázisosztálya. A származtatott osztályok az eredeti bá:.::isosztálynál g:lzdagabb struktúrát alkornak Ahogy a kutya fogalma is gazdagabb az eml6sállaténál, úgy a kutya Q.'iztály is g'l:.::dagabb néhány mel6dussal vagy adauagga! az emlos osztálynál. A bázisoszt:í.lyoknak álraláb:m van néhány származtatott os:.::t:'i lyuk. Ahogya kutyák, macskák és lovak különböz6 fajtll em l ősá ll mok, a nekik mcgfclcltetell osztály származhat az emlos osztálybóL
Modellezzük az
öröklődést
,
az Allatok Országával
Ebben a részben különmle állatosztályokat képzelünk el a7. örökl6dés és :L származtatás tárgyalás.1hoz. Képzeljük el, hogy egy játék teTVezése a feladatunk - ~gy állatfannOl szeretnénk szimulál ni gycrmekek számám. EI6ször mindenfC:le állatot képzelünk el, lovlIkal, teheneket, kutyál, macskM. birkál stb. Különböz6 met6dusokat is tervezünk a sz.1mukr-.l, hogy úgy viselkedh~ssenek, ahogy ilZ a gyerekek kép7.dctében él, de ill most csak egy kiírásr.:t csupaszílSllk le ezek L'll1almát. Egy függvény lecsllpaszítása vagyelnagyolása (sLllbbing) azt jelenti, hogy csak annyit íru nk meg be161e, hogy Játsszon , hogy meg lett hívva - a r{!szletek megírását pedig későbbre hagyjuk. Évekig gyártonam JecsupllszítQ[[ függvényt!ket, várva a dicsőséges napra, amikor több időm lesz rájuk. Íme az egyik el6nye II felülről való tervezésneka munka a legfelső szinten kezdhető, és fokozatosan egyre mélyebbre ha1:Ldva folytatható. Munka közben nem kell egyből kidolgozni a problém:l minden részl<:! tétj a "csonkok" jó térkitölt6ként működ nek. Ez egyben azt is jelenti, hogy akár mások is befoltozhatják a lyukakat, kidoJgozhalják a részletekct, amíg az e l ső progmmozó már más részekkel foglalkozik. Ha valakinek több ideje van, nyugodtan kibővítheti ennek a fejezetnek minimális kóclrészleteit, hogy még élethűbben viselkedjenek legyenek az állatok.
293
294
. rész • Örökl6dés
A származtatás szintaxisa Egy osztály deklaciciójakor megadható. hogy melyik másik osztályból származzon. Az osztály saját nevét kövct() kettőspont után következik a származtatás Lípusa (például public) és a bázisosztály neve. Egyelőre csak a public (nyilvá nos) tírust fogjuk használni. Íme egy péld,,: CldSS Dog : public Mammal
A származtatás tipU5.'Ír3 még visszatérünk ebben a fejezetben. A bázisosztálynak már deklarálva kell lennie, kü lönben fordítási hibát kapunk. A 16.1 Lista bemutatja, hogy hogyan lehel a Dog (lmlya) osztályt a Mammal (emlős) osztályból szárma;;:talni. 16.1 Usta - Az agyszerü
öröklődés
(simpleinherit.cpp)
0 , 1/16.1 Lista II Egyszerű OrOkl6dés 1, #include 2,
3 , enum BREEO { YORKIE, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAS J; .... II Fajt6k 4, S: class Mammal 6, ( 7: public: 8 : II Konstruktorok 9 : Mammal(); 10: -Mammal() ; ll: 12 : II Hozzáfér6 rOggvények 13 : int Geti\ge () const; 14: void SetAge(inl); 15: int G€'!tWeight() const; 16: void SetWeight();
17: 18: II Más tagfo.ggvények 19: void Spe",k{); 20 : void Sleep{);
21 : 22 : 23: protected : 24 : int itsAgc; 25 : int itsweight;
26 , } ; 27 : 28 : class Dog
public Hammal
29 : 30 , public : 31 : /1 Konstruktorok 32 , Dog() ;
33 : -Dog(); 34 :
16. óra • Az örökl6dés 295
35 : II Hozzáfér6 függvények; breed=fajta 36 , BREED GetB reed() const ; 37 : void SetBreed(BREED) ; 38, 39, II Más tagfüggvények 40, /1 WagTail(); /I ParokcsÓvó.1ás 41 , /1 BegPorPood(); II KajáértKuncsorgás 42 : 43 : protceted , 44 : BREED itsBreed; 45 : );
46 : int main() 47: ( 48: return O:
49:
A programnak nincs kimencIc, mert itt csak az osztá lyok deklarnciói láthatók a mcgva· 16síl:is nélkül. Ennek ellenére van mil né:mi rajta, sót még le is fordu l.
5-26. osztályb:Ln dt!khuflljuk a Hamma1 osztályt. Figyeljük meg, hogy a Mammal osztá lynak nincs b:ízisosztálya, nem szármilztattllk semm iből. A val6 világban ez r1cm így van, :IZ »emI6sök" az "á llatok" egy fa jtája. C++ programjainkban az eredeti gondolat· körnek csak e~,.y elvonalkoztatását képezzük le. A v••lós,íg túlságosan részletgazdag ah· hoz, hogy egy progr.r.m mindent megval6sítson. A C++ programokban ábrázolt hierar· ehia a meglév6 adatoknak csak egy haszn.Hja. A jó tClVczés ismélVc, hogy oly m6don képezi le kérdéses területeket, hogy ,IZ ~sszen1en, hitelesen megfeleltethct6 a val6ság adott szeleténck AZ
A hierarchiának valahol el keH kezd6clnic j ebben a progt:lmban ez ti Mamma1 oszt{i[y. E döntts miatt lehetséges, hogy lesznek olYlm tagváltozók, melyeket egy magasabb szintG osztályban kellene deklarálni, mi mégis ilt tesszük meg. Pl!ldául bizonyára min· den állatnak van kom és súlya; így, ha az AnimaI (állat) osztályból szármaZOlI II Hammal osztály, akkor sl."imíthalnánk arra, hogy ezek a tulajdonságok onnan fognak örökl6dni. Jelen esetben azonban ezek a jellemzők csak a Mamma1 osztá lyban jelennek meg. A program ésszerű keretek közön tartása érdekében csak hal metódus kelÜll a Mamrnal osztályba - négy hozzáfér6 függvény , valamint a Speak () (beszélj) és a Sleep () (al/telj).
A Dog osztálya Manuna1·ból szánnazik, ahogy azt a 28. sor mutatja. Minden Dog objeknLmnak h,írom tagváltozója lesz, i tsAge, itsWeight és itsBreed (/...'Ora, sú{).'a ,fajlája). Figyeljük meg, hogy a Dog osztály-deklarációban nem szerepel az i t sAge és az itsWeight, mert ezeket a Marnmal osztályból örökli az összes többi metódussal együtt, kivéve a konstruktort, a másoló konstruktort és a destruktort.
296 1V. rész • Örö~ódés és többalakúság
Privát vagy védett? Talán felfigyelt rá az olvasó, hogya 16. 1 Lista 23. és 43. sorában egy új kulcsszó került e16, a protected. Korábban az osztály saját tagváltoz6il a private, azaz privát kulcsszóval adtuk meg. Az ilyen adanagok azonban nem elérhetőek a származtatott osztá Jyokban. Nyilvánossá (public) leheme lenni a szübéges adaltagokat (it sAge és itsWeight), de igazából nem ez a célunk; nem szeretnénk, ha más osztályokból ezeket közvel1enül ellehctne érni. 4
Mi cgy olyan megjelölésre vágyu nk, amely arra utal, hogy ezen osztály tagok legyenek elérhetőck ebben az osztályban és ennek származtatott osztályaiban. Erre szolgál a protected (v{'(lefl). A védett adattagok és függvények Caz aclott osztályon kívül) csak a szármn.talott osztályokbar, látszanak, a többiek s:.:::ím:ír:l láthatatlanok, mim a privát változ6k. 1\ védett vához6k elérhet6ségc tehát valahogy fé lúton van a privát és a nyilvános kö-
zÖu. Kevésbé szigorú a védelme, mint a privfité, de azért ne m annyira laza, mim a r'yilvános. Ezzel megismenük mindhúrom létez6 hozzMérés-szabályozó módosít6sz6t: nyilvános, védett és privát. I-la egy függvényhen példányosítunk egy osztályt, akkor a függvény ennek az osztálynak csak a nyilvános t.1gvállozóit és tagfOggvényeit mdja elérni. Egy oszuíly saját tagrüggvényei azonhan ezeken kívül a privát tagválloz6knt és tagfüggvényeket is Mtjúk. igy :1 Dog: : WagTail () függvény e léri az itsBreed privát adatait, valamint a Hammal osztály védett adatait is. J la netán más osztá lyokat is dekl,uá lnánk a Hanunal és a Dog közé (példáu l olyat, hogy DomesticAnimal, luíziálll/t), akkor is elérné a Dog osztálya Hammal védett adatait, feltéve, hogy ezen köztes osztályok is mind nyilv{mos ö rökl6dést h:lsználnak (mim a Dog).
1\ 16.2 List.1 szemlélti:!Li, hogy hogyan lehet Dog típusú objektumokallétrehozni, és
a hozzá tartozó adatokat és függvénye ke t használni.
16.2 lista - Egy származtatott osztály Iderivedobjectcpp)
o:
/1 16.2 Lista /1 ogy származtatott oszt3ly használata 1: *include
,.
3 : enum BREED { YORKIE, CAIRN , DANDIE. SHETLAND, DOBERMAN, 4. 5 : class Mammal
,.
7 : public :
L~B
};
B: II Konstruktorok 9 : Mammalj) : itsAge(2), ilsWeight(51 {} 10 : -Manunal()(} 11 : 12 : II Hozzáfér6 függvények 13 : int GetAge()const { return itsAgc ; } 14 : void SetAge(int age) { itsAge = age: 15 : int GetWeight{) const { return itsWcight: 16 : void SetWcight(int weight) { i t sWeight = weight: 17 : lB : II Más tagfüggvények 19 : void Speak{)const ( std : : cout « "Manunal sound ! \n" ; ) ... II Em16sá11athang o t d : : cout« " shhh . I ' m sleeping . \n " ; 20 : void Slccp() conGt .... II Alszom 21: 22 : 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 : 32 : 33 : 34 :
p r otected: Lnt itsAgc ; i nt itsWeight ; };
class Oog : public Hammal ( public : II Konstruktorok [log j) : itsBreed(YORKIE) {} -Dog () ()
35, II Hozzáfér6 függvények 36 : BREED GetBreed() const { retllrn itsBreed: } 37 : void SetBrccd(BREED breed) ( itsBreed = breed; 3B , 39 : II Más tagfüggvények 40 : void WagTail() ( std :: cout« 'Tai! wagg i ng ... \n ': ) .... II Farokcs6válás 41 : void BegForFood() ( std :: cout« "Segging for f ood . .. \n' ; .... II Kajáé r t Ku n csorgás 42 : 43 : priv atc : 44 , BRE ED itsBreeo ; 45 : l ; 46 : 47 : i m . mai n {)
48 : 49 , 50 : 51 : 52 : 53 : 54 :
DOg fido ; fioo . Speak() : fi d o . WagTail () : std : : cout « 'Fido is " « return O:
fido . Ge t Age {) «
" ye a rs old\n ";
296 1V. rész • Örö~ódés és többalakúság
menet Mammal sound!
TaU wagging .. Fido is 2 years old
Az S-26. sorlxm deklaráljuk a Hamma! osztályt (:1 helytakarékosság kedvéért minclen függvény he lyben kifejtett). A 28-45. sorban deklaráljuk a Dog osztályt, mint a Manunal osztály lcszármazottját. Ily módon nlinden kutyának (Dog) van kora , súlya és faj tája. A 49. sorban létrejön Fido kutya. Minden kutya- (és így eml ős-) ltllajdonsággal rendelkezik Azaz tud pélcl:hil farkat csóválni, .beszélni" és aludni (WagTail, Speak, Sleep) .
Konstruktorok és destruktorok A kutyák em16sök. Ez a lényege a ,részd' reláci6nak. Amikor Fido létrejön, először bázisosztály konstru ktora hívódik meg (az em16söké), majd a kurya oszl:í ly konstruktora fejezi be a kutya létrehozását. Mivel nem adtunk Fido-nak semmilyen paramétert, mindkét esetben az alapértelmezen ko nstruktor hív6doll meg. Fido addig a pillanatig nem létezik, amíg teljesen meg nem "konstruá16dik n , aza z az eml6s része és a kutya része is el nem kf:szül. Mindkét konstruktornak le kell tehát futnia. ti
Amikor Fido befejezi I::lcLpály{lj{ll, el6ször a kutyák a esLruktora hívódik meg, és csa k ezut:ín az em[6sállatok deslrlJktora. Minde n desLruktornak megvan a lehet6sége, hogy takarítson maga után a memóriában. Mi se fe[edkezzüllk el tehát takarítani kuty{lOk után! A 16.3 Lista ezt mutatja be.
16.3 Usta - A konstruktorok és destruktorok hivása (conanddest.cpp)
o:
II 16.3 II KonstruktoroK és destruktorok hívása
1: 2.
.inc1ud~
3 : enum BREED { YORKIE , CArIDl,
DANDIE,
SHETLAND , OOBERMAN,
4. 5 : class MallUnal
6. 7 : public : B: II KonstruktoroK 9 , Marrunal () ; 10 , -Hamma l (l;
ll , 12 , II Hozzáfér6 fOggvények
13 , int GetAge() const { return itsAge; } 14 , void SetAge{int age) ( i tsAge = age; l 15 : int GetWeight() const { r eturn itsWeight ;
LAS l ;
16, 6ra • Az örökl6dés 16: void SetWeight(int weightl
{ itsWeight
weight;
}
17 :
18 : II Más tagfüggvények 19 : 20 : 21 : 22: 23 :
void Speak() const { std::cout« void Sleep() const { std : :cout «
'Manunal sound!\n' ; } 'shhh. I'm sleeping . \n';
protected: int itsAgc; 24 : int itsWeight; 25 : l;
26 :
27 : class Oog : public Manunal 28 : ( 29 : public :
30 : II Konstruktorok 31 : Dog() ;
32 :
-Dog() ;
33 : 34 :
II
Hozzáfé~6
fOggvények
35 : BREED Ge tBreed() const ( return itsBreed ; ) 36 : void SetBrcod{BREED brood) ( itsBreed = breed; 37 : 38 : II Más tagfüggvények 39 : void WagTail() (std::cout« 'TaH wagging ... \n" ; ) 40: void BcgForFood(} { atd :: cout « 'Begging for food .. . \n' ; 41: 42 : private:
43: BREED itsSreed; 44 :
};
45: 46 : HaIl\JTlllI: :HaTMUll() ,
47, itsAge(l), 48 : itsWeight(5} 49, ( 50 : std, , cout «
"Mammal constructor ... \n ';
51 : ) 52 : 53 : Hammal :: ~Mall\JTllll ()
54 : ( : std :: cout« :
55 56 57 58
"Hammal dcstructor ... \n';
: : Dog: : Dog() : 59 : itsBreed(YORKIE)
60 : { 61 : std :: cout« 62 : }
'Oog conSLructor . .. \n" ;
63 : 64 :
Dog : : ~Dog(l
65 : {
66 : std :: cout« 67 : 68 :
}
'Oog destructor . .. \n ';
}
I
300 V. rész • Önl~6dés és többalakúság 69: 70 : 71: 72 :
int main() {
/1 a Kutya születése fido. Speak () ; 73: fldo.WagTail(); Dog fido;
74 : std :: cout« 75: return O, 76 :
"Fido i s · «fido . GetAge() «
• ycars old\n" ;
Maromal constructor ... Dog constructor ... Mummul sound !
Tail wagging ...
Fido is 1 ycars old Dog destructor ... Mununul destructor ...
A program szinte megegyezik lIZ elózóvel, azzal a különbséggel, hogy most konstrukrorok és destn.lktorok kiírnak egy nyomjelz6 (Izenetet. Amikor Fido létrejön , e16ször 3z eml6sök konslrukloid hívódik meg, majd ezek ut1i.n a kutyák/!. Innent61 kezdve már teljes mértékben létezik a kUlya , és tagfüggvényei meghívhat6ak. Amikor
il
Fido hatókörön kívülre k erűl, majd az eml6sök destruktom,
először
a kutyák deslruktora
í~a
ki nyomjelz6 Ozenelét,
Paraméterek átadása a báziskonstruktornak Megv:m arra is a le h etőség, hogy a konstnlktorokat túlterhcljük; az cml6sállatnak például meg szeretnénk határozn i az induló kor.it vah'Y a kutya fajtáját. Hogyan lehet megoldani, hogy ti kor ~s sú ly kezdóértékek a megfelel6 konslruktormlk ad6cljanak át? Mi '\\...~~'-.~'\...~~""lS..\.~~i;:,.~",,~~~~~'ts..\.~~ ...~'~L~~~~~~~~w..
ftdtétlenül?
...-.-
Egy bá:lisosztály inicial izációja úgy is történhet, hogy megadtu" .a I'fm)a. zt\rí:»I:\ol:n ',fL. a'\;::\. , anOS'j >e'L a \.~."""-"._ . '_ _
o:
II 16 ,4 /1 Konstruktorok túlterhelése a 1 : 'include 2,
származ~a
16. 6ra • Az öröij6dés 301 7 , public : 8 : /1 Konstruktorok 9: 10 : ll , 12 : 13: 14 : 15 : 16 :
Hamma l () ; Marnmul(int age); -Mammal();
1/ Hozzáfér6 fuggvények
int GetAgc{) const { ret.urn itsAge ; } void Setllge(int age) { itsAge '" age ; } int GctWeight() const ( ret.urn itsWeight. ;
17 : void Setwelght(lnt wcight)
( itsWcight = weight;
18 : 19 :
20 : 21 : 22 : 23 : 24 : 25 :
II Más ta9fü9gv~nyek void Speak!) const ( std :: cout« voi d Sleep () const. ( std :: cout «
"Hammal sound ! \n "; ) ·shhh. I ' ro sleeping . \n· ;
protected : i n t i e sAga ; int itnWeight; 26 : ); 27 :
28 : class OOg public Hammal 29 : 30 : public : 31 : II Konstruktorok 32: Dog(); 33: Dog(int age); 34 : Dog(Jnt age, int wcight.); 35: Dog{int age, BREEO breed); 36 : Dog(int 4gC, int wcight, BREEO brccd); 37 : -Dog(); ]8 : 39 : /1 Hozzáfér6 függvények 40: BREED GetBrced{) const ( rcturn itsBreed ; ) 41 : void SetBreed(I3RRF.O breed) { itsI3reed = breed; 42 : 43 : II Más tagfOggvényck 44 : void WagTail {} ( .!ltd :: cout « "Tail wagging ... \n" ; ) 45 : void BegForFood{) { std :: cout« " Begging for food ... \n" ; 46 : 47 : private : BRF:RD i taBrm'ld ;
48: 49 :
};
50 : 51 : 52 : 53 : 54 : 55 ,
Hammal: : Marnmal () : itsA.gc{11, itsWeight(5) { std : : cout « "Hammal constructor ... \n" ;
56 :
57 , 58 : 59 , 60 :
Hammal::Mammal(int age} : itsllge(age) , itswcight (5)
30z 1V.,ész • Örö~6dés és többalakúság 61 : 62 : 63 : 64 : 65 : 66 : 67 : 68 : 69 : 70 : 71 :
72 : 73 : 14 : 15 : 16 : 11 : 78 : 79 : 80 : 81 : 82 : 8] : 84, 85: 86, 87, 88 : 89 : 90 : 91 : 92 : 93 : 94 : 95 , 96 : 97: 98 : 99 : 100 , 101 , 102 : 103 : 104 : 105 : 106 : 107 , 108 : 109: 110 : III , 112 : 113 , 114 :
{ std :: cout«
"Mammal{intl constructor .. . \n" ;
Mammal : : -Mammal () ( std : : cout « "Mammal destructor ... \n' ;
Dog : : Dog () : Mamma l () , itsBreed(YORKIEI { std : :cout «
"Dog constructor ... \n";
Dog :: Dog(int age) : Mammal (age), itsBreed (YORKIE) { std : : cout « "OOg (int) constructor .. . \n' ;
Dog : :Dog(int age, int weightl : Mammal(age), itsBreed(YORKIE) { itsWeight :: weight; std : : cout « " Oog (int, int) cons t ructor ... \n' ;
Oog : : oog(int age, int weight, i3REED breed) : Mamma1 (age) , itsBrccd (breed) ( itsWeight weight; std::cout« "Oog(int, int, BREED) constructor ... \n";
Dog : , Dog(int age, BREED breed) : Mammal (age) , itsBreed(breed) { std : , cout « "Dog (int, BREED) constructor . . . \n" ;
Dog , , -Dog() ( std :: cout «
int main () Dog fido ;
"Oog destructor ... \n";
16. óra • f>J. örö~6dés 303 115 : 116 :
Dogrove r (5) ; Dog bustcr{6 , 8) ;
117 :
Dog yorkie (3,YORKIE); (4 ,20, OOBERMAN) :
118 :
Dog dobbie
119 : 120 :
fido.Speak(); rover .WagTail () ;
121 :
std : : cout « 'Yorkie is « yor ki e . GetAge () « • years old\ n': s td: : CQut « "Dobbic wcighs • « dobbi e. GetWeight() « • pou nds \n';
122 : 123 : 124 :
return
125 : 126 :
Apropó
o:
Sormmok akimenetben
Az. elemzés utalásainak megkönnyrtéséhez sorszámokkalláttuk el a kimenetet. Futás közben ezek nem jelennek meg a képernvön.
1 : Mammal constructor ... :2: Dog constructor ... 3 : Mammal (int) constructor ... 4: Dog (int ) constructor ...
5 : Mammal(int) cons t ruc t or .. . 6 : Dog (int , i nt ) constructor .. . 7 : Marruna l (int) constructor . 8: Dog (int, BREED) constructor .... 9: Mammal(int) constructor ... 10: Dog(int, int, BREED) constructor. .. ll : Hammal sound ! 1," : Tail wagging ... 13 : Yorkie is 3 ycars old 14 : Dobbic wc i ghs 20 Dounds 15 : Dog destructor . . . 16 : Hamma! destructor . .. 17 : Dog destructor ... 18 : Hammal destructor ... 19 : Dog destructor . .. 20 : Mammal d estructor. .. 21 : Dog destruct o r .. 22 : Hammal destructor ... 23 : Dog destructor ... 24 : Hammal dcstructor . ..
-
A 16.4 Lista 10. sorában megjelenik egy túlterhelt konstruktor: a Mammal elfogadja paraméterkénl az em16s korát. Az 58-63. sorban találhaló megval6sítás az itsAge tagvá!cQz6t a konstruktornak átadott érték alapján inicializálja, a paraméterrel nem jellemzett itsWeight énékét pedig 5-re állítja.
304 1V. rész • Örö~6dés és többalakúság A Dog számára öt tülterhelt konstnlktor áll rendelkezésre (32-36. sor). Az e lső az a lapénelmezelt változat, a másodiknak az á llat korát adhat juk meg (csak úgy, mint a Mammal esetében). A harmadik ko nstruktor a kort és a súlyt kéri, a negyedig a kon és a fajtát, az ötödik pedig a kort, a súlyt és a fajtát. Figyeljük meg a 71. son , ahol a kutyák alapértelmezett konslruklOr.l az e ml ősök alapértelmezett konstruktorát hívja meg. Bár ez nem lenne kötelez6, ez egyben azt is dokumemálja, bogy szándékosan a paraméter nélküli a lapértelmezett báziskonstmkto rt hívjuk meg. A bázisko nstmktor meghívása egyébként is megtörténne, dc így még nyilvánvalóbban látszik erre vonatkozó szándékunk. A 77-82 . sorban van az a kutyakonstruktor, amely egy paramé tert vár, éspedig az é letkon. A kezdőért~klistában (78-79. sor) a Dog iniciaJizá lja saját bázisosztá lyát, ii tadva neki a kor par.'lmélelt, majd a fa jta inicializálása köve tkezik. A 84-90. sorban találunk egy újabb kutyakonstnlktort, amely két paramételt vár. Megint csak a saját bázisosztály inicializáci6j:lval k ezdőd ik az értékad61ista (a megfele le> konstmktor meghívásáva!), de ezúttal a fajt
std :: cout «
'Dog{int, int) constructor ... \n";
)
mivel :l bázisoszlfi ly saját változóját tilos in inicia1i;cilni. Hasonlóképpen ezt sem írhatjuk le: Dog : : Dog(inL age , int weightl :
Mammal(age, weight), II hibás ! i t sBreed(YORKIE) (
s t d :: cout «
"Dog( i nt . i nt ) con6tructor . . . \n":
mivel a ManunalOSztálynak nincs súlyparamélert elfogadó konstruktora. Ezt az értékadást a Dog konstmktomnak törzsében lehet csak megtenni. Dog : : Dog{int age, int weight) : Hrurunal(age) , II báziskonstruktor itsBreed{YORKIE) II inicializáció (
itsWeight = weight : II értékadás std :: cout« "Dog{int, int) constructoz:: ... \n" : )
16. 6ra • Az örö~6dés 305 Nézzük végig a további konstruktorokat, és győződjünk meg arról, hogy értjük a mllködésüket. Figyeljük meg, mi az, amit inicializálhatunk a kezdőértéklistában, és mit vagyunk kénytelenek a konstruktor tör/..sében megoldani. A kimenet számozása megkönnyíti az elemzést Az első két sorban az alapértelmezeu konstruktor ad hírt magáról: Fido létrehozásáról tanúskodik. A kimenet 3. és 4. surd Rover létrejöttét mutatja; az S-6. sorban Buster szülelik meg, mégpedig az egypar.unéleres Marnmal és a kétparamétercs Dog konslruktor j6voltából. Miután minden állat eI6(tllt, működésük befejezése után hatókörőn kívül kerülnek. Ahogy sorban szűnnek mcg lélezni, minden kutyára először a kutyadestruktor hívódik meg, m;Jjd ezután az cmJősdestruktor; mindez összesen ötször 05-24. sor). Remek példát láttlmk a szánuaztatott osztá lyok alnpmetódusainak tülterhd6s6rc.
Függvények felülbfrálata A kutya objektumok elérik a Hammal bázisoszlály tagfüggvényeit és persze a saját Dog tagfüggvényeiket is, amilyen például a WagTail () (faro!..'CSóválás). Ez utÓbbiak akár felül is írhatják a bázisoSZlály függvényeil, ami azt jelenti, hogya s7.ármaztatOll osztály új megval6sítást írhat II bázisosztály val:lmely függvényére. A származtatott osztályba tartozó objektum ilyenkor a számánl írt függvényt fogja használni. Pontos,lbban megfogalmazva: felülírásnak vagy felülbír-.ílatnak nevezzük azt, amikor egy szármntatotl osztály olyan függvényt valósít meg, melynek mind a neve, mind a paraméter-szignatúrája, mind a visszatérési értéke megegyezik egy bázisosztálybeli 13gfüggvényéveJ. A paraméter-szignatúra, azaz a függvény prolotíp usa többet tartalmaz, mint egy visszatérési érték, mert (a paramétereken kívül) láthat6 belőle a függvény neve és al. esetlegesen használt const kulcsszó is. A paraméter.szignatúra tehát a függvény neve, paraméterei (és azok típusai), esetleg a c:onst kulcsszó, de nem értendő bele a függvény visszatérési értéke. A 16.5 Lista bemutatja, hogyan lehet a kutyák számára felülírni az ernl6söknél általában használt beszédet (Speak). Ezekből az osztály-dekladciókból helytakarékosságb61 kimaradtak a hozzáfér6 függvények.
306 V. rész • Öröld6dés és
1&.5Ii1ta- A - . - , .............ot
~
o:
/1 16 . 5 1/ Báz i sosz tál y tagfuggvény éne k fe l u l í rása .. egy származtatott os ztályba n
1 : #include 2, ] : enum BREED ( YQRKIE, CAIRN, DANDIE, SHETLAND , OOBERMAN, LAS) ; 4. 5 , class M.limnal 6: ( 7 , public :
8 , II Konstruktorok 9 : Hammal () { std :: cout « 'Marrunal constructor ... \0' ; } 10 : -Hamma l () { !:ltd :: cout « "Hamma l destructor ... \n' : }
11 , 12 : II Más tagtüggvények 13 : void Speak()const { std :: cout« 'Ma mma l sQu nd l \n' ; } 14 : void Sleep()conat ( std :: cout« " shhh . I 'm s!eeping . \n ";
15 : 16 : protected : 17 : int itsAge ;
IB : int itsWeight; 19 : l ;
20 : 21 : class Dog public Mammal 22 : 23 : public : 24 : II Kons t ruktorok 25 : Dog()! std :: cou t « 'Oog cons t ructor ... \n';
26 :
~Dog(){
std :: cout«
'Oog des t ructor ... \n' ;
27 : 28 : II Más tagföggvények
29 : void WagTail() ( std : : cout« "Tail wagging .. . \n" ; ) 30 : void BegForFood() ( std :: cout« "Begging (or food . .. \n" ; ) 31 : void Speak()const ( std :: cout« "Woof!\o" ; ) 32 : 33 : pr ivate : 34 : BREED itsBreed ;
35 : } ; 36 : 37 : int main()
38 : 39 : Mammal bigAnirnal ; 40 : Dog fido;
41 : bigAnirnal.Speak.(); 42 : fido. Speak () ; 43 : return O; 44 :
16. óra • Az öröklődés 307
Mammal const ructor .. Mamrna l construct o r .. . Dog const ruc t or ... Mamma l sound! Woof ! Dog desLructo r . . Mam.mal des true tor . Mnmmal dcstructor ...
A 31. sorban a Dog osztályban felülirjuk a Speak () met6dust, aminek a hatásár.!. a kutyák azt fogják mondani, hogy" IVOOp'. A 39. sorban egy bigl'l.nlma l (nagytífla/) ncvű em l6s{J llalot hozunk létre; kOllstruktoriÍnak köszönhet6 a kimenet els6 sora. A 40. sorban jön létre fi do kUlya, emiatt kapjuk a kimenet következ6 két sorát, ~Ihol az eml6sés a kutyakonsu1Jklor ad hírt magáról. A 41. sorban megszó lal a biglulimal, majd a 42. sorban fido kutya is. A kimc netb61 látszik, hogy mindkét állat a saját Speak () met6dusát hívta meg. Végül :1 kt:t objektum hatökö rön kívül kerül, és meghívódnak a destnIktorok.
Túlterhelés vagy felülfrás? e lt ~r6 n tartalmuk. Amiko r túlterhelü nk egy függvényt, akkor ann:tk több viÍltozatál készítjÜk el (tlgyanabban az osztályban) azonos néven, de más-más paraméter-szignatúrftva1. Amikor viszont felülínlnk egy függvényt , akkor a szármaZL.1ton oszlályban ugyano lyan ncvCí és 118)'al1o l)'a/I paraméter-szignatúrájú függvényt deklarálunk, mint mni a báúsosztályban is megvan.
E kél fogalom nem áll messze egymástól, azonba n alapvet6en
A bázisosztály met6dusainak elfedése AZ előz6 progmmba n a Dog osztály Speak () mel6dusa elfedte a b!izisosztály azonos nevű
Jllctódusát. Ez volt a slándékunk, azonban váratlan mellékhatáSOli is le hetnek e Ha az emlősöknek van egy mozgást kifejez6 túlter11elt metódusa, és a kutyák számára ezt felülírjuk , akkor ezzel az összes ilyen nev(i metódusváltozatot felülínuk. lehetőségnek.
Ha a Ma mmal túlterheli a Move () függvényt, és három változatot valósít meg belőle (az egyik paraméter nélküli , a másik egy egész paramétert, a harmadik egy egész és egy irány jcllegCí paramétert vár), és a Dog ezek közül csak a paraméter nélküli változatot írja felül, akkor nem lesz egyszeru a Dog objekrumok számára a másik két Move ( ) változat elé rése. Ezt a 16.6 Listában liÍlhatjuk.
30a l V. rész • Örö~ódés és többalakúság 16.6 lista - Metódusok eHedése (hidingbase.cpp)
o:
1/ 1 6 . 6 II Metódusok el fedése
1.
2 : #include 3. 4: c l ass Hammal 5: ( G: public : 'I : void Hove!) const ( std : : cout «
'Hammal move one step\n' ;
8 : voi d Move{int distance) const 9 : { std :: cout « 'Marnmal move • «
distancc
«~ O
stcp s . \n '; }
10 : protected : 11 : int i tsAgc; 12 : in t itsWeight;
13 : 1 4: 15 : 16 : 17 : 18 :
}; c l ass Dog
public Ma mmal
public : void Move() const { std :: cou t« 'Oog move 5 stcps . \n' ; } 19 : l ; II A jobb forditóprogramok figyelmeztp.tnok , hogy el fogunk fedni .. egy fOggvényt ! 20 : 21 : int main()
22 : ( 23 : Hammal blgAnimal ; 24 : 25 : 26 : 27 : 28 , 29 : 30 :
Dog fido ; bigAn i mal ,Movfl ( ) ; bigAni ma l.Mov e (2) ; fido . Move() ; II fido . Move(lO) ; return O;
Kimenet
Mammal move one stcp MaJnlUal move 2 Gt ops . Dog move 5 s l epl:l .
Be
s
Minden fölösleges adatoLés függvényr cltünlettünk az osztátyokból. A 7. és 8. sorban deklaráljuk a Mammal osztály tú herhelt Move () függvényeit. A 18. sorban a Dog feWlírja a Move () függvény paraméter nélküli változatát. A 25-27. sorban hívjuk meg e függvényeket, melyek végrehajtását a kimenet mutatja. A 28. sor azonban megjegyzésbe van téve, mert fordítá si hib{1l okozna. Ha nem történt volna felülírás, akkor a Dog péJd,lny meghívhatná a Move ( egész) szignatúrájú változatot, de mivel felÜlírtl.lk az eredeti
függvényváltozatok egyikét, így valamennyi változatot újra kell fogalmazni, ha használni akarjuk őke t. Ez emlékeztet arra a s7,ab{t1yra, hogy ha megadun k saját konstruktort, akkor a fordítóprognlm már nem t,ímogalja az alapértelmezett konstruktor hívását. Gyakori hib-d., hogy függvénye k felülírásakor elfelejtjük a const kulcssZÓl, amely pedig a paraméter-szignatúrn része. Igy a paraméter-szignatúra megváltoztatásával cs.lk elfedjük a bázisosztály eredeti függvényét, nem pedig felülírjuk Egyes fordít6programok figyelmeztetnek az elfedésre valahol
;1
15-19. sor környékén.
A Borland fordító nem teszi ezt. Más fordítóprogrdmok úgy ítélik meg, hogy - bár van lehetőség a metódusok elfedésére - cz gyakran tévedésb61 bkad, így inkáhh küldenek egy figyelmeztetést, ha ilyent észlelnek.
A bázismetódus meghfvása lia felül is írtunk egy biízismetódust, megvan rá a lehetőség , hogy m~gis azt hívjuk meg, csak meg kell adni a teljes nevél. Ez a bázisoszt,'ily nevéb6I, kél kett6spontb61, végül a függvény nevél>61 áll, például: Mnmmal: :Move()
A 28. sort ennek megfelel6en átlehetlle fogalmazni, hogy lcfordu ljon a 16.6 Lista : 28 : fido.Mammal::Move(10);
Ez kifejezeuen a Mammal saját metódus:'ít hívja mcg. A 16.7 Lista ezt a lehct6séget nlUtaLja.
, 6.7 Usta - A bázismetódu5 meghívása egy felülirt melódusból (callingbase.cpp)
o: II 16 . 7 II 11 bázismet6dus meghívása a felal!rL met6duGbó l 1 : j inc lude 2: ] : cl ass Marranal 4,
(
5 : public :
6: 7: 8: 9: 10: 11 : 12 ,
void Move () cons t ( std : : cout « void Move(int distance) const ( std : : cout « "Hammal move « d istance « ' steps . \n"; ) protected: int i tsAge; int itsWeight ;
13 :
l;
14: 15: class Dog public Mamma l 16 : 17 : public ! 18: void Move ()const ; 19 : };
'Marrunal move one step\n"; )
I
310 V. rész • Örökl6dés és 1öbbalakús!g 20 , 21: 22: 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 :
void Dog : : Move() const ( std: : cou t « 'In dog move . .. \n' ; Hammal :: Move (3) ; }
int main() Manunlll b igAnimai ; (ido ; bighnima l . Move{2) ;
Dog
32 : fido . Mammal :: Move{6); 33 : return O; 34 :
menet Mammal move 2
step~ .
Mammal move 6 stcps .
Bamzás A 29. sOl'ban létrejön egy bigAnimai (nagyl/ lIat) nevű cml6s{lllal, majd a 30. sorb:11l fido, a kutya. A 31. sorban [[Ilható metód ushívás n e ml6sök Move () függvényét hívja mcg, amely egy egész szám pardmétert vár. A programozó a kutya számára is ilyen egyparaméleres Move () függvényt szeretne meghívni, dc ez akadályba ütközik. A Dog osztály felü lírta az eredeli Move () függvényt, de nem vette a fárad ságot II lúlterhelésrc, és nem fogalmazta meg az egypa raméteres vflltozatot. Ezen úgy segíthetünk, hogy kifejezetten a bázisoSZlály Move () függvényét nevezzük meg a 32. sorban.
H81y81
H8lytelen
Érdemes kiLerjeszteni a bevMt osztá-
Ne essünk abba a hibába , hogy a paraméter-szignatúra megvá ltoztatásával véletlenü l elfeclünk egy bázisfüggvényt.
lyok fl.lnk cionalitását a szá rmntalott osztályokban. A bázisoszlály metóclusa in:lk felülírásával érdemes bizonyos s7..lÍnnaztatotl fiiggvények múködését megváltaztatni.
Kérdések és válaszok Kérdés: Orók/6dncl.!-e több generáción át a származ/atoll osztályok adaltagjai 6függem/ős, és elZ em/ős egy ál/al, akkor örökli-e II kut)'a az állatokra jellemző ada/okai és jiiggvényeket?
t-tllyei? ifa a Mu lya egy
Válasz: Igen. Az öröklődés folyamatában a származlatott osztályok den adatát és függvényél megkapják.
bázjs~ztályuk
min-
Kérdés: Álalakífhal-e etv' származtatott osztály egy nyi/vállos (p/lblic) bázisJliggvényl
priuá'ra? Fá/asz: Igen, de ebben az esetben ez nem fog látszani
il
további származtatott osztá-
lyokbnJl.
Gyakorlatok Az elmúlt 6dban megismerkcdtűnk ;iZ örökl6déssel. Most válaszoljunk meg néhány kérdést és v{:gczzünk cl néhány feladatot tudásunk ellen6rzése és megerosítése végett!
Kvfz l. Mi a lecsupaszítás célja? 2. Miért érdemes egy osztCtlyl szánnaztatva el őállítani? 3. A fejezel programjaiban (1 6.1-16.5) lalálkozhatott az enum ut:lsitássa1. Ez mit csinál? 4. Miért lehet é rdemes elrejteni egy bázisfüggvényt a származtatott osztály e l ől (mint ahogy az a 16.6 listában is történt)?
Feladatok l. A hidingbase.cpp programban 06.6 Lista) vegye ki a megjegyzésből a 28.
SOlt. Mi tÖl1énik? Hogyan lehet mégis múködőképessé te nni a programot? 2. Módosítsa úgy a derivedobject. cpp programot 06.2 Lista), hogy a fajta (breed) nyilvántartásám a felsorolásos változó helyett szöveges változót haszmí ljon. 3. A baseoverride . cpp programban (16.5 Lista) csak az emlősök sz.3.mára áll rendelkezésre a Sleep () függvény. Módosítsa úgy a programot, hogy bigAnimal és f ido is hívja meg ezt a mel6dust.
312 1V. rész • Örö~6dés és többalakúság
Válaszok a kvfzkérdésekre 1. A lecsupaszítás révén pillanatok alalt futtathat6vá tehető egy függvény. Nem kell
a feladat megvalósításának apró részletei re koncentrálni; egy kés6bbi időpont ban is be lehet fejezni a vázlatosan elkí!szült függvényt. 2. Egy osztály származtatása gyakran sokkal egyszenThb, mint a semmiből megírni. Ha már néhány vonatkOzásban jól működik , miél1 kezdenénk elöl ről? 3. Az enum lehet6vé teszi (a mesterkélt és nehezen élthel6 számk6dok helyett) a beszédes szimb61umnevek használat.ál, lrunt például a YORKIE. 4. A sz..~rmaztaton osztály viselkedése i dőnként erősen eltér a bázisosZtályét61, olyannyira, hogy néhány bázisf'Üggvény már haszná lhatatlan. Mivel nem mindig van arra lehetőség, hogya bázisoszrályhoz hozzányúljunk (péld:lul mert nincs is meg a forrása), ezt a megoldásT érdemes haszn{llnL
17.
ÓRA
A többalakúság és a származtatott osztályok Ebben az órában a következőkrlíllesz szó: • • • •
Mik azok a viltu:'Ílis függvények Hogyan használjuk a virtu:'Ílis dcslruktorokat és másoló konstmktorokat Miként teszik leher6vé a virtuális függvények a bázisosztályok többalakúsng:'Ít Milyen költségei és veszélyei vannak a virtuális függvényeknek
Avirtuális függvénye kkel megvalósított többalakúság Az előző fejezet azt hangsúlyozta , hogya kutya típusú objekrum egyben emlős lípusú objektum is. Eleddig cz pusztán .lZl jelentene, hogy a kmya objektum örökölt minden Tulajdonságot (adatot) és képességet (fOggvényr) a bázisosztályát61. A C++-ban azonban a "részt!' reláció ennél méJyebb összefüggést takar.
314
rész · A többalakúság lehetővé teszi, hogy úgy bánjunk egy szá rmaztatott osztállyal, mintha az a bázisosztály objektuma lenne. Tegyiik fel például , hogy létrehozunk néhány specializált eml6stj kutyát, macskát, lovat stb. Mindezek az emlős osZtályból szá rmaznak, így az emlős-tagfüggvények is elérhetőek a sÚmukra. Az egyik ilyen lehet a Speak () . Minden emlősállat ad ki valamilyen hangol. Meg szeretnénk tanítani minden származtatott állatfajt a specializált hangadásra. A kutya ugasson, a macska nyávogjon stb. Minden osztályban felül kell írnunk a Speak () met6dust. Ugyanakkor, ha már van egy sereg állatunk (például egy farmnyi kutya, macska, ló és tchén objektum), akkor szeretnénk farm játt:kunkban tudatni ezekkel az állatokka l, hogy jó vol na , ha beszélnének (SfX'ak), de azzal nem kívánun k foglalkozni, hogy miként is va16sítj:ík meg eZl a képességüket a saját Speak () metódusukkaL Amikor ezeket az állato-kat cgysz(,;!rú(,;!!1 <::1l116söknek (Mammal) tekintj ük, és meghívjuk r!ljuk Marranal. Speak () metódl.ls[, ,Ikkor ezzel a löbbBIBkúságot valósítjuk meg. Angolul ez a "polymorph" tulajdons.'íg; a szó elemzéséve\ is a ~többalakúság" fogalmálloz jutunk. VaI6b,lll , ,IZ e ml ősök kel fog lalkozunk, annak többtele megjelenési formájában.
,I
Lehet például egy eml6s objektl.lmra vonatkozó mutatót deklarálni, majd értékül adni neki egy kutya objektum címét a dinamikus memóriában. Mivel a kutya ~ltSZl!' az emlősök halmazfinak, ez teljesen sL1bályos: Mammal* pMammal = new Dog: Ezzel a mutat6val aztán az eml6sök tctsz61cges lagfüggvénye meghívható. Igazából azt szeretnénk, hogy ha egy függvényt felülírtu nk a kutya osztályában, akkor az hív6djon meg alka lmas pillanatban. A virtuális függvények ezt teszik lehetőv(: . Ha több:!lakúnak tekintjük ezeket az objektumokal, akkor elegendő meghívni az eml6s mutatóm vonatkozó tagfüggvényt, és nem kell a konkrét származtatott objektum mibenlétére koncentcllni (hogy ez mOSt épp egy kutya), vagy hogy miként is van megval6sítva a ha.szn:ílandó függvény. A 17."1 Lista
szem l ~ lteti II
vimlális függvényck által megva l6sítható többalakúságot.
17.1 U... - Virtu6lil flIggrinyek használata (virtmeltlod.cpp, o : 1117 . 1 Lista - Virtuális függvény ek használata l: 8include 2,
3:
,,
class Mammal
S: 6: 7: 8: 9,
public: Mallll\al() : itsAge(l) { std " cout« 'Ma!ll1\ll;l constructor ... \n· ; -Marnmal{) { std:,cout« "Marnmal destructor ... \n'; } void Hove() const ( std: : cout « "Mammal move one step\n" ; virtual void Speak { ) const { std: :cout « "Harnmal speak! \n":
.6ra • 10 : 11: 12 : 13: 14 : 15 : 16 : 17 , 18: 19 : 20 : 21 , 22 : 23: 24 : 25 ,
Dcotected : int itsAge; }; class Dog : public Mamma1 ( public : Dog{) ( std:: cout « "Dog constructor ... \n "; ) -Dog() { std " cout« "Dog destructor ... \n" ; ) void WagTail() {std :: cout« "Wagging Tail ... \n" ; void Speak{)const I std :: cout « "Woof!\n "; } void Move()const { std :: cout« "Dog moves 5 steps ... \n" ; }; int main(}
26 : 27 , 28 , 29 : 30 : 31 :
Mamrna l *pDog .. n ew Dog ; pDog->Move(); pDog->Speak{) ; return O;
Hammal constructor ... Dog Constructor ... Mammal move one step woof!
A 9. sorban az eml6s oszt.ály biztosit egy Speak () nevű vinuális függvényt. Az oszt.ály meglcrvczőjc ezzcl kifejezi azt a sz.'indékát, hogy cz az osztály más osztályok bázisoszlálya legyen. A származulloll osztályokban valószínűleg felül fogják irni ezt a függvényt. A 27. sorbHn létrejön egy eml6sre ulaló mUlll1Ó, a pDog; ehhez egy új kutya objekmm címét rendeljük hozzá. Mivel a kutya eml6s, ez a hozzárendelés helyes. Ezzel a mutalóval hívjuk meg azután a Move () függvényt. A fordítóprogram úgy tudja, hogya pDog egy eml ős mutatója, így az em16söknél ke resi a Move () metódust. A 29. sorban a mutat6val a Speak () függvényre hivatkozunk. Mivel a Speak () vimIális függvény, a kurya osztálya által felülírt Speak () metódus hívódik meg. Ez már-már bűvészmutatvány, legalábbis abban a vonatkozásban, hogya hívó függ vénynek csak egy emlős mutat6ja van, ám ill mégis egy kmya met6dusllud meghívni. Ha egy egész lömbnyi emlős mutatónk lenne, és mindegyik valamilyen emlős -leszár-
315
316 mazOllrd hivatkozna, akkor is mind-mind sorban a neki szánt függvényt hívná meg. A 17.2 Lista ezt mUlalja be.
0 , II 17 . 2 Lista - TObb virtuális függvény egymás utáni meghívása 1: 'include 2, ]:
4, 5: 6: 7; 8: 9: 10 : ll :
12 : l] : 14 : 15: 16:
class Mamma!
public : Mammal() : itsAge(l) -Mammal( ) { ) virtual void Speak() cons t protected : int itsAge;
( sta : : cout «
); c l ass Dog
publ ic Mammal
public: void Speak () const ( std :: cout «
17:
);
18: 19 : 20:
class Cat
21:
{
22: 2]: 24 : 25 : 26 : 27: 28: 29: 30:
public: void Speak()const { std::cout « );
31:
"Mammal speak!\n";
"Woof! \n " ; )
public Hammal
class Horse (
"Meow!\n"; }
public Hammal
public:
void Speak () const ( std :: cout «
'winnie! \n" ; )
J;
32 : ]] : 34 : 35 : 36 :
class Pig
public : void Sp eakO c onst ( f;td :: cout «
37 :
);
public Hamma l
'Oink l \n' ; )
38 : 39 : 40 :
41 : 42 :
int main()
Mammal * theArray(5J ; Mammal* pt r;
43:
int choice, i ;
44 :
for
45 :
{
46 : 47 :
( i
"
O; i <5;
iH )
std : : cout « '(l)dog (2)cat std : : cin » choice ;
(3)horse (4)pig : ' .
és a származtatott 48 , 49 : 50 ,
switch (choice) {
case 1 ,
51 :
ptr '" new Dog :
break : case 2 ,
52 ,
53 ,
ptr ,... new eat ;
54 : 55 ,
break ; case 3 : ptr = new Horse ;
56 :
57 :
58 ,
break : case <1 :
59 : 60 :
ptr :: new Pig :
61 , 62 : 63 , 64 65 66 67 68 69
br eak : default : pLr = new Ma lTunal :
: : : , : :
bre ak: the Array [ i] '" p tr : tor (i-0 ; i<5 ; i .... ) theA r ray f 1 J ->Speak () ;
70 ,
return O:
71 :
Kimenet (1 )dog
(2)cat (31 horse (3)horse (2)cat (3)horse (l)dog (2)cat (3)horse (l)dog (2)cat (3)horse Woof ! (l) dog {1 ) dog
(2)cat
(4)pig : (4)plg : (4)pig : (4)pig : ( 4 ) picjj:
l 2 3
• 5
Meow! Wi nnie ! Oink ! Hamma l Speak
•
S
Ez a végsőkig leegyszerűsített program, amdy minden osztálynál csak a legszerényebb funkcionalitást valósítja meg, a virtuális tagfüggvényeket a lehet6 legtisztább formájukban mutatja be. Négy osztályt deklarálunk: kutyát, macskát, lovat és disznót, mindegyiket az emlósból származtatva. A 8. sorban az eml6s S p eak () függvényét virtuálisnak deklaráljuk. Később a 16, 23, 30. és 36. sorban a származlatott osztályok felülírják a Speak () függvény eredeti megval6sításáL
317
A felhasználót arra szólítja föl a program, hogy válasszon, melyik objektu mot hozzuk létre, majd a kiválasztott mutatók bekeriiInek egy tömbbe a 44-65. sorban.
Apropó
Dinamikus kötés
Igen érdekes az, hogy fordítási idöben még nem lehet tudni, mely objektumokat kell majd létrehozni, és így mely Speak () metódus meghívására lesz szukség. A pt r mutató objeklumhoz kötése csak futási idöben történik meg. Ezt hívják dinamikus kötésnek (vagy futásidejú kötésnek), ami a statikus kötés (vagy fordltásidejú kötés) ellentéte.
Hogyan mílködnek a virtuális függvények Amikor egy származtatott objektum, például egy kutY:1 (oog) létrejön, clóször a b{IZisoszt~íl y konslnlktora, majd a sz~rmaztatot! osztií ly ko nstruktora hívódik mcg. A '17. 1 ábra nwtatja, hogy hogyan épül fel a kutya objeklum II létrehozása un'in. Figyeljük meg, hogya memóriában az eml6s "rész" szá rnám lefoglalt helynek a folytatása a kutya "réSZ" helye.
~
, -----,,,
Em", "" I--_E_m_"_,_-.j Kutya objektum Kutya
17.1 i b", KI/f)vl objektulII (/ létrel/OzáStI utáll
Amikor egy objektumban virtuális függvényt deklarálunk, az objektumnak nyomon kell követnie ennek a függvénynek a később i szá rmazékail. Több fordítóprogram készír e célból egy virtuális függvény táblázatot, úgynevezctl v-táblázatot. Ez minden virtu::llis típu s számára létrejön. Minden ilyen típusú objektum tarta lmaz egy virtuális táblázat mutat6t, v-mulal6t (vptr), amely a virtuális táblázatra mutat Bár a megval6sítások különböz6ek lehelnek, minden fordítóprogramnak meg kell 01dania ezt a feladatot; nem téved nagyot, aki ezt a vázlatot képzeli el. Minden objektum vptr mutatója hivatkozik egy v-táblázalra, amelyben minden virtuális tagfüggvényre egy mUlató UlaI. Amikor a kutya eml6s . resze" U:lrejön, a vptr úgy iniciali7..álódik, hogy a7. eml6s osztály virtuális függvényeire mUlasson 07.2 ábra).
VPTR
12
& Move & Speak
Mamm al
17.2 ábra Egy e/lllős /)-Ió",áztllt/
Amikor egy kutya konstruktor meghív6dik és elkészül az objektum kutya r(:sze is, akkor a vptr ügy korrigál6dik, hogy a megfelelő mutatók a kutya objektum felülírt tagrüggvényeire mlltass:mak (ha vannak ilyenek) , Ezt mutlllja a 17.3 ábra. VPTA
"""""
-
l?
& Mammal: Move () & Dog: Speak ( l
"" 17.3 ábra Egy Imt)'fI v-túbláz(l/t/
Amikor egy emlős mutat6t használunk , II vptr továbbra is a helyes függvényre fog mulami, az objeknun konkrét típusától függ6en. így, amikor meghívjuk a Speak () -et, a helyes függvé ny indul el.
Nem juthatsz innen amoda Ha egy kutyának van olyan metódusa (wagTai l ()), amely nincs meg az emlőS osztályban, akkor azt nem lehet emlős mutat6val elérni (hacsak nem használunk típuskonverziót kutya mutató,d). MinLhogy a wagTail () nem virtuális függvény , és nincs meg az emlős osztály példányaiban, nem é rhető el kutya objektum (vagy leg.tlábbis egy erre való tipuskonverzió) nélkül. Bár át lehet alakítani az emlős mutatót kutya mUL1tóvá, vannak ennél sokkal jobb és biztonságosabb megoldástok a WagTail () elérésére. A CH nem nézi jó szemmel
320
rész • Örö~6dés és ;iZ explicit típuskonverziót, nlivel hibalehetőségeket rejt. A téma részletesen el6keru.. még a 18. órában, amikor a többszörös örökloof:srő1 beszélünk, illetve a sablonokn'
szól624. órában is.
Szeletelés (slicing) Figyelem, a virtuális függvények búvészlllutatványa C5.1 k mutat6kr.t vagy
hivatko~
ra működik! Egy objektum érték szerint való elküldése nem okoz virtuális hívást. A 17.3 Lista ezt mutatja be.
11.3 Ulti - Az adatol< 1...._,"" ódák szerinti plllm6te,6tedú ko, fllicl ..."", 0 , /1 17.3 Lista - AdaLszeletelés az érLék szeri nt va16 ... paraméterátadáskor
l:
~i nclude
2.
3 : clasA Mammsl 4: ( 5 : public : 6 : Hamme1() : itsAge{l) 7 : ~Mammul() ( )
8 : virtuel void Speak () const { std : :cout « 9: protected : 10: int itsAgc; ll :
"Hammal speak! \0";
};
12 : 13 : 14 : 15 : 16 : 17 : 18 :
class Dog public Mammal { public : void Speak()const ( std , : cout «
'Woof!\n'; )
);
19 : class Cat : public Hamma! 20 : 21 , public:
22 : void Speak()const { std :: cout« 23 , } ;
'Meow!\n'; }
24 :
25: void ValueFun c tion (Hamma!) ; 26: v oid PtrFunction (Hammal'); 27: void ReíFunction (Mammal&); 28 :
29 : int ma in() 30 : 31 :
Mammal~
{
34 :
I
ptr=O; 32 : int ehoiee ; 33 : while (1)
35 : bool fQuit = fa!se ; 36 : std :, eout « "(1)d09 (2) eat (O)Qu it: 37 : std : : ein » choiec ; 38 : switch leho i ee)
' ;
a szánnaztatott 39 ; 40 : case O; 41 ; 42 : 43 ; 44 ; 45 ; 46 ; 47: 48 ; 49 ; 50 : 51 ; 52 : 53 : 54 : 55 : 56 : 57 : 58 : 59 : 60 :
fQuit = true;
break; case 1 ; ptr = new Dog; break; case 2 ; ptr = new Cat; break; default ; ptr - new Hammal; break; } i f (fQult) break ; PtrFunction(ptr) ; RefFunction(*ptr) ; ValueFunction (*ptr) ; ) re t urn O;
61: 62 : void ValueFunction (Mammal HammalValue) .. II Ez a függvény hívódik meg utoljára 6] : 64 : MammalValue.Speak{); 65 : } 66 : 67 : void PtrFunction (Hammal " pMarorna.l) 68 : 69 : pMammal->Speak(l; 70: } 71 : 72 : void RefFunction (Mammal & rMammal) 7]: { 74 : rMammal . Speak (); 75 : )
Kimenet (l)dog (2) cat (O)Quit : 1 Woof Woof MilIMlal Speak! (l) dog (2lcat (O)Quit : 2 Meow! Meow! Hammal Speak! (ll dog (2)cat (O)Quit : O
322 1V. rész· Öröklődés és többalakúság
A 3-23. sorban az em lős, kutya és macska oszlályok leeb'Yszerusíleu változatait deklaráljuk. Három függvényt is deklarálunk, PtrFunc t ion (). Re fFun ct i on (), és Va lue Func tion (l nt'!ven. Ezek egy eml6s mutatót, e ml6s hivatkozást illetve eb'Y eml ős objektumot várnak par-améterkénl. Mindhárom függvény ugyanntteszi: meghívja a Speak () metódusl. A fel hasznflló választhat kutya és macska közötti választásának megfelel6en készül el egy mulató a megfelelő típus!".! a 38-52. sorban. A kimenet első sora szerint a felhasználó a kutyát v{L!aszlja. A kutya objektum a dinamikus mem6riatertlleten készül el ti 44 . sorban. Ez a kutya azlán mut;ltóként, hivatkozásként és érték szerint keiii i átadásra Speak () függvény kelill meghívásra. Ez látszik a felhasználó első választása utáni két sorban. A feloldott mutatót már érték szerint adjuk át. A függvény egy eml5s objektumot vár (err61 tanúskodik a paraméter-szignatúrája), így a fordítóprogram lccsonkítja a kutya objektumOt llZ emlős . részére". Ily m6don
Virtuális destruktorok Gyakori megoldás, hogy egy származtatott objektum mutatóját adjuk át Oll, ahol a bázisosztály mUlatóját várja a program. Mi történik egy ilyen mulató törlésekor? Ha a destruktor virmális (ahogy annak lennie kel!), akkor a megfe l e l ő dolog történik: a származtatott osztály destmktora hívódik meg. Mivel a szárma zt:Holt osztály destruktora automatikusan meghívja a bázisosztály destruktorát is, ezért a teljes objektum megfelelő módon törlődik. A levonható tanulság: ha osztályunkban akad akár csak egyetlen virtuális függvény is, akkor a destrl.lktornak is virtuálisnak kell lennie .
Virtuális másoló konstruktorok Ahogy korábban már említettük, a konstnlktorok nem lehetnek virtuálisak. Ennek ellenére vannak helyzetek, anlikor programunk kétségbeesetten próbál beszuszakolni valahova egy bázisosztáJybeli objektumot címző mutatóI, és próbálja megszerezni a létrejöv6 származtatott objektum másolatát. Ennek a problémának jól bevált megoldása,
ho b')' egy másoló mel6dusl hozunk lélre a bázisosztályban, majd virtuálissá tesszük. A másoló metódus létrehozza az aktuális objektum másolatál, és ezt adja vissza VissZ.1 térési értékként.
Mivel minden származtatott osztály felülírja a másoló metódust, a szánnazlatoU osztályok példányai jönnek létre ennek használatával. A 17.4 lista mutatja , hogya n.
11.4 Usta - Virtuális másoló konstruktorok (virtualcopy.cpp)
o: 1, 2, 3: 1, 5: 6: 7:
8, 9: 10: 11 :
II 17.4 Lista - Virtuálio máo016 konotruktor ~include
cl ass Hammal public : MammalO :i tElAge{ l ) ( std : : cout « 'Mamrnal constructor . . . \n "; l vir.tual -Hammal() ( std :: cout« 'Mammal dcstructor ... \n '; ) Mammal (const Mammal & rhs) ; virtual void Speak () const ( std :: cout « ' MaJ~!nal spp.akl \n" ; virtual Mamma1* Clone{) ( return new Mammal{*thisl; ) int GetAge()const ( return itsAge; )
12 : 13: 14 :
protected: int itsAgc;
15:
);
16: 17: 18: 19: 20:
Mammal::Hammal (const Hammal & rhs) : itsAge{rhs . GetAge{») ( std : :cout « 'Hammal Copy Constructor ... \n";
21 : 22 :
class Dog
public Mammal
23 : 24 : 25 :
26 : 27 :
28 : 29 :
30 :
public : Dog() { std: : cout « 'Oog constructor ... \n"; } virtual -Dog() ( std : : cout « 'Dog destructor . .. \n"; ) Dog (const Dog & rhs) ; void Speak()const ( std : : cout« 'Woof!\n ' ; ) virtual Mamma l* Clone() ( retur n new Dog( *this) ; l;
31 : 33 :
Dog : : Oog(const Dog & rha) : Hammal (rhs)
34 :
{
32 :
35 : 36 : 37 : 38 : 39 : 40 :
41 :
42 :
otd :: cout«
class Cat
'Oog copy constructor ... \n" ;
public Marnmal
(
public : Cat() std : : cout « "Cat constructor ... \n"; ) virtual -Cat () { std :: cout « "Cat destructor . .. \n"; }
324 1V. rész • Örö~6dés és többalakúság 43 44 45 46 47 48 49 50 51 52
: : : : : : : : : :
COlt (const Cat &) : void Spcak()const ( std :: cout« "Meow!\n' ; l virtual Mammal* Clone() { return new Cat {*this ) ; };
Ca t : : eüt (const COlt: Mammal(rhs)
&
rhs) :
{
std : : couL «
"Cat copy constructor ... \n' :
53 : 54 : 55 :
56 :
enum ANIMALS { MAMMAL, DOG, CAT}; const int NumAnima1Typcs = 3; int main()
57 :
58 : 59 :
60 : 61 : 62 : 63 : 64 : 65: 66: 67 :
68: 69 :
Mamma1 ·theArray[NumAnimalTypes]; MaITunal" ptr ; int choicc ,i; for (1 = O; i
70 :
case
71 : 72 :
ptr = new Cat ; break ; default : ptr = new Mamma1: break;
73 : 74 : 75 : 76: 77: 78:
CAT:
theArray ri J = ptr;
79: 80 :
Manunal ·OthcrArray [NumAnirna1Types) ; for (i=O; i
81 :
(
82 : 83 :
theArray[il >Speak() ; OtherArray [il '" t hp.Array [i j - >C l one () ;
84 :
85 : 86 : 87 : 88:
-
f or (i=O;iSpeak() ; return O;
1 : (l)dog (2)cat (3)Mammal: 1 2 : Hammal constructor . 3 : Dog Constructor . ,
17. 6ra· A 4:
(l)dog
(2)cat
(3) Mammal :
és a szánnaztBtott
2
5 : Hamma1 const ructor ...
6 : Cat constructor ... 7:
(ljdog (2)cut (3)Mammal: 3
8: Mammal constructor .. 9 : Woof!
10: Mumma1 copy constructor ... 11: Dog copy constructor . . 12 : Meow!
13: Mamma! copy constructor . . 14 : Cat copy constructor . . 15: ManI/nal speak!
16: Mamma! copy constructor .. 17: Woof! 18: Meow!
19: Mamma! speak!
EI.
A 17.4 Lista igen hasonlít az előző kett6höz, attól eltekintve, hogy egy új virtuá lis t3gfüggvény is megjelem az emlős osztályban: a clone () . Ez egy mutat6t ad vissza t:gy újonnan létrehozott emlős objekLUmr:a, miközben meghiv6dik a másol6 konstmktor, aminek önmagát ("thiS) ;ldja át konstans 1livatkozáskénl. A kutya és a macska is felülírja a clone () met6duSl, mclyben önmaguk másolatát ad· ják át másoló konstruktoruknak. Mivel a clone () vinuális függvény, ennek hatásár..! ténylegesen létrejön egy virtuális másoló konstnlktor, ahogyez:I 83. sorban látszik. A felhaszná lónak Iehet6sége van kutya, macska vagy emt6s közül vfllasztani, melyek létre is jönnek a 65·76. sorban. Minden választ{lsnak megfelel egy mulató, melyet a 77. sorb~11l látható tőmbben tárol unk. Ahogya program sorban végiglépclel az egyes iterációs lépésekben a tömbön, minden objektumra meghívja a Speak () (82. sor) és a clone () met6dust (83. sor). A clone () meghívásának eredménye egy mUI,lló lesz, amely az eredeti objekmm m.ísolmára mu· tat. Ez egy újabb tőrnbben tárolódik a 83. sor szerint. A kimenet c1s6 sorában a felhasználó a kutya létrehozását választja (vagyis az l-est). Az emlős és a kutya konstruktorai elvégzik teendőjüket Ugyanez megismétl6dik ma(.:5kára és eml6sállatra is a kimenet 4-8. soráb.m. A kimenet 9. som mutatja a7. első objektum, a kutya megsz6lalását (az első for ciklusban, a 82 . sorban). 1\ virtuális Speak () függvé nyt hívjuk meg, és a m egfelelő Speak () mcgvalósítás fut le. Ezek után a clone () függvény következik, és mivel ez szintén virtuális, a kutya klónozó met6dusa lép munkába. Először az emlősök másoló konsrruktom, majd a kUlyák másoló konstruktora indul el.
326 1V.
resz • Örö~6dés és !Öbbalakúság Ugyanez ismétl6dik a macskára (kimenet 12-14. sora) majd az emlósre (kimenet 15-16. sora), Végül végigjárjuk frissen keletkezett tömbünket (kimenet 17-'19. sora, a program 85-86. SOI<.l), és megsz61altatjuk új objektLlmainkat Az a f6 különbség e megközelítés és a másoló konstruktor használata közön, hogy itt
a progmmoz6nak kifejezetlen meg kell hívnia a c l one () függvényt. A másoló konstruktor 3momatikusan meghívódik, amikor lemásolódik egy objektum . J6 tudni, hogy minden származtatott oszt{ilyua n megírhatjuk saját felülíró c lone () függvényOnket, bár ez a megközelítés sokat ronl a helyzet n!galrnasságán,
A virtuális függvények költségei Mivel :1 virtuális függvényeket tartalmaz6 objektumoknak gondoskodniuk kell egy vt:íblázat karbantarcásáról , ennek va n némi többlet erőfo rrásigénye. Ha kis oszt:.'l lyról van szó, amelyből várhatólag nem fognak m(ls osztályok örökl6dni, r,e rn é rde mes virtuális függvényekbe bonyolódni . Ha bármilyen t:lgfüggvényt virtuálisnak deklarálunk, ezzel már meg is fizettük a vtáblázat költségeinek nagy részét (habá r minden tov:'ibbi megvalósítás foglal még némi memóri:!terii letet). Innent61 kezdve va l ószínűl eg virtu~liské nt szeretnénk majd dekbr{llni a destruktorc is, sőt feltételezhető, hogy várhatólag minden egyéb tagfüggvény is virtu~lis les?.. Vessünk szigorú pillantást minden nem-virtuális lagfüggvényre, és gondoljunk erőse n bele, hogy mivel indokolható, hogy ez nem virtuális.
Helyet
Holytel..
Haszná ljunk virtuális függ;v€:nyeket , ha arm számítunk, hogy osztályu nkból ezek ö rök l őd ni fognak. Használjunk virtuális dest11.lktorokat, ha m:.'lr van virtuális függvényOnk.
Ne jelöljük a konstruktorokat virtuálisnak.
Kérdések és válaszok Kérdés: Miél111e neveznénk ki minden tagfilgguiin)'t vi11uálisnak? Válasz: Az első virtuális függvény létrehoúsára komoly költségterhet ró a v-táblázat előállítása.
Az ezt követő többJetkö ltségek már elhanyagolhatóak. Sok C++ programozó úgy véli, hogy ha már van egy virtuális fuggvé nyünk, akkor már mindegyiket annak érdemes kinevezni. Más progmmozók ezzel szembe n úgy vélik, hogy csak indokolt esetben szabad virtuális függvényt deklar.'ilni.
17.6... A
ás a származtatott
Kérdés: Tegyük fel, IIogy báZ/sOSZlál)'llllkban van egy SomeFunc () nemi viltuális fi/gglJéIl)', me~)lCt fúlterhelttink, azaz elfogad egy vagy kél egesz paraméle/1. Egy származ-
ta/ott osztálybanfclt1/ítj/lk az ''8J'JXlnlméteres változatot. Mi ló/tél/ik, ha a származtatott osztály egJl muta/ója meghívja CI ketparam(2teres változatol? Válasz: Az egyp;mullétcres változat fe lülírása elfedi a bázisosztály összes ilyen nevű függvényét. Ily módon fordítási hibát kapunk, amely aITÓl fog panaszkodni , hogya hibásan meghívoll függvény csak egy egész paramétert vár.
Gyakorlatok Ebben az órában ;1 többalakúsággal és a származtatott osztályok szépségeivel ismerkedhetlük meg. Válaszoljtmk most meg néhány kérdést és végezzünk CI néhány fe ladataI ludf\sunk ell enőrzése végett!
Kvfz 1. l..e hcl-e úgy módosílani a slicing . epp progrdmOt 07.3 Lista), hogy
a Value~unction () felülírásával hívjuk meg a származtatott Speak ( ) függvényt? 2, Honnan mdja a program, hogy melyik virtuális függvényt kell mcghívni, ha :lZ objeknunok egy a báziso.~ztá l yhoz tartozó változóban vannak? 3. Milyen típusú mel6dus nem lehet virtuális? 4. ~'I i II többalakúság?
Feladatok L Mi történik, ha a sz!Írmaztatott osztály nem írja fölü l a bázisosztály valamelyik
virtuális függvényét? Tegye megjegyzésbc a virtmethod. epp program 07.1 Lista) 2l. sorát, melyben a kutya osztály Speak () függvényének deklarációja álL El md képzelni olyan példát, ahol ennek van értelme? 2. Mi történne, ha:\ s licing . epp program (17.3 Lista) 8. sorából kitörölné a virtual szót a bázisosztály Speak () függvényének tlcfiníciója e161? Miért nem hív6dnak meg sohasem a felülíró függvények? (ScgíL<;ég: érdemes megnézni az els6 kérdést.) 3. Próbálja futtatni a fejezet minden egyes példaprogramját. Prób{lljon ki a könyvben olvashat.ótól cltér6 értékeket is, hogy jobban érezze, hogyan is működnek a fenti programk6clok.
327
32s 1V.rész • Örö~ódés és többalakúság Válaszok a kvízkérdésekre 1. Közvetlenü! nem. Bár létre lehet hozni a függvé nyb61 egy kutya (oog) vá ltozaLot, eZll:Öoha nem hívmí meg a fordítóprogram , mivel a várt paraméterv:.'iltoz6 a bázisosztályból való (emI6s; Mammal). A Val ue Function-nek pedig sajnos csak az a változat;! kerül meghívásra, amelynek emlős a paramétere. 2. A y-t..í.blázat tartja nyilván ezeket az információkaL Ennek a táblázatnak ,IZ adm inisztrációja jelenti II virtuál is függvények löbbletkölL'>égét a normál fOggvényekhez képest. 3. A konstruktor (beleértve a má5016 konstruktort is), 4. A löbba1akús.'ig szerint úgy tekintjük a sz:.'irmntaloU oszLályok példányait. mintha azok a bázisosztályb61 volnának. Mintha ugyanannak az osztálynak fóbb megjelenési formája lenne.
18.
ÓRA
A többalakúság kifinomult használata Ebben az 6rában a követk.ezőkrőllesz szó: • Mi a típusvá ltás (casting down) és mikor é rdemes használni • Mik az elvont adattípusok • Mik az üres virtuá lis függvények
Problémák az egyszeres
örökJődéssel
Az előző leckék során nl<1r beszéltünk a származtatott objektumok é~ alaposztályaik
többalakú kczclésér61. Uíthattuk, ho&'Y ha az alaposztálynak van egy Speak () tagfüggvénye, amelyet a származt,ltott osztályban felüldefiniálunk, akkor az alaposzÚIly tipusára felkészített mutatóban, amely a származtatott osztályra mutat, helyesen a származtatott osztályban felüldefmiáJt ragfüggvény jm élVén yre. A 18.1. lista ezt az esetet mllt.'l~a.
330
Iv. rész • Öriild6dés és többalakúság 18.1. Lilla - VirldlllIItIfiiIIriaJeI ( - . . " ,
o: 1, 2; 3: 4. 5: 6:
7: 8: 9: 10 : ll : 12 : 13 : 14 : 15 :
/1 18 . 1 Lista - virtuális t a g füg gv é ny e k #include
c l ass Ma nunal public : Mammal () : itsAge(l)
( std : : cout « "Hamma l (eml6 s ) .. konst.ruktor ... \n" ; l v i rt u al -Mammal() { st d: : cout « "Mammal (emI6a) .. destruktor ... \n" ; } virtual void Speak() const { std : : cout « " Egy Marm\al beszél!\n" ; J p rotected , i nt itsAge ; };
class Ca t: pu blic Mumrnal p ublic :
16 : Cat{) ( std :: cout« 'Cat (macska) konstruktor ... \n "; 17 , -Cat!) {std :: cout« · Car. (macska) destruktor ... \ n " ; 18 : void Speak()const. I std:: cout« 'M ij~ú ! \n '; } 19 : }; 20 : 21 , int ma i nl) 22 : 23 :
Mamma1 " pCat", new Ca t ;
24 : ::lS :
pCa t ->Speak( ); r eturn O;
26 :
Hammal (eml6s) konstruktor
Car. (macskalkonstruktor Mijáú !
A 8. sorban a Spea k () tagfüggvf!nyl virtuálisként határOZluk meg . A 18. sorban rdüldefiniáltuk, majd a 24. sorban meghívtuk. Ne feledjük, hogy a pCat mUlatót a Marnma l osztályhoz vezettük be, dc egy Cat típusú objektum címét rendeltük hozzá. Ahogyan a 17. leckében (Többahlkúság és származtatott D..';zrályok) már elmondtuk, ez a többalakúság lényege. De mi történik akkor, ha olyan tagfüggvénnyel b6víljük a e at (macska) osztályt, amely nem alkalmazható a Mamma l (emlős) osztály példányaira? Tegyi.ik fel, hogy egy Purr () (dorombolás) neV"Ú tagfüggvf!nnyel bővítjük a e at osztályt. A macskák dorombol nak, de más emlősök nem , Az osztályunkat ekkor ekép p határoznánk meg :
18. óra •
kifinomult használata
class Cat : publ i c Hammal (
public : Cat() { std :: cout« 'Cat (macs ka ) const ructor ... \ n'; -Cat () ( std : : cout « 'Cat (macska) destructor ... \ n'; void Speak () const { std :: cout « 'Mijáú! \ n'; } void Purr () const ( std :: cout « 'rrrrrrrrrrrrrrrr\n' ; ) };
A probléma a következő: ha most meghívjuk a Purr () lagfüggvényt egy Mammal .típusú" mutatÓn keresztül, fordítási hibát kapnánk. A Pun () hívása kor egy Borland fordító valami ilyesmivel válaszolna: 'vi r t uülmethode. c pp ' , E23l6 ' Purr ' is not a membe r o f in function ma in () at l ine 50
Természetesen mfts rordítók 111fts hibaüzenettel állnának error C2039 :
'Purr'
e l ő.
'Marruuul'
Például valal11i ily\:!smivt::l:
: is not a member of 'Hammal '
Amikor a fo rdítÓ megpróbálja feloldani a Purr () tagfüggvényt a Marrunal-hoz renddt virtuális tábláza.t alapján, nem fog ilyen bejegyzést találni. Ánehetjük a tagfüggvényt az aJaposztályba, dc ez nagyon rossz megoldás lenne. Habár mint megoldás, k&L<;(:gtelenül mQ"köclik , llZ aJ:lposztá lyok felhígítá s.'l a sZllrmaztatott oszlályokra jellemz(5 metódusokkal igen rossz progl'l.lmozói gyakorlat, és egyenes út a nehezen k:lrbanta rth:u6 kód készítése felé. Az egész prohléma valójában a hibás tervezés eredménye. Általánossflgb:m az al aposztályra felkészített, de a származtaton osztály példányával összerendelt mutatÓ azt jelenti, hogy többalakú osztályként szándékozunk h'l.<;znál ni, és ebben az esetben nem kelle ne olyan tagfüggvények hívásával prÓbálkoznu nk, amelyek valamilyen szá rmaztatott osztályra vonatkoznak. Vagyis nem az a baj, hogya származtatoll osZl1Uyokra jellemz6 ragfüggvér1yekkcJ dolgozunk, hanem az, ha ezeket al aJaposltályhoz létrehozott mutatón keresztül prÓbáIjuk meg elérni. Egy tökéletes világban, ha. ilyen mutatóink vannak, nem kellene az ilye n tagfüggvények elérésére pr6bálkm:nunk. De természetesen nem egy tökéletes világban élünk. Id6nként azon kapjuk magunkat, hogy van egy csomÓ alapobjektumunk, például egy állatkert tele eml6sökkel. Egyszer csak észrevesszük, hogy van egy macskánk (Ca t típusú objektum), amelyik a fejébe vette azl az átkozott doromholásl. Ilyenkor általában csak egyetlen dolgaI lehetünk: csalunk. Csalni pedig úgy lehet , hogy az alaposztály ~tí pusú n mutatónk típusát megváltoztatjuk (casting) és . típusazonosítjuk n a származlatott osztállyaI. Azt mondjuk a fordít6nak: ~Nézd Fordít6! Programozó vagyok, és pontosan tudom, hogy ez valójában egy macs-
332 1V. rész • Önl~6dés és többalakiJSág ka, úgyhogy eredj, és csináld azt, amit mondok." Ez úgy hangzik, mintha egy gengszter mondaná, mivel eközben úgy is viselkedünk; kier6szakoljuk egy macska (Cat) viselkedését egy emlős (Manuna l) lípusú elemre hivatkozó mulalóból. A feladat végrehajtásához a dynamic_ ca s t operátort fogjuk használni. Ez az operátor gondoskodik arról, hogy amikor típust válrunk, biztonságosan tegyi.ik azl. Segít továbbá gyorsan megtalálni azokat a részeket a kódban, ahol éltünk ezzel a lehet&éggcl, így azonnal eltávolíthatjuk őket, a mint megjött a józan eszünk. A dolog II
következőképp
mO"ködik: ha van egy olyan aJaposztály típusú mutat6nk,
mint II Manun",!, és ehhez a mural6hoz eb')' o lyan származtatott osztá ly példányát rendeljük, mint a Cat, akkor a Mammal mutatót, mint többalakú elemet haszn;ílhatjuk a virtuá lis tagfüggvények elérésére. Ekkor, ha egy Cat típusú objekmmot akarunk rajra keresztül elérni, annak is kifejezeuen a purr () tagfOggvényél, csinfilnunk kell egy Cat típusú lllulatót a dynamic_ cast operátor segítségével. Az alaposzt{lly mut:lt6 ellen6rzése futásid 6bcn történik. Ha az átalakítás lehetséges, az új Cat típusú mutatónk létrejön. Ha viszont az átalakítás nem lehetséges, vé gül mégsem Ca t típusú obje kturnra hivatkozmnk, az új mutató nk értéke null lesz. A J8.2. Lista ezt mutatja be.
18.2. Uoto-Dl....... ~
(aooIIoa1. mB --IG.........-
(dynallllccalt.cpp l O,
/I 18.2 . Lista - Dinamikus t ípus konverzió
l : 'include 2 , u!:ling std : : cout ; II a program használja az std : :cout hivást
,. 4:
5: 6~
class Mammal (
pubU c : Mammal(l : itsAge{l) ( cou t «
ll :
"Mammal (em1 6s ) konstru ktor ... \n "; virtual -Marnmaol () ( cout « "Marnmaol (emI6s) wo destruktor ... \n" ; virtual void Speak() const ( cout « "Egy Manunal wo beszél ! \n "; ) pro t ec t ed : int it sAge ;
12 :
);
7:
wo
8: 9:
10 :
1) :
14 : 15 :
class Cat: pub lic Mammal
16 :
17 : 18 : 19: 20:
public : Cat () ( cout « "Cat (macska) konstruktor ... \n' ; _Ca t {) { cout « "Cat (macs ka) des trukt or ... \ " " ; void Spea k () const { cou t « "Mij áú ! \n' ; } void Purr() const { cout « "rr r r r rrrrrr\n ' ; }
21 :
};
22 :
18. óra • A többalakClság kifinomult hasmálata 1333 23 :
class Dog : public Hammal
24 :
(
25 :
public: ( cout «
(kutya) konstruktor ... \n"; "Dog (kutya) d e s t ruktor ... \n'; void Speak () cons t { cout « 'Vaú! \n" ; }
26 :
OOg{)
27 : 28 :
-Dog()
29:
'OOg
{ cout «
);
30 :
31: 32: 33: 34: 35:
int main(} {
const int NumberMamma1a ,. 3; Hammal- Zoo[NumbcrMammalsJ: Mammal- pMammal; int choice,i:
36: 37:
38:
for (i=O; i
39: 40:
{
cou t « "( l)Kutys (2)Macoka : ", std : : cin » c hoicc ; i f (choice "" l) pMammsl = new Dog:
41: 42: 43:
44:
else
pMarnmal " new C
45:
46: Zoo[i] " pMalMlal;
47:
48:
49: cout «
50:
'\n';
51 : 52 :
for (i=O ; i
53 : 54 :
(
Zoo[i]->Spcak{) :
55: 56:
Cat *pRealCat"
dynsmic_cast (Zoo[ij);
57 :
if {pRealCat}
58: 59: 60: 61 :
pRealCat->Purr() ; else cout«
'Húha,
6í! :
delete Zoo [ i] ; cout « "\ n";
63 : 64 :
65 : 66 : 67:
rcturn O;
68 :
IGmenet (I)Kutya (2)Hacs ka : l
Hammal (eml6s) konstruktor. Dog
(kutya)
konstru ktor . . .
ez nem
macska!\n';
334 1V. rész • Öröij6dés és többalakúság (llKu t y a
(2) Mac!lka : 2
Hammal (em16s1 kons truktor .. . Cat (macska) konstru ktor .. . (l)Kutya (2)Macska : 1
Mammal (emlos) konstruktor ... Dog (kutya) konstru ktor ...
Vaú ! Húha, ez nem macska ! Hammal (cm16s) destruktor ..
Mijáú rrr r r rrrrrr Hammal (em16s1 deHtru ktor ... Vaú ! Húha ,
Mamma l
EI.
~Z
n em maCSkll !
(em16s) des t ru kt o r .
S
A 38.·48. sorokb:Ln :l felhasználó választása szerint adunk hozzá Cat vagy Dog objekrumokat a Mammal típusú mutatók tömbjéhez. Az 52. sorban végigmegyünk a tömb ek.... mein, majd az 54. sorban mindegyik objektum speak () mCl6c\usát meghívjuk. A tagfüggvények többaJ:.kúként viselkedve különböz6 eredményekkeltérnek vissza: A kutyák ugalnak, :I m:,cskák nyávognak. Az 59. sorban egy Cat típusú objektumon szeretném meghívni ;1 purr () (dorombolás
tagfüggvé nyt , dc nem szeretném abban az esel!)en, ha Dog típusú objektummal van do lgom. Az s6. sorban has7.0áll dynamic_cast operátor gondoskodik r6 1a, hogy az objektum, aminek a purr () tagfüggvényét hívju k, biztosan Cat típusú legyen. És in található a kulcsmomentum: Ha eat lípusú , a rnutató ~néke nem null, így megfelel az 58. sorb:m találhmó fe ltételvizsgálatnak.
Elvont adattfpusok Gyakr:m készítünk osztályhierarchiákat. Ha például létrehozunk egy Shape (síkidom) osztályt alaposzlály gyanánt, majd ebből származlatjuk a Rectangle Úéglal:.1p) és Circle (kör) osztályokat, máris hierarchia keletkezett, ha nem is valami bonyolult Ezután a Rectangle osztályból tovább származtathatjuk a Square (négyzet) osztályt, nunt a négyszögek egyik speciális képvisel6jél. Mindegyik sZiÍ rmaztatOlt osztály felüldefmiálja a Draw () (rajzo!), és a getArea () (teruletszámítás) metódusokat és még másokat. A 18.3. Lista a Shape osztály lecsupaszíton megval6sítását mutatja a szánnaztaton Cir c1e és Rectangle osztályokkal.
18. óra • A
kifinomult használata 335
18.3. Ut.. - SIkldom autítyok (.h.pecl....cpp) O, 1, 2,
II 18 . 3 . Lista - Sikidom osztályok lincludc
3:
class Shape
4: 5, 6:
( public : Shapc() (l
7: 8: 9: 10 :
virtual virtual virtual virtual
11 :
);
12 : 13 :
class Circle
-Shape(){) long GetArea() { return -1 ; } II hiba long GetPcrim{) { return -1; } void Draw() {)
public Shape
14 :
15 : 16 : 17 : 18 :
19 :
20 : 21 :
22 :
public : Circlc (int radius) : i tsR!!Idius {radiusl (l -Ci rel e () {}
long GetArea{) ( return 3 • itsRadius long GetPerim() void Draw ( ) ; privata: int i tsRadiu$ ;
23 :
{ return 9 * itsRadius;
itaRadius ; }
int itsCircumference ;
24 :
};
25 : 26 : 27 : 28 : 29 :
void Circ!e: :Draw() ( std: : cout « "KOr rajzolását végz6 utasítások helye! \n";
30 : 31:
32 :
class Rectangle
33 : 34 : 35 : 36 : 37 : 38 : 39 : 40 : 41 : 42: 43 : 44 : 45 : 46 : 47 : 4B : 49 : 50 ,
(
public Shape
public : Rcctangle(int len, int width) : itsLcngth (len), itswidth(widt h) () virtual -Rec tangle{) {} virtual long Ge tArea () ( return itsLength * i tsWidth; ) virtual long GetPerim() (return 2 *i tsLength + 2 *i tsWidth; v irt ual int GetLength{) ( return itsLength; virtual int Gctwidth() { return itsWidth; } virtual void Draw ( ) ; private : int itsWidth ; int itsLength ; ); void Rectangle : : Draw() for (int i
='
O; i
V. rész • Öröklődés és 51 : 52 :
for (int j = O; j
53 : 54 : 55 : 56 : 57 : 58 : 59 : 60 : 61 : 62 : 63 : 64 : 65 : 66 : 67 : 68 : 69 : 70 : 71 : 72 : 73 : 74 : 75 : 76 : 77 : 78 : 79 : 80 : 81 : 82 : 83 : 84 : 85 : 86 : 87 : 88 : 89 : 90 : 91 : 92 : 93 : 94 : 95 : 96 : 97 : 98 : 99 : 100 : 101 : 102 :
std : : cout«
jH)
"\ n " ;
class Squace : public Rectangle { public : Square{int len) ; Square(int len, int width) ; -Square(){) long GetPerim() (return 4 * GetLcngth() ; ) }; Squarc : : Squace (i nL len ): Rectangle{len , l en)
II Square : :Square(int len , int wldthl : Rectanglellen,width) if (CetLenoth() != GetWidthi» std : : cout « "Hiba, ez nem egy négyzet ... ... inkÁbb egy Téglalap?\n" ;
int main ( ) int choice ; bool fQuit = false ; Shape " sp ; while (1) { std :: cout« "(l)KOr (2)Téglalap (3)Négyzet (O)Kilépés : ", sLd : : ein » cho i ce ; s witch (choice) ( case l : sp = new Circle(5) ; break ; case 2 : sp = new Rcctang1eI4.6); break; case 3 : sp = new Square (5) ; break ; def.ault : fQui t = t ru e ;
l B. óra • 103 : 104 : 1 05 : 106 : 107 : 108 : 109 :
break ; if
(fQuit.) break ;
sp->Draw() ; std :: cout «
"\n" ;
110 :
111 :
r eturn O;
112 :
(l)KOr (2) 'régl a lap {3)Négyzct (O ) Kil é p és : 2
xxxxxx xxxxxx xxxxxx xxxxxx ( l )KOr (2) Téqla l ap
{3) Négyzet ( O)Ki lépés : 3
xxxxx xxxxx xxxxx xxxxx xxxxx (l)KOr (2)Téglalap (3) Négyzet (O)Kilépés : O
A 3-11 . serok között határoztuk meg a Shape (síkidom) osztályt. A getArea () (terület) és getPerim () (kerület) met6dusok egyelőre hibával térnek vissza, a Draw () (kirajzol) tagfüggvény pedig nem csinál semmit. Végeredményben nem is tudjuk pontosan, hogy milyen síkidomr6l V:1n szó . Csak a síkidomok különböző típusait (kör, tégla lap) nldjuk kirajzol ni, a síkidom, mint elvont fogalom önmagában nem lerajzolhat6. A eire l e (kör) osztálya Shape leszármazottja, amely felüldefiniálja a fent említett három ragfüggvényt. Figyeljük meg, hogy nincs szükségünk újabb vir t ual ku1c:ssz6m, mivel ezt is örökölik HZ :t laposztályt61. Ám annak sincs semmilyen negatív következménye, ha máském járunk el, ahogy tesszük ezt a Ree tang l e (t6g1alap) osztá ly megh:Hározásakor a 38-42. sorokban. Hasznos dolog szerepeltetni a vi r t ual kulcssz6t, egyfajta doku mentádókéOl hadd emlékeztessen bennünket, hogy a tagfüggvény virtuális. A Square (négyzet) a Rectang l e osztályból származik, és ebben is felüldefiniáljuk a getPerim() metódust, a többit pedig változatlanul örököljük a Reetangle osztályból.
I
338 V, rész • Örö~6dés és többalakúság Eléggé zavaró, hogy ól Shape osztályt is tudjuk példányosítani , jó volna, ha ezt mega kadályozhatnánk. A Shape osztály csak azért van, hogy felületet adjo n a bcl61e származlatotl osztályok számám. Mint olyan, egy elvonl adattíp/lsr61 ( abstract data type) van sz6, vagy rövidítve ADT-ról.
Az absztmkl adauípusok egy-egy fogalmat jelentenek (mint pé ldául a síkidom), és nem o bje ktumo kat (mint például a kör) . C++-ban egy ADT mindig más sz<"irmaztatott osztályok alapjául szolgáló osztály, az ADT-k példányosítása é rvé nytelen mllvelel. Azaz, ha a Shape osztályt ADT-ként határoztuk meg, nincs lehet6ségünk annak obje ktumként tö né n6 példá nyosítására .
Üres virtuális függvények A CH támogatj:l ;lZ elvont osztá lyokban az üres virtuális rüggvé nyck létre hozását. Az üres virt1.lális fü ggvény olyan virtuális függvény , amely kötel ez ően felüldefiniáland6 a származtato tt osztályo kban. Egy virtuális függvé nyt úgy határozhatlmk meg üreské nt, ha O kczd6érté kkellátjllk e l, mint a követk e ző példában is: virtual void Draw()
~
O;
Bá rme ly osztály, amely ta rtlllm
Ne péklá nyosíL<;uk az o bjektumot, szánnaztaSSlmk be16 1e. Ne fe ledjük fe lüldefiniálni az üres virtuális függvényeke t.
Mindegyik osztály, amely ADT-ból származik, Oreské nl ö rö kli aZ üres virtuális függvénye ket, azaz Fe ltétlenül felül kell defmiálnunk ezeket, ha példányosítani sze re tné nk az osztályunkat. Ennél fogva, ha a Rectang l e osztálya háro m üres virtuális függvé nyt tartalmazó Shape osztályból származik, a Rectangle osztályban mindhárma! fe lül kell definiálni, különbe n a Rec t a ngle maga is egy ADT lesz. A 18.4 . Lista elvo nt adattípuské nt írja újra:l Shape osztályt. Cseréljük le a 18.3. Lista 3-11. soraib:m talá lható Sha pe osztály meghatározását a 18.4. listában található osztálydefinícióra (ezzel a 18.5. Listát k:lpjllk ere dményOl), majd futtassuk le újra a progf""dffiOl.
18.4. Ua.. - Elvo.. adatlip•• ok (adtcpp) l : 2:
class Shape (
3:
public :
4: 5: 6: 1:
Shape(){} virtual -Shape () {} virtual long Ge t ll rea () O· virtual long Ge tPe rim{) = O;
18. óra • A . 8, virtual void Draw() 9 : private : 10 : l;
Figye!em!
O;
Az adt.cpp nincs lefordfthat6 állapotban!
Ne veszódjünk a 18.4. lista !eforditásáva! - ez csak a változásokat tartalmazza a 18.3. listához képest. A 18.5. lista (amely a 18.3. és a 18.4.listát összefésülé· sével keletkezett) az, amelyik leforditható, próbálkozzunk azzal. 18.5. Lista - A Sh.p. _ y b.mutIIáo••Ivont .dottfpusk6nt 1.d1ah.pecl....cpp) O: 1: 2. 3: 4: 5: 6: 7: 8: 9: 10:
ll: 12 : 1] :
/1 18 . 5 . Lista - Síkidom osztályok lIincludc
ClASS Shllpe ( public: ShaDe () {} virtual ~ShaDe () (l virtual long GetArea () • O; virtual long GetPerim() =- O; virtual void Draw () = O; }; class Circ1c , public Shape
14 :
{
IS: 16 : 17: 18 : 19 :
public: CI rclo (int radius) : itsRadius (radiusl {} -Circle(){} long GetArea I) ( return 3 * itsRadius ,. itsRadius; long GetPerim() ( return 9 .. itsRadius; void Draw(); private : int itsRadius ; int itsCircumference; };
20 :
21: 22 , 23 : 24 : 25 : 26 : 27 : 28 ,
29 : 30 : 31 : 32 : 33 : 34 : 35 : 36 : 37 , 38 : 39 : 40 :
void Circle: : Draw() std : : cout «
'Kör raj zolását végz6 utas ítások helye! \0' ;
public: Shape class Rectaog!e ( public : Rectanglelint Ion, int wid t h) : itsLength (len), itsWidth (width) l} virtual _Rectaoglol)l} virtual long Getl\rea(} I return itsLength * itsWidth; l virtual long GetPerim() (return 2*itsLength + 2"itswidth; virtual int GetLength() I return itsLength;
I
340 V. rész' Örökl6dés és többalakúság 41 :
42 :
virtual int Ge tWid th ()
44 :
virtual void Draw () ; private: int itsWidth ;
45 : 46 :
l;
47 : 48 :
void Rect ang !e : : Draw ()
4] :
( ret ur n itsWid th ;
)
int itsLeng th;
49 : 50 , 51 :
for
(int i
O; i
(
" O; j
(or (int j
52 : 5] :
54 : 55 :
std :: c(')ut«
"\n ";
56 : 57 :
58:
public Rectangle
!.l9 : 60 :
class square
61,
65 :
public : Square(int; len ) ; Squarc(lnt len, int width) ; -Square () (l long GetPerim{) (return 4 • GetLength() ; }
66 :
}
67 : 68:
Square: : Square(int len) :
62 : 63 :
64:
(
,
69 : 70:
Rectangle(lcn,len) ()
71: 72 :
Square : : Square(int l en , int width) :
73 : 74 :
Rectangle(len,width)
75: 76 :
i f (Get Length () !. GetWidth () ) std: : cout <:< "Hiba, ez nem egy négyzet ... ... inkább egy Téglalap?\n" ;
77 :
78 : 79 :
80 : 81 : 82 : 83 ;
int main{) int cho i cc; bool f QuiL '" f alse; Shapc " sp;
84 : 85 :
while
86 :
{
87 : 88 :
(1 )
std : : cout « "{ljKOr (2)Téglalap (3)Négyzet {O)Kilé pés: s t d: : ci n » cho ic e ;
89 :
90 , 91 ;
switch (choice)
92 :
case 1 ;
93 :
(
sp
=
new Circle(5) ;
".
18. óra· A többalakúság kifinomult használata 1341 break ; case 2 : sp '" new Rectangle{ 4, 6); break ; case 3 : sp - new Square{5); break; default : fQuit '" true ; break ;
94 : 95: 96: 97 , 98 : 99 : 100, 101 : 102: 103 : 104 : 105 : 106 , 107 : 108 : 109, 110 : 111 : 112:
i f (fQuit)
break; sp->Oraw{) ; std : :cout «
"'n ";
re t urn O;
(l)KOr (2)Téqlalap {3)Négyzet (O)Kilépés : 2
xxxxxx xxxxxx xxxxxx xxxxxx (1)K6r (2)T6qlalap (3)Néqyzct (O)Kilépés : 3
xxxxx xxxxx xxxxx xxxxx xxxxx (l)Kör (2)Tóqlalap (3)Négyzct (O)Kilépés : O
Látható, hogya program múködéséhez nem nyúlrunk. Az egyetlen különbség, hogy
most nem lehetséges a Shape osztály pl!ldftnyosítása . Apropó
Elvont adattfpusok
Az elvont adanlpusok meghatározása úgy történik, hogy az adott osztály definíciójá· ban szerepeltetünk egy vagy több üres virtuális tagfüggvényt. Üres virtuális függvé· nyek meghatározásához pedig írjunk ", O-t a függvény deklarációja után. Például: class Shape { }
,
v ir tual void Draw () = O; II üres vi rtuális függvény
342 1V. rész • Örökl6dés és több,l,köság
Az üres virtuális függvénye k megvalósítása jellemz6en, az elvont alaposztályokban szerep16 üres virtuális függvényeket nem implementáljuk. Mivel ilyen tipusú objektumot soha nem hozunk létre, semmi szükség rá, hO!:,'Y megva l6sításl adjunk rá. Az ADT tisztán egy felület meghatározása kém működik , amelyből küJönböz6 osztályokat s7..:Írmaztathatunk. Ugyanakkor adhatunk megvalósítást az üres virtuális függvények nek. A függvény ekkor az ebból az osztályból származtatott objektu mok tagfüggvényeiként hívhar6k, s többnyire valamilyen általános működést biztosít annak a sZ:lrmazt:ltolt osztályokban felüldefiniált függvények számára. A 18.6. Lista ic másolja a 18.3-ast, ezúttal a Shape osztályt, mint ADT-t fe lruházva a Dr a w( ) üres virtuális függvény megval6sílásával. A c i rcle osztály, mivel számára ez kÖlelez6 , felüldefiniálja a Dr a w ( ) lagfüggvényt', dc ami a további rn t1veJ etek~l ill eti , visszahivaLkozik fe lrelé az alaposztály függv ényére. Ebben a példában a további művelet nem más, mint egy újabb üzenet kiíratása a ké perny6re, dc ne m nehéz elké pzelni, hogy az alaposzlá ly egy általános rajzolási lépést osszon meg a származtatott oszlályok számár.!, l:>éldiiul egy olyan ablak létrehozását, a hová az összes síkidom kirajzolódik.
18.6. lista - Üres virtuális fOggvénvek meghatérozása {implement.cpp ) 0,
l , 2, ], 4, 5, 6, 7,
8:
9, 10 , ll , 12 :
1/ 18 . 6 . Lista - Ores vi r tuális föggvény ek meghatározása Iinclude
class Shape ( p ublic ! Shape () {} vi rtua l -Shape () ( ) virt ual long GetArea () = O; virtual long GetPe rim()= O: virtual void Draw!) : O: private : };
l]:
14 : 15 :
16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : 24, 25 ,
v o id Sha pe : , Dra w ( ) st d: , cout «
"El v ont ra jzolási lépések! \n" ;
class Circ l e public Shape { p ublic : Circle ( int rad i us) : i t sRadius ( radius} {} _Ci rcle( ) l ) long GetAr e a () ( re t u rn ] * itsRa dius * i t sRad iu s; long GetPe rim() { r eturn 9 • itsRadius; }
18. óra • Atöbbalakúság kifinomult használata 1343 26: 27 :
28 : 29 : 30 : 31 : 32 : 33 : 34 :
void Draw{) ;
private : int itsRadius; int itsCircumference ; }; void Circle : :Draw()
std : : cout «
35 :
'KOr rajzolását végz6 utasitások helye!\n';
Shape : : Oraw();
36 :
37 : ]8 : 39 : 40 : 41:
cIa!>!. Rectangle
public : Rectanglelint len,
int wi d th) : itsLcngth( l e n), i ts Width(width ) ( J
42 : 43 : 44: 45 : 46 : 47: 48 : 49 : 50 :
virtual -Rectang l e () {} long Ga tArea ( ) ( eetu !:'n its Length * itsWid th; lo ng Ge t Perim() (return 2 "' its Length + 2 "it 9Wid th ; virtual int GetLength() ( rcturn its Length ; ) virtual int GetWidth() ( return itsWid th ; J void Oraw() ; privata: int itsWidth ;
51 : 52 :
53 : 54 : 55 : 56 : 57 , 58 :
59 : 60 ,
public Shape
int itsLength: };
void Rectanole : :Oraw!) { for (int 1 '" O; l
61 : 62 : 6] : 64 : 65 :
"'n";
Shape :: Draw() ;
66 :
class Square
67 : 68 : 69 : 70 : 71 : 72 :
(
pu blic : Squ are( int l en) ; Square(i n t l en , int width) ; -Square ( ) ( ) long GetPcrim() (return 4" Ge tLength() ; )
7] :
J;
74 : 75 : 76 : 77 :
Squar e : : Square(int len) : Rectangle(len,lcnl {}
78 : 79 :
Square , : Square(int len , int wi dth ) :
public Roc tangle
344 1V. rész • Örö~ódés és többalakúság 80 :
Rectangl e(l en,width)
81 : i f (Get.Length (j !" Ge tWidth () ) std : : cout « "Hiba, ez nem egy négyzet ... .. inkább egy Té glalap?\n" ;
82 :
83 :
84 : 85 :
86 :
int main()
87 :
88 : 89 :
int choice ; bool fQuit :: false:
90 :
Shape
* sp ;
92 :
while
(1)
93 : 94 :
{
91 :
95 :
std :: cout « "(l)KOr (2)Tóglal
96 : 97 :
s witch (chojce)
98 :
(
99 :
case l ,
100 : 101 : 102: 103 : 104 : 105 , 106 : 107 :
sp - new Circle(5) ;
break; case 2 :
sp:: new Rect a nglc (d,6); break ; case 3 : sp = new Square (5) ;
break ;
108 :
default :
109 : 110 :
fQuit ,.. true; break :
lll :
112 : 113 : 114 : 115 :
i f (fQuit) break ; sp->Draw () ;
std :: cout«
116 : 117 : 118:
"\n' :
return O;
menet (l)KOr (2)Téglalap (3)Négyzet
(O)Kilépés : 2
XXXXXl(
xxxxxx xxxxx x
xxxxxx Elvont rajzolási lépések ! (l)KOr (2)Téglalap (3)Négyzet (O) Kilépés : 3 xxxxx xxxxx
xxxxx XXXXX
==x Elvont ( l )Kőr
raj zolás i lépések! (2)Téglalap (3) Négyz et (O) Kilépés: O
A 3-12. sorokban, meghalt'irozluk a Shape osztályt egy elvont adauípusként, mindhárom tagfüggvényét üres virtuálisként megadva. Ne feledjük, hogy ez nem szükséges. Ha csak egyet is üres virluáliskétl1 adnánk meg, az osztályunk ugyanúgy ADT lenne. A ge tArea ( ) (:s ge LPerim() ragfüggvényeket nem valósÍtottuk meg, de a Draw ( ) függvényt igen. A Circ l e és Rectang l e osztályok mindketten felüldefini{llj~Ik a Draw () 11lgfüggvényt, és mindketten vissza hivatkoznak felfelé az alaposzLály mel6dusára, kihasznMva :IZ elosztott möködésbe>l eredő előnyöket.
Az elvonatkoztatás összetett hierarchiája Valahányszor cgy ADT-ból egy másik ADT-t sL1.rmazt.'mmk, el6jön, hogy bizonyos örökölt üres virtuális függv~nyeket megval6sítsunk, másokat viszont vált07.
példányosílhatÓ, de minden eml6s örökli a szaporodásra vonatkozó ket, azaz már nem kell felüldefiniálni.
működési
eleme-
A 18.7. Lista a fenti elgondolást mutatja be az oszlályok lecsupaszított megvalósítá.sával.
346 1V. rész • Örö~6dés és többalakúság
1B.7. Usta - ADT-k származtatása más ADT-kb61 {darivingadt.cpp)
o: l :
2: 3, 4: 5, 6, 7, 8,
9, 10 :
II 18.7 Lista II ADT-k szárma z tatása más ADT-kb61 #include enum COLOR { Hed, Green , Blue,
class Animal
Yellow, White, Black, Brown}
;
1/ közös alaposztály a lovak és madarak számára
(
public : AnimaI (int) ; virtual -Animat()
18 : 19 :
( std :: cout « "Anima! destruktor .. . \n"; virtual int GetAge() const ( return itsAge; ) virtual void SetAge{int age) ( itsAge = age ; ) virtual void Sleep() const z O; virtual void Est() const = O; virtual void Reproduce() const = O; virtual void Move() const .. O; virtual void Speak{) const = O; private : int itsAge;
20: 21 : 22 : 23:
Animal: :hnimal{int age) : itsAge(agcl
ll :
12 : 13 : 14 : 15 :
16: 17 :
24 : 25 : 26 :
(
std : : cout «
'Animal ko nstruktor . . . \n";
27 :
28 :
class Mammal
public Animal
29 :
30 : 31: ]2 :
3]:
]4 : ]5 :
36: 37 :
public : Manunal(int age) : Animal (agel { std :: cout « "Mammal konstruktor ... \n" ; } virtual -Hammal () { std :: cout « "Harranal destruktor ... \n";} virtual void Reproducc() const ( std: : cout « "Az eml6s0k szaporodásának leírása . .. \n"; ):
]8 :
class FiSh
39 : 40: 41 : 42: 43 : 44 : 45 : 46 : 47 : 48 : 49 :
public An imaI
public : Fish(int agc) : AnimaI (a ge) ( std :: cout« 'Fish konstruktor ... \n" ;) virtual -Fish () (std :: cout« 'Fish destruktor ... \n"; virtual void Sleep() const (std :: cout« "a halak hortyogása . . . \n" ; virtual void Eat{) cons t ( std ,: cout« " a halak étkezési szokása .. . \n· ; virtual void Rcprodu ce{ ) const
)
18. ó.. oA
( std :: cout « 'a halak i k rara kási szokása . . . \n" ; virtual void Move () const { std: : cout« "a halak ú szá s i technikája ... \n"; virtual void SpealcO const { }
50 : 51 : 52 : 53 : 54 :
55 : 56 : 57: 58 :
59 : 60:
kifinomult használata 347
};
clnss Horse (
public Manunal
public :
Horse(int age, COLOR color) : Mammal(agc) , itsColor(color)
61 :
{std :: cout«
'Horse konstruktor ... \n"; }
62 :
virtual -Horse()
63 : 64 :
{ std : : cout « 'Horse destruktor . .. \n'; } virtual void Speak () const
65 :
66 : 67 : 68 : 69 : 70: 71: 72: 73: 74, 75, 76: 77: 18 , 19: 80, 81: 82: 83: 81\, 85: 86 : 81: 88 : 89: 90 : 91: 92 : 93 : 94 : 95 : 96 : 97 : 9S , 99 : 100 : 101 : 102 , 103 :
(l:ItCl :: cout«
'Ny i haha! ...
\n ";
virtual COLOR GetltsColor() const { roturn itsColor ; } vir t ual void Sleep() const ( std : : cout « OA l ovak alvási szokásai ... \n' ; ) virtual void Eat() const ( std : :cout « "A lovak étkezési szokásai .. . \n" ; virtual void Move!) const ( std , :cout« 'A 16 futni szokott ... \n";)
protectod, COLOR itsColor; }; class Dog
public Hammal
public: Dog(int age, COLOR color) , Manm~l(age), itsColor(color) { std : :cout« "DOg konstruktor . .. \n"; } virtual -Dog() ( std: : cout « "Oog destruktor . .. \n'; ) virtual void Speak()const ( std: : cout« ·Vaú ! .. . \ n ' ; l virtual void Sloop() const ( std : : cou t « 'A kutyák alvás i szok ása i ... \n' ; ) virtual void Eat () const ( std : : cout « "A k utyák étk ezési szokásai ... \ n'; virtual void Move() con s t { std :: cout « "A ku t ya fu t ni szokot t ... \n " ; } virtual void Reproduce() cons t ( std :: cout « "A kutyák szaporodása . .. \n" ; ) protected : COLOR itsColor; l: int main ()
348
Iv. rész • Örö~6dés és többalakúság 104 : 105 : 106 : 107 : 108 : 109 : 110 : 111 : 112 : 113 : 114 : 115 : 116 : 117 : 118 : 119 : 120 : 12 1: 122 : 123 : 124 , 125 : 126 : 127: 128 : 129 : 130 : 131 : 132 : 133 : 13 4: 135 : 136 : 137 : 138 : 139 :
AnimaI *pAnimal =O: int choice ; bool fQuit = false; while (1) { std : : cout « • (l)Kutya (2)L6 (3)Hal (O)Kilépés : '; std : : cin » choice ; s witch (cho ice ) ( case 1 : pAnima l new Dog(5 , Brown) ; Ql;"eak ; case 2 : new Horse(4, Black) ; pAnimal break; c
break ; pAnimal->Speak() ; pAnimal->Eat () ; pAnimal->Reproduce() ; pAnimal->Move() ; pAnimal->Sleep() ; delete pAnimal ; std : : cout « "\n"; return O;
140 :
menet (l) Ku tya (2) L6 (3)Hal (O)KiIópós: 1 AnimaI konst r ukto r . Mamma! konstruktor . Dog konstruktor ... Vaú ! ...
A kutyák étkezési szokásai ... A kutyák szaporodása . A kutya futni szokot t . . . A kutyák a lvás i szokásai . Dog destru k tor ... Mamma! destru ktor . . . AnimaI destruk tor . . . (1)Oog (2)Horse (3)Bird (O)Quit: O
349
A 6-20. sorok közön adtuk meg az Animal osztály, mint elvont adattipus meghatározásál. Tartalmaz egy i tsAge () (kora) nem űres virtuális függvényt , valamint öt másik ürese!: Sleep (l, Eat (), Reproduce (), Move (), és Speak (). A Hammalosztály az Animal-b61 származik, a 28-36. sarok között található a meghatármása. Nem hoz létre új tulajdonságokat, de felüldefiniálja a ReprOduce () függvényt, általános formában leirva a sznporodásl az összes eml6sre vonatkozóan. A Fish osztálynak (elül kell clefinb'ílnia :l Reproduce () tagfüggvényt, mivel az közvetlenül az Animal OSZtá lyból szá rmazik, és nem tudja felhasználni a Mamma! osztály szaporodására vonatkozó !llliveleteket (ami egyébként így helyes). A Mammal-b61SZ!lrma7.Latoll osztályoknak nem keJl felü ldefini5lni II Reproduce () függvényt, de meglehetik, ha afra nn szükség (mint a Dog osztá ly esetében a 95. és 96. sorban). A Fish, a Horse és Dog osztályok mind felüldefiniálják az összes maradék tagfüggvényt, amelynek következtében ezek az oszt,1]yok példányosíthat6k. A program tönsében egy Anim!lltípusú ml..nat6t használunk a különböző szá rmaztatott objektumok e lé résé hez. Amikor a virtuális ragfüggvényckcl meghívjuk, a futásid6beni hozzárendeléseknek megfelel6en mindig a kívánt osztályok tagfüggvényei futnak le. Az AnimaI és M!lrnrMl osztálYOk példányosítására teu kísérlet fordítási hibához vezetne, mivel mindkett6 elvont adattípus.
Mely trpusok elvontak? Az egyik programban az Animal elvont oszttily, a másikban nem az. Mi hat5rozza mcg azt, hogy mikor kell egy oS7.tályt elvontként meghatároznunk? A kérdésre adott válasz nem a val6 világ valamely lényeges tényezője
I
350 V. rész • Örö~6dés és többalakúság
HoIya Használjunk elvont osztályokat, hogy
általános működést biz.tosítsunk egyes kapcsolódó osztályok számára Definiáljunk fe lül minden üres vimJális függvényt. Uresként határozzuk meg azokat a függvényeket, amelyeket kötelezőe n felül akarunk definiáltatni.
Ne próbáljunk elvonl osztályokat példányosítani.
Kérdések és válaszok Kérdés: Miko,. beszéhi.nk a ,, 1I117k6désjelszivárogfCIttisár61''l
Válasz: Arra az elképzelC:sre vonatkozik, hogya megoszlOlt funktiókat mozgassuk ~fel fele egy közös ll laposztályba. Ha egynéllöbb osztály osztozik ugyanazon a mtlködési módon, ajfmlatos egy közös ahlposzlályt találni a számára, ahol az adott függvényt mcgva l6síthatjllk. Kérdés: A IIl1ik6dés fc/szivároglattisa minden esetbell jó dolog?
"álasz: Igen , ha mindig több osztály által használt funkciókat sziv.1rogtatunk felfe lé, nem jó dolog azonban, ha amit mozgarunk, az valójában egy fe lület. Az..'lZ , ha ne m tudja nünden származtatott osztály használni az adon tagfüggvényt, a kkor hibás lépés közös alaposztályba tenni azt. Ha mégis ezt tesszük, futásid6bc n kell majd váltanunk atípusok között, és eldönteni, hogy hívharjuk-e az adon metódust az adott mUlatón keresztül. Kérdés: Miér' rossz dolog
cl
dinamikus /ípusvállás (dynamie eas/il/g)?
l/Ci/asz: A virtuális függvények lényege, hogya virtuális táblázat döntse el f'l.ltásid6ben a programozó helyett, bogy mi a típusa az adott objektumnak. Kérdés: Miérl bajládjllnk elvon / adallípusok létrehozásával? Miért nem htllározzuk meg őket nem elvon/kél/ t, és csak elkerüljük CI példá llyosí/tísát? Válasz: A legtöbb C++-ban bevezetett szabály, konvenció célja, hogyafordítóra bízzuk a hibák kiderítését, hogy eJkeriiljük a futásidőben bekövetkez6 hibákat, s a vásárlók a hibátlan programot kapják meg. Egy osztályelvontként történő meghatározása
1a.6ra-
kifinomult használata
(azaz, üres virtuális függvények szerepeltetése a meghatározásban) a fordító számára azt jelenti , hogy jelölje meg az összes objektumot, amelyeket elvont osztály példányosításával akartak létrehozni, és geneciljon fordítási hibát. Megvan továbbá az az előnye is, hogy felhasználhatjuk az osztályokat más al kalmazásokban, vagy átadhatjuk őket más programozóknak.
Gyakorlatok Miután megismertük a többalakúságot, vá1:lszoljuk meg a következő néhány kérdést, illetve oldjuk meg a feladato kat, hogy elmélyíL'iük a témában szerzet! tudásunkat.
Kvfz 1. Mi a különbség egy virtuális függvény és egy felüldefiniált függvény között egy
alaposztályban? 2. Mi II kmönb.'iég az üres és nem üres vimJális függvények között? 3. Az elvontak aZ elvont adattípusok? 4. Mién helytelen az alaposztályokba tenni olyan tagfüggvényekel, amelyek csak egyetlen származtatott osztály számára hasznosak?
Feladatok· 1. Módosítsuk a 18.7. Listában (derivingadt.cpp) bemutatott programot, úgy,
hogy példányosíL'iUnk A.nimal vagy Mammal típusú osztá lyokat. Mit fog mondani
a fordító, és miél'L? 2. Gondolkodjunk üzleti alkalmazásokban: milyen előnyt adhatnak számunkra az elvont adattípusok? Megjegyzés: A banksz;í.mlák nagyon hasonlóak, és hasonló;:m is viselkednek, de létezik egy csomó banks7.ámlatípus. 3. Mi történne a 18.2. Listában (dynamiccast. cpp) bemutatott progranunal, ha az 58. sorban elrávolítanánk az i f feltételt Ca 60. és 6 1. sorblIll ugyanúgy). Mely objektumok ml1köclnének helyesen, és melyek eredrnényeznének hibát?
352 1V. rész • Örö~6dés és többalakúság
Válaszok a kvfzkérdésekre 1. A vimJális függvények esetében arra szánútunk, hogya származtalOlt osztályok-
ban felüldefiniálják őket. 2. A nem üres függvényeket tartalmazó osztályok példányosíthat6k. Ha a vinuális tagfüggvények üresek, nincs futtatható kód, melynek eredményeképp fordítói hibát eredménye7., ha megpróbálunk egy ilyen osztályt példányosítani. Az üres virtuális függvényeket kötelező felüldefmiálni. 3. Valójában azért elvontak, mert vannak üres virtuális függvényeik, és önmagukban ncm példányosíthat6k. Nem készítbet6 bel61e konkrét példány, származtatnunk kell egy osztályt az elvont osztályból a megfelel6 tagfüggvényekkel, és a szá nnaztatott osztályt példányosíthat juk. 4. Egészen 6szintén gyenge tervezést feltételez. Elméletileg ,IZ abposztály meLóduS:1in:1k:1 lehető legtöbb, ha nem az ÖSS:les származtatoLL oszL{J lyra i11cszkcdnie kell. Az elképzelés, hogya származtatás során eg(:s:lítsük kl az aJapmetódusok:1l, és nem az, hogy ne foglalkozzunk bizonyos r(:szcivcJ.
19.
ÓRA
Láncolt listák Ebben az 6rában a következ6kr611esz szó: • • •
Mi az II láncollIista Hogyan hozh,llu nk létre láncolt listát IIogyan zárju k egységbe a funkcio nalitáSl az öröklődés r(:vt:n
Láncolt listák és egyéb struktúrák A tömbök olyanok, mint II Tupperware edények. Nagyszeru táro16helyet b iZLosít,l11ak, de megválLozLathat3tlan ti méretü k. Ha túl nagy edényt választunk, akkor fel eslegesen pazaroljuk erő"forrásainkat. Ha túl kicsit, akkor kiömölhel II tartalma, és óriási káosz lesz a konyhában. Az egyik megoldási lehet őség II láncolt lista használata. A láncolt lista o lyan adatszerkezet, amelyben kisebb tárol6egységek ~kattannak egymásbaH. Ebben a7. összefüggésben a tárol6egységek olyan osztályokat jelentenek, melyek a listába szervezendő objektumokat tartalmazzák. Az ötlet lényege abban áll, hogy olyan osztályt készítünk, ami egy
354 1V. rOsz • Örö~ódés és tóbb,lakúság ad..telemet tartalmaz (pl. egy macskát vagy egy téglabpoü, és rámutat a lista következő tárolóegységére. Minden tárolandó elemhez készítünk egy táro16egységet, majd a kívánalmainknak megfelelően összeláncoljuk őket. Ezeket a kis tárolóegységeket csom6pontoknak hívjuk. Az első csomópont alistafej, az uw!só a lista rarka. A listáknak három fontosabb változata vanj al egyszerúbbt61 a bonyolultabb felé haladva: • egyszeresen láncolt listák • kétszeresen h'incolt listák • fák Az egyszeresen láncolt listában minden csomópont a rákövetkező listaelemre mUlal. Egy adott csomópont megtalMásáhol a fejtől kell indulni, és v~gigjárni az addig vez<::16 Össze.~ csornópontol, mint a kincskeres6 játékba n CA következő lcvf!l a dívány alatt van~). A kél..Szerescn láncolt listáb,ln mindkét irányban lehet mozogni, előre és visszafelé is. A fa egy összetett struktúra, melyben minden csom6pont két (vagy három) másik csom6pomra mutathat.
•
19.1 ábra LlÍllcolt listák
A számításludományban más, ezeknél bonyolultabb és .okosabb" s[n.Jktúrákal is használnak; ezek általában egymáshoz kapcsolódó csomópol1lokból állnak.
Esettanulmány a láncolt listák használatáról Ebben a részben egy esettanu lmány kapcsán alaposan szemügyre veszünk egy láncolt lislál. Látni fogjuk, hogy miként lehet összetett adatstruktúrák:tl létrehozni, és - ami ennél is fontosabb - hogy hogyan használhatók ki az öröklődésben, többrétúségben és egységbe zárásban rej161ehet6ségek egy nagyobb projektben.
Afelel6sség átruházása Az objektum-oricntált programozás eb'Yik alapelve, hogy minden objektumnak legyen egy (j61 definiált) feladata , amil kitűnőcn el tud végezni, és ami nem szigon"ian az 6 h'ltáskörébe tartozik, azt bízza másra. Az autó remek pélJ{tj:j c:wn elv megva16sít5sCl11ak. fo. motor dolg:! :l forgal6nyom;lték biztosítása. Ennek a s:d:tosztása nem az ei feladata; CL már a sebességváltó és a fogaskerekek felelőssége. A fordulás lebet6ségét nem a motornak és a sebességváltónak kell biztosítania: ez a kerekek teend6je.
Egy jól megtervezett gépnek sok kisebb-nagyobb, jól megérten része van. Mindegyik jól végzi ;1 saját dolgát; egyiHt tudnak rm1ködni eb'Y magasabb cél érdekében. Egy jól megLervezett program is hasonl6 szerkezetű: minden osztály tudja a helyét, mint:l szemek ti kötötl mintálxm, és ezek együtt kiadnak például egy norvég mintás pul6vert.
Az. alkot6részek A lfincolt listánk csom6pontokb61 fog állni. A csomópontokat egy-egy absztrakt osztály jelenti, pontosabban ennek h{lfOm fajtája lcsz: egy oJy:1n, amelY:1 lislafejct képezi, lesz egy farka a listának (gondoljuk meg, mi lcsz ennek a feladata), és nulla vagy több köztes csomópont, melyek ri listába foglalandó adatokat fogják tartalmazni. Figyeljünk arra, hogy ri lista és az adatok különooz6 fogalmat takarnak. Elméletileg mindenfajta adatot listába szervezhetünk. Nem maguk az adatok vannak összekapcsolva, hanem a csom6pontok, amelyekben "mellesleg" adatok is vannak. A program végső soron nem tud a csomópontokr61j ez csak a listával dolgozik. A listának azonban önmagában nem kell sokat tennie: egyszeruen átadja a munkát a csomópontoknak.
19.1 Lista -láncolt lista használata (linkedlist.cpp)
o: ] : 2: 3: 4:
II ********************************* ****** ******** II 19 . 1 Lista II II Célja a láncolt lista szemláltetése II
35s 1v. rész • Örö~6dés és többalakúság 5 : 1/ 6 : /I
7 : 1/ 8 , 1/ 9 : 1/ Ez a prog ram a láncol t listát objektum orientált szcmsz6gbdl 10 : II közelíti meg . A lista át r uházza a munkát a csom6pontoknak ll : II A csomópont egy abszt r akt adattipus . Három fajta csom6pontot 12 : II használunk : fejet , lista f arkat és kOz tes csom6pontokat . 13 : 1/ Csak a kOztes csomópontok tartalmaznak adatokat . 14: 1/ 1 5 : 1/ A Pat a os ztá l yt anna k be mu t atására hozzuk létre , hogy a lánco l t 16 : 1/ list ában objektumokut tar t halunk . (Pl . egy nOvekv6 szárnsorozatot . ) 17 : 1/ 18 : 1/ ~"'."'* ' ** * "*'*.*'**""* . ".* ""* **'***'.'
19 : linclude 20 , 21 : e num { kIsSrnal1er , kIst..arger , kIsSame} ; 22 : 23 : 24 : 25 : 26 : 27 : 28 :
29 : 30 : 31 :
32 : 33 :
34 : 35 :
36 : 37 : 38 , 39 : 40 : 41 : 42 ,
43 : 44 ,
45 : 46 :
47:
II ll. lán co l t listáb!l ágyazandó a d a t osztá l ya II A láncolt lista minden osztályának kell ismernie k6t metódust : II Show (megmutatja az értéket) és Comparc II (visszatérési értéke a relstiv pozíció) clau Data ( public : Datalint val ) :myValuelvnl){) ~ Dnt(l() {} int Compare(const Data &) ; void Show() { std : : c out « myValue « std : : endl ; ) private : int myValue ; }; II A Compare fuggv énnycl dOn th e t 6 cl egy · egy a da t II elhelyezk edése II 1 istában . i.nt Data : : Compare{const Data'" theOtherData) { if (myVnlue < the0therData . myValue) return kIsSmaller ; if (myValue > t he0therData . myValue) return k I sLargc r; else reLurn kI sSame ;
48 :
49 : 50 : 51 : 52 : 53 : 54 : 55 :
II további deklarációk class Node ; class HeadNod e ; class Tail Node; class Interna lNo d e ;
56 : II Az a lábbiak jeleni tik meg a lista csomópont i objektuma i t 57 : II Minden származtatott osztálynak felül kell i rnia az Insert és _ a Show me t Ódust .
19. óra· Láncolt li 58 : class Node 59 :
(
60 : public : 61 : Node () () 62 : virtual -Node () () 6] : virtual Node" Inscrt(Data .. 64 : virtual void Show() = O; 65 : private: 66 , ); 67 : 68 : 69 : 70 : 71 : 72 : 73 , 7 4: 75 : 76 : 77 , 7B :
79 , 80 :
theData) ~ O ;
II Ez az aktuális objektumot hordozó csomópont . II Esetunkben az objektum Data tipusú II K6s6bb, amikor a sablonokról lesz sz6, látni fogjuk, II hogyan tehct6 meg mindez még általánosabban class InlernalNodc: public Node ( public , Inter:"nalNodc(Oata .. theData , Node " next) ; virtual -Interna l Node() f delete myNcxt ; delete myOata ; ) virtual Node" Insert (Data ' theData) ; virtual vojti Show() ( myData >Show() ; rnyNext->Show{) ; } II átruházás!
81 : private:
82 : Data" myData; II maga az adat 8] : Node ' myNext; II a láncolt lista kövctkez6 csomópontjára mutat 84: }; 85: 86 : II A konstruktor csak inicializál 87: InternalNode: : InternalNodefData .. theData, Node" next): 88 : myData(theData),myNcxt(next) 89: (
90: ) 91: 92 : II a lista tartalma 9] : 1/ Ha új elemet adunk a lJstához, 94 , 1/ akkor ez egy a csom6ponthoz kerul , ami eldönti, hogy merre 95 : II kell tovább haladnia az új elemnek , hogy végal helyére keruljön. 96 : Node" InternalNode : : Insert(Da t a ~ theData) 97 : 9B :
99 : II Az új tag ki~ebb vagy nagyobb nálam? 100 : int resu l t = myData->Comparc (*thcDat a) ;
101 : 102 : 10] : switch(result) 104 : ( 105 : II közmegegyezés szerint az az első vizsgálat, hogy azonos-e 106 : case kIsSame: II átcsorgunk 107 : case kIsLarger: II az új adat elém ke rül lOB : f 109 : InternalNode * dataNode = 110 : new InternalNode(theData, this) ; lll , return dataNode;
357
358 1V. rész • Örö~6dés és többalakúság 112 : 113 :
114 : 115 : 116 : 117 :
/ / nagyobb nálam, így átadom a következ6 csom6pontnak, / / kínl6djon meg VELE inkább ő . case kI sSmaller : myNext = myNext->Inscrt(theDatu} ; 118 : r eturn thi s ; 119 : 120 : return this ; 1/ a fordítóprogram megnyugodhat 121 : 122 : 123 : 124 : / I A lista farka csak egy őrszem 125 : class TailNode public Node 126 : { 127 : public: 129 : 'l'ailNode () () 129 : virtual -TailNode() (J 130 : virtual Node * Insert(Oata * theData) ; 131 : virtual void Show() { } 132 : private: 133 : l; 134 : 135 : 1/ Ha hozzám érkezik egy adat. akkor annak elém kell kerl11nie. 136 : /1 mert én vagyok a vég, és SEMMI scm jöhet utánam 137 : Node ~ TailNode :: Insűrt (Data * theData l 138 : ( 139 : InternalNode • dataNode = new Interna1Node(theData, this) ; 140 : rcturn dataNocle ; 141. :
142 : 143 : II A fej nem tartalmaz adatot, hanem rámutat 14 4: II az ada tlista elejére 145 : class HeadNode : public Node 146 : ( 147 : public : 148 : HeaclNode(); 149 : virtual _HeadNodc() ( delete myNcxt ; I 150 : virtual Nod ű * Insert(Data * the~ta); 151 : virtual void Show() { myNext->Show(); } 152 : pr ivate : 153 : Node * myNcxt; 154: } ; 155 : 156 : 1/ Mihelyt kész a fej . 157 : 1/ létrehozza a farkat 158 : HeadNodc, : HeadNade ( ) 159 : { 160 : myNext .,. new TailNode ; 161 : ) 162: 163 : 1/ Semmi nem állhat a (ej előtt, igy továbbadja a z adatot 164 : 1/ a köv e tk ező csom6pon tnak 165 : Node * HeadNode :, I nscrt( Data * theData)
19. 6ra-
166 : 167 : myNext = myNext-> Inser t(theData) ; 168 : re turn thi s ; 169 : 170 : 171 : II Minden hatalmat én kaptam, és én nem fogok dolgozni 172: class LinkedList 173: ( 174 : public: 175: LinkedList(); 176 : _Li nkedList () { delete myHead; } 177 : void Insert (Data * theData) ! 178: void ShowAl1{) ( myHead->Show{) ; 179: private , 180: HeadNode ~ myHead; 181, }; 182: 183 : II Születésemkor létrehozom a lista(ejet 18 4: 1/ Az megalkotja a farkat 185: II igy az üres lista mutatójo a fejre mutat, 186: II ez pedig a farokra . és közt.ük nincs sellVlli. 187: LinkedList: : LinkedList() 188: ( 189: myHead = new HeadNode; 190 : ) 191 : 192 : II Átruházás, átruházás , átruházás 193 : void LinkedList : : Insert(Data • pDaLa) 194: ( 195 : myHcad->Insert(pData); 196: ) 197 : 198 : II teszteloproqram 199 : int main() 200 : ( 201 , Data * pDat.a ; 202, int val; 203 : LinkedList. ll; 204, 205 : II Kérjünk a fe1haszná16tól néhány értéket 206 : 1/ és t együk ezeket a listába 207 , for (;;) 208 : { 209 : std : : cout « -What value? (O to stop) : - ; 210 : std : : cin » val; 211 : i f (lva!) 212 , break; 213 : pDat a = new Data(val) ; 214 , ll . Insert (p Data) ; 215 : } 216 :
I
360 V. rész' Öröklődés és többalakúság 21 7 : II Most j árjuk be a listát és mutogass uk meg az ada tokat 21 8 : 11 . ShowAl 1() ; 219 : return O; 1/ II hatókör ön kivűl ke rű l és mcgs zan i k 220 : }
menet value? (o to What value? (o to Whac valuc? (o to Wh ot value7 (o to What value? (o to What va luc? 10 to Wh ot value? (o to Wh ot
stop) : 5 s t op) : a
stop) : 3 !S t op) : 9 s t op) : 2 stop) : 10 s t op) : O
2 J
,, 5
10
EI.
s
konst~n!i három értékkel: kIsSmaller, kIsLa.rger, és kisSame (kisebb, nagyobb, egyel/ló), A lista minden objektumának ismemie kell a Compar e () függvényl; ezek .1 felwrolásos vfiltoi'.ók ennek
Az els6, ami a szemünkbe ötlik , az egy felsorolásos tTpusú
a függvénynek a visszatérC!si értékei.
A 27-36. sorban létrejön az illusztrációs célú Data osztály, ennek Compare () metódusa a 40-48. sorban készül eL A Da t a osztálynak egyetlen stiÍmC:rtékc van, amely össze tudja magát hasonlílllni ( compa re) egy másik Data osztá ly száml!rtékével, illetve V'1n egy Show() tagfüggvénye is, mellyel ki lehet íratni a számértéke!. A láncolt lista megértésének legegyszeníbb mMja, ha végign&zzük példánkat, amely azt haszn:ílja. A 199. sorb:m kezdód ik a főprogram. A 201. sorban egy Dat a objektum"l irányul6 mutatól hozunk lérre, a 203. sorban ped ig egy helyi láncolt listát defjni:1Iunk. A láncolt lista létrehozásakor a 187. sor konstruktora indul cl. Ez csak annyillesz, hogy elhelyez a memóriában egy HeadNode (lis/aJf!) csom6jJ()//t) ubjektumot, és ennek d mét átadja annak a myHead mutat6nak, ami a láncolt lista fe jét tartalmazza (180. sor). A HeadNode elhelyezése meghívja a 158. sorban definiált HeadNode konstruktort, amelynek felad
• láncolt listák 361 ily módon pusztán azzal, hob'Y elhelyezünk egy láncolt listát a veremben, létrejön an~ nak feje és farka is, b kiépülnek a megfele l ő kapcsolatok - ezt mutatja a 19.2 ábra. Lárlcolllisla Kezdő
csomóporlt
Záró csomóporlt
19.2 ábra A fcí/Jcolllis/a l.!ezdót.ifflljJOla
A 207. sorban egy végtelen ciklus kezdőd ik. A felhasználó bcírll ~lt kül ö nböző számértékeket, melyek a h'inc.:olL lista elemei lesznek. B5 rmeiuoyi értéket he lehet írni ; a O beírásával lehel befejezni a l:>evilelt. A kód 211. som éJtékeli ki a bevitt sZ{lmol, és ha ez O, akkor kilép II ciklusbóJ. Ha nem O-l írt be a felhasználó, akkor egy új Data objektum jön létre a 213. sorban, melyet a 214. sorban illesztünk a listiiba. Tegyük fel, hogy 15-öt írt be a fe lhasználó. Ez meghívja ri 193. sorban található Insert () taglUggvényt. A láncolt lista azon nni leoszt ja a listarejnek az objektum bcszúr{ls5n:tk fe le!6sségéc, amely a 165. sorban található Inuert () tagfüggvényt hívja meg. A fej sem viseli sokáig a munka felelősségé n ek terhét, hanem azonnal ,ítruházza a myNext áltlll mut:llotl csom6pontnak, amely (a legels6 pillanatban) a lista [
a saját myData mutatóját, amelyet Data p
362 V. rósz • Örö~6dés és . Node: : Insert () metódus visszatérési értéke. Visszakerülünk a HeadNode : : Insert () tagfüggvényhez, amely a 167. sor szerint hozzárendeli a kapott friss InternalNode CÍmel a saját myNext mutatójához. Végül a HeadNode eimét megkapja a láncolt lista, amely azlán semmit scm csinál vele (195. sor), hiszen 6 már rudja a HeadNode eimét. Miért bajlódunk akkor ezzel a visszatérési értékkel, amit úgyis eldobunk? Az Insert-ct a bázisosztályba n (Node) deklarálru k. A visszatérési érték más megval6sítások swmám fomos. Ha me~:válloztaljuk a HeadNode : : Ins ert () visszatérési értékél, fordítási hibát kapunk. Érdemesebb egyszerűe n visszaadni a HeadNode-ol és e lhajítani a kapun címet.
Mi is törLénllehát? Bckcrült egy adat a láncolt listába, melyet a lista át.adoll a fejnek. A fej eZl vakon továbbadta oda, ahová a saját , következő elem" (myNext) mut3lója é ppen irányult. Az első alkalomma l a fej é ppen a lista farkára mutatoll, amely meghívásakor rögtön létre is hozott. egy köztes csomópontol. Ennek " következő elem"-e a lista farka lett. A farok viSSzaadta a friss csomópont dmét a(z 6l meghívó) fejnek, amely átállította " követ kező elem" mutatóját a friss csomóponLr
láncolt lista Záró csomópont
Kezdö csomópont
Belsö csomópont value ~1 5 myO~la
"'"
19.3 ébra il láncolt lis/ti (jz első elem />esnírása Illáll
A 195. sorba n a lista újra átadja a fejnek a kapott adatot. A HeadNode: : Insert () most is annak adja át ezt, akire a myNext mutat. Mint tudjuk, ez most az a csomópont, amelynek Data része 15-öt tartalmaz. Ez meghívja az InternalNode : : Insert () metódust a 96. sorban .
• láncolt listák 363 A 100. sorban az akmális Int er nalNode ös..<;zehasonlitja a myData állal mulatott számértéket (ami most 15) azzal a számmal, ami a kapott t heData objektumban van (ez most 3), mégpedig a Compare () tagfüggvénnyel (40. sor). Az összehasonlítás során az derül ki, hogya 15 (myValue) nagyobb, mint a 3 ( t he OtherData . myValue), így a kIsLarge r lesz a visszatérési érték. A vezérlés emiatt a 107. sor,..1 kerül. Az új Data objektum számára létrejön egy új I nternalNode csomópont. Ennek nkö-
vetkcz6 eleme az aktuális InternalNode objektumm fog mutatni. A frissen keletkezett InternalNode címe lesz az InternalNode : : Insert () visszatérési értéke, melyet a HeadNode megkap. í~.'y az új csomó po nt, melynek tárolt s7..ámértéke kisebb, mint mint az akmális csomóponté, beilleszl5dik a listába. Ez most a 19.4 ábrának megfelel6 en néz ki.
II E-láncolt lista
Kezdő csomópont
Záró csomópont
~~
BeIS6 csofTlÓ!lOnt
Bets6 C10l11Ó!lonl VOlue .. 1S
Data myNexl
19,4 ábra A MI/col/lis/a a második elem lx'Szlírtím uM"
Tegyük fel, hogy a ciklus harmadik meghívása kor a felhasználó B-at üt be . Ez nagyobb 3-nál, de kisebb lS-nél, így a két köztes csomópont közé kell kerülnie. Pontosan úgy fog minden történni, mim az előzéS példában, azzal a különbséggel , hogya 3-mal való összehasonlítás Ulán nem a kl sLarger, hanem a kl sSmal ler lesz a Compare () visszatérési énéke (azaz az akmális objektumbeli 3 kisebb, mint a friss objekmmbeli 8). Ez az InternalNode : : I nse rt () tagfüggvény összehasonlító elágaL'lsai közül ez alkalommal a 116. sorban lev6 fut Ic. Ahelyett, hogy egy új csomópontot hoznánk lérre (és szúrnánk be az aktuális elem elé), az Interna lNode továbbadja az új adatot annak a csom6pontnak , ahová a myNext mutat j ennek Insert () tagfüggvénye hívódik meg. Esetünkben annak a köztes csomópontnak abeillesztő metódusa kerül meghívásra, amelynek adaltagja a l5-öt tartalmazza.
Újra összehasonlítódnak az értékek; most már létre kell hoznunk az új csomópontot. Ennek .rákövet kező elem"-e a 15-öt tartalmazó köztes t:somópontra mulat, és saját címe annak a csom6pontnak kerul átadásra (visszatérési értékként), amelynek adanagja a 3-at tartalmazza, ahogy ez a 118. sorban látszik. Mindennek hatására az új csomóponl a lista megfelel6 helyére
illesztődik
be.
Végs6 eredményké nt pedig egy nagyság szerint. rendezett lista van a kezünkben, függetlenül an6l, hogy milyen sorrendben vittiik be az adatokat.
Mi a láncolt listák tanulsága? Ahogy Dorothy mondja az "Óz, allagy varázslo'" vége felé: "Ha újm szívem vágyait követem, nem megyek többé messzebb il hátsó udvaromnál". Ahogy .mindenütt j6, de legjobb otthon", ugyanígy párja nincs az cljárásközpontú (jJrocedurtífis) programozásnak, amelyben egy vezérl6 metódus vizsgálja meg az adatokat és a hfv6 függvényeket. Ehben az objektum-orientált megközelítésben minden objektumnak megvan a szűk, de jól definiált felel5sségi köre. A láncolt lista főobjektuma csak a fejérl felel6s, ez viszont azonnal továbbadja az új adatot annak a csom6ponlnak, .unire mutat, függetlenül att61, hogy az éppen mi is. A lista farka , ha adatot kap, azonna l létrehoz számára egy csorn6pontot maga el6tt, és oda illeszti be. Ez csak ennyit tud: adatot kaptam - beszúrom mag'lm elé. A köztes csom6pontok alig bonyolultabbak. Saját adattartalmukat hasonlítják össze a kapott adattal, és ennek eredményét61 függ6en beillesztik vagy továbbadják. Figyeljük meg, hogy magának a csom6pontnakfo,galma sincsarr61, hogy mikfnl7..ajlik az összehasonl1tásj ez az objeklumok dolga. A köztes csom6pont csak meg tudja kérni az objektumokat, hogy hasonlítsák össze magukat, és a három IchcL<;éges V{IIaSZ közül vár t:!gyel. Az egyiknek :1 hatására beilleszti, a másik kett6 hatására a továbbadja az adatOL, nem törődve azzal, hogy mindez hogyan fog végzódni. Akkor hát kit: is a fe lel6sség? Egy jól megtervezctt objektum-orientált programban kié. Minden objektum leszi a maga apr6 dolgát, és a gépezel si:'.épen mCíkÖdik.
SGll-
A láncolt lista szépsége, hogy bármilyen jellegű adat lehel a Data osztályban. Esetünkben ez egy egész szám volt, de ez lehet akár több beépítetl adattípus vagy bármi más objeknun (akár láncolt lista is). Egy olY'1Il projektben dolgoztam, melyben különböző fcjczetcket kellett létrehozni, melyek szöveges bekezdéseket tartalmaztak. Adatbázis tárolta az adatokat, így bármelyik fejezetet e l ő lehetett hívni. Belsőleg a dokumentum fejezetek láncolllislája voll; fe-
19. óra • láncolt li jezetcím, fejezeLS.cim, még ez-az a fejezet ről, majd bekezdések lá ncolt listája köveckezeu. Ez utóbbi minden egyes fejezctr61 lan.almazolt némi információt és magát a bekezdés szövegéL A dinamikus memória használata lehec6vé teszi, hogy a láncolt listák minimális memóriát foglaljanak el akkor, amikor a listák kis méretűek, vagy rengeteg memóriát, ha nagy méretűek. Igen fontos jellemz6jü k, hogya tárolt adatok mellliyiségével arányos a méretük. Ezzel szemben a tömbök el6re lefoglalnak valamen nyi helyet, ami egyszerre paza rlás és korlátoz.-'ís. Apropó
Miért nem használunk mindig láncolt listát?
Joggal velódi k fel a kérdés, hogy ha a láncolt listák ilyen hatékonyak, akkor miért nem mindig ilyeneket használunk? Azért, mert ennek a hatékonyságnak ára van. A láncolt listák szekvenciális szerkezetűek, azaz csak az egyik végén lehet nekikezdeni a keresésnek, és lehet, hogy egészen a lista farkáig végig kell rágnunk magunkat a láncon (hogy megtaláljuk a keresett elemet). Ez az elérési mód alkalmanként lassú lehet. Gondoljunk a telefonkönyvre. Milyen lassú lenne benne a keresés, ha mindig végig kellene olvasnunk az e lej étől az összes számol, míg rábukkanunk a miénkre. A legtöb· ben szeleteléses technikával dolgozunk, azaz valahol a könyv közepe láján kezdjük a keresést, vagy ott, ahol a me gfe l e l ő kezdóbelút sejtjük; aztán a találattól függóen valamelyik irányban elmozdulunk, és ezt addig ismételgetjük, amfg meg nem találtuk a megfeleló nevet. Számítástudományi kifejezéssel élve ez a logaritmikus keresés.
Kérdések és válaszok Kérdés: Miél1l1oz'/Iánk lé/re láncofllislát, Ita egy tömb is IIIeJifeld a célnak? Válasz: A tömbök nek rügzítclt ml!relük van, ezzel szemben a láncolt listák mérete dinamHcusan v{lltozhat futás közben. Kérdes: MiérI IJl'II6/1í!)/ik el az adatohj ekllllll o/ a
cso mópo lltló/.~
Válasz: Ha van egy jól működ6 láncoll listá nk a megfelel6 csomóponlokkal, akkor ez a program újrahasznosíthat6 bármilyen adatobjektumok számára, melyeket listába szeretnénk rendezni.
Kérdés: Ha más objeklumokClt is/cl szere/nén k ve/mi a listába, a kJ.'O r akJ.'O I" líj lista tí-
pust es lij csomópollllípUSI kell defilliálni? Fáiasz:
Egyelőre
&'Yalásakor.
igen. Szebb megoldást is láthatunk: majd a 23. ór.:í.ban, a sablonok lár·
365
366 1V. rész • Örö~6dés és 'öbbalakúság
Gyakorlatok Ebben az órában egy meglehetősen összeten adatszerkezettel, a láncolt listával ismerkedhettünk meg. SZ<1njunk most egy kis időt néhány kérdés megválaszolás:kJ és végezzünk el néhány feladatot, míg a tanultak frissen élnek emlékezetünkben!
Kviz 1. 2. ;. 4.
A Iist:íknak melyik három típusát ismertük meg? Mi a szerepe a fcj mutatójának? Mi a szerepe a "következ6~ mutatónak? Mi kell egy kétszeresen 15 ncolt listához?
Feladatok l. Fuuassuk le a linkedlist . cpp progr.:lmol (19.1 Lista) nyomkövet6 módban, és lépegesliünk végig néhány csomópont bcszúrás{tn. Figyeljük meg, ahogy ezek a mel6dusok egymást hívják, és figyeljük meg a mutatók megfe l elő beállítását is. 2. M6dosítsuk a linkedlist . cpp programot 09.1 Lista) oly módon, hogya Data osztály az egész helyett egy dupla pontosságú számot (doIIbie) tartalmazzon. Mit kell álírni? 3. M6dosítsuk tovflbb II linkedlist . cpp programot 09.1 Lista) úgy, hogy a Data osztály több beépítelllípust Lartalmazzon (ne csak eg(:szet vagy lebeg6pontosat). Mit kell átírni? Ötlet: a megfelelő ..kisebb" és ~nagy()bb~ döntéshez szükség lesz va1:lmiféle ku lcsmez6re.
Válaszok a kvfzkérdésekre ]. Egyszeresen lflncolt, kétszeresen láncolt listák és a fák. 2. Ahogy II neve is mulat ja, ez II lista fejének (vagy cSÚCSfiiKIk vagy kezdetének) <.:ÍméL tflrol ja. Igen fontos tudnunk, hogy hol is kezd6dik a lista, hogy elérhessük az eb'Yes elemeket! 3. Ahogya neve is mutatja, ez a lista következő e lemének <.:Ímél tárolja. Úgy is felfoghatjuk, mint a lista maradék részének a fejét. Ez a voltaképpeni láncszem a láncolt listában! 4. A láncolt listának van fej- és farokmutatója, valamint az egyes csom6ponroknak ~e I 6z6~ és "következő" mutatóik. Ennél fogva bármelyik végen kezdhetjük II keresést, haladhatunk a másik végpont felé.
VI.
RÉSZ
Különlegességek 20. óra 21. óra 22. óra 23. óra 24. óra
Különleges osztályok, függvények, mutatók Az eldfeldolgozó Objektum-orientált elemzés és tervezés Sablonok Kivételek, hibakezelés és néhány tanács
20.
ÓRA
Különleges osztályok, függvénye k,
mutatók Ebben az 6rában a következőkJőllesz sz6: • Mik azok a statikus tagváltozók és statikus ragfüggvények • Hogyan használjuk a statikus tagvá ltoz6kat és statikus tagfüggvényckcl •
Mik azok
• •
Hogyan használhat juk II barátfüggvényeket speciális problémák megoldására Hogyan használjuk a tagfüggvényekre vonatkozó mutatókat
II
barácfüggvények és barátosztályok
Statikus tagváltozók Az eddigiekben úgy képzeltük el az objektumok tagváltoz6it, hogy azok egy adon objektumhoz tartoznak, és SCffilni közük az osztály többi példányához. Ha van például öt macska objektumunk, akkor mindeb'Yiknek megvan a maga kora, súly,\ és egyéb adatai. Egyiknek II kora nem befolyásolja a másikét.
I
370 VI. rész • KilIönl.gesség.k Vannak helyzetek, amikor olyan információkal szeretnénk számon tartani , amely egy osztály több példányától is függ. Például kíváncsiak lehetünk ami, hogy eddig összesen hány macska született, vagy hogy hány van még életben. A többi t:lgvához6t61 eltérően a statikus (static) tagváltozókat egy osztály minden példánya elérheti. Átmenetel képviselnek a globális adatok közön, amelyek a progmm minden résl-ér61 elé rhetőek , valamint az adattagok kÖZÖlt, melyeket csak az egyes objektumok érhetnek cl. Gondolhatunk úgy a statikus tagváltoz6kra , mint amelyek nem az objektum hoz tartoznak, hanem az osztályhoz. A normál adattagok objektumonként, a statikus adattagok oS7.1[llyonkénr léteznek. A 20, l Listában deklarál unk t!gy Cat osZtályt egy statikus adattaggal , II HOwManyCats-szel ( Hál1yMacskaVall). Ez a v5 1toz6 kövel i nyomo n II macsk;ík lélszám5t; a macskák léLrehozásakor növeljük, l ebont~ sa kor pedig csökkent jük ;1 HowManyC
20.1 Lista - Statikus adattagok használata Istaticmember.cpp)
o: /1 20 .1 Lista Statikus adattagok 1 : hnclude
,,, ]
"5 ,
,.
7,
8,
9, 10: 11 : 12 : 13 : 14 :
class Cat public: Catlint age = 1) : itsAgelage) (HowManyCats++; virtual -Cat() ( HowManyCats - ; ) virtual int GetAge() ( rcturn itsAge; age; virtual void SetAge(int age) ( itsAge static int HowManyCats; private: int itsAge ;
15 : 16 :
};
17 : 19 : 19 : 20 : 21 : 22 : /.3 : 24: 25 :
int Cat : : HowManyCats - O; Lnt main() ( const int MaxCats = 5 ; Cat *CatHollse [MaxCats ] ; int i ; for li = O; i
26 : 27 : 28 :
29 : 30 : 31 :
for
li = O; i
"There are "; Cat: : HowManyCats ; " cats left ! \n" ;
)
20. óra • 32 :
std : :cout « "Deleting the one which is " . std: :cout« CatHouse[ij ->Ge tAge () ; std: :cout « " years old\n "; delete CatHouse[il ; CatHouse[il = O;
3] :
34 : 35 : 36 : 31 :
38 : 39 :
return O:
There are 5 cals Deleti.ng the one There ara cats Deleting the one There are cats Deleting tho one There ara catll Deleting the ooe There arc 1 cats Deleting th. ono
,
, ,
left! which ie O years old left ! which l ycars old left ! which 2 years old lef t ! ycars old which left ! which io 4 years old
" ", "
A 3-15. sorb:1n egy egyszen1sített macska osztályt deklarál unk. A lD.sorban deklará·
lunk egy egész típusú statikus tagváltoz6t, a HowManyCats-C1.
A HowManyCats de klar.'iciója nem foglal le egy egész számnak vlll6 mcmóriahelyer. Ellentétben a nem-statikus változókkal, semmiféle helyfoglalás nem törté nik egy Cat objektum példányosulásakor, me rt II HowManyCats tagváltozó nem része az objektumnak. Emialt külön kell inicjalizálni a 17. sorban. Gyakori hiba, hogy elfelejtjük definiálni az osztályok statikus tagváltoz6it. Figyeljünk arra, hogy ez ne történjen meg! Ha netán mégis el6fordulna , a linkcr eZI egy hibaüzenettel jelzi, például az alábbi módon Cllorlandnál): ' Er ror : Unresolved external ' Cat : :HowManyCa ts' referenced from C: \PROJECTS\STATICHEMBER.Oru" Azaz : feloldatlan kalso ' Cat : : HowManyCats , hivatkozás a ... fá j lb61 Vagy minl a következ6 sor (másfajta linkelt használva): undefinc~reference toCat : ,HowManyCats Azaz: hiányzó hivatkozás :
Ezl nem keH meglenni az itsAge számára, mivel ez nem statikus tagválloz6, és mindig létrejön, amikor egy macska objektum megszületik - in ez a 25. sorban val6sul meg.
371
372 1VI.
resz • Különlegességek A macska ko nstruktor..! inicializálja a kort, és eggyel megnöveli a statikus tagváltozót a 6. sorban. A destniklor eggyel lecsökkenti ezt a 7. sorban. így mindig aktuális marad a létrehozott, de még nem törölt macskák létszá ma a HOwManyCa ts-ben.
A 19-39. sorban a
főprogram
Öl macskát példányosít, és l>cmkja
őket
egy CatHouse
( MacskaHáz) tömbbe. Ennek sorá n ötször hív6
Cats ötször növe1 6dik meg a kezdeti O é rték ről. A program ezután egy ötös ciklusban nézi végig a tömb öL cle mét , és az aktuális macska mUlll16 tö rlése előtt rendre kiirja, hogy hány macska él még. A kimcnctb6llátszik, hogya kezdeti 5-r61 (hiszen végül is 5 macskát hoztunk 161re) minde n törléssel eggyel csökken" macskák szá ma . Figyeljük meg, hogy a HowManyC~ts nyilvános válLOZ6, így közvetlenül a r6program kezelheti. Nincs oku nk arra, hogy ilye n módon tegy(ik ki különr~le veszélyclme k ezt a tagváltoz6l. Jobb volna priváuá lenni a többi t.1gváltoz6 val együll, és inkább egy nyilvános hozzáfér6 függvényt kellene megadni , hiszen úgyis c5.1 k macska példányo kon keresztül szeretnénk hozzáférni /:!hhez az adathoz. I-Ia netán macsk:! nélkül is szeretnénk manipulálni eZl HZ adatot , k é l alte rnalivánk van: nyilvános cl6réslinek fll /:!ghagyni, vagy irni hozzá cgy statikus lagfüggvényl.
Statikus tagfüggvények A sm(iklls lagfüggvé nyek olyanok, mint a statikus tagváltoz6k: nem objekturnho z tartoznak, hanem egy osztályhoz. így anél kül is mcghívhat6ak, hogy lenne példányunk az adott osztá lyból , ahogy az a 20.2 Listában is látszik.
20.2Usta - Statikus tagfüggvénvek használata (staticfunction.cpp) 0 , 1/ 20 . 2 Lista St.atikus tagfl1ggvények l : U nclude 7. : 3: class Ca t 4: ( S: public : 6: Ca t (int ag e '" l) :i t sAge ( age) (HowMünyCa ts ++ ; 7: vi rtual -eat ( ) ( HowMa nyCats-- ; J 8: virt u al int GetAge() { return i tsAge ; } 9: v i r t ual void SetAge( in t age) { it sAgc '" age ; ) 10 , static int Ge t HowMany () { return HowMa nyCat s l l 11: priva t e : 12 , int i ts Agei l]: stat ic int HowManyCats;
14 :
};
15 : 16 : 17 :
int eat : , HowMa nyCat s = Oi
20. óra· 18 :
373
void TclepathicFunction();
19 , 20 :
i nt main()
21 : const int MaxCats '" 5 ; Cal *CatHouse[MuxCato) ; int i; for (i '" O; l
22 :
23 : 2 4: 25 : 26 : 27 :
(
Ca tHollse{i] = new Cat(i) ; TelepathicFunction();
28: 29 : 30 : 31 :
for ( i = O; i
32 : 33 :
34 : 35 :
return O;
36 : 37 :
38 : 39 : 40 :
void TelepathicFunction () {
std : : cout « "There are « " cats nlive!\n" ;
41: 42 :
«
Cat : :GetHowMany ()
43 :
Kimenet There 'I'hero There There There Thcre There There There There E~
""
.ro ar, aro ar, ar, ar, are .,.re
1 cats alive
,,
2 cats alive
S 4 3 2
1 are O
cats cats cats cats cats cats cats cats
alive alive alive alivc alive alive aU ve alive
•
Ebben a prog'dmban (az elózőve l ellentétben) privál elérés\1nek deklaráltuk a HowManyCats statikus tagvállOz6t a Cat dckbrftci6jának a 13. soráb.m. A GetHowMany () nyilvános hozzáfénS függvényt viszont stmikusnak és nyilvánosnak adtuk meg a 10 .
.sorban. Mivel a GetHowMany () nyilvános, bármelyik függvényhől elérhető, és mivel statikusnak deklarftlluk, nincs szükség létező macskára a meghívásához. Ezért rt 41. sorban ál16 'l'e lepathicFunction () el tudja érni a nyilvános és statikus hozzáfér6 függvényt,
374 1VI. rész • Különlegességek bár magukhoz a macskákhoz nincs hozzáférése. Te rmészelesen az elé rhető macska objektumokon keresztül is meg lehetne hívni a Get HowMany () függvényt a főprog mm ban, mint ahogy bármely más hozzáféró fü ggvényt is.
Apropó
A this mulató
A statikus tagfüggvényeknek nincs thi s mutatójuk, tehát nem is lehet őket konstansként deklarálni . Mivel a t his mutatón keresztül érik el a tagfüggvények is
az adattagokat, a statikus tagfü ggvények csakis statikus adattagokat tudnak elérni.
Egymást tartalmazó osztályok Mint ahogy az már a korábbi péld5 kon is 1(uszott, az osztályok ada ttagjai lart<1lmazhaljii k más osztályok objektumpéldányait. A C+ + programozók úgy mondják, hogya kül56 osztály tartalmazza a bels6 osztályt. így például egy Employee ( Alkalmazott) osztály tartalmazhat karakterlánc objektumokat (az alkalmazottak nevéhez) és egész szá mokat is Ül fizetés hez) stb. A 20.3 ListMml egy J cc.~ upa szít o lt , mégis hasznos String OSZTályt é pítünk feJ.
20.3 lista - A String osztály (string.hppl
o: /1 20 . 3 Li sta A St rinQ osztá l y 1 : linc1ude Z : linc1ude 3 , class String
., :
{
public , /1 Kons truktorok String () ; String{const char "const) ; Stri ng (cons t St ri ng ... ) ; 10 : -S t ring() ; 5: 6, 7, 8,
"
11 :
lz : 1/ túlterhelt oper átorok 13 : 14 : 15 : 16 : 17 ,
c har & operatorf ] (int of f set) ; c har operator[ l (int o f fse t) c on st ; String operator.(const Str ing&) ; void operator.={const String&) ; Stri ng & operator; (const Str ing &)
;
IB : 19 , 1 / Elta l ános hoz zá f 6ro fuggvények
20 : i nt GetLen ()con st ( retu r n itsLen; 21 : const c har * GetStr ing () c onst { re tu rn itsStri ng ; } 22 : 1/ stat i c i nt Constr uctorCount ; 23 :
24: pri vate :
375 25 : String(in t) ; II privát kons t ruktor 26 : char * i t sStdng ; 27 : int it5Len ; 28 :
);
29 : 30: 31 : 32: 33 : 34 :
II az a lapértelmezett konstruktor O bájtos karakterláncot hoz létre String " String() ( itsString = new char[l) ; i t sS t ring[O j = ' \0 '; 35 : itsLen =O ; 36: II std :: cout « • \tDefault string constructor\n· ; 37 : II ConstructorCount++ :
38: } 39 : 40: II privát (segéd) konstruktor, csak osztálymet6dusok 41 : II használják meghatározott méretu új karakter láncok 42 : II létrehozására. Nullal töltve.
43 : 44 : 45: 46: 47: 48: 49:
String : : SLr ing (int len) ( itsString = new char[len+l]; int i;
for Ci = O; i<_len; 1++] itsString]i] = '\0' ; itsLen=len : 50 : II std : : cout « "\tString(int) constructor\n" ; 51 : II ConstructorCount++: 52 : ) 5) :
54 : String : : String (const char * const cString) 55: { 56 : itsLen = strlen(cString); 57 : itsString = new char[itsLen+1] ; 58 : int i; 59 : for ( i = O; i
}
65 : 66 : String : : St ring (const String & rhs) 67 : (
68: 69: 70: 71: 72: 7) : 74 : 75 :
itsLen=rhs.GetLen() ; itsStri ng '" new char[itsLen+l]; int i; for ( i " O; i
76:
)
77 :
376 1VI. rész • Kmönl.gességek 78 : 79 : 80 : 8l: 82 : 83 : 84 : 85 :
String : o-String () { delete [J itsString ; itsLen - O; II std : : cout « "\tString destructor\n"; ,
II Egyenloség operá t or ; felszabad ítja a lefoglalt memóriát , 86 : II majd másolja a karakterláncot és a méretet
String& String : : operator=(const String & rhsl { i f (this -- &rhsl return *this ; delete t J it 5S t ring ; itfiLcn-rha . GctLcn() ; itsString " new char[itsLen+l l; int 1; for (i " O; i itsLen) 108 , return itsString(itsLen-1) ; 109: else 110 : return itsStr1ng(offset] ; III : 112 : 113 : II constant offset op erator for use 114 : Ilon const o b jects (see copy constructor ! ) 115 : char String : : op e r atorl ) (int o f fse t ) const 116 : ( 117 : i f (offset> itsLen) 118 : return itsString[itsLen-l l ; 119 : e l se 120 : return itsString[offGe t] ; 87 : 88 : 89 : 90 : 91 : 92 : 93 : 9 4: 95 : 96 : 97 : 98 :
ln :
12 2 : 123 : 124 : 125 : 126 : 127 : 128 : 129 : 130: 131 :
II új karakterláncot hoz létre, á t adva az ak tuá l i s I I karakterláncot az dis- nek String String : : operat or + (const String& rhs) ( int totalLen = itsLen + rhs . GetJ.en() ; int i,j; String temp(tota1Len) ; for (i =. O; i
20. óra • 132 , 133 : 134 : 135 :
for (j '" O; j
mutatók
iH}
136 ,
137 : 138 : 139 : 140 : 141 , 142 : 143: 144 , 145 : 146 : 147 :
II mcgváltoztatja az aktuális karatkerláncot, és nem ad viooza semmit void Str ing: :opcrator+"'(eonst St ring& r hs} ( int rhsLen " rhs.GctLcn(); int totalLen = itsLen + rhsLen ; int L j ; String temp(tota1Len); for (i '" O; i
Kimenet A progra rnnak [lines kimencle,
EI.
8
A 22.sorban deklaráljuk a ConstructorCount statikus tagválto z6t , és a 153. sorban inicializáljuk. Ezt a változ6t nóveljük a karakterláne konstruktorban. A 20.4 Listában leínrnk egy Employee ( Alko/mozou) osztályt , amely hárorn kamkterláne objektumot tartalmaz. Figyeljük meg, hogy néhány sor megjcgyzésl:>e van téve; ezek a fejezet végi feladatokban kerülnek sz6ba.
Apropó
Újrahasznosfthat6ság
A 20.3 lista kódja a string. hpp állományban van. Ha szükségünk van a String osztályra, beemelhetjük a 20.3 listát egy hnclude paranccsal. A 20.4 lista elején például látható egy lIinclude • string . hpp· sor; ennek révén használhat juk programunkban a String osztályt.
378 1VI. rész' Különlegességek
20.4 Lista - Az Employe8 osztály és a főprogram lemployeemain.cpp)
o:
~include
'string .hpp ·
1: II Az iostrcam-ct már beemelte a másik állomány 2 : using std : : ccut; /I Ez a fájl std: , cout-ot használ 3,
4, S, 6, 7, 8, 9, 10,
class Employee (
public : Employee() ; Emp!oyca (char * ~ Employee()
char * , char
*
long) ;
:
ll: 12 : 13 :
Emp loyee{con!:l t
14 :
const String & GetFirst Name(} const ( retu rn i t sFirs tName ; COn!:lC St ring & GetLastName() const { reLurn itsLastNa mo; } const St ring & Ge tAddress( ) const { return itsAddrass ; } long Get.Salary() const ( .etu rn ltsSalary; )
Employee
15: 16:
17 :
&
Employee&)
l
operat or= (const Employee
&) ;
18:
19 :
20 , 21 :
22 : 23 : 2'1 : 25 : 26 : 27 : 28: 29 : 30 :
31:
void SctFirstName(const String & fName) ( itsFirstName = fName ; l void SetLastNama(const String & INama) { itsLastName = IName; } void SetAddress(const String & address) ( itsAddress = address; ) void SetSalllry(long salary) { itsSellary private: String itsF'irstName; String itsLastName; String itsAddress ; long itsSalary ; J,
ol
salary; }
32 :
33 : 34 : 35 :
36 : 37 :
38 : ]9 : 40 : 41: 42 : 4]: 44 : 45:
Employee : : Employae() : itsFirstName(") , i tsLastName(" " ), i tsMdress ('"), itsSalary(O) {} Employee : : Employee (char * f i rstName , char * lastName, char * address, long salary) : itsFirstName(firs t Name), itsLastName(las tName), itsAddress{address), itsSalary(salary)
46 :
{I
47 : 48 : 49 :
Employee ::Employee (const Employee & rhs) : itsFirstNama(rhs.GetFirstName(») ,
óra •
so : 51 : 52 : 53 :
itsLastName(rhs .GetLastName(}) , itsAddress (rhs .Get Address () ), it sSalary(rhs .GetSalary(» ()
54 :
55 :
Employee : :-Emp!oyee() fl
56 : 57 : 58 : 59 : 60 : 61 : 62 :
Ernployee & Employee: : operator: (const Ernp!oyce & rhs) { i f (this == &rhs) reLurrl *this;
itsFirstName '" rhs.GetFirstName();
6]:
itsLastName '" rhs . GetLastNamc (l ;
64:
itsAddress e rhs .GetAddrcss();
65: 66 : 67 :
itsSa!ary '" rhs . GetSalary() ;
return *thi o ;
68 : 69: 70:
int main()
71: 72:
Employee Edie("Jane","Ooe","1461 Shore Parkway', 20000):
73: 74:
Edie.SetSalary(50000); String LsstName('Levine') ;
7S :
Ed.Le. Se t LastName (LastNamc) ;
76 : 77 :
Edie . SetFlrstName ( 'Edythe" ) ;
78 :
"Name:
79:
Edie .GetFirstName() .GetString(); • • « Edie.GetLastName() .GctString():
80: 81: 82 : 83 :
84 : 85 :
' ;
•. \nAddress: ': Edie . GetAddreS9 () . GetString () ; • . \ n Salary : " : cout « Edi e . Ge tS alary() ; return O;
86 :
Name ; Edythe Levine Address : 1461 Shore Parkway Salary : 50000
A 20A Lista szemlélteli az Employee osztályt, melyben három karakterlánc objektum va n: itsF i rstName, itsLastName és itsAddress.
I
380 VI. rész • Különlegességek A 72. sorban létrejön egy Employee példány, és négy adattal inkializálódik. A 73. SOfban az Employee hozzáfér6 függvénye, a SetSalary () hív6dik meg, az 50000-es számErtékkeL Egy igazi progmmban ez egy futási időben meghatározott dinantikus érték vagy egy konstans lenne. A 74. sorban egy C++ karakterlánc konstanssal inicializál6dik egy String objektum, ami a SetL"stName() paramétere lesz a 75. sorban. A 76 , sorban az Employee tagfüggvénye, a SetFirstName () fUlle, szintén egy karakterhínc konstanssaL Ha erősen figyelt az olvasó, észrevehette, hogy az Employee-nek nincs olyan SetFirstName () t:1gfüggvénye, amely karakterláncot fogadna el par.unéterkénlj a SetFirstName () konstans karakterJ{mcra való hivatkozo:íst vár. A fordítóprogram megoldja czt, mert a 20.3 Lista 8. sora alapján már tudja, hogy hogyan csináljon egy konstans karakterláncból String-el.
A tartalmazott osztály tagjainak elérése Az Employee objekmilloknak nin<_"S speciális hozzMérése a String tagv:íltoz6khoz. Ha al Edie alkalmazott o bjektum megpr6biilná elérni saját itsFirstName tl.lgváltoz6j,1nak itsLen tagváltoz6ját, a z fordítási hibát generálna. Ez igazából nem jelem túl nagy terhet. A hozzátérő fügsvények megfelel6 kezelőfelületet nyújtanak a String osztályhoz. Az Employee OSztályban nem kell többel foglal kozmmk a megval6sítás részleteivel, mint amennyit mondjuk az itsSalary egész változó konkrét infonnációtárolási módja miatt kellene.
A tartalmazott osztályelérésének korlátozása Figyeljük meg, hogy a String osztály biZtosítja a •+" operátort (operator+). Az Ernployee teIVez6je azzal akadályozta meg, hogy az operator+ meghívható legyen az Employee objekmmokra, hogy úgy deklará lt minden karakterlánc hozzáfér6 függvényt, (mint például a Get FirstName () -et is), hogy konstans referenciát adjon vi.ssza. Mive! az operaton nem (is lehet) konstans függvény, hiszen meg kell változtatnia a hívott objektumot, a következő .sor hibát adna: String buffer
~
Edic.GetFirstName() + Edie.GetLastName();
A GetFirstName () cgy konstans $tring-et ad vissza, amire nem lehet az operator ... műveletet meghívni. Ez( a GetFirstName () túlterhelésével tudjuk kijavítani.
Ne legyen az új függvény konSlans: const String & GetFirstName{) const { return itsFirstName; String & GctFirstName{) { return itsFirstName; } Figyeljük meg, hogy sem II visszatérési érték, sem maga a ragfüggvény nem konSlans. A visszatérési érték megváltozlatása még nem elegendő a túlterheléshez; változlarni kell a függvény konstans vollán is. Azáltal, hogy megadunk egy konstans és egy nem konslans vállozatot is, a fordítóprogram prnbálja majd hívni a kom;tans vállozatot, ahol csak lehet (például amikor :IZ ügyff:lprogmm a GetFirstName-et prnbálja hívni), és ha nincs más lehetőség, meghívja a nem konstans verziói (például az operator+ esetén).
A tartalmazás költsége Fontos tudn i, hogy al Employee oszt,~ly felhasználója nünden egyes k3rakterlár1C lf:lreho:disána k az árút megfizeti, amikor létrejönnek a karakterláncok, vagy ha másolat készü l egy Employee péklányr61.
Érték szerinti és hivatkozás szerinti másolás Amikor érték szerint adunk át egy Employee objektumot, minden egyes tartalmaZOlt kardkterlánc is lemásol6dik ; eközben pedig másoló konstnlktorokat hívunk meg. Ez igen költséges, mcm6ria ~s id6 tekintetében. Ha cím szerint adjuk át az Employee objektumokat (mulatót vllgy hivmkoz.1st használva), mindezt elkerülhetjük. Emiatt keményen dolgoznak a C++ programozók , hogy néhány bájtnál nagyobb objekuHnol ne kelljen érték szerint átadni.
Baráti viszonyban
levő
osztályok
Időnként
egy(ill hozunk létre osztályokat, egy csoportba tartozó módon. Ezeknek az összeterelL oszt.\lyoknak szükségük lehet egymás privát adatlagjaira, ám nem szeret· nénk ezeket az információkat nyilvánossá tenni. Ha egy osztály privát adatait egy másik osztá ly rendelkezésére szeretnénk bocsátani, akkor a fr iend ku1csszóval kell ezt a . barátkozó" osztá lyt deklarálni. Ez kiterjeszti a másik osztály kezel6felületét, hogy lássa a barátosztályt is. Megjegyzendő, hogya barátság nem tranzitív. Anól még, hogy Jóska és PiSla a baráraim, ők egymásnak nem barátai. A barátság nem is öröklődik. Az, hogy egy barálommal megbeszélem a lilkaimal, nem jelemi azt, hogy az ő gyermekeinek is meg szeretném mondani.
382
rész •
A barátság még csak nem is szinulletrikus: Az EgyikOsztály-t a MásikOsztály barátjának deklarálva még nem lesz a MásikOsztály az EgyikOsztály-nak is barátja. Attól , hogy te el szeretnéd mondani nekem a titkaidat, nem biztos, hogy én is elmondom neked a sajátjaimal. A barátosztályokat igen nagy körültekintéssel szabad csak használn i. Ha két osztály kibogozhatatlanul összefonódoll, és gyakran kel! egymás adatait manipulálniuk, akkor megfontolandó a baráL'iág kimondása. De legyünk takarékosak: sokszor ugyanilyen egyszeru nyilvános hozzáfé rő függvényeket használni. Ez lehetővé teszi, hogy az egyik osztályt anélkül váhoztassuk meg, hogy a másik:LL újr.t kellene fordítani.
Apropó
Egységbezárás
Gyakran hallani kezd ő C+ + programozóktól, hogy arról panaszkodnak, hogya barátság aláaknáua az objektum-orientált szemléletben oly fontos egységbezárást. Ez enyhén szólva téves megközelítés. A baráttá deklarálás egyszerüen az osztály keze l ő fel ül eté hez csatolja a barátosztályt; ez nem ássa jobban alá az egységbezárást, mint mondjuk a nyilvános származtatás.
Baráti viszony függvények között Előfordu l hlIt, hogy II barátságot nem egy teljes osztálynak szeretnénk megadni, ha nem csak az osztály egy-kéL tagfüggvényének. Ez meg is teheL6 oly módon, hogy nem az osztályt deklaráljunk barátnak, hanem a szükséges tagfüggvény(eke)r. Sót, igazából ennek nem is kell osztályhoz tartozónak lennie; bármilyen függvényt kinevezhetünk bacitfüggvénynck.
Függvénymutatók Ahogya tömbök nevei olyan konstans mULatók, melyek a tömb első demére mutatnak, a fü ggvények nevei is konstans muLatók magár:l a függvény re. Lehet deklará lni kifeje%etlen függvényre vonatkozó mutatót, és ennek segítségéve! meg is hívható lL függvény. Ez nagyon hasznos leheL például olyan program írásához, amely a felhasználó :'iItal adott bemenet alapján hív meg kül önböző függvé nyeket. A függvénymutatók megénésének nehézsége az, hogy tulajdonképpen milyen típusú objekrumr.t is mutatunk. Ahogy egy egész számra vonatkozó mutató egy egész típusú változóra mutat, ugyanígy a függvé nymutatónak egy adott visszatérési típusú , adott par.tméler-szignatúrájú fűggvényre kell mutatnia.
20. óra • Nézzük a következ6 deklarációt: long
j.
func;Pt r)
(int) ;
Ebben a funcPtr-l úgy határozzuk meg, hogy ez egy mutató lesz (hiszen ott a • jel a név el6tt), amely egy egész szá mol elfogad6 és egy h OSSl Ú egészt visszaad6 függvényre vonatkozik. A "· funcP t r" köré zár6jelt kell tennünk , mert egyébként az (i nt ) " erősebb hatású" [e nne, azaz korCtbban hajt6dm\ végre, mint a mutat6fc1oldási operátor e). Az első zár6jclpár nélkül ez egy egész számot elfogadó függvé nyt jelentene, amely egy hosszú egész számra vonatkozó mulalól ad vissza. (Ne feledjük , hogyaszóközöknek
ill
nincs jelen tősége ,)
Vi:r.sgáljuk meg az alábbi két deklarációt: long· Func t ion (int); long ( * func Ptrl (int) ;
Az els6ben a Punction () egy fi'lggvény, mely egy egész számOLvá r, és egy hosszú egész sz. \ mra vonatkozó lllutatót ad vissza. A másodikban a funcPtr egy illU/a/ó, amely egy filggvényre vonatkozik, amely egy egész számot vár (:s egy hosszú egészct 3d vissza . A függvénymutatók deklarációjában mindig meg kcll adni 3 visszatérési típustj a második zárójelben kell felsorolni az esetleges paraméterek típuslistiíj:ít. A 20.5 Lista szemlélteti a f'Üggvénymutatók deklarációját és haszniílatát.
20.5listB - Függvénymutatók használata (ptrtofunction.cpp)
o: / /
20.5 Li s t a rOggvénymutat6k használata 1 : Itinclude
2:
,. 3,
5, 6, 7, 8, 9, 10 : 11 :
void void void void void
Squarc (1nt&,int&) ; Cube (int& , int&) ; Swap (int&, int & ) ; GetVals(int&, int&);
PrintVals (i nt, int) ;
i n t main () void (* pFuncl
(int &, int &l ;
12 : 13 :
boo1 fQuit ,. fals e;
14 :
int va1One=1 , int choice ;
15 : 16 : 17 : 18 :
valTwo~2 ;
whil e (fQuit == fals e) {
19 :
s t d :: cout « " (O)Quit (l)Change Va lues « " (2l Square (3)CUbc (4)Swap : ";
20 :
std: :cin
»
choice ;
383
384 1VI. rész • Kmönlegességek 21 , 22 ,
s .... itch (choice)
23 ,
case 1 , pFunc = break ; case 2 , p Func = brea k; case 3 : pE"unc = break; case 4 : pF\mc = break; default , fQuit " break ;
(
2 <'\:
25 : 26 ,
27 : 28 , 29 : 30 : 31 : 32 :
33 , 34 : 35 , 36 : 37 : 38 :
39 , 40 :
GetVals;
Squa re ;
Cube ;
Swap;
true ;
i f (fQuit) break;
41:
42 : PrintVals(valOne, valTwo); pFunc(va10ne, valTwo); PrintVals(valOne, valTwo);
43 : 44, 45 : 46 , 47,
return O;
48 : 49 , 50 : 51 , 52 : 53 , 54 : 55: 56 , 57 , ~8 ,
void PrintVals(int x, i nt y) { std : : cout « ·x: • « x «
void Square (int & rX, int & rY) ( rX rX; rY -- rY;
-=
59 , 60 , 61 : 62 , 63 , 64 : 65 :
66 : 67 : 68 , 69 : 70 ,
71 , 72 , 73 :
void Cube (int & rX , int & rY) {
int tmp; tmp "' rX; rX ' = rX ; rX = rX • tmp; tmp = rY; rY rY ; rY = rY - tmp ;
.=
" y: • «
y «
std : , endl ;
20. óra • 74 : 75 : 76: 77 : 78: 79 : 80: 81: 82: 83 : 84: 85: 86 : 87 : 88 :
void swap(inL & rX, ( int temp ; temp" rX; rX = rY; rY = temp;
i nt
&
mutatók
rY)
void GctVals (int & rValOne,
int & r ValTwo)
std : : cout « "New value for ValOne : ' ; std : : cin » rValOne; std :: cout « "New value for ValTwo : .; std: :cin » rValTwo;
Kimenet (OlQuit (llChange Values (2)SQuare (3)Cubc (4)Swap: l
x: l y:2 New valuc for ValOne: New value for Val1'wo :
x: 2 y : 3 (O)Quit (l)Change x : 2 y:3 x: 8 y : 27 (OlQuit (l)Change x:8y : 27 x:64 y : 729 (O)Quit (l)Change x : 64 y , 729 x , 729 y , 64 (O)Quit (l)Change
2 3
Values (2)Square (3)Cubc (4)Swup : 3
Values (2)Square (J)Cube (4)Swap : 2
Values (2)Square (3)Cubc (4)Swap : 4
Values (2)Square (3)Cubc (4)Swap : O
Bends A 3-6. sorban négy függvényt adunk meg, azonos paraméter-szignatúrával (két hivatkozás egészekre) és visszatérési típussal (void). A ll . sorban deklaráljuk a pFunc mutatót, amely egy olyan függvényre vonatkozik, ami két egész hivatko7..ást vár és ~semm i t " (void) ad vissza. Az e\6ző1eg megadott függvényck bármelyikére vonatkozhat ez a pFunc mutató. A fe lhasználónak ismételten felkínflljuk, hogy válasszon a függvények közül, és ennek megfelelően lesz a pFunc beá llítva. A 43-45. sorban a két egész szám pilla natnyi értékét kiíratjuk, majd az aktuálisa n beálliton függvény lefultatása után újra megjelennek a sZ<Í mértékek.
386 1VI. rész • Különleges,égek
Rövidftett frásmód A függvénymUlatót nem kell feloldani , bár erre megvan a lehet6ség. így, ha a pFunc egy olyan függvénymutató, amely egy egész számot vár és egy hosszú egészet ad ViSS7..a, és ehhez hozzá is rcndeltünk egy megfelelő füSb'Vényt, akkor ez kétféleképp is
meghívható: pFunc(x) ;
vagy (* pFu n cl (x l ;
A két változat egyenértékiI. Az els6 nem más, mim a második róvidített írásmódja.
Függvénymutatók tömbje Ahogy például egészekb61 összeállíthamnk egy tömböt, ugyanúgy deklarálhatunk (adoll viSSZ;lté rési típusú és paraméter·szignat(Hiijú) függ:vé nymutat6kból is . (L;\5d a 20.6 Lislát)
o:
II 20 . 6 Lista Filggvénymutat6k tOmbje
1 : 'include 2. 3:
5:
void Square (i nt& , int&) ; void eube (int&: , int &) ; void Swap (int&: , int &Ol ;
6: 7: 8.
void GetVala{i n t& , int& ) : void PrintVals (int , int) ;
9:
i. nt ma.in()
10 : 11 : 12 : 13 : 1 4: 15 :
i n t va l One~l , valTwo~2: i nt c ho ice , i : COn!l t i n t MaxArray " 5; v o id (·pFuncArray [MaxArray]) ( i nt&,
16 : 17 : 18 : 19 : 20 :
f or (i=O ; i
21 :
22 :
int& ) :
s td: : cout « • (l ) Change Val ue s " « " ( 2 }Square {3 }Cu be (4)Swap: std: : cin » choice; s wit c h (choice )
";
0
2006ra' case 1 : pFuncArray[il
23 :
24 :
mutat6k 387
1 1
GetVal s:
break :
25 :
26 :
case 2 :
27 : 28 : 29 :
break ; case 3 ,
pFuncArray(i)
30 :
pFuncArray [i I
]1 : 32 :
break ;
Square ;
~
Cube ;
case 4 : pFuncArray[i] = Swap ; break; default : O, pFuncArray ( i 1
33 : 34 : 35 : 36 : 37 :
38 : ]9 :
40 :
fo r
41 :
(
( i .. O;i
i+ 't )
pFu n cArray [i J (valOnc , valTwo) ;
42 : 43 : 44 :
PrintVals(valOne , valTwo) ; return O;
45 : 46 : 47 :
48 :
void PrintVals(int x, int y)
49 : 50 : 51 : 52 :
std : : cout «
'x: • «
SJ :
void Square (int & rX, int
54 : 55: 56 :
(
57 : 56 : 59 :
x «
tit
r'il
rX .... rXi r'i ' ", rV i
void Cube (int tit rX , int tit rYl
60 : 61 : 62 : 63 : 64 : 65 : 66 : 6 7, 68 : 69 :
int tmp : tmp = rX i
r X * ", rX i rX = rX .. t mp ; tmp = r'i i
r.Y * - rV i r'i = rY •
tmp ;
70 :
71 :
72 :
void Swsp(int tit rX , int tit r'il
73 : 74 : 75 ,
{
int temp; temp '" rX;
• y: • «
y «
std : :endl;
388 1VI. rész • Különlegességek 76 :
rX
rY;
77 :
rY
= temp ;
78 : 79 :
80 : 81 :
void GetVals
82 : 83 : 84 : 85 :
(int
&
r ValOne,
int & r Va l 'J'wo)
std :: cout « "Ne w value for ValOne : " ; std : : cin » rVa l One ; std :: cout « "New va!ue for ValTWo : " ; std : : cin » rValTwo ;
86 :
Flgyeleml
Legyünk 6vatosak ezzel a példaprogrammal!
Ahogy a könyv elején említettük, nem írtuk példaprogramjainkat golyóálló biztonságúra; nem követtük a Hdefenzív programozás" elveit. Ha az 1, 2, 3 vagy 4 helyett más számot Irunk be a fenti program bemeneteként, hibát kapunk. A pontos hiba· üzenet operációs rendszertől, fordftóprogramt61 és a nyomkövetötöl is függ; a hiba oka az, hogy definiálatlan függvényt próbáltunk meghívni (lásd a 36. sort). Bár nem
lenne nehéz úgy átfrni a programot, hogy ez ne fordulhasson hosszabb és kevésbé szemléletes lenne.
elő,
ezzel sokkal
menet (l)Change (l )Change (1 )Change (l)Change (l)Change New Value New Value X: 2 y : J x: 4 y : 9
Values (2)S quare Values (2)Sl:;Iuare Values (2)Square Va l ucs (2)SQuare Values (2)Square for ValOnc : 2 for ValTwo : 3
(J}Cube (4) Swap : (J)Cubc (4)Swa.p : (J)Cube (4 )Swap : {J)Cube (4}Swap : (J)Cube (4)Swup :
l 2
3 4 2
x : 64 y : 729 x : 729 y : 64 x : 531441 y : 40 96
Elemzés A 16-38. sorban lehetőséget ad a program a felhasználó számára, hogy válasszon a meghívható függvények közül. Egy tömb elemeit rendre beállítjuk a megcélzott függvény eimére. A 40-44, sorban egymás után meghívjuk ezeket a függvényeket (a 42. sorban zajlik le a tömbelemnek m egfelelő konkrét függvényhívás). Minden hívás utá n kiíródnak az eredmények.
20. óra •
Függvénymutatók átadása más függvényeknek A függvénymmatók (vagy akár az ezekb61 képzetttömOök) átadhat6ak más függvé· nye knek, amelyek felhasznál hatják ezeket, és alkalmas pillanatban meghívhatják a szükséges függvényt a megfele l5 mUlató használatával. A 20.6 ü stát például lö kéletesítherjük oly módon, hogy a kiválasztott függv(:nyl egy másik fí.iggvénynek adjuk át (a fap rogramon kívül), és ez fogja kiírni az értékeket, meghívnia soron következ6 függvényt , majd újm megjeleníti az értékeket. A 20.7 Lista eZl a változatOT mutatja be.
20.7 Lista - függvénymutatók átadása egy másik függvénynek paraméterként lpassingptrfunction.cpp) O, 1/ 20 . 7 Lis t a Függ v é nymutat6 k á t adása paraméterként 1 , h nc1ude 2 : usin; namOApacc s t d ; / I e z a z á llomány s td:, objektultloku t használ 3, 4 , voi d Square (int& , int&) ; 5: void CUM (i n t& , int&) ; 6 : void Swap (int&, int &1 ; 7: void GetVals(int& , int&) ; 8: void I'rintVals(void ( * )(1nt&, int&) , int& , inl.&) ; 9, 10 : int !MinO ll : { 12 : int val0ne=1, valTwo=2 ; 13 : int choice; 14 : boo1 fQuit = false ; 15 : 16 : void ("pFunc:) (int& , int&) ; 17 : 18 : whi1e (fQuit ~= false) 19 :
{
20 : 21 : 22 : 23 : 2 4:
cout « . (O}Qui t (l)Chang e Val u es • « " (2)SQuare (3)Cube (4) Swap : ' ; cin » choice ; s wi tc h (c ho i ce )
25 :
case 1: pFunc '" breu k; case 2 : pFunc = b reak; case 3 , pFunc break ; case 4 : pFunc =
26 : 27 : 28 : 29 , 30 : 31 , 32 : 33 , 3 4: 35 ,
Gc t Vals;
Square;
Cube ;
Swap ;
389
I
390 VI. rész • Különlegességek break ; de fault : EQuit = true ; break;
36 : 37 : 38 :
39 : 40:
i f (EQuit -== true) break; PrintVals ( pF'unc, valOne, valTwo) ;
41: 42: 43 :
44 : 45 ; 46: 47 : 48 : 49:
re t urn O;
void PrintYals( void (*pFunc) (int&,
int&),int& x,
50 : cout « 'X : pFunc(x ,y ) ; cout « 'x :
51 : 52 :
53 : 54 :
«
X
«
«
y «
endl;
«
x «
«
y «
endI ;
55 :
56 : 57: 58:
59 : 60 : 61 :
62 : 63 : 64:
void Square (int ( rX *..,. rXI rY *= r'l;
& rX . int & rY)
void eu be (int & rX , int & rY) ( int tmpl
65 : 66:
tmp = rX; rX *= rX; rX = rX * tmp;
67 :
68 : 69 :
tmp = rY ; rY * : rY; rY = rY * tmp;
70 :
71 : 72 :
73 : 74 :
75 :
void Swap(int & rX, int & rY)
76 : 77 : 78 :
i n t temp ; ternp = rX; rX rY; rY = temp;
79 :
80: 81 : 82 : 83 :
void GetYals (int & rVal0ne,
84:
(
85 : 86 : 87 : 88: 89 :
int & rValTwo)
co ut « 'New va!ue Eo r ValOno : ' ; cin » rValOne ; cout « 'New vaIue fo r ValTwo : ' . cin » rYalTwo;
int& y)
20. óra •
menet (O)Quit (l)Change Values (2)Square (3)Cube (4)Swap: l 1 y:2 New value for ValOne : 2 New valuc f or Val '!'wo: 3 x: 2 y : 3 x:
(O)Quit
(l)Change Values (2)Square (3jCube (4)Swap : 3
x : 2 y:3 x: 8 y: 27
(O)Quit (l)Change Values (2)Square (3)Cubc (4jSwap: 2 8 y: 27 x:64 y:729 (O)Quit (l)Change Values (2)Square (3)Cube (4)Swap: 4 x:64 y:729 x: 729 y : 64 (O)Quit (l)Changc Values (2)Square (3)Cube (4)SwQP: O x:
Bemzé. A 16. sorban deklarnljuk a pFunc függvénymut3tót, ami két egész hivatkozást vár és üres a visszatérési értéke. A 8. sorban deklaráljuk a PrintVals () függvényt, amely há-
rom p;mun{:lCI1 vár. Az első egy függvénymul
A typedef használata függvénymutatókkal A void (*) (int&, int') konstrukció használata enyhén szólva kényelmetlen. Egyszenísítésként használhat juk a typedef ku\csszót. Deklaráljunk egy VPF típust olyan Függvl:nymutatóként, amely két egész hivatkozást vár és üres a visszatérési értéke. A 20.8 Listában újrafogahnazzuk a 20.7 Lista elejét, és a typedef segítségéve! két lépésben deklaráljuk a Prin t Vals () függvényt .
391
392 1VI. rész· Különlegességek
20.8 lista - A ftIggrinymutat61c OIVaShat6bbá _
(lIIiogtypedef.cppl
o:
II 20 . 8 List a typede f használ ata 1 : 'lnclude 2 : uslng namespace std; II ez az á llomány std :: objektumokat has zná l 3. 4 : voId Square (int& , lnt&) ; 5 : voidCubc (in t& , int ,\,) ; 6 : void Swap (Inl& , inl &) ; 7 : void GetValsCint& , i nt&) ; 8 : typedef void ( *VPF) (int'\' , int&) 9 : void PrintVals(VPF, int& , int &) ; 10 : ll : i nt mal n () 12 : 13 : int v n I One=l , v aI Two=2 ; 14 : int choice ; 15 : boo l fQu i t ~ false ; 16 : 17 : VPF pf'un c ; 18 ; 19 : while (fQuit ~= false) 20 ;
{
21 : 22 : 23 ; 24 : 25 : 26 : 27 : 28 : ::19 , 30 : 31 , 32 : 33 : 34:
cout « . (O)Quit (l)Change Val ues' « "{2)Square (3)Cubc {4)Swap : "; cin » choice ; s witch {choicel ( case l : pFunc ~ GctVals ; break; case 2 , pFunc = Square ; break ; case 3 : pFunc " Cube ; break ; case 4 : p Func - Swa p; b rea k; d efau l t : f Qu i t - t ru e ; break;
35 :
36 : 37 : 3B :
39 : 40 :
41 : ) 42 : i f (fQuit == tru e) 43 : break;
44 : PdntVals ( pFunc , valOne, va l Two) ; 45 : ) 46 ; return O; 47 : 48 :
49 : voId PrintVa ls{ VPF pFunc,lnt& x, l n t& y) 50 :
20. óra • 51 : cout « 'x: 52 : p F'unc( x, y) ; 53 : cout « 'x :
mutatók
«
x «
y.
«
y «
e nd l ;
«
x «
y.
«
y «
cnd1 ;
51\ :
55 : 56 : void Square (int & rX. 57 : 58 : rX - rX ; 59 : rY '- rY ;
int & rY)
,
60 : 61 : 62 : 63 : 64 : 65 : 66 : 67 : 68 :
void Cube (int &: rX . int &: rY) int t mp ; tmp ::: r X; rX .. = rX ; rX • r X • tmp ;
69 , 70 : t mp = rY ; 71 : rY * ,. rY ;
72 : rY = rY • tmp ; 73 : 74 : 75 : 76: 77 : 78 : 79 : 80 : 81 : 82 : 83 : 84 : 85 : 86 : 87 : 88 : 89 ,
} void Swap(int &: rX, ( int temp ; temD -' rX; rX • rY ; rY = temD; )
int &: rY)
void GetVAls (int ~ rVolOne, int &: rVal'I\.Po) ( cout « "New vAluc for ValOne : " cin » rVAIOne ; cout « 'New vAlue for ValTwo : ' ; cjn » rValTwo ; )
Kimenet (O)Qu i t (l)C ha nge Values (2)Squa r c (3)Cube ( 4 )Swap : l 1 y :2 New value for ValOne : 2 New value for ValTwo : 3 x: 2 y : 3 (O)Quit (l)Change Values (2)Square (3}Cube (4)Swap : 3 x, 2 y : 3 x : 8 y : :n x :
394 1VI. ,ész' KOIönl.gesség.k (O)Quit (l)Change Values (2)Square {3)Cube (4 )Swap: 2 x; 8 y: 27 x:64 y :72 9
(O)Quit (l)Change Values (2)Square (3)Cube (4)Swap : 4 x:64 y : 729 x:729 y,64 (O)Quit (l)Change Values (2)Square (3)Cube (41Swap : O
• A 8. sorhan a typedef segíL'iégével deklarál juk a VPI" függvénytípusl, amely kf:t pammétert vár (egy-egy egészre vonatkozó hivatkozást), és (ires a visszatérési értéke. A 9. sorban dckbráljuk a PrintVals () függvé nyt, amely három pamrnétcrt vár: a:l el56 VPF tíPlISÚ, a többi egy-egy egészre vonatkozó hivatkozás. 1\ 17. sorba n már a VPI" típussal deklarálharjuk a pFune függvényt. A VPI" definíci6jával sokkal világosabbá válik e nnek a típusnak a használata, példáu l pFune és a PrintVals () deklarációja kor. A typedef igazából csak egy szinonimfu boz létre; a 20.7 és 20.8 Lista csak az olvashatóságb,Ln tér el egymástól.
:L
Tagfüggvényekre vonatkozó mutatók Eddig a pillanatig csak o lyan függvé nymutatókat használtunk, amelyek osztályon kívüli , általánosan használható függvényekre vonatkoztak. Természetesen tagfüggvényekhez is gyárthatunk mutatókat. Tagfüggvénymulatót ugyanolyan szintaxissal lehet létrehozni, mint amit eddig is h:lsználrunk normál függvénymuratók esetében, csak a • jel el6tt meg kell adni az osztá ly nevét és a hatókör operáton (két kett6sponlot) is. l-la például a pFunc a Shape oszlálybeli tagfüggvényre mutat, amelynek üres (void) a viss~atérési é rtéke, paraméterként pedig két egészet vár, akkor az alábbi módon lehet deklarálni: void (Shape: :*pFunc)
(int,
int);
A tagfliggv6nyckrc vonatkozó mutatókat ugyanú!,'Y lehet haszn:'ilni, mint az egyéb függvénymutatókat, kivéve azt, hogy csak a meghívandó osztályból való objektumokkal képesek dolgozni. A 20.9 Lista hemutatja a tagfüggvényekre vonmkozó mutatók használatát.
Figyelemi
A fiiggvény neve vagy címe?
Néhány fordítóprogram nem teszi lehetövé, hogya függvény nevét használjuk memóriacíme helyett. Ha a fordítóprogram hibát vagy figyelmeztetést ad az alábbi programra, próbálja az alábbi módon megváltoztatni a 71 -76. sorokat. 71 : 72 : 73 : 74 : 75 : 76 :
case pFunc o & Marrunal : : Speak ; break ; default : pFunc o & Manunal: :Move; break:
"
A következő programokban is ilyen módon korrigáljuk ezt a fajta hibát (vagyis egy jól eltalált & jellel a függvény neve előtt). Borland fordítóval nincs ilyen probléma.
20,9 Usta - Tagfüggvényekre vonatkozó mutatók használata (ptrtomember.cpp) o : / / 20.9 Lista Tagfllqqvényekre vonatkozó mutatók l: linclude 2, 3: enum BOOL U'ALSE , TRUE) ;
4, 5: 6:
14:
class Mammal ( public : Manunal() : itsAge(l) ( virtual -Ma.mmal() ( ) virtual void Speak() const = O: virtual void Move() const = O; protected: int itsAge: );
15 : 16:
class Dog : public Mammal
7:
8: 9: 10: ll:
12: 13:
17 :
18: 19 : 20 : 21: 22: 23: 24 : 25: 26 : 27 : 28 :
29 :
public : void Speak ()const { std :: cou t « void Move() conl'lt ( std: : cout« ): class Cat
public Ma.mmal
public : void Speak {)const void Move() const );
"Woof!\n" ; } 'Walking to heel ... \n" ; )
std :: cout« std : : cout «
"Meow!\n ' ; ) ' slinking ... \n' ;
396 1VI. rósz' Különlegessógek 30 :
class Horse
]1:
(
32 : 33: 34 : 35: 36 : 37: 38:
public : void Speak()const void Move() const };
]9: 40: 41:
42: 43 :
public Mammal
std : : cout « std :: cout«
"Winnie!\n"; l "Galloping ... \n" ;
int main() void (Hammal : :* pFunc) () const ,,0; Mamma1* ptr "0: int AnimaI; int Method; boo1 fQuit " false ;
44:
45:
while (fQuit "" false)
46 : 47 :
{
48 ; 49 :
50 : 51 : 52 : 53 :
54: 55 : 56 ; 57 :
58: 59 : 60 :
61 : 62 :
std: : cout« "( OlQuit (l)dog (2)cat std: : cin » Animai; switch (AnimaI) (
case l : ptr " break; case 2: ptr " break; case ]: ptr = break; default: fQuit break;
new Dog ;
new est;
new Horse;
= true;
63 : 64 : 65 :
if
(fQuit) break;
66 : 67 : 68 : 69 ;
std : , cout « • (1)5peak std , : cin » Method; switch (Method)
(2)Move :";
70 :
{
71 :
case l ; pFunc Mammal : : Speak; break ; default: pFunc = Mammal : :Move ; b r eak;
72 : 73 : 74: 75 : 76 : 77 : 78 :
79 : 80 ;
(ptr-> *pFunc) delete ptr;
81 : 82 : 83:
return O;
(J ;
(J)horse:
";
20. 6ra •
menet (O)Qu i t (1) Dog (2)CaL (1)Speak (2)Move : 1 Woof! (O)Quit (1)oog (2)Cat (1)Speak (2)Move : 1
(3}Horse : 1
(3)Hor sc :
2
(3)!-!orse:
3
(3)Horse :
o
Meow! (O)Quit ( 1) Oog (2)Cat ( 1 )Speak (2)Movc : 2
Galloping (O)Quit
(1)Oog
(2)Cat
Az 5-14. sorban a Mammal (emlös) absLtr..kt adattípust deklar:lljuk két tiszt~n virtll{llis metódussal; ezek a Speak () és a Move () . A Mammal alosztúlyai a Dog, ;1 Cat és a Horse (azaz a klltya, a macska és a 16), mindegyik feIOlírj:\ a Speak () -et és a Move ()-ol. A f6program megkérdezi a felhaszná lót, hogy melyik áJlatfajtát ho zzuk létre. Ekkor létrejön 11 mum6riában az AnimaI-nak megfelel6 ~J[:l!oszt{lly, és címe bukerül ti ptr nmtmóba a 49-63. sorban. Ezután eldöntheti a felhasz.náló, hogy melyik metódlIst szerelné meghívni. A döntésének megfele l ő metódus címe bekerül
Tagfüggvényekre vonatkozó mutatókból álló tömbök Ahogy a normál függvénymlltatókat tömbbe lehet szervezni, ugyanígy a tagfiiggvényekre vonatkozó mutalókat is. A tömb a különböző tagfüggvények címeivel inicializá lható, melyek kés6bb meghívhatóak az adon indexű tömbelemre hivatkozva. A 20. 10 Lista mutatja ezt a lehetőséget.
397
398 VI.
rész •
20.10 usta - TagflIggvényeIcre _ _ 6IhI _ _ _
,"rnyptrfunctioo.cppl o: II 20.10 Lista
TagfOggvényekre vonatkozó mutat6kból tömbje 1: linclude 2. 3 : class Dog 4:
{
5: 6: 7: 8: 9: 10 : ll : 12 : 13 :
public : void void void void void void void
14 :
};
Speak()consL ( std : : couL « 'Woof!\n'; ) Move () const ( std :: cout « 'Walking to heel ... \n"; Eat() const ( std :: cout« "Gobbl ing feod . .. \n ", l Growl{) const { etd :, cout « 'Crrrrr \n" , } Whimper () const { std : : cout « 'Whining no i se!! ... \n"; } RollOver () const ( std : : oout « 'Rol l ing over ... \n '; ) PluyOead () const { std :: cout« "Is t his the end of Li t tIe Caesar?\ n ' ;
15 : 16 : 17 :
IB : 19 :
20, 21: 22 :
23 : 24 : 25:
typedef void (Dog : ," PDF) ()const int main() {
const int MaxFuncs '" 7; PDF DogFunctionslMaxFuncsJ { D o g : : Speak, Dog' ::Move, Dog : : Eat, Dog : : Growl,
Dog , :Wh imper, Dog : : RollOver, Dog : : PlayDead
26: 27 :
28 :
l;
29 :
30: 31 :
Oog* pOog ,.0 ; int Method ;
32 : 33: 3 4: 35 : 36 : 37 :
bool f Qu it " false :
38 : 39 :
40:
while ( !fQuit) ( std :: cou t « ' (O)Quit {l)Speak. (2) Mov e (3)Eat (4) Growl'; s t d :: cout « ' (5) Whimper (6)Roll Over (7)P l a y Dea d : " ; s t d : : cin » Method ; i f (Method ,,= O)
{ fQuit '" true ; break;
41 :
42 : 43 :
44 :
else
45 : 46: 47 :
{
pDog = new Dog; (pDog - >*DogFunctions[Method- lJI {l ;
20. óra • delet e pDog;
48 : 49 :
50 : 51 : 52 :
return O;
menet (O)Quit Dead: l Woorl (O)Quit Dead: 4 Grrrrrr (O)Quit Dead : ., Ia this
(l)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Rol l Over (7)Play
(l)Speak (2)Hove (3)Eat (4)Growl (5)Whimper (6)Roll Over (7)P!ely
(l)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll Over (7)Play the end of Littie Caesar?
{O)Quit (l)Speak (2)Move (3)Eat (4)Crowl (5)Whimpcr (6)Roll Over (7)Play Dead: O
A 3-14. sorb:ln létrebozzuk a Dog osztályt h6llagfüggvénnye l, melyek mindegyike azonos visszatérési típusCl és pamméter-szignatúrájll. A 16. sorban typedef-fel definiáljuk a PDF típust, amely egy olyan Dog-tagfüggvényre vonatkozó mUlató, amely nem vár par·DogFunctionsIMethod-lj) () ;
Valljuk be, ez így egy kissé ezoterikus, de ha a programban valami miau éppen ragfü8b",ényekb61 álló táblázatm van szükség, ez Cf,'Yszeruöbé és 0lvashat6bbá teszi a kódot.
I
400 VI. rész· Különlegességek
Kérdések és válaszok Kérdi'!>: Miél1 haszllá/llánk slatikus vállozókal, ha lehel globálisakails lIasZ/lálni? \fáIasz: A statikus változók ha16köre egy osztályra ko rlátozódik, igy csak ezen osztály példányaiból érhetóek el: nyilvános változ6 csetén explicit és teljes osztálynév-megadással, vagy esetleg egy statikus tagfüS&",ényen keresztüL A statikus adatok típusa az osztályhoz kÖl6dik, és a szigorítou elérés, illetve :I Z erds típusoSs{lg miatt b iztonságoslibban használh:Hók, mint a globálisak. Kérdés: ANér/ "aszt/álnúnk slaukus lagfüggvényel-.:cI, lIa lehet glo/:Jálisakal is !taszllál"i? Válasz: A statiku.s lagv:i lloz6k egy osztály hatókörébe tm1.07;nak, és Csak az adott osz-
t(lly objektum{m keresztül érl1et6ek el, vagy kifejezett és teljes osz!álynévvel, mi nt példúu l C1U8sName: : Funct;.ionName ( ). Kérdés: Miér/ nCIII Icsszf}k mindcll osztályul/kal aZOIl osztá{}'Ok barálaivá, amelyeket használllak? Vúlasz: Egy o..o;ztály baráuá nyilv{mít:'isa napvilágra hozza a megval6.sítás részleteit és la7.ítja az egységlx:zárást. A cél :lZ, hogy az osztályok megvalósításának a részletei lehet6leg maradjanak rcjrvc más osztályok eI6t1.
Gyakorlatok Az elmúlt órában megisrnerkedtünk a különleges osztályok, fí.iggvények és mUlatók világával. Yíllaszoljunk meg néhány kérdést és végezzünk cl nC:hány fel::tdatot, hogy el1l1élyíL<;ük ezzel k:lpcsol:ltos tudásunkat.
Kvíz 1. Mi II .szerepe a statikus adatugoknak egy osztályon beled? 2. Mi a szerepe a statikus tagfüggvényeknek egy osztályon belLil? 3. Milyen e16nye van annak, hogy lélrehozzuk II string. hpp állományt (20.3 List:I), ahelyett, hogy beillesztenénk tartaImát II 20.4 Listába, ahol haszn5ljuk? 4. Milyen e16nyökkel jár a barátosztályok használata?
20. óra· "1 I
mutatók 401
Feladatok 1. Vegyük ki a megjegyzésb61 a string . hpp állomány (20.3 Lista) cout lIIasításait
(36, 50, 62, 74, 82, és 99. sor), majd futlassuk le az employeelllain. epp programot (20.4 Lista). így láthatóvá válik, hányszor hívódnak meg a konstruktorok, destruktorok és operátorok. 2. Módosítsuk úgy a ptrarrayfunction . cpp programot (20.6 Lista), hogy helyesen kezdje a fe lhasználó által bevitt értékeket! (Ötlet: Mi tárolódik eJ a tömbben érvénytelen bevitel esetén? Ne felejtkezzünk el a 42. sorról!) 3. Hasonlítsuk össze a string . hpp állományban (a 20. 1 tistában) definiált String osztály lehet6ségeit azzal a szabványos könyvtárral, amely a fordít6programrnal (:rkezett. A Borland fordítóprogram esetén ez a Súgó, Súgó témák, Já l1alom menüpont Referellcia részére katti ntva érhető el. A súg6ablakhan látszani fog a h ivalkozá.~.
Válaszok a kvfzkérdésekre 1. Az osztály összes objeklumához csak egyetlen példányban léteznek. Lehet6vé teszik, hogy az Oszt<'ilypéldányokra vonatkozó fontos közös adatokat nyilvánrartsuk (például a létez6 példányok darabszámát). 2. A statikus adattagokhoz hasonloon aZ osztály összes objektumához egyetlen változatban példányosulnak. Ált.1lában a statikus adauagok eléré~re szolgálnak. 3. A string. hpp-hez hasonlÓ beemelend6 (illclllde) állományok létrehozásával lehet6vé válik, hogy ugyanazt a k6drészletet több programmodul is (esetleg több munkatársunk is) használhassa. Ez egy remek programozási gyakorlat; inkább Jehet6ség, minL<;em a fordil6progmm elvárása. 4. A ban'Ítok lehet6vé teszik m:~gu k közt ~ tilkaik" mcgoszliisát. Ahogya védett elérés lehet6vé teszi :LZ egymásból származtatott osztályok között :lZ adatok és rüggvények elérését, míg a világ többi része el6l mindez elzárva marad (így a priv,ít és a nyilvános "közötti" az elérési módjuk), hason lóképp működ i k a baráti elérés is a megfelel6 osztályok, tagfiiggvények illetve az erre kiv{dasztott I'i.iggvények között.
21.
ÓRA
Az előfeldolgozó Ebben az órában a következőkról lesz szó: • Mil nevezünk felt ételes fordításnak, és hogyan alkalmazhatjuk a gyakorlatban • Hogyan írhatlInk makr6k.11 • Hogy:m használhatjuk lZ el6feldolgozó szolgáltat.1sait hibakeresésre
Az előfeldolgozó és a fordftó Valahányszor elindítjuk egy program fordítását, először az előfeldolgozó kapja meg azt. Az e16feldolg07.6 először is kikeresi II forrásk6db61 a neki szóló utasítások:lt, amelyek mindegyike egy kelt6skereszttcl (ji j pound) kezd6dik. Ezek az utasítások kivétel nélkül valamilyen szövegm6dosítási eljárást írnak le, amit az előfeldolgozó a forrnsk6don fog végrehajtani. Az eredmény egy új, átmeneti fOfrásfájl, amit progmmoz6ként általában nem is látunk. Ugyanakkor e16írhaljuk az eltífe1dolgoz6nak azt is, hogy készítsen egy mentést cbb6l a köztes állapotból, hogy késo"bb tanulmányozhassuk.
VI. rész •
A fordítóprogram soha nem használja az eredeti forrásfáj lt, helyette mind ig az előfel dolgozó kimenetén kezd cl dolgozni. A már eddig is gyakra n látott # include direktíva például arra utasítja az el6feldolgozót, hogy keresse meg a parancs után megadott fájlt, majd annak teljes tartaImát szúrja be a {Ydrancs helyére úh'Y, mintha azt nú magunk gépe1tük volna be. Amikor a fordító megkapja a fájlt, a beszúrt tartalom már on van.
A köztes állapot mentése Gyakorlatilag valamennyi fordítónak van egy oly
A #define direktfva használata A IIdefine direktívával egy karaklerláncokon végezhct6 hclycuesílést írhatunk e16. Nézzünk egy pé ldát: IIde(if\tl EIG 512
Ez az elCífeldolgozónak szóló utasít.ls azt jelenti, hogy bárhol, aho1 a BIG szöveget látja , cserélje azt le 512-re. Fontos hangsúlyozni , hogy itt a k.. raklerlánc szót nem annak C++-os értelmében használjuk. A IIdefine direktíva után tulajdonképpen egy jelet (tokent) adunk meg, ami a sl6 általános értelmében véve egy karakterlánc, és amit a kMszövegben egy másik karakterláncr:1 fog le<::serélni az előfeldolgozó, függetlenü1 attól, hogy a7. egy karaktcr!{111ccal, egy állandóval , egy tömbbel vagy bárm i egyébbel kapcsolatban bukkan-e fel. Né7.zünk talán egy konkrét esetet: Idefine BIG 512 int myArray [BIGI; Ilyenkor az cl6fcldolgozó által el6állíloll köztes fájl II következ6t fogja tartalmazni: int InyJ\r ray [ 512 J ; figyeljük meg, hogya ltdefine lJ(;lSítás már nincs sehol. Az elGfeldolgozó a köztes k6dból eltávolítja az összes neki szóló utasítást, v'lgyis azokal a fordítóprogram soha nem is látja.
21.óra-
A #define használata állandókkal kapcsolatban A idef i ne egyik leggyakoribb felhasználási módja az állandók definiálása. Amim eZl már kor.ibban is említelllik, ez aO! esetek túlnyomó többségében nem valami jó ötlet, mert az el6feldolgoz6 egysl.cru szöveghelyettesítést végez, a kontexmst, vagy a típusokat soha nem elemzi. Éppen ezért sokkal biztonságosabb a const kulcssz6t használni ilyen helyzetekben.
A #define és az #ifdef használata vizsgálatok végzésére A #dcfine direktíva hasznli(at{lnak másik módja az, amikor mindössze :umyit közlünk általa :IZ clófcklolgozóv;!l, hogy egy karakter!{tnc - illetve az általa képviseltloken defini:í !l. lr!wjlJk p61dául a következ61: ~d e finc
BIG
A k(x.! későbbi részeiben aztán megvjz'sgfl lh:ltjuk, hogy meghatározotl-e ennek a tokennek az érték, éS:l vizsg!i1at eredményét61 függ6en végeztethctünk el más mGvelctcket. Ehhez az lIifde [ és az IIlCnde f direktívákat basználhaljuk, melyek közül az elM) :lkkor ign, lm a tokcn del1nitilt, a másik pedig akkor, ha nem. Mindkét utashástömhöt eb'Y fendif direktívának kell zárnia, még a kövctkez6 bezáró k.lJX..-.;Q.c; zárójel előtt. Az li fdef segitségével megfogalmazou reltétel tehát akkor értékelődik ki igazra, ha az utáml sze replő tokent korábban már def'ini:íltuk. Ha például egy feltételesen végrehajtandó nyomkövetési utasítlist szeretnénk beépíteni a k6dba, azt a következ6képpen tehetjük meg: U fdc f DEBUG cou t « 'OCbug d c fin cd '; .cndif
Arnikor ez előfcldolgozó elérkezik az lIifdef direktívához, megnézi ti belső nyilvlintar(Ó táblázatában, hogy szcrepel-e benne a DEBUG szimbólum. Ha igen, :lkkor :IZ Itifdef ut'l.~ítás igazra é rtéke l őd i k ki, melynek következtében minden, a következ6 !telse vagy ltendiC direktív:íig szerepl6 k6dsor belekerül a fordítónak {uadotl köztcs kódba . Ha ezzel szcmben a logikai vizsgálat hamis értéket credményez, akkor az It ifdef és a következő #else vagy ~endiC direktíva által határolt sorok nem kerülnek bele II Icfordítandó kódba. Kimaradnak, mintha soha nem is lenek volna. Jegyezzük meg, hogy
405
406 1VI. rész • Különlegességek
Az #else direktfva Arnint a név is sugallja, az #else direktíva az " ifdef vagy #ifndef és az II endi f kü~ zött szerepelhet, és a feltételes utasításrendszer másik ágát képviseli. A eddig megismert direktívák használatára mulat példát a 21.1. Lista.
21 .1. Lista - A #defin8 direktfva használata lusingdefine.c
o:
1/ 21.1. Lista A Ide fine direktiva használata
1 , 'define DemoVersion 2 : 'deHne OOS_VERSION 5
3 : 'include 4 : int main () 5: { 6 : s td : : cou t « 'Check Jng on t he dc f i n i t ione oC DemoVersion, • 7: « 'DOS_VERSION {lnd WI NOOWS_ VERSION .. . \ n ";
B. 9 : *ifdef DemoVeraion 10 : std : : cout « "DemoVersion defined . \n '; 11 : ~else II DemoVeraion 12 : std :: cout«
' OCmoVersion not defi n ed . \n ";
13 : Icndif /1 DemoVersion H ,
15 : 'iEndef DOS_VERSTaN 16 : 17 : l B: 19 : 20 : 21 : 22 : 23 : 24 : 25 : 26 : 27 : 28 : 29 :
std :: cout« "DOS_VERSION not de(ined ! \n "; "else II DOS_ VERSION s td: : cout « " DOS_VERSI ON dcfined "'S : " « DOS_VERSION « s t d :: cmU; nc nd if II DOS_VERSION li f def WINDOWS_VERSION std : : cout« "WINOOWS_VERSION defined ! \n" ; 'else II WINDOWS_ VERSTON std : : cout « 'WINDOWS_VERSION w",s not defined . \n" ; 'endif II WINDOWS_VERSION std : : cout « rc t ur n O;
"Donc . \n' ;
30 :
Check ing on Ule definitions o f DemoVersion, OOS_ VERSION a n d WINDOWS_ VERSION ... DcmoVers ion defined OOS_ VERSION defincd as : 5 WINOOWS_VE RSION was not de finc d . Done.
21. 6ra • Az
Az 1. és 2. sorokban definiáltuk a De moVersion és a OOS_VERSION Lokeneket. Közü lük a OOS~V ERS! ON énéket is kapott CS). A 9. sorban az elófeldolgoz6val megvizsgált3ljuk, definiálva van-e a DemoVersion. Bár ehhez a tokenhez nem tartozik érték, .tzért szerepel az előfeldolgozó bels6 tábláz.ttában, tehát a vizsgálat logikai igazra értékel6dik ki. Ennek hatására a 10. sor bekerül .. k6db.. , vagyis a képernyóre ki fog ír6dni a megfelelő szöveg. A 15. sorban a DOS_VERSI ON dcOniáltságát, pontosabban annak hiányát vil'_<;gáljuk. Mivel az előfe l dolgozó számára. a OOS_VERSION token is definiált, ezért a teszt é rtéke hamis, ami azt jelenti, hogy nem a 16., hanem a 18. sor kerül bele a Icfordítandó kódba. Ht a DOS_VERSION helyére egyben behelycucsít6dik az; érték, vagyis a fo rdítóprogram végü l a következ/'} kódsol1. fogja látni: cout «
'OOS_VER SION defined as : • «
5 «
Gnd!;
Figyeljük meg, hogya DOS_VERSION else; e l őfordulásá t az előfeldolgozó érintetlenül hagyja , mivel az kettős idézőjelek között szerepel a k6dban. C.<;ak a második OOS_VERSION . ulnik el ", illelve kenU be helyére a megfelelő élték. Maga a fordítóprogra m tehát ebben ;1 sorban mar csak az S-ös számot fogja lálni ugyanúgy, mintha azt mi gépeltük volna be a megfelel6 helyre. Végezetül a 22. sorban az el6feJdo lgo z6 megvizsgáJja a WINDOWS_VERSION to ken definiáltságát. Mivel :IZ ezt me gel6 z6 kódban sehol nem szerepelt a ' dcfine WINDOWS_VERSION
sor, ezért ez a token az előfeldolgozó számára ismeretlen, nem definiált. A vizsgálat végeredménye így logikai hamis, vagyis a lefordítand6 kódba nem a 23. hanem a 25. sor kenil majd be.
Beszúrás és annak vezérlése Egy projekt {tltalában számos forrásfáj lb6l á ll. Alapvet6en sokféleképpen lehet szervezni egy kód szerkezctét, de objektum-orientált tervezésnél az egyik legkézenfekv6bb módszer az, ha minden egyes osztálynak van egy saját fejlécállom5nya C. hpp), benne az oszL'íly deklaráci6jával , és egy a konkrét megva l6sításltanalmazó . c pp állománya, ami a metód~l sokat tartalmazza. A main () filggvényt célszerűen szintén egy önálló. cpp állomány tarta lmazza. A fordítás során aztán valamennyi. cpp fájlb61 készül egy tárgyk6dú állomány C. obj), amelyeket végül a linker összeszerkeszt egyetlen funatható p rogrammá.
408 1VI. rósz· Különlegességek Mivel a programok többsége s7.ámos osztályt használ, számos különböző helyen kell a kódba beszúrni az ezek deklarációját L,1rtalmaz6 fejlécállományokat. Emellett az sem ritka, hogyafejlécállományok egymásra hivatkoznak, vagyis egymást töltik be. lia például van egy szá rmaztatott osztályunk, és ehhez tartozik egy külön fejlécállomány, akkor ennek nyilvánvalóan be kell emel nie az alaposztMy fejlécá llomá nyiít is. Képzeljük e l, hogy az AnimaI nevű osztály deklarációjá( az ANIMAL . HPP fájl tartalmazza. A Dog osztályt az AnimaI osztályból származt:ltjuk, ezért a JX)G. HPP-nek be kell szúmia az ANIMAL. HPP-l is, Iliszen az tartalmazza az alaposzlállyal kapcso1:l1os információkaL Tegyük fel , hogy létrchozunk egy eae osztályt is, amelynek alaposztálya szintén az Anima! oszt..'íly, vagyis a CAT . HPP-ben is szerepelnic kcll cb')' az ANIMAL. HPP-l bes7.úró parancsnak. MármOSI ha ínmk egy olyan metódlIst, amely egyszerre hasl.nál C6t és Dog típusú objekl'Umokat, akkor az ezt tartalmazó k6dnak egyaránt be kell emelnie II CAT . HPP-t, és a rxx; . HPP-t, ami viszont azzal jár, hogya köztes k6dllnkban k6tSZt:r fog szerepelni az ANIMAL. HPP t:1rt<1IIl1
A helyzet tehát é rdekes, hiszen erre fordítási hibát kell kapnunk, mivel nem deklarálha\'unk egy oszt{llyt - jelen esetben az l'Inimal-L- ké tszer, még akkor sem, ha a k(:t deklaráció egyébként mindenben megegyezik. EZl a p robU~ mát úgy kerülhetjü k meg, ha megfelelő jelzőkkel vezéreljük a fejlécl lJományok beszúrását Onclusion guards). A módszer egészen egyszen1. Szúrjuk be az ANIMAL. Hpp elejére, illetve végére a következő sorokat: ~ifndcf
ANIMAL_HPP
*define hNIMl'IL_HPP ... II ide jOn a fejlécállomány te lj es tartalma ~endif
Ez a kódrészlet az c1őfeklolgoz6 számára a következőt jelenti: ~ Ha az ANTMAL.-HPP token nem meghatározoti, akkor tegyi.ik nzá, amúgy pedig mehetünk tovább a kódban ~. A Idefine és az lendif között szerepel a fej léc-.íllomány teljes ~hasznos~ tartalma. Amikor progmmunk els6 alkalommal szú~a be a fenti fejlécállomá nyt, az előfe l do lgozó elolvassa az első SOlt, amely igazra é rtékelócJik ki, hiszen az ANlMAL_HPP token még soha nem szerepelt. En nek megfelelően a feldolgozás halad tovább, a k6clba bekerül a fej J6d llomány tel jes t:l l'talma, mellesleg ped ig meghatározottá válik az ANIMll.L_ HPP. Amikor második alkalonum l pr6báljuk meg heszúrni ugyanezt a fáj lt, az előfeldolgoz6 megint elolvassa az els6 son. Ekkor azonban a feltétel már hamis lesz, hiszeo ANtMAL_ HPP- t az előző alkalommal definiáltuk. Ennek megfelel6en az előfeldolgozó elkezdi keresni a kövelkező lIelse direktívát (jele n esetben nincs ilye n) vagy a következő fiendif-ct (ez esetünkben egyben a fájl vége), és addig a pontig mindent kihagy. Ez konkrétan azt jt!lenti, hogy gyakorlatilag nem tönénik senuni, hiszen a fej lécállomány teljes tartaimát kihagyjuk, vagyis bár kéLo;zer szerepelt II kódban a z AnimaI osztály deklarációját tartalmazó részlet beszúrása, mégsem keletkezik szintaktikai hiba.
21.óra A kódban szercpl6 szimbólum neve CANH1AL_HPP) amúgy teljesen lényegtelen, de az általánosan bevett szokások szerint megegyezik a fejlécállomány nevével úh'Y, hogy azt csupa nagybetűvel írjuk, és a pontot Iccseréljük aláhúzás karakterre. Hang5Úlyozzuk, hogy ez csupán konvenció, nem a nyelv álta l támasztott követelmény.
Tudta hogy...?
A beszúrási direktfvák védelme
A fent bemutatott védelmi módszernek semmiféle ~káros mellékhatása~ nincs. Hasz· náljuk tehát bátran, mivel bizonyos esetekben sok órányi hibakeresést spórolhatunk meg vele.
Szimbólumok definiálása a parancssorban Csaknem v:alamennyi C++ fordító Ichct6vI! teszi, hogy mindazt, am it a /ldefi ne direktíV'I s(!gíts(;gével magában a k6dhan dcfini:t1n{l11k, megadhassuk a par.Jncssorb:lll, vagy az intcgr.ílt rejleszt6i környezeten keresztül is. Ha tehát a célszerűség úgy kívánja , a 21.1. Uslából akár ki is hagyhatjuk az 1. és a 2. sort," DcmoVersion és a OOS_VE:R · S ION szimbólumokat pedig bizonyos fordításoknál meg:ldhatjuk a parancs.'.orban, míg más esetekben simán kihagyhatjuk 6ket. A programozók körében teljesen :'ihaláno.'ian alkalmazott munkunódszer:lZ, hogy a nyomkövetéshez használt k6drészletckct fi fdef DEBUG és lendif direktívák közé zá~ák, majd amikor nyomküvetést akarnak végezni, akkor fOfclít{\skor a parancssorban megadják ezt a szimb6l u111QL. Ez rcndkívü l egyszerű és kényelmes módszer :lml, hogy a nyomkövetési részeket egyetlen mozdulattal kihagyhassuk al efordított kódból, de ~zükség eselén azonnal ~vissz.1Varázsolhassuk " 6ket. Ha a végs6 kódot fordítjuk, egyszerűen ki kell hagyni a param:ssorból a DEBUG meghatározását és kész.
A meghatározottság megszüntetése Ha korábban meghatároztunk egy szimb6lumot, de a kód hátralevő részében törölni szerelnénk azt, akkor használhatjuk az lIundef direktÍv;ít. Ez egyszcrúen a "define ellentéte, vagyis törli az előfeldolgozó bels6 táblázatából a kérdéses tokent.
Feltételes fordftás A Itdefi ne és a parancssori dcfiníciók, valamint az hfde f, "eJse és IIHndef direktívák segítségéve l olyan kéxlOl írhanmk , amdynek egyes darabjai attó] füg,gően kerülnek bele alefordított progmmban, hogy éppen nillyen IIdefine direktívák szerepelnek benne, vagyis melyek az aktuálisan meghatározott szimbólumok. Ezt a lehet6séget felhasz-
409
nálhatjuk például egy olyan program fejlesztése során, amelynek két különböző platformon, például DOS és Windows alatt is működnie keH, de nyilván máskénl. Így nem keU kél külön kódoL ka rbantartanunk, elég egyetlen kódbázisra építeni a projektet. A feltételes fordítás másik nagy felhasználási területe a nyomkövetés, amikor a már korábban említett OEBUG szimbólum meghatározottságát vizsgáljuk a megfelelő pontokon. Errol a módszerr61 amúgy hamarosan bővebben is ejtünk még szót az assert () kapcsán.
Makróként megvalósított függvények A Itdcfi ne direktív{lt úgynevezett mak.rófüggvények megadására is felhaszn1ilhatjuk. A makr6függvény olyan az elóf"e1dolgozó által értelmezett nyelvi konstl1.lkdó, amely form:lilag a függv(:nyekllez teljesen hasonlóan képes argumentumokat fogadni. Ebben az esetben persze a behdyettesítf!st az előfeldol gozó végzi el, a p:lrarnéterek pedig természetesen mindig karakterláncok. Nézzünk talán egy példát, ami rávilágít a dolog lényegére. Definiáljunk egy olyan TWICE nevlÍ m3krófüggvényt, amely egy 1llegszoroz egy számot kelt6vel: ~ define
TWICE(x)
(
(x)
*
2
)
EZl :IZ új nyelvi konstrukciót megadása után a kódban a
következőképpen
haslnálhatjuk:
TWICE(4)
Az e lőfeldolgozó ebben az esetben a TWICE (4 ) karakterláncot el fogja távolítani a forráskódból és kicseréli 8-ra. Ez kissé részletesebben úgy történik, hogya behelyettesítés eredményeként előá1l6 ( (4) * 2 ) kódot az el6feldolgoz6 ki is énékeli, és ennek az eredménye keJiIl a makrófüggvény hívásának helyére. Egy makrónak természetesen egynél több paramétere is lehet, és bármely paramétert egynél többször is felhasználhatunk az elvégzendő míivelet leír(lsa során. A két talán leggY'lkrabban használt többparaméteres makró, a MAX és a MI N definíciója például ... követkcz6képpen fest: #def i ne MAX(x,y) ~d ef i ne
MIN(x,y)
(
(x)
>
(y)
(x)
""
(y)
? ?
(x)
(y)
(x)
(y)
Ügyeljünk r:í, hogya makródcfinídókban a paramétereket körülzáró zárójelpár nyitó tagjának közvetlenül a makró neve után kell következnie, nem lehet köztük szóköz. Általában is igaz, hogy az előfeldolgozó sokkal kevésbé megbocsátó a fölösleges üres karakterekkel kapcsolatban, mint a fordítóprogram. Lássuk, mi történik, ha megfeledkezünk Ildefine MAX (x ,y) ( (x )
>
(y )
?
erről:
(x) : (y ) )
21. óra •
I I
Pr6báljuk csak ki, mit tud a MAX makrónak ez a változata: int x = 5, Y = 7, Z; z = M!\X(x,y);
A fenti k6drészletct az előfeldolgozó ebben az esetben a
következőképpen
ala látja át:
int x = 5 . Y = 7 . z; z .".
( x,y )
(
(x )
>
(y )
?
(x )
,
(y )
)
( x,y )
Látható, hogy a fölösleges szóköz miatt közönséges szövegbchdyettesítés történt a makr6füID,>vény értelmezése helyett. Az előfeldolgozó tehát úgy gondolta, hogy van a MAX szimbólum, amit a (x,y ) ( (x ) > (y ) ? (x ) : (y) ) szöveggel kell heIyettesítenic, hiszen ez szerepelt a definícióban. És a maga módján igaza volt... J Ja ellenben kivesszük azt a bizonyos extra szÓközt, akkor a köztes kód a képpen fest:
következő
int x • 5 , Y • 7, Z;
z ". 7 ;
Mi ez a rengeteg zárójel? Az olvasÓ bizonyára <:!ksod§ lkozott non, mit keres az a rengeteg, mer6ben fölöslegesnek nín6 zárójel az eddig bemutatott makr6kban. Mag'l ,IZ el őfeldolgozó ugyan nem követeli meg ezeknck a kiírását, ugyanakkor számos potenciális hibát kerülhetünk el az1hal, ha mindent, az utolsó részlctig akkur1nls.1n bez1r6jelezilnk. Efféle nem várt mel1ékhatáSOk általában akkor bukkannak fe l, ha nem egyszeru számokat vagy változókat adunk át paraméterként a makróknak , hanem összetettebb kifejezéseket. A dolgot megint egy példán lehel igazán jól szemléltelni. Vegyük a már látott MliX makr6mlk egy "zár6jelmentes1tett" vá ltozatát, és nézzük m<:!&, hogyan mLTköc!ik: Idefine MAX (x, y) x > y
? x
: y
Ha parnmélerként az S és 7 értékeket adjuk ál neki, minden a legnagyobb rendben. De ha valami összetettebb kifejezéssel próbálkozllnk, röglön érdekes dolgok lörténnek, Ezt szemlélteti a 21.2. Lista.
21.2. Ulta - Miért használunk annyi zárójelet 8 makrókban .. , (usingparen,cpp)
o: 1/ 21. 2 Lista Makr6k kifejtése 1: .include 2, 3:
~ d efi ne
4,
#def ine THREE( a ) a .. a .. a
CUBE(a)
S, ú : int
mai n ()
7, { 8 : lon g x
=
5;
(
(a)
..
(a)
..
(a)
)
411
rész· 9, 10: ll : 12 : 13 : 14 : l S:
long y long
,
"
CUBE(x) ; THREE(x) ;
std : : cout « std : : cout «
,
lo ng
16: y
,
" 5,
b
.y :
«
y «
·z:
«
«
"
,
std: : end1 ; std : : endl;
7,
CUBE(a .. bj ; THREE(a+b) ;
17 : " 18 : 19 : std : : cout « 20 : std : : cout « 21 : return 0, 22 :
.y: "z :
« «
y «
,
«
std : : end1; std : :end1;
Kimenet y , 125 z : 125 y , 1728 82
"
A 3. sorban cld"iniálunk egy CUBE (köb) nevt1 makr6l, amelynél az a paramélen annak minden egyes előforcl ulásakor 711rójeibe tesszük. A 4. sorban lálható THREE makr6nálamely hítsz61ag ugyanazt él mQ'velelel hivatott elvégezni - nincsenek zárójelek. A makrók haszná latár demonstráló kódban e lőbb mindkettőnek az 5 értéket adjuk :'it paraméterként, és amint az a kimenetból látszik, mindkettő mOködik is szépen. A CUBE (5) kifejt<:se az ( (5) * (5) .. (5) ) karakterlánc lesz, ami kissé pongyolának runik, de az eredménye kétségkívül 125, ami helyes. A THREE (5) kifejtése ezzel szemben egyszeruen 5 * 5 .. 5, ami szép, egyszeru , és szintén 125 az eredménye. És akkor jön a meglepetés. A 15-17 sorokban paraméterként az 5 + 7 kifejezéSI használjuk. Ebben az esetben CUBE(5 + 7) kifejtése a következ61csz: (
*
(5 +7 )
(5+7)
*
(5+7)
EZl kicsit tovább a.1akílva a (
(12)
*
(12)
~
(12)
)
követk ezőt
kapjuk:
)
Az eredmény pedig természetesen 1728, amint az várható is. Lássuk, hogy teljesít a másik makró ugyanebben a sziruáciÓban. A THREE (5 + 7) az előfeldolgozó munkájának eredményeként a következ6vé változik: 5 + 7 * 5 + 7 * 5 + 7
21. óra • Mivel a szorzás magasabb precedenciájú mIlvelet, mint az összeadás, a fenti kifejezés t'gyenértékű a következővel: 5 + (7 .. 5)
-I-
(7 .. 5)
Ezt tovább alakítva a 5
t
(35)
+
(35)
+ 7
következőhöz
jutunk:
+ 7
Az eredmény 82, ami a sz6 egy bizonyos értelmében ht'lyes ugy;m, de egyáltalán nem
ezt vártuk
ettől
a makrótól. Nos, ezért kell oda az a rengeteg zárójel.
Makrók, függvények, sablonok A CH rmkróival van néhány (összesen négy) probléma, El őször is a nagyobb d,u". . bokm meglehet6sen ne héz visszafejteni, mivel minden makró denníciójának egyetlen sorban kell szerepelnie. A backslash (\) karakwrrel ugyan beikWthaturlk sortörést, de az igaJ'_:U megvallva ez nem sokat haszmíl. Akármit teszünk, a nagyobb makrók egyszeruen nehéz karb:mtartani. A másik problémát a l1lakr6k mllködési logikája jelenti. A makr6kat :lZ e l őfeldol gozó értelmezi, vagyis az általuk el őírt helyettesítési nlliveletek mindannyiszor leflltnak, valahá r'yszor a makr6 felbukkan il k6db;U1. [la 12-szer haSzn{t[lI11k valamit, akkor az e[őrcldo lgozó l2-szer fogja "lefuttatni» a m,lkrót, szemben ;I Z igazi függvényekkel, amelyeknél csak egy hiv:ilkozás kerül a kódba. Ugyanakkor az is igaz, hogy a makrók álta lába n gyorsabbak, mint a fOggvények, hiszen segítségü kkel a lefordított kódban clkerüljük a függvényhívással járó löbbletmunk:'it. A feldolgozás módjából adódik a harmadik probléma is: maguk II makrók a köztes kódba n már nem szerepelnek, azokkal a fordítóprogram egy:'iltal:'in nem találkozik. Ebb6l következ61eg a legtöbb nyomkövető sem. I (a tehát hibakeresés során egy makfÓra gyanakszunk, mindenréle kö rmönfont trükköket kell alkalmaznunk. A legnagyobb problémáI a végére hagytam: a makrók nem típusbizrosak. Egyrészr61 nagyon kényelmes, hogy egy makr6nak bármit átadhanmk par
413
414 V1.ré,,' ..
Karakterláncok kezelése Az elófe klolgoz6 két olyan speciális operátort bocsát a rendelkezésünkre, amelyekkel a m .. kr6kba n felbukkanó karakterláncokat lehet feldolgozni. A szövcgg(: alakító Cstringizing) operátor (I) akárnti is következik utána , azt idézőjelek közt: zárt karakterlánccá al akítja . Az össze fúző operátor Cconcatenation operator; III) nevének megfele16en két karakte rláncOl ch'Ycsít.
A szöveggé alakrtó (stringizing) operátor A szövegg/! alakító (slringizing) operátor, vagyis a ... úgy működik , hogy :IZ utána kő velkez() szövegel - legyen az bármi - idézőjelek közé teszi, egészen a következő üres kanlkLc.:rig. ]-Ia tehát ;l következőt írjuk: Idefinc WRITES TRING(x) cout «
majd meghívjuk ezt
rt
#x
lllakr61:
WRITESTRING(Thio io a string) ;
akkor az előfe l dolgozó a cout «
következő
köztes kódot fogja
előá ll ítani :
"Thi s is a string" ;
Figyeljük meg, hogya fordítónak átadou k6dba a karaklerlánc (This is a string) már ke tt6!'i idé.:őjcl ck között szerepel, ahogy azt a cout megköveteli.
Az összefúző operátor Az összefírző operátor (concatenalion operalor; II H) lehet6vé teszi, hogy kél vagy több
karakte rláncot egy új sz6vá fűzzünk össze. Az új szó a fordító számára valójában egy token lesz, amit kés6bb használhatunk egy osztály vagy v{jltoz6 nevekém, egy tömb címzésekor eltolási értékként, vagy bármely olyan szituád6ba n, amikor betl.1k sorozatát v,hja a fordítóprogram. Tl:gyük fel például, hogy van öt függvényünk, amelyekí:!l a következ6képpen hívnak: fQnePrint, íTwoPrint, fT hreePrint, fFo urPrint, és f FivePrint . Ezek használatál egyszcn1sítcnd6 létrehozhatjuk a következő makr6l: fdefine fPRINT(x) f 1111 x U Print Ha kés6 bb szükségünk van mondjuk az fTwoPrint függvényre , :Ikkor aZl a fPRINT(Two) fo rmában is meghívhatjuk. Teljesen hasonlóan a fpRINT(Three) forma a fThreePrint függvénynévvellesz egyenértékű.
21. óra • A 19. órában a láncolt listák kapcsán létrehoztunk egy Pa rtsList nevű osztályt. Ennek él listának kizá rólag Li s t típusú elemei lehelnek. Tegyiik fel , hogy ez a mócbzer valamilyen programmal kapcsolatban kiválóan beválik, és a sikeren felb~lzdll lva szeretnénk létreho7.0i állatok, autók, vagy számítógépek listáit is. Ez egyik megoldás ilyenkor az, hogy él megrele l ő kódrészletek szolgai átmásolásáva ! létrehozzuk az Anima lLi s t , CarList vagy Compu t e r"Lis t nevl1 osztályokat is. Ez elsőre ugyan egyszen1nek tl1nhet, de ha karban kell tartani a kódot, igencsak fájni fog a fejünk , hiszen bárhol is vezelÜnk be valamilyen módosítást, azt át kell vczctnünk az összes többi helyre is. Sokkal elegánsabb és persze hatékonyabb is, ha megfelelő makrókat és az összeftízés operátorát használjuk . íme egy lehetséges megoldás: Rdefine Listof(Typel class Type'.List \ (
\
pub lic , \ Type ~#L ist(){)
\
private : \ int i tsLeng th; \
"
A fel1ti példa természetesen kissé er6ltetett, hiszen leginkább pcdagógiai célokat szo lgá i, ám él lényeg világos.111Iátszik: ;I megfelel6 helyre beemeljOk :IZ összes adatot és metódusl. Ha magával a makróval készen vagyunk, az AnimalList osztályt eb'Yszeruen a kövctkezőképpen hozhat juk létre : Listot (Animal )
Bármennyire meglepő, ez az egyetlen sor az előfeldolgozó munkája nyomfl n átalakul az An:Í.mal List oSltály teljes dek lart'lciójává. Persze azért van n~hány probléma is ezzel a megközt!lítéssel , ezekről azonban majd a 23. órában lesz sz6 a sablonok k:lpcs.'Ín.
Előre
meghatározott makrók
Számos fordítóprogram eleve tartalmaz néhány tucat általánosan használt makr6l. Jlyen például a _DATE_, a _TI ME_, a _ LINE_, és a _ FILE_. Minden ilyen makr6 nev~ t elöl és hátul két-két aláhúzásjel határolja, amire csupán az~rL van szükség, hogy ezzel minimálisra csökkentsük a felhasználó {Iltal definiált makrókkal való névütközés es~lyér.
Amikor az előfeldolgozó egy ilyen makróvaltalálkozik, automatikusan elvégzi a szüksége.,; hclyeuesítéseket. A _ DATE_ hatására például behelyeuesíti az aktuális dátumot, a _ TIME_ helyére pedig beszúrja az időt. A _ LINE_ és a _ FILE_ az akruális sor számát, illetve a forrásfájl nevét jelenti. Ügyeljünk rá, hogy ezeket a makr6katmint nlinden más makrót - az előfeldolgozó kezeli, vagyis az értékek a fordításra és nem a lefordított program futtatására vonatkoznak. Ha tehát valahova bcszúrjuk
415
rész • a _DATE_ segítségével az dátumot, akkor az a fordítás dátuma lesz, nem pedig az az időpont, amikor a progr.ullot futtat juk. Ezeknek abeépíten makróknak általában a hihakeresés során vehetjük nagy hasznáL
Az assertO beépftett makró S7..3mos fordítóprogram rendelkezik eb')' assert () nevtl előre deHniált makróvaL Az assert () működése viszonylag egyszeru: igaz értéket ad vissza, ha a neki paraméLerkém átadou kifejezés igazra értékel&lik ki, és vagy végrehajtja a megadott urasítássol1, ha nem. A fordíl6programok működése ezen a ponton eltér6 lehet. Számos program megáll, ha egyassert () makró hamisf'J. él1éke lődik ki. Mások csupán kivételt dobnak ilyenkor. CA kivételekr61 és a hibák kezelésér61 majd a 24. órában lesz szó.) Az assert () makró egyik hasznos tulajdonsága, hogy al: el6fddolgozó Leljesen figyelmen kívül hagyja il vele kapcsolatot kódol, ha ti DEBUG token nem def"iniftlt. Amíg fejlesztjük a kódot, a lefordított progmmb:m végig benne lehetnek azok a részk:Lck, amelyek a hib:!keresést.~e gítik. Ugyanakkor nyilv{m értelmetlen volna ezeket a végső programban is benne ha~,'yni . hiszen az így nagyobb és lassúbb lenne. Ha az a!:l!:lert () -ct használtuk, a hi1:mkereséshez használt k6dok egyetlen mozdulattal eltávolíthat6k a bin:írisbóL Természetesen nem kötelez6 a fordít6progmm által felkínft lt assert () makr6ra tá· maszkodni. Megírhatjuk akár a saját változatunkat is. A 21. 3. Lista egy ilyen egyedi - viszonylag egyszerű - assert () megoldásra nllltat pl:ld{ll.
12.3. Usta - Egyegyszertl assenI) makró megvalósftása (simple8ssen.cpp)
o: 1: 2: 3, 4: 5: 6: 7: 8: 9: 10 : 11 :
/1 21 . 3 . ListA 11.2 Assert() makr6 #define DF.BUG linclude
lifndef DEBUG
Idefine ASSERT{x) 8else *define ASSERT{x) \ H (! (x») \ ( \ std : : cQu t « 'ERROR l ! Assert ' « Ix« std :: cQu t « on li ne « _ L! NE_« 12 : std :: CQut « • in fi l c ' «_FILE_«
13 : 14 : 15, 16: 17 : 18 : 19, 20 : 21 :
lendit int main() int x ~ 5 ; 5td,:cQut « 'First asser t : \ n'; ASSERT{x==5) ; 5td :: CQut« ' \nSeco nd as sert : \n';
" fai l ed\n '; "\n" ; \ "\n' ; \
\
21. óra • 22 , ASSERT (x ! = 5) ; 23 : s td:: cout« O\nDone . \n" ; 24 , r eturn O; 25 :
Figyeleml
Fáj~onnátumok
Egyes fordítóprogramok a 21.3. lista 1-13 sorait több sorra darabolhat ják. Ilyenkor ki kell törölnünk a kódból a sortöréseket, másként nem tudjuk lefordítani. A problémát amúgy az okozza, hogy a különböző operációs rendszerek, fejlesztői környazetek és fordítóprogramok különböző karaktereket használnak a sor végének jelölésére (..újsar karakte(), ami aztán a fentihez hasonló értelmezési gondokkal járhat. Apropó
Egysoros makr6k
A makrók kódjának a szó múszaki értelmében nem kell egy sorra korlátozódnia. A 1-13 sorokban látható kód például inden kétséget kizáróan több soros, mégis egyetlen makrót ír le. Ez úgy lehetséges, hogy minden sor végén ott van egy backslash N karakter is, amely elrejti az egyébként fizikailag jelenlevő újsor karak· tert, és a sorok összefüzésére utasftja az előfeldolgazól. Ügyeljünk rá, hogy egy backslash segftségével csak egy újsor karaktert lehet .hatástalanrtani~, vagyis ez azon kevés szituációk egyike, amikor az egymást követő üres karakterek számának jelentősége van.
menet First assert : Second assert : ERROR !! Assert xl .. 5 flIIiled on line 24 in file E:\adisk\cppin24 new\Hour21\simpleassert . cpp
Az első sorban definiáljuk a DEBUG szimbólu mot. Ea amúgy megtehetjOk n pnranessorból vagy az integrált fejleszt6i környezet segílségével is, ami sokak számám egyszerűbb módja a nyomkövetés ki- illetve bekapcsolásának. Az assert () makró mcghatároz.ísát a 7-13 sorok tartalmazzák. Az ilyesmit általában egy fejlécállományba szokás helyezni, amit esetünkben bizonyára ASSERT . HPP-nek neveznénk és a megfelel ő po nlon beszúrnánk a forrásfájIokba. A 4. sorban megvizsgáljuk, hogy definiált-e a DEBUG szimbólum. Ha nem, akkor az assert () nem hoz létre senuniféle kódot. Ha azonban a DEBUG definiált állapotll , akkor a 7-13 sorokban ta lálható kód bekerül a fordítónak átadou fájlba.
418 VI. rósz'
Maga az assert () makró az előfeldolgozó .számára tulajdonképpen egyellen nagyon hosszú par.mcs, amit jelen esetben hél rövidebb sorra törtünk az olvlIshatóság kedvé~ ért. A 8. sorban megvizsgáljuk egy paraméterként átadon kifejezés értékér. Ha ennek a kiértékelése hamis eredményt szolgáltat, akkor II 10-12 sorokban látható utasításokat hajtjuk végre, amelyek egy hibaüzenetet jelenítcnek mcg. Ha ellenben a makr6nak átadolt kifejezés él1éke igaz, akkor semmi sem történik.
Nyomkövetés az assertO makró segftségével Miközben írunk egy programot, ott belül, mélyen II lelkünkben s:dunos dologról tudjuk, hogy igaznak kell lennie. Egy függvé nynek az adon szitlllki6ban egy bizonyos értékel kell vissz~ladnia, egy mUlal6nak érvényes dmCI kell tartalmaznia és így tovább. Progr.m1()z{lsi hibár61 - többek között - akkor beszélünk, ha egy ilyen "alapigazság" bizonyos IlclyzlJtlJkben mégsem tel jesül. Példáu l pontosa n tudjuk, hogy egy 111uLat6nak a megfelelő értékkcllwll rendelkeznie, a programunk mégis egy ezzel kapcso1:.nos hibávalleá ll. Az ilyen és ehhez hasonl 6 hibák 111egtalálásában n:lgy segít.ségünkre lehet az assert () mllkr6, de csak akkor, ha kellően gyakran és :l megfelelő szabadsággal használjuk az általunk fejleszten kódban. Például valahányszor értékel adunk egy nlUtat6nak, vagy paraméterként átadjuk azt egy függvé nynek, ellenőrizzük le az assert () segíL'iégéve::l, hogy az értéke val6ban érvényes-e. Kicsit általánosabban fogalmazva vab llányszor o lyan he::lyzetet látunk, amikor programunk működése egy adott vá ltoz6 adon 61ékétőJ függ, ellen6ri;>;zük le az assert (l-lel , hogy valóhan az a~ énék v:m-e abban a változóban.
Semmiféle káros következménye nincs annak, ha az assert () makrot gyakran használjuk. Ha kikapcsoljuk a nyomkövetést (vagyis nem definiáljuk a DEBUG szimb6Jumot), vabmennyi, az assert () -tel kapcsolatos kód e l tűn ik a progr-.Jmból, mintha on se lett voln:l. A hibakeresés mellett az assert () kifejezései a kód egyfajta dokumcn:... h'isM is jelentik, hiszen a karbantartást végző pontosan [{nhatja, hogy mely pontokon mirc kell í.lgyelnie.
A makr6k mellékhatásai Meg l chc:t őscn gyakori ám eLsőre kissé paranormá lisnak tűnő jelenség, hogy egy hib csak akkor jck:ntkczik, ha eltávolítolluk a lefordított kódből az assert () makrÓkh. kllpcsolmos kifejezl!sekel. Ennek csaknem mindig az az oka, hogya programunk rT'~ köclése nem s7,.ándékosan ugyan, de függ az assert () makrok által végrehajtott m . > veletek, vagy bármilyen más, hibakereséshez használt kódrészletek mellékhatásait( Nézzünk egy példát: ASSER'J' (x -
5)
21. 6ra • Az lu szinte bizonyosan azt akartuk ellen6rizni, hogy x értéke valóban 5, vagy valami más (x == 5). Ehelyett elkövettünk egy bosszantó hibát, hiszen az ötöt értékOl adtuk a változónak, vagyis még ha eddig nem is 5 volt az értéke, most már az. Tegyük fel, hogy közvetlenül ez elűÜ az assert () e16tt volt egy függvényhfvás, ami valamilyen hibás mCiködés folytán nullára állította x értékét. Az assert () kódj[lban sajnos :ítmenetileg kiküszöböljük ezt a csorbát, hislen visszaállítjuk az 5-ös értéket. Mivel pedig az értékadásnak is van visszatérési értéke, mégpedig maga az érték, ezért az x = 5 kifejezés nem csak beállítja x értékét, hanem az assert () -nek is visszaadja azt. Mivel az 5 mint visszatérési érték nem nulla, ezért logikai igaznak felel meg, vagyis az assert () azt látja, hogy - az am6gy teljesen hilúsan megfogalmazott - feltétel teljesü lt. Összességében tehát elkövettünk közvetlenül egymás után kél olyan hibát, amelyek hatásai kiejtették egymást. Az x vált07.6 értéke 5 (naná, hiszen mi állítottuk be), tehát a program tökéletesen mCiködik, egészen addig, amíg be van kapcsolva a nyomkövetés. Aztán amikor elérkezünk a kibocsátáshoz, amikor kikapcsoJjuk a hibakeresésl, jön :l meglepetés. Most, hogy az ass ert () k6dja már nem kenU bele a lefordított állományba, nincs többé semmi, ami x értékél 5-re változtamá. A hibás függvény viszont, ami x értékét nullázza ottmarad, tehát progmmunk hibásan fog működni. EZI konstmáJva lennl'slete.';en viss7.akapcsoljuk a 11yomkövetést, am ire a hiba eltűnik ... Ez az a dolog, amin j6t derül az ember, ha tudja, mir61 van SZÓ, dc sirni tudna tőle, ha kénytelen ~é Jesben " megélni. Legyünk lehát óvatosak a nyomkövetéshez használt k6c:lok mellékhatásaiv;J1. Ha pedig olyan jelenséget találunk , amely kizárólag bekapcsolt nyomkövetés mellett bukk:m fel, az e lső h'Yan(lsíIOIt maga a nyomkövetéshez haszn:'ilt kód legyen. Vizsgá ljuk meg, hol lehet az a bizonyos f-urfangos mellékhat.ás ..
Apropó
Mellékhatások nagyon sok makr6han felhukkanhalnakl
Amint azt a 21.2 Lista 16. és 17. sorában láthattuk, makr6kat nem csak egyes vál· tozókkal vagy értékekkel, hanem egészen összetett kifejezésekkel kapcsolatban is meghívhatunk. (Ilyen történik például az adott kód 9. és 10. sorában.) Ha azonban ez az összetett kód módosftja egyes változók értékét, annak nem várt mellékhatásai lehetnek. Tegyük fel például, hogya CUBE makrót az aH kifejezéssel hIvjuk meg: y
= CUBE (aH);
Al ember ezt a sort olvasva úgy gondolná, hogy a értéke csupán eggyel lesz nagyobb a makró kódjának végrehajtása után. Ez azonban nem Igy van, mégpedig azért nem, mert ilyenkor a makró a következó kóddá alakul az elófeldolgozó munkája nyomán: (
(d++)
"
(a++)
* (a++) )
Ez pedig bizony nem egy, hanem három inkrementálást tartalmaz, vagyis egyáltalán nem az történik, amire a kód olvastán számItunk.
I
410 VI. rész· Különlegességek
Osztályinvariánsok A legtöbb oszLályhoz rende l hetők olyan logikai feltételek, amelyeknek igaznak kell lenniük, valahányszor lefuttatl.lnk egy az osztályhoz tartozó tagfüggvényt. Ezeket a fel ttteleket nevezzük osztályinvariánsoknak (class invariant). Ha például van egy CI RCLE nevű osztályunk , akkor enne k egy invariánsa lehet az a fe ltétel, hogy a sugár nem lehet nulla. Hasonlóan val6szíml, hogy egy az ANIMAL osztályba tartozó obje krumná l a kor (age) mindig nagyobb mint nulla, de kisebb, mint 100. Az elmondottak miatt általában hasznos, ha minden ilyen osztályon be1ül létre hozunk egy olyan Invarinats () nevtl metódust, amely akkor és csak akkor ad vissza igaz értéket, ha valame nnyi osztá lyinvariáns vizsgálata igaz é rté ke t szolgáltat. Ha ugyanis van ilyen met6dusunk, akkor megtehetjük, hogy minden egyes metódushívás előtt éli utá n végrehajtunk egy asse'Ct (Invariants ()) hívást, és ezzel ell enőrizzük, hogy minden re ndbt.!n van-e. Ezze l:) módszerrel természetéb61 ad6dóan azonnal jól behatárolhat6 az esetle ges hib:!. Van persze két o lyan sZitu5ci6, amikor megengedhetó, Ilogy az I nvari ants () metóduli nem adjon vissza igaz énéket::1 konstruktor, illetve a destru ktor teljes lefutása e l őtt ne m biztos, hogy :lZ adolt objckrum a megfelel6 állapotban van. Az Invariants () metódus használatát a 21.4. Lista szemlélteti egy meglehetősen triviális felépítéSll osztállyaI kapcsolatban.
o:
II 21 . 4 . Li sta Osztá1yinvariánsok
#define DEBlJG 2 : *define SHOW_INVARIANTS 3 : finclude 4: tinc1ude l :
S,
6 : tifndef DEBUG 7 : tdefine ASSERT(x) 8 , 'else 9 : tdefine ASSERT(x) \ 10 , i f (! (x)) \ ll : ( \ 12 : std: : cout « "ERROR! ! Assert • « #x « 13 , std :: cout« on line « __ LINE __ « 14: std :: cout « • in file • « __ FILE__ « 15 :
)
16: tendif 17 :
18: class String 19 : ( 20 : public : 21 : II Kons truktorok 22 : String () ; 23 : String(const char ~cons t) 24 : St ri ng (conöt String &) ;
;
" f ailed\n' ; \ " \n" ; \ • \n' ; \
25 : -String() ;
26 : 27 : char & operator[1 (int offset) ; 28 : char operator [) ( int offset) const ; 29 : 30 : Std ng & operator= (const String &) ; 31 : int Ge t Le n()const ( return itsLen; ) 32 : const c har * GetString() const ( ret urn its String ; ) 33 : bool Invariants () const ; 34 :
35 : privaLa : 36 : String (int) ; II private constructor 37 : char · itsString ; 38: unsigned short itsLen; 39 : ) l 40 : 41: II AZ alapértelmezet t
42: 43: 44 :
45 : 46 ,
konstruktor létrehoz egy csupa. O bájtból á1l6 __ karakter l ánco t String : : String ( ) ( itsString =: new char{l) ; itsString[OJ =: ' \0' ; itsLen",O; ASSERT(Invariants(j);
47 : 48: ) 49 : 50: II Privát (kisegit6) konstruktor . Csak arra használjuk 51 : II hogy segitségével maqának az osztálynak a met6dusai 52 : II létrehozhassák a megfele16 méret~ karakterláncokat .
... Ez u t 6bbiek at nullákkal t ölti f el . 53 : String : : String{i n t l en ) 54 : 55 : 56 : 57: 58 : 59 : 60 : 61 : 62 : 63 :
( itsString = new char[len+1) ; for (int i '" O; i<=len ; i++) itsString[iJ _ '\0'; itsLen=len ; ASSERT(Invariants() ;
J II K"lra ktertOrnb áta l akitása String os ztá lyba tartozó o bj ektumrná String : : Str ing (const char * const cStr ing )
64 :
(
65 : 66 , 67 : 68 : 69 : 70 :
itsLen -' strlen{ cS t ring) ; it!lS t ring = new char[itsLen+1) ; for (int i = O; i
71 :
l
72 : 73 : II Máso16 konstrukt o r 74 : String : :String (const String & rhs l 75 :
{
I
422 VI. rész • Külön~gességek 76 ,
it sLen~ r hs.GetLen();
77 : itsS tring :: new char(it.sLe n+ll; 78 : for ( int i = O; i
80 : i tsStri ng[its Len ] = ' \0 '; 81 : ASSER"{Invariants()) ;
82 83 84 85
: ) : : II Destruktor :
, 86 , 87 , 88 : 89 : 90 : 91 : 92 :
felszabadí tja a dinamikusan lefoglalt memóriát
String :: ·String () ( ASSERT(Invariants(); delete [l itsString; itsLen - O;
II Az cgycn16ség operátorának túlterhelése. Felszabadítja a meg!ev6
93 : II 11lel1L6riát , majd lemásolja a karakterláncot és a méretet
94 : String& String :: operator=(cons t St ring & rhs) 95 : ( 96 : lISSER"(Invariants(); 97 : i f (this :. ... &.rhs)
retwrn *this; 99 : de!ete lj itsString ; 100 : itsLen."rhs .eetLen () ; 98 :
101 : 102 : 103 : 10 <1: 105 : 106 : 107 : 108 :
itsString :: new char(itaLen+l] ; for (int i " O; i
109 : 1/ Nem állandó eltolási (offset) operátor. 110 : II Egy karakter hivatkozásával tér vissza, amely igy rajta lll : 1/ keresztül módositható . char & String: : operator]] (int offset) ( ASSERT{Invariants(» ; i f (offset> itaLon) r eturn itsString [ i t sLen - ll; else re turn itsStr ing [offset] : ASSERT(Invaria nts(»; }
112 : 113 : 114 : 115 : 1 16 : 1 17 : 1 18 : 119 : 120 : 121 : 122 : 123 : 124 : 125 : 126 : 127 : 12 8 :
II Állandó eltolási (offset) operátor. Const objektumokkal II kapcsolatban használható (lásd a konstruktort!) .
char String :: operator [] ( int offset) const { ASSERT(Invari ants () ) ; if (offsc:t > itsLen) return itsString [ i t sLen - 1 ] ; 129 : else
130 : return itsStr ing!offset] ; 131 : ASSERT (Invariants(» ; 132 :
}
133 : 134 , II Meggy6z6dünk róla, hogy vagy van a karakterláncnak érvényes 135 : II hossza , i l l etve a mutat6 ért éke nem null , vagy hogy a hos s z is
.. és a mutató é rt éke is nulla egyidejIlleg . 136 : bool String :: lnvariants(} const 131:
{
138 : *i fdcf SHOW_INVARIANTS 139 : std :: cout « • String OK 140 : fendif Hl : return ( (il::sLen 6o6o itsString) 142 : 143 : 144 : class AnimaI
II
(!itsLen 6o6o !itsString)
);
US :
146 : public : 147 : Animal{) : ltsAge(l),itsName('John Q. AnimaI') 148: (ASSERT(Invariants () } ;} 149: AnimaI (int, const String&); 150 : _Animall){} 151 : int GetAgel) ( ASSERT(Invariants() ; rcturn itsAgc;} 152 : void SetAge(int Age) 153, ( 154 : A~SERT(lnvaria n ts()) ; 155 : itsAgc :: Age; 156, ASSERT(Invarianta(); 157, J 158 , String&: Ge t Name I) ( ASSER'l' ( I nvarian t s () ) ; return i t DName; ) 159 : void SetNamelconst String& name) 160 , I 161 : ASSERT(Invariants(»); 162 : itsName _ name ; 163 : ASSERT(Invariantsl» ; 164 : I 165 : bool Invariants () ; 166 : private : 167 : int itsAge; 168 , String itsName; 169 , );
170 : 171 , 172 : 173 : 174 :
AnimaI : : Animallint age . const string& name) : itsAge(age) , itsName (name) ( 175 : ASSERT(Invariants()}; 176 : ) 177 : 178 : bool AnimaI :: I nvaria nt s () 179 : { 180 : lifdef SHOW_tNVARIANTS 181 : std : : cout « • AnimaI OK 182 : !l e nd i!
424 1VI. rész • Különlegességek 163 :
return (itsAge > o
&&
itsName.GetLen (»
;
184 : 185: 186 : int main ()
181 : 188: AnimaI sparlcy(S, 'Sparky'); 189 : std : : cout « "\n" « sparky . GetName{) . GetString() « 190: std: :cout « sparky.GetAge{) « • years old,"; 191 :
• is ";
sparky.Set.Age(B) ;
192 : std : : cout « 193: std: :cout « 194 : roturn O:
"\n' « sparky . GűtName() .GetString() « sparky .GetAge{) « • years old,' ;
• is ';
195:
String OK String String OK Stri ng Sparky is AnimaI Sparky is Animal
Aprop6
OK String OK String OK String OK String OK OK AnimaI OK String OK An ima I OK
OK 5 years old . Anima! OK AnimaI OR AnimaI OK OK 6 years old . String OK
A fordft6programt61 figyelmeztető üzeneteket kaphatunk
A fenti program fordítása során az itt látottakhoz hasonló figyetmeztetö üzeneteket kaphatunk. Ezek azt átlftják, hogy az assert () -tel kapcsolatos kódrészletek némelyike soha nem kerül végrehajtásra. Ez a kód tehát ebben a formájában kissé furcsának túnhet, de megfelel egy amúgy követendő szabálynak: minden függvény elején és végén van benne egy assert () . ·usinginvariants.cpp": W8066 Unreachablc code in function String: :operator [] (int) at line 119 "usinginvariants.cpp· : W8070 Function should rcturn a value in function String :: operator [l (int.) at line 120 ·u~inginvariants . cpp" : W8066 Unreachable code in function String: : operator II (int) const at line 131
·usinginvariants .cpp· : W8070 Function should return a value ln function St ring : : operator [ J (int.) const at line 132
Az assert () makró kódját a 6-16 sorok tartalmazzák. Ha a DEBUG szimbólum meg.. rozott, a makr6 hibaüzenetet jelenít meg, ha a neki áeadott kifejezés h
ki. A 33. sorban találjuk
21. óra •
Ugyanez a minta isméll6dik meg a másik konstruktorral kapcsolatban is. A destruktor ezzel szemben csak az el6tt hívja mcg az Iovarinals () metódust, micl6tt megkezdené az objektum lebontását. Az összes többi, az osztályhoz tartozó tHgfüggvény futásának megkezdése előtt, és a m(iveletek befejezése után is ~önellenőrLés" végez, vagyis meghívja az Invariant s () -t. Mindez jól demonstrálja a c++ egyik alapetvét: a konSLruktoroktól és destruktoroktóJ eltekintve a tagfüggvények csak érvényes állapotban levő objektumokon m01<.ödhetnek, és azokat érvényes állapotban is kell hagyniuk. A 165. sorban látható al Anima I osztály saját Invariants () metódusának deklarációja , amelynek megvalósítása a "178-184 sorokban olvasható. Figyeljük meg, hogya 148, ISI , 154, 156, 161, és 163 sorokban inline függvények formájában hívjuk meg az Invarian t s() metódust.
Köztes értékek kifratása Amellett, hogy az assert () makró segítségével ellenőrizzük bizonyos feltételek teljesülését, néha szükség lehet arra is, hogy menet közben megjeleníl.sÜk egyes változ6k, mUlatók vagy karnklerláncok aktuá lis tarl:llmát. Ezzel például nyomon követhet{5 progr,ununk végrehajtása, és kisztirhet6k olyan apró ám bosszantó hibák, mint az eggyel túlfutó ciklusok A módszer működését a 21.5. Lista szemlélteti.
21.5. Usta - rtékek kilratása DEBUG módban (printingvalu8s,cpp) Q : / I 21 . 5 . Értékek kHrat.ása DEBUG módban 1 : 'inc1ude 2 : 'defi ne DEBUC
3, 4:
~ i fnde f
5 : #dcfi ne
DEBUC PR J N~ ( x )
6 : " else 7 : "d ef ine PRINT( x ) \ 8 : std :: cout « "x « 9 : tendU
": \t" «
IQ : lt : int mai n () 12 : 13 : int. x " 5 ; 14: loog y ~ 738981 ; 15 : PRI NT {x ) ; 16 : for (int i " O; i < x ; i ·H ) 17 : {
18 :
PRI NT{ i ) :
19 : )
20: 21 : 22: 23 : 24:
PRINT
(y) ;
PRINT{'Hi . " ) ;
i nt. ~px " &x; PRINT (px ) ;
x «
st.d :: endl :
426 1VI. rész • Különlegességek 25: PRINT (*px) 26: return O;
;
27 :
"i: o5 i: i: i : i:
1
2 3 4
y. 73898 "HL", HL px : 1245064
" px : 5
A 4-9 sorokban megadOll makr6 kiírja a neki átadott p:lmmt:tcr énékét. Figyeljük meg.
hogya cout-nak átadou els6 dolog a paraméter karakteresítct[ (stringized) változata. Ez tehát aZt jelemi, hogy ha paraméterként mondjuk az x v,íhoz6t admk ál, akkor a cout elóször egy 'x " kar,.lk((:!rJúncot kap.
A couL következő param6tcrc minden esetben egy": \t" braklerlánc, :uni kiír egy kett6spontol, majd utána egy tabulálort A harmadik maga a kiírandó vá lloZÓ (jelen esetben x), majd végül egyendl, ami kiüríti a puffert és kiír egy újsor karaktert. A 24. sorban látható kód vagy egy akimenetben bemutatotthoz hasonló értéket ír ki (ilyen a Borland fordítóv:ll el6:'illrtot.l kód), vagy olyasmit mint a Ox2100 . Ennek a sornak" működése tehát fordítófügg6.
Nyomkövetési szintek A kifejezeuen nagy és összetett fejlesztési projekleknél az embernek általában töbh a nyomkövetés.~e1 kapcsol:ltos ellen6rzési lehet6ségre van szüksége annál, mint ho;"\ a DEBUG szimb6lumot ki és bckapcsolhatja. Ilyenkor megadhanIOk nyomkövetési .;, teket, és az el6feklolgoz6 ezek vizsgálat.1 alapján döntheti el, hogy a makrók közü melyek kerüljenek bele alefordítandó kódba. Ha nyomkövetési szinteket akanmk meghatározni, nincs más dolgunk, mint a C~ után megadni egy számot is. Ha már szinteket adunk meg, általában hasznos, ha .. vetkez6 négyet különböztetjük meg: HIGH, MEDIUM, LOW és NONE. A 21.6. Lista a ~~
21. óra • Listában bemutaloU String és AnimaI osztályok kapcsán mutatja be a nyomkövelési szimek használatál. Helytekerákosság nliatt kihagyruk minden , az Invariants () -l61 különbözo metódus kódját, mivel azokat a változás nem érinti.
21.6. 1JsbI- A nyomkiiYet6slZintjei ldebugginglev....cppl o : II 21.6 . Lista NyomkOvetési szintek használata 1 : linclude 2 : linclude <8tring.h> 3. 4, enum LEVEl. { NONE, LQW, MEDIUM , HIGH } ; 5. 6 , ~define OEBUGLEVEL !iIGli 7. 8 , hf OEBUGLEVEL < LQW 1/ Csak LOW , MED IUM vagy HIGH lehet az értéke 9 , ~define ASSBRT(x) 10 : #elsQ ll : #define ASSERT(x) \ 12 : i f (! (xl) \ 13 : ( \ 14 : std : : cout « "ERROR!! ~8sert " « #x « " failed\n"; \ 15 , std : , eout « on line «_LINE_ « " \n "; \ 16, std : ,cout « " in file" « _FILE_ « "\n"; \ 17, l 18, 80ndif 19 : 20: _if DEBUGLEVEL < MEDIUM 21: 'do fine EVAL{x) 22: 'else 23: 'doHne EVAL{x) \ 24: std::cout« Ix« ',\t"« x« std " endl; 25: 'endH 26: 27, DEBUGLEVEL < IiIGli 28: #define PRINT(x) 29 : ~e1se 30 , IIdefine PRIN'I'{x) \ 31 : std : , cout « x « std : : endl ; 32 : llendif 33 : 34 : class String 35 : { 36 : public: 37 : II Konstruktorok 38 : String{); 39 : String{const char · const) ; 40: String(const String &) ; 41: -String () ;
'if
42 ,
43 : char & operator[ l (int offset) ; 44 : char operator[] (int offset) const ; 45 , 46: String & operator= (const String &);
428 1VI. rész • Különlegességek 47 : int GetLen{)const ( r e turn itsLen: 48: const char * GetSt ring () const 49 : { return itsString ; } 50: bool Invariants() cons t;
51 : 52 : private : 53 : String (int); / / Privát konstruktor 54 : char * itsString : 55 : unsigned short itsLen ; 56 : ); 57 :
5B : bool String : : Invariants() const 59 : ( 60 : PRINT( " (String Invariants Checkad)") ; 61 : return ( (bool) (itsLen && itsStrin~J) 62 : ( ! itsLen Ct& ! ltsString) ); 63 : }
II
64. :
65 : c1t1ss AnimtIl
66 : ( 67 : public : 68 : Animal() : itslIge(I),itsName('John Q . AnimaI") 69 : (ASSERT(Invariants(» ; } 70 : 71 : Animal(int, const String&) ; 72 : _Anima1() II 73: 74 : int GetAge{) 75: ( 76 : ASSERT(Invariants(»);
77 : return itsAge; 78 : )
79 : BO : B1 : 82 : B3 : 84:
void Setllge(int Age) ASSERT(Invariants(» : it9Agc '" Age ; lISSERT(Invariants(»;
BS : }
86 : 87 : BB : 89 :
String& GetName() ( ASSERT(Invariants(» ; return i.t s Name ;
90 :
91 : 92 : 93 : 94 : 95 :
void SetName(const Str ing& name ) ( ASSERT(Invariant s(» ; itsName '" name ; 96 : ASSERT(Invariant9(»; 97 : } 98 :
99 : bool Invariants() :
21. óra· Az 100 : 101 : 102 : 103: 104 :
private: int itsAgc ; String itsName; };
105 : II Az alapér t elmezett konstruktor O bájtokból áll6 106 : 107 : 108: 109 : 110: lll, 112: 113 , 114: 115, 116 :
.. karakterláncot hoz létre . String :: String () { itsString : new char]l]:. itsString[O) =. '\0' ; itsLen .. O; ASSERT(Invariants () ) ; )
II Privát (kiscgit61 konstruktor. Csak arra használjuk II hogy segitségével magá ntlk
117: String : : String(int len)
118: { 119 : itsString = new char[len+1]; 120 : tor (int i = O; i<_len; i++) 121 : itsString[i) = '\0'; ll!2 : itsLcn.1en; 123 : ASSERT(Invariants(» ; 124 : } 125 , 126 : II KaraktertOmbOt String tipusú ob jektummá alakit . 127 : String : : String(const char * const cS t ring) 128 : ( 129 : itsLen .. strlen(cString); 130 : itsString = new char[itsLen+ll; 131 , for (int i " O; i
I
430 VI. rész • Külön~gességok 151 : ASSER'l'(Invariants(»;
152 : de!ete I I it sString ; 153: itsLen "- O; 154 : J 155: 156: /1 Az egycn16ség operátorának túlterhelése . Felszabadítja 157: 1/ a meglev6 memóriát, majd lemásolja a karakterláncot ... éG a méretet .
158: String& St ring: : operator=(const String & rhs) 159: ( 160: ASSERT(Invariants(»
161: iC (this == &rhs) 162: return ~th is ;
;
163 : delete [l itsString ;
164: itsLcn=rhs ,GetLen () ; 165: itsString :: new char[itsLen+11; 166: for (int i = O; i
169: ASSERT(Invariants(» ; 170 : rcturn *this; 171 :
172 : 173 : II Nem állandó eltolási (offset) operátor. Egy karakter 174 : il hivatkozásával tór vissza, ame ly igy rajta keresztal 175 : il m6dosithat6 . 176: char & String: : operaLor[ ] (int offse t) 177 : (
178: 179: 180: 181: 182 : 183 : 18 4: 185: 186: 187 : 188: 189 : 190 : 191 : 192: 193: 194: 195 : 196: 197 : 198 :
ASSERT(Invariants(»): i f (offset> itsLen) return itsString[itsLen-1): else return i tsString[offset l; ASSER~(Invariants()) ;
) II Allandó eltolási (o ffset ) operátor . Const objektumokkal II kapcsolatban használhat6 (lásd a konstruktort!). char String: : operator[) (int offset) const ( ASSERT(Invariants()); it: (offset> itsLen) return itsString[itsLen - 1 ]; else return itsString[offset) ; ASSERT(Invariants(); )
AnimaI : : Anima1 (int age , const String& name) : 199 : itsAge( age),
200: itsNallle(name) 201 : {
21 , 6ra • kl 202 : ASSERT(lnvariants(» ; 203 : }
204 : 205 : bool Animal :: Invariants () 206 : { 207 : PRINT ( • (AnimaI Invariants Checked) ') ; 208 : return (itsAge > O && itsName.GetLen{» 209 :
;
210 : 211 : int main() 212 :
2 13: const int AGE = 5; 214 :
EVAL(AGE) ;
215 : AnimaI sparky(AGE . 'Sparky') : 216: std : : cout « " \n' « sparky . GetNarne() . GetString() ;
217 : 218 : 219 : 220 , 221 :
std :: cout«' is ' I std :: cou t «sparky . GetAge() «
AGE:
5
• years old . ' ;
spnrky . SetAge (8) ;
std: : cout « std :: cout « 222 : std :: cout « 223 , return Oi 224 :
' \n " « sparky .GetName( ) . GetString() ' is' , sparky .GctAgc() « • years old ."
(String Invariants CheckedI (String Invariants Checked) (String Invariants CheckedI (String Invariants Checked) (String Invariants Checked) (Stri ng Invarianto Checked) (String Invariants Checkcd) (String Invariants Checked) (String Invariants Checked) (String Invariants Chec~ed) Sparky is (AnimaI Invariants Chec kedl 5 Ycars old . (AnimaI Inv",ri",nts Checkcd) (AnimaI Invarisnts Checkcd) (Animul Invariants Checked) Sparky is (lmirnal Invaria nts Checked) 8 years old . (String Invaria nts Checked) (String Invariants Checkcd) II run again with DEBUG : MEDIUM AGE :
5
Sparky is 5 years old . Sparky is 8 years old .
i
431
432 1VI. rész • Különlegességek
Apropó
A fordftáskor figyelmeztető üzeneteket kaphatunk
Ennek a kódnak a fordítása közben is felb ukkanhatnak ugyanazok a figyel meztető üzenetek, a melyekről a 21.4. Lista kapcsán már volt szó. Az. ok természetesen ugyanaz.
Elemzés Az assert () makró k6dját a 8-18 sorok tartalmazzák. Ez ebben az esetben úgy van kiahtkítva, hogy Im a DEBUGLEVEL énéke alacsonyabb mint LOW(vagyis NONE), akkor a nyomkövetési kódok egyáltalán nem kerülnek bele a fordítandó állományba. Ha a nyomkövetés bármilyen szinten megengedett, akkor az assert () ml1ködésbe lép. A 20-25 sorokban híthat6 EVAL nem kerül bele a k6dba , ha DEBUGLEVEL énéke alacsonyabb mint MEDIUM. Ez tehát azt jelenti, hogy ha DEBUGLEVEL énéke NONE vagy I.OW, akkor EVAL nem kerül bele a k6dba. Végezetül a 27-32 sorokban látható PRINT makró aktjválásá hoz DEBUGLEVEL HIGH ~r léke szükséges. Ez tehát azt jelenti, hogy cz a makró még a MEDIUM nyomkövetési szinten is hatást.dan , miközben ezeken aszinteken EVAL és assert () már ml1ködnek. A PRINT makrót az InvarÍ
Helyes
Helytelen
A makrók neveiben használjunk csupa nagybetúl. Ez egy álcalánosan elfogadotl é... alkalmazott szokás, vagyis ha ncm taJtjuk hozz:'i magunkat, azzal összezavarhatjuk a többi programozót. A makfÓfü&,>vények valamennyi paraméterét tegyilk kerek záfÓjelek közé.
Igyekezzünk olyan makrókat írni, amelyeknek egyáltalán nincsenek mellékhatási. Ne inkrementáljunk például makrón belül változót, vagy ne adjunk értéket neki.
Kérdések és válaszok Kérdés: Ha a C++-nak vmmak az előfe/dolgozóI/ál jobb szolgáltatási, akkor miél1 Illeg még mil/dig benne ez ff lehelőség is?
Vim
\lálasz: El6ször is a C++ visszafelé kompatibilis a C nyelvvel , vagyis annak lninden je-
lent6s szolgáltatását tartalmaznia kell. Ilyen az előfeldolgozó is annak minden direkti-
21. óra • Az
vájával és makrójával. Másodszor az elófeldolgozónak vannak olyan szolgáltatásai, amelyeket a CH programozók is előszeretettel és gyakran használnak. Ilyen például a beszúrási műveletek ellenőrzése (inclusion guards). Kérdés: Miérl használ/mk makfÓJrigguényeket, ha ugyanaz a Jeladatot igazi fiiggtJényekkel is megoldJ/aljuk? Válasz: A makr6függvények nem igazi függvé nyek, mivel aZ el6feldolgozó fejti ki és szúrja be őkel a megfelelő helyre. Ezek tehál tu lajdonképpen a kódolást könnyítő eszközök, hiszen nem kell újra és újra begépclnünk ugyanolyan, vab'Y nagyon hasonló kódokat. Ugyanakkor az is igaz, hogy a sablonok jobb alternatívát jelentenek. Kérdés: Mi~)ICH más módszer vagy cszk6z áll a a rendelkezésüllkre az előfeldolgozó helyett, ha hlbakercsés I.:ózbcll változók k6ztes énekeit szere/lléllk kiírallli.? Válasz: A legjobb alternativa a
megfigyelő
lltasítások (watch statements) használata egy nyomkövet6prograrnban. Ezzel kapcsolatban olvassuk el a fordítóprog~mur1khoz mellékell nyomkövető dokumentációját.
Feladatok Mo.st, hogy megismerkedtünk az előfeldolgozó szolgáltatásaival és fel használási módozataival , megszerzett tudásunkat meger6sítendő válaszoljunk néhány kérdésre és végezzünk el néhány gyakorlatot.
Kvrz 1. Hogyan törölhelünk makr6változókal? 2. Mikor kerülnek feloldásra a makrók, illetve lnikor kapnak értéket a szimbólumok? 3. Hogyan alakíthatjuk karakterlánccá egy makró valamely paraméterét? 4. Milyen operntorra l fúz!1etünk össze karakterláncokat egy makr6ban?
Gyakorlatok 1. Mooosít.suk a 20. óriiban bemutatott employeernain . cpp programot (20.4. Lista) úgy, hogy az kétszer szú~a be a string . hpp fejlécállományt. (Ismételjük meg
az elsó son.) Mi törll!nik fordításkor? 2. Alkalmazzuk az ebben az órában megismert módszert a string. hpp fej ll!cá llomány beszúrásának vezérlésével kapcsolat Onclusion guards). PfÓbáljuk meg újra lefordítani az e l őző gyakorlatban módosítorr kódot. Mi történik?
433
434 VI. rész •
3. A dokumentáció segítségével delÍt50k ki, hogyan adhatunk értékel egy makróváltoz6nak a parancssorban. Írjunk egy olyan progrdmot , ami kiírja a képerny6re a makr6nak így áradott értéket.
Válaszok a kvfzkérdésekre 1. Makr6kat az fundef direktíva segítségével lehet törölni. Ez a módszer egyaránt
mJködik a magában a kódban, illetve a parancssorban megadott makr6kkal kapcsolatban. 2. A makr6k kifejtése (vagyis a forráskód megfelel6 módosítása) fordítási id6ben történik. Ez azt jelenti, hOb'Y az így beállított értékek később, furásid6ben már nem m6dosíthat6k, hiszen azok abban a köztes k6dba kerültek be, amit a fordítóprogram az el6feldolgoz6t6! megkapott. 3. A paraméterek a karakteresít6 (stringizing) operátor (It) segítségével alakíthat6k br:akterlánccá. Ez tulajdonképpen csak annyit tesz, hogy kelt6s idéz6jelek közé z{lrja a kérdéses argumentumot, akármi legyen is az. Ezzt::l a m6dszerrellerrnészetesen mi magunk is élhetünk. Bármi, amit a makr6 k6djában kett6s idézőjele közé les7.ünk attól kezdve konstans lesz, vagyis ·x" már nem magának az x változónak a tartaimát jelenti, csak egy betűt. 1. Az összeli1zés operátora a U.
22.
ÓRA
Objektum-orientált elemzés és tervezés Ebben az órában a következőkról lesz szó •
Hogyan elemezzünk problémákat objektuffi.orientálr szemszögb61
• Ilogyan tervezzük meg a progmmunk:!.l objektum-orienl:'ilt szemsz("gb61 • Áttekintjük az elemzési folyamatot, illetve ezek átültetését C++ nyelvre a tervezési célokat szem előtt tartva • Hogyan telvezhetí.\ nk bővíthető és újra hasznosítható kódot
A fejlesztési ciklus Szá mos könyvet és összefoglalót írtak már a fejlesztési ciklusr61. Néhány a vízesés modellt támogatja, ahol a tervező határozza meg, mire legyen képes a program, a mérnök határozza meg, hogyan épüljön fe l az, milyen osztályokat tartalmazzan , és így tová bb,
436 1VI. rész • KOIönlegas.égek végűl a programozó felada ta az í!,'Y kialakult tervet és architektúmt egybefonni. A programozó ennél a megközelítésnél már a kész tervet kapja meg, így csu pán annyi a feladata, hogy megvalósítsa az abban leírt funkciókat.
Noha a vizesés modell kétségkívül működik, azért egymagában val6színúleg elég szegényes lenne igazán jó programok írásához. Fejlesztés közben van egy tennészetes és szükséges vissL1C5.1lolás a m.ir megírt, illetve a még megírásra váró programrészek között. Az tény, hogy a jó C++ programokat alaposan megtervezik, mielőtt elkezdenék az érdemi programoList, azonban az már nem igaz, hogy a telV nem változik a ciklus alan. Hogy mekkora munkát igényel az előzetes tervek elkészítése, az természetesen a programunk mé re t étől függ. Egy igen bonyolulL projektnek, melyen tucatnyi programozó hónapokig dolgozik, nyilván világosabb, kidolgozottabb telVVel kell rendelkeznie, 111il1t egy ujjgyakorlalként megírt segédprogramnak, melyet egy nap ~I l att egy programozó kof:s7.Ítell el. Ebben az órúban olyan nagy és összetett programok Ic jlcszl(:sére konccnlriUunk , melyeket esetleg évekig fogunk bővíteni és fejleszteni. Szftmos progrmnoz6 egyszeruen élvezi, ha a:wkkal .t legú jabb technológiákkal dolgozhat, melyek tudása és segédeszközci határd it rcszegetik. A CH nyelvet sok tekintetbe n éppe n arm tervezték, hogy segíl<;égével egyre bonyoluhabb és bonyo lultabb rendszereket fej leszthessenek a progr.lmozők VlIgy progmmozói csapatok.
Egy riaszt6rendszer szimuláci6ja A szimuláció egy a valódi vikig egy részének leírására alkotott számítógépes model. Számos o kból szoktunk szimuláciőhoz folyamodni. Egy jó tervhez e l őször is meg ke: értenünk azokat a kérdéseket, amelyekre választ vántl1k a szimu lációt61. Kiindu lásképpen vizsgáljuk meg a következő problémát: relkértek minket, hogy láljuk egy ház riasztőrendszerét. Egy kétszintes családi házról van sz6, mely egy k .... ponti tér köré rend(:!zelt négy hál6szobával, berendezett alagso Tml és egy rnélygarázzsal rendelkezik. A rölclszinlcn a küvetkező helyeken találh.m6k ablakok: Itárom a konyhában, né~ az ebédlőben , egy a WC-ben, kettő-kettő a nappaliban és a t{lrsalg6ban, valamint '.c" ablak a bejámti ajtó mellett. Négy hálószoba található az emeleten. Minden hál~ két ablaka van , kivéve fő hál6szobál, ahol né!,'Y található. A két fü rdÓSZOIYd egy-e:;:\ lakkal rendelkezik. Végűl négy kis ablak található az alagsorban, egy pedig a
gan"".
A lakók els6sorban a bejárati ajtót használják közlekedésre. Emellett a konrha egy elhúzható üvegajtó, valamint két ajtó a garázson az autó knak, illetve egr al. alagsorhoz. Van továbbá egy pinceajtó, amely a hálsbkertre nyílik.
22, óra •
elemzés és tervezés 437
Az ablakok és az ajtók riasztóval védenek, emellen minden telefonon van vészjelző va-
lamint egy nyomógomb közveuenül az ágy mellett a f6 hálószobában. Kültéri riasztók is va nnak, de ezeket körültekint6cn állitouák be ahhoz, hogy kis testű állatok vagy madamk ne indítsák be őket. A riasztórendszer központja aZ alagsor!>an található, mely azonnal figyelmeztető jelzést ad, amint vabmclyik szenzor jelez. Amennyiben nem kapcsoljuk ki a riasztót a megadolt időn belül, úgy telefonon értesíti a rendőrséget. Ha megnyomjuk a vészjelzőt , a rendszer azonnal értesíti a rendórséget. A riasztór.1 vannak kötve még a túZ és füst jelzők valamint a tűzoltórendsze r is. Maga a riasz16rcndszer központj:! hibalűró, mely a heépített tartalék áramforrásnak és a tűz álló burkolatnak köszönhető.
Koncepcióterv Ebben a fázisban megpróbáljuk megéneni, mit vár a megrendelő a programt61. Mi célja a progr:tmnak? Milyen kérd~sekre kellene a szimu lád6nak v;'i]aszt adnia? Megfelelőek-e az alábbi kérdések: .,A szenzor jelzése után mennyi időve l riasszan a rendsze r?~ vagy . Kijátszhat6ak-e az ;ibl;lkriasztók úgy, hogy nem fusson lx: l:rtcsítés a rcnd-
:l
őrség hez?~
A koncepci6terv készítése megfelel6 id6szak arra, hogy átgondoljuk, mi van a programon belül és mi van kívül. Szerepel a rcnd6rség a SZi111Uláci6blm? Hésze magának a szimulált rendszernek:l:l húz rias%l6renuszerének vezérlése?
Elemzés és az igények felmérése A probléma megfogalmazásának fázisa után az elemzés fázisa kövelkezik. Elemzés közben feladalunk segíteni a megre lldelőt, hogy mcgérLo.;e, mit is v~rhat egy ilyen progn!mtól. Pontosan milyen viselkedést mutasson a progmm? Milyen beavatkozási lehet6ségei lesznek a felhasználónak? Ezeket az igények rendszerint dokumentumokba gyűjtjük össze, amelyek felhasz nálási e.settanulmányokat is tanalmazhatnak. Használati eseldiagr.:lm írja le a program viselkedését: a kölcsönhatásokat és a használati mintákat, segítve ezzel a programozót a rendszer céljainak megértésében.
Tudta hogy...?
Egységes modellez6 nyelv (Unified Modeling language - UML)
Az igények felmérésnek. és az elemzés dok.umentálásának egyik. lehetséges módja az egységes mode!tező nyelv (Unified Modeling Language; UM L) használata.
438 1VI. rész • Különlegességek
Az. UMl hasmálatának két elónye is van: grafikus, vagyis könnyű áttekinteni, illetve egységesített formátumot használ, így nagyon sokan - akár nem szakértők is megértik. Noha az UML bemutatása meghaladná a könyv terjedelmét (a témában már számos kiváló könyv jelent meg), tudnunk kell, hogya használati esetek bemu-
tatását is lehetövé teszi. Objektum-orientált fejlesztéshez különösen hasznos, hiszen számos absztrakt és egyéb osztálytípust támogat közvetlenül. Jó forrás a témában: Teach Vourself UMl in 24 Hours, maly a Sams kiadásában jelent meg.
Magas és alacsony szintű tervezés Miután leljesen megértettük a program célját és az igényeket a megfelel6 dokumentumban rögzítettük, hozzákezdhetünk a magas szinn1 telVezéshez. Ebben a fázisban a programozó nem foglalkozik a pl:nformmal, az oper.'iciós rendszerrel, vagy a programozási nyelv saj{ttságaival. Ehelyett a rendszer működésére koncentrál. Azt vizsgá lja, mik annak a főbb elemei, illetve hogy milyen kölcsönhatás les7. közötlük. A probléma egy lehetséges megközelítése az, hogy félrelesszük a felhaszná l6i felület kiabkít:'Ísának kérd6scit és csupán a problématér komponenseire koncentrálunk. A problémalér azon problémák halmaza, melyeket a programunkkal megpr6bMunk megoldani. Hasonló:tn a megoldástér a lehetséges megoldások halmaza. Ahogy haladunk a magas szi ntO tervezéssel, végiggondoljuk az objektumok felad:!lai!. Mit csinálnak és milyr.:n információkat tartalmaznak? Természetesen végig gondoljuk együtt működésüket is. Milyen objektumokkal vannak kölcsönhatásba n? Például világos, hogy vannak különböző érzéke16ink, egy központi riaSZl6rem.lszerunk, gombjaink, vezetékeink és tdefonjaink. Tovább gondolva a dolgot szimu1:ílnunk kell szobákat, a padlót és emberek csoportjait, num például a tulajdonosokat va&'Y a rend6rséget. Az érzékelők tovább bonthat6ak mozgás, betörés, hang és füstérzékelőkre, stb. Ezek érJ.:ékelótipusok, szó szerinti é rzékelő nincs. Világosan látszik tebát, hogy az l!rzékel6 egy absztrakt adattípus (Abstract Data Type; ADT). Absztrakt adattípusként a Sensor osztály biztosít interfészt az összes érzéke16típushoz és minden származt,HOlt típus saját implememációval rendelkezik. A kliensek anélkül dolgozhatnak az érLékel6kkel, hogy rudnák milyen típusú, mégis mindegyik a típusának megfel elően fog viselkedni. Egy jó absztrakt adattípus létrchozásához teljes mértékben meg kell értenünk, mit csinálnak az érzékelők - hogy ezt hogyan teszik, az számunkra most lényegtelen. Például az érzékelők aktív vagy passzív eszközök? Várna k valamely elemük fel melegedésére,
egy vezeték szakadásám, a tömítés olvadására , vagy esetleg szondázzák a környezetüket? Néhány érzékel6 lehet bináris (riaszt vagy sem), de néhány köztes állapotokat is felvehet (hány fok meleg van?). Az absztrakt adattípus interfészének megfelel6en teljesnek kell lennie, hogy kezelni tudja ezeket a számtalan származtatott osztályban.
Egyéb objektumok Ahogy haladunk a tervezéssel , számos egyéb osztálym is szükségünk lesz, hogy megfeleljünk a spcdfikádónak. Például ha naplózni szeretnénk az eseményeket, akkor valószínűleg egy id6zít6re is szükségünk lesz. De vajon az id6zít6 szólítja meg :IZ (:rzékel6kel vagy minden é rzékelő maga küld időnként helyzetjelentést? A felhaszn:'i16 a következ6képp avatkozhat VI:! a n::ndszer működéséve: élesítheti, kikapcsolhatja, il letve a programozilatja, tehát szükségünk lesz valamilyen felhaszná lói fe lületre is, Esetleg létn;hozhatunk cgy külön objektumot a szimuláci6nkban a ri:lS2t6programhoz is.
Milyen osztályaink lesznek? Amint megoldottuk ezeket II problém:'íkat, llZ osztályok tervezésével folytat ju k a tervezést Például már kiderült, hogya HeatSensor osztályt a Sensor osztá lyból fogjuk származtami. Ha :lZ érzékelő időnként jelentést küld, akkor többszörös örökl6désként a Timer osztály is a szüli) osztálya lesz, vagy az időzítő csupán tagváltoz6 lesz. A HeatSensor val6szfml1eg rendelkezik CurrentTernp () és SetTempLimit () tagfLiggvényekkel , és valószínűleg örökli a Sensor ősosztály SoundAlarm () függvé nyél is. Rendszeres téma :\z objektum-orientált tervezés közben az egységbezárus. Képzeljü nk el egy olyan megold!ist, ahol a ria$zt6rendszer rendelkezik egy HaxTernp értékkel. A riasztó Ickérdezi a hómérséklctszenzortól az akmális h6mérsékletet és riaszt, h:\ túl mcleg van . Egyesek szerim ez megsérti al egységbezárás elvét. Valószínűleg jobb lenne, ha nem a riaszt6rendszcr foglalkozna a hőmérséklet elemzés részleteivel, h:mem - bár vitatható - ezt II HeatSensor osztá ly tenné.
Tudta hogy...?
Egyéb források
Az objektum-orientált felfogásról érdemes elolvasnunk Matt Weisfeld The ObjectOriented Thought Process (ISBN: 0-672-32611 -6) könyvét, mely már második kiadásban jelent meg a Sams kiadóná!.
I
440 VI. rész • Kmönleges,égek
Akár egyetértünk ezzel, akár nem, a probléma elemzése közben koncenLní lnunk kell az ilyen döntésekre. Az elemzés folytatásaként e1gondolkodharunk azon, hogy csupán az érzékelő és a Log objektum lássa-e az ér.lékelÓ aktivitibát, és maga az Alarm objekrum egyáltalán ne tudjon rola, illetve ne foglalkozzon vele, vagy alkalmazzunk más megközelítést. Megfele l ő az egységbezánís, ha nunden osztály koherens egész és két osztály nem hajtja végre ugyanazt. Ha a Sensor osztály felelős az akruális hőmérséklet figye\ést:n, úgy semelyik más osztálynak nem s7.ahad vele fogla lkoznia. Másfelől
viszont a többi osztály bóvítheti a szü kséges funkc ionalitással. Például ha Sensor oszt.ály fe l e l ős a h őmérsékl et figyelésén és naplóz:ís:íért, akkor ezt úgyis meg· val6síthatja, hogy egy Log objektumm bízza az aktuális adatok rögzítését. A t(;vékcnys6gck éles clhatároJásával II programunk t:gyszenlbben bővithető és kar· bantarth:n6 lesz. Ha később a riaszt6rendszert egy fejlettebb modulra szeretnénk cserélni, úgy
Hogyan jelezzük a riasztásokat? Amcnnyiben az érzéke l ők riasztanak, úgy rengeteg információt kell átadniuk a rendt s(:gct tárcsázÓ objektumnak és a naplózó objeknunnak. Érdemes létrehoznunk eb'Y Conditions osztályt, melynck a konstruktora számos mé rést végez. A mérések bon~ • luJtság,ínak függvényében lehet ak.'ir objekmm, akár egyszenl egész LípL1SÚ skalár (-rtL Elképzelhet6, hogya Condi tion objekmmot közvetlenül a központi riasltóobjekt nak adjuk :ít, vagy a Condilion objekmm az Alarm objektum alosztálya lesz, mely tudja, mit kell tennie vészhclyzer csetén. Elképzelhető az is, hogy nincs központi jektum, ehelyeu az érzé kelők hozzák létre a Condition objektumokat. Néhány Condition objektum saját magát naplózza, míg a többi II rend6ség elérésért felel" ~ Egy megfclel6en tervezett, eseményvezérelt rendszernek nincs szüksége közporn ordinátorra. Képzeljük el, hogy a szenzorok függetlenül fogadnak és küldenek
______________________________~2~2.~6~~~·~Ob~j~.~~m~~~n~·.~má~tt~'~I'~mri~S~é=s~te~~~.~m=s~ I ,«~I~____~I··i teket más objektumoknak, állítanak be paramétereket, végeznck mérésekcl, figyelik a házat. Ha hibát észlel nek , az Alarm objeknllll naplózza a problémát (például üzenetet küld a Log objektumnak) és végrehajtja a szükséges lépéseket.
Eseményhurkok Egy ilyen esemény vezérelt rendszer szimuláci6jához a programunknak rendelkeznie kell cscm6nyhurokkal. Az eseményhurok gyakorlatilag e~,.y végtelen ciklus - ilyen pél-
dául a whil e (1) -, mely fogadja az operációs rendszer üzeneteit (egél'kattintás, billentyű leütés, stb.), egyenként lekezeli őket, majd vissza ugrik az elejére, amíg nem teljesül a kilépési felt étt!l. A 22.1 Lista cgy egyszeru esemény hurkOl mUlat be. 122.1 Usta - Egy egyszerü eseménvhurok (simpleevent.cpp) O, II 22.1 Lista l: jinclude 2, 3: class Condition 4. :
(
5, 6: 8:
public: Condition () { } virtual -Condition() () virtual void Log () = Ol
9:
l;
7:
10: ll : l:t. : 13: 15 : 16: 17 :
c l ass Normal : public Cond i tion ( public : Normal() ( Log() l l virtual -Norma1() {} vj rtua1 void Log () ( std : : ..:ou t « ' 1.ogging normal condition s .. . \n ' ; )
18 :
l;
19 : 20 : 21: 22: 2] : 24 : 25 : 26 :
class Error { public : Error() virtual virtual );
14:
: public Condition
( Log();) -Error() (l void Log() ( st.d : : cout «
"Logging error!\n" ; )
27 : 28 : 29 : ]0 :
31 : ]J :
J] :
]4 :
class Alarm : p ublic Condi tion ( public : Alarm () ; virtual -Alarm () {} virtual void Warn() { std :: cout « 'Warning ! \n" ; } virtual void Log () { std:: cout « "General Alarm 10g\o' ; l
I
442 1 ~. rész • KlIlönl.g.sség.k 35 :
36 :
virtual void Call()
O,
};
37 :
38 :
Alarm::Alarm()
39 : 40:
( Log();
41: 42:
Warn();
43: 44: 45: 46: 47:
class FireAlarm : public Alarm public: Firelllürm() {Log() ; } ;
48,
virtual -FircAlarm()
49: 50 : 51 : 52 : 53 : 54: 55: 56:
virtual void Call{) ( std: : cout« virtual void Log () { std :: cout «
(j
);
int maln!) {
int input ; int okay :o 1;
57: 58:
Condition· pCondition; while (okay)
59: 60:
{
std : :cout « • (O)Quit std: :cin » input ;
61: 62:
okuy = input;
63:
switch (input)
64: 65 :
ci!lse O:
break; c.!Ise 1:
pCondition = new Normal; delete pCondition;
6B : 69 : 70 : 71 :
72 : 73 : 74: 75 : 76 : 77 : 7B :
79 :
break ;
case 2 : pCondition = new F.i reAlarm; delete pCondition ; break ; default : pCondi t ion'" n ew Error ; de l e t e pCondition; okay", O; break;
BO : B1: S2: S3:
(11Normal
{
66 : 67:
' calling Fire Dept . I \n" ; ) " Logging rire call . \0' ; }
return O;
(O)Quit {l)Normal
(2)Fire : 1
(2)Fire : '.
II
____________________________~U~.~ ~_·~O~~~e~==m~~~n~·.~n~~ ~ el=emri ~= s =és~t=.~ =e=re=s~ 1« ~ 3 _______ii; Logg ing normal conditions ... (O)Quit (l)Normal (2) Fire : 2 General Alarm log Warning! Logging fire call . (O)Quit (l)Norma l (2 )Fire : O
r Az 58. és 81. sorok között található esemény hurok egy éí.lékel6 szimuláci6j{1t valósítja meg: értesít a normális mLÍködésr(51 vagy a túzrÓI. Megfigyelhetjük, hogy az értesítés hatására létrejön egy Conditi on típusú objektum, melynek konstruktor.! kü lö nbözó tagfüggvényckct hív meg. Virtw'ilis ragfüggv6nyeket konstruktorb61 megbívva zav
56bb, ha már !C:trcjöu II F'ireAlnrrn, ami már a FireAlarm : : Log () .
II
konstrukto r ismét meghívja a Log () függvényt ,
PostMaster: egy esettanulmány Ussunk most egy másik problémát, amelyen gyakorolhat juk az objekrum-orientált tervezést. Az Acme Sofrware cég felbérelte az OlvasóI egy új szoftver megalkol:lsára és egy CH programozói csapat toborzás,íra. Jim Grandiose, az új termékek fejlesztési részlegének alelnökc a megbízónk. Azt szeretné, ha megtelVcznénk és elkészítcnénk a PostMastert, egy olyan e-maii olvasó programot , mellyel a különböz6 szolgáltatók rendszerét elérhetjük. A potenciális célközön~g o lyan üzlclc mberekból áll, "kik egynél több e-maii szolgáltatót használnak, mint például: CompuSclVe, America Online, Internet Maii, lotus Notes, és így tovább . A felhasználó mcgt:tníthatja a PostMastert, hogyan csatlakozhat a különböz6 c-maii szolgáltatókhoz. A PostMaster letöiti a leveleket és egységes formában tá~a II fe1hllsználó elé, Ichetóvé téve az e-mailek rendezését, a választ, a továbbítást a szolgáltatók között, és í!,'Y lovább. A PosrMas[er Profcssional - nlinc a PostMaster következ6 veni6ja - szintén tervbe van véve. Ez rendelkezni fog adminisztratív segéd - titkár - móddal, ennek keretében a felhasználó feJj ogosítllat egy másik személyt az összes vagy csak néhány e-maii elolvasá-
444 1VI. rész • KlIllnl,g,..óg,k sával, rutin levelezések kezelésével, és í&'Y tovább. Tennészelesen már a marketing osztály is jelezte igényét eb'Y mesterséges intelligenciával bíró komponensre, melynek segítségével a PostMasler rendezhetné és megfelelő prioritással látná el a leveleket a tárgyuk, illetve a tartalmukba n lévő kulcsszavak alapján. Egyéb bővítésekr61 is volt szó, például nemcsak levelek, de levelezőlisták vagy Internetes hírcsoportok kezelése. Nyilvánvaló, hogy az Acme nagy reményeket fúz a POS1Master-hez, és piacra dobáshoz szűk idókerettel, de gyakorlatilag korlátlan köll-
ségvetéssel rendelkezünk.
Mérj kétszer, vágj egyszer Megrendeltük az eszközöket és berendeztük az irodá t. Ezután els6 lépésként pontosan speci fik51juk ~l lerméket. A piac fe lmérése után egyplatformos fejlesztést javasolunk
a f6nökün kn<::k , de döntenünk kell , hogy Unix, Macintosh vagy Windows legyen-e ez a bizonyos egy? Számos fámszló megbeszélésünk voll Jim Grandiose-:z.1l. Kiderül, hogy tökéleles döntés nincs, tehát két részre boOljllk a projektet: el6térre, vagyis a fe lh:'lsznál6i felületre, és háttérre, vagyis az adatbázisért és kommunikációért felelős részre. Hogy gyorsabban haladjon a fejlesztés , el6ször Windowsra fejlesztünk, kés6bb pedig Unixra és va lószínIlleg Mac-re. Ez az egyszen1 döntés számos új kérdést vet fel projckuel kapcsolatban. Azonnal nyilvánva lóvá ,válik, hogy szükségünk lesz számos függvénykönyvtárrn , melyek feladata a mcmóri:lkezclés, a kül önböző fe lhasználói felületek kezelése, valamilll a kommunikáció és :I Z adatbázis kezelése. Gmndiosc úr komolyan gondolja, hob'Y a projekt azon áll v:lgy bukik, hogy megfelelő en átlátjuk-e problémáI. Éppen ezért arra kér minket, hogy csak a kezdeti elemzés és telvezés után toborozzunk kollégákat. Nekilánmk a probléma elemzésének.
Oszd meg és uralkodj Hamar kiderül , hogy igazán több problémát kell megolda nunk. A projektet az alábbi nagyobb alprojektekre osztjuk: • • •
Kommunikáció: Az e-maii szolgáltató elérése modemes kapcsolaton vagy hálózaton keresztül. Adatbázis: Adatok tárolása a lemezre és visszaolvasása. E-maii: A küJönböz6 (szolgálta tófuggő) e-maii formámmok olvasása és létrehozá.......
22. óra • Objektum-orientátt elemzés és telVezés 1 445 • Szerkesztés: Kíváló szerkes ztő felület bil.tosftása üze netek létrehozásához Í!s m6dosításához. • Platform kérdések: A felhasznál 6i felület elemei nek megjclenítése minden platformon. • Bővíthetőség: Tervek a fejlesztésre , bóvítésre. • Szervezés és ütemterv: az egyes fejl eszt6k igazgatása és k6djaik közti függősé gek kezelése . Minden csoportnak kitűzi és közzéteszi a hat.á ridőit, hogy utána ennek megfelelőe n It::hessen tervezni. A cégvezetésnck Í!s :I marketingeseknek természetesen tudniuk kt!ll , nukorra készül el a termé k. Felveszünk egy menedzscn, aki a szervezésén és a határidőkért fele l. Ezután felveszünk vezet5 programoz6kat, akik segítenek az elemzésben és a tervezésben, valamint utána részt vesznek II megval6sításban. Ezek a vezető programoz6k a z alábbi csapatokat hozzák lé tre: •
Kommu nikációs csoport: A betárcsáz6s és egyéb hálózati ka pcsolatok kezelése. Ok csomagokkal, :Idatfolyamokkal és bitekkel foglalkoznak, nem pedig magukkal az c-maikkkel. • Üzenetek formátumáért fele l ős csoport: Ez C5.'lpat felel a különböző e-maiI formátumok és a kanonikus formátum - PostMasler sz..1bv~ny - közti oda-vissza ~dakítflsé rt. Szintén ez a csOport foglalkozik az üzenetek ICme7.re me ntésével és ig6ny szerinti visszatöltésévcl. • Üzenctszerkeszt6ért felelős csoport: Ez a csoport felel6s a teljes relhasználói felüIct~rt mindegyik platformon. A sze rkesztők feladata, hogy a háttér és a klienseld:.1 közti kommunikáció minél egyszen1bb és igényeit tekintve minél szűkebb legyen , így a termék kiterjesztése más platfonnokra nem igényli a forrás másolásá:t.
Üzenetformátum Úgy döntünk, bOh'Y fé lretéve a kommunikációs és fe lhasználói felületet, el6ször az üzenel formátumával foglalkozunk. A többi csak azután következik, bogy teljesen megértettük, mir61 is van szó. Nem sek é rtelme va n azon törni a fejünket, hogyan jelenítjük meg az információkat, amíg nem tudjuk , milyen információkal kell megjeleníte nünk. A különböző e-maii formátumok vizsgálata során kiderült, hogy sok hasonl6ság van köztük a számos különbség ellenére is. Minden e-maiI üzene tnek van felad6ja , címzettje és létrehozási dátuma. Csakne m minde n üzenet rendelkezik címmel vagy tárgysorral, a levéltörzs pedig egyszeru vagy összetett szöveg (formázásokkal), tanalmazhat grafikát, esetleg hangot, vagy bármilyen egyéb extrákat. A legtöbb ilyen e-maii szolgá ltatás lehet6vé teszi csatolt áll ományok kezelésél is, melyet a felhasználó más progmm segítségével megnyithat.
44s l VI. rész· Különlegességek Megerősítjük korábbi döntésünket, mely szerint az eredeti formátumot áta lakít juk Postl\1aster [ormátumúvá. Ezzel a megoldással csak egy formátumot kell tárolnunk, valamint a lemezre írás/ olvasás is egyszeruDbé válik. A fejlécben található információkal (küldő , címzett, dátum, tárgy, stb.) a levéltörLSét61 elkülönítve tároljuk. Így ha a felhasználó keresni szeretne a fejlécekben, ezt anélkül leheti meg, hogy a lcvéltörzsét is be kellt!ne olv:I:.nia rt progrdmunknak. Előrelátóa n felkészü lünk arm az esetre is, amikor a felh
Az osztályok kezdeti terve Az elemzés ut:ín megtervezzük a Message osztá lyt. A bővíthet6séget szem elŐlt tartvH később tudjuk kezelni a nem e-mai i jellegű üzeneteket is - létrehozzuk az Email Message osztá lyt a Message absztrakt alaposztályb61. Az EmailMeosage oszt:'ilyb61 sz:'irmaztatjtlk a Pos tMasterMessage, InterchangeMessagc, CISMessage, PrOdigy Message , stb. osztályokat. Az üzenetek természetesen objektumok lesznek a programban, azor,ban megalkotni a megfelelő objektl.lI11okat az, ami a legnagyobb kihív ~s az objektum-orientált programozásba n. Néhány esetben, mint például üzeneteknél, az els6dleges objektumok kicsnek, mint lehetséges megoldások. Gyakran hOSSL111 és komoly:.., gondolkoznunk kelJ azon, hogy megtaláljuk a megoldandó feladathoz a megfe l elő objektumokat. Ne essünk kéL~égbe , H legtöbb terv nem lesz tökéletes cls6 nekifutásrn. Kiinduláském fogalmazzuk meg a probléma leírását szóban. Késl-Ítsünk listát a leírásában szereplő f6 nevekr6 1és igékr61. A f6nevekb61 jó eséllyel
r
______________________________~2~2,~6~m~·~O~b~ie~~~m~~~n~en~~~tt~e~le~mz~é~s~é~s~m~N~e~re~s~I~44~7~______k~ t Ha e-maiI érkezik ,I szolgáltatótól, nem feltétlen kapjuk meg külön a fcjJécet és külön a törzset. Sok szolgáltató egy nagy adatfolyamkt':nt küld i, amelyet a progl"Jffiunknak kell szétbomania. Talán érdemes ennek megfele l ően kialakítani a hierarchiát Tovább vizsgálva a feladatot arra jutunk, hogy meg kell próbálnunk összegyűjteni az üzenet tulajdonságait, szem e l őtt tartVa új képességek bevezetésének lehet6ségét és az adauárolás megfe l elő absztrakciós szintjét. Az objekrum rnlajdonságainak összegyújtésével könnyen meghatározhatjuk az adattagokat, illetve az esetleges egyéb objekrufiakat, melyekre szükségünk lehet. Az üzeneteket tárolnunk kell, akárcsak a felhasználói beállításokat, telefonszfimokat, és így tovább. A tfirolásnak a hierarchia alj-án kell elhelyezkednie. Vajon közös legyen az üzenetek és a beállítások aJaposzt!llya?
Egy vagy több kiindul6pontú hierarchia Az örökl6déssel kapcsolatban alapvet6en kétféle megközelítés létezik: az egyik szerint minden, vagy majdnem minden osztály cgy közös 6st61 - il gyökéroszliilyt61 - ered, a másik szerint megengedjük a többszörös örökl6dé.'it. A közös 6sosztály előnye, hogy gyakran elkeri.\lhet6 a többszöröS örökl6dés, hátránya viszont, hogy az irnplernenládó gyakran visszaszivárog az alafX)$ztályba. Az osztályok egy halmaza gyökeres struktúrájú, ha minden osztálynak kÖZÖS:lZ 6se. Gyökérrel nem rendelkez6 hierarchiáknál nem minden osztálynak ugyanaz az &osztálya. Elhatároztuk, hogy több platformra fejlesztünk, azonb:tn a többszörös örökl6dés összeten és nem feltétlen támogatja minden fordító megfelel6cn az összes pbtformon. igy közös gyökérrel rendelkező hierarchiában és egyszeres öröklődésben fogunk gondolkodni. Megkeressük azokat a helyeket is, ahol a többszörös öröklődés szóba jöhet majd. A hiemrchiát később fájtblommcntcsen felbonthatjuk és beil1eszthetjí.lk a többszörös öröklődést. Ii A belső osztályok neveit p előtaggal l áljuk el, így könnyen mcg ludjuk mond;:mi, mely osztályok szá rmaznHk tőlünk és melyek más függvény könyvtárakb61. A pObject lesz a gyökérosztály. Gya korlalilag ez az objektum az 6se minden más osztálynak. A pObj ect egyszen.1, csupá n adalcsere lesz a szerepe. Amennyiben gyökérrel rendelkező hierarchiát szeretnénk, a gyökérosztálynak általános nevet adjunk (minl a pObjec t) és csak kevés képességgel ruházzuk fe l. A gyökér objektumnak létre kell tudni hozni az utódait és azokra, mint pObj e ct példányokra hi\·atkozni. Cserébe az interfész &'Yakran áL,>zúródik a &'Yökérosztályba.
448 1VI. rész • Különlegességek A hiera rchiában a pStored és a pwired osztályok következnek. A pStored objektumokat a lemezen tároljuk (például ha nem használjuk a programot), a pWired objektumok pedig modemen vagy hálózaton küldjük üzenetekként. Csaknem minden objektumollernentünk lemezre, így érdemes azt a gyökérhez közel elhelyezni a hiemrchiában. Az összes elküldöu objektumot tárolnunk kell, de nem minden tárolt objektumOl küldünk el, igy jogosan származtatjuk a pWired osztályt a pStored oszlályból. Minden származtatott osztály hozzáfér a sz lilőosztá l y tudásához (adat) és fun kcionalitásához (metódusok), és mindegyik egyedi képességekkel bővÍli azl. Ebből kiroly61ag a pWircd larl:.llmazhat plusz metódusokat, dc ezek a mOO(!ffit:s adatátvitelért lesznek felelfisek. Lchc(!';éges, hogy minden elküldött objektumot t{irolunk, vagy minden tárolt objektumOt elkü ldünk, de akár az is el6fordulhat, hogya fenti kijelentések egyike sem teljesül. Ha csak néhány objeklumot tárolunk és csak néhány tárolt objektumot kolc!Onk el, akkor a többszörös örökl6déssel kell dolgoznunk, V:lgy más módon kell megoldanunk a problémát. Az egyik lcheL~égcs megoldás, hogya Wired osztMyt a Stored osztályból sz:"irma:a;njuk. és JélrehoZllnk met6dusokat a tároláshoz, melyek nem csinálnak semmit, vagy hibát jeleznek, ha az objektumot rnodemen elküldtük, de nem tároltuk. Vegyük észre, hogy néhány objektumot nem kell elküldenil nk, llycnek például a felhasználói beállítások. Persze minden küldött objektum tárolandó, így az ennek megfelel6 örö kl ődési hicruchiál a 22. l-es ábra mutatja .
PObjOO
pSto'ad
pWirad
I pMeuage
22.1 ábra A l...>c:zdcli 6r6k1ődési hierarchia
pPrefe rences
pProvlder Info
22. óra • Objektum-orientált elemzés ás tervezés 449
Apropó
Függvénykönyvtárak: Vásároljuk vagy frjuk?
Vásároljuk vagy írjuk? A tervezési fázisba n mindig felmerül a'kérdés, hogy milyen rutinokat kell megirnunk és melyek azok, amelyeket megvásárolhatunk. Elképzelhető, h ogyelőnyösebb már meglévő kereskedelmi függvénykö nyvtárt használni, mely megoldást nyújt néhány vagy akár az összes kommunikációval kapcsolatos kérdés· re. A döntésnél persze a licencdíjakat és egyéb, nem technikai jellegú kérdéseket is figyelembe kell venni. Gyakran előnyös ilyen függvénykönyvtárakat vásárolni, és a tényleges problémára koncentrálni, fgy nem kell újra feltalálnunk a kereket. Akkor is érdemes elgondolkodni a vásárláson, ha nem kifejezetten C++-hoz szánták a fej lesztők a függvénykönyvtárat, de az alapfunkciói használhatóak. Ez jelentős szerepet játszhat abban, hogy tudjuk tartani ahatári dőket.
Interfészek tervezése Fontos, hogya tervezés ezen szakaszában ne nyugtalankodjunk az impk:ment,íd6 miali. Minden energi:'Ínk:lt az osztá lyok közti letisztult imerf!E!sLek rnegtervezésérc kell koncentr.'ilnunk, majd pedig fel kell vázoJnunk, hogy az osztályon belül milyen adatok1".1 és metódusokrn lesz szükség. Gyakr::\n hasznos, h:l teljes mértékben megértjük aL alaposztályokat, miel6tt a s7...árrnaztatott osztályokkal foglalkoznánk, tehát e lsőként a pObject-re, a pStored-re és a pWired-re fogunk konccmrálni. A pObject - II gyökérosztály - csupán olyan adatokkal és metódusokkal rendelkezhet, melyek közösek. Mondjuk minden objekmm rendelkezhetne egyedi azonosító számmal. Létrehozhatnánk egy pID cpostMaster ID) váltOZÓI a pObject adatwgjaként, de e lőször tegyiik fe l a kérdést magunknak: Szükséges-e ilyen azonosít6s7..ám olyan objekwmhoz, amelyet nem tárolunk, ts nem is küldünk el? Ez persze felveti a következő kérdést is: Van-e olyan objektum, melyet nem t:irolunk, de része ennek a hieF.lrchiának? Ha nincs ilycn objektum, érdemes mérlegelni a pObject és a pStored osztá lyok összcvonását, azonban ha minden objektum tárolt objektum, akkor mi alapján teszünk különbséget? Ezt végiggondolva afra a következtetésre jutunk, hogy néhány objektum, mim példtlu l a cím objektu mok, amelyet önmagában nem tárolunk, jogosan származtathatnánk a pObject-b6I, hiszen ha tároljuk, akkor azt csakis egy másik objektum részekénttönénnc. Jelenleg tehál egy külön pObject osztály mégiscsak hasznos lenne. Képzeljünk el egy á mlistát, amely pAddress objekmmok gyűjte ménye lenne és noha önmagában pAddress objektumot sose tárolnánk, mégis rendelkezhemének ezek az objektumok egyedi azonosítósz:ímokkal , ami hasznos.
I
450 VI. rész • Különlegességek Próbaképp rendeljük hozzá a pID--t a pObject oSllályhoz, amely í!,'Y néz ki minimálisan: class pOjbect (
public : pObject () ; -pobject ();
pID GetID{) const; void SatID() ;
pr-lvate: pID ltsID;
Számos dolgot megfigyelhetOnk ebben az osztálydeklar.:'ici6ban. Elóször is, n osztály nem származtatott osztály, ez ti gyökéroszlály. Másodszor: senuni jele az irnplementáciúnak, még a GetID ( ) -t sem implementáltuk, amely minden bizonnyal egy helyben kirejlett (inline) met6dus lenne. Harm:ldszor: felfigyelhetünk a const metódusnl, mely az interfész része, nem az implement<ÍCióé. V6gü1 pedig bevezettünk egy új ad'lltípUSl pID néven. A pID itt inkább tíPlIS, mint tényleges vállozó - jelenesetben előjel nélküli long típus -, mellyel rugalma-
sabbá válik a progr.llnunk fe lé pítése. lia kidelÜl , hogy nincs szükségünk el6jel nélküli long-ra , vagy az el6jel nélIriili long nem elég nagy, egyszeníen módosítjuk a pID-t. Ez a módosítás mindenhol jelentkezni fog, ahol pID-Ve! dolgozik a program, nem kell megkeresni ti forrásk6dban az összes olyan helyet, ahol el6fordul ::t pID. Most::t typedef segíL'iégével a pID-t ULONG típusúnak deklaráltuk, az ULONG-Ol pedig cl6jcl nélküli long-ként deklaráltuk. Ez felveti ::t ké rdést: v:ljo n hova kerüljenek ezek a deklarációk? Nagy projektek esetén a különooz6 fájlokat is meg kell terveznünk. Projektünkbe n az egyik hagyományos megközelítést alkalmazzuk: minden osztály külön fejléc állománnyal és implementáci6val (.CPP állomány) rendelkezik. Ezén az állományaink ~ vc OBJECT . HPP és OBJECT . cpp lesz. Hasonlóképp hozzuk létre a pMessage osztály deklaráci6jához II MSG . HP?, az implementáci6jiihoz pedig a MSG . ePF iillományt.
Prototfpus létrehozása Egy olyan nagy projekt esctén, mint a PoslMaster is, nem valószínű, hogy az els6 ter. tökéletes és teljes lesz. Könnyen belefutharnnk a függ6leges skálázhatóság problém... ba. Egyene,> út vezet a katasztr6fához, ha úgy próbáljuk meg létrehozni az összes g tályt és kiegészíteni az interfészeket, hogy még egy sor működó kódot sem ínunk.
______________________________~2~2~.6~~~·~O~bi~'~~m~~~n~·e~má=k~'=I'=mm~·S~é~S~m~N~'~Zé~S~I.4~51~_____1I ~ Számos igen jó ok van arra, hogy miért érdemes a tervet egy prototípuson kipróbálni amely egy kevéssé elegáns, de működ6 példa az alap ödeteinkre. Számos típusa van a prolOtípusnak, melyek ntind más és más igényeket elégítenek ki. A felhaszn(116i interfész prototípus lehet6vé teszi a végfelhasznál6kmlk szánt felület tesztelését
A funkcionális prototípus nem foglalkozik a felhasználói felillettcl, viszont kipróbálhatóak olyan képességek, mim pfldáullevelek továbbítása vagy csatolt állományok kezelése. Végül pedig a fe lépítéssel kapcso];uos prototípus megadja a lebet6o,;éget egy kisebb progr..1111 fejlesztésére , valamint annak megbecsülésf:re, vajon a tervezés tükrében kés6bb hogyan skálázható a program. Rendkívül fontos, hogy végig világos,lk maradjan:lk a prototípus céljai. Döntsük el, hogya felhaszná lói felületet vizsgáljlIk, a funkcionalitáss
A BO/BO-as szabály Egy amolyan tervezési őkőls7",,1.bály szerint
.~
452 1VI. rész· Különlegességek
Apropó
Parelo szabálya
Van egy másik szabály - a 80/20-as szabály -, amely szerint a program elsó 20%ának megírásához az idő 80%-ra van srukség, a program hátralévó 80%-a pedig az idő másik BO%·át igényli. Na jó, ez csak vicc. Az igazi szabály szerint az
idő
80%-a a munka 20%-ához szük·
séges, de egy egészen másik összefüggésben azt is szokták mondani hogy egy cég hasznának 80%-a az ügyfelek 20%-tól várható. Ezt a szabályt tehát széles körben lehet alkalmazni.
A PostMasterMessage osztály tervezése Mindezeket szem el6n tartVa úgy ctönrünk, hogy
il
továbbiakban a ElostMa sterMessage
osztályra koncentmlunk. Ez az az osztály ugyanis, amit a legközvetlenebb módon
lU -
dunk el len6rizni.
Természetesen az interfész részeként a PostMasterMessage-nek a különböz6 tfPllsok közt.i párbeszédet is bonyolítania kell. Azt reméljük, hogy az egyéb üzenettípusok szolgáltal6ival sikerült majd szoros együtt.működésl kialakítani , és meg t.udjuk szerezni 16llik az Ozenetformáwmok specifikáci6ir. Egyel6re .. zonban C$.1 k ügyes t.. lálgmásokra tudunk hagy.. tkozni azok .. lapján, amit az adol:{ rendszerekb61 kapllJnk. A PostM!lsterMessage minden esetben rendelkezik felad6val , címzettei, feladási dátllmmal és tárggyal, valamin( tartalmazza levél vagy üzenet törzsét és esellegesen csatolt állományoka!. Ezek alapján nyilvánvaló, hogy elérési met6dllsokat kell majd írnunk ezekhez az :mribútumokhoz, a csatolt állomá rlyok és a levél méretének jelzéséhez, és így tovább. Néhány s7.0lgált..tó, melyekhez csatlakozni fogunk, b6vített szövcgrormátllmot haszna. vagyis olyan szövegel, mely utasításokat a betűtípussal és egyéb, :t formázáss:!l kapcsolatos utasításokat (félkövér, dőlL) is tartalmaz. Más szolgáltatók ugyanakkor nem támog:lIják ezeket az attribúlumokat, viszont esetleg saját rormáttunot használnak bővített formátumú szöv(;!gek kezcl6sére. Az osztályunk tch,ít rendelkezni fog konvcrzi6s eljárásokkal, amelyek a bővitett szövegrormátumot egyszerű szöveggé alakitják, illetve PostMaster formátumúra hozzák az egyéb formátumokat.
Az alkalmazásfejlesztói interfész Az alk:llmazásfejleszt6i interrész (Applicalion Programming Interface - AJl!) leír.'isok es rutinok gyűjteménye egy szolgáltatás használatához. Számos e-maii szolgáltató elérhet6vé teszi saját API-ját, igy a PostMaster is használhatja az olyan kifinomult lehetősé-
22. óra • geket, mint például a bővített szövegformátum kezelése vagy a beágyazott állományok hozzáfűzése. Minden bizonnyal a PostMaster API-jának közzététele is hasznos lesz, hiszen így más szolgáltatók számár.. lehetóvé válik a Posl1\1asterrel való egyiitunúködés. A PostMasterMessage-hez egy jól megtervezett nyilvános interfészt keJllétrehoznunk, a PostMasler API-jának egyik legfontosabb részét pedig az említet! konverziós fü8!,lYények jelentjk majd. A 22.2. listában látható a PostMasterMessage felületének pillanatnyi leírása.
Figyelem!
Még csak meg se pr6báljuk...
A lista nem definiálja az alaposztályt (Mai1MeSSage ), és emiatt nem is fogjuk tud· ni lefordftani.
22.2 Um - A Po.tM••terM8IIag. interfész (postmasterinterfaca.cpp)
o: 1. 2:
3: 4: 5:
6: 7: 8: 9: 10 :
1/ 22 . 2 Lista. a PostMa.sterMessagc osztá l y
class PostMasterHessaqe : publ i c MailMessage { public : PostMaatcrMcsaaqc(); Poflt.Ma~terMel'lsaqe( pAddre~s Sender. pAddrc99 Rccipient. pString Subject. pDatc crcationDate);
11 :
12 : II egyéb konstruktorok jOnnek ide 13 : II ne fe1edkezzank el a máso16 konst r uktorr61 14 : II mint ahogy a storagc 15 : II és a wire (ormátumú konstruktorokr61 sem 16 : II A kalönbözó egyéb formátumok konstruktorair61 se feledkczzQnk meg 17 : -PostMasterMessage() ; 18 : pAddress& GetSender() const; 19 : votd SetSender (pAddress&) ; 20 : II egyéb adattag elérési fOggvények 21: 22 : II operátor metódIJsokat ide . beleé rtve az egyen l őség operátort is 23 : II valamint a különböző konverziós rut i noka.t me lyekkel 24 : II a PostMaster azene t ck más formátumra alakithat6ak. 25 : 26 : private : 27 : pAddress itsSender ; 28 : pAddress itsRecipient ; 29 , pString itsSubject; 30 , pDate itsCreationDate ; pDate itsLastModDate ; 31 : pDate itsRecciptDatc ; 32 :
454 1VI. rész • Különlegességek pD~te
33: 34 : 35:.
itsFlrstReadDate : pDate itsLas tReadDate ; );
A PostMasterMessage osztályt a MailMessage osztályból származtauuk. Számos konstnlktort adtunk meg, melyek lehetóvé teszik PostMasterMessage objckrumok létrehozását más Üzenelformátumokból.
Számos adaltagelérő me16clllsL adtunk meg a különböz6 adaLLagok eléréséhez és 016dosításához, az operálorok pedig lehetővt! teszik ;~Z üzenet egy részének va!,')' egészének átalakítását más formánlffiokrn. A korábban tárgyalt üzenetolvas6 és tároló függvényeket is fe1tünteltük.
Programozás nagy csoportokban Már ez a kezdetleges terv ;s elég arra, hogy lássuk, a kí.i1önbözl'í fejlesztői csopon.oknak mil is kell megval6sít:u,;uk, A Kommunikációs csapat máris kezdheti a kommum-
kációs háuérrel kapcsobtos munkál, de meg kell egyeznie az Üzenetformátum csoportral annak a szíik interfésznek a felépítésé r61, amelyen keresztül a kommunikáciö.. háttér szolgáltatása elérhet6ek lesznek. Az Üzer,etfonm1rulll csapat v::rlószínO"leg egy abpvető interfészt készít a Message OSZJ Iyokhoz, ahogy azt már korábban tettük, majd azzal a kérdéssel fog foglalkozni , h~.... l<'íroljuk és olvassuk ViSSz..1 :rz adatokat. Miután tisztáz6dtak az adauárolással kapcsol. kérdések, máris egyeztetni lehet a kommunikációs réteghez vezet6 interfészr6l,
•
Az lizenctszerkeszt6 fejlesztői erős késztetést fognak érezni arra, hogya bels6 ÜZeT' osztályok részletes felépHését szem e1Őlt tartva valósítsák meg II szerkeszt6ket, ez azonban kifejezett tervezési hiba lelme. Nekik is egy szűk interfészen kell kommup kálniuk :1 Message osztállYlII, az Ozenetszerkeszt6 objekrumok pedig csupán mim'...... ismeretekkel rendelkezhetnek az üzenetek belső strukru rájál illet6cn.
A tervezési folyamattal kapcsolatos szempontok Ahogy halad a projekt , újra és újra felmerül majd néhány alapvető tervezési kém, Melyik osztályba kenJljön egy adott funkcionalitás vagy információ? Ez a fúggH~'!" az üzenet osztályba vagy a cím osztályba kerüljön? A szerkesztő tárolja ezt az m: ciót, vagy az üzenet saj,ít magát menl<;e el? Az osztályokat a titkosügynökökhöz hasonlóan csak minimális tudással érd~
házni, hogy a szükségesnél több adatot ne oszthassanak meg.
r ______________________________~2~2.~6~~~·~Ob~je=tru ~~ ~n=·'~n~=~~'=I'=mm ==s~é=s~te=~~e=~=s~I~4=55~____~I.
Tervezési döntések A munka d<3rehaladlával százával jele ntkcwek majd tervezési ké rdése k. Lesznek amolyan globális k6rdések, például »Ez mit csináljon r de akadnak majd specifikusak is minL például "Hogyan bírjuk mO'ködésrer, Noha az implementáció részletei:1 progmm végs6 formáj áig nem biztosak, illetve néhány inlt!rfész változik vagy cserél6dik munka közben is, már a fo lyamat elején is világos tervvel kell rendelkeznünk. Létfontosságú, hogy még azelóLL megértsük, mit szeretnén k létrehozni, miel6tt akár egy sor progmmk6dot is írnánk. A szoftverfejlesztéssel kapcsobtos cs6dök leggyakoribb oka az, hogya folyamat elej!.!n nem szü letett meg a megfele l ő megállapodás arról, hogy mi is az, ami készüL
Mikor hozzunk döntéseket? Hogy legyen valami viszonyítási :llaplll1k azz.'li kapcsolatban, milyen is egy tervezt!si folya mat, vizsg:'iljllOk meg egy konkrét kérdést: mi lesz a menüben? A PostMaster-ben az els6 va lószínl:Tleg az nÚj üzenet ír.'Ís.'l" lesz, amely viszont felvet egy újabb kérdést. Ha ól felhasználó kiválasltja al ~Új üzenet létrchozása ~ pontot, akkor pontOS:1fl mi fog történni? lkrrejön egy szerkeszl6, amely :lZtán lélrehozla az üzenetet vagy cl6bb It!lrejön az üzenet, amely ,lzután létrehozza a szerkeszt őt?
.
A pal"'dncs, amin dolgozunk, az Új üzene t létre hozása, tehát az (Jj ÜZ(;!I1Ct l (:trehozá~;a tű nik logikusnak. De mi törté nik, ha az iJzcnetírása után a felhasználó rögtön a Mégse gombm kattint? Valószínűleg tisztáhb le nne el6bb a szerkeszt6t lé tre hozni, mely létrehozza (és birtokolja) az új Üzcnel(;!t. Ennek a megközelítésnek az a hibája, h ogyasze rkesztőnek másképp kell viselkednie új üzenet létrehozásakor és másképp egy már meglévó szerkesztésckor. Ha az üzenet már korábban létrejött, átadjuk a szerkeszt6nek, így csak egy kód szüksl:ges, hiszen mindig egy már mcglévó üzenetet kell szerkeszteni. I la az üze netet hozzuk létre először, ki hozza létre? A menü p;lrancs kódj,\ houa létre? lia igen, a m enütől kapja az Ü7.enet a szerkesztési utasítást, vagy ez a Mes sage osztály konstm klorának része? Első pillantásra ugyan van beJUle logika, hogya konstmktor csinálja, e lvégre ha létrehozunk egy üzenetel, valószím11eg szerkesZleni is szeretnénk. Mindazonáltal tervezési szempontból ez nem jó gondolat. Először is nagyon valóslÍnű , hogy már az alapfe\tevésiJnk is hibás. Az ember gyakl".m küld afféle . konzerv üzeneteketn, például egy a7. egyben továbbít a renJszergazdának egy hibaüzenetet. Ilyenkor nincs szükség szerkesztbre. M,ísodszor - és ez talán még az el6bbinél is fontosabb - a konstruktor fel-
I
456 1VI. rész· Különl.g.sség.' adata
:lZ
objektum létrehozása. Egy rendes konstruktor ne tegyen ennél se többet, se
kevesebl>cl. Ha egy Ozenerobjektum elkészült, a konstruktor munkája véget ér. A szerkesztő met6clusnak a ko nstruktorból való meghívása c5<1k összekavarja a konstruktor szerepét másrésl:l sebezhet6vé teszik az üzenet objektumot a szerkesztő hibáival szemben is.
Ami pedig még ennél is rosszabb, az az a tény, ho!,')' az edit metódu5 meghív egy másik osztályt - a szerkeszt6t- vagyis meghívja annak a konstnlktorát is. Ugyanakkor a sze rkesztő nem alaposztálya a üzenet osztá lynak, nem is tanalm3zza ót :I Z üzenet osztály, tehát tökéletesen szerencsétlen, hogy az üzenet létrehozása a szerkeszt ő konstnlktornnak sikeres !efutásán múlik. Végül teljesen nyilvánvaló, hogy sose hívnánk meg a szerkeszt6t, ha ll1ag:ít :lZ Ozenetet sem sikerült létrdlo:t.ni, m:hpedig ennél a megold,ísnál éppen arról van szó, hogya létrehoús sikr.::rl;! a szerkeszt6 sikeres hívását61 függ. Ez tehát (,!gy tökéletesen kjfordított h e lyz~L Világos, hogy csak nzután hívhatjuk meg a Message : : Edit ( ) -et, miután a ü7.enet oszttily konslmklora teljesen lefutott.
Munka a vezér1őprogramokkal A tervezéssel kllpcsolalos hibák felderítésével kapcsolatban az egyik lehetséges megközelítés lIZ, hogy már a lenrezés illetve megvalósítás kora; fázis.'iban létrehozunk egy vezérl6progr.lmot (driver program). A vezér16program o ly:m függv61Y melynek feladat.1 csupán függvé nyek mllködésének bemutatása vagy ell e n őrzése. Péld;íul a PostMaster vezér16progra mja rendelkezhet egy nagyon egyszen1 menüvel, mely létrehoz PostMasterMessage objeknullokat, manipulálja őket, v,lgy más módon teszteli ;1 ter... egyes részeit. A vezérl6programot éppen ezén szokás ~l eszthánUl ak" is hívni .
A 22.3. Lista bemutatja a PostMélsterMessage kissé robllsztllsabb vflllOZalál, valamint egy egysze rű vezér16programot.
22.3 LII1a - A PostM....rM....ge vezé~6progr.mja Idrivllflll'Olfam.cpp)
o: l: 2: 3,
II 22.3
Li~ta
#include ttinclude
typedef unsighed long pDat e ;
5: 6:
enum SERVICE { PostMaster, Interehange, CompuServe , Prodigy, AOL. Internet } ;
7. 8:
class String
,. 10 :
public :
11 : 1/ konstruktorok 12: String();
22, 6ra • Objektum-orientált elemzés és tervezés 1457 13 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : 24 :
String(const char · const ) ; String(const String &); _S tring () ;
II tú lterhel t operátorok char & opcratorl) (int o f fset) ; char operator(] (int offset) const ; String opcra l:or+ (const String&) ; vo i d oper~tor + =(consl String&) ; String & operator= (const String &); friend atd : : ostrearn & operator« (std : : ostrearn& theStream, String& theString) ; II alap adattagelér6k lnt GetLcn()const { return itsLcn ; } const char · GetSt.ring{) const { return it.sSt.ring ; } II stat.ic int Construct.orCount:
25 : 26 : 27 : 28 : 29 :
30 : 31 : 32: 33: 34 : 35 : 36 : 37 : 38 , 39 : 40 : 41 : 42: 43: 44 :
privHte : St ring (int) ; char ' itaString; int. itsLen; };
II privát konstruktor
II az alap konstruktor létrehoz egy O bájt hOSSZÚSÁgú sztrin{Jet String: : String() ( it.sSt.rlng = new char!l) ; itsSt.ring[O ] '" '\0'; itsLen=O ; II st.d: : cout « "\tDefault string construct.or\n"; II ConstructorCount++ ; )
45 : 46: 47 : 48: 49 : 50 : 51 : 52 , 53 : 54 : 55 : 56 : 57 :
1/ privát (segéd) konstruktor, melyet csupán II aZ osztály metódusai haslmálnak üres sztringek II létrehozására a kivánt méretben, Nullával töltjük fel. String : :String(int len) ( j tsString • new char [len+ l) ; int i; for ( i '" O; i<,.len; i++) itsString{l) = ' \0 ' ;
58 :
}
itsLen~len;
1/ std : : cout «
"\tString(int) constructor\n" ;
II ConstructorCount.++:
59 : 60 : 61 : 62 , 63 : 54 : 65 : 66 :
/1 KaraktertOmböt alakítunk át sztringgé String : : String(const char· const cString) { itsLen -- strl en (cString) ; i tsString = new char [ i ts Len+l ); int ifor ( i '" O; i
450 1VI. rész • KUlönlegos,ógek 67 : 68:
69 : 70: 71: 72, 73: 74, 75 : 76 : 77 , 78 : 79 , 80 , 81 : 82 : 83 : 84 :
itsString[i] '" cString[i]; itsString[itsLen]='\O'; II std: : cout « "\tString(char*) constructor\n"; II ConstructorCount ++;
II másoló konstruktor String: : String (const String & rhs) I itsLen=rhs.GetLen() ; itsString := new char(itsLcn+ll; int i, for li = O; i
85: 86: II destruktor, felszabadítja a lefoglal t memóriát 87: String : : -String (j 88: ( 89, delet e lj itsString; 90: itsLen = O; 91, II std : :cout « "\tString destructor\n ";
92:
93: 94: 95: 96: 97 : 98: 99: 100 : 101: 102: 103 : 104: 105 : 106: 107 : 10 8 :
String& String: : operator= (const String & rhs) I i f (this ... &rhs) return "this; delete II itsString; itsLen=rhs.GetLen(); itsString ~ new char[itsLen+l]; int i; for (i .. O; i
109 : II nem konstans eltolási operátor, mely 110: II a karakter pozícióját adja meg, me ly így változtatható 111 : 112: 113 : 114: 115 : 116: 117 : 118 :
char &. Str ing : : oper ator[ ] (in t offset) { i f (offset> itsLen) return itsString[itsLen- l]; elRO return itsString[offset];
119, II konstans eltolási operátor, melyeket 120: II konstans objektumokon használunk (lásd másoló konstruktor)
f
22. óra • 121 : 122 : 123 :
124 : 125 ;
elemzés és tervezés
char Str i ng : : operator [ ] (int offset) const { if (offset> itsLen) return itsString[itsLen-lJ: else
126 :
retu rn itsString[of fse t];
127 :
128 : 129: II létrehoz egy új sztringet, mely az aktuális sztringet 130 ; 1/ hozzáadja az rhs-hcz 131 : String String : : operator; (const String& rhsl 132 : ( 133 : int total Len = itsLen + rhs ,GetLen () ;
13 4: 135 : 136 : lJ7 : 138 : 139 : 140 :
141 :
String temp(totalLen) ; int i,j; for (i = O; i
i+-l·)
tcmp!i] " itsString[i); (j = O; j
j++ .
iH)
return temp;
142 : 143 :
144 : /1 mcgváltoztatja az uktuális sztringet . nincs visszatérési értéke
145 : 146: 147 : 148:
149:
ISO : 151: 152 : 153 :
154 : 155 :
156 : 157 :
void String : : operator+=(const String& rhsl { int rhsLen = rhs.GotLen() ; int totalLen .. itst,en .to rhsLen; String temp{ totalLen) : int i , j : for ( l = O; i
158 :
159 ,
II int String : : ConstructorCount
O:
160 :
161 : 162 : 163 : 164 : 165:
166 :
std , : ostream& operator« ( std : : ostream& th oStream , String& theString) theStream « theString.GetString{) ; return theStream :
167 :
168: 169 :
class pAddre ss
170 :
171 : 172 : 173 :
174 :
public , pAddress(SERVICE theService, cons t String& theAddress , const St ring& t heDisplay) ,
I
460 VI. rész • Különlegességek 175: 176: 177 :
itsService (theService ) , itsAddres sString(theAddress ) , itsDisplayString( t heDisplay)
178 :
{}
179: 180: 161:
II pAddress (String , String); /1 pAddress() ; II pAddress (const pAddress&);
182:
-pAddress(){}
183: 184 : 185: 186 : 161 : 188: 189 : 190 : 191 : 192 : 193 : 19 4:
friend std : : ostream& operator« ( st.d : :ostream& theStream, pAddress& theAddress) : String& GetDisplayString{) ( return itsDisp!ayString ; private: SERVICE itsService ; String itsAddressString ; String itsDisplayStri ng ; ); std : : ostrearn& opera t or« std : : ostrcam& theStream , pAddress& theAddress)
195 , 196 :
theStream «
197 : 198 :
return theStream ;
theAddress .GetDisplayString() ;
199 : 200 : class PostMasterMessage 201 : ( 202 : public: 203 : 1/ PostHasterMessage () ; 204: PostHasterMessage(const pAddress& Sender, 205: 206: const pAddress& Recipient. 207 : const String& Subject, 208: const pDate& creationDate) ; 209 : 210: -PostMasterMessage(){) 211 : 212 : void Edit(); 1/ meghívja a szerkesztől erre az üzenetre 213 : 214 : pAddress& GetSender () re t urn itsSender ; } 215 : pAddrcss& GetRec i pient ( ) {return itsRccipient; 21 6 : Stri ng& GetSubject( ) {rct urn itsSubject ; 217 : /I vo i d Se tSender( pAddress& ) ; 218 : 1/ egyéb adat t age l ér6k 219 : 220 : /I operátor metódusokat i de , beleértve az egyenlőség operát ort 221 : 1/ valamint a kl1l0nbóz6 konverziós rutinokat 222 : /1 melyekkel a Pos tMaster I1 zenetek más formátumra alakíthatóak. 223 : 224 : private : 225: pAcldress itsSende r ; 226: pAddress itsRecipient; 227 : String itsSubj ect ; 228 : pDate itsCrea tionDat e;
~~
22. óra· Objektum-orientált elemzés és tervezés 1461 229 : 230 : 231 : 232 : 233 : 234 : 235 : 236 : 237 : 238 : 239 : 240 : 241 : 242 : 243 : 244: 245 : 246: 247: 2 48 : 249 : 250 , 251 : 252 : 253 : 254 : 255, 256: 257 : 258 , 259 : 260: 261: 262: 263 : 264 : 265 : 266 : 267 : 268 : 269 : 270 : 271 : 272 : 273 :
pDate pDate pDat e pDate
itsLastHodDate; itsRecoiptDato ; itsFirstReadDate : itsLastReadDate ;
}: PostMasterMessage : , PostMasterHessage( const pAddress& Sonder, const pAddress& Recipient , const String& Subject . const pDato& creationDato) : itsSender(Sender), itsRccipicnt(Recipient) . itsSubject(Subjcct) , i t sCreationDate(creationDate) , itsLastModDate(creationDate) , i t sFirs tReadDate (O) , i t sLastReadDate(O) s td: : caut «
" Post Master Message created . \0" ;
void PostMasterMessage : : Edit() { std :: cout« "PostMasterMessage edit function callod\n";
int main (l pAddress Sender( PostMaster, " j1ibcrty@PostMaster", "Jesse Liberty"): pAddress Recipient ( PostMaster, "s1ibcrty@PostMastor",'Stacey Libcrty") : PostMasterMess8ge PostMasterMessage( Sender , Recipient, 'Saying Hello', O) ; std : : COllt « "Messaqe review . . . \n " ; std : : cout « "Prom : \ t \t' « PostMasterMQS1HI.ge .CotSender () « std :: end1 ; std : : cout « ' Tc : \ t \t· « PostMasterMessage .CetRecipient () « std :: endt : std : , ccut « "Subj ect : \ t." « PostMasterMessage.GetSubject() « std : : endl; retur n O;
Post Master Message created . Me ssage review ... From : Jesse Libcrty To : Stacey Liberty Subject : Say ing Hello
462 1VI. rész • Különleges,égek
A 4. sorhan a pDate típust előjel nélküli long-nak definiáltuk. Nem ritka , hogya dátumokat long típusú egészként tárolják. Gyakran ez olyan önkényes d5tumokt61 , mint például 1900 január 1 . , eltelt másodpercek sz:ímát tartalmazz
A pAddress-t:l 169. sortól a 191. sorig terjede; szakaszban dekJa ráltuk. Ez cstlpán osztály alap funkd onalitás{ll adja és bővíthet6 , ahogy egyre jo bban megénjük a programot. Ezek az objektumok lét.fontosságú elemei minden üzenemek: a feladó és a dmzell <:Íme is ilyen típusÚ. Teljes funkcionalitá ssa.I bíró pAddress objekulmmal oldjuk meg lovábbítoll, megválaszoll, slb. levelek kezelését. :lZ
A pAddress objcktum Celadala a megjelenítell sZl"ring és a bels6 l!tv<'l.lasztÓ sztring követése :I Z adou szolgáltatáshoz. Egy megválaszolatlan ké rdés, hogy csupán egy pAddress objektum legyen , vagy minden szolgáltatástípushoz legyen megfel el6 310szt~ílya? Jelenleg a szolgáltatást egy ko nstans felsorolásból vál:ISZljuk ki és "Address objekUlmban adauagkéOl tároljuk. A 200. és 233. sorok között találjuk a PostMasterMessage osztály interfészét. A listában az osztály még magában szerepel, de hamarosan az örökl6dési hierarchia részé\'é tchetjük. l-la üjratcrvezés során a Message osztályból származtat nánk, néhány tagváltGzó bekcriilhctne az alaposztftlyba, illetve néhány aJaposzt5!ybeli tagfüggvényt felülbíráIhatna agyerekosztá ly. Az oszt{tly tc\jt:s L-nékú múködéséhez szükség lesz számos konstruktorra, adattageléro függvényre és egyéb lagfüggvényre. Ez a programlista arra hívja fel a figyelmet, hogy nem kelll00%-0s állapotban lennie a programunknak ahhoz, hogy vezérl6prograIllOl írhassunk és néhány szempomból tesztelhessük azl. A 251-254 sorokban egy csonka Edit () függvényt találunk, csupán jelezlük, hOY3 kerüljön a függvé ny, ha az osztályunk már teljesen működőképes lesz.
____________________________~2~2~,6~~~·~O~bj~e~~m~-o~n~·e~má~l~te~le~~ ~s~é~s~re~N~.~ú~s~ 1 4~63~______~1 1 A vezérlőprogmffiol a 257. és 273. sorok között runtettük fel. Jelenleg
II
program nem
csinál mást, mint meghív pár adattagc1ér6 függvényt és a túlterhelt « operálott
(oper-ator«). Mindazonáltal megfelele; kiindulópontot nyújt a PostMasterMessage és II kerelrendszerrel való kisérletezéshez - osztályok módosítása és a módosítások hatása.
Kérdések és válaszok Kerdc.>S: Miben kIII6nbózik a!apvetöcn az objcktum-orielllált elemzés és leroczés ti
főbbi
II/egMzelítést6l?
\fa/asz: Az objektum-orientált prog'ramozás előtti technikák esetén az elemZők és a progmmoz6k úgy tekintenek II programra, mint függvényekrc, melyek műveleteket. hajtamlk végre adat.okon. Az objektum- orientálL programozás úgy tekint
Gyakorlatok Ebben a leckében az objektum-orientált elemzést és tervezést sajátíthattuk el. V!ilaszoljunk most néhány kérdésre és oldjuk meg a feladatokat, hogy elmélyítsük II megszerzeLl tudást.
Kvfz 1. Programoz:1s megkezdése előtt mit kell termünk? 2. Mit nevezünk alkalmazásfejleszt6i felülernek (Application Progmmming Interracei AP!)? 3. Mit csinál a vezér1őprogl'"dm? 4. Miért előnyös a nagy alkalmazásokat apróbb részekre bontani?
464 1VI. rész • Különlegességek
Feladatok Ne feledjük,
ti
problémák megoldásai megralálhat6k a CD-n.
1. Válasszunk egy kis problémát a ffiunkánkból vagy a mindennapi élellX51 és hajtsuk végre rdjta a leckében bemutatott lépéseket. 2. M6doslL'iuk a 22.1 LiSl ál (simpleevent . cpp) úgy, hogy minden destruktort bő vlL"ünk egy az std: : cout küldött (izenettel. Futtassuk le a programot és fl&'Ye1jük, mikor futnak le a destmktorok . Azt már tudjuk, mikor futnak le a konstruktorok, hiszen a napló üzenet megjelenik. 3. Keressünk llZ [nterneten C++ osztályokat és függvény könyvtárakat. Néhányat mcgvizsgálva gondolkozzunk el azon, hogy megvás6.rolnánk v~lgy inkább megírnánk?
Válaszok a kvfzkérdésekre 1. Kengeteg tennivalónk van a programozás megkezdése cl6tt: igényfelmérés (mit akar a végfelhasznál6?), elemzés (mi szükséges az igények kielégítéséhez?), és tervezl!s (hogyan valósíthatjuk meg a kívánt funkci6k:it?). Sajnos lú l sok cég gondolja ügy, hogy . Ha már itt ol ez a sok programozó, hát :Ikkor programozzanak. Kl érdekel, hogy nem teljes az igénylisca," 2. Az API egy dokumenrum és eljárás gyűjtemény, mely lehct6vé teszi egy szolgáltatás használatát. Nem kellrudnunk, hogyan mt.1ködik a mögötte álló kód, csupán használjuk. A megvalósítás részletei rejtve maradnak el6ttünk. 3. A vezérl6program segítségével kipróbálhatjuk és demonstrálhat juk a program már mesval6sítou funkcióit, illetve tesztelhet jOk az osztályokat fejlesztés közben Noha ez a program rendszerint teljesen nyers és egyszen1, segítségével megfigyelhetjOk a program viselkedését és az elemei között fe llép6 kölcsönhatásokai.. 4. A részekre bontás egyik legnagyobb el6nye az, hogy több ember (vagy csopon között oszlik meg a munka. Amíg mi az egyik osztályon dolgozunk, addig másvalaki egy másik osztállya I foglalkozhat.
23.
ÓRA
Sablonok Ebben az órában a következ6k,61lesz szó: • • •
Mik a sablonok és hogyan használjuk őket? Mil6\ jobbak a sablonok a makr6knál? Hogyan hozzunk létre osztálys.'lblonokat?
Mik azok a sablonok? A 19. ó rában megtanulluk, hogyan készítsünk láncolt listákat, amelyeket szépen bcc.:samagoltunk, szakzsargonnal befogla ltuk (encapsulation): A lista csak a kezd6muLatór61 tudott, ami delegálta a munk:'ít a belső mUlatóknak, stb. Az egyetlen zavaró tényez6 az voll, hogy a láncolt listánk csak egy adott típusú objek-
tumot tudott kezelni. Más típust sajnos nem tudtunk beletenni. Nem tudtunk például Car vagy Cats objektumokat tárolni benne, és semmi mást sem, ami nem egyezett az eredeti listában lévő tíPUSS,1 1.
466 1VI. rész • KOIönl.ges.ég.k
Korábban ezt úgy oldolták meg, hogy az új adattípusokhoz az objektum új verzióját készítették eL Az objektum-orientált világban egy List ósosztályból származtathatju k a CatsList osztályt, és a LinkedList osztály kódjának jelentős részét felhasználhatjuk a Cats List deklarációjában. I-Ia azonban később Car objekru mok tárolásám akarjuk befogni újdonsült láncoltlistánkat, az öröklést meg kell ismételnünk: új CarList osztályt kell létrehozni , amelybe a meglévő kód egy része átmásolható. Egyik megoldás scm tűnik üdvözítőnek. Az idő múlásával a List osztályt majd b6víteni kelt , és rémálom lesz ezeknek a változásoknak az érvényesítése a már létezt> származtatou osztályokban. A C++ egy viszonylag új adal(:ka, a sablon megoldást jelent a gondunkra. A divatját múllmakr6kkal szemben ráadásu l a nyelv szerves részét képezi, vonatkozik rú ti lípusellen6rzés, és rendkívül sokoldal(lan használható . Lchetúvé teszi, hogy létrehozzu nk egy általfinos célú osztályt, majd egy parametrizált típUSt íitadv;l, a sablon aZt példányosítja.
A sablon példányosftása Sablon segitSégével megtaníthaljuk II fordítól, hogy ne cS3k néhány el6re megadott típusból hozzon Iéire list.'it, hanem bármiból. A PartsList Part objeknunok listája, a CatsList Cat objeknllnok listája. Az egyetlen dolog, amiben különböznek az, hogy milyen típusú dolgoka! szervezünk listába. A sablonok esetében a tárolandó típus az osztálydefiníci6 paramétere lesz. Példányosításnak hívjuk azt, amikor (egy osztályból) objektu mot, illetve egy sablonból konkr(:t típust hozunk létre. Az így létrejött egyedi osztályok :I s:lblon példányai.
A sablon definfciója A parametrizált List osztályt (listasablont) a
következő'
módon dekJaráljuk:
template II a sablon és a paraméter deklarációja class List II a parametrizált osztály public : List () ; II az osztály teljes deklaráci ója }
,
A sablonosztályok deklarációját és definícióját minden esetben a template kulcsszó cl6zi meg, amelyet :I példányonként változ[atható paraméter kövel. Az előbbi kódrészleIben bemutatottlistasablonban például a listában tárolt objektumok típusa változhat. Az egyik listapéldányba tehetünk Integer, míg egy másikba AnimaI objeknllllokat.
Példánkban a c lass kulcsszó jelzi, hogy a pamméler egy típus. Ezt követi a T azonosító, amellyel a továbbiakban a parametrizált típusra hivatkozunk a sablondefinícióban. A lista egy példánya tehát a T azonosítót mindenütt az int, míg egy másik a eat típussal helyettesíti. A parametrizált lista int és eat típusú objektumokat tárol6 példányait a módon deklaráljuk:
következő
Li st anl ntList; List aC~tList ; Az anlntList objektum típusa int , a CatList objektumé Cat objektumokat tároló
lista. Mostantól már használható a List típus bárhol , ahol a szokványos típusok is el6fordulnak: függvé ny visszatérési értékénél, függvény paramétereinél stb. A 23.1 List{tban a Lis t objektumot pllnun éterezzük. Ez kiv{116 techinka sablonok gyártás{tlloz: El6ször bírjuk működés re az objektumOl egy típussal, ahogy ezt a 19. órán tettük, majd általánosílsuk, hogy bármely típust kezel ni tudja.
23.1 ulla - A parametrizált listjk bemutatna lIá••oh listi.all Ipannli"'.pp)
o:
II •••••••••••••• • • _ •••••••••••••••••••••••••••• • *
l : /I 23 . 1 példa 2 : II 3 : II Paraméterezett lista bemutatása 4: /I 5 : /I 6 , II '1 : II 8 : /I 9 : 1/ Paraméterezett lista objektum-orientált megval6sitása .
10 , /1 A List a z absztrakt Node táro16elcmnek delegálja a munká t . ll : II Háromféle tárol6elemet használunk , ke~dot, zár6t és 12 , II kOzbnlsot . Csak ez ut6bbiban tárolu nk hasznos objektumot . 13 , II 14 , II 15 , II A Data os ztályt azér:-t hozwk létre , hogy példánya i t
16 : 1/ a listában t á roljuk. 1 7, 1/ IB , 1/ *•• **.**. *.** .**.*.**.* •• **.**.*.**. ** ***.*.*** 19 : #include
20 : 21 , enum { kl s Sma lle r , kls Large r, kIsSame } ; 22 : 23 , II A li stában t.á rolhat6 Data osztály 2 4: 1/ Minden olyan o sz tálynak , melye t ebben a listában s ze r e tnénk ... t.árolni , rendelkeznie kell a következo két met6dus sal": 25 : /1 Show: Kiir ja az objektum értékét . 26 : /1 Compa re : Összehasonlítja két ob j ektum é rté ké t, ... é s meghatá t"o zza a r e lat i v poz i ci6t.
468 1VI. rész • Különlegességek 27 , class Data 28 :
{
29 : 30 : 31 :
public , -Data()
32 :
{
Data(int val) : myValue(val) {}
std :: cout « std::cout«
33 : 34 :
35 : 36 :
"Data objektum tOrlése. melynek értéke : myValue«
int Compare(const Data
37 : 38 :
void Show()
" \n" ;
&) ;
{ std : : cout «
myValue «
std: :endl;
}
privatc:
int myValue ;
39 :
J;
40:
41 :
42 : II A Compare metódust arra használjuk , hogy egy adott objektum 4 3 : II listábun elfoglalt helyét megállapitsuk. 44: int Data : :Compare(const Data & theOthcrObject) 45 :
(
46 : if (myValue < theOtherobject . myValue) return 48: li (myValue 49: return SO : else 51 : return 47 :
kIsSmaller ; > theOtherObject.myValue) kleLarger; kIsSame ;
52 :
53 : II Egy másik osztály, amit szintén a listában kívánunk tárolni II Ebben a láncolt listában is a korábban említett két met6dusra II lesz szakség : II Show : Kiírja az objektum értékét . 58 : II Compare: Osszehasonlítja két objektum értékét. és relatív .. podci6t ad vissza . 59 : 60: class Cat 61 : { 62 : public : 63 : Cat(int age} : myAge(age} (j 6 4: _Cat() 65 : { std : : cout « myAge « " éves macska tOrlése\n" ; 66 : 67 : 68 : i n t Compare (const Cat &) ; 69 : void Show() 70 : 71: std , : cou t « "Ez a macska': std : : cout « myAge « • éves\n": 72 : 7] : 74 : private: int myAgű ; 75: 76 : } ; 77: 78 , 54 : 55 : 56 : 57 :
1 23, óra • 79 : II A Compare met6dust arra használjuk , hogy egy adott objektum 80 : II listában elfoglalt helyét megállapits uk . 81 : B2 : B3 : B4: BS : 86 : 87 : 88 : 89 : 90:
int Cat : :Compare (const Cat
&
theotherCat )
if (myAge < theO therCat . my Age) return k IsSmaller ; i f ImyAge > theO therCat .myAge) return kIsLarger; else return kIsSame;
91: 92 : 93 :
94 : 95 : 96 : 97 : 98 : 99 : 100: 101: 102: 103: 104: 105: 106 : 107 : lOS : 109 : 110 : lll :
112: ll3:
II A liűta táro16objektumának absztrakt adattipusa II Minden származtatott tipusnak falűl kell defint~lnia
Sz Insert
.. és Show met6dusokat tcmplate class Node { public : Node () () virtual -Node I) I) virtual Node ~ InsertIT· theobject)zO; virtual void Showl) = O; private : };
ternp1ate class Interna lNode : public Node public : InternalNode(T * theObjcct , Node • next); -Interna1Nodel){ deleta n~Next; delete myObject ; virtual Node • InsertiT · theobject) ; virtual void Show ( ) (
114 : myObject - >Show(): 115 : myNex t->Showl); 116 : II delegálás 117 : private : US : T • rnyObject ; II Maga az objektum 119 : Node * myNex t ; II a lista kOvet kez6 t áro16e l emére muta t 120 : ) ; 121 : 122 : II A konstru ktor csak inicializál 123 : tcrnplate 124 : InternaINode:: InternalNode IT * theObject, Node * nex t) : 125 : rnyObjectltheübject),myNext(next) 126: ( 127 : ) 128 : 129 : II A lsita velej e 130 : II Amikor a l i s t ába új objektumot tes zü n k, 131 : II átadju k a táro16elemnek , amely ki ta l álja
470 11n, rész • Kiilönlegesség.k 132 : /1 a helyét. és beszúrj a a listába. 133 : templat c 134 : Node ~ InternalNode : : Insert(T * theObject) 135 :
136 : 137:
/1 Az új szerzet kisebb vagy nagyobb nálam? i nt rcsult = myOhject->Colnpare(*thcObjec t) ;
138 :
139 : 140 : 141 :
14 2 : 143: 144 : 145:
switch(result) { 1/ 11. tradició szerint, ha egyen16ek vagyunk, övé az els6bbség. case kisSame : / I {gy ez az ág átvezet arra az esetre, .. amikor nagyobb nálam case kisLarger: 1/ Az új objektum elém kerül ( IntcrnalNode<'I'> • ObjectNode =
146 :
new InternalNode(thcObject , t his):
147 :
return Objcc t Nodc l
1.48 :
149 : II Nagyobb nálam , tehát adju k át a kOvetkez6 táro16elcmnck 150 : 151 : case kisSmaller : 152 : myNext = rnyNext->Insert(thOObjcct) ; 153 : return this; 154: 155, return this; 156, 157: 158 , II A Tail tárolóelern csak őrszem szC!repet játszik 159 , template 160: class Tai1Node : public Node 161 : ( 162 : public : 163: TailNoda(){} 164 : virtuel -TailNode(){} 165 , virtual Node • Insert (T .. theObject) ; 166 : virtual void Show() { } 167 : private: 168 : }: 169 : 170 : II Ha az Object típusú objektum hozzám kerül, mindenképpen elém 171: /1 lesz beszúrva, hiszen én vagyok a zár6elem , .. aki IllOgOt t már semmi sincs. 172 : templat e 173 : Node • TailNode :,Insert( T .. LheObject} 174 :
175 : 176 : 177: 178 :
Interna1Node • ObjectNode = new InternalNode<'J'>(theObject, this); return ObjectNode;
179 : 180 , II A kezdd táro16elemben nincs objektum,
181 : 1/ csupán a lista 1ege ls6 el emére mutat 182 : templat e 183 : class HeadNode : publ ic Node
23. óra • Sablonok 471 184 : 185 : 186 : 187 : 188 , 189 : 190 : 191 : 192 : 193 : 194 , 195: 196: 197 , 198: 199: 200 : 201 , 202 : 203 : 204 , 205 : 206 : 207 , 208 : 209 , 210: 211 : 212 : 213 : 214 , 215 : 216 , 217 : 218 : 219 , 220 : 221 : 222 : 223 , 224 : 225 : 226 , 227 : 228 : 229 : 230 : 231 : 232 : 233 : 234 : 235 : 236 : 237 :
public: HeadNode(); virtual ~HeadNode() { delete myNext; } virtual Node * Insert(T * theObject) ; virtual void Show{) ( myNext~>Show() ; ) private: Node ~ myNcxt; }; / / Mihely 16trejön ti kezd6elem, / / az nyomban maga mögé teszi zár6elemct is template HeadNode: : HeadNodc() { !lIyN!:lxt : new TailNode ;
/1 A k ezd6elem el6tt nincs semmi, :így az objektumot II rögtön továbbadhatjuk a k övet kez6 táro16e l emnek . template Node * HeadNode :: Insert (T • theObject) ( myNext : myNext - >Insert(theObject) ; return this;
II Minden munkát delegálok, de enyém a dics6ség template class LinkedList
{ public : LinkedList(); -LinkedList() { deiete myHead ; } void Insert(T ~ theObject) ; void Showl\l1() ( myHead->Show() ; private : HeadNode * myHead ; }; II Születésemkor létrehozom a kezd6eleme t . a mely maga mögé II h e lyezi a zár6elemet . II Az üres lista tehát egy kezd6e lcmb6l áll . amelyet azonnal a /1 zár6elem követ , köztük pedig ninc s semmi . template LinkedList: :LinkcdList() ( myHead = new HeadNode ;
1/ oelegálj , delegálj, delegálj template void LinkedList :, Insert(T * pObject) {
472 1VI. rész • KilI6nl'llosségek 238 : 239 : 240:
myHead->Insert(pObject ) ;
241 : 1/ Tesztprogram 242:
243: 244: 245 : 246: 247 :
int main ()
• pCat; Data * pData; int val ;
C~t
Li nkedList
ListOfCats ;
248 : 2 4 9: 250: 251:
LinkedList ListOfOata:
252 253 254 255 256
for (;;)
: : : : :
1/ a felhaszná16t61
bekérűnk
pár értéket
1/ és elhelye2:zük azokat II l i stában { std :: cout« "Adj meg' egy értéket std :: cin »va l; i f (ival) break: pest ~ ncw Cat(val); pOata: new Data (val) ;
257 : 258: 259:
260: 261: 262: 263 :
(O vége):
";
ListOfCats.lnsert(pCat); ListOfOata.lnsert(pData):
264 : II végigmegyOnk a listán és kiírjuk az e l emek értékét . 265: std: : cout « ' \n ' ;
266: ListOfCats.ShowAll(); 267: std: :cout «
"\n",
268: ListOfData.ShowAll(); 269 : std :: cout « "\n ****,,***." •• \n\n"; 270: return O; II A listák kikerülnek a hatókOrb61 , és megsemmisülnek. 271:
Adj Adj Adj Adj Adj
meq meg egy meg egy meg egy meg egy Adj meg egy E, a macsk.!! a macska E, a macska a macska E, a macska
"'
"'
"""
értéket é'rtéket értéket értéket értéket értéket 2 éves 5 éves 7 éves 9 éves 13 éves
(o (o {o {o (o (o
vége) vége) vége} vége} vége) vége)
: : : : : :
5 13
2 9 7
O
Data objektum törlése, melynek é rt éke: 13 Dato objektum t ö r lése, melynek é rtéke : 9 Data objektum törlése, melynek értéke: 7
23. óra • Sablonok 1473 Data objektum törlése, melynek értéke: 5 Data objektum törl ése, melynek értéke : 2 13 éves macska t órlés e 9 éves macska t Or lése 7 éves macska törlése 5 éves macska t ö dés e 3 éves macska tOrlé se
Mindenekelőtt,
vegyük észre, mennyire hason lít a 23.1 Lista a 19. órán bemutatOllhoz.
A legnagyobb változás, hogy minden osztály- és metódusdcklaráci6 el6tt a következ6 preAx áll: Lempl ate c 1ass
Ebb6 1 ludja II fordító, hogy olyan típussal parnmétereztük a listál, amelyet c&1.k a példányosítás pillan:náhan fogunk megmondani. A lárol6elem Node osztályának deklarációja most például íh'Y néz ki: template class Node
Eszerint :1 Node osztály önmagábrtn nem létezik, de példányosítani fogjuk a Cats és a Data oszlállyaL A paramélCrczeshez használt típust T jelöli. Hasonlóail, az Interna lNode helyett most InternalNode áll (T típus InternalNode - ja). Továbbá, InternalNode nem egy Data objeklumra es egy másik Node-ra mutat, h.mem egy T típusú objektumra és egy Node objektumm. Ez lámató a 118. és a 11 9. sorban. Nezzük meg alaposabban a 133-156. sorban definiált Insert mel6duSl. A logika ugyanaz, de ahol korábban konkrét típust (Data) használrunk, Olt most T szerepel. Tehát, a 134. sorban a paraméter mulató egy T típusú objekrumra. Később, a példányosít{\skor, a fordító T helyére behelyettesíti II megfelel6 típust (Data és Cats). A lényeg, hogy az Internal Node az adattípustó függetlenül elvégzi a feladatát. Tudja, hogyan hasonlílSa össze az objektumokat. Egyáltalán nem érdekli, hogy a Cats objektumok pontosan úgy hasonlítják össze egymást, mint a Data objekrumok. Valójában, a Cats osztályt újraírhatjuk úgy, hogy a kort nem tárolja, hanem a születési id6ból szükség szerint kiszámítja. Az InternalNode-ól ez egy cseppet sem érdekli.
474 1VI. rész • Külön~g.sség.k
Sablontípus használata A sablontípus úgy használh:tt6, ahogy a többi típus. Átadható függvénynek paraméte rk~n\, de lehet vis.~zaté rési érték is, mindkét esetben énékkel vagy referenciávaL Ezt mutatja be a 23.2 Lista. Hasonlítsuk össze a 23.1 Listával!
23.21J1111- Pa_1Isto ~......... (16aCOIt- _ _ 6YoI1
f--.
l
1 : II 23 . 1 példa
2 : II 3 : /1 Paramétere2ctt lista bemutatása 4: / I
5 : II 6 : II 7, / I 8, / I 9: II 10, II 11: II 12, II 13 : /1 14 : II 15 : II 16 : II
Paraméterezctt lista objektum-orientált megva16s1tása. A List az absztrakt Node táro16elemnek delcgálja a munkát.
Háromf61c táro16elemet használunk: kczd6t. zár6t és kOzba!s6t. Csak ez utóbbiban tárolunk hasznos objektumot. A Data osztályt azért hozzuk létre , hogy példányait a listában tároljuk.
II 18 : II **** ************** ********* **.**** ****** -******
17 :
19 : 20 : 'inc1ude 21 : 22 ; enum { kIsSma1ler. kIsLarger , kIsSame} ; 23 : 24 : II A listában tárolható Data osztály 25 : II Minden olyan osztálynak, melyet ebben a listában szeretnénk .. tárolni , rendel kezni e kell a kOvetkez6 két metódussal , 26 : II Show : Kiírja az objektum értékét. 27 : II Compare: OsszehasonH tja két objektum értéké t, és meghatározza _ a relatív poziciót . 28 : class Data 29 :
30 , 31 , 32 : 33 : 3 4: 35 :
public : Data{int val),myValue{val){) -Data () ( std : : cout « 'Data objektum tOrlése, melynek értéke: '; std: : cout « myVa 1ue « '\n ' ;
36 :
37 : 38 ,
int Compare(const Dat a & ) ; void Show() ( std : : cout « myValue «
std : : endl ; )
-
- --
-
-
-
-
23. óra • Sablonok 1475 - - - -- -- --=-=---==""'-''----------' ..
39 : prlvate : 40 : int rnyValue ; 41 :
J;
42 :
43 : II A Compare met ódust arra hasz ná ljuk, hogy egy adott objektum 44 : II listában el f ogl al t helyét megá l l apit suk . 45 : i nt Da t a : : Compa re (const Data & t h e OtherObj ect ) 46 : 47 :
48 : 49: 50 : 51 : 52 : 53 :
{
i f (myVa l ue < theOthcrObject.rnyValuc)
return kIsSmaller ; if (myValue > theOtherObject . myValue) retu r n kIsLarger ; else return kIsSame; l
54 :
55 : 1/ Egy másik osztá l y , ami L szint én a listában kívánunk tárolni 56 : II Ebben él láncol t lisLában i s a korábba n említett ké t met6dusra 57 : II l esz
szű k ség :
58 : /1 Show: Kiírja az objektum értékét . 59 : /1 Compare : Összeha sonlítja két objektum értékét, ... és relatív poziciót ad vissza . 60 : 61 : class Cat 62 : ( 63: public : 64 : Cat(int age) , myAge(ag-e) {} 65 : -Cat() 66 : ( std : : cout « !nyl\ge « 67 :
r.~l ~:;~:<
' éves macs ka t Orlése\n" :
68 , 69 ,
int Compa r e (const Cat &.) : void Show()
70 ,
71 ,
72 , 73 ,
std : : cout « std : : cout «
" E'Z II macska • . myAge « • éves\n" ;
74 , } 75 : private : 76 : int myl\ge ; 77 : } ;
78 : 79 : 80 : II A Compa r e met6 dus t arra ha!:\z ná l juk, ho gy egy adot t 81 : II listában e lfogla l t h elyé t mcgá 11api tsuk . 82 : int Cat : : Compare (const Ca t & theOthe r Ca t)
83 :
{ if (myAge < theOtherCat.myAge) re t u rn kI ssmaller ; if (myAg e > theOtherCat.myAge ) r e t u r n ki sLarger ; else retu rn kis same :
84 :
85 : 86 : 87 : 88 : 89 : 90 :
91 :
}
obj ekt um
476 1VI. rész· Különlegességek 92 :
93 : II A lista tárol60bjektumának absztrakt adattipusa 94 : /I Minden szá rmaztatott tipusnak felal kell d efiniálnia .. a z Insert és Show metódusokat 95 : template 96 : class Node 97 : 98 : public : 99 : Node(){} 100 : virtual -Node () {} 101 : virtual Node * Insert{T theObjectl"'O; 102 : virtual void Show() '" O, 103: private : 104 : ), 105 : 106: template 1 07: class Int crnalNode : public Nodo 1 08 : 109 : public : 110: Interna lNode( T * theObj ect, Nodo * n e x t) ; 111: virtual -I n ternal Node () ( delete IllyNext ; delete myObject : l 112 : virtual Node * I nser t (T * theObjectl ; 113 : virtual vo i d Show () I I delegálás 114 : ( 115 : myObject->Show() , myNext->Show() : 116 : 117 : private : 118 : T * myObject ; II Maga az objektum 119 : Node * myNext : II a lista kOvetkez6 t6ro16clcmérc mutat 120: ) ; 121 : 122: II A konstruktor csak inicializál 123 : template 124 : InternalNode : : InternalNode (T * theObject, Node * next) : 125: myObjcct(theobject) .myNe x t(next) 126 : { 127 : } 128 : 129: II A lista veleje 130 : II Amikor a listába új objek tumot tes zün k , 131 : II átadj u k a táro16elemnek , amely kitalálja 132 : II a helyét . és beszúr ja a l iRt ába . 133 : template 134 : Node * I nternalNode :: Insert (T ~ th eO b jec t ) 135 : ( 136 : II Az új szerzet kisebb vagy nagyobb nálam? 137 : int rosul t '" myObject - >Compare( *theObject) ; 138 : 139 : switch{rcsu1t l 140 : { 141 : II A tradició szerint, ha egyen16ek vagyunk , 6vé az els6bbség. 142 : case ItlsSame : II ez az ág átve zet arra a z esetre, .. amikor nagyobb nálam
23. óra • Sablonok
146 : 147 :
case kIsLarger : /1 Az új obj ek tum e lém ke rul ( InternalNode * ObjectNode : new InternalNode (theübj ect, this) ; return ObjectNode;
148 : 149 :
/1 Nagyobb nálam. tehát adjuk át a következ6 táro16elemnek
143 :
144 : 145 :
150 :
case kIsSmal1er:
151 :
myNext = myNex t->Insert( t heübject) ; rcturn this ;
152 : 153 : 154 : 155 : 156 : 157 :
rcturn thic ;
158 , 159 : /1 A Tail táro l óe l em csa k úrszem szerepet játszik 160 : temp] ate
161:
class TuilNode : public Node
162 :
(
163 :
publ ic :
164 :
TailNode() (l
165 : vir t ual ~TailNode() (j 166 : virtual Node.:T> * Insert(T * theobject) ; 167 : virtual void Show { ) { } 168 : privata : 169 : ); 170: 171 : II Ha az Object tipu!'Iú objektum hozzám kerül, mindenképptm elém 172: II lesz beszúrva, hiszen én vagyok a zár6e1em, aki mOgOtt .. m6.r semmi sincs , 17] : temp1ate 174 , Node ~ Tai1Node :: Insert(T • thCObject) 175 : ( 176 : Interna1Node • ObjectNode ~ 177 : new Interna 1Node(thCObjec t, this) ; 178 : return ObjeCtNOde ; 179 : 180 : 181 : II A kezd6 táro16e1emben n incs ob j e k t um, 182 : II csupán a l i s t a lege1s6 e l emé re mu t a t 183 : t e mplate 18 4 : c l ass HeadNod e : p ub l ic No de<'l'> 185 : 186 : p ublic : 1 67 : Head Node{) ; 168 : virtual -HeadNode() { d e lete myNext; } 169 : virtual Node .. I nsert {T * t heübject) ; 190 : virtual void Show () ( myNe xt- >Show () ; } 191 : private : 192 : Node * myNe x t ; 193 : }; 194 ,
1477
I
478 VI. rész • KlIlönl.g.sség.k 195 : 1/ Mihelyt létrejön il kezd6 elelll, az nyomban 196 : 1/ maga mögé teszi zár6elemet is
197 : template 198 : HeadNode : : HeadNode (J 199:
(
200 :
myNext '" new TailNode;
201 : 202 : 203 : II A kezd6elem e16tt nincs semmi, igy az objektumot rOgtOn 204 : II továbbadhatjuk a következő táro16clemnek. 205 : template
206 : Node • HeadNode :: Insert(T " theObject) 207 , { 208 :
myNcxt ., myNcxt->Inscrt (thcObject) ;
209 : 210 :
return this ;
211 : 212 : II Minden munkát delegálok, de enyém a
213 : 214 : 215 :
template elauG LinkedLi ot
216 :
public :
217 : 218 :
LinkedList() ;
219 :
void Insert (T " theObject) ;
220: 221 : 222 : 223 : 224 :
void ShowAll() ( myHead->Show() ; private:
~LinkedList()
HeadNode
~
diCsőség
( delete myHead; )
myHead;
);
225: II SzOletésemkor létrehozom a kezd6elemet,
am~ly
226: II helyezi a zár6elemt. 227: II Az Ores lista tehát egy kezd6elemb6l áll . 228 : II záróelem kOvet. kOztOk pedig nincs semmi.
maga mOgé
am~lyct
229: templat~ 230 : LinkedList : : LinkedList() 231 : ( 232 : myHead =: new HeadNode ; 233 : 234 :
235 : /1 Oalogál j . delegálj , delegál j 236 : 237 : 238 : 239 : 240: 241 : 242 : 243 : 244 :
tcmplatc void LinkedList : : Insert( T * pObject) ( myHoad->Inser t (p Object) ;
void rnyFunction(Lin kedList& ListOfCats); void myOtherFunc tion (LinkedList< Data>& ListOfData);
245 : II Tesztprogram 246: 247 : 248 :
int main{) LinkedList
ListOf Cats ;
azonnal a
23. óra • Sablonok 479 249 : 250 :
Lin~edList
251 :
myFunction(Lis t OfCats): myOtherFunction(ListOfData);
252 :
ListOfData ;
253: 254 : 255:
256 : 257 :
256 : 259 : 260 :
II VégignézzOk a listát, és kiírjuk az elemek értékét. std : : cout « "\n" ; ListOfCats . ShowAll(); std : :cout« "\n" ; !>lsLOfData . ShowAll () : std::cout « "\ n *~**~**** .. *. \n\n"; return O; I I A listák kikerOlnek a hat6kOrb6l, és megsel1lllisülnek.
261 : 262 : 263 : void myFunction(LinkedList& ListOfCats) 264 : 265 : Cat • pCa t; 266 : int val; 267 : 268: 1/ A felhasznál6 adjon meg néhány értéket, 269 : II amit II listába teszünk 270: for l;:) 271: 272 :
273 : 274 : 275 : 276 : 211 : 218 : 219: 280: 281: 282 : 283 : 28-t : 285: 286: 287: 288: 289: 290 : 291 :
292 : 293: 294: 295 : 296: 297 : 298 : 299 :
{
std : :cout « " \nHány éves a macskád? (O vége): std: : cin » val; i f (Ivel) break: pCnt .. new Cat(val); ListOfCats . lnsert{pCat);
void myOtherFunction (LinkedT"ist& ListOfData) { Data .. pData ; int val; I I A felhasznál6 adjon meg néhány értéket, /1 amit II listába teszünk f or ( :; ) { std : : cout « 'Adj meg egy értéket (O vége) : ' . std : : cin » val; i f (!val) break ; pData = new Data{val); ListOfData . lnscrt(pData);
•
I
480 VI. ,ész' Külőnlegességek
Hány éves a macskád? Hány éves a macskád? Hány éves macskád? Hány éves a macskád? Hány éves a macskád?
,
(o (o (o (o (o
vége) v ége) vége) vége)
: 12 : 2 : 14 : 6
váge) : O
meg egy értéket (o vége) : 3 meg egy értékat (o vége) : 9 meg egy értéket 10 vége) : 1 meg egy értéket (o vége) : 5 meg egy értéket (o vége) : O
Adj Adj Adj Adj Adj
E,
a macska 2 évec E, a macska 6 éves
,
Ez macska 12 éves E, a macska 14 é ves LOrlése , melynek értéke : 9 Dat a ob jektum törlése , melynek é r té ke : 5
Da\.a objekt..um
Data objektum tOrlésc , melynek értéke : 3 objektum tOrlése , melynek értéke : 1 14 éves macska tOrlése 12 ével:! macska tOrlése Dl;ltu
6 éves macIJka tOrlése 2 éves macsk", tOrlésc
Ez a példa majdnem ugyanaz, mint az e16z6, azt leszámítva, hogyaLinkedList objeknunokat refcrendával adjuk át az azokal feldolgozó függvényeknek. Ez rendkívül fontos tulajdo nság. Miut:ín a listákat péld,ínyosítotluk, teljesen definiált típusú objckrumokként kezelhet6k: függvény paramétereként és visszmérési 6nékeként.
A szabványos sablon könyvtár A szabványos sablonkönyvtán (Standard Template Library - STL) a C+ + szabvány definiálja. Minden fordító , amelyről azt állílják, hogy szabványos, tartalmazza az STL-l . Az STL sablonnal megvalósílott tárol60sztályok, például tömbök, lisUík, sarok, vermek stb, továbbá számos közös algoritmus, például rcndez(:s, keresés stb. gytijteménye. Az STL célja az, hogy ezeknél a gyakran e l őforduló feladatoknál ne kelljen újra feltalálnunk a spanyolviaszt. Az STI.-t tesztelik és javítják, jó a teljesítménye, és nem utolsósorban ingyenes! Ennél is fontosabb, hogy az STI újrahasznosítható. Mihelyt megtanulluk és megértettük a használatát, minden programunkban alkalmazhatjuk, ahelyett, hogy újra és újra magunk megirnánk.
23.6ra • Sablonol1481 Mindig emlékezzünk rá, hogy a termelékenység és a az újrahasznosítás!
könnyű
karbantarthatóság kulcsa
Kérdések és válaszok Kerdés: Mié11 használjunk sablonl, amikor makróval is mego/dhatunk egyjeladatot? Válasz: A sablon!"... típuscllcn6r..:és vonatkozik, és integrált részét képezi a nyelvnek. Kérdés: Mi ti lu1 lő1lbség a paraméterezltetó jüggwllysabloll és a szokvtillyosjligsvény panl1néterei között? Válasz: Szokványos függvé ny (nem függvénysablon) előre megadott típusú paramétereken végez /Jniveleteket, A függvénysablon ellenben azl is lehet6vé leszi, hogya pammétereket paraméterezhessü k. Kérdés: M/I...'Or hasz náljunk sablol/t, és mikor öröklődést? Válasz: Használjunk s.1.blont akkor, ha a művel etek halmaza azonos, csak az oszclly másmás típusm alkalma zz.1. azokaL Ha azon k:-tpjuk m:lgunkat, hogy kóclot m:'isolunk, és csak egy-két tag típus.1t v:.\ltoztatjuk meg, ideje elgondolkodnunk a s.1.blon haszn:'i.lat1n .
Gyakorlatok A sablonok megismerése után pr6báljuk megv:.\laszolni, illetve megoldani az alábbi kérdéseket és feladatokat, hogy a tárgyban szerzett ismeretek megszilárdulj:mak.
Kvrz 1. Honnan tudja a fordító, hogy nem swkványOl:i osztályt, hanem sablont definiálunk? 2. A 22.1 példában szerepl6 láncoltlista-sablon honnan tudja, hogy az új e lemeket milyen sorrendben kelJ beszúrnia? 3. Hogyan cleklarálunk egy osztálysablonból objektumot, és hogya n jelezzük, milyen osztályt kell ehhez használnia? 4. Honnan tudja a fordító, mikor kell megsemmisítenie a listában tárolt objekmmokat?
482 1VI. rósz' K11lönl'g,ssóg,k
Feladatok 1. HasonlíL'iuk össze a 21.1 (parmlist . cpp) és 19. 1 (linkedlist . cpp) Hstákat! Észre fogjuk venni, hogy milyen sze mbetűnó a hasonlóság. 2. Ellenő rizzük a fordítónk dokumentáci6jában , hogy a szabványos sablonkönyvtár mely osztályai vannak me bJ\la16sítva! Borland fordító esetében, használja a súg6t (Help) a f6 menüben, majd válasszon témakört (Help Topics) és végül a bal oldali ablakban hivatkozást ( Reference)! 3. Hasonlít.'>a össze II 22.1 példa láncolt listájának sablonját a fordító dokumentáci6j{lban szereplő list STL-oszlállya\1 Keresse mcg a hasonlóságokat és a különbségeket!
Válaszok a kvfzkérdésekre l. A fordító a template prefIx miatt tudja , hogy sablont defil1i{llunk. 2. A ké rdéses obje ktumok Compare () metódlls:'\n:lk hívása :1 137. sorban ! :ílh~H6. A függvény é rtelemmel megtöltése a programozók, naz a mi feladatunk (vcgyük szemügyre a 44 . és a 81. sorban kezd6d6 filggvényekeÜ. Ha egy kutyákat rcprczentl'iló osztá lyt is szeretnénk :I listában tárolni, ahhoz is meg kelle ne írnunk az összehasonlító függvényt. 3. Nézzük meg a 22.1 LiSla 247. és 248. sorM A recept a következ(}: oablonnév sablonobjektumnév. 4. Amikor a main függvény visszatér, a láncolt lista elhagyja a l1at6 kört':t, így :IZ osztály destruktor.! lesz végrehajlv.l. Minthogy a táro lt elemeke t a lista dinamikusan hozta létre, a törlésük is a lista feladata. Ezt ot 23:1 Lista 110. soriiban láthatjuk .
24.
ÓRA
Kivételek, hibakezelés és néhány tanács Ebben az órában a következőkrőllesz szó: • Mik azok 1:1 kivételek • Hogyan használjuk a kivételeket, és használatuk milyen kérdéseket vel fe l • Hogyan í~llnk hibátlan kódot • Merre tovább
Programhibák. tévesztések. kódrnegromlás Könyvünk példaprogramjai illuszlrációs céllal születtek. Szándékosan kerültük a hibaforrások taglalását , hogy ne vonja el figyelmünker az éppen tárgyalt témáktóL Az éles programoknak azonban figyelniük kell a hibalehet6ségekrcj sőt, a valódi é l etből veU programkódok legnagyobb részét a hibákra való felkészülés és a hibakezelés teszi ki.
484 1VI. rész • Különlegességek Azt mondják, hogy ha a városok is úgy épülnének, mint a szoftverek, akkor az első arr.:!. járó harkály romba dömené a civilizációl. Mindenesetre tény, hogy szinte minden kereskedelmi program, köztük a legnagyobb gyártók termékei is tartalmaznak hibákat. Komoly programhibákat. Attól még, hogy ezt tudjuk, ez nincs így rendben. Bárkinek, aki komolyan programozásra adja a fejét, elsődleges fontossággal afnt kell törekednie, ho&'Y robusztus, hib;1 nélküli programokat írjon. Állítom, hogya szoftveripar legnagyobb és kiemelked6 problémáját a hibás, inswbil k6dok jelentik. Az mindenesetre tény, hogya program megírásának legkölcségesebb rés7.e a tesztelés, hibakeresés és a hibák kijavítása. Számos olyan apró hiba van, ami gondot okozhat egy progr.lIn futásakor. Az első a rossz logika: a program azt csinálja, amit a progr.HTloz6 szeretne; csakhogy (5 nem gondolta végig dég alaposan II mcgval6sítotl algoritmust. A második II szintaktika: rossz kifejezésm6dot, függvényt vagy struktúrát használtunk. Ez a két leggYllkoribb, és a legtöbb programozó ezek megt:ll~lására törekszik. Sokkal mvaszabbak azok :1 hibák, medyek csak akkor ütköznek ki, amikor a felhasználó valami váratlan dolgot múvel. Ezek a piciny, logikai tapos6aknák csendesen és zavartabnul meglapulhatnak. Amikor már úgy tűnik, minden rendben van, egyszer csak hirtelen - BUMM! - valaki rossz helyre lép, és progr.uTlunk megfeneklik. A kutatások és a gyakorlati tapasztalat egyaránt azt mutatjá k, hogya fejlesztés minél kés6bbi stádiumában kerül el6 egy hiba, annál drágább a kijavítása. A lego1csóbbak azok a problémák és hibák, amelyeket sikerül még a progmm megírásakor elkerülni. Csak alig drágábbak azok, melyeket a fordít6program kidob. A C++ szabvány afra tö· rekszik, hogy .1 fordít6progmmokb61 a lehet6 legtöbb hibajelcntést és fi gyelmeztetést kipréselje (még fordítási időben). A IcfordílOu programoknak azok a hibái sokkal olcsóbbak, amelyek rögtön az e l ső futtatáskor kideriiinek (amelyek mindig összeomlást okoznak), mim amelyek csak nag)' ritkán okoznak problémát. A logikai és szintaktikai hibáknál nagyobb gondot jelentenek az esetleges hibák; amikor például a programunk remekül múködik, ha számol ad meg a felhasznál 6, dc összeomlik, ha belűt ír be, Más progmrnok akkor fagynak le, ha kevésnek bizonyul a mem6ria, vagy ha kint felejtettilk a hajlékonyJemezl, esetleg ha a modem éppen bontja a vonalaI. A programozók az effajta törékenység ellen úgy küzdenek, hogy próbálják programjukat ~golyóállóvá ~ tenni. Golyóá1l6 az a progmm, amely minden futásidóben elképzelhet6 esetre fel van készítve, kezdve a bizarr felhasználói bemeneuől egészen a memóriahiányig. Ez a fajta programozás hasonlít a "defenzív vezelésre~, amely abból áll, hogy nem bízunk a másik sofőr (esetünkben felhasznál6 vagy rendszer) udvariasság:i-
_ __
_
_ _ _ _ _ _ _ _ _ _"'24"-"",6ra,,-0-"Kiv ..é...'."""I....~,.h""ib..,.k"eze=lé... s .., és"n..,éh..,é...nyL'...an...' ... csCJ
I,,48"''s--__--"""
ban, hanem azt feltételezzük, hogy bármilyen meglepá esemény is megtönénhet. Ha azonban elórelál6ak vagyunk és felkészülünk az effajta dolgokra, akkor elkerülhető az összeütközés. Fontos különbséget tenni, a progrdmhibák közön. Vannak ol}"dnok, amelyek abból adódnak, hogy a programozó szintaktikai hibát vételt. Vannak logikai hibák, amelyek onnan erednek, hogy a programozó félreértette a feladatot vagy a megoldás módját. Végül vannak olyan kivételek, melyek szokatlan (ám nem megjósolhatatlan) események miatt lépnek föl, mint például az erőforrások (memória vagy merevlemezhc\y) hiánya.
Váratlan események kezelése A programozók hatékony fordít6progr.lmoka t haszn:ílnak, és külö n böző biztosítékokka l rakják tele a kódot, amely Icbct6vé teszi a programozási hibák felfedezését. Újra és újra áuekintik a tervet, 6s kimcrít6 tesztelést folytaUlak, hogy megtaláljlik a logikai hibákat A kivételek azonb:m más jelleguek. A kivételes körülményeket nem lehet teljesen kiküszöbölni, csak felk(!szülni lehet clJuk, Felhasználóink számítógépein id6nként belelik amemória; ilyer,kor az il kérdi!s, hogy mit csináljon a program. Az alábbi lehet6ségek közül lehet váJaszt:mi: • Hagyjuk összeomlani a programot • Jelezzük a helyzetet il relhasználónak, és elegánsan lépünk ki • Jelezzük a helyzetet a relhasználónak, és lehetővé tesszük s7Jlmára, hogy megkísérclje a helyreállításI éS a folytatást • A progrnm maga javítja a problémáI, és tovább fut a felhasználó megzavarása nélkül Nem muszáj, S61 nem is kívánatos, hogy minden programunk automatikusan (!5 csendben maga küszö\)öljön ki minden váratlan helyzetet, az azonba n nyilvánvaló, hogy többet kell tennünk, mint hogy hagyjuk csak (Igy összeomlani a programot. A CH kivételkezelés típusbiztos és szervesen beépülő módszert biztosít arra, hogy megbirkózzunk a program fu tása közben fell épő ritka, de megjósolható eseményekkel.
Kivételek A C++ nyelvben a kivétel egy olyan objektum, amely a kód egyik részér61 a másikra adódik ál: a hibát kiváltó reszr61 a lubát kezelő részre. Amikor bekövetkezik a kivételes esemény, akkor azt mondjuk, hogy a program .továbbdobon egy kivételtH; a kivétel kezelését pedig .a kivétel elkapásának mondjuk. H
486 1VI. rész· Különlegességek A kivétel típusa dönti el, hogy melyik kódrészlet fogja kezelni a problémát; a továbbdobott objektum tartalma (ha van), visszajelzést nyújthat a fdh
Hogyan valósítsuk meg akivételkezelést? A try blokk segítségével körbe lehet venni a veszélyesnek ígérkez6 k6drészletet. Ebben a blokkban v5rjuk ti kivétel továbbdobás5t.. Például: try ( Ves~edelmesFüggvény();
}
A catch blokk közvetlenül a try blokk után következik; ebben kezelhet6ck a kivételek. Például: try ( valamiVes~élyesFüggvény{)
;
}
catchfOutOfHemory) (
II Akkor lép életbe, amikor a
ross~
memóriahelyzeten kell segíteni
)
catch(FileNotFound) (
II Akkor lép életbe. amikor nincs meg a keresett fájl )
A kivételkezelés
alapvető
lépései a következ6k:
1. Keressük meg a program azon részeit, amelyek olyan
műve l etet
melyben kivétel keletkezhet, és vegyük körbe try blokkal.
kezdenek,
24. óra • Kivételek. hiba kezelés és néhány tanács 1487 2. Hozzunk létre alkalmas catch blokko kat, hogy le.gyen hol elkapni a továbbdobotL kivé teleket; kezeljü k a mem6ria p roblémáka l és je1ezzünk vissza a felhasznál6nak, ha ez szüb;éges .
Apropó
Továbbdobás ós elkapás
Amikor kivétel keletkezik, a vezérlés azonnal az aktuális try blokkot blokkra kerül.
kö vető
catch
A 24 .1 Lista szemlélteti a try és a ca t ch blokkok haszná latát.
24.1 Usta - Kivétel továbbdobása (exceptions.cpp)
o:
/1 24 . 1 Lista Kivételek továbbdobása l : #include
2, 3 : const int DefaultSize " 10 ; 4, 5 : /1 definiáljuk a "kivételes" osztályt 6 : class xDoundary 7: ( 8 : public : 9 : xBoundary () {} 10: -xBoundary () () ll : privatc : 12 : ) ; 13 : 14 : class Array 15 : 16: public : 17 : II konstruktorok 18 : Array(int itsSize = DefaultSize) ; 19 : Array(const Arr ay &rhs) ; 20 : -Array () ( delete [ l p'J'ype ; ) 21 : 22 : II operátorok 23 : Array& operator=(const Array&); 24 : int& operator[) (int offS€:t) ; 25 : const int& operator[ ] (int offSe t ) const ; 26 :
27: 1/ hozzáféro ft1ggvények 28 : int Get it sSize() const { return it sSize ; J 29 :
30 : II barácf üggvé nye k
31 : fri end st.d : : os t. ream& operat.or« 32 : 33 : private : 34 : int. *pType ;
(std: : ostream& , c onst Array&) ;
488 1VI. rész • Kiilönlegességek 35 : int itsSize ; 36 : } ; 37 :
38 : 39 : Array " Array(int size} : 40 : it sSize (size ) 41 : (
42 : 43 : 44 : 45 : 46: 47 : 48 : 49 :
50 : 51 : 52 : 53 : 54 : 55 : 56 : 57 : 58 :
pType '" n e w i n t [ size) ; for (int i '" O; i
Array& Array :: operator= (const Array &rhs) { i f (this ;: &rhs) return · this ; delete [ 1 p'J'ype ; itsSizQ " rhs.GctitsSize() ; pTypc '" new int [ i tsSize) ; for (int i • O; i
59 :
60 : Array : :Array(const Array &rhs) 61 :
(
62 : 63 : 64 : 65 : 66 : 67 : 68 : 69 , 70 : 71 : 72 : 73 : 7 4: 75 :
itsSize " rhs . GetitsSize () ; pType = new jnt[itsSize] ; for (int i = O; i
int& Array : : operator[] (int offSet) ( int size = GetitsSize() ; i f (offSet >: O && offSet < size) r eturn pType[offSetl ; throw x Boundary() ; return pType[offSetl ; 1/ Az MSC lecsillapítására . ... A Borland figyelme ztetés t ad.
76 :
77 : 78 :
79 : const int& Array : : operator[] (int o ff Set) con s t 80 :
81 : 82 : 8] : 84 : 85 :
86 : 87 :
int mysi ze = GetitsSize () ; if (offSet >= O && offSet < mysize) return pTYPe[offSetl; throw xBoundary(); return pType[offSet ] ; /1 Az MSC lecsillapítására . ... A Borland figyelmeztetést ad.
24. óra • Kivételek, hibakezelés és néhány tanács 1489 8B : 89 , 90 : 91 : 92 : 9] , 94 : 95 : 96 : 97 : 98 ,
std : : ostr e am.& operator« (std , : Qstream& output , const Array& t heArray) { for ( int i = O; i
int main() Arr
99 : try
100 , 101 , 102 , 10] : 104 , 105 ,
{ for (int j = O; j< 100; j++l { intArray[jl = j ; std :: cout « 'intAr ray { ' « j « " l okay . «std , : endl ;
106 :
107 : 108 : cotch (xBou ndary) 109 : (
110 , 111 , 112: 11] : 114 :
std :: cout«
"A bemen6 adatot nem sikerült feldolgozn! . \n";
l std :: cout« return O;
"Kész.\n';
not intAr ray[O j okay ... intArray[l l okay . intArray [2 J okay .. . intArray[31 okay .. . intIIrray [4) okay .. . intArray[51 okay .. . intArray(6) okay .. . lntArray [7 l okay .. . intArray(8] o kay . . . intArray [910kay .. . intAr ray [ 10] okay .. . intArr8y(ll] okay . intArray[12] okay ... intArray[131 okay . intArray[14] okay . intArray[151 okay . intArray[16] okay . intArray[171 o kay .. . intArray[18] o kay . . . inLArray[191 okay . . . A bemen6 adatot nem sikerOll feldolgo zni . Kész .
I
490 ~. rész ' Killönl.g.sség.k
A 24.1 Listában egy lecsupaszított Array osztályt hozunk létre egy egyszeru kivt:tdkclelés bemutatására. A 6-12. sorban egy igen egyszenl kivétel osztályt deklarálunk (xBoundary). Figyeljük meg, hogy semmi, de senuni nem teszi ezt ;IZ osztályt speciálisan kivétel osztállyá. Voltaképpen bármilyen osztály, bánnilyen mel6dusokkaJ és (agváltozókkal vígan viselheti a kivétel szerepét. Pusztán atl61lesz ez kivétel oS7.tály, hogy eZI használjuk a továbbdobásnál (thr ow) a 74. sorban, és ez kerül e lk:lpásrJ. (catch) II 108. sorban. Az eltolási (offset) operátor akkor dobja tovább az xBound-,ry-t, amikor ,IZ osztá ly ügyfélprogramja a tömb határain kívüli elemet próbál elérn i (74. &s 84. sor). Ez messze
túlmutat <1Z0n, ahogy a normál tömbök kezelik az efféle k éré!';~ k ~t; 6k visszandnak bármi szemelet, ami ti hivatkozott mem6riacímen található. Ez alkalmasint garantáltan összeomlaszlj:1 :1 programol. A 99-107. sorban ta lálh ató a try utasításblokk, amelyben sz{tz egész számot próbálunk meg bevinni II 98. sorban dekl:.n1h LÖmblx:. A 108. sorban kezd6dik a catch blokk, melyben a kivételek el kapása
l~het6vé
v:í lik.
Apropó
try blokkol< A try blokkal különbözó ~veszélyes- utasitásokat fogunk közre, kezdve a try kulcssz6val, és körbevéve kapcsos zárójelekkel. például Igy: tn' (
Filggvény ( ) ;
l
catch blokkol< A catch blokkban utasítások helyezkednek el, kezdve a catch kulcssz6val és a bezárójelezett kivételtipussaI. valamint körbevéve kapcsos zárójelekkel, például Igy: try (
r' ü gg v ény () ;
l
catch (Ou t OfMemory ) (
II védelmi tevéke nység
24. óra • Kivételek, hibakezelés és néhány tanács 1491
--------------------~~~==~==~~~==~-~------,--
A try és catch blokkok használata A kivételek használatának legnehezebb része valószínűleg annak kitaláJása, hogy hová is tegyük a try blokkot. Nem mindig nyilvánvaló, hogy mi válthat ki kivételeket. A következő kérdés az, hogy hol kapjuk el a kivételeket. Elképzelhető , hogy olt lenne jó továbbdobni a mem6riagondokat okozó kivételeket, ahol amemória lefoglalása történik, míg az elkapásuk a program egy későbbi részén lenne ideális, ahol a felhasználói felülettel foglalkozunk. Erdemes ott elintézni a try blokkokat, ahol a mem6ria és más erőforrások lefoglalása, használata zajl ik Más dolog a lömbhatárok kezelése, a rossz bemenő adatok elbíráL.lsa stb.
A kivételek elkapása A következő módon za jlik a kivételek elkapása: a program a kivétel dobása után megvizsgálja a hívási vermet. Ez nem 111.1s, mint az adott pillanatig meghívoll rüggvények időre ndi listája. A hívási verem tárolja a végrehajtás útvonal{tt is. Ha a main () meghívja az Allat : : KedvencÉteltSzerez ( ) függvényt, majd a KedvencÉteltSzerez () meghívja az Allat ; : SajátBeállitások() -at, amely a következő lépésben az fstream: : operator » ( ) -t hívja meg, akkor mindez eltárolódik a veremben. Egy rekurzív függvényt több példányban is megtalálunk a veremben. A kivételt a hívási verem az összes aktuális blokkban érvényesíti. Ahogy a veremben levő függvények soriit felgombolyít juk, a veremben lévő helyi objektumok destnlktorai érvénybe lépnek, és az objektumok megsemmisülnek. Mi nden try blokk után van egy vagy több catc h utasítás. Ha a kivétel ilJcs:.:kedik valamelyik ca tch utasításra, akkor az ahhoz tartozó utasítÍlsblokk végrehajtásÍll tekinti a program a kivétel kezelésének. Ha egyikre sem illeszkedik, akkor tov{lbb folytatódik a verem felgöngyölítése. Ha a görgetés elér a főprogram (main) legelejére, és a kivétel még mindig nincs elkapva, akkor a t erminat e () (befejezés) függvény hívódik meg, ez pedig az abort () meghívásával vet véget a program futásának. Fontos tudni, hogy a kivételek fe\gombo lyítása egyirányú folyamat. Előrehaladásakor a verem objektumai febzámolód nak, és ebbő l nincs visszaút; miután sikeresen kezeltük a kivételt, a program a kivételt kezelő catch utasításhoz tartozó try utasításblokk utÍln folytatja a végrehajtást.
492 1VI. rész· Különlegességek. így a 24.1 listában a végrehajtás a 11 1. sorban fo lytatódik, a mely az els6 sor az xBoundary-t kezeld catch utasításhoz tartozó try utasításblokk után. Emlé ke zzünk csak vissza: a kivéte l ke letke zése után a vezé rlés ne m a kivétel továbbdo bása utáni po ntról folytatódik , hanem a catch blokk után.
Több catch megadása Le hetséges tö bb catch feltételt is megadni; ez eselben a megfeleld prog ramrészek úgy sorako znak egymás utá n, mint a switch utasítáS elágazásai. Az alapértelme zeu (default) e lágazásnak, vagyis a . kapj el minde nt" eselne k a catch( . . . ) fe le l meg.
Flgyeleml
Vigyázatl
Érdemes nagyon meggondolni, hogy egymás után sorakoztassunk-e fel két catch utas ítást, melyben az egyik egy myExeption bázisosztályhoz, a másik pedig ennek specializált, származtatott osztályához kapcsolódik (mint például mySpecificExeption). A végrehajtás mindkét utasításblokkba be fog lépni. Elké pze lh ető, hogy bizonyos esetekben pont ez a szándékunk, de mindez nem várt következményekkel is járhat. Jobb e lővigyázatos na k lenni.
Hivatkozás elkapása - többrétílség akivételkezelésben Kihasználhatjuk a kivé tele k a zo n tu lajdonságát, hogy 6 k tulajdo nképpe n többré t{íen ke ze lhe l6 osztályo k. A kivé te l cím sze rinti átadásávaJ haszmílbalu nk egy örö kl6dési hierarchiát is a futá si id6ben ke letkezett kivételtípus Icgmegfe le l6 bb kezeléshe. A 24 .2 listában töb bré t(l"en kezeljük a kivéte le ket. A kimene lta núsága szerint háro mszor fllt~ [a ttuk II progl"J.mo t: egyszer egy S elemű , másodszor egy SOOOO e l e mű, harmadszor ped ig egy 12 e le mG tö mbbel do lgozrunk.
24.2 Ulla - Többrótll kivót8lkozelés (pulya.coptions.cpp) 0 , II 24.2 Lista II A kivéte l ek tObbrétG elkapása 1 , #include
2, 3 , const int DefaultSize = 1 0 ;
4, 5 : II A kivételosztályok de f ini álása 6 : class xBoundary (l ; 7,
,, 8,
10 , ll : 12 , 13 : 14 :
class xSize
public : xSize(int size) ,itsS ize(sizel (l - xSize() (l virtual int GetSize () ( return itsSize ; v irtua l void PrintError ( )
24. 6ra • Kivételek. hibakezelés és néhány tanács ( 493 - - - - - - - - - - - - ="""-'-""="-""'=""'-'''-'''''''''''-=''''-'''''-----: .... 15 : 16 : 17 : 18 :
19 : 20 : 21 : 22 : 23 :
24 : 25 :
26 : 27 : 28 :
( std : : cout « 'Size error . Received : « its Size « std : , endl ; ) protected : int it sSize; l; class xTooBig : pub l ic xSize public : xTooBig(int size) , xSJ.ze(size) (j virtual voi d PrintError() { std : : cout « "Too big! Received : "; std : : cout « xSize : : itsSize « std , : endl;
29 : 30 :
);
31 : 32 : 33 : 34: 35 :
36 :
class x TooSmal l public xSi ze ( public : x'I'ooSmall(int size) : xSize(size} () virtual void PrintError()
37 :
(
38 :
std : : cout « "Too small! Received : ' ; std :: cout «xSize::itsSlze« std :: endl ;
39 :
40 : 41 ,
);
42 : 43 : 44 :
45, 46 : 47: 48 : 49 :
50 : 51 : 52 :
53: 54 : 55 : 56 : 57 : 58 :
class xZero public xTooSma l l ( public : xZc r o( int size) , x TooSmal1{size) {} virtual void PrintError() { std : : cout « ·Zero!! . Received : "; std : : cout « xSize : :itsSizc « std : :cndl; );
class xNegative
public xSize
public : xNegativ e(i n t size ) :xSizc(si z e) (J v i r t ual v o i d PrintError()
59 :
{
60: 61 : 62 :
s t d : : cout « std :: cout«
63 :
};
64 : 65 : 66 :
class Array
"Negativel Rece ived : " ; xSize :: itsSi ze « std :: endl;
494 1 ~ ...sz • Kü~nleg,sségek 67, 68 : 69 : 70 : 71 : 72 : 73 : 74 : 75: 76 : 77 :
public: II Konstruktorok
Array{int itsSize " DefaultSize); Array{const. Array &rhs) ; -Array () ( delete [l pType;)
1/ Operátorok Array& operator= (const Array&) ; int& operator[) (int offSe t ) ; const. int& operator[) (int offSet) const ;
78 : /1 Hozzáfér6 fÜ9gvények 79 : int GetitsSize() const { rcturn itsSize; } 80 : 81 : /1 Barátfüggvényck
82 : friend std : : osLrea.m& operator« 83 :
(std : : ostream&, const Array&);
84 :
85 :
86: 87 : 88:
private : int *pType: int itsSize; };
89: 90:
Array::Array(int. size} :
91: 92:
itsSize{size) { i f (size -- O) throw xZero(size) :
93 :
94 : 95 : 96 : 97 : 98 : 99 : 100: 101 :
i f (size < O) throw xNegative(size) ; i f (size < 10) throw xTooSmal1(size);
102: i f (si%e :> 30000) 103 : throw xTooBig(size); 104 : 105 : pTypa : new int [size] ; 106 : for (int i • O; i= O && offset < size) 114 : return pType[of fset); 115 : throw xBoundary(); 116 : return pType[offsetJ: II Az MSC boldogítására van így ; .. a Borland figyelmezte t 117 : 118 :
__________________________~24~.~6~~·~KW~~~.I.~k~.h~ib~.~k.~m~lé~s~és~n~é~hj~~Lm~ná~cs~I~4~95~____~., 119 : const int& Array : : operator!)
(int offset) const
120 : 121 : int si ze = GetitsSize (): 122 : if (of fs et >= O && offset < si ze)
123 : return pType{offsetJ; 124 : throw xBoundary{); 125 : return pType[offset): 1/ Az MSC boldogitúsára van igy: ... a Borland figyelmeztet
126 :
127 : 128 :
int main{)
129 : 130 : 131 :
try (
132 : 133 :
lnt choice ; std : : cout «
134 : 135 : 136 :
std : : cin »
Array intArray (choice) l for (int j = O; j< 100;
137 :
(
138 : 139 : 140 : Hl : 142 :
catch (xBoundary)
144 : 145 : 146 : 147 :
(
-
std: : cout «
"i
jH)
int.ll.rray[jl '" j: std : : cout « • iotArra}' [ . « « std : : endl;
143 ,
148 : 149 : 150 : 151 : 152 : 153 : 154 : 155 : 156 : 157 : 158 :
'Enter the array size : choi.ce;
j
'Unable to process your input!\n" ;
catch (xSize& t heEx cepLion) ( thcException.PrintError(); catch ( ... ) { std :: cout « 'Something went wrong,' « 'but I've no idea what!' «std : : end1 ; std : : cout « return O;
'Done . \n ';
Enter the array size : S Too sma11! Received : S Done . Enter the array size : 50000 Too big ! Received : 50000 Done. Enter the array size : 12 intArray[O] okay .. . intArray [l] okay .. .
496 1VI. rész • Különlegességek intArray[2] okay . intArray[31 okay .. . intArray[4] okay .. . intArray[51 okay .. . intArray[6] okay .. . intArray [710kay .. . intArray[BI okay .. . intArray [910kay .. . int Array [10] okay .. . intArray[ll] okay .. . Unoble to process your input! Done .
A 24.2 Listában az xSize osztályb
Hogyan írjunk professzionális
minőségű
programokat?
A sablonokkaJ és kivétcJekkel felvértezve immár jól mega lapozott, profi C+ + eszköztárral rendelkezünk. Mielőtt a legközelebbi sarokba hajítanánk a könyvet, tekintsünk még át néhány dolgot a professzioná lis m in őségű progmmok írása érdekében. Mihelyt a kedves olvasó már nemcsak kedvtel ésbő l progmmozik, hanem egy fejieszt6 csapat tagja lesz, olyan módon kell megírni a kódot, hogy az ne épp csak mt1ködjön, hanem mások számám is érLheL6nek kell lennie. A megrendelő kéréscinek megfelelően a kódot leglöbbszőr a sze rzőnek kell karbantartania és módosítan ia, majd a későbbiekben esetleg másnak is, ha a szerző elhagyja a projektet
I
24. óra • Kivétele~ hib.kezelés és néhény tanác. 497 Igazából nem túl lényeges, milyen kódolási stílust választunk, csak az fontos, hogy ez legyen következetes. A következetes stílus segít kilalálni, mi voll a progf'dffiOz6 célja az adott résszel , és megszabadít az olyan id6igényes tevékenységektől , hogy például azt kelljen keresgélni, hogy mennyire volt nagybetűs a múltkor valamelyik függvény. Az in következő önkényes, de jól múköd6 irányvonalak olyan a projekteken alapulnak, melyekben magam is dolgoztam. Minde n programozó kialakíthatja persze a sajátját stílusát - ezeket tekinLSünk elindulásnak. Ahogy Emerson mondja, ~a rend a kis elmék támasza ft, a kód következetes stílusa azé n nem egy rossz dolog. Nagyban megkönnyíli a progrdffiozó &s munkatársai életét, ha egy csoport közmegegyezéseit betartják. Ez nem jelenti azt, hogy a stílust teljesen rögzíteni kell , his".en az új ötle te k és a továbbfejlesztések menet közben születnek, de a következetesség minclcnki számára megkönnyíti a közös munkát. Fontos megé rteni, hogy többféle kü lönböz6 stílus van, és hogy kemény elle nállást válthat ki az alábbi iránymutatás né hány pontja. Ne feledjük , hogy ezek alka lmazása csak egy l e hetőség.
Zárójelek A zárójelek igazítása az egyik legellentmondásosabb téma, ami szembeállítja a C és C++ progm moz6kat. Én az alábbiakat javaslom: • Az összetartoz6 zárójelek legyene k fOgg6legesen egymás alatt. • A definíciók és deklarációk legküls6 zárójelpárja legyen a bal ma rg6nál. A bels6 utasításoknál használjunk beljebb kezdést. A belső záfÓjelpárok igazodjanak a megfelel6 f6 utasításhoz. • Az igazán hosszú utasításblo kkok eselén tegyünk egy megjegyzést a zá r6 zár6jeihez, hogy könn)'ll legye n azonosítani a blokk célját. Ha ránézünk egy zjr6 zárójeire, és nem látjuk, melyikhez tartozik - ez már egy "igazán hosszú utasításblokk ~. Például: if (feltétel~- true) (
II renge t eg sor , kóztük akár más utasitásblokkokk II renge t eg sor , kö ztük akár más utasitásbl okkokk /1 renge t eg sor, kó ztük a kár más utasitásblokkokk
) II i f (feltétel"", tr ue)
Ne írjunk a zár6jel sorába más kódrészletet. Például: if (feltétel==true) j
,. k ;
ValamiFüggvény() : m++;
/"2·4' .,.
"
,
: <
"
,
.
I.
.y, '
.. .
y
.,.. -.
.'
498 1 ~, rósz • Különl'g...ég,k
Hosszú sorok Úgy írjuk meg a programk6d sorait, hogy ne 16gjanak lül a sorok a képcrny6 egyszerre látható t3rtományán. A kilógó sorrészekel könnyt'i szem elől téveszteni, és bosszantó, ha víz.';zinlesen kell görgeulünk a szövegel. Sortörés esetén kezdjük beljebb az új SOlt. Törekedjünk ésszeru töréspontokr.a, és lehet61eg hagyjunk az eredeti sor végén valamilyen műveleti jelet (ahelyett, hOh'Y az új sor elejét kezdenénk vele), hogy világosan látsszon, hogy annak a sornak még folytat6Unia kell, és még nincs vége az adott ponton. A CH függvények rövidebbek C nyelV11 társaiknál, dc II régi jótanács itt is megállja a helyét: törekedjünk egyetlen képerny6n elfér6 fi.lggvények írására. A tabulátorok három vagy négy szóköznyi mérel(iek legyenek. Győződjünk meg róla, hogy szerkesztóprogramunk valóban így alakítja-e át II tabulátorokat.
A switch utasftások Az elágazásokat azonos
Ví7~<;zinles
pozíciókban érdemes megtartani:
switch(variablcl (
case ValueOne: l\ctionOne{) ; break; case ValueTwo: ActionTWo() ; break; default : assert("wd Action"); break ;
A program szövege Álljon itt néhány jótanács, hogy olvashatóbb legyen a kód, Könnyebb a kód karbantartása, ha az alábbiakat megtartjlIk: • Használjunk bátmn térköz karaktereket az olvashatóság elósegítéshe. • Az objektumok és tömbök egy dologra vonatkoznak; ne tegyiink sz6közöker az objektumokra utaló operátorok közé ( . ,->, [l). • Az egyoperandusú operátorok közvetlenül az operandushoz kapcsolódnak, így nem kell közéjük szóközt tenni. Tegyük a szóközt az opedtorok másik oldaláf3. Az egyoperandusú operátorok a ! , _, H , - -, -, * (mutatók), & üípusátalakításl és a sizeof .
24. óra • Kivételek, hibakezelés és néhány tanács 1499 •
A kélOpemndusú operátorok mindkét oldalára tegyünk szóközt: +, =, .. , I, %, », «,<,>,==, ["'.&, 1,&&, 11,? : ,=,+=stb. • Ne akarjuk a szóköz hiányával kifejezni a műveleti sorrendet (4+ 3 *2). • A vesszők és pontosvessz6k után (és ne elé) tegyük a sz6közl. • A zár6jelekhez (belül) ne írjunk sz6közt. • A kulcsszavakat, mim például az if, válasszuk el sz6közzel: i f (a == bl. • A megjegyzést határoljuk el egy sz6közzel a II jeltől. • írjuk a mutat6 és hivatkozás opcrátorokat közvetlenül a típusnév után, ne pedig a változ6névhez. igy legyen: char" foo; int& the Int ;
és ne így: char .. foo; int &thclnt;
Azonosrt6nevek Néhány ötlet az azonosít6nevek célszení használatár61: Válasszunk hosszú azonosít6neveket, hogy kell ően kifejez6ek legyenek. . Kerüljük a rejtélY($ rövidítéseket. Szánjunk kellő időt és energiílt a megfe l e l ő szövegezésre. Rövid változ6neveket (i, p, x stb.) csak Olt használjunk, ahol a rövidst:g o lvashat6bbá teszi a kódot, és ahol olyan nyilvánvaló a szerepük, hogy felesleges lenne őket megmagyarázni. • NL azonosít6név hossza legyen arányos a hat6körével • NL azonosít6nevek különbözzenek kellő mértékben egymástól leírva és kiejtve is, hogy ne lehessen 6 ket könnyen összekeverni. • A függvények (és mel6dusok) nevei legyenek igék vagy igéből képzett főnevek: Search (), Keres (), Reset () , Visszaállít () , FindParagraph ( ), BekezdésKeresés () , ShowCursor ( ), KurzortMutat () . A változ6nevek legyenek elvont f6nevek, lehet6leg egy másik f6névvel e!:,'Yütl, példáu! count, dbszám, state,állapot,windSpeed, szélSebesség,windowHeight, abl akMagasság. • • • •
A nevek belÚZése és a nagybetúk kérdése A betűzés és a nagybetúk kérdései majd rögzü!nek, amikor a kedves olvasó rátalál saját stílusára. Néhány ötlet következik ebben a témában: •
Érdemes az azonosít6k neveit következetes módon megválasztani; használjunk felváltva kis- és nagybetűket, ahogy az alkalmas. A függvénynevek , met6dusok,
I
500 VI. rész · Különlegességek osztályok, a t ypedef és struct nevek kezd6djenek na~,'ybetúkkel (ez[ gyakran Pascal Stílusnak hívják, mint például MyFunction). Az adattagok vagy helyi változók nevei kezdódhetnek kisbetűkkel (ezLgyakran Camel Stílusnak hívják, mint például myVariable) • A felsorolásos típus ko n:"itansait néhány olyan kisbetűvel érdemes kezdeni, ami az enum név rövidítése (például ts a TextStyle helyetú: enum TextStyle (
tsPlain. tsBold, tsItalic,
tsUnderscore, )
,
Megjegyzések A megjegyz6sek nagyban megkönnyíthetik a kód megértését El6fordulhat, hogy a programozó hetekig-hónapokig nem dolgozik egy progl"d.mmal, esetleg nagyobb fontosságú projektekkel van elfoglalva. Ez idei alatt könnyen feledésbe merillhet egy-egy kódrészlet szerepe. Ha másnak kell majd foglalkoznia progl"amunkkal , akkor még nagyobb nehézségel jelenthet egy-egy részlet megénése. A jól átgondo lt, következetes stílusú megjegyzések írása megéri a befektetett energiát. Néhány ötlet a megjegyzésekre vonatkozóan: • Aho l csak lehet, haszná ljunk inkább II meg jegyzéseket, semmint / * * I stílusú:lkat. • A magas szintű megjegyzések nagyságrendekkel fontosabbak , mint a feldolgozás részletei. Programunk tényleges értéké! növeljük a megjegyzésekkel, ne csak a méretét. Ennek például semmi értelme: n ++ ; II n értékét eggye l megnövelj\1k
Ez nem éri meg a rászánt időt. Inkább a függvényck és kódrészletek értelmét kell kiemelni. Fogalmazzuk meg emberi nyelven, mit csinál egy függvény. Jelezzük a mellékhatásokat, a paraméterek és a visszatérési érték típusát. Jegyezzük fel feltételezéseink et (vagy amil nem várunk), példi'iul: . n feltehet6leg nem negatív", vagy .- l-et ad vissza, ha x élVénytelen". Összetett logi kai szerkezetben jelölhetjük az adott kódrészletben érvényes feltételezésünket. • Használjunk teljes mo ndatokat, megfel e l ő közponrozással. Meghálálja magát az erre szánt idő. Ne legyünk rejtélyesek, ne használjunk rövidítéseket. Ami a kód írása közben napnál világosabbnak l11n ik, az néhány hónapon belül arcpirítóan ködösnek és érthetetlen nek fog látszani.
24. óra • Kivételek, hibakezelés és néhány tanács 1501
• A program, a függvények és a fej lécállományok elejére írjunk összefoglal6 megjegyzéseket, amelyból kiderül az adott modul szerepe, bemeneti és kimeneti jellemz6i, paraméterei, az eredeti szerz6je és mindenféle változás (szerz6vel és dálummal). • Nyugodt szívvel használjunk elegend6 üres sort, ha ez segíti az olvasóI annak megértésében , hogy mi is történik. Különítsük el a logikailag összetartozó k6drészleteket.
Hozzáférés Érdemes következetesen kezelni .t progmm e~>'Ycs részeihez tartoz6 hov..áfér&si szinteket. • Mindig írjuk kl a public, protected és private kulcsszavakat; ne támaszkodjunk az alapértelmezések használatára. • El6ször soroljuk fel a nyilvános adatokat, majd a védetteket, végül a priv{tl változókat. Minden csoportban a metódusok után sorakozzanak az adattagok. • A megfelel6 k6dr6szben először álljanak a konstnlktorok, majd a destmktorok. Az azonos nevt1 túlterhelt függvények egymás után következzenek. Egy CSOIJOltban soroljuk föl a hozzáfér6 függvé nyeket, ha csak lehet. • Fontoljuk meg a mel6dusok és az adauagok csoportonként tőrt(:n 6 névsorba rendezését. A beemelend6 (íf/ duda) állomá nyneveket azonb,m mindenképpen névsorba n soroljuk fel. • Bár a túlterhelés esetén a virtua l kulcsszó használata ne m kötelez6, mégis érdemes h:lsználni. Ez segít emlékezni annak virtuális szerepére, és a deklar.'ici6 is következetes rnal"'.ld.
Osztálydefinfci6k Törekedjünk arra, hogya metódusok definídóinak sorrendje egyezzen meg a deklarációk sorrendjével; így könnyebb ők et megtalálni. A függvénye k clefinícióinál a visszatérési típust és az egyéb módosítószókat helyezzük cl egy külön ,~orban, hogya függvények és osztályok nevei a bal margón kezd6dhessenek. Így sokkal könnyebb megta lálni egy-egy keresett függvényt.
Beemelendó állományok Amennyire csak lehel.Séges, kerüljük a fej lécállományokba való állomány-beemelésl. A legjobb minimális választás az, ha csak annak az osztálynak a fej lécállomá nyát emeljük be, amelyb61 az aktuális osztály származik. Kötelező még beemelni azon objeklumokét, amelyek a deklar.ílt osztály tagjai. Az olyan osztályok esetében, melyekre csak mutató vagy hivatkozás irányul, elegendő a megfelelő típusú hivatkozás.
502 1VI. rész • Különlegességek Ne felejtkezzünk el semelyik szükséges fejlécállományról azzal a feltételezéssel, hogy egy másik . cpp állománynak majd úgyis szüksége lesz rá, abba majd bizonyára beemel6dik.
Figyelem!
Vigyázzunk a fejléc.kre
Minden beemelendö (include) állomány elejére írjunk beemelési tanácsokat.
assertO Haszná ljuk báln:tn az assert () utasítást. Segít a hibák megkeresésében is, de amiatt is hasznos, mert a küJs6 szemlé16 számára is világosabban látszanak a programozó feltevé.~ei, valamint az, hogy mit tarl a szerző érvényesnek 6s mil érvénytelennek.
const llaszná!juk a const kulcssz6t, ahol csak lehet. Paraméterekre, váll0z6kra és met6d~I Egy fOggv6nyb61 gyaknln szükség van konstans és r,em konstans változalra; nc spóroljlLk mcg egyiket sem, ha ténylegesen kell mindkett6. I.együnk m.gyon körültekintl5ek , amikor konstansból n(;!m konstanssá történő típusfltabkítást végzünk (vagy fordítva). Vannak helyz.etek, amikor csak ez jelent megoldást, de &'Yőz6djil nk mcg egészen biztosan arr61, hogy van értelme a dolognak , és lássuk cl megjegyzl!:sscl is. sokl'~1.
További források Keml!:ny munka áll az olvasó mögött; és ezen információk birtokában már alkalmas C++ programoz6nak tekintheti magM - ezzel együLL még nem tek inthető befejezennek a tanulási folyamat. Még rengeteg mindent el lehel sajátítani, és még sok könyvet végig kell rágni, amíg a kezdő C++ progmmoz6b61 professzionális lesz.
Hol kapunk segftséget és tanácsokat C++ progmmozóként a legels6 teendő bekapcsolódni egy vagy több C++ konferencia online szolgáltatásába. Ezekben a csoportokban közvetlenül el lehet érni C++ progf".Jmoz6k százait~ezreit , akik válaszoJbatnak kérdéseinkre, tanácsOl adhamak, és esetleges ötleteinknek mélyebb alapot biztosítanak. Vannak megfelelő levelezőlisták és internetes megbcs z6lő f6nullok is, valamint értékes hírcsoportok (például comp.std.C++ és társaik).
24. óra • JGv,tel,k, hib.k",I's és néhány tanács 1503
Ajánlott olvasmányok Tömémelen sok jó C++ (és általános programozási) könyv ];ÍloLt napvilágol. Néhány ezek közül: •
SCQ1T MI!YERS: Effectil.'e C+ + (Addison-Wesley, ISBN: 0201924889, 1997). Második
kiadás. • MJCKEY
et aL: C++ UlIlcashedCSams, ISBN: 0672312417, 1999). A kifinomult C++ technikákhoz. • JESSI! LUlF.R1Y: Clouds To Code (\YIrox Press, ISBN: 1861000952, 1997). Betekintést
•
WIU1AMS
nyerhetünk egy valódi alkalmazásba, melyet C++ nyelven írtak, objektumközponlú elemzéssel és tervezésseL JESSE LIBERlY Cl al.: SC/ms Tcacll YO/lrseIfC++ for liNUX ill 2 / Days(JSBN: 06723 18954, 2000). További tudnivalók a C++-ról és a progrJ.!l1ozásr61 Linux
környezetben. Az alábbi forrásokat is jó szívvel ajánlom: BJAJU-';Il STROUSTII.UI'; nu.! C++ Prosramming LallgL/age(Addison-\'(lesley, ISBN: 0201700735,2000); magyarul a Kiskapu Kiad6 gondoz<'ísáb:tn: A C++ programozási Ilyc//JOSBN: 963930]1 75, 2001). • BJUAN W. K.1!1t.\lIGHAN és P.). PlI\UGER: 711C Elements of Programmillg SI)'le (McGraw-Hill, ISBN: 0070342075, 1988) • STEVI! OUALLlNE: C Elements of Slyle: 771C Pl"Ogrammer's Slyle Mawull fal" ElegaJlI C (fnel c ++ Programs (Hl.lngl)' Minds, ISBN: 1558512918, 1992) • DONALD E. KNUTH: 17IC Art of Computer Programmillg (3 M /e/es sorozat): Fllndamelllal Algorilhms, Samillllmen"cal A/gor/tllms, és SOl1ing a nd Saarcllill/5 CAddison-Wcsley, ISBN: 0201485419, 1998) • 711C Praclice aj Prog rammillg by Briall \17. Kernighal1 and Rob Pil..'e(AddisonWcsley, ISBN: 02016] ,86X, 1999) • BRIAN W. KERl'ilGHAN és P.J. Pl.AUGER: Software Tools(Addison-Wesley, ISBN: 020103669X, 1976) • ClC++ Usersjoumal Chavi!ap), http://www.cuj.com • Végü l talán haszonnal forgaljákJpssE LmERTY: 711C Complete Idiol's Gu ide to a C(lraer ln. Co mpu/er Programlllil1g dmú írását (Que, ISBN: 0789719959,1999), melyben szó esik arról, hogy hogyan érdemes beveLni képességeinket egy remek munka megszerzésének érdekében. •
Figyelem!
Maradjunk kapcsolatban!
Örömme! fogadunk megjegyzéseket, javaslatokat vagy ötleteket ezzel a könyvvel (vagy más könyvekkel) kapcsolatban. Látogassanak el honlapunkra a http://Www.LibertyAssociates.com címre (Jesse Libertyj és a http://www.cobs.comoldalra(DavidB.Horvath.CCP).Alig várjuk, hogy kedves olvasónktól visszajelzést kapjunk.
504 1VI. rész· KOlönlegességek
Kérdések és válaszok Kérdés: Miért használnánk try blokkokat, ha van általános kivételkezelés is? Válasz: A try blokk speciális kivételkezelést tesz lehet6vé az adon k6dblokkban. Jómagam általános kivétclkezclést használo k, ha az egész k6dm vonatkozóan kell megoldani valamit, viszont try blokkokba helyezek nunden speciális e,sete. Kérdés: Mi a fccnd6m még, hogy elértem a k6nyu ue8ére? Válasz: Folycassa a tanulást! Próbálja ki más példaprogramjait, írjon saját progmmokat, és olvasson sokat a nyomtatott vagy a hálózaton találhat6 fo rr~iso k b6l!
Gyakorlatok Véget ért a 24 tanóra. Válaszoljunk meg néhány befejez6 k6rd6st a kivételkezeléssel kapcsolatban és lássunk neki egy feladatnak tudásunk meger6sítésére!
Kvfz l. 2. 3. 4.
Mi a try blokk szerepe? Hogy lehet definiá lni a kivétel-továbbdobást? Mi a különbség a throw és a try közöttt? Hogyan lehet megadni az alapértelmezeuen életbe lepő catch blokkot?
Feladatok Ehhez:I fejezethez csak egyetlen feladat caItozik: Iránya nagyvilág és a programozás! Próbá lj:l ki a könyvhöz mellékelt összes példaprogramot, és a fordít6programmal érkezetteket is.
Válaszok a kvfzkérdésekre 1. A t ry blokkban találhat6 ut.'lsításokra kiváltód6 kivételeket az azt követő ca tch blokk kezeli. 2. Minden programozó clkészítheti saját kivétel-oszt.ályait, melyet egy-egy alkalmas továbbdobással (throw) hívhat életre. A kivétel kezelését a hOZ7...á kapcsolód6 catch blokk végzi el.
_ _ _ _ _ _ __
_
_
I
_ _ _ _...:2:.:4:.: . 6:::ra=-"-=Kiv"é:::'."I.",k..::h", iba:::k:::9Z8:::I::::és:.:é:::s.::"::éh::::á"",y...:ta::"::::ác:::s.J.", 50:::5_ _ _ .-
3. A t hrow egy kifejezetten kért kivétel, melyet maga a program hív meg C Houston, egy kis baj van~). A try blokk viszont azt tudatja a fo rdíl6progranunal, hogy ha az adon k6clrészben kivétel keletkezik (bármilyen meghívott könyvtári alprogmmban, az operációs rendszer szintjén vagy akár egy throw révén), akkor ezl kezelni szeretnénk a kapcsolódó catch blokkban. 4, Ha olyan catch blokkoL szeretnénk megadni, amelyben azokat a kivételeket kezeljük, amelyeket más ca t ch bJokkok nem kaptak el (a s witch-beli de f ault analógiájá ra), akkor a paramétcrnév helyett három pontot írjunk a zárójelbe: catch( ... ) .
J
VII.
RÉSZ
Függelékek A függelék B fUggelék
A bináris és a hexadecimális számrendszerek Gyakran használt kifejezések
A FÜGGELÉK A bináris és a hexadecimális számrendszerek Az ember az iskolában már régen megtanulta, nu fán terem az aritmetika. Fontos is ez a tudás, elvégre mi lenne velünk nélküle a hétköznapokban. Ha rnnézünk egy árc('<.!u· Jára és azt látjuk rajta ~ 145 ~ , rögtön ki tudjuk olvasni, miszerint ~száznegyvCnÖln . Nem is volt ezzel semmi gondunk egészen idáig. De most, hogy programozni tanulunk, jön
II
keltes meg
II
tiz(,!nhatos számrendszer, így
újra M kell gondolnunk, mi is az a 145. Ez persze továbbra is cgy számol. jelent, de maga II leíl'l jelsoroz31 már nem maga a szám, hanem annak a k6dja. És ez nagy különbség. Na de kezdjük az alapoknál. Nézzük cS:lk meg, mi is II különbség a h:lrmas szám és a ~3n között. A 3" az tulajdonképpen egy olyan kunkori jel a papíron. A három Illint szám ezzel szemben egy fogalom. A papírra ín számjegyet ennek a fogalomnak a képi megjelenítésére használjuk. A különbségtétel talán még nyilvánva16bb, ha belegondolunk, hogy a hármat számos más módon is leírhatjuk. A megfelelő szövegkörnyezetben a .', I I I, lU , és .... jelek valamennyien ugyanazt jelentik: három,
j
I
510 VII. rész· Függelékek A (ízes számrendszerben összesen tízféle számjegyet használunk: 0,1,2,3,4,5,6,7,8, és 9. Ennek a tíz jelnek a kombinálásával bármely számot leírhanmk. De nézzük csak meg közelebbről, hogyan reprezentáljuk például a tízet. A fentieknek megfelel6en hasznáihatnánk nlindenféle kőkorszaki módszereket is, teszem azt leírhatnánk egymás után tíz darab A-t, vagy írhatnánk azt hogy 111111111 1. Ezek is megtes7.ik. Az ókori rómaiak például azt írták: X. M. általunk manapság használt rendszert az arabok találták ki. Lényege, hogy a számok megadásában nem csupán a számjegyek számítanak, hanem azok pozíci6ja is. A szám végén, vagyis a jobb szélén vannak az ~egyesek~, őket követik a ~tízesekn és így tovább. A tizenötöt tehát e rendszer szerin két darabból kell összeraknunk veszünk egy dar.lb tizest, meg öl d
sok, és így tovább . .~. ri::! a harmadik pozíció
Más számrendszerek Ha jól meggondoljllk, nincs abban semmi véletlenszen1, ho!,'Y a mindennapi életben éppen a tízes számrendszert használjuk, hiszen tíz ujjunk van. Ebből a felbmerésból vi· SZOnt rögtön az is következik, hogy ugyanilyen jól hllsználhatnánk bármilyen más rendszert is. Nézzük csak, miféle szabályokat használ nának a nyolc ujjú emberek: l. Összesen 8 szá mjegy használható (O-7). 2. A helyiérlC:kck nyolc halványai, vagyis vannak egyesek, nyolcasok, hatvannégyesek és így tovább. 3. n .~zámjeggyel l egfeljebb O és 8 n·1 közé es6 számok írh:n6k Ic. Persze ha mindenféle számrendszerekkel dolgozunk, akkor egy papírra leírt jelsorozat mást és mást fog jelenteni attól függ6en, hogy milyen helyiértékek szerepel m.:k benne. Megelőlendó a félreértéscket ilyenkor a szám után jobb alsó indexként oda szoktuk ír· ni, hogy milyen alapú számábrázolásra vonalkolik az adott forma. Ha tehát tízes alapon akarjuk ábrázolni a tizenötöt, akkor ezt írjuk: 1510. Ugyanez a sz.1m nyolcas számrendszerben így fest: 178. EZl persle már nem tizenhétnek olvassuk, hanem valahogy így: "c!''Y bét nyolcas alapon". Hasonlóan az "igaz i ~ tizenötöt is olvashatjuk így: "egy Öl tízes alapon ~. A lényeg persze mindkét esetben a .tizenöt~, hiszen ez az a szám, allut nlindkét fomla reprezentál.
_ _ _ _ _ ________---'A""fii,,'g"'g."'le""k'-'-""A"b,"in",á..ris"",ás".".'"he"x".d"."c"im"á,"lis"","'","-m"re",n"dsze""r",."kL I "-5',,'_ ___ Na de a nyolcas számrendszerbeli ábrázolás az miért is éppen 17? Az 1 az jelenti, hogy vegyünk egyszer nyokat, a 7 pedig az egyesek helyén áll, vagyis ehhez még hozzá kell tennünk hétszer egyet. És mivell~ét meg nyolc az éppen ti7:enöt, rendben is volnánk. Vagy képzeljük el a dolgot még vizuálisabban. Vegyünk 15 csillagot: * *** ..
** ** .. *****
Az emberek többsége ezt eleve úgy rajzolja le, hogy csinál kél csoportol. Először leraj-
zol10 csillagot, aztán még ötöt. Ez tulajdonképpen nem más, mint a tízes számrendszerbeli reprezentáció, hiszen vettiink egyszer tízet és ÖL<;zör egyet Na de léteznek más csoportosítási módszerek is, mint például ez itt:
Ez itt nyolc csillag, meg még hét, vagyis a tizenöt nyokas számrendszernek reprezentációja (17g),
m egfe l elő
A számábrázolási alapokról Látható tehát, hogyatizenötöt mindenf(!le számr<:!ndszerben fel tudjuk írni. A szokásos tízes szá mrendszer aZl írhatjuk, hogy 15, vagy ha precízebbek
4 343
,
3
2
7' 49
7'
Apropó
1
7
Bánnely szám nulladik hatványe
Az matematika egy alapszabálya szerint bármely szám nulladik hatványa eggyel egyenlő:
70 = 1, 100 = 1,217,549,3430 = 1.
0 /
I
512 ini. rész • FOgg,lék,k Az els6 sor a helyiérték sorszáma . A másodikban láLható hét megfelelő hatványa , a harmadik pedig maga a helyiérték, vagyis hét megfelelő hatványának értéke decimális formában. Ha egy decimális szá mot szeretnénk hetes alapra konvertálni, a következő algoritmus szerint kell eljárnunk. Először is keressük ki azt a legnagyobb helyiértéket, amivel még osztható a kérdéses decimális szám . Ha például a decimális 200-Z.1t szerelnénk átahlkítani , a kko r a 4 oszlop (343) már nem jó, mert ezzel osztva az eredmény egészrésze nulla lesz. Egyszóval ezzel a helyiértékkel már nem is kell foglalkoznunk. A lege l ső, ami részt vesz az algoritmusban a 49-es helyiérték. Osszuk tehát el 200-at 49cel. Az eredményegészrésze 4, vagyis a hetes számrendszerbeli számnak ezen a pozíci6ján a négyes sz{im áll majd. A2 iménti osztás maradéka 4. Ezt a soron következ6 hclyiénékkcl, vagyis 7-tel nem lehet osztani, pomosabban le hetni éppen lehet, csak nulla lesz az egészrésze. Így a hetes számrendszerbe li szám hetes hdyiértékén nulla fog állni. Végül maradtak :I l egyesek. Az nyilvánvaló, hogya négybcn az egy négyszer va r' m(.!g, vagyis az egyesek helyé n 4 lesz. Az eredmé ny tehát 404,.
Nézzünk t:d{111 még egy példát Tegyük fe l, hogya 968 10 decimális számol szeretnénk hatos számre ndszerben ábrázolni. Ehhez a következő tábl:izatra lesz szükségünk:
5
4
64
6'
1296
216
3
6'
36
2
6'
6
60 l
A 968-b:lIl nincs meg az 1296, (ehát ezzel a helyiénékkel már nem kell fogla lkoznunk. /l.z eredmé ny ötödik pozícióján már nulla áll . 968-lIt 216-t:rl Q.o;ztva az eredmény egészre-
sze 4, :l maradék pedig 104. A negyedik helyiénéken tehát 4 áll. A 104-et36otal o.o;ztva az e redmény 2 a marndék 32 . A harmadik helyiénéken szere pl6 számjegy tehát 2. a 32-[ 60tHI osztva az erwmény 5 II maradék 2. a végeredmé ny tehát 425~ avagy táblázatosan:
5
,
6'
63
1296
O
3
2 16
4
62 36
2
l
6'
6
6'
2
5
2
l
li a val:HI1i1yen más számre ndszerból konvertálunk IrZeSfi:!, a m6dszer va lamivel egyszerűbb, hiszen csak szorozni és összeadni kell. Példaként végi:!zzük cl :I fen ti konverziÓ( visszafelé:
4·2 16 2·36
864
5•6
30
2•1
2
968
72
A fOggeiék · A bináris és a hexadecimális számrendszerek 1513
Kettes számrendszer A kettes számrendszer a lehetséges számrendszerek amolyan "alsó határát" képviseli, hiszen ill már csak kétféle számjegy képzelhető el, egyes b nulla. A fem bemutatott konverziós táblázat kenes s7.áml'endszerre a következ6képpen fest: Oszlop: 8 lIatvány: 27 Ilelyiérték: 128
7
6
2'
2'
64
32
5
16
4 8
3 2' 4
2
1
2'
2'
2
1
Nézzünk meg iu is egy példát a konverzi6m. Tegyük fel, hogy a 88 10 sz!imot szerelnénk viszontlátni hináris formában. A 128-as helyiértékkel már nyilván nem kell roglalkoznunk, mivel ez a 88-ban nincs meg. A nyolcadik helyiértékcn tehát les7..
°
A 64 a 88-ban egyszer van meg, v:lgyis II hetedik helyiérték 1 lesz, az oszt!'IS maradéka pedig 24. Ebben a 32-es helyiérték nincs meg egyszer sem, teMl " hatodik pozíci6n O lesz. A következő helyil':rték a 16. Ez megvan egyszer a 24-ben, vagyis az ötödik helyiérték 1, a maradék pedig 8. Ebben éppen egyszer van meg a következ6 helyiérték, vagyis a negyedik helyen 1 szerepel majd, az össze többi helyen pedig 0, hiszen ennél a részlépésnél nem keletkezett maradék, a konverziónak tehát vége. Az eredmény:
o 1 Ol 1 OOO Az eredmény helyességét természetesen le is kt!llCS szil. rnrcndszerbe!i SZlÍmot decimálissil.: l
O l l
•
•
..
•
O 16
~
,• 8
O O • 2 O • l 88
~
ha visszaalakítjlIk a femi
"
•
32
16
ellenőrizhetjük,
8 O O O
Miért éppen a kettes alap? A kettes S7.1unrendszen az teszi minden másnál megfelelőbbé a szá mítógépekkel kapcsolatban való használatr.t, hogy rendkívüli módon hasonlít a gépek alapvető logikájára, arra, amit egy számít6géppel egyáltalán meg \ehet oldani. A gépek alapvetően nagyon buták. Fogalmuk sincs sl.iJ11okról, betűkr6l, utasításokr6l mcg programokról. A szá mítógép némiképp leegyszerűsítve nem más, num egy csomó áramkör, amelyekben vagy folyik éppen áram, vagy nem, egy·egy csatlakozáson vagy magas a feszültségszint, vagy alacsony.
514 1VII. rész • Függelékek A logika egyszerusítése végen a mérnökök nem különböztetnek meg köztes fes7.üILSégszinteket, vagyis a számítástechnikában nincs o lyan, hogy kis feszülL'iég, kicsit nagyobb feszültség, közepes feszültség, magas feszültség meg óriási feszültség. Itt csak annyi számít, hogy .elég feszültség" van-e, vagy éppen "elégtelen". A rendszer tehát röviden szólva bin{lris. Kicsit továbblépve az ahsztrskd6k felépítésében az .elég" meg az .elégtelen" helyett egyszeruen annyit szoktak monelani hogy "igen" vagy .nem". Esetleg megfelel ő az .igaz és a . hamis" is. Akármelyik terminológiát használjuk is, a két különböz6 szintel kiv{116an lehet reprezentálni l -gyel és O-vaL Megegyezés szerint 1 jelemi az .igen" vagy . igaz" énéket, O pedig a ~nem" vagy "hamiS" megfele16je. Fontos azonban hangsúlyozni, hogy ez csak egy megállapodás, vagyis elvi ak:ldálya nem volna annak sem, hogy a dolog épp fordítva legyen. H
Most, hogy tisztában vagyunk az alapelképzeléssel, talán már az is világos mindenki szá mára, miért é ppen a kettes számrendszer a "nyer6". Eggyel és nullával éppen aZT lehet reprezel1lálni, amire egy áramkör a számitógépben képes: van feszü ltség, vagy nincs. Minden, amit a számítógép "tud" nem egyéb, mint hogy melyik áramkör van éppen bekapcsolva, és melyik ki. Ami be van kapcsolva, az neki 1, ami nincs,:LZ 1.
Bitek, bájtok, nybble-ek Miután megegyezlük magunkkal és a hOZ7..ánk hasonlókkal, hogy 1 fogja az igazat és O a hamisat jelenteni, a bináris sz.1mok, és azok számjegei, il bitek fontosságukban a többi szám fölé emelkedlek. Mivel az els6 számítógépek egyszerre 8 bitet voltak képesek kezelni , magától adódott, hogya biteket nyolcasával állítsuk be programoz{ls közben. 1.11nen pedig már csak egy lC:pés voll, hogy n bitnyokasok nevel is kapjnnnk: ők a bfijtok. Apropó
Kis bájtok
A bájt felét, vagyis négy bitet szokás nybble-nek nevezni. Talán azt is érdemes megemlfteni, hogy ezt egyes helyeken nibble-nek frják.
Egy bájton, vagyis 8 biten összesen 2S6 különböző énéket ábrázolhatunk . Hogy mjénJ Nézzük meg a lehetséges helyiénékeket. Ha minden bitet l-re állítunk, akkor a keletkező bináris szám decimális értéke 255. Ha minden bit O, nkkor -természetesen - O lesz a decimális érték is. Mivel pedig ebben a számábrázolási módszerben nincsenek lyuknk, O és 255 közölt bármely egész szá mOl képesek vagyunk 8 biten megjeleníteni. Ez pedig éppen 256 különböző érték .
Mi az a KB? J Ja meggondoljuk 210 (vagyis 1024) úgy nagyjából egyenlő l ()3~nal (vagyis WOO-rel). Nos, ez egy olyan "döbbenetes hasonlóság" amit egyszeruen nem lehetett kihagyni a terminológia megalkotása során, így aztán az informatikusok a 21 0 byte adatm ennyi~ séget elkezdték kilobájtként emlegetni, és 1 KB·nak leírni. "Normális" esetben - énesd a tudomány egyéb területein - a .kilo" előtag ugyebár ezret jelent, az a maradék 24 meg végül is kit érdekel.
I..
_ _ _ __ __ _ _ _ __ _ _"A"fü..,"g"g,..I..é"k_"-'A"b"in...á..."...s ..é.. s .. '"h"' .x"....d...c...im .."ál'i..s..s"zá...m..re...n..d=Sle=re...kJ 5.. 15_____ .;, Hasonlóan laza szemléletet követve az 1024 * 1024 cl 048576) elég közel esett az egymillióhoz, így ő lett a megabájt, vagy 1 MB . És ha már itt taltunk, miért hagynánk abba a dolgot. 1024 megabájt az - kis e1hanyagolással- lehet 1 gigabájt (a giga előtag ezermilliót, vagyis egymilliárdot jelent).
Bináris számok A számítógépek tehát egyesekből és nullákból álló mintázatokat használnak llundennek a kódolására. Egyesek és nullák kóJolj5k azokat a gépi utasításokat, amelyek az alapár:clmköröket vezérlik. Tgaz ugyan , hogy egyesek és nullák mindenféle halmazai számokká alakíthatók a megismert szabályok szerinL, hiha volna azonban azt képzelni, hogya számítástechnikában ténylegesen minden szám. Jócskán vannak olyan bináris jelsorozatok,
Vannak bináris számok, amelyeket UlasításkénL kell értelmezni, mtisokat adatként, VHgy kÓdkénl. Az egyik fontos kódolási rendszer például az ASCII. Ebbe n a kódrendszerben minden betűnek, számjegynek és írtísjelnek megfelel egy-egy 7 bites bin{lris szám . A kis "a~ betűnek például a 01100001 bináris k6d felel meg. Ez ebben az összefüggésben nem szám, mert bár mondhatjuk rá, hogy ez nem más mint 97 10 (64 + 32 + cle azért itt mégis a kis ,,<1" betűről van sz6, nem m{lsr61. Sokan szokták nt is mondani, hogy az ASCII k6doi<ísban a kis "a" betűnek a 97 felel meg, az igazság azonba n az, hogy cz csak egy olyan apró tévedés, amit nem szoktunk sz6vá lenni. A helyes kijelentés valahogy úgy hangzik, hogya 97 bináris rcprezcntílciój:t, vagyis a 01100001 az "a" betű kódja. Az, hogy mi a 97-et emlegetjük, csak kényelmi szempont, semmi több.
n,
Tizenhatos (hexadecimális) számábrázolás A bináris számokat elég nehéz olvasni, így szükség volt egy kényelmesebb, áttekintheírásmódra. Ha bináris sZámollízes sómrendszerbe akanlnk átírni, akkor - amint láttuk - az algoritmus nem bonyolult ugyan, de hOSSlú. A 16-os számrendszerbe való átírás eZle! slemben egészen egyszerű, van benne u!:''Y~mis benne egy kézenfekvő egyszerusítési lehelőség. tőbb
Ennek megérréséhez azonban először meg kell ismerkednünk a lilenhatos, vagy közkel etű nevén hexadecimális számrendszer logikájával. Alizenhatos sómrendszerben tizenhat számjeg!:''Yel kell dolgoznunk, amelyek a következők: 0, 1,2,3, 4,5,6,7,8,9, A, B, C, D, E, és F. Az utols6 hal ~számjegy" kiválasztása tulajdonképpen tetsző l eges
51s 1VII. rész' Függelékek volt. Azért pont az A-F betúket használjuk erre a célra, mert ezek minden billenty\1zeten megtalálhatók. A hexadecimális számrendszer helyiétték-táblázata a következőkép pen fest:
4
3
2
1
256
16
1
A hexadecimálisr61 decimálisra történő átalakítás természetesen továbbr.!. is csak szorzás! és összeadást igényel. Az FSC 16 .szám decimális értékér például a következőkép pen kaphat juk meg: F * 256 = 15 • 256 : 3640 8 * 16 = 126 C * 1 = 12 * 1 • 12 3980
lia az FC l6 számot binárisra szeretnénk lefordítani, .lkkor .1 lcgcgyszeníbb módszer talán az, ha elóbb kiszámhjuk a decimális értékét, majd azt konvertáljuk ál kettes számremlszerrc: F * 16 = 15 * 16 ~ 240 C * 1 = 12 • l = 12 252
A 252 10 decimális érték bináris al11km konvertálása a - táblázat és algoritmus szerint történik: Oszlop: 9 Hatvány: 28 Helyiérték: 256
következő
8 27
7
6
26
5
25
24
23
128
64
32
16
8
- korábbr61 már ismert
3 22 4
2 21 2
1
20
A7. 256-os hclyi(;nék"n o $ZercpeJ.
l l l l l l O
128 maradék 124 64 maradék 60 32 maradék 28 lG·maradék 12 8 maradék 4 4 maradék O
O l l
l
l
l
100
A végeredmény tehát a
következő
bináris szám: 11 11 1100.
A feladatot tehát tulajdonképpen megoldottuk, de felfedezhetü nk az egészben egy egyszeru trükköt. Tekintsük ugyanis a fenti nyolc bitet két négybites egységnek, és vegyük észre, hogy ez lehet6séget ad e,gy "mágikus" átalakításra.
A függelék · A bináris és a hexadecimális számrendszerek 1517
--------~=~~=-=-=======~ - ~---,,'
A jobb oldali bitnégyes az 1100, ami decimális 12-nek, vagyis hexadecimális C-nek felel meg. A bal oldali bitnégyes 1111, ami decimális 15, vagyis hexadecimális F. Röviden tehát a következő megfeleltetést sikeIÜl találunk: 1111 1100 F C
lia a két hexadecimális értéket egyszeruen egymás mellé írjuk, visszakapjuk az FC értéket, vagyis azt, amiből az 111111002 bináris értéket kaptuk. Némi fejtöréssel belátható, hogy ez a négyes csoportosítás mindig ml1ködik, vah'Yis tetsző l egesen hosszú bináris számot átírhatunk hexadecimálisra úgy, hogy a be l őle alkotott bitnégyeseket egyenként lefordítjuk, és a kapott hexadecimális digiteket egyszeruen egymás mellé írjuk. Mi több, a dolog visszafelé is tökéletesen mííködik. Ha tehát hexadecimálisr61 szeretnénk binárisrJ átírni egy számot, nincs más dolgunk, mint a tizenhatos számrendszerbeli alakot különálló digitekre bontani, ezeket egyenként átírni binárisra, majd a kapott bitsorozatokat egymás mellé írni. Ha visszaemlékszünk rá, hogya kettes szá mrendszerben az első négy helyiérték rendre a 8, 4, 2 és 1 decim,í lis értékek nek felel meg, a hexadecimális digitek egyenkénti átírása egész egyszeníen e lvégezh ető . Lássunk talán egy hosszabb példát: 101 1 0001 1101 0111
Az ebben a bináris számban előfordu l ó helyién6kek a következők: l, 2, 4, 8, 16, 32, 64, 128,256,512,1024,2048,4096,8192,16384, és 32768. Ha tehát átírjuk clecimálisra az ismert algoritmus szerint, a következ6t kapjuk: 1 x 1 1 x , 1 x 4
O x 8 1 x 16 O x 32 1 x64 1 x
x x x x x x
, 1
~
4
O 16
~
O 64
~
~
12'
128 256
255 " 512 " O 1024 O 2048 O 4096 1 " 8192 l O x 16384 l x 32768 Összesen : l
O O O 4,096
8, 192 O 32 , 768 45 , 527
Ennek a decimális értéknek a hexadccimálisra való átírása során a kekkel kell foglalkoznunk: 6 5535
4 096
256
16
l
köv e tkező
helyiérté-
518 1VII. re"
• Függelékek
A 65535--ös helyiértéken nyilván O lesz, hiszen 45527 nem osztható ezzel a számmal. Az első "érdekes· helyiérték tehát a 4096. Ez ll-szer van meg az eredeti számban, a maradék pedig 471. A következő hclyiérték a 256, ami 47 1-ben egyszer van meg, a marddék pedig 215. Ebben a következő , 16-os helyiérték 13-szor van meg (ez összeszorozva 208), vagyis a maf"dd6k 7. Összességében tehát a fenti decimális illetve bináris számnak megfelelő hexadecimális érték a 81D7. Biztos ami biztos, ellen6rizzük le, hogy valóban így van-c: B (11) * 4096 = 45.056 1*256=256 o (13) * 16 .. 208 7 * 1 = 7 CnS2cscn 45.527
E7. VOll tehát::l "gyalogos" módszer. Most nézzük meg, hogyan fest mindez az imént felfedezett "négy bites rövidítéssel", Az átvált:tnd6 bináris érték ugycb:ír az 10'11000111010111, amelynek a következ6 bitnégyesek felelnek meg: 1011 0001 11010111. L,í.ssuk, hogyan festenek ezek 3 négyjegyíi bináris számok egyenként hexadecimális reprezt::ntáci6ban:
, , o, 1 ,
·• ,•
, , ,
·
1011 1 1 2 1
1 2
o
8 • 8 Oss zes en : 11 Hexadecimális : B 0001 1 1 • 1 O 2 O 4 • O O 8 • O O Osszesen : 1 Hexadecimális : 1 1101 1
O 1
, , ,
·•
1
2 4
• •
1
O 4
1 x 8 8 OS6 zesen : 13 Hexadecimális: D 0111 • l x l l l x 2 2 l x 4 II O x 8 '" O Osszcsen? 7 Hexadecimá lis: 7 A végső hexadecimális alak : BlD7
A módszer tehát in is
működött.
.'
B FÜGGELÉK Gyakran használt kifejezések Ebben a függell:!kben összegyt1jtötlük a leggyakrabban el6fordu16 szakkifejezések a rövid meghatározását Tankönyvr61 lévén szó a rendszerezés tekintetében
~I
felbukkanás
sorrendjét tekintettük elsődlegesnek, vagyis az órák szerint csoportosítOLtuk a meghatározásokat. így ha az olvasó a könyv használata során olyasmivel ta l:'i.lkozik, amit nem én, ehhez a függeJékhez hátralapozva gyorsan információhoz juthal.
,
,. Ora Olyan linkc[het6 (bináris programjainkhoz szerkeszthet6) fájlok amelyek érkezhetnek a fordít6progmmmal egyillt, de külön is mcgvásárolhatjuk, vagy mi magunk is létrehozhatjuk 6kel. K6 11)'Vfiir (lib l"01yJ -
gyűjteménye,
Fiigg vény (jilllction) - Egy viszonylag önálló kódrészlet, amely egy bizonyos feladatot hajt végre. Ilyen feladat lehet például két sZ;'Ím összeadása vagy v:llaminek a képernyő re való kírása.
I
520 VII. rész • FOggelók,k
Osztály (e/ass) - Egy új típus definíciója. Az osztály adatok, és a rajtuk vagy velük dolgozó függvények gyűjtemé nye. Fordítóprogram (compjler)~ Olyan program, amely képes az ember sz.ámám is o lvasható k6dszövegb61 (forrásk6dbóO gépi kódú á llományt el őá llítani. A fordítás kél lépésben történik: el6bb egy tágykódú állomány (object cooe) jön létre, amit aztán a linker (lásd a megfelelő címszöt) futtatható programmá alakít. lilll...>er (szerl. . esztő) - Olyan program, amely végrehajtható (futtatható) állományt hoz létre a fordítóprogram által el6állitott tárgykódú állománybó l (object code).
Objektum-MzjJonttí (object-orie1lted) - Programozási metodol6gia, amely a procedurális és a strukturális programozás utáni következő fejlődési lépcsőnek tekinthet6. Amint arra neve is utal, ez a megközelítés úgynevezett objeklumokk;l l (osztályelemekkel) dolgozik, ezekhez rt!ndellulajdon.ságokat és viselkedésformákat. Gyakori, hogy ezt a kifejezést c1s6sorb:ln markeLing célokkal nem megfelelO" összefüggésekben is használják. 00 - Lásd az objektum-központú címszót. ANSI (A/I/(-!I"Cfllll Natiol1al Standards Illslffute) - Nonprofit SZCIVCz.et, amely az Egyesült Államokban használatos szabványokért feJel6s. Számos országnak, iIle(ve régió nak (például az Európai Uniónak) is van hasonló szeIVezete. Ezek egyes esetekben a korm:'m yzat részeként mtlködnek, ám az ANSI nem kormányzati szeIV. További informáciÓt a http : //www . ansi. org címen találhatunk. ISO (Imemationtll Orgtlllizationfor Slalldardfzat;Ol1) - Egy az ANS I-hoz hasonló, de a nemzetközi szabványokért fclcJ6s szeIVezeL Az ISO szintén nem kormányzati szeIV. B6vebb információt a http : //www . iso. org címen találh:uunk.
2. Óra Fordítás (co li/piliny - A forráskódot futtatható állománnyá alakító folyamat első lépés.
ilye nkor a fordítóprogmm eh'Y úgynevezett tárgyk6dú állományt hoz létre (. obj fáj!), a mit, aztán a linker abkít fullatható programmá. Szerkesz/és (/i nkeiés; lillkillg) - A forráskódot futtatható állománnyá alakító folyamat második lépése, amelyben a linker a fordítóprogram által eJ6állított tágyk6dú ;.mományokból összeszerkeszti a nmatható programot. Futtafharó program (execulable program) - Program, amely az operációs rendszer fel-
ügyelete alatt közvetlenü l futtatható.
B függelék' Gyakran haSl1lá~ kifojezések 1521 Él1e/mező (interpreter) - Az imcrpreler olyan program, amelyek a forráskódot a p rogram futása közben, soronkém alakilja gépi k6ddá.
Pmcedulrilis programozás (procedl/ral p rogramm ing) - Olyan fejlesztési paradigma, mely szerint a program egy adathalmazon végrehajtott műveletek sorozata.
Slmk/uráll programozás (strclured programming)- Olyan programfejlesztési paradigma, melyné l a megoldandó fekldatot szisztematikusan egyre kisebb részekre tagoljuk, és a részfeladatok megoldás<íra írju k meg a megfelelő eljárásokat. Beágyazás (ellcapsulalioll)- Önmagukat meghatározó, önálló objektumok létrehozása.
Ada/rejtés (data hiding) - Az objektum állapotának elrejtése a külvilág e161 privát adattagok használ
típus lulajdor,ságait,
T6bbafakríság, polImorfizmlIs (jJolyI/JOIphism) - Az a ké pesség, amikor számos altí~ pust CtípusváltozalOl) tudunk úgy kezelni, mintha azok egyetlen alaptípus vál t07..atai lennének. Előf(!/dolgoz6 (preprocessor)- Olyan program, amely a fordítóprogmm clótt fut Ic, és változtatásokat végez a forrásk6don, az abban található il jellel kezdődő sarok (dircktívák) a lapján.
Megjegyzés (commenl)- Olyan szövegrész a forrásk6dban, amely nem képezi annak integráns részél, csupá n II programozó s7.ámára hordoz információt. Aláírás (sz/glia/lÍra; sigllature)- Eb'Y függvény neve és annak argumentumai együttVéve.
,
3. Ora Vciltozó (va ritibie) - Névvel ellátott hely a me móriában , amelyben egy é rtékel tíÍrolhatunk. RAM (Random Access Memo /y) - Véletlen
hozzáférésű
memÓria.
rrpus (type) - Egy o bjektum mérete és egyéb jellemz6i. Előjeles
tárolhal.
(sig l/ed) - Olyan változ6típus, amely pozitiv és negatív értékeket egyaránt
522 1VII. rész · Függelékek E/ójelllélkt7/i (1I115i8,I1OO)- Olyan változó[Ípus, amely kizárólag pozitív sz:'lmokat tárolhat. ASCII (Americali Stalldrd Code/or biformaliOlI E:.\:change)- A legtöbb számítógéptípus állal h:lszn:ílt kódolási eljárás, amellyel számok, betúk és írástelek írhat6k le numerikusan. Érzéke1lység a kis- nagyhe/ űkre (case-sensitiue) - Olyan helyzet, amikor logikai különbség van a kis- és a nagybelúkkelleílt információ között. CA C++-ban például a myVal és a Myval nem azonos objektumokat jelent.)
7iJmsdefll1ícl6 (type definilion)- E&'Y adattípus meghatározása. Az így megadott típus kés6bb ugyanúgy használható, mint a beépített típusok. Állal/dó (cO I1Slalll) - O lyan tárolási egységek, amelyek tartalma a progr::lIn ruLása során nem változik. Beta szeriIIf é/1elll/fJz(Jndő cíflandó (/irem' cOl/stant) - Olynn érték, ,unit közvetlenül a fo rrflskódba gépe ltünk be. Ha például a kódban fe lbukkan valnhol n 35, a kkor ez egy liler!i lis 1\ ll3ncló. Szimbo/il.ms álfclI1dó (symbolic cOllsf(mt) - Olyan típussal és névvel rendelkező érték, amit 1\lIandónak deklar.''ilunk. Ilyen lehet péld(1U 1 egy anyag fo rr!tspontja (BoilingPoint). Fe/sorolt ál/'IIId6k (ellllll/ef'(/ted constants) - Állandók névvel rende[kez6 ha[ma2.1.
,
4. Ora Utasítás (statemel1t) - Olyan módszer, amivel a végrehajtás menetét, vagy egy kifejezs kiértékelését tudjuk befolyásolni. Ores karakter (wftiteS/XIce)- Ilyen a szóköz, a tabulátor és az újsor karakter. Összetett /ltasítás (co mpoul1d statement) - Egy nyitó és egy záró kapcsos zárójel között megadott utasít(\ssorozat, a me ly bárhol felbukkanhat, ahol egyetlen utasítás is. Kifejezés (e;\p ression) - Bármely olyan utasítás, ame ly vissza;ld vn lamilyen é rtéke t. Operátor (opercuolj - Olyan szimbólum, amely egy bizonyos utasítás végrehajtására készteti a forclít6progrdmot. Operandus (operand)- Matematikai szakszó, amely egy kifejezésnek azt a részét jelöli, amely a benne szereplő operátor által feldolgozott adatnak vagy objektumnak felel meg.
B függelék· Gyakran haszná~ kifejezések 1523 Hozzárcndclő operátor (assignmenl
szerepl6 operandus a jobb oldalon
opel"Cltor; " ) - Hatására az operálor baloldalán értéket veszi fel.
szereplő
Ba/é/1ék (l-lx/lue) - A bal érték olyan openmdus, amely egy oper.ítor baloldalán szerepell1ct.
jobbtJrték (r-vahwJ - A jobbérték olyan opemndus, amely egy operátor jobb oldalán szerepelhet.
Relációs operátorok (relatiollal ojJCnors)- Olyan operátorok, amelyekkel megállapíthatjuk, hogy kél szám egyenl6-e, vagy hogy az egyik kisebb vagy nagyobb-e, mim a másik. IlIkrcmCllláléis (il/crementil1g) - Egy változó értékének eggyel való növelése (a ++
opcr:1torra l végezhet6
művelet).
Dckrememáltls (dccramcllting) - Egy vállOZÓ értékének eggyd való csökkentése (a -o perátorral végezhet6 művelet). E/ő/ag opcrálor (prefu: opera/or)- Az inkrementáló vagy dekrementáló operátor olyan használata , amikor el6bb jut érvényre az opeditor, s csak aztán énékel6dik ki a kifeje~ zés (például ++nlyAge).
Utótag operátor (posrfix opera/ot) - Az inkrementáló vagy dekrement51ó operátor oly:ul használata, amikor cJ6bb kiértékel6dik a kifejezés s csak aztán jut élvényre az oper{tLor (p(:ldául myAg e H). ho~,.y a fordítóprogrJ.mmlk - zárójelezés hiányában - milyen sorrendben kell kiénékelnie az egyes operálorokat.
Mi/ve/eff rangsor (precedencia,. precedence) - Ez a rang határozza meg,
,
5. Ora Verem (stack) - Olyan speciális mem6riaterület, amit az operációs rendszer automatikusan lefoglal a futó program számám, és amely a függvények és az 6ket hívó k6drészle~ tek közti kommunikációra szolgál. A vermet szokás UFO~nak is nevezni (lásd ott). UFO (/..asI III First Out) - Az ilyen szervezésú mem6riából kiolvasáskor legel6ször a legutoljára beín érték kerül e16.
Függ/Jénydek/aráció (function dec/araNoll) - Egy függvény formális leírása, ameJyool a fordítóprogram megrudja annak nevét, visszatérési típusát, valamint hogy meiUlyi és mi lyen típusú paramétere van.
524 1VII. rész • FOgg,lók,k Prototípus (prorot)'pe) - A függvénycleklaráció másik elnevezése.
Fiiggwnydefillíci6 (fimcfiOll definilion) - A függvény működésének, kódjának leírása, vagyis az a forma , amely nem csak a deklarációban megadott információkallartalmazza , hanem a konkrét megvalósíLásl, a függvénytörzset is. Filggoony paraméterlistája (fu nClion parameter lisO - Egy függvény be menő paramétereinek fe lsorolása, amely vesszőve l elválasztva tartalmazza az argumentumok tipusát és esetleg nevét.
Helyi változók (local vtl1iables) - Olyan változók, amelyek csak egy adott függvényen belül léteznek. Érvényességi Mr (scape) - A k6dnak az a területe, amelyből egy adott vá ltozó látható és e l érhető , Globális változók (g loba! varlabfes) - Olyan vá ltozók, amelyek egy program bármely
pontjflr61 elé rllct6k,
6. Óra Iteráció (ileralfoll) - Olyan múvelelSor, amely során ub'Y:mazt a tev{: kenys~get végezzük újra és újra. Végtelen ciklIIs (fl/fil/ ite loop) - Olyan múvelelSor, amely során ugyanazt a tevf:kenysf:gCt v{:gezzíl k vég nélkül, Ez általában olyasmi, amit a programozók igyekeznek elkerülni, bár bizonyos helyzetekben megvan a maga haszna, SpagclrI/..>6d (spaghcll i codc)- Kevéssé stnlkturált módon, ösSZeViSSL'l megírt program , :Imely nehezen olvasható, Ncvét onnan kapta , hogy logika ilag (Igy néz ki, mint egy tál ,~pagctl i , :unelyben gyakorlatilag lehetetlen követni egy-egy szál lefutását.
,
7. Ora Kliellsck (eliel/IS) - Olya n osztályok vagy függvények, amelyek az álmlunk fejlesztett osztályt használjiik, Tagvállozók ( member vun'ables) - Ezeket szokás adattagoknak (dala members) is nevezni. Gyakorlatilag egy osztály saját változ6készletét szokás összefoglaló néven így említeni. Adal/agok (dala members) - Lásd a Tagvállozók címsz61.
B függelék · Gyakran hasmált kifejezések 1525 Tagfüggvények (membIT functions) - Ezeket szokás tagmetódusoknak (member methods) is nevezni. Egy osztály saját függvényeit szokás ilyen összefoglaló néven említeni. Tagmetódusok (member methods) - Lásd a Tagfüggvények címsz6t. Objektum (objectJ - Egy osztály egy eleme vagy példánya. Nyilvános hozztífén'js (public access) - Nyi lvános hoz7.áférésr61 akkor beszélünk, ha egy tagváltozóhoz vagy tagfüggvényhe7. bármely más osztály bármely met6dusa hozzáférhet. Privát hozzáférés (private access) - Privát hoz:láférésrGI akkor beszélünk, ha egy tagvagy tagfüggv6nyhcz kizárólag egy osztály saját met6dusai, illetve a belőle származtatott osztályok met6dusai férhetnek hozzá. vál~ozóhoz
Hozzéiférési mefódusok (acccsso r mCfliods)- Olyan metódusok, amelyek kifejezetlen a priváladattagokhoz való hozzáférés biztosítása végett készültek. Mefódusdefiuíció (mefl/od defi nit/on) - Olyan függvénydefiníci6 , amely egy osztály
nevével keznek.
kezdőd i k ,
majd két
kettőspont
után a függvény neve és a paraméterei követ-
Afapé/1elmezett konsfmklo r (defal/It constn./ctol:) - Olyan konstruktor, amelynek nin-
csenek
bemenő
paraméterci.
,
8. Ora Állandó lagfi./ ggwny (constant member fUl1ction) - A konstans ragfüggvény olyan
függvény, amely nem változtatja meg egy osztály egyetlen tagválroz6jának érlékét sem. IlIfcrjész (ínterJace) - Szokás osztályfeJületnek vagy osztályintcrfésznek is nevezni. Azoknak az ac1atoknak és metódusoknak az összességét jelenti, amelyekhez más o.~ztá Iyok és függv ényck hozzáférhetnek. Ez tehát nem magának az osztálynak a kódja, hanem csupán azt mondja meg, hogyan használható az kívülr61. Ezt az információt rendszerint egy különálló, kifejczcncn az adott osztályhoz tartozó fejlécállományban tárolják, amit az összes az osztályt használni kívánó moclulnak be kell szúrnia. ImPlementáciÓ (implementation) - Szokás oS7.t.ályme!,'Valósításnak va!:,'}' osztá lyimplemenláci6nak is nevezni. Egy adott osztály kódját és deklarációit nevezzük tb'YEz az a kód, amihez az interfészen keresztül hozzáférhetünk. Az osztályok megvalósítását rendszerint egy külön . epp fájlban tároljuk, amit aztán tárgy kódú állománnyá le-
526 1VII. rész • Függelékek fordítva, vagy könyvtárba helyezve lehet használni. Sok esetben a felhasználónak csak a tárb'Ykódú állomány és az interfésztleír6 fejléci llomány áll rendelkezésére, amelyekkel használhatja az adott osztályt, de nem módosíthatja a kódját.
,
9. Ora Mu/aló (pointel) - Olyan változó, amely egy mem6riacímel tárol.
lndirekció (illdoreclioll) - Az a műve l et, 3nlikor egy értékhez az annak a címét L1ro16 mutató segítségével férünk hozzá. DinCtmikuSCIII kezelt mell/ória (heap) - A mem6rián:-l k az a területe, amely a k6dleti.iJel, a globális névtér és a verem lefoglalása után mcgm:-lmd. Szokás eLL a részt "szabad tárnak" is nevezni. Ez az a mem6riaterület, ameJyb61 a new operátor és a mal loe () függvény kihasítanak egy-egy darabot a dinamikus tárkezelés során,
,
10. Ora Eltévedt mutató (s/ray poil/tCr, dangling poillfer) - Olyankor beszélünk általálxm err61 a jelenségről, amikor a már fdszabadítOltuk azt a mem6riaterü letet, amit II kérdéses mutató címez, mégis újra megprób,ílunk onnan olvasni, vagy oda írni. Ez egy általános, ugyanakkor rendkívül nehezen felderíthet6 hiba, mivel a mem6riához (örtén6 t6v(;!s hozzáférés általában jóval II lörlési utasítás mán következik csak be.
11. Óra llivatkozás (re/eret/ce) - Egy objektum álncve.
,
12. Ora Hatékonyság (efficiellcy) - A programozók körében meglehet6sen - talán tiHzottan istéma. Általában arról van szó, hogy heroikus küzdelmek árán óriási átalakításokat végzünk egy programon azért, hogy elenyésző mértékben gyorsabbá tegyük azt. Talán ez a legfontosabb információ, amit kezdő programozónak észben kell tartania a dologgal kapcsolatban, persze hacsak nem va lós idejű rendszereket, orvosi műszerek beágyazott szoftverél, vagy rakétairánytó központok vezérlókódjál fejleszti. Általában jobb, ha a fordítóprogramra hagyjuk, hob')' a hatékonysággal tör6djön. népszerű
B függelék · Gyakran hasmált kifejezések 1527
,
13. Ora Felszíni másolat (shallow copy) - Olyan máve\et eredménye, amely egy objektum ak-
tuális értékeit (adattagjai t) másolja ál egy másik objektumba. Szokás tagonkénti másoIá.snak is hívni. Mélységi máso/ar (de,p copy) - Olyan másolási művelet eredménye, amely nem csak az adattagok aktuális értékét másolja ál egy új objektumba, hanem a mutatók által cím-
zeLl memóriaterületeket is.
,
14. Ora EgyopertmdusIÍ operátor (Imal)' operato /o) - Olyan operátor, amelyegyel1en kifejezésen végez valamilyen nniveletet. lIyen például az inkrementálás operátora (aH ). Ellentéte a kélOperandusú (binary) operátor, amelynek két dolgot kelJ megadnunk. Ilyen például az összeadás operátora, aminek a jobb és a baloldalán is kell legyen egy operandus (a+b). KétoperclI1dllslí. operátor (binal)1 operatolo) - Olyan operátor, amely két dolgon képes
valamilyen
műveletet
végezni. Ilyen például az összeadás opeflÍlora (a+b).
Háro1lloperandusIÍ operátor (ten/alJI opcmtor) - Olyan operátor, amely hflrom objektum megadását igényli. 1\ C++ nyelvben mindössze egy ilyen van, a ? operátor, amit a következő formában keH használni: a <
? IG AZ , HAM IS ;
Ha a megadott kifejezés értéke igaz, akkor az IGAZ ágban lenkező esetben a HAM IS ág lépé érvénybe.
szereplő
utasitÍls fut le, el-
Qperand1.lsszám (arity) - Egy operátor által igényelt operandusok száma. A C++ eseté-
ben ez az érték lehet egy (unary operator), (rernary operator).
kettő
(binary operator) vagy három
,
15. Ora Tömb (array) - Azonos típusú objektumok halmaza. Tömhindex (subscript)- Az eltolás nagysága e,gy tömbön belül. A C++-ban a myArray tömb negyedik eleme a 3-as tömbindexen keresztül érhető el így: myArray [3 ] .
Karakter/ánc (string) - Karakterek tömbje, amelynek végét egy null karakter jelzi.
528 1VII. rész· Függelékek
16. Óra Ell/agyo/ás (stubbing) - Az a módszer, amikor egy függvényb61 éppen csak annyit írunk meg, hogy le lehessen fordítani a kódOl, a lényeget pedig későbbre hagyjuk. Fellilbí,-álás (ovcrriding) - Az az cset, amikor egy származratotl osztály eltérő funkci0nalitással megval6sít egy olyan füg&>vényt, amelynek már az alaposzrályban is volt
megva16sítása. lIyenkor fontos, hogy a felülbírál6 függvény visszatérési típusának és pamrnéterl istájának meg kell egyezni az eredeliével.
,
17. Ora Virtuális lII e/ódlls (vll1l1 a/ lll etl/od) - Az egyik azon módszerek közül, amelyeken keresztül :1 C+. mcgv:dósítja a többalakúságot (polymorphism). Lényege, hogy lt!11t!16vé teszi a sz:irma;.::LatOll objektumok alaposztályba tartozó objektumként való kt!ztdését.
Vil1//álls mel6t/l/stálJla (/J-tuble) - Olyan be l ső mechanizmus, amdy lt:hetóvl! teszi az objektumokon belüli virtuális fliggvények kezelésél.
,
18. Ora Absztrakt admlíplls (abstract dala lype) - Angolul gyakran rövidítik ADT-nek. Az absztrakt adatrípus inkább koncepci6t, elképzelést képvisel, nem pedig egy konkrét
dolgol. Az
"alak ~
példáu l absztrakt adattipus, míg a
. kör~
konkrél.
Tisztán virll/álfsjiiggvény (pl/re tlint/al fU/lction) - Olyan virtuális függvény, amil a származtatott osztálynak kötelezően felül kell bírálnia, mivel az alaposztályban egyáltalán nincs megvalósítása, nem tartozik hozzá kód. Az ilyen függvé ny tehát az alaposztályb:1Il egy teljesen absztrakt funkciót ír le.
,
19. Ora Láncolt lista (linked list) - Olya n adatszerkezet, amely logikailag egymáshoz kapcsolt csom6polltokból áll. Egyszeresenlállcolllisla (singly linked list) - Olyan láncolt lista, amelyben a csom6-
pomok csak a soron
következő
csomópontra tartalmaznak hivatkozást, visszafelé nem.
Kettősenlál/colt lista (t/oubly linked list)- Olyan láncolt lista, amelyben a csom6pomok az 6ket követ6 és megelőző csomópontra is tartalmaznak hivatkozásl.
B függelék • Gyakran haszná~ kifejezések 1529 Fa (tn>e) - Olyan összetett adatszerkezet, amt:lyben a csomópontok több másik csom6pomra tartalmaznak hivatkozást.
kettő,
vagy anná
,
20. Ora f r iend - Kulcsszó, amely azt írja
elő,
hogy egy osztály hozzáférhessen egy másik osz-
t:í ly privát tagjaihoz is, ad,magokhoz és metód usokhoz egyaránt.
Sratik/ls adattag (static member data) - Olyan adaltag, amely az általános adattagoktól eltén5en nem replikál6dik a példányosítás (objektumok létrehozása) sor.:in. Az ilyen elemekből csak egy példány létezik, ehhez azonban valamennyi az adou osztályba tartoz6 objektum hozzáférhet. Az ilyen adattagol általában a rrd használják, hogy segíts6gével nyilvántartsák a létrehozott objektumok számát, de lerm~szctcsen tárolható benne bármilyen más olyan info rmáció is, amely valamennyi objcktumra vonatkozik. Statikm tagft'/ggvéll)'ek (staIIc member fi/llctiolls) - Ugyanúgy, ahogya statikus adatIagok, ezek is csak egy példányban léteznek, és csak az adott Osztállyai kapcsolatban haszná lhatók, az osztály elemeivel kapcsolatban nem. Természetesen meghív:ísuknál sem kell egy adon objektumr.t hivatkoznunk.
,
21. Ora IIdefine - EI6feldolgoz6i direktíva, amely va lamilyen karaklerláncokkal kapcsolatos helyeuesitési műveletet ír elO:.
Token - Speciális jelentéssel felruházott karakterlánc.
22. Óra Vízesés úllater/all) - Olyan elj:írás, amelynél minden egyes részmtlvelet végreh .. jtásának megkezdéséhez be kell fejez6dn i az összes, őt megelőző résznllivelet végrehajtásának. Valamennyi állapot diszkrét és önmaga által meghatározott. Ezt a megközelítést nem csak szoftverfejleszlésre lehet alkalmazni, hanem számos más dologra is. (Jlyen logika alapján történik például egy ház vagy egy autó megépítése.) Szimuláció (simulalion) - Egy valós rendszer vagy részrendszer múködését leíró számít6gépes modell. Koncepció/elV (col1ceptllalizalioll) - Egy szoftverfejlesztési projekt alapötlete. Felhasználási esettanulmány (use case) - Egy rendszer fe lhasználási módjának leírása.
I
530 VII. rész • Függelékek UMl (Ul1ificd Modeling Lallguage) ~ Szabványos grafikus ábrázolási rendszer, amely a rendszerekkel szemben támasztott igények leírásában, illetve a tervezésben segít. Problématér (problem space) - Azoknak a Feladatoknak és problémáknak a halmaza, amelyeket egy programmal meg kívánunk oldani.
Megoldiistér (solu tion space) - Egy probléma lehetséges megoldásainak halmaza.
Mcghajtóprogram (d/ivel· program)- A tesztprogram másik megnevezése.
23. Óra Sob/Oll ((emp/ale) - Olyan eszköz, amivel általános met6duso kat vagy osztályokat hozhatunk I~trc. Ezekb61 az általános leírásokból aztán egy tírusnak paraméterként történ6 megadásáv., 1 {llIíthatunk el6 konkrét oszt:llyokat vagy me16dusokat. Példtmyosilás (fllstantialion)- Az a mOvelel amellyel egy o.'iztály a lapján egy objekmmot, vagy egy sablon alapján egy lípust (oszlályt) hozunk lélre.
,
24. Ora KitXJt(J! (e:;(CepUO'l) - Olyan objektum, amit probléma esclén az egyik kódrészlet a másiknak átad, A kűld6 a problémás k6drerület, :l fogadó pedig az a kód, amely a problémát kezelni képes.
TÁRGYMUTATÓ
532 1Tárgymutató
#414 lf1f 414
ASCII 41 assertO 416, 502
#defi ne 50, 99, 404 #else 405 #endif 405 #ifder 405
azonosíL6nevek 499
#jfndef 405 #indude 377 ~ndef
409
.c9 .cbx 7 .cp 9 .epp 7, 9
.cxx 9 _DATE_415 _FILE_415 _LlNE_ 415 _TIME_4 15
-- 257 8O/ 80-as szabály 451
B balérték 59 !r.uáli viszonyban 381 BASIC 20 báziskonslruktor 300 bázismet6dus meghívása 309 bázisosztály 293 bázisosztály inicializáci6j:1 304 bázisosztály metódusainak eJfedése
307 BCC32 11
beágyazás 22 beépítet! makró 416 beszúrás 407 binary 251 Borland C++BuilderX
A,Á
5
break 111
adattag 133
adattípus 38 address of 164 alapértelmezett é rtékek 234 , 235 alapértelmezett konstruktor 141
alias 196 alkalmazásfejleszl6i interfész 452 á!landó 49 ANSI 4 ANSI szabvány 25 ANSI/ISO CH 4 API 452 Application Programming Interface
452 a rgumenmm 32 argumentumok 90 a rity 254
C, Cs C++ 20
case 124 casting down 329 catch blokk 486, 490 célobjekrum 196 char 39 ci klus 107 cím 164 cím szerinti paraméterátadás 214 class invariant 420 COBÜL 20
compiler 5, 20 concatenation operator 414 const 50, 502
Tárgymutató 1533
continue III
érte[mez6k 20
cOUl45 csomópont 354 csökke ntés 60
érvényességi kör 88 escape karakter 165 eseményhurok 441
F
D DEHUG 409
fa 354
deep copy 238 default 125 definíció 81
fájlfo rmálumok 417
dekrementálás 60
fejlesztési ciklus 435 felel6sség átru házása 355 fe lsorolt .'illand6k 51 fe lszíni másolás 238 fe lillbírálat 305 [elillet 148 fe lülírás 305 float 41 for 117 fordílóprogram 5 fordil6programok 20 free 180 friend 381 futásidejú kötb; 318 futóindex 265
do ... while lvi double 47
E, É egyenl6ség operátor 257 egymásba ágyazott ciklusok 123 egyoperandusú oper.ítor 251 egységbezárás 133, 382 egységes modellez6 nyelv 437 egyszercsen láncolt lista 354 e l6feldolgozási direktív{lk 25 e lőfeldolgozó 25, 403 e l őjeles egész 48 el6tag 61 else ág 67 elvont adanípusok 341 encapsulation 22, 133,465 endl45 enum 51 érték szerinti paraméteráwdás 213
fcj[(:cállomány 150 fejlécállományok 25
függvény 30, 79
függvény argumentumai 89 függvény definíci6ja 83
filggvény prototípusa 81 függvény túlterhelése 97 függvénydeklaráció 81,82 függvények többalakúsága 231 rüggvénymutatók 382, 389 függvénymutatók tömbje 386 füg&ovényparamérer 32 ruggvénytúlterhelés 231
534 1Táryymutlrtó
G, Gy
interfész 150 intcrpereter 20 InvariantsO 424 iostream 45 ISO 4 iteráció 107
gazdátlan mUlató 190 globális névtér 175 golyóálló 484
goto 107 gyökeres struktúm 447
J
H
játék 110 jobbérték 59 h:lsználaLi esetdiagmll1 437
heap 174 helyi változók 86 hilxlkeresés 149 hivatkozás 196
K karakterlánc-osztályok 286
hivatkozások 221
karaktertömbök 282
hosszú serok 498 hozzMérés 501
kategória 131 kerílésoszlop hibák 268 kétoper-d.ndusó operátor 251 kétszeresen láncolt lista 354 kezd6énék 44 kifejezés SS, 57 kivétel 177 kivételek 485, 486 kivérelek e lkapás:1 491 kivételkezelés 485 kóbor mutató 190 k6cltér 175 koncepcióterv 437 konstans mutatók 191 konstans lllutat6val 217 konstans tagfüggvény 147 konstruktor 140 konstruktorok 236, 298 konstruktorok rlllterhelése 236 konzolos alkalma:.:ás 5 közvetett elérés 168
hozzáférés-szabályozó m6dosí16szó
296 hOZ7..árendelési vagy értékadó operátor 57
I IDE5 időzített bomba 190 if utasítás 66 IilNK3211 implementáció 149 indude 45 indusion guards 408 indireclion 168 inherilance 22 inkrementálás 60 inliJle f'i\ggvény 99 intO operátor 260 intelligens fordítóprogramok 223
Tárgymutat6 1535
mutatók tömbje 278 mutal6lömbök 276
L láncolt lista 353 leak 176 lecsupaszítás 293 linker S lista farka 354 lislafej 354 literdl 49 lógó mulató 190 lokális változók 136 long 39
jwva lue 59
N, Ny na hrybclűk 499 nevesített állandó 49 névtér 113 new 176 növelés 60
NULL 167
null pointer 167 nullmuta16 167
nyelvi állandó 49 nyomkövetés 418
M
nyomkövetés! szinte k 426 makr6fOggvény 410
makrók 4]5 makr6k mellékhatásai 418 maJloc 180 másol6 konstluktor 237 másoló met6dus 323 MAX 410 megjegyzés 28
megjegyzések 500 megoldástér 438 megval6sítás 149 mellékhatás 419 mélységi másolás 238 memóriacím 164 mem6riafoglalás 31:1 memóriaszivárgás 176 mctódus 133 MIN 410 nmemonikok 20 modulo 59 modulus 59 mulató 183 mutatók 163
""
0,0 objecl code 20 objektum-központú paradigm:l 22 objektumok iniciali7..álása 237 objektumtömbök 270 operandus 57 operátor 5$, 164 operátor operandusainak szám:. 254 operátor túlterhelés 245, 247 operator+ 251, 252, 380 operator- 254 osztály kezel6felülete 150 oszt:'ilydefi níd6k 501 osztályinvariánsok 420 oszrálymetódus megval6sítása 150 osztályok 131 overloading 97 örökl6dés 22, 291. 329 összefúz6 operátor 414 összetett utasítás 56
53s 1Tárgymutató
p
S, Sz paraméterlisla 81 paraméternek alapénelmezett értéke
95 paraméle r-szignaníra 305 Pareto szabálya 452 példányosítás 135 polimorfizmus 22 polymorphism 22
postfIX 61 postfix inkrcme l'tál6 operátor 248 postfix operátor 248 precedencia 63 prefIX 61
prefIx operátor 246
preprocessor 25 preprocessor directive 25 private 296 problém atér 438 procedurális 21 program szövege 498
programblokk 88 projekt 6 protected 296 prototípus 8 1 public 296
R
sablon defi níci6ja 466 sablon példányosítása 466 sablonok 465 sablonosztályok 466 sablontípus 474 s hallow copy 238 signed 40 sizeofO 40
slicing 320 spagetti kód 108 stack 101, 175, 276 Standard Template Library 480 stalikus 370 statikus tagfüggvények 372 statikus tagváltozók 369 STL 480 su cpyO 284 stringizing aper.hor 414 strncpyO 284 subscript 265 .switch 124 switch utasítások 498 symbolic 49 szabványos sabJonkönyvtár 480 származtatás 292, 293 szeletelés 320 szignatúra 32 szimuláció 436 szöveggé ala kító operátor 414
RAM 38 Random Acces Memory 38
referencia 174, 195 regiszter 175 reláci6s operátorok 65 rövidített írásmód 386 r-value 59
T, Ty tagfüggvény 133 tagmetódus 138 tagok 132 tagonkénti másolás 238 tagváJtazó 133
Tárgymutató , 537
tdrgykóOu jlh)Jll~lll\ 20 tartdlm,lzntt ')szt~lly 5)O\() [clt:pítés (J [hb JRR llli~ mUIJtó 5--i típu,:, 1.'\1 tíPllSdh:ís 329 többajakúsig 22, ~I-f. .~.!') többclimenziós tömbök T2 többclimenzi 6~ tömbök dekbr.1cióFl
v vad tllU1Jtó 1(1változú ~":lltm:Ú értéke .~~ dlto:t:ú lipus::l :.R "ár.1I1an e~emény ... k -f}O\:; "égtehm ciklus 113 "életlen elérésű memóna _\8 \crem 101, l-5. 2-6 virtuális destruktorok 322 viml:'ilis függvények 313, 314, 329, 342 virtuális másoló konstruktorok 322 visszahiv:llkozá.s 169 vil>Szatérési é rtékek 26 visszatérési típus 92 vízesés modell 435 v-mutató 318 v-tábMzat 318 v-t ábl~zat köllségei 326
273 tömb 265 tö m b mut~Hój;l 278 tömbök elemei 266 tömbök c1<1készífése 269 lömbök lürll:~(: lSl lömbök túHdsa 268 If)' blokk 4H6, 490
Iypeclel' 4'5, :W I
.. U,U
UML 437 unaf)' 251 IlnH'cd Modeling L1nguage 437 un.'>igncd 40 ula~ít;í.'> 55 utols(mak be eJs6nek kin 03sl-intlrsl-out) elv 101 utó tag 61
w ,,";II·nin.~ II whik' 101{ whih· .... lxIL'L· <;6 \\ dd puintL'f 167
z