P. 1
C++ könyv (2005, 1291 oldal)

C++ könyv (2005, 1291 oldal)

|Views: 3|Likes:
Published by Zsolt Punkosti
A basic guide to C++ programming in Hungarian...
A basic guide to C++ programming in Hungarian...

More info:

Published by: Zsolt Punkosti on Aug 04, 2012
Copyright:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as PDF, TXT or read online from Scribd
See more
See less

01/27/2014

pdf

text

original

Forrás: http://www.doksi.

hu

Bevezetés

Ez a bevezetés áttekintést ad a C++ programozási nyelv fõ fogalmairól, tulajdonságairól és standard (szabvány) könyvtáráról, valamint bemutatja a könyv szerkezetét és elmagyarázza azt a megközelítést, amelyet a nyelv lehetõségeinek és azok használatának leírásánál alkalmaztunk. Ezenkívül a bevezetõ fejezetek némi háttérinformációt is adnak a C++-ról, annak felépítésérõl és felhasználásáról.

Fejezetek 1. Megjegyzések az olvasóhoz 2. Kirándulás a C++-ban 3. Kirándulás a standard könyvtárban

Forrás: http://www.doksi.hu

Forrás: http://www.doksi.hu

1
Megjegyzések az olvasóhoz
„Szólt a Rozmár: Van ám elég, mirõl mesélni jó: ...” (L. Carroll – ford. Tótfalusi István) A könyv szerkezete • Hogyan tanuljuk a C++-t? • A C++ jellemzõi • Hatékonyság és szerkezet • Filozófiai megjegyzés • Történeti megjegyzés • Mire használjuk a C++-t? • C és C++ • Javaslatok C programozóknak • Gondolatok a C++ programozásról • Tanácsok • Hivatkozások

1.1. A könyv szerkezete
A könyv hat részbõl áll: Bevezetés: Elsõ rész: Második rész: Az 1–3. fejezetek áttekintik a C++ nyelvet, az általa támogatott fõ programozási stílusokat, és a C++ standard könyvtárát. A 4–9. fejezetek oktató jellegû bevezetést adnak a C++ beépített típusairól és az alapszolgáltatásokról, melyekkel ezekbõl programot építhetünk. A 10–15. fejezetek bevezetést adnak az objektumorientált és az általánosított programozásba a C++ használatával.

Forrás: http://www.doksi.hu

4

Bevezetés

Harmadik rész: A 16–22. fejezetek bemutatják a C++ standard könyvtárát. Negyedik rész: A 23–25. fejezetek tervezési és szoftverfejlesztési kérdéseket tárgyalnak. Függelékek: Az A–E függelékek a nyelv technikai részleteit tartalmazzák. Az 1. fejezet áttekintést ad a könyvrõl, néhány ötletet ad, hogyan használjuk, valamint háttérinformációkat szolgáltat a C++-ról és annak használatáról. Az olvasó bátran átfuthat rajta, elolvashatja, ami érdekesnek látszik, és visszatérhet ide, miután a könyv más részeit elolvasta. A 2. és 3. fejezet áttekinti a C++ programozási nyelv és a standard könyvtár fõ fogalmait és nyelvi alaptulajdonságait, megmutatva, mit lehet kifejezni a teljes C++ nyelvvel. Ha semmi mást nem tesznek, e fejezetek meg kell gyõzzék az olvasót, hogy a C++ nem (csupán) C, és hogy a C++ hosszú utat tett meg e könyv elsõ és második kiadása óta. A 2. fejezet magas szinten ismertet meg a C++-szal. A figyelmet azokra a nyelvi tulajdonságokra irányítja, melyek támogatják az elvont adatábrázolást, illetve az objektumorientált és az általánosított programozást. A 3. fejezet a standard könyvtár alapelveibe és fõ szolgáltatásaiba vezet be, ami lehetõvé teszi, hogy a szerzõ a standard könyvtár szolgáltatásait használhassa a következõ fejezetekben, valamint az olvasónak is lehetõséget ad, hogy könyvtári szolgáltatásokat használjon a gyakorlatokhoz és ne kelljen közvetlenül a beépített, alacsony szintû tulajdonságokra hagyatkoznia. A bevezetõ fejezetek egy, a könyv folyamán általánosan használt eljárás példáját adják: ahhoz, hogy egy módszert vagy tulajdonságot még közvetlenebb és valószerûbb módon vizsgálhassunk, alkalmanként elõször röviden bemutatunk egy fogalmat, majd késõbb behatóbban tárgyaljuk azt. Ez a megközelítés lehetõvé teszi, hogy konkrét példákat mutassunk be, mielõtt egy témát általánosabban tárgyalnánk. A könyv felépítése így tükrözi azt a megfigyelést, hogy rendszerint úgy tanulunk a legjobban, ha a konkréttól haladunk az elvont felé – még ott is, ahol visszatekintve az elvont egyszerûnek és magától értetõdõnek látszik. Az I. rész a C++-nak azt a részhalmazát írja le, mely a C-ben vagy a Pascalban követett hagyományos programozási stílusokat támogatja. Tárgyalja a C++ programokban szereplõ alapvetõ típusokat, kifejezéseket, vezérlési szerkezeteket. A modularitást, mint a névterek, forrásfájlok és a kivételkezelés által támogatott tulajdonságot, szintén tárgyalja. Feltételezzük, hogy az olvasónak már ismerõsek az I. fejezetben használt alapvetõ programozási fogalmak, így például bemutatjuk a C++ lehetõségeit a rekurzió és iteráció kifejezésére, de nem sokáig magyarázzuk, milyen hasznosak ezek. A II. rész a C++ új típusok létrehozását és használatát segítõ szolgáltatásait írja le. Itt (10. és 12. fejezet) mutatjuk be a konkrét és absztrakt osztályokat (felületeket), az operátor-túlterheléssel (11. fejezet), a többalakúsággal (polimorfizmussal) és az osztályhierarchiák hasz-

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

5

nálatával (12. és 15. fejezet) együtt. A 13. fejezet a sablonokat (template) mutatja be, vagyis a C++ lehetõségeit a típus- és függvénycsaládok létrehozására, valamint szemlélteti a tárolók elõállítására (pl. listák), valamint az általánosított (generikus) programozás támogatására használt alapvetõ eljárásokat. A 14. fejezet a kivételkezelést, a hibakezelési módszereket tárgyalja és a hibatûrés biztosításához ad irányelveket. Feltételezzük, hogy az olvasó az objektumorientált és az általánosított programozást nem ismeri jól, illetve hasznát látná egy magyarázatnak, hogyan támogatja a C++ a fõ elvonatkoztatási (absztrakciós) eljárásokat. Így tehát nemcsak bemutatjuk az elvonatkoztatási módszereket támogató nyelvi tulajdonságokat, hanem magukat az eljárásokat is elmagyarázzuk. A IV. rész ebben az irányban halad tovább. A III. rész a C++ standard könyvtárát mutatja be. Célja: megértetni, hogyan használjuk a könyvtárat; általános tervezési és programozási módszereket szemléltetni és megmutatni, hogyan bõvítsük a könyvtárat. A könyvtár gondoskodik tárolókról (konténerek – list, vector, map, 18. és 19. fejezet), szabványos algoritmusokról (sort, find, merge, 18. és 19. fejezet), karakterlánc-típusokról és -mûveletekrõl (20. fejezet), a bemenet és kimenet kezelésérõl (input/output, 21. fejezet), valamint a számokkal végzett mûveletek („numerikus számítás”) támogatásáról (22. fejezet). A IV. rész olyan kérdéseket vizsgál, melyek akkor merülnek fel, amikor nagy szoftverrendszerek tervezésénél és kivitelezésénél a C++-t használjuk. A 23. fejezet tervezési és vezetési kérdésekkel foglalkozik. A 24. fejezet a C++ programozási nyelv és a tervezési kérdések kapcsolatát vizsgálja, míg a 25. fejezet az osztályok használatát mutatja be a tervezésben. Az „A” függelék a C++ nyelvtana, néhány jegyzettel. A „B” függelék a C és a C++ közti és a szabványos C++ (más néven ISO C++, ANSI C++) illetve az azt megelõzõ C++-változatok közti rokonságot vizsgálja. A „C” függelék néhány nyelvtechnikai példát mutat be, A „D” függelék pedig a kulturális eltérések kezelését támogató standard könyvtárbeli elemeket mutatja be. Az „E” függelék a standard könyvtár kivételkezelésel kapcsolatos garanciáit és követelményeit tárgyalja.

1.1.1. Példák és hivatkozások
Könyvünk az algoritmusok írása helyett a program felépítésére fekteti a hangsúlyt. Következésképpen elkerüli a ravasz vagy nehezebben érthetõ algoritmusokat. Egy egyszerû eljárás alkalmasabb az egyes fogalmak vagy a programszerkezet egy szempontjának szemléltetésére. Például Shell rendezést használ, ahol a valódi kódban jobb lenne gyorsrendezést (quicksort) használni. Gyakran jó gyakorlat lehet a kód újraírása egy alkalmasabb algoritmussal. A valódi kódban általában jobb egy könyvtári függvény hívása, mint a könyvben használt, a nyelvi tulajdonságok szemléltetésére használt kód.

Forrás: http://www.doksi.hu

6

Bevezetés

A tankönyvi példák szükségszerûen egyoldalú képet adnak a programfejlesztésrõl. Tisztázva és egyszerûsítve a példákat a felmerült bonyolultságok eltûnnek. Nincs, ami helyettesítené a valódi programok írását, ha benyomást akarunk kapni, igazából milyen is a programozás és egy programozási nyelv. Ez a könyv a nyelvi tulajdonságokra és az alapvetõ eljárásokra összpontosít, amelyekbõl minden program összetevõdik, valamint az összeépítés szabályaira. A példák megválasztása tükrözi fordítóprogramokkal, alapkönyvtárakkal, szimulációkkal jellemezhetõ hátteremet. A példák egyszerûsített változatai a valódi kódban találhatóknak. Egyszerûsítésre van szükség, hogy a programozási nyelv és a tervezés lényeges szempontjai el ne vesszenek a részletekben. Nincs „ügyes” példa, amelynek nincs megfelelõje a valódi kódban. Ahol csak lehetséges, a „C” függelékben lévõ nyelvtechnikai példákat olyan alakra hoztam, ahol a változók x és y, a típusok A és B, a függvények f() és g() nevûek. A kódpéldákban az azonosítókhoz változó szélességû betûket használunk. Például:
#include<iostream> int main() { std::cout << "Helló, világ!\n"; }

Elsõ látásra ez „természetellenesnek” tûnhet a programozók számára, akik hozzászoktak, hogy a kód állandó szélességû betûkkel jelenik meg. A változó szélességû betûket általában jobbnak tartják szöveghez, mint az állandó szélességût. A változó szélességû betûk használata azt is lehetõvé teszi, hogy a kódban kevesebb legyen a logikátlan sortörés. Ezenkívül saját kísérleteim azt mutatják, hogy a legtöbb ember kis idõ elteltével könnyebben olvashatónak tartja az új stílust. Ahol lehetséges, a C++ nyelv és könyvtár tulajdonságait a kézikönyvek száraz bemutatási módja helyett a felhasználási környezetben mutatjuk be. A bemutatott nyelvi tulajdonságok és leírásuk részletessége a szerzõ nézetét tükrözik, aki a legfontosabb kérdésnek a következõt tartja: mi szükséges a C++ hatékony használatához? A nyelv teljes leírása – a könnyebb megközelítés céljából jegyzetekkel ellátva – a The Annotated C++ Language Standard címû kézikönyvben található, mely Andrew Koenig és a szerzõ mûve. Logikusan kellene hogy legyen egy másik kézikönyv is, a The Annotated C++ Standard Library. Mivel azonban mind az idõ, mind írási kapacitásom véges, nem tudom megígérni, hogy elkészül. A könyv egyes részeire való hivatkozások §2.3.4 (2. fejezet, 3.szakasz, 4. bekezdés), §B.5.6 („B” függelék, 5.6. bekezdés és §6.[10](6. fejezet, 10. gyakorlat) alakban jelennek meg. A dõlt betûket kiemelésre használjuk (pl. „egy karakterlánc-literál nem fogadható el”), fon-

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

7

tos fogalmak elsõ megjelenésénél (pl. többalakúság), a C++ nyelv egyes szimbólumainál (pl. for utasítás), az azonosítóknál és kulcsszavaknál, illetve a kódpéldákban lévõ megjegyzéseknél.

1.1.2. Gyakorlatok
Az egyes fejezetek végén gyakorlatok találhatók. A gyakorlatok fõleg az „írj egy programot” típusba sorolhatók. Mindig annyi kódot írjunk, ami elég ahhoz, hogy a megoldás fordítható és korlátozott körülmények között futtatható legyen. A gyakorlatok nehézségben jelentõsen eltérõek, ezért becsült nehézségi fokukat megjelöltük. A nehézség hatványozottan nõ, tehát ha egy (*1) gyakorlat 10 percet igényel, egy (*2) gyakorlat egy órába, míg egy (*3) egy napba kerülhet. Egy program megírása és ellenõrzése inkább függ az ember tapasztaltságától, mint magától a gyakorlattól.

1.1.3. Megjegyzés az egyes C++-változatokhoz
A könyvben használt nyelv „tiszta C++”, ahogyan a C++ szabványban leírták [C++, 1998]. Ezért a példáknak futniuk kell minden C++-változaton. A könyvben szereplõ nagyobb programrészleteket több környezetben is kipróbáltuk, azok a példák azonban, melyek a C++-ba csak nemrégiben beépített tulajdonságokat használnak fel, nem mindenhol fordíthatók le. (Azt nem érdemes megemlíteni, mely változatokon mely példákat nem sikerült lefordítani. Az ilyen információk hamar elavulnak, mert a megvalósításon igyekvõ programozók keményen dolgoznak azon, hogy nyelvi változataik helyesen fogadjanak el minden C++ tulajdonságot.) A „B” függelékben javaslatok találhatók, hogyan birkózzunk meg a régi C++ fordítókkal és a C fordítókra írott kóddal.

1.2. Hogyan tanuljuk a C++-t?
A C++ tanulásakor a legfontosabb, hogy a fogalmakra összpontosítsunk és ne vesszünk el a részletekben. A programozási nyelvek tanulásának célja az, hogy jobb programozóvá váljunk; vagyis hatékonyabbak legyünk új rendszerek tervezésénél, megvalósításánál és régi rendszerek karbantartásánál. Ehhez sokkal fontosabb a programozási és tervezési módszerek felfedezése, mint a részletek megértése; az utóbbi idõvel és gyakorlattal megszerezhetõ.

Forrás: http://www.doksi.hu

8

Bevezetés

A C++ sokféle programozási stílust támogat. Ezek mind az erõs statikus típusellenõrzésen alapulnak és legtöbbjük a magas elvonatkoztatási szint elérésére és a programozó elképzeléseinek közvetlen leképezésére irányul. Minden stílus el tudja érni a célját, miközben hatékony marad futási idõ és helyfoglalás tekintetében. Egy más nyelvet (mondjuk C, Fortran, Smalltalk, Lisp, ML, Ada, Eiffel, Pascal vagy Modula-2) használó programozó észre kell hogy vegye, hogy a C++ elõnyeinek kiaknázásához idõt kell szánnia a C++ programozási stílusok és módszerek megtanulására és megemésztésére. Ugyanez érvényes azon programozókra is, akik a C++ egy régebbi, kevésbé kifejezõképes változatát használták. Ha gondolkodás nélkül alkalmazzuk az egyik nyelvben hatékony eljárást egy másik nyelvben, rendszerint nehézkes, gyenge teljesítményû és nehezen módosítható kódot kapunk. Az ilyen kód írása is csalódást okoz, mivel minden sor kód és minden fordítási hiba arra emlékeztet, hogy a nyelv, amit használunk, más, mint „a régi nyelv”. Írhatunk Fortran, C, Smalltalk stb. stílusban bármely nyelven, de ez egy más filozófiájú nyelvben nem lesz sem kellemes, sem gazdaságos. Minden nyelv gazdag forrása lehet az ötleteknek, hogyan írjunk C++ programot. Az ötleteket azonban a C++ általános szerkezetéhez és típusrendszeréhez kell igazítani, hogy hatékony legyen az eltérõ környezetben. Egy nyelv alaptípusai felett csak pürroszi gyõzelmet arathatunk. A C++ támogatja a fokozatos tanulást. Az, hogy hogyan közelítsünk egy új nyelv tanulásához, attól függ, mit tudunk már és mit akarunk még megtanulni. Nem létezik egyetlen megközelítés sem, amely mindenkinek jó lenne. A szerzõ feltételezi, hogy az olvasó azért tanulja a C++-t, hogy jobb programozó és tervezõ legyen. Vagyis nem egyszerûen egy új nyelvtant akar megtanulni, mellyel a régi megszokott módon végzi a dolgokat, hanem új és jobb rendszerépítési módszereket akar elsajátítani. Ezt fokozatosan kell csinálni, mert minden új képesség megszerzése idõt és gyakorlást igényel. Gondoljuk meg, mennyi idõbe kerülne jól megtanulni egy új természetes nyelvet vagy megtanulni jól játszani egy hangszeren. Könnyen és gyorsan lehetünk jobb rendszertervezõk, de nem annyival könnyebben és gyorsabban, mint ahogy azt a legtöbben szeretnénk. Következésképpen a C++-t – gyakran valódi rendszerek építésére – már azelõtt használni fogjuk, mielõtt megértenénk minden nyelvi tulajdonságot és eljárást. A C++ – azáltal, hogy több programozási modellt is támogat (2. fejezet) – különbözõ szintû szakértelem esetén is támogatja a termékeny programozást. Minden új programozási stílus újabb eszközt ad eszköztárunkhoz, de mindegyik magában is hatékony és mindegyik fokozza a programozói hatékonyságot. A C++-t úgy alkották meg, hogy a fogalmakat nagyjából sorban egymás után tanulhassuk meg és eközben gyakorlati haszonra tehessünk szert. Ez fontos, mert a haszon a kifejtett erõfeszítéssel arányos.

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

9

A folytatódó vita során – kell-e C-t tanulni a C++-szal való ismerkedés elõtt – szilárd meggyõzõdésemmé vált, hogy legjobb közvetlenül a C++-ra áttérni. A C++ biztonságosabb, kifejezõbb, csökkenti annak szükségét, hogy a figyelmet alacsonyszintû eljárásokra irányítsuk. Könnyebb a C-ben a magasabb szintû lehetõségek hiányát pótló trükkösebb részeket megtanulni, ha elõbb megismertük a C és a C++ közös részhalmazát és a C++ által közvetlenül támogatott magasabb szintû eljárásokat. A „B” függelék vezérfonalat ad azoknak a programozóknak, akik a C++ ismeretében váltanak a C-re, például azért, hogy régebbi kódot kezeljenek. Több egymástól függetlenül fejlesztett és terjesztett C++-változat létezik. Gazdag választék kapható eszköztárakból, könyvtárakból, programfejlesztõ környezetekbõl is. Rengeteg tankönyv, kézikönyv, folyóirat, elektronikus hirdetõtábla, konferencia, tanfolyam áll rendelkezésünkre a C++ legfrissebb fejlesztéseirõl, használatáról, segédeszközeirõl, könyvtárairól, megvalósításairól és így tovább. Ha az olvasó komolyan akarja a C++-t használni, tanácsos az ilyen források között is böngészni. Mindegyiknek megvan a saját nézõpontja, elfogultsága, ezért használjunk legalább kettõt közülük. Például lásd [Barton,1994], [Booch,1994], [Henricson, 1997], [Koenig, 1997], [Martin, 1995].

1.3. A C++ jellemzõi
Az egyszerûség fontos tervezési feltétel volt; ahol választani lehetett, hogy a nyelvet vagy a fordítót egyszerûsítsük-e, az elõbbit választottuk. Mindenesetre nagy súlyt fektettünk arra, hogy megmaradjon a C-vel való összeegyeztethetõség, ami eleve kizárta a C nyelvtan kisöprését. A C++-nak nincsenek beépített magasszintû adattípusai, sem magasszintû alapmûveletei. A C++-ban például nincs mátrixtípus inverzió operátorral, karakterlánc-típus összefûzõ mûvelettel. Ha a felhasználónak ilyen típusra van szüksége, magában a nyelvben definíálhat ilyet. Alapjában véve a C++-ban a legelemibb programozási tevékenység az általános célú vagy alkalmazásfüggõ típusok létrehozása. Egy jól megtervezett felhasználói típus a beépített típusoktól csak abban különbözik, milyen módon határozták meg, abban nem, hogyan használják. A III. részben leírt standard könyvtár számos példát ad az ilyen típusokra és használatukra. A felhasználó szempontjából kevés a különbség egy beépített és egy standard könyvtárbeli típus között.

Forrás: http://www.doksi.hu

10

Bevezetés

A C++-ban kerültük az olyan tulajdonságokat, melyek akkor is a futási idõ növekedését vagy a tár túlterhelését okoznák, ha nem használjuk azokat. Nem megengedettek például azok a szerkezetek, melyek „háztartási információ” tárolását tennék szükségessé minden objektumban, így ha a felhasználó például két 16 bites mennyiségbõl álló szerkezetet ad meg, az egy 32 bites regiszterbe tökéletesen belefér. A C++-t hagyományos fordítási és futási környezetben való használatra tervezték, vagyis a UNIX rendszer C programozási környezetére. Szerencsére a C++ sohasem volt a UNIX-ra korlátozva, a UNIX-ot és a C-t csupán modellként használtuk a nyelv, a könyvtárak, a fordítók, a szerkesztõk, a futtatási környezetek stb. rokonsága alapján. Ez a minimális modell segítette a C++ sikeres elterjedését lényegében minden számítógépes platformon. Jó okai vannak azonban a C++ használatának olyan környezetekben, melyek jelentõsen nagyobb támogatásról gondoskodnak. Az olyan szolgáltatások, mint a dinamikus betöltés, a fokozatos fordítás vagy a típusmeghatározások adatbázisa, anélkül is jól használhatók, hogy befolyásolnák a nyelvet. A C++ típusellenõrzési és adatrejtési tulajdonságai a programok fordítási idõ alatti elemzésére támaszkodnak, hogy elkerüljék a véletlen adatsérüléseket. Nem gondoskodnak titkosításról vagy az olyan személyek elleni védelemrõl, akik szándékosan megszegik a szabályokat. Viszont szabadon használhatók és nem járnak a futási idõ vagy a szükséges tárhely növekedésével. Az alapelv az, hogy ahhoz, hogy egy nyelvi tulajdonság hasznos legyen, nemcsak elegánsnak, hanem valódi programon belül is elhelyezhetõnek kell lennie. A C++ jellemzõinek rendszerezett és részletes leírását lásd [Stroustrup, 1994].

1.3.1. Hatékonyság és szerkezet
A C++-t a C programozási nyelvbõl fejlesztettük ki és – néhány kivételtõl eltekintve – a C-t, mint részhalmazt, megtartotta. Az alapnyelvet, a C++ C részhalmazát, úgy terveztük, hogy nagyon szoros megfelelés van típusai, mûveletei, utasításai, és a számítógépek által közvetlenül kezelhetõ objektumok (számok, karakterek és címek) között. A new, delete, typeid, dynamic_cast és throw operátorok és – a try blokk – kivételével, az egyes C++ kifejezések és utasítások nem kívánnak futási idejû támogatást. A C++ ugyanolyan függvényhívási és visszatérési módokat használhat, mint a C – vagy még hatékonyabbakat. Amikor még az ilyen, viszonylag hatékony eljárások is túl költségesek, a C++ függvényt a fordítóval kifejtethetjük helyben (inline kód), így élvezhetjük a függvények használatának kényelmét, a futási idõ növelése nélkül.

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

11

A C egyik eredeti célja az assembly kód helyettesítése volt a legigényesebb rendszerprogramozási feladatokban. Amikor a C++-t terveztük, vigyáztunk, ne legyen megalkuvás e téren. A C és a C++ közti különbség elsõsorban a típusokra és adatszerkezetekre fektetett súly mértékében van. A C kifejezõ és elnézõ. A C++ még kifejezõbb. Ezért a jobb kifejezõképességért cserébe azonban nagyobb figyelmet kell fordítanunk az objektumok típusára. A fordító az objektumok típusának ismeretében helyesen tudja kezelni a kifejezéseket akkor is, ha egyébként kínos precizitással kellett volna megadni a mûveleteket. Az objektumok típusának ismerete arra is képessé teszi a fordítót, hogy olyan hibákat fedjen fel, melyek máskülönben egészen a tesztelésig vagy még tovább megmaradnának. Vegyük észre, hogy a típusrendszer használata függvényparaméterek ellenõrzésére – az adatok véletlen sérüléstõl való megvédésére, új típusok vagy operátorok elõállítására és így tovább – a C++-ban nem növeli a futási idõt vagy a szükséges helyet. A C++-ban a szerkezetre fektetett hangsúly tükrözi a C megtervezése óta megírt programok „súlygyarapodását”. Egy kis – mondjuk 1000 soros – programot megírhatunk „nyers erõvel”, még akkor is, ha felrúgjuk a jó stílus minden szabályát. Nagyobb programoknál ez egyszerûen nincs így. Ha egy 100 000 soros programnak rossz a felépítése, azt fogjuk találni, hogy ugyanolyan gyorsan keletkeznek az újabb hibák, mint ahogy a régieket eltávolítjuk. A C++-t úgy terveztük, hogy lehetõvé tegye nagyobb programok ésszerû módon való felépítését, így egyetlen személy is sokkal nagyobb kódmennyiséggel képes megbirkózni. Ezenkívül célkitûzés volt, hogy egy átlagos sornyi C++ kód sokkal többet fejezzen ki, mint egy átlagos Pascal vagy C kódsor. A C++ mostanra megmutatta, hogy túl is teljesíti ezeket a célkitûzéseket. Nem minden kódrészlet lehet jól szerkesztett, hardverfüggetlen vagy könnyen olvasható. A C++-nak vannak tulajdonságai, melyeket arra szántak, hogy közvetlen és hatékony módon kezelhessük a hardver szolgáltatásait, anélkül, hogy a biztonságra vagy az érthetõségre káros hatással lennénk. Vannak olyan lehetõségei is, melyekkel az ilyen kód elegáns és biztonságos felületek mögé rejthetõ. A C++ nagyobb programokhoz való használata természetszerûen elvezet a C++ nyelv programozócsoportok általi használatához. A C++ által a modularitásra, az erõsen típusos felületekre és a rugalmasságra fektetetett hangsúly itt fizetõdik ki. A C++-nak éppen olyan jól kiegyensúlyozott szolgáltatásai vannak nagy programok írására, mint bármely nyelvnek. Ahogy nagyobbak lesznek a programok, a fejlesztésükkel és fenntartásukkal, módosításukkal kapcsolatos problémák a „nyelvi probléma” jellegtõl az eszközök és a kezelés általánosabb problémái felé mozdulnak el. A IV. rész ilyen jellegû kérdéseket is tárgyal.

Forrás: http://www.doksi.hu

12

Bevezetés

Könyvünk kiemeli az általános célú szolgáltatások, típusok és könyvtárak készítésének módjait. Ezek éppúgy szolgálják a kis programok íróit, mint a nagy programokéit. Ezen túlmenõen, mivel minden bonyolultabb program sok, félig-meddig független részbõl áll, az ilyen részek írásához szükséges módszerek ismerete jó szolgálatot tesz minden alkalmazásprogramozónak. Az olvasó azt gondolhatja, a részletesebb típusszerkezetek használata nagyobb forrásprogramhoz vezet. A C++ esetében ez nem így van. Egy C++ program, amely függvényparaméter-típusokat vezet be vagy osztályokat használ, rendszerint kissé rövidebb, mint a vele egyenértékû C program, amely nem használja e lehetõségeket. Ott, ahol könyvtárakat használnak, egy C++ program sokkal rövidebb lesz, mint a megfelelõ C program, feltéve természetesen, hogy készíthetõ mûködõképes C-beli megfelelõ.

1.3.2. Filozófiai megjegyzés
A programozási nyelvek két rokon célt szolgálnak: a programozónak részben eszközt adnak, amellyel végrehajtható mûveleteket adhat meg, ugyanakkor egy sereg fogódzót is rendelkezésére bocsátanak, amikor arról gondolkodik, mit lehet tenni. Az elsõ cél ideális esetben „gépközeli” nyelvet kíván, amellyel a számítógép minden fontos oldala egyszerûen és hatékonyan kezelhetõ, a programozó számára ésszerû, kézenfekvõ módon. A C nyelvet elsõsorban ebben a szellemben tervezték. A második cél viszont olyan nyelvet követel meg, mely „közel van a megoldandó problémához”, hogy a megoldás közvetlenül és tömören kifejezhetõ legyen. A nyelv, melyben gondolkodunk/programozunk és a problémák, megoldások, melyeket el tudunk képzelni, szoros kapcsolatban állnak egymással. Ezért a nyelvi tulajdonságok megszorítása azzal a szándékkal, hogy kiküszöböljük a programozói hibákat, a legjobb esetben is veszélyes. A természetes nyelvekhez hasonlóan nagy elõnye van annak, ha az ember legalább két nyelvet ismer. A nyelv ellátja a programozót a megfelelõ eszközökkel, ha azonban ezek nem megfelelõek a feladathoz, egyszerûen figyelmen kívül hagyjuk azokat. A jó tervezés és hibamentesség nem biztosítható csupán az egyedi nyelvi tulajdonságok jelenlétével vagy távollétével. A típusrendszer különösen összetettebb feladatok esetében jelent segítséget. A C++ osztályai valóban erõs eszköznek bizonyultak.

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

13

1.4. Történeti megjegyzés
A szerzõ alkotta meg a C++-t, írta meg elsõ definícióit, és készítette el elsõ változatát. Megválasztotta és megfogalmazta a C++ tervezési feltételeit, megtervezte fõ szolgáltatásait, és õ volt a felelõs a C++ szabványügyi bizottságban a bõvítési javaslatok feldolgozásáért. Világos, hogy a C++ sokat köszönhet a C-nek [Kernighan, 1978]. A C néhány, a típusellenõrzés terén tapasztalt hiányosságát kivéve megmaradt, részhalmazként (lásd „B” függelék). Ugyancsak megmaradt az a C-beli szándék, hogy olyan szolgáltatásokra fektessen hangsúlyt, melyek elég alacsony szintûek ahhoz, hogy megbirkózzanak a legigényesebb rendszerprogramozási feladatokkal is. A C a maga részérõl sokat köszönhet õsének, a BCPL-nek [Richards, 1980]; a BCPL // megjegyzés-formátuma (újra) be is került a C++-ba. A C++ másik fontos forrása a Simula67 volt [Dahl, 1970] [Dahl, 1972]; az osztály fogalmát (a származtatott osztályokkal és virtuális függvényekkel) innen vettem át. A C++ operátor-túlterhelési lehetõsége és a deklarációk szabad elhelyezése az utasítások között az Algol68-ra emlékeztet [Woodward, 1974]. A könyv eredeti kiadása óta a nyelv kiterjedt felülvizsgálatokon és finomításokon ment keresztül. A felülvizsgálatok fõ területe a túlterhelés feloldása, az összeszerkesztési és tárkezelési lehetõségek voltak. Ezenkívül számos kisebb változtatás történt a C-vel való kompatibilitás növelésére. Számos általánosítás és néhány nagy bõvítés is belekerült: ezek a többszörös öröklés, a static és const tagfüggvények, a protected tagok, a sablonok, a kivételkezelés, a futási idejû típusazonosítás és a névterek. E bõvítések és felülvizsgálatok átfogó feladata a C++ olyan nyelvvé fejlesztése volt, mellyel jobban lehet könyvtárakat írni és használni. A C++ fejlõdésének leírását lásd [Stroustrup, 1994]. A sablonok (template) bevezetésének elsõdleges célja a statikus típusú tárolók (konténerek – list, vector, map) és azok hatékony használatának (általánosított vagy generikus programozás) támogatása, valamint a makrók és explicit típuskényszerítések (casting) szükségének csökkentése volt. Inspirációt az Ada általánosító eszközei (mind azok erõsségei, illetve gyengeségei), valamint részben a Clu paraméteres moduljai szolgáltattak. Hasonlóan, a C++ kivételkezelési eljárásainak elõdjei is többé-kevésbé az Ada [Ichbiah, 1979], a Clu [Liskov, 1979] és az ML [Wikstrm, 1987]. Az 1985-1995 között bevezetett egyéb fejlesztések – többszörös öröklés, tisztán virtuális függvények és névterek – viszont nem annyira más nyelvekbõl merített ötletek alapján születtek, inkább a C++ használatának tapasztalataiból leszûrt általánosítások eredményei. A nyelv korábbi változatait (összefoglaló néven az osztályokkal bõvített C-t [Stroustrup, 1994]) 1980 óta használják. Kifejlesztésében eredetileg szerepet játszott, hogy olyan eseményvezérelt szimulációkat szerettem volna írni, melyekhez a Simula67 ideális lett volna,

Forrás: http://www.doksi.hu

14

Bevezetés

ha eléggé hatékony. Az „osztályokkal bõvített C” igazi területét a nagy programok jelentették, ahol a lehetõ leggyorsabbnak kell lenni és a lehetõ legkevesebb helyet foglalni. Az elsõ változatokból még hiányzott az operátor-túlterhelés, valamint hiányoztak a referenciák, a virtuális függvények, a sablonok, a kivételek és sok egyéb. A C++-t nem kísérleti körülmények között elõször 1983-ban használták. A C++ nevet Rick Mascitti adta a nyelvnek az említett év nyarán. A név kifejezi mindazt a forradalmi újítást, amit az új nyelv a C-hez képest hozott: a ++ a C növelõ mûveleti jele. (A „C+”-t is használják, de az egy másik, független nyelv.) A C utasításformáit jól ismerõk rámutathatnak, hogy a „C++” kifejezés nem olyan „erõs”, mint a „++C”. Mindazonáltal a nyelv neve nem is D, hiszen a C-nek csupán bõvítésérõl van szó, amely az ott felmerült problémák elhárításához az eredeti nyelv szolgáltatásai közül egyet sem vet el. A C++ név más megközelítésû elemzéséhez lásd [Orwell, 1949, függelék]. A C++ megalkotásának fõ oka azonban az volt, hogy barátaimmal együtt nem szerettünk volna assembly, C vagy más modern, magas szintû nyelven programozni. Csak annyit akartunk elérni, hogy könnyebben és élvezetesebben írhassunk jól használható programokat. Kezdetben nem vetettük papírra rendszerezetten a fejlesztési terveket: egyszerre terveztünk, dokumentáltunk és alkottunk. Nem volt „C++ projekt” vagy „C++ tervezõbizottság”. A C++ a felhasználók tapasztalatai és a barátaimmal, munkatársaimmal folytatott viták során fejlõdött ki. A C++ késõbbi robbanásszerû elterjedése szükségszerûen változásokat hozott magával. Valamikor 1987-ben nyilvánvalóvá vált, hogy a C++ hivatalos szabványosítása immár elkerülhetetlen és haladéktalanul meg kell kezdenünk az ilyen irányú munka elõkészítését [Stroustrup, 1994]. Folyamatosan próbáltuk tartani a kapcsolatot mind hagyományos, mind elektronikus levélben, illetve személyesen, konferenciákat tartva a különbözõ C++ fordítók készítõivel és a nyelv fõ felhasználóival. Ebben a munkában nagy segítséget nyújtott az AT&T Bell Laboratories, lehetõvé téve, hogy vázlataimat és a C++ hivatkozási kézikönyv újabb és újabb változatait megoszthassam a fejlesztõkkel és felhasználókkal. Segítségük nem alábecsülendõ, ha tudjuk, hogy az említettek nagy része olyan vállalatoknál dolgozott, amelyek az AT&T vetélytársainak tekinthetõk. Egy kevésbé „felvilágosult” cég komoly problémákat okozhatott volna és a nyelv „tájszólásokra” töredezését idézte volna elõ, pusztán azáltal, hogy nem tesz semmit. Szerencsére a tucatnyi cégnél dolgozó mintegy száz közremûködõ elolvasta és megjegyzésekkel látta el a vázlatokat, melyekbõl az általánosan elfogadott hivatkozási kézikönyv és a szabványos ANSI C++ alapdokumentuma megszületett. A munkát segítõk neve megtalálható a The Annotated C++ Reference Manual-ban [Ellis, 1989]. Végül az ANSI X3J16 bizottsága a Hewlett-Packard kezdeményezésére 1989 decemberében összeült, 1991 júniusában pedig már

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

15

annak örülhettünk, hogy az ANSI (az amerikai nemzeti szabvány) C++ az ISO (nemzetközi) C++ szabványosítási kezdeményezés részévé vált. 1990-tõl ezek a szabványügyi bizottságok váltak a nyelv fejlesztésének és pontos körülhatárolásának fõ fórumaivá. Magam mindvégig részt vettem e bizottságok munkájában; a bõvítményekkel foglalkozó munkacsoport elnökeként közvetlenül feleltem a C++-t érintõ lényegbevágó módosítási javaslatok és az új szolgáltatások bevezetését szorgalmazó kérelmek elbírálásáért. Az elsõ szabványvázlat 1995 áprilisában került a nagyközönség elé, a végleges ISO C++ szabványt (ISO/IEC 14882) pedig 1998-ban fogadták el. A könyvben bemutatott kulcsfontosságú osztályok némelyike a C++-szal párhuzamosan fejlõdött. A complex, vector és stack osztályokat például az operátor-túlterhelési eljárásokkal egyidõben dolgoztam ki. A karakterlánc- és listaosztályokat (string, list) Jonathan Shopironak köszönhetjük (azért én is közremûködtem). Jonathan hasonló osztályai voltak az elsõk, amelyeket egy könyvtár részeként széles körben használtak; ezekbõl a régi kísérletekbõl fejlesztettük ki a C++ standard könyvtárának string osztályát. A [Stroustrup, 1987] és a §12.7[11] által leírt task könyvtár egyike volt az „osztályokkal bõvített C” nyelven elõször írt programoknak. (A könyvtárat és a kapcsolódó osztályokat én írtam a Simula stílusú szimulációk támogatásához.) A könyvtárat késõbb Jonathan Shopiro átdolgozta, és még ma is használják. Az elsõ kiadásban leírt stream könyvtárat én terveztem és készítettem el, Jerry Schwarz pedig Andrew Koenig formázó eljárása (§21.4.6) és más ötletek felhasználásával az e könyv 21. fejezetében bemutatandó iostreams könyvtárrá alakította. A szabványosítás során a könyvtár további finomításon esett át; a munka dandárját Jerry Schwarz, Nathan Myers és Norihiro Kumagai végezték. A sablonok lehetõségeit az Andrew Koenig, Alex Stepanov, személyem és mások által tervezett vector, map, list és sort sablonok alapján dolgoztuk ki. Alex Stepanovnak a sablonokkal történõ általánosított programozás terén végzett munkája emellett elvezetett a tárolók bevezetéséhez és a C++ standard könyvtárának egyes algoritmusaihoz is (§16.3, 17. fejezet, 18. fejezet §19.2). A számokkal végzett mûveletek valarray könyvtára (22. fejezet) nagyrészt Kent Budge munkája.

1.5. A C++ használata
A C++-t programozók százezrei használják, lényegében minden alkalmazási területen. Ezt a használatot támogatja tucatnyi független megvalósítás, többszáz könyvtár és tankönyv, számos mûszaki folyóirat, konferencia, és számtalan konzultáns. Oktatás és képzés minden szinten, széles körben elérhetõ.

Forrás: http://www.doksi.hu

16

Bevezetés

A régebbi alkalmazások erõsen a rendszerprogramozás felé hajlottak. Több nagy operációs rendszer íródott C++-ban: [Campbell, 1987] [Rozier, 1988] [Hamilton, 1993] [Berg, 1995] [Parrington, 1995] és sokan mások kulcsfontosságú részeket írtak. A szerzõ lényegesnek tekinti a C++ engedmény nélküli gépközeliségét, ami lehetõvé teszi, hogy C++-ban írhassunk eszközmeghajtókat és más olyan programokat, melyek valósidejû, közvetlen hardverkezelésre támaszkodnak. Az ilyen kódban a mûködés kiszámíthatósága legalább annyira fontos, mint a sebesség és gyakran így van az eredményül kapott rendszer tömörségével is. A C++-t úgy terveztük, hogy minden nyelvi tulajdonság használható legyen a komoly idõbeli és helyfoglalásbeli megszorításoknak kitett kódban is. [Stroustrup, 1994, §4.5]. A legtöbb programban vannak kódrészletek, melyek létfontosságúak az elfogadható teljesítmény tekintetében. A kód nagyobb részét azonban nem ilyen részek alkotják. A legtöbb kódnál a módosíthatóság, a könnyû bõvíthetõség és tesztelhetõség a kulcskérdés. A C++ ilyen téren nyújtott támogatása vezetett el széleskörû használatához ott, ahol kötelezõ a megbízhatóság, és ahol az idõ haladtával jelentõsen változnak a követelmények. Példaként a bankok, a kereskedelem, a biztosítási szféra, a távközlés és a katonai alkalmazások szolgálhatnak. Az USA távolsági telefonrendszere évek óta a C++-ra támaszkodik és minden 800-as hívást (vagyis olyan hívást, ahol a hívott fél fizet) C++ program irányít [Kamath, 1993]. Számos ilyen program nagy méretû és hosszú életû. Ennek eredményképpen a stabilitás, a kompatibilitás és a méretezhetõség állandó szempontok a C++ fejlesztésében. Nem szokatlanok a millió soros C++ programok. A C-hez hasonlóan a C++-t sem kifejezetten számokkal végzett mûveletekhez tervezték. Mindazonáltal sok számtani, tudományos és mérnöki számítást írtak C++-ban. Ennek fõ oka, hogy a számokkal való hagyományos munkát gyakran grafikával és olyan számításokkal kell párosítani, melyek a hagyományos Fortran mintába nem illeszkedõ adatszerkezetekre támaszkodnak [Budge, 1992] [Barton, 1994]. A grafika és a felhasználói felület olyan területek, ahol erõsen használják a C++-t. Bárki, aki akár egy Apple Macintosht, akár egy Windowst futtató PC-t használt, közvetve a C++-t használta, mert e rendszerek elsõdleges felhasználói felületeit C++ programok alkotják. Ezenkívül a UNIX-ban az X-et támogató legnépszerûbb könyvtárak némelyike is C++-ban íródott. Ilyenformán a C++ közösen választott nyelve annak a hatalmas számú alkalmazásnak, ahol a felhasználói felület kiemelt fontosságú. Mindezen szempontok mellett lehet, hogy a C++ legnagyobb erõssége az a képessége, hogy hatékonyan használható olyan programokhoz, melyek többféle alkalmazási területen igényelnek munkát. Egyszerû olyan alkalmazást találni, melyben LAN és WAN hálózatot, számokkal végzett mûveleteket, grafikát, felhasználói kölcsönhatást és adatbázis-hozzáférést használunk. Az ilyen alkalmazási területeket régebben különállóknak tekintették és általában különálló fejlesztõközösségek szolgálták ki, többféle programozási nyelvet használva.

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

17

A C++-t széles körben használják oktatásra és kutatásra. Ez néhány embert meglepett, akik – helyesen – rámutattak, hogy a C++ nem a legkisebb és legtisztább nyelv, amelyet valaha terveztek. Mindazonáltal a C++ ♦ elég tiszta ahhoz, hogy az alapfogalmakat sikeresen tanítsuk, ♦ elég valószerû, hatékony és rugalmas az igényes projektekhez is, ♦ elérhetõ olyan szervezetek és együttmûködõ csoportok számára, melyek eltérõ fejlesztési és végrehajtási környezetekre támaszkodnak, ♦ elég érthetõ ahhoz, hogy bonyolult fogalmak és módszerek tanításának hordozója legyen és ♦ elég „kereskedelmi”, hogy segítse a tanultak gyakorlatban való felhasználását. A C++ olyan nyelv, mellyel gyarapodhatunk.

1.6. C és C++
A C++ alapnyelvének a C nyelvet választottuk, mert ♦ ♦ ♦ ♦ sokoldalú, tömör, és viszonylag alacsony szintû, megfelel a legtöbb rendszerprogramozási feladatra, mindenütt és mindenen fut, és illeszkedik a UNIX programozási környezetbe.

A C-nek megvannak a hibái, de egy újonnan készített nyelvnek is lennének, a C problémáit pedig már ismerjük. Nagy jelentõsége van, hogy C-vel való munka vezetett el a hasznos (bár nehézkes) eszközzé váló „osztályokkal bõvített C”-hez, amikor elõször gondoltunk a C bõvítésére Simula-szerû osztályokkal. Ahogy szélesebb körben kezdték használni a C++-t és az általa nyújtott, a C lehetõségeit felülmúló képességek jelentõsebbek lettek, újra és újra felmerült a kérdés, megtartsuk-e a két nyelv összeegyeztethetõségét. Világos, hogy néhány probléma elkerülhetõ lett volna, ha némelyik C örökséget elutasítjuk (lásd pl. [Sethi, 1981]). Ezt nem tettük meg, a következõk miatt:

Forrás: http://www.doksi.hu

18

Bevezetés

1. Több millió sornyi C kód van, mely élvezheti a C++ elõnyeit, feltéve, hogy szükségtelen a C-rõl C++-ra való teljes átírás. 2. Több millió sornyi C-ben írt könyvtári függvény és eszközillesztõ kód van, melyet C++ programokból/programokban használni lehet, feltéve, hogy a C++ program összeszerkeszthetõ és formailag összeegyeztethetõ a C programmal. 3. Programozók százezrei léteznek, akik ismerik a C-t és ezért csak a C++ új tulajdonságait kell megtanulniuk, vagyis nem kell az alapokkal kezdeniük. 4. A C++-t és a C-t ugyanazok, ugyanazokon a rendszereken fogják évekig használni, tehát a különbségek vagy nagyon nagyok, vagy nagyon kicsik lesznek, hogy a hibák és a keveredés lehetõsége a lehetõ legkisebbre csökkenjen. A C++-t felülvizsgáltuk, hogy biztosítsuk, hogy azon szerkezetek, melyek mind a C-ben, mind a C++-ban megengedettek, mindkét nyelvben ugyanazt jelentsék (§B.2). A C nyelv maga is fejlõdött, részben a C++ fejlesztésének hatására [Rosler, 1984]. Az ANSI C szabvány [C,1990] a függvénydeklarációk formai követelményeit az „osztályokkal bõvített C”-bõl vette át. Az átvétel mindkét irányban elõfordul: a void* mutatótípust például az ANSI C-hez találták ki, de elõször a C++-ban valósították meg. Mint ahogy e könyv elsõ kiadásában megígértük, a C++-t felülvizsgáltuk, hogy eltávolítsuk az indokolatlan eltéréseket, így a C++ ma jobban illeszkedik a C-hez, mint eredetileg. Az elképzelés az volt, hogy a C++ olyan közel legyen az ANSI C-hez, amennyire csak lehetséges – de ne közelebb [Koenig, 1989]. A száz százalékos megfelelõség soha nem volt cél, mivel ez megalkuvást jelentene a típusbiztonságban, valamint a felhasználói és beépített típusok zökkenésmentes egyeztetésében. A C tudása nem elõfeltétele a C++ megtanulásának. A C programozás sok olyan módszer és trükk használatára biztat, melyeket a C++ nyelvi tulajdonságai szükségtelenné tettek. Az explicit típuskényszerítés például ritkábban szükséges a C++-ban, mint a C-ben (§1.6.1). A jó C programok azonban hajlanak a C++ programok felé. A Kernighan és Ritchie féle A C programozási nyelv (Mûszaki könyvkiadó, második kiadás, 1994) [Kernighan,1988] címû kötetben például minden program C++ program. Bármilyen statikus típusokkal rendelkezõ nyelvben szerzett tapasztalat segítséget jelent a C++ tanulásánál.

1.6.1. Javaslatok C programozóknak
Minél jobban ismeri valaki a C-t, annál nehezebbnek látja annak elkerülését, hogy C stílusban írjon C++ programot, lemondva ezáltal a C++ elõnyeirõl. Kérjük, vessen az olvasó egy pillantást a „B” függelékre, mely leírja a C és a C++ közti különbségeket. Íme néhány terület, ahol a C++ fejlettebb, mint a C:

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

19

1. A C++-ban a makrókra majdnem soha sincs szükség. A névvel ellátott állandók meghatározására használjunk konstanst (const) (§5.4) vagy felsorolást (enum) (§4.8), a függvényhívás okozta többletterhelés elkerülésére helyben kifejtett függvényeket (§7.1.1), a függvény- és típuscsaládok leírására sablonokat (13. fejezet), a névütközések elkerülésére pedig névtereket (§8.2). 2. Ne vezessünk be egy változót, mielõtt szükség van rá, így annak azonnal kezdõértéket is adhatunk. Deklaráció bárhol lehet, ahol utasítás lehet (§6.3.1), így for utasítások (§6.3.3) és elágazások feltételeiben (§6.3.2.1) is. 3. Ne használjunk malloc()-ot, a new operátor (§6.2.6) ugyanazt jobban elvégzi. A realloc() helyett próbáljuk meg a vector-t (§3.8). 4. Próbáljuk elkerülni a void* mutatókkal való számításokat, az uniókat és típuskonverziókat (típusátalakításokat), kivéve, ha valamely függvény vagy osztály megvalósításának mélyén találhatók. A legtöbb esetben a típuskonverzió a tervezési hiba jele. Ha feltétlenül erre van szükség, az „új cast-ok” (§6.2.7) egyikét próbáljuk használni szándékunk pontosabb leírásához. 5. Csökkentsük a lehetõ legkevesebbre a tömbök és a C stílusú karakterláncok használatát. A C++ standard könyvtárának string (§3.5) és vector (§3.7.1) osztályai a hagyományos C stílushoz képest gyakrabban használhatók a programozás egyszerûbbé tételére. Általában ne próbáljunk magunk építeni olyat, ami megvan a standard könyvtárban. Ahhoz, hogy eleget tegyünk a C szerkesztési szabályainak, a C++ függvényeket úgy kell megadnunk, hogy szerkesztésük C módú legyen. (§9.2.4). A legfontosabb, hogy úgy próbáljunk egy programot elképzelni, mint egymással kölcsönhatásban lévõ fogalmakat, melyeket osztályok és objektumok képviselnek, nem pedig úgy, mint egy halom adatszerkezetet, a bitekkel zsonglõrködõ függvényekkel.

1.6.2. Javaslatok C++ programozóknak
Sokan már egy évtized óta használják a C++-t. Még többen használják egyetlen környezetben és tanultak meg együtt élni a korai fordítók és elsõ generációs könyvtárak miatti korlátozásokkal. Ami a tapasztalt C++ programozók figyelmét gyakran elkerüli, nem is annyira az új eszközök megjelenése, mint inkább ezen eszközök kapcsolatainak változása, ami alapjaiban új programozási módszereket követel meg. Más szóval, amire annak idején nem gondoltunk vagy haszontalannak tartottunk, ma már kiváló módszerré válhatott, de ezekre csak az alapok újragondolásával találunk rá.

Forrás: http://www.doksi.hu

20

Bevezetés

Olvassuk át a fejezeteket sorban. Ha már ismerjük a fejezet tartalmát, gondolatban ismételjük át. Ha még nem ismerjük, valami olyat is megtanulhatunk, amire eredetileg nem számítottunk. Én magam elég sokat tanultam e könyv megírásából, és az a gyanúm, hogy kevés C++ programozó ismeri az összes itt bemutatott összes eszközt és eljárást. Ahhoz, hogy helyesen használjunk egy nyelvet, behatóan kell ismernünk annak eszközeit, módszereit. Felépítése és példái alapján ez a könyv megfelelõ rálátást biztosít.

1.7. Programozási megfontolások a C++-ban
A programtervezést ideális esetben három fokozatban közelítjük meg. Elõször tisztán érthetõvé tesszük a problémát (elemzés, analízis), ezután azonosítjuk a fõ fogalmakat, melyek egy megoldásban szerepelnek (tervezés), végül a megoldást egy programban fejezzük ki (programozás). A probléma részletei és a megoldás fogalmai azonban gyakran csak akkor válnak tisztán érthetõvé, amikor egy elfogadhatóan futtatható programban akarjuk kifejezni azokat. Ez az, ahol számít, milyen programozási nyelvet választunk. A legtöbb alkalmazásban vannak fogalmak, melyeket nem könnyû a kapcsolódó adatok nélkül az alaptípusok egyikével vagy függvénnyel ábrázolni. Ha adott egy ilyen fogalom, hozzunk létre egy osztályt, amely a programban képviselni fogja. A C++ osztályai típusok, melyek meghatározzák, hogyan viselkednek az osztályba tartozó objektumok, hogyan jönnek létre, hogyan kezelhetõk és hogyan szûnnek meg. Az osztály leírhatja azt is, hogyan jelennek meg az objektumok, bár a programtervezés korai szakaszában ez nem szükségszerûen fõ szempont. Jó programok írásánál az a legfontosabb, hogy úgy hozzunk létre osztályokat, hogy mindegyikük egyetlen fogalmat, tisztán ábrázoljon. Ez általában azt jelenti, hogy a következõ kérdésekre kell összpontosítani: Hogyan hozzuk létre az osztály objektumait? Másolhatók-e és/vagy megsemmisíthetõk-e az osztály objektumai? Milyen mûveletek alkalmazhatók az objektumokra? Ha nincsenek jó válaszok e kérdésekre, az a legvalószínûbb, hogy a fogalom nem „tiszta”. Ekkor jó ötlet, ha tovább gondolkodunk a problémán és annak javasolt megoldásán, ahelyett, hogy azonnal elkezdenénk a kód kidolgozását. A legkönnyebben kezelhetõ fogalmak azok, amelyeknek hagyományos matematikai megfogalmazásuk van: mindenfajta számok, halmazok, geometriai alakzatok stb. A szövegközpontú bemenet és kimenet, a karakterláncok, az alaptárolók, az ezekre a tárolókra alkalmazható alap-algoritmusok, valamint néhány matematikai osztály a C++ standard könyvtárának részét képezik (3. fejezet, §16.1.2). Ezenkívül elképesztõ választékban léteznek könyvtárak, melyek általános és részterületekre szakosodott elemeket támogatnak.

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

21

Az egyes fogalmak (és a hozzájuk kapcsolódó elemek) nem légüres térben léteznek, mindig rokonfogalmak csoportjába tartoznak. Az osztályok közti kapcsolatok szervezése egy programon belül – vagyis az egy megoldásban szereplõ különbözõ elemek közti pontos kapcsolatok meghatározása – gyakran nehezebb, mint az egyes osztályokat kijelölni. Jobb, ha az eredmény nem rendetlenség, melyben minden osztály függ minden másiktól. Vegyünk két osztályt: A-t és B-t. Az olyan kapcsolatok, mint az „A hív B-beli függvényeket”, „A létrehoz B-ket” és „A-nak van egy B tagja” ritkán okoznak nagy problémát, míg az olyanok, mint az „A használ B-beli adatot” rendszerint kiküszöbölhetõk. Az összetettség kezelésének egyik legerõsebb eszköze a hierarchikus rendezés, vagyis a rokon elemek faszerkezetbe szervezése, ahol a fa gyökere a legáltalánosabb elem. A C++-ban a származtatott osztályok ilyen fastruktúrákat képviselnek. Egy program gyakran úgy szervezhetõ, mint fák halmaza, vagy mint osztályok irányított körmentes gráfja. Vagyis a programozó néhány alaposztályt hoz létre, melyekhez saját származtatott osztályaik halmaza tartozik. Az elemek legáltalánosabb változatának (a bázisosztálynak) a kezelését végzõ mûveletek meghatározására a virtuális függvényeket (§2.5.5, §12.2.6) használhatjuk. Szükség esetén ezen mûveletek megvalósítása az egyedi esetekben (a származtatott osztályoknál) finomítható. Néha még az irányított körmentes gráf sem látszik kielégítõnek a programelemek szervezésére; egyes elemek kölcsönös összefüggése öröklöttnek tûnik. Ilyen esetben megpróbáljuk a ciklikus függõségeket behatárolni, hogy azok ne befolyásolják a program átfogó rendszerét. Ha nem tudjuk kiküszöbölni vagy behatárolni az ilyen kölcsönös függéseket, valószínû, hogy olyan gondban vagyunk, melybõl nincs programozási nyelv, amely kisegítene. Hacsak ki nem tudunk eszelni könnyen megállapítható kapcsolatokat az alapfogalmak között, valószínû, hogy a program kezelhetetlenné válik. A függõségi gráfok kibogozásának egyik eszköze a felület (interfész) tiszta elkülönítése a megvalósítástól (implementáció). A C++ erre szolgáló legfontosabb eszközei az absztrakt osztályok (§2.5.4, §12.3). A közös tulajdonságok kifejezésének másik formája a sablon (template, §2.7, 13. fejezet). Az osztálysablonok osztályok családját írják le. Egy listasablon például a „T elemek listáját” határozza meg, ahol „T” bármilyen típus lehet. A sablon tehát azt adja meg, hogyan hozhatunk létre egy típust egy másik típus, mint paraméter átadásával. A legszokásosabb sablonok az olyan tárolóosztályok, mint a listák, tömbök és asszociatív tömbök, valamint az ilyen tárolókat használó alap-algoritmusok. Rendszerint hiba, ha egy osztály és a vele kapcsolatos függvények paraméterezését öröklést használó típussal fejezzük ki. A legjobb sablonokat használni.

Forrás: http://www.doksi.hu

22

Bevezetés

Emlékeztetünk arra, hogy sok programozási feladat egyszerûen és tisztán elvégezhetõ elemi típusok, adatszerkezetek, világos függvények és néhány könyvtári osztály segítségével. Az új típusok leírásában szereplõ teljes apparátust nem szabad használni, kivéve, ha valóban szükség van rá. A „Hogyan írjunk C++-ban jó programot?” nagyon hasonlít a „Hogyan írjunk jó prózát?” kérdésre. Két válasz van: „Tudnunk kell, mit akarunk mondani” és „Gyakoroljunk. Színleljük a jó írást.” Mind a kettõ éppúgy helytálló a C++, mint bármely természetes nyelv esetében – és tanácsukat éppolyan nehéz követni.

1.8. Tanácsok
Íme néhány „szabály”, amelyet figyelembe vehetünk a C++ tanulásakor. Ahogy jártasabbak leszünk, továbbfejleszthetjük ezeket saját programfajtáinkhoz, programozási stílusunkhoz illeszkedõen. A szabályok szándékosan nagyon egyszerûek, így nélkülözik a részleteket. Ne vegyük õket túlzottan komolyan: a jó programok írásához elsõsorban intelligencia, ízlés, türelem kell. Ezeket nem fogjuk elsõre elsajátítani. Kísérletezzünk! [1] Amikor programozunk, valamilyen probléma megoldására született ötleteink konkrét megvalósítását hozzuk létre. Tükrözze a program szerkezete olyan közvetlenül ezeket az ötleteket, amennyire csak lehetséges: a) Ha valamire úgy gondolunk, mint külön ötletre, tegyük osztállyá. b) Ha különálló egyedként gondolunk rá, tegyük egy osztály objektumává. c) Ha két osztálynak van közös felülete, tegyük ezt a felületet absztrakt osztállyá. d) Ha két osztály megvalósításában van valami közös, tegyük bázisosztállyá e közös tulajdonságokat. e) Ha egy osztály objektumok tárolója, tegyük sablonná. f) Ha egy függvény egy tároló számára való algoritmust valósít meg, tegyük függvénysablonná, mely egy tárolócsalád algoritmusát írja le. g) Ha osztályok, sablonok stb. egy halmazán belül logikai rokonság van, tegyük azokat közös névtérbe. [2] Ha olyan osztályt hozunk létre, amely nem matematikai egyedet ír le (mint egy mátrix vagy komplex szám) vagy nem alacsonyszintû típust (mint egy láncolt lista) a) ne használjunk globális adatokat (használjunk tagokat), b) ne használjunk globális függvényeket,

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

23

c) ne használjunk nyilvános adattagokat, d) ne használjunk „barát” (friend) függvényeket, kivéve a) vagy c) elkerülésére, e) ne tegyünk egy osztályba típusazonosító mezõket, használjunk inkább virtuális függvényeket, f) ne használjunk helyben kifejtett függvényeket, kivéve ha jelentõs optimalizálásról van szó. Egyedi és részletesebb gyakorlati szabályokat az egyes fejezetek „Tanácsok” részében találhatunk. Emlékeztetjük az olvasót, hogy ezek a tanácsok csak útmutatásul szolgálnak, nem megváltoztathatatlan törvények. A tanácsokat csak ott kövessük, ahol értelme van. Nincs pótszere az intelligenciának, a tapasztalatnak, a józan észnek és a jó ízlésnek. A „soha ne tegyük ezt” alakú szabályokat haszontalannak tekintem. Következésképpen a legtöbb tanácsot javaslatként fogalmaztam meg; azt írtam le, mit tegyünk. A „negatív javaslatokat” pedig nem úgy kell érteni, mint tiltásokat: nem tudok a C++ olyan fõ tulajdonságáról, melyet ne láttam volna jól felhasználni. A „Tanácsok” nem tartalmaznak magyarázatokat. Helyette minden tanács mellett hivatkozás található a könyv megfelelõ részére. Ahol negatív tanács szerepel, a hivatkozott rész rendszerint alternatív javaslatot tartalmaz.

1.8.1. Hivatkozások
Barton, 1994 Berg, 1995 Booch, 1994 Budge, 1992 John J. Barton and Lee R. Nackman: Scientific and Engineering C++. AddisonWesley. Reading, Mass. 1994. ISBN 1-201-53393-6. William Berg, Marshall Cline, and Mike Girou: Lessons Learned from the OS/400 OO Project. CACM. Vol. 38 No. 10. October 1995. Grady Booch: Object-Oriented Analysis and Design. Benjamin/Cummings. Menlo Park, Calif. 1994. ISBN 0-8053-5340-2. Kent Budge, J. S. Perry, an A. C. Robinson: High-Performance Scientific Computation using C++. Proc. USENIX C++Conference. Portland, Oregon. August 1992. X3 Secretariat: Standard - The C Language. X3J11/90-013. ISO Standard ISO/IEC 9899. Computer and Business Equipment Manufacturers Association. Washington, DC, USA. X+ Secretariat: International Standard- The C++ Language. X3J16-14882. Information Technology Council (NSITC). Washington, DC, USA. Roy Campbell, et al.: The Design of a Multirocessor Operating System. Proc. USENIX C++ Conference. Santa Fe, New Mexico. November 1987. James O. Coplien and Douglas C. Schmidt (editors): Pattern Languages of Program Design. Addison-Wesley. Reading, Mass. 1995. ISBN 1-201-60734-4.

C, 1990

C++, 1998 Campbell, 1987 Coplien, 1995

Forrás: http://www.doksi.hu

24

Bevezetés

O-J. Dahl, B. Myrhaug, and K. Nygaard: SIMULA Common Base Language. Norwegian Computing Center S-22. Oslo, Norway. 1970. Dahl, 1972 O-J. Dahl, and C. A. R. Hoare: Hierarchical Program Consturction in Structured Programming. Academic Press, New York. 1972. Ellis, 1989 Margaret A. Ellis and Bjarne Stroustrup: The Annotated C++ Reference Manual. Addison-Wesley. Reading, Mass. 1990. ISBN 0-201-51459-1. Gamma, 1995 Erich Gamma, et al.: Design Patterns. Addison-Wesley. Reading, Mass. 1995. ISBN 0-201-63361-2. Goldberg, 1983 A. Goldberg and D. Robson: SMALLTALK- 80 - The Language and Its Implementation. Addison-Wesley. Reading, Mass. 1983. Griswold, 1970 R. E. Griswold, et al.: The Snobol4 Programming Language. Prentice-Hall. Englewood Cliffs, New Jersey. 1970. Griswold, 1983 R. E. Grisswold and M. T. Griswold: The ICON Programming Language. PrenticeHall. Englewood Cliffs, New Jersey. 1983. Hamilton, 1993 G. Hamilton and P. Kougiouris: The Spring Nucleus: A Microkernel for Objects. Proc. 1993 Summer USENIX Conference. USENIX. Henricson, 1997 Mats Henricson and Erik Nyquist: Industrial Strenght C++: Rules and Recommendations. Prentice-Hall. Englewood Cliffs, New Jersey. 1997. ISBN 0-13120965-5. Ichbiah, 1979 Jean D. Ichbiah, et al.: Rationale for the Design of the ADA Programming Language. SIGPLAN Notices. Vol. 14 No. 6. June 1979. Kamath, 1993 Yogeesh H. Kamath, Ruth E. Smilan, and Jean G. Smith: Reaping Benefits with Object-Oriented Technology. AT&T Technical Journal. Vol. 72 No. 5. September/October 1993. Kernighan, 1978 Brian W. Kernighan and Dennis M. Ritchie: The C Programming Language. Prentice-Hall. Englewood Cliffs, New Jersey. 1978. Kernighan, 1988 Brian W. Kernighan and Dennis M. Ritchie: The C Programming Language (Second Edition). Prentice-Hall. Enlewood Cliffs, New Jersey. 1988. ISBN 0-13110362-8. Koenig, 1989 Andrew Koenig and Bjarne Stroustrup: C++: As close to C as possible - but no closer. The C++ Report. Vol. 1 No. 7. July 1989. Koenig, 1997 Andrew Koenig and Barbara Moo: Ruminations on C++. Addison Wesley Longman. Reading, Mass. 1997. ISBN 1-201-42339-1. Knuth, 1968 Donald Knuth: The Art of Computer Programming. Addison-Wesley. Reading, Mass. Liskowv, 1979 Barbara Liskov et al.: Clu Reference Manual. MIT/LCS/TR-225. MIT Cambridge. Mass. 1979. Martin, 1995 Robert C. Martin: Designing Object-Oriented C++ Applications Using the Booch Method. Prentice-Hall. Englewood Cliffs, New Jersey. 1995. ISBN 0-13-203837-4. Orwell, 1949 George Orwell: 1984. Secker and Warburg. London. 1949.

Dahl, 1970

Forrás: http://www.doksi.hu

1. Megjegyzések az olvasóhoz

25

Parrington, 1995 Graham Parrington et al.: The Design and Implementation of Arjuna. Computer Systems. Vol. 8 No. 3. Summer 1995. Richards, 1980 Martin Richards and Colin Whitby-Strevens: BCPL - The Language and Its Compiler. Cambridge University Press, Cambridge. England. 1980. ISBN 0-52121965-5. Rosler, 1984 L. Rosler: The Evolution of C - Past and Future. AT&T Bell Laboratories Technical Journal. Vol. 63 No. 8. Part 2. October 1984. Rozier, 1988 M. Rozier, et al.: CHORUS Distributed Operating Systems. Computing Systems. Vol. 1 no. 4. Fall 1988. Sethi, 1981 Ravi Sethi: Uniform Syntax for Type Expressions and Declarations. Software Practice & Experience. Vol. 11. 1981. Stepanov, 1994 Alexander Stepanov and Meng Lee: The Standard Template Library. HP Labs Technical Report HPL-94-34 (R. 1). August, 1994. Stroustrup, 1986 Bjarne Stroustrup: The C++ Programming Language. Addison-Wesley. Reading, Mass. 1986. ISBN 0-201-12078-X. Stroustrup, 1987 Bjarne Stroustrup and Jonathan Shopiro: A Set of C Classes for Co-Routine Style Programming. Proc. USENIX C++ conference. Santa Fe, New Mexico. November 1987. Stroustrup, 1991 Bjarne Stroustrup: The C++ Programming Language (Second Edition) AddisonWesley. Reading, Mass. 1991. ISBN 0-201-53992-6. Strostrup, 1994 Bjarne Stroustrup: The Design and Evolution of C++. Addison-Wesley. Reading, Mass. 1994. ISBN 0-201-54330-3. Tarjan, 1983 Robert E. Tarjan: Data Structures and Network Algorithms. Society for Industrial and Applied Mathematics. Philadelphia, Penn. 1983. ISBN 0-898-71187-8. Unicode, 1996 The Unicode Consortium: The Unicode Standard, Version 2.0. Addison-Wesley Developers Press. Reading, Mass. 1996. ISBN 0-201-48345-9. UNIX, 1985 UNIX Time-Sharing System: Programmer's Manual. Research Version, Tenth Edition. AT&T Bell Laboratories, Murray Hill, New Jersey. February 1985. Wilson, 1996 Gregory V. Wilson and Paul Lu (editors): Parallel Progrmming Using C++. The MIT Press. Cambridge. Mass. 1996. ISBN 0-262-73118-5. Wikström, 1987 Ake Wikström: Functional Programming Using ML. Prentice-Hall. Englewood Cliffs, New Jersey. 1987. Woodward, 1974 P. M. Woodward and S. G. Bond: Algol 68-R Users Guide. Her Majesty's Stationery Office. London. England. 1974.

melynek fõ alkalmazási területe a rendszerprogramozás és ♦ ♦ ♦ ♦ egy jobbfajta C. rész – ford.1.Forrás: http://www. . Mi a C++? A C++ általános célú programozási nyelv.hu 2 Kirándulás a C++-ban „Az elsõ tennivalónk: öljünk meg minden törvénytudót” (Shakespeare: VI.doksi. Németh László) Mi a C++? • Programozási megközelítések • Eljárásközpontú programozás • Modularitás • Külön fordítás • Kivételkezelés • Elvont adatábrázolás • Felhasználói típusok • Konkrét típusok • Absztrakt típusok • Virtuális függvények • Objektumorientált programozás • Általánosított programozás • Tárolók • Algoritmusok • Nyelv és programozás • Tanácsok 2. támogatja az objektumorientált programozást. támogatja az elvont adatábrázolást. II. valamint az általánosított programozást. Henrik.

mivel ezek a nyelvek nem támogatják közvetlenül az említett megközelítéseket.doksi. hogy késõbb visszatér rájuk. Az egyes programozási módok támogatása nem csak az adott megközelítés közvetlen használatát lehetõvé tévõ nyelvi szolgáltatások magától értetõdõ formájában rejlik. ha hiányzik az átfogó szemléletünk a nyelvrõl és használatának alapvetõ módszereirõl. A nyelvi tulajdonságok részletes megértése – még ha a nyelv összes tulajdonságáé is – nem ellensúlyozhatja azt. további támogatást adnak az egyes megközelítési módokhoz. nem pedig a C++ programozás elkezdéséhez szükséges részletes információt adni az olvasónak. Mindenesetre. . amely az objektumokat középpontba helyezõ programozási stílust támogató eljárásokról gondoskodik. Ha az „objektumorientált programozási nyelv” szakkifejezés egyáltalán jelent valamit. Itt fontos megkülönböztetnünk két fogalmat: egy nyelvrõl akkor mondjuk. de szükségtelenül nehezen. ekkor a nyelv csupán megengedi. 2. ha olyan szolgáltatásai vannak. A típusellenõrzés erre a legkézenfekvõbb példa. melyek védelmet adnak a stílustól való akaratlan eltérés ellen. olyan programozási nyelvet kell hogy jelentsen. tegye meg magának azt a szívességet. hogy támogat egy programozási stílust. ha kivételes erõfeszítés vagy ügyesség kell az ilyen programok írásához.Forrás: http://www. Ha az olvasó túl elnagyoltnak találja e fejezet némelyik részének tárgyalásmódját. egyszerûen ugorja át és lépjen tovább. A nyelven kívüli szolgáltatások. ha átugrik részeket a fejezetben. Lehet strukturált programot írni Fortran77-ben és objektumközpontút C-ben. Célja általános áttekintést adni a C++-ról és használatának fõ módszereirõl. mint a könyvtárak és programozási környezetek.hu 28 Bevezetés Ez a fejezet elmagyarázza. Programozási megközelítések Az objektumorientált (objektumközpontú) programozás egy programozási mód – a „jó” programok írása közben felmerülõ sereg probléma megoldásának egy megközelítése (paradigma). hogy belemenne a nyelv meghatározásának finomabb részleteibe. A támogatás hiányzik. anélkül. Késõbb mindenre részletes magyarázatot kap. de a kétértelmûség észlelése és a futási idejû ellenõrzések szintén a programozási módok nyelvi támogatásához tartoznak. mit jelentenek a fentiek. hogy az adott megközelítést használjuk. hanem a fordítási/futási idõbeni ellenõrzések finomabb formáiban.2. melyek által az adott stílus használata kényelmes (könnyû. biztonságos és hatékony) lesz.

Sok példa van ennek az ellenkezõjére is. hogy az elvont adatábrázolást.és „speciális célú” tulajdonság. A fentiek közül az elsõ elv az esztétikához és a logikához való folyamodás. A C++-t úgy terveztük. Ennek kívánalmai a következõk: 1. .Forrás: http://www. A hangsúly a tervezési megközelítéseken és a programok szerkezeti felépítésén van. ha kifejezetten használja azt egy program írásához. hogy a meglévõ tulajdonságok elegendõek-e a kívánt programozási stílusok támogatására a kívánt alkalmazási területeken. nem a nyelvi részleteken. A következõ kettõ a minimalizmus gondolatának kifejezése. csak azért. A lehetõ legkevesebb legyen az ál. melyhez egyébként külön nyelvi tulajdonságok lennének szükségesek. mégpedig az e megszorítások mellett támogatott hagyományos C programozási módszereken kívül. 3. hogy olyan megoldást adjanak. mint hogy megértsük. A fontos kérdés nem annyira az. az utolsó kettõ pedig így összesíthetõ: „amirõl nem tudunk. illetve az objektumorientált és az általánosított programozást támogassa. Ezen a szinten sokkal fontosabb. A nyelvi tulajdonságok bemutatása nem teljes. hogy minden felhasználóra egyetlen programozási stílust kényszerítsen. A következõkben néhány programozási stílust és az azokat támogató fõbb tulajdonságokat vesszük számba. Kirándulás a C++-ban 29 Egy nyelv nem szükségszerûen jobb. amelyek a másikban nem találhatók meg. Minden tulajdonság tisztán és „elegánsan” a nyelv szerves része legyen. Minden megközelítés az elõdjére épül. hogy fogalmat kapjunk arról. mindegyik hozzátesz valamit a C++ programozók eszköztárához. hogyan. melyek nem igénylik azokat. az nem fáj”. hanem inkább az. 2. mint egy másik. A tulajdonságokat egymással párosítva is lehessen használni. mit lehet megtenni C++-t használva. melyek az eljárásközpontú (procedurális) programozástól elvezetnek az objektumorientált programozásban használt osztályhierarchiáig és a sablonokat használó általánosított (generikus) programozásig. 5. milyen tulajdonságai vannak egy nyelvnek. Az egyes tulajdonságok megvalósítása nem okozhat jelentõs többletterhelést olyan programoknál. Nem arra szolgál. A bemutatás egy sor programozási eljárással folytatódik. 4. és mindegyik egy bevált tervezési módot tükröz. A felhasználónak csak akkor kell tudnia a nyelv valamely részhalmazáról. mert olyan tulajdonságokkal rendelkezik.doksi.hu 2.

hu 30 Bevezetés 2. // .3.doksi. A középpontban az eljárás áll – a kívánt számításhoz szükséges algoritmus. Ezt egy jól érthetõ matematikai számítással éri el: double sqrt(double arg) { // a négyzetgyök kiszámításának kódja } void f() { double root2 = sqrt(2). } A kapcsos zárójelek a C++-ban valamilyen csoportba foglalást fejeznek ki. hogy rendet teremtsünk az eljárások labirintusában.Forrás: http://www. makrók stb. Eljárásközpontú programozás Az eredeti programozási alapelv a következõ: Döntsd el. mely eljárásokra van szükséged és használd azokhoz a lehetõ legjobb algoritmusokat. A kettõs törtvonal // egy megjegyzés (comment) kezdete. Magukat az algoritmusokat függvényhívásokkal és más nyelvi szolgáltatások használatával írjuk meg. Az e gondolkodásmóddal kapcsolatos irodalom tele van a paraméterátadás és a különbözõ paraméterfajták megkülönböztetési módjainak (eljárások.) tárgyalásával. rutinok. hogy az f függvény nem ad vissza értéket. mely a sor végéig tart.. itt a függvény törzsének kezdetét és a végét jelzik. . A „jó stílus” jellegzetes példája az alábbi négyzetgyök-függvény. Átadva egy kétszeres pontosságú lebegõpontos paramétert. A void kulcsszó jelzi. a függvény visszaadja az eredményt. A következõ alpontok vázlatos képet adnak a C++ legalapvetõbb szolgáltatásairól a számítások kifejezéséhez.. A nyelvek ezt az alapelvet függvényparaméterek átadásával és a függvények által visszaadott értékekkel támogatják. Programszervezési szempontból a függvényeket arra használjuk.

így azokat egymással tetszés szerint keverhetjük: . A típus egy név vagy kifejezés megfelelõ használatát határozza meg. amely meghatározza a végrehajtható mûveleteket: int inch.14 vagy 299793.0 A char változók természetes mérete egy karakter mérete az adott gépen (rendesen egy bájt). Például: bool char int double // logikai típus. A fenti deklaráció például azt adja meg. hogy inch típusa int (vagyis inch egy egész típusú változó). A C++ több alaptípussal rendelkezik. egy.és kétoperandusú is lehet // szorzás // osztás // maradékképzés Ugyanígy az összehasonlító mûveletek is: == != < > <= >= // egyenlõ // nem egyenlõ // kisebb // nagyobb // kisebb vagy egyenlõ // nagyobb vagy egyenlõ Értékadásokban és aritmetikai mûveletekben a C++ az alaptípusok között elvégez minden értelmes átalakítást. melyek közvetlen megfelelõi bizonyos hardverszolgáltatásoknak.hu 2. vagy 1216 // kétszeres pontosságú lebegõpontos szám. egy. mely a programba egy nevet vezet be. lehetséges értékei: true (igaz) és false (hamis) // karakter. 'z'. Változók és aritmetika Minden névnek és kifejezésnek típusa van. A deklaráció olyan utasítás. Az aritmetikai mûveletek e típusok bármilyen párosítására használhatók: + * / % // összeadás vagy elõjel.doksi. az int változóké az adott gépen mûködõ egész típusú aritmetikához igazodik (rendszerint egy gépi szó).Forrás: http://www. például 3.és kétoperandusú is lehet // kivonás vagy elõjel. Ehhez a névhez egy típust rendel. például 1.1. például 'a'. 42. Kirándulás a C++-ban 31 2. vagy '9' // egész érték.3.

Forrás: http://www.doksi. mely a felhasználótól választ kér és a választól függõ logikai értéket ad vissza: bool accept() { cout << "Do you want to proceed (y or n)?\n". A példa kissé javítható. // kérdés kiírása // válasz beolvasása . if (answer == 'y') return true. // kérdés kiírása // válasz beolvasása } A << („tedd bele”) mûveleti jelet kimeneti operátorként használtuk. char answer = 0. char answer = 0. milyen bemenet fogadható el és ez a beolvasás célpontja. mint a C-ben. a cin a szabványos bemenõ adatfolyam. i = d*i. Elágazások és ciklusok A C++ az elágazások és ciklusok kifejezésére rendelkezik a hagyományos utasításkészlettel.hu 32 Bevezetés void some_function() { double d = 2. } // értéket vissza nem adó függvény // lebegõpontos szám kezdeti értékadása // egész kezdeti értékadása // összeg értékadása // szorzat értékadása Itt = az értékadó mûvelet jele és == az egyenlõséget teszteli. a cout a szabványos kimeneti adatfolyam. d = d+i. A >> („olvasd be”) a bemenet mûveleti jele. A \n karakter a kiírt karakterlánc végén új sort jelent.3. return false.2. A >> jobb oldalán álló kifejezés határozza meg. Íme egy egyszerû függvény. cin >> answer. 2. cin >> answer.2. int i = 7. ha egy 'n' választ is számításba veszünk: bool accept2() { cout << "Do you want to proceed (y or n)?\n".

} A switch utasítás egy értéket ellenõriz. switch (answer) { case 'y': return true. // nemleges válasznak veszi return false.\n" . case 'n': return false. A programozónak nem kell alapértelmezésrõl (default) gondoskodnia. cin >> answer. A case konstansoknak különállóknak kell lenniük. Kevés programot írnak ciklusok nélkül. default: cout << "Sorry. // nem érti a választ tries = tries + 1. állandók halmazát alapul véve. Esetünkben szeretnénk lehetõséget adni a felhasználónak néhány próbálkozásra: bool accept3() { int tries = 1. . amíg a feltétele hamis nem lesz. a vezérlés a default címkére kerül.\n". // nemleges válasznak veszi return false. char answer = 0.\n". Kirándulás a C++-ban 33 } switch (answer) { case 'y': return true. default: cout << "I'll take that for a no. case 'n': return false.hu 2. és ha az érték egyikkel sem egyezik. // kérdés kiírása // válasz beolvasása } } cout << "I'll take that for a no.Forrás: http://www. I don't understand that.doksi. } A while utasítás addig hajtódik végre. while (tries < 4) { cout << "Do you want to proceed (y or n)?\n".

i<10. Mutatók és tömbök Egy tömböt így határozhatunk meg: char v[10]. Minden tömbnek 0 az alsó határa..Forrás: http://www. Ha egész típusú változóra alkalmazzuk. // p a v negyedik elemére mutat A „címe” jelentéssel bíró operátor az egyoperandusú &. míg a * jelentése „mutatója” (pointer to).. v[0]…v[9]. mint 10. az i-edik elemet másoljuk át. 2.3. és növeljük meg i-t”. Moduláris programozás Az évek során a programtervezés súlypontja az eljárások felõl az adatszervezés irányába tolódott el. // mutató karakterre A deklarációkban a [ ] jelentése „tömbje” (array of). tehát v-nek tíz eleme van. Egyebek mellett ez a programok nagyobb méretében tükrözõdik. int v2[10]. // 10 karakterbõl álló tömb Egy mutatót így: char* p. Az egymással rokon eljárásokat az általuk kezelt adatokkal együtt gyakran modul-nak nevezzük.4. ++i) v1[i]=v2[i]. a ++ növelõ mûveleti jel az értéket egyszerûen eggyel növeli.doksi. hogyan másolhatjuk át egy tömb tíz elemét egy másik tömbbe: void another_function() { int v1[10].3. Lássuk. } A for utasítás így olvasható: „állítsuk i-t 0-ra. // . A mutató változó a megfelelõ típusú objektum címét tartalmazhatja: p = &v[3]. A megközelítés alapelve ez lesz: . for (int i=0.hu 34 Bevezetés 2. amíg i kisebb.

} // felület void f() { Stack::push('c').Forrás: http://www. } A Stack:: minõsítés azt jelzi. E nevek máshol történõ használata nem lesz befolyással erre a programrészre és nem fog zavart okozni. push() és pop() függvények). A megoldandó fõ problémák: 1. . A Stack kifejtése lehet a program külön fordítható része: namespace Stack { // megvalósítás const int max_size = 200. A C++ egymással rokon adatok. char pop(). Ahol az eljárások nincsenek az adatokkal egy csoportban. az eljárásközpontú programozás megfelel a célnak. hogy a push() és a pop() a Stack névtérhez tartoznak. int top = 0. mely modulokra van szükség és oszd fel a programot úgy. 2. Egy Stack modul felhasználói felülete például így adható meg és használható: namespace Stack { void push(char). függvények stb. Biztosítani kell.doksi. Biztosítani kell a verem elsõ használat elõtti elõkészítését (inicializálását). különálló névterekbe való csoportosítására ad lehetõséget. Ez az adatrejtés elve. 3. hogy az adatokat modulokba helyezed. hogy a verem megjelenítése (pl. A modulra a legközönségesebb példa egy verem (stack) létrehozása. Kirándulás a C++-ban 35 Döntsd el. char v[max_size]. if (Stack::pop() != 'c') error("lehetetlen"). az elemek tömbje) csak ezen a felhasználói felületen keresztül legyen hozzáférhetõ. Gondoskodni kell a verem felhasználói felületérõl (pl. Az egyes modulokon belüli eljárásokra a „jó eljárások” tervezésének módja is alkalmazható.hu 2.

Ezt arra használhatjuk. } .1. Következésképpen a C++ bármilyen deklaráció elhelyezését megengedi egy névtérben (§8. hogy a felhasználói kódot a Stack::push()-t és a Stack::pop()-ot megvalósító kód elszigeteli a Stack adatábrázolásától. vagyis a függvények. Ennek következtében a namespace Stack { void push(char). jellemzõen egy fájlba írjuk. hogy hatással lenne a felhasználói kódra. nevei szintén modulba helyezhetõk.h nevû fájlba kerül. char pop(). hogy egy programot részben független részekre bontsunk.hu 36 Bevezetés } void push(char c) { /* túlcsordulás ellenõrzése és c behelyezése */ } char pop() { /* alulcsordulás ellenõrzése és a legfelsõ elem kiemelése */ } A Stack modul fontos jellemzõje.Forrás: http://www. } // felület a stack. A fenti Stack modul a verem egy ábrázolásmódja. az adatrejtés elve az információrejtés elvévé terjeszthetõ ki. A felhasználónak nem kell tudnia. Mivel az adat csak egyike az „elrejtendõ” dolgoknak. A következõkben többféle vermet használunk a különbözõ programozói stílusok szemléltetésére.).doksi. típusok stb.2. if (Stack::pop() != 'c') error("impossible"). melyek egy modul felületét írják le. melynek neve a használatot tükrözi. a megvalósítás pedig anélkül módosítható. Külön fordítás A C++ támogatja a C külön fordítási elvét.h" // a felület beépítése void f() { Stack::push('c').4. hogy a verem egy tömbbel van megvalósítva. 2. Azokat a deklarációkat. a felhasználók pedig ezt az úgynevezett fejállományt (header) beépítik (#include): #include "stack.

A program részei a következõképpen ábrázolhatók: stack. Pontosabban. A user. . ha a modularitást a lehetõ legnagyobb mértékig fokozzuk.h: veremfelület user. de a két fájl egyébként független és külön-külön lefordítható. a Stack modult megvalósító fájl szintén tartalmazza a felületet: #include "stack.Forrás: http://www.h" verem megvalósítása A külön fordítás követelmény minden valódi (tényleges használatra szánt) program esetében. és 9. Kirándulás a C++-ban 37 Ahhoz. nyelvi tulajdonságok által ábrázoljuk.h-ban megadott veremfelületet. fejezet). char v[max_size].c és a stack. majd külön-külön hatékonyan fordítható fájlokon keresztül valósítjuk meg (8.doksi.c fájlokban lévõ kód közösen használja a stack. A legjobb. hogyan lehet egy adott nyelvi megvalósítás elõnyeit a legjobban kihasználni. int top = 0.c: #include "stack.c).h" verem használata stack. } // a felület beépítése // ábrázolás void Stack::push(char c) { /* túlcsordulás ellenõrzése és c behelyezése */ } char Stack::pop() { /* alulcsordulás ellenõrzése és a legfelsõ elem kiemelése */ } A felhasználói kód egy harmadik fájlba kerül (user. inkább annak módja.h" namespace Stack { const int max_size = 200.hu 2. nem csak a moduláris felépítésûeknél (mint pl. a külön fordítás használata nem nyelvi követelmény. hogy a fordítónak segítsünk az egységesség és következetesség biztosításában.c: #include "stack. a Stack).

a hibakezelést a modulok szintjén kell elvégeznünk.hu 38 Bevezetés 2. vagyis „egy Overflow kivételt dobhat”: void Stack::push(char c) { if (top == max_size) throw Overflow(). Ehhez „visszatekerjük” a függvényhívási vermet. Mit kell tennünk. valamilyen függvényben. miközben megkísérelte a mûvelet végrehajtását.4. Kivételkezelés Ha egy programot modulokra bontunk.. A megoldás: a Stack írója kell. A helyreállítási tevékenység a mûveletet kezdeményezõ modultól függ. melyik modul felelõs az adott hiba kezeléséért? A hibát észlelõ modul gyakran nem tudja..doksi. Például: void f() { // . A kérdés. char pop(). hogy visszajussunk a hívó függvény környezetéhez. Így a throw úgy mûködik. mint egy többszintû return. amikor túl sok karaktert próbálunk push()sal egy verembe rakni? A verem modul írója nem tudja. hogy tudatában legyen a túlcsordulás veszélyének és ezután az (ismeretlen) felhasználóval tudatnia kell ezt. amelyik észlelte a hibát. // c behelyezése } A throw a vezérlést a Stack::Overflow típusú kivételkezelõnek adja át. A felhasználó majd megteszi a megfelelõ lépést: namespace Stack { void push(char).Forrás: http://www. A programok növekedésével – különösen kiterjedt könyvtárhasználat esetén – a hibák (vagy általánosabban: a „kivételes események”) kezelési szabványai egyre fontosabbá válnak. mely közvetve vagy közvetlenül meghívta a Stack::Push()-t. nem attól.2. // felület // túlcsordulást ábrázoló típus Túlcsordulás észlelésekor a Stack::Push() meghívhat egy kivételkezelõ kódot. ami ahhoz szükséges. Vegyük megint a Stack példát. try { // a kivételekkel az alább meghatározott kezelõ foglalkozik . mit kell tennie. hogy a felhasználó mit akar tenni ilyen esetben. a túlcsordulás nem történne meg). } class Overflow { }. a felhasználó pedig nem mindig észleli a hibát (ha így lenne.

Forrás: http://www. Elvont adatábrázolás A modularitás alapvetõ szempont minden sikeres nagy programnál.hu 2. } catch (Stack::Overflow) { // hoppá: verem-túlcsordulás. részletek és példák: §8.doksi. Az elõzõekben leírt alakú modulok azonban nem elegendõek ahhoz.5. Ha például sok vermet akarunk – az elõbbi Stack modulban található egyetlen helyett – megadhatunk egy veremkezelõt. stack create(). fejezet. hogyan küzdjünk le problémákat a felhasználói típusok közvetlen létrehozása révén. az alábbi felülettel: namespace Stack { struct Rep. a megfelelõ mûvelet végrehajtása } // .3. // a verem szerkezetének meghatározása máshol található // új verem létrehozása // s törlése . Az alábbiakban a modulok használatának egyik módjával foglalkozunk (felhasználói típusok létrehozása). További tárgyalás. majd megmutatjuk.5. Típusokat leíró modulok A modulokkal való programozás elvezet az összes azonos típusú adatnak egyetlen típuskezelõ modul általi központosított kezeléséhez. 2. hogy tisztán kifejezzünk összetett rendszereket. 14. E könyv minden tervezési vizsgálatában ez marad a középpontban. void destroy(stack s).. A while ciklus örökké ismétlõdne.1.. Kirándulás a C++-ban 39 } while (true) Stack::push('c'). ezért ha valamelyik Stack::push() hívás egy throw-t vált ki. „E” függelék. typedef Rep& stack. a vezérlés a Stack::Overflow-t kezelõ catch részhez kerül. 2. A kivételkezelõ eljárások használata szabályosabbá és olvashatóbbá teheti a hibakezelõ kódot.

typedef Rep& stack. Stack::destroy(s2).'c'). Stack::destroy(s1). így a Stack::create() újra használhatja azt: namespace Stack { const int max_size = 200. hogy a felhasználónak nem szükséges tudnia. Egy megvalósítás például elõre lefoglalhatna néhány verempéldányt és a Stack::create() egy nem használt példányra való hivatkozást adna át.hu 40 Bevezetés void push(stack s. // ábrázolás .Forrás: http://www. struct Rep. Amíg a felületet változatlanul hagyjuk. ha úgy döntünk. if (Stack::pop(s2) != 'k') throw Bad_pop(). Fontos. de késõbbre hagyja a típus meghatározását (§5. hogy Rep egy típus neve. Stack::push(s2. Az alábbi deklaráció a stack nevet adja egy „Rep referenciának” (részletek §5. char c). char pop(stack s).7). A Stack::stack mûködése nagyon hasonlít egy beépített típuséhoz: struct Bad_pop { }. Stack::stack s2 = Stack::create().'k'). hogy a vermet saját Stack::stack-jével azonosítjuk és a további részleteket a felhasználó elõl elrejtjük. Ezután a Stack::destroy() egy ábrázolást „nem használt”-ként jelölhet meg. hogy átírjuk a Stack-et.5-ben). if (Stack::pop(s1) != 'c') throw Bad_pop(). hogyan tesszük ezt.doksi. // c behelyezése az s verembe // s legfelsõ elemének kiemelése } A következõ deklaráció azt mondja. void f() { Stack::stack s1 = Stack::create(). a felhasználó nem fogja észrevenni. Az ötlet az. // új verem létrehozása // még egy verem létrehozása } Ezt a Stack-et többféleképpen megvalósíthatnánk. Stack::push(s1.

Forrás: http://www. részben attól. a Stack::stack-ek értékadási és elõkészítési (inicializálási) szabályai drámai módon megváltoztak volna (ami néha valóban kívánatos lehet). bool used[max]. Ez gyakran kevesebb az ideálisnál. és kisebb vagy más támogatást élveznek. Ez azonban azt mutatja. }. . használtként // megjelölése. az ábrázoló típus becsomagolása felületi függvények készletébe. nem a szokásos nyelvi szabályok. Még lényegesebb.hu 2. hogy a kényelmes vermek szolgáltatásának problémáját egyszerûen áttettük a Stack modulból a Stack::stack ábrázoló típusba. hogyan mutattuk be a Stack-et ábrázoló típust a verem felhasználóinak. mint azok. rá mutató hivatkozás visszaadása } void Stack::destroy(stack s) { /* s megjelölése nem használtként */ } Amit tettünk. } typedef Rep& stack. hogy mikor használható egy Stack::Rep. Az. nem úgy viselkednek. Rep stacks[max]. const int max = 16. Jelentõs probléma. ha stacks[i] használatban van void Stack::push(stack s.doksi. melyeket az adott megvalósító típushoz hozzáférést adó modul határozott meg. int top. hogy az ilyen „mûtípusoknak” a felhasználók részére való bemutatása az ábrázoló típus részleteitõl függõen nagyon változó lehet – a felhasználókat viszont el kell szigetelni az ábrázoló típus ismeretétõl. elõkészítése. hogy az eredményül kapott „stack típus” hogyan viselkedik. részben pedig magától az ábrázoló típustól. char c) { /* s túlcsordulásának ellenõrzése és c behelyezése */ } char Stack::pop(stack s) { /* s alulcsordulásának ellenõrzése és a legfelsõ elem kiemelése */ } Stack::stack Stack::create() { // használaton kívüli Rep kiválasztása. Ha például egy jobban kidolgozott adatszerkezetet választottunk volna a verem azonosítására. mint a beépített típusok. // vermek maximális száma // elõre lefoglalt verempéldányok // used[i] igaz. Kirándulás a C++-ban 41 struct Rep { char v[max_size]. hogy azok a felhasználói típusok. a Stack::create() és a Stack::destroy() függvény ellenõrzi. részben attól függ. Azt például. hogyan adtuk meg ezeket a felületi függvényeket.

friend bool operator==(complex. A szerzõ inkább a felhasználói típus (user-defined type) megnevezést kedveli. } complex(double r) { re=r. Ezeket az alábbi módon adhatjuk meg: . complex).. public: complex(double r. Az ábrázolás privát (private). Az elvont adattípus kifejezõbb meghatározásához „absztrakt” matematikai leírás kellene. Ha adva volna ilyen. friend complex operator/(complex.doksi. im. amiket itt típusoknak nevezünk.5. mint a beépített típusok. friend complex operator-(complex. A programozási megközelítés most ez lesz: Döntsd el. A complex osztály (vagyis felhasználói típus) deklarációja egy komplex számot és a rajta végrehajtható mûveletek halmazát ábrázolja. azok. Vegyük az alábbi kódot: class complex { double re. vagyis a re és az im csak a complex osztály bevezetésekor megadott függvények által hozzáférhetõ. Ott. complex). im=0. melyek közel úgy viselkednek. ADT) nevezzük. hogy a felhasználó közvetlenül adjon meg típusokat. mely típusokra van szükség és mindegyikhez biztosíts teljes mûveletkészletet. friend complex operator-(complex). } friend complex operator+(complex. elegendõ a modulokat használó adatrejtési stílus.0) // kétoperandusú // egyoperandusú // egyenlõ // nem egyenlõ }.. // . } complex() { re = im = 0. complex).hu 42 Bevezetés 2. friend bool operator!=(complex. Felhasználói típusok A C++ ezt a problémát úgy küzdi le. Az olyan aritmetikai típusok. complex). friend complex operator*(complex. közönséges példái a felhasználói típusnak. ahol egy típusból egy példánynál többre nincs szükség.2. complex). hogy engedi. im=i. az ilyen valóban elvont egyedek konkrét példányai lennének. Az ilyen típusokat gyakran elvont vagy absztrakt adattípusoknak (abstract data type. mint a racionális és komplex számok. complex). // complex létrehozása két skalárból // complex létrehozása egy skalárból // alapértelmezett complex: (0.Forrás: http://www. double i) { re=r.

Forrás: http://www. melynek neve megegyezik az osztályéval. complex c = a+b*complex(1. Stack(int s). a harmadik alapértelmezett érték alapján. 2. } Az a tagfüggvény. az 1/a jelentése operator/ (complex(1). // . Konkrét típusok Felhasználói típusok változatos igények kielégítésére készíthetõk.3. int max_size.doksi.5. egy másik egy double párból.hu 2.2.a). A c!=b jelentése például operator!=(c. A complex osztály így használható: void f(complex z) { complex a = 2.b). complex b = 1/a. Kirándulás a C++-ban 43 complex operator+(complex a1.3). ez a Stack típus paraméterként elemei számát kapja meg: class Stack { char* v. Ahhoz. } A fordító a komplex számokhoz kapcsolt mûveleti jeleket megfelelõ függvényhívásokká alakítja. if (c != b) c = -(b/a)+2*b. hogy kissé valósághûbbé tegyük a példát.3. int top. class Overflow { }. Vegyünk egy felhasználói veremtípust a complex típus soraival együtt. class Bad_size { }.. complex a2) { return complex(a1. A complex osztály három konstruktort tartalmaz. public: class Underflow { }. A legtöbb – de nem minden – modul jobban kifejezhetõ felhasználói típusként. A konstruktor írja le az osztály egy objektumának elõkészítési-létrehozási módját.a1.im)..re. a konstruktor.re+a2. Egyikük egy double-ból csinál complex-et.im+a2. ~Stack(). // kivétel // kivétel // kivétel // konstruktor // destruktor .

char pop(). void push(char c). s_var1. // 10 elemet tárolni képes globális verem // hivatkozás a Stack veremre // lokális verem i számú elemmel // mutató a szabad tárban levõ Stack-re } Ez a Stack típus ugyanolyan névadásra. Ez a függvény gondoskodik a kezdeti értékadásról.6) A konstruktor egy új Stack változót hoz létre. a destruktort: Stack::Stack(int s) // konstruktor { top = 0. élettartamra. kupac – vagy dinamikus tár) a new operátor használatával. // "||" jelentése "vagy" max_size = s. void f(Stack& s_ref. s_ptr->push('d'). int i) { Stack s_var2(i). ahogy a beépített típusú változókat szokták. // . A felhasználók a vermeket ugyanúgy hozzák létre és használják. vonatkozó szabályoknak engedelmeskedik. s_var2. hely felszabadítása újrafelhasználás céljára (§6.. valahányszor létrehozzuk az osztály egy példányát. Ehhez lefoglal némi helyet a szabad tárból (heap – halom. felszabadítva a tárat.2. másolásra stb.Forrás: http://www.push('c').hu 44 Bevezetés }. megadhatjuk a konstruktor ellentétét. A Stack(int) konstruktor meghívódik. Például: Stack s_var1(10). } // destruktor // elemek törlése. amikor az osztály egy objektuma kikerül a hatókörbõl. .push('b'). if (s<0 || 10000<s) throw Bad_size(). Stack* s_ptr = new Stack(20). A destruktor takarít. s_ref.doksi. hatókörre.push('a'). // az elemek szabad tárba helyezése } Stack::~Stack() { delete[ ] v. Az egész a Stack-ek felhasználóinak beavatkozása nélkül történik. Ha bármilyen „takarításra” van szükség.. mint az int vagy a char beépített típusok. v = new char[s].

1.5. Ha azonban teljesen el akarjuk szigetelni az adott verem felhasználóját a megvalósítás változásaitól. ellentétben az absztrakt típusokkal. hogy fordítsa.5. hogy a konkrét típusok pontosan ugyanúgy viselkedjenek. Az ábrázolás nem válik el a felhasználói felülettõl. hanem része annak. ezért csak a tagfüggvényeken keresztül hozzáférhetõ. return v[top].4. és ahol lokális változók gondoskodnak a szükséges tisztaságról és hatékonyságról. v[top] = c. class Overflow { }. } A complex és Stack típusokat konkrét típusnak nevezzük. amit be kellene építeni (#include) a vermeket használó programrészbe. ha nem tudjuk a típus ábrázolásának méretét. top = top + 1. a felhasználó újra le kell. Elõször határozzuk meg a felületet: class Stack { public: class Underflow { }. top = top . mint egy modul (§2. Nevezetesen egy típusból nem lehetnek valódi lokális (helyi) változóink.hu 2.Forrás: http://www. Ekkor a megoldás leválasztani a felületet az ábrázolásról és lemondani a valódi lokális változókról.5. // kivétel // kivétel . Absztrakt típusok Amikor a Stackrõl. melyek nem változnak gyakran. egy tulajdonságot elvesztettünk. de jelen van.doksi. mint a beépítettek. Kirándulás a C++-ban 45 Természetszerûen a push() és pop() tagfüggvényeket valahol szintén meg kell adni: void Stack::push(char c) { if (top == max_size) throw Overflow(). } char Stack::pop() { if (top == 0) throw Underflow(). Azon típusoknál. 2. ez elfogadható és gyakran ideális. a legutolsó Stack nem elegendõ. Ha bármilyen jelentõs változást szenved.1) által megvalósított „mûtípusról” áttértünk egy saját típusra (§2. Az ábrázolás privát.3). ahol a felület tökéletesebben elszigeteli a felhasználót a megvalósítás részleteitõl. Ezt az árat kell fizetni.

hogy „az adott osztályból származtatott osztályban késõbb felülírható”.Ezt a Stack-et így használhatnánk: void f(Stack& s_ref) { s_ref. mint „származtatva …-ból”. } Vegyük észre. hogy a megvalósítás a konkrét Stack osztályból mindent tartalmazhat. amit kihagytunk a Stack felületbõl: class Array_stack : public Stack { char* p. Az f() függvény részére. A furcsa =0 kifejezés azt mondja.hu 46 Bevezetés }. valamilyen másik függvény kell létrehozzon egy objektumot. Az olyan osztályt. int top. gyakran többalakú (polimorf) típusnak nevezzük.pop() != 'c') throw Bad_pop(). Nem meglepõ. hogyan használja f() a Stack felületet. Egy Stack-bõl származtatott osztály a Stack felületet valósítja meg. // Array_stack megvalósítja Stack-et }.doksi. void push(char c). int max_size. „megvalósítja …-t”. virtual char pop() = 0. a megvalósítás mikéntjérõl mit sem tudva. public: Array_stack(int s). hogy a verembõl származtatott osztálynak meg kell határoznia a függvényt.push('c'). A „:public” olvasható úgy.Forrás: http://www. vagy „…altípusa …-nak”. ~Array_stack(). mely más osztályoknak felületet ad. if (s_ref. char pop(). mely a megvalósítás ismeretének teljes hiányában egy Stacket akar használni. amelyen az f() mûveletet hajthat végre: void g() { . mely tartalmazza a push() és pop() függvényeket. virtual void push(char c) = 0. Ilyenformán a Stack felületként szolgál bármilyen osztály részére. A virtual szó a Simulában és a C++-ban azt jelenti.

amely táblázat függvényekre hivatkozó mutatókat tartalmaz. az lc.pop_front hívás eltávolítja az elsõ elemet.5. az Array_stack::pop()-ot. } char pop(). f(ls). a Stack objektumnak információt kell tartalmaznia arról. ugyanolyan jól fog mûködni a Stack egy másik megvalósításával is: class List_stack : public Stack { list<char> lc. amikor g()-bõl. char List_stack::pop() { char x = lc.Forrás: http://www. public: List_stack() { } // List_stack megvalósítja Stack-et // (standard könyvtárbeli) karakterlista (§3. mint lc elsõ elemét. csak a Stack felületet ismeri. void push(char c) { lc. Mivel f() nem tud az Array_stack-ekrõl. lc. Kirándulás a C++-ban 47 } Array_stack as(200). A táblázatot „virtuális .doksi. a List_stack::pop()-ot kell meghívni.push_front(c).push_front(c) beteszi c-t. } 2.7. hogy ezt feloldhassuk. Ahhoz. Egy függvény létre tud hozni egy List_stack-et és f() használhatja azt: void h() { List_stack ls.front() pedig lc elsõ elemére utal.5. f(as). return x.hu 2. hogy futási idõben mely függvényt kell meghívni.pop_front(). Virtuális függvények Hogyan történik az f()-en belüli s_ref.pop() hívás feloldása a megfelelõ függvénydefiníció hívására? Amikor h()-ból hívjuk f()-et. A fordítóknál szokásos eljárás egy virtuális függvény nevének egy táblázat valamely sorszámértékévé alakítása. az lc.front(). } // az elsõ elem lekérése // az elsõ elem eltávolítása Itt az ábrázolás egy karakterlista. Az lc.3) }.

majd megmutatjuk. Problémák a konkrét típusokkal A konkrét típusok – a modulokban megadott „mûtípusokhoz” hasonlóan – egyfajta „fekete dobozt” írnak le. mint a „normális függvényhívás”. kivéve. az nem lép igazi kölcsönhatásba a program többi részével.hu 48 Bevezetés függvénytáblának” vagy egyszerûen vtbl-nek szokás nevezni.6.6. 2. valamint egy-egy vtbl minden osztályhoz. Minden virtuális függvényeket tartalmazó osztálynak saját vtbl-je van. hogy az objektumot akkor is helyesen használjuk. mely azonosítja az osztály virtuális függvényeit. Objektumorientált programozás Az elvont adatábrázolás a jó tervezéshez alapfontosságú.Forrás: http://www. Ez grafikusan így ábrázolható: Array_stack objektum: p max_size top List_stack objektum: lc vtbl: Array_stack::push() Array_stack::pop() vtbl: List_stack::push() List_stack::pop() A vtbl-ben lévõ függvények lehetõvé teszik. illetve a virtuális függvények sorszámát. hogy új felhasználáshoz igazítsuk. A hívónak mindössze a vtbl helyét kell tudnia a Stack-en belül. E részben elõször egyszerû felhasználói típusokkal mutatunk be egy problémát. a könyvben pedig a tervezés végig központi kérdés marad. Ha egy fekete dobozt létrehozunk. Ez a virtuális hívási eljárás lényegében ugyanolyan hatékonnyá tehetõ. Nincs mód arra. hogy kiszolgálják igényeinket.1. ha de- . 2. ha a hívó nem ismeri annak méretét és adatainak elrendezését. hogyan lehet azt megoldani osztályhierarchiák használatával. Többlet helyszükséglete: a virtuális függvényeket tartalmazó osztály minden objektumában egy-egy mutató. A felhasználói típusok azonban önmagukban nem elég rugalmasak ahhoz.doksi.

Point center. . illetve végét jelöli. (A Pascal-szerû nyelvekben egy változó rekordtípust használhatnánk. Tegyük fel. hogy léteznek az alábbiak: class Point { /* . Tegyük fel azt is. */ }. Kirándulás a C++-ban 49 finícióját módosítjuk. }.hu 2.. // . // típusmezõ A k típusazonosító mezõ azért szükséges.. hogy az olyan mûveletek számára. milyen fajta alakzattal van dolguk. case triangle: // háromszög rajzolása break. */ }. Egy alakzatot az alábbi módon adhatunk meg: enum Kind { circle. class Color { /* . k címkével). // . Vegyük például egy grafikus rendszerben használni kívánt Shape (Alakzat) típus meghatározását. square }. A /* és */ egy megjegyzés kezdetét... hogy pillanatnyilag a rendszernek köröket.8) class Shape { Kind k. // felsorolás (§4. Color col. háromszögeket és négyzeteket kell támogatnia.. public: void draw().. mint a draw() (rajzolás) vagy a rotate() (forgatás) meghatározhatóvá tegyük. triangle. Ez a helyzet ideális is lehet.. A jelölés többsoros megjegyzésekhez is használható. A draw() függvényt így adhatnánk meg: void Shape::draw() { switch (k) { case circle: // kör rajzolása break.doksi.. de komoly rugalmatlansághoz is vezethet.Forrás: http://www. void rotate(int).

. } virtual void draw() = 0. mely minden alakzat általános tulajdonságait leírja: class Shape { Point center. milyen alakzatfajták léteznek. } Ez azonban „rendetlenség”. /* .Forrás: http://www. hacsak hozzá nem férünk minden mûvelet forráskódjához.. hogy az ábrázolásoknak (legalább is néhánynak) illeszkednie kell abba a – jellemzõen rögzített méretû – keretbe. egy körrajzoló függvénnyel lehet megrajzolni stb. } void move(Point to) { center = to. minden mûveletet meg kell vizsgálni és (lehetõség szerint) módosítani kell azokat. . E megkülönböztetés kifejezése és elõnyeinek kihasználása az objektumorientált programozás lényege. Elõször létrehozunk egy osztályt. valahányszor a rendszerhez egy új alakzatot adunk. melynek sugara van. az ilyen munka nagy ügyességet kíván és hibákat vihet be a más (régebbi) alakzatokat kezelõ kódba. Az egyes alakzat-ábrázolások kiválasztását komolyan megbéníthatja az a követelmény. melyek e megkülönböztetés kifejezését és használatát lehetõvé tévõ szerkezetekkel rendelkeznek. */ draw(). Mivel egy új alakzat hozzáadása magával vonja minden fontos alakzat-mûvelet kódjának módosítását.) és egy adott alakzatfajta tulajdonságai közt. public: Point where() { return center.). Osztályhierarchiák A probléma az.2. 2.... rajzolhatóság stb. (A kör például olyan alakzat. melyet az általános Shape típus leírása képvisel. támogatják az objektumközpontúságot.doksi. Color col. A függvényeknek – mint a draw() – tudniuk kell arról. más nyelvek nem. Ezért az ilyen függvénynél mindig növekszik a kód.hu 50 Bevezetés } case square: // négyzet rajzolása break. }.. virtual void rotate(int angle) = 0. A rendszerhez nem adhatunk új alakzatot.6. // . A megoldásról a Simulából kölcsönzött öröklés gondoskodik. Ha új alakzatot hozunk létre. Azok a nyelvek. hogy nincs megkülönböztetés az egyes alakzatok általános tulajdonságai (szín. // .

világos. public: void draw() { /* .4 absztrakt Stack típusában. base). int angle) // v elemeinek elforgatása angle szöggel { for (int i = 0. mennyire alkalmazható egy problémára az objektumorientált megközelítés. melyek alakzatokra hivatkozó mutatókból álló vektorokat kezelnek: void rotate_all(vector<Shape*>& v.doksi. azokat a függvényeket. hogy a Shape osztályból származik (derived). biztosíts mindegyikhez teljes mûveletkészletet. A származtatott osztályról azt mondjuk. Más szóhasználat szerint a Circle és a Shape alosztály (subclass).és származtatott osztályok használatát általában öröklésként említjük. az öröklés segítségével pedig határold körül pontosan a közös tulajdonságokat. de a konkrét megvalósítás még nem ismert. illetve fõosztály (superclass). ++i) v[i]->rotate(angle).5.Forrás: http://www. elegendõ az elvont adatábrázolás.hu 2.size(). bázisosztálya (alaposztálya. a Shape osztályról pedig azt. A C++-ban a Circle osztályról azt mondjuk. Ahol nincs ilyen közös tulajdonság.. például az interaktív grafikában.. hogy alakzatról van szó és meg kell határoznunk konkrét tulajdonságait (beleértve a virtuális függvényeket is): class Circle : public Shape { int radius. hogy a Circle osztály õse ill.A programozási megközelítés itt a következõ: Döntsd el. ezért a bázis. melyeknél a hívási felület meghatározható. } Egy konkrét alakzat meghatározásához meg kell mondanunk. A fenti meghatározás alapján már írhatunk általános függvényeket. A típusok közti. virtuálisként (virtual) vezetjük be. hogy az objektumközpontúságnak óriási le- . mely osztályokra van szükséged. */ } void rotate(int) {} // igen. hogy örökli (inherit) a bázisosztály tagjait. i<v. Némely területen. Kirándulás a C++-ban 51 Akárcsak a §2. öröklés és virtuális függvények használatával kiaknázható közösség mértéke mutatja. üres függvény }.

Amikor egy rendszert tervezünk – és akkor is. kölcsönösen támogató jellegûek. Más területeken. míg a modulok osztályokat és függvényeket.Forrás: http://www. mely algoritmusokra van szükség. Az egyes típusok közös tulajdonságait megtalálni nem egyszerû. Az objektumorientált programozás konkrét programozási nyelvi szerkezetek nélkül való elemzésére irányuló kísérleteket lásd [Kerr.6-ban. Tárolók Egy karakterverem-típust általánosíthatunk.5. és úgy lásd el azokat paraméterekkel.4) nem kölcsönösen kizárják.7. amikor a rendszerkövetelményeket leírjuk – aktívan kell keresnünk a közös tulajdonságokat. Még általánosabban. A kihasználható közösség mértékét befolyásolja a rendszer tervezési módja. ha sablont (template) hozunk létre belõle és a konkrét char típus helyett sablonparamétert használunk. így az objektumközpontúság támogatásához szükséges szolgáltatások feleslegesnek tûnnek. mint a klasszikus aritmetikai típusoknál és az azokon alapuló számításoknál. A tapasztalt tervezõ sokféle megközelítést használ – ahogy a szükség parancsolja. független a karakter fogalmától. akkor így is kell tenni. így az itt felsorolt irányelvek is inkább egymást kiegészítõ.1987] és [Booch.5). amelyeket egy közös bázisosztályban kihasználhatnánk. 2. ha egy algoritmus az ábrázolástól függetlenül és logikai torzulás nélkül kifejezhetõ. Általánosított programozás Ha valakinek egy verem kell. A programozási irányelv a következõ: Döntsd el. mutatnak-e olyan hasonlóságokat. 1994] a §23. nem feltétlenül karaktereket tartalmazó veremre van szüksége. Az osztályok és modulok például függvényeket tartalmaznak. 2. A verem általános fogalom. a létezõ osztályokat pedig meg lehet vizsgálni.doksi.1. alig látszik több lehetõség. Következésképpen függetlenül kell ábrázolni is. Például: . Osztályokat lehet kifejezetten más típusok építõkockáiként tervezni.hu 52 Bevezetés hetõségei vannak. Az osztályhierarchiák és az absztrakt osztályok (§2. hogy minél több típussal és adatszerkezettel mûködjenek. hanem kiegészítik egymást (§12.7. mint az elvont adatábrázolás.

push(complex(1. void push(T). class Overflow { }.pop() != 'c') throw Bad_pop().1.2)) throw Bad_pop().Forrás: http://www. egészekbõl álló lista számára } .push('c'). top = top . Stack(int s). Kirándulás a C++-ban 53 template<class T> class Stack { T* v. int top. Hasonlóképpen adhatjuk meg a tagfüggvényeket is: template<class T> void Stack<T>::push(T c) { if (top == max_size) throw Overflow(). A template<class T> elõtag T-t az utána következõ deklaráció paraméterévé teszi. top = top + 1. a vermet az alábbi módon használhatjuk: Stack<char> sc(200). // verem 200 karakter számára // verem 30 komplex szám részére // verem 45. scplx.doksi. if (scplx. void f() { sc. v[top] = c. int max_size. if (sc. T pop(). return v[top]. Stack< list<int> > sli(45).pop() != complex(1.2)). } Ha a definíciók adottak. // konstruktor // destruktor }. public: class Underflow { }. } template<class T> T Stack<T>::pop() { if (top == 0) throw Underflow(). Stack<complex> scplx(30).hu 2. ~Stack().

általában container class-nak vagy egyszerûen tárolónak (konténernek) hívjuk. Ismét használhatjuk tehát az általánosított (generikus) programozás irányelveit algoritmusok tárolók általi paraméterezésére. Tegyük fel. fejezetek). anélkül. . §3. mégpedig olyat. fej. és 18. Íme a sorozat fogalmának grafikus ábrázolása: Kezdet Vég elemek: . a sorozatokra összpontosít és azokat bejárókkal ( iterator) kezeli. de elképzelhetõ más is. Általánosított algoritmusok A C++ standard könyvtára többféle tárolóról gondoskodik. amelyet a C++ standard könyvtárában a tárolók és nem numerikus algoritmusok megközelítésébõl (18.doksi.8) vettünk át. A sorozatnak van egy kezdete és egy vége. milyen fajta tárolóról van szó. sablonokként adhatunk meg listákat.. hogy vektorokat. Konvertálni nem akarunk egyetlen adott adatszerkezetre sem. vektorokat. A sablonoknak fordítási idõben van jelentõségük. A „vége” fizikai ábrázolása lehet egy „õr” (sentinel) elem. A sorozat vége ugyancsak egy bejáró. copy() és search() függvényeket. listákat és tömböket akarunk rendezni.hu 54 Bevezetés Hasonló módon.. így listákkal és tömbökkel is. 2. melyet egy konkrét sort függvény elfogad.. de a felhasználók sajátokat is írhatnak (3.2. hogy pontosan tudnánk.7. 17. asszociatív tömböket (map) és így tovább. hogy minden egyes tárolóra megírnánk a sort(). A bejáró (iterator) valamely elemre hivatkozik és gondoskodik arról a mûveletrõl. mely a sorozat utolsó elemén túlra hivatkozik. hogy egy tárolót anélkül használjunk. másolni és átkutatni. melynek hatására legközelebb a sorozat soron következõ elemére fog hivatkozni. amely megengedi. Az egyik megoldás. ezért találnunk kell egy általános módot a tárolók leírására. amely valamilyen típusú elemek gyûjteményét tartalmazza. A lényeg. Az olyan osztályt. tehát használatuk a „kézzel írott” kódhoz képest nem növeli a futási idõt. hogy a sorozat számos módon ábrázolható.Forrás: http://www.

// következõ cél ++from. // hivatkozott elemek másolása ++to. list<complex>& lc) { copy(&ac[0]. Ezt azért tesszük. A forrás és a cél típusait egyetlen paraméter helyett két sablonparaméter.Forrás: http://www. vc2 elsõ elemétõl kezdõdõen. A fentieket adottnak tekintve. void g(vector<complex>& vc. az elsõ helyén kézenfekvõ választás a * dereferencia (hivatkozó vagy mutató) operátort használni. } . Ha az alapötletet megértettük.lc. Például: complex ac[200]. mint „egy bejáró által férjünk hozzá egy elemhez” és„ a bejáró hivatkozzon a következõ elemre” szükségünk van valamilyen szabványos jelölésre.end(). // 200 karakter tömbje // 500 karakter tömbje void f() { copy(&vc1[0]. Kirándulás a C++-ban 55 Az olyan mûveletekhez. class Out> void copy(In from.(iterator) és sorozatjelölést. In too_far.. } Ez vc1-et elsõ elemétõl az utolsóig vc2-be másolja.3) támogatja ezt a bejáró. Minden standard könyvtárbeli tároló (17. az In és Out jelöli.vc. alacsonyszintû tömb és mutató típusai rendelkeznek a megfelelõ mûveletekkel: char vc1[200].&vc2[0]).&ac[200]. Out to) { while (from != too_far) { *to = *from.doksi. az alábbi módon írhatunk kódot: template<class In. a másodiknál pedig a ++ növelõ mûveleti jelet.begin()). §16. Fej. amelyre a formai követelmények betartásával bejárót adhatunk meg. // következõ forrás } } Ez átmásol bármilyen tárolót.lc.begin()). char vc2[500].hu 2. A C++ beépített. mert gyakran akarunk másolni egy fajta tárolóból egy másik fajtába.&vc1[200]. copy(lc.begin().

A C++-t ezért úgy terveztük. jó programozási stílusban. Következésképpen egy nyelv megtanulásánál a feladat a nyelv sajátos és természetes stílusainak elsajátítására való összpontosítás. §2. Nem mindent lehet közvetlenül kifejezni egy nyelv beépített tulajdonságait felhasználva.hu 56 Bevezetés Itt a tömböt a list-be másoljuk. ne a nyelvi tulajdonságokra.8.1. §1. amely az elsõ elemre mutat. [2] Jó programok írásához nem kell ismernünk a C++ minden részletét. Utóirat Egyetlen programozási nyelv sem tökéletes. 2.7. Valójában egy általános célú programozási nyelv nem is lehet minden feladatra tökéletes. §2. gyakran komoly fogyatékosságokat mutathat egy másiknál. a list-et pedig a vector-ba. Valójában ez nem is lenne ideális. . Egy szabványos tárolónál a begin() a bejáró (iterator). Szerencsére egy programozási nyelvnek nem kell tökéletesnek lennie ahhoz. amire csak használják. hogy jó építõeszköz legyen a rendszerek széles választékához és a fogalmak széles körét közvetlenül kifejezhessük vele. Csak akkor lesz jelentékeny. Egyetlen nyelvi tulajdonság önmagában nem túl érdekes. hogy képesek legyünk együttesen használni a nyelv szolgáltatásait. hogy a C++ részletekbe menõ vizsgálatának igazi célja az. mivel az egy területen való tökéletesség magával vonja a szakosodást. 2. A nyelvi tulajdonságok egy sereg programozási stílus és módszer támogatására valók. nem az összes nyelvi tulajdonság minden részletre kiterjedõ megértése. ha ismerjük a legrejtettebb nyelvi tulajdonságokat vagy ha a legtöbb tulajdonságot kihasználjuk. [3] A programozási módszerekre összpontosítsunk.doksi. kérjük. hogy jó eszközként szolgáljon nagyszerû rendszerek építéséhez. egészséges tervezési környezetben. Tanácsok [1] Ne essünk kétségbe! Idõvel minden kitisztul. Ami egy feladatra tökéletes. Amikor tehát az Olvasó a következõ fejezeteket olvassa. A gyakorlati programozásnál kevés az elõnye.Forrás: http://www.9. emlékezzen arra.1. ha az egyes programozási módszerek és más tulajdonságok környezetében tekintjük.

Bemutat olyan hasznos könyvtári típusokat. Bevezetés Nincs olyan jelentõs program. Elõször a nyelvet támogató könyvtárakat fejlesztik ki.hu 3 Kirándulás a standard könyvtárban „Minek vesztegessük az idõt tanulásra. mit lehet a C++ és standard könyvtárának segítségével megtenni. hogy a következõ fejezetekben jobb . ezek képezik a további munka alapját. fejezet folytatásaként ez a fejezet gyors körutazást tesz a fõ könyvtári szolgáltatásokban. Ez lehetõvé teszi. valamint használatuk legáltalánosabb módjait. mikor a tudatlanság azonnali?” (Hobbes) Szabványos könyvtárak • Kimenet • Karakterláncok • Bemenet • Vektorok • Tartományellenõrzés • Listák • Asszociatív tömbök • Tárolók (áttekintés) • Algoritmusok • Bejárók • Bemeneti/kimeneti bejárók • Bejárások és predikátumok • Tagfüggvényeket használó algoritmusok • Algoritmusok (áttekintés) • Komplex számok • Vektoraritmetika • A standard könyvtár (áttekintés) • Tanácsok 3. hogy fogalmat adjon. vector.Forrás: http://www. mely csak a puszta programnyelven íródik.doksi. A 2. mint a string. list és map.1.

A program e függvény végrehajtásával indul. a rendszer a sikeres befejezést jelzõ értéket kap vissza. A C++ standard könyvtárán kívül a legtöbb megvalósítás a felhasználó és a program közti párbeszédre grafikus felhasználói felületeket is kínál.doksi. hogy legyen egy main() nevû függvény. Hasonlóképpen.1. ha van ilyen. Az e könyvben leírt standard könyvtárbeli szolgáltatások minden teljes C++-változat részét képezik.2 mutatja be. A standard könyvtárat részletesebben a §16. melynek nincsenek paraméterei és nem tesz semmit. A programok jellemzõen valamilyen kimenetet állítanak elõ. és megõrizni a példák „hordozhatóságát” (más rendszerekre való átültetésének lehetõségét). Helló. Ha nincs visszatérési érték. Íme egy program.Forrás: http://www. és megértsük a leghasznosabb könyvtári szolgáltatások legegyszerûbb használatát. az hibát jelent. fedezze fel a legtöbb rendszerben meglévõ. A 2. ha a részleteket nem érti tökéletesen. E fejezet célja. világ! A legkisebb C++ program: int main() { } A program megadja a main nevû függvényt. kiterjedt lehetõségeket – ezt azonban a gyakorlatokra hagytuk. Minden C++ programban kell. Ha a main() nem nulla értéket ad vissza. világ!: #include <iostream> int main() { std::cout << "Helló. fejezethez hasonlóan bátorítani akarom az olvasót. melyeket gyakran GUI-knak vagy ablakozó rendszernek neveznek. 3. Természetesen biztatjuk az olvasót. amely kiírja: Helló.hu 58 Bevezetés példákat és gyakorlatokat adjak az olvasónak. ahogy a szabványban szerepel. ne kedvetlenítse el.2. világ!\n". úgy. a legtöbb programfejlesztõ környezetet alapkönyvtárakkal (foundation library) is ellátták. kivéve a külön megjelölteket. hogy megízleljük. a program visszatérési értéke „a rendszerhez”. } . A main() által visszaadott int érték. ne zavarja. mi következik. amelyek a szabványos fejlesztési és/vagy futtatási környezeteket támogatják. A szándékunk a C++ önálló leírását adni. Ilyeneket nem fogunk leírni.

fordított perjel) az utána következõ karakterrel valamilyen egyedi karaktert jelöl. nem valamilyen más cout-ét. A benne szereplõ \ (backslash. Ezért írtunk std::cout-ot cout helyett. amint a §3. Ebben az esetben a „Helló. vagy globálissá kell tenni minden nevet az std névtérbõl (§8. A standard könyvtár névtere A standard könyvtár az std névtérhez (§2.3): . Esetünkben az \n az új sor jele. fejezetben szereplõ felsorolásokban szerepelnek).4. §8. Kirándulás a standard könyvtárban 59 Az #include <iostream> utasítja a fordítót. Az itt közölt programrészletek fordításához és futtatásához a megfelelõ fejállományokat be kell építeni (#include. Ez egyértelmûen a standard cout használatát írja elõ. világ!\n" értelmetlen volna. A << („tedd bele”) kimeneti operátor második operandusát beírja az elsõbe. A standard könyvtár minden szolgáltatásának beépítésérõl valamilyen.2) tartozik. az <iostream>-hez hasonló szabványos fejállomány által gondoskodhatunk: #include<string> #include<list> Ez rendelkezésre bocsátja a szabványos string-et és list-et.7. Használatukhoz az std:: elõtagot alkalmazhatjuk: std::string s = "Négy láb jó. világ!\n” karakterliterál a szabványos kimeneti adatfolyamba.hu 3. világ!” szöveget sortörés követi. hogy illessze be az iostream-ben található adatfolyam-bemeneti és -kimeneti szolgáltatások deklarációját a forráskódba.5. A karakterliterál egy " " jelek közé zárt karaktersorozat. illetve a szükséges #include <fejállomány>-okat. Ezenkívül vagy az std:: elõtagot kell használni.6 és a 16. E deklarációk nélkül az alábbi kifejezés std::cout << "Helló. az std::cout-ba íródik. §8. std::list<std::string> slogans. Az egyszerûség kedvéért a példákban ritkán írjuk ki az std:: elõtagot. tehát a kiírt „Helló.doksi.Forrás: http://www.2. 3. két láb rossz!".3.

Alapértelmezésben a cout-ra kerülõ kimeneti értékek karaktersorozatra alakítódnak át.4.doksi. 3. // a szabványos karakterlánc-szolgáltatások elérhetõvé tétele // std nevek elérhetõvé tétele az std:: elõtag nélkül // rendben: a string jelentése std::string string s = "A tudatlanság erény!".Forrás: http://www. cout << '\n'. Mindazonáltal. azt vagy a szabvány ajánlja. } Ugyanezt teszi az alábbi kód is: void g() { int i = 10. ha tehát egy nevet használunk onnan. a nyelvi és könyvtári tulajdonságokat illusztráló programrészletek rövidre fogása érdekében elhagytuk az ismétlõdõ #include-okat és std:: minõsítéseket. Kimenet Az iostream könyvtár minden beépített típusra meghatároz kimenetet. E könyvben majdnem kizárólag a standard könyvtárat használjuk. Általában szegényes ízlésre vall egy névtérbõl minden nevet a globális névtérbe helyezni. de felhasználói típushoz is könnyen megadhatjuk. cout << i. cout << i. } A különbözõ típusú kimenetek természetesen párosíthatók: void h(int i) { cout << "i értéke ". void f() { cout << 10. } .hu 60 Bevezetés #include<string> using namespace std. A következõ kód például az 1 karaktert a 0 karakterrel követve a szabványos kimeneti adatfolyamba helyezi. vagy egy magyarázat része (hogyan határozható meg az adott szabványos szolgáltatás).

5. } cout << s3. amikor több rokon tételt kell kiírni. fejezetben található. Kirándulás a standard könyvtárban 61 Ha i értéke 10. 3.hu 3. Karakterláncok A standard könyvtár gondoskodik a string (karakterlánc) típusról. a kimenet a következõ lesz: i értéke 10 A karakterkonstans egy karakter. Szerencsére maguk a kimeneti kifejezések eredményei felhasználhatók további kimenetekhez: void h2(int i) { cout << "i értéke " << i << '\n'.Forrás: http://www.doksi. hogy kiegészítse a korábban használt karakterliterálokat. Az adatfolyamok részletes magyarázata a 21.Az ember hamar belefárad a kimeneti adatfolyam nevének ismétlésébe. A string típus egy sereg hasznos karakterlánc-mûveletet biztosít. void m1() { string s3 = s1 + ". hogy a karakterkonstansok nem számértékként. Vegyük észre. cout << 'b'. egyszeres idézõjelek közé zárva. } A fenti kód kimenete például abc lesz. string s2 = "világ". ilyen például az összefûzés : string s1 = "Helló". . " + s2 + "!\n". hanem karakterként íródnak ki: void k() { cout << 'a'. cout << 'c'. } Ez egyenértékû h()-val.

fejezet írja le. } // . A karakterláncokhoz karakterliterálokat és karaktereket adhatunk.5...10). . Ez más hasznos tulajdonságai mellett lehetõvé teszi a részláncok (substring) kezelését is. string& s2) { s1 = s1 + '\n'. Sok alkalmazásban az összefûzés legáltalánosabb formája valamit egy karakterlánc végéhez fûzni. name. } // s = "Stroustrup" // a név új értéke "Nicholas Stroustrup" lesz A substr() mûvelet egy olyan karakterláncot ad vissza.doksi. mely a paramétereivel megadott részlánc másolata. Ezt a += mûvelet közvetlenül támogatja: void m2(string& s1."Nicholas").Forrás: http://www. //varázsszó // varázslás megkezdése } A standard könyvtár string osztályát a 20. mert tömörebb és valószínûleg hatékonyabban valósítható meg. Például: string name = "Niels Stroustrup". Az elsõ paraméter egy. void respond(const string& answer) { if (answer == incantation) { } else if (answer == "yes") { // .Természetesen a karakterláncok összehasonlíthatók egymással és literálokkal is: string incantation. de az utóbbit elõnyben részesítjük.. // sortörés } A lánc végéhez való hozzáadás két módja egyenértékû.replace(0. a karakterlánc egy adott helyére mutató sorszám. void m3() { string s = name.hu 62 Bevezetés Az s3 kezdeti értéke itt a következõ karaktersorozat (új sorral követve): Helló. világ! A karakterláncok összeadása összefûzést jelent.substr(6. // sortörés s2 += '\n'..

lebegõpontos d változóba: void f() { int i. A C stílusú karakterláncokat kezelõ függvények meghívásához képesnek kell lennünk egy string értékének C stílusú karakterlánc formában való kinyerésére. s a Stroustrup értéket kapja. 5 hosszúságú részlánc a Niels.34e5-öt ír a kétszeres pontosságú. A c_str() függvény ezt teszi (§20.2).A replace() mûvelet a karakterlánc egy részét helyettesíti egy másik karakterlánccal. A >> jobb oldalán álló típus határozza meg. mint az a részlánc. Meg fogjuk mutatni. double d.3. a cin a szabványos bemeneti adatfolyam.8) például az alábbi módon írathatjuk ki: void f() { printf("name: %s\n". Az ostreams-hez hasonlóan az istreams is a beépített típusok karaktersorozatként történõ ábrázolásával dolgozik és könnyen bõvíthetõ. // egész szám beolvasása i-be // kétszeres pontosságú lebegõpontos szám beolvasása d-be } . például 1234-et olvas be a szabványos bemenetrõl az i egész változóba és egy lebegõpontos számot. hogy egy C stílusú karakterláncot könnyen bevihetünk egy string-be.Forrás: http://www. cin >> i. amelyet helyettesít. Vegyük észre.c_str()).doksi. ez helyettesítõdik a Nicholas-szal. A name végsõ értéke tehát Nicholas Stroustrup.5.6. Bemenet A standard könyvtár bemenetre az istreams-et ajánlja.1.5.7). hogy felhasználói típusokkal is meg tudjon birkózni. 3. mondjuk 12. } 3. Az alábbi kód egy számot. A name-et a printf() kiíró C-függvénnyel (§21.hu 3. Ebben az esetben a 0-val induló. A >> („olvasd be”) mûveleti jelet bemeneti operátorként használjuk.C stílusú karakterláncok A C stílusú karakterlánc egy nulla karakterrel végzõdõ karaktertömb (§5. cin >> d. Mivel a sorszámozás (az index) 0-tól indul. Kirándulás a standard könyvtárban 63 a második a kívánt részlánc hossza.name. milyen bemenet fogadható el és mi a beolvasó mûvelet célpontja. hogy a helyettesítõ karakterláncnak nem kell ugyanolyan méretûnek lennie.

} } // lebegõpontos szám beolvasása // mértékegység beolvasása // inch (hüvelyk) // 1 hüvelyk 2. switch (ch) { case 'i': in = x. break. char ch = 0. in.hu 64 Bevezetés A következõ példa hüvelykrõl centiméterre és centiméterrõl hüvelykre alakít. A switch utasítás egy értéket hasonlít össze állandókkal. Gyakran akarunk karaktersorozatot olvasni. cin >> ch. Ennek kényelmes módja egy string -be való helyezés: int main() { string str. cm.54. .Forrás: http://www. Ha az ellenõrzött érték egyikkel sem egyezik. float x. A programozónak nem kell szükségszerûen errõl az alapértelmezett lehetõségrõl gondoskodnia. break. A break utasítások a switch utasításból való kilépésre valók. a vezérlés a default-ot választja. A case konstansoknak egymástól különbözniük kell.doksi. melynek végén egy karakter jelzi az egységet (centiméter vagy hüvelyk). A program válaszul kiadja a másik egységnek megfelelõ értéket: int main() { const float factor = 2. case 'c': in = x/factor. cm = x*factor.54 cm-rel egyenlõ // cm cout << in << " in = " << cm << " cm\n". Bemenetként egy számot kap. cout << "Írja be a hosszúságot: ". break. default: in = cm = 0. cin >> x. cm = x.

hu 3. például egy szóköz fejezi be. tehát ha beírjuk a hírhedt király nevét Erik a Véreskezû a válasz marad Helló. " << str << "!\n". cout << "Írja be a nevét!\n".doksi. Kirándulás a standard könyvtárban 65 } cout << "Írja be a nevét!\n". } E programmal az alábbi bemenet Erik a Véreskezû a kívánt kimenetet eredményezi: Helló. cin >> str. Ha begépeljük a következõt Erik a válasz az alábbi lesz: Helló.2). Erik! Alapértelmezés szerint az olvasást egy „üreshely” (whitespace) karakter (§5.Forrás: http://www. cout << "Helló. getline(cin. Erik a Véreskezû! . cout << "Helló. Erik! A getline() függvény segítségével egész sort is beolvashatunk: int main() { string str.str).5. " << str << "!\n".

Entry phone_book[1000]. konténer) nevezzük. void print_entry(int i) // egyszerû használat { cout << phone_book[i]. helyet pazarolunk. }. majd a karakterlánc kiíratása. 3.name << ' ' << phone_book[i]. A standard könyvtár a vector típust (§16.doksi. int number. Ha nagy méretet választunk. általánosan tárolónak (container. tehát ha néhány megabájtnyi pontosvesszõt adunk meg. a program valóban több oldalnyi pontosvesszõt ad vissza – hacsak gépünk vagy az operációs rendszer valamilyen kritikus erõforrása elõbb el nem fogy.3) bocsátja rendelkezésre. Egy egyszerû példa karakterek karakterláncba helyezése. melynek fõ célja objektumok tárolása. } A beépített tömbök mérete azonban rögzített. Adott feladathoz megfelelõ tárolókról gondoskodni és ezeket támogatni bármilyen program építésénél nagy fontossággal bír.vagy szám-) párokból álló tömb: struct Entry { string name.number << '\n'. Mindkét esetben alacsonyszintû tárkezelõ kódot kell írnunk.Forrás: http://www. amely megoldja a fentieket: . A standard könyvtár leghasznosabb tárolóinak bemutatására nézzünk meg egy egyszerû programot. Az olyan osztályt. a tömb túl fog csordulni. Vektor Sok C programozó számára alkalmas kiindulásnak látszana egy beépített (név.7. 3. amit beviszünk.1. amelynek változatai az eltérõ hátterû emberek számára is „egyszerûnek és maguktól értetõdõnek” tûnnek.hu 66 Bevezetés A szabványos karakterláncoknak megvan az a szép tulajdonságuk. Tárolók Sok számítás jár különbözõ objektumformákból álló gyûjtemények (collection) létrehozásával és kezelésével. Ez az a fajta program. amely neveket és telefonszámokat tárol. ha kisebbet.7. hogy rugalmasan bõvítik a tartalmukat azzal.

Ahol a másolás nem kívánatos. referenciákat (hivatkozásokat) vagy mutatókat kell használni. Ez nagyon különbözik a beépített tömbök bevezetésétõl: vector<Entry> book(1000). Tehát f()-ben az elõkészítés (inicializálás) és értékadás után v és v2 is egy-egy külön másolatot tartalmaz a phone_book-ban lévõ minden egyes Entry-rõl. Ha egy vektor sok elemet tartalmaz. // vektor 1000 elemmel // 1000 üres vektor Ha hibásan [ ] –t (szögletes zárójelet) használnánk ott. melynek megadtuk a kezdeti méretét. A vector egy példánya objektum. void print_entry(int i) // egyszerû használat. az ilyen ártatlannak látszó értékadások megengedhetetlenül „költségesek”. } A vector-ral való értékadás az elemek másolásával jár. Egyetlen vector<Entry> típusú objektumot hoztunk létre. . v = v2..Forrás: http://www.size()+n). a fordító majdnem biztos. // .hu 3..resize(phone_book. amikor a vector-t használni próbáljuk. ahol egy vector deklarálásában ()-t értettünk. } void add_entries(int n) // méret növelése n-nel { phone_book. Vegyük észre a () zárójelek használatát a phone_book definíciójában. mint a tömbnél { cout << phone_book[i].name << ' ' << phone_book[i]. melynek értéket adhatunk: void f(vector<Entry>& v) { vector<Entry> v2 = phone_book.number << '\n'.doksi. hogy hibaüzenetet ad. Kirándulás a standard könyvtárban 67 vector<Entry> phone_book(1000). vector<Entry> books[1000]. } A vector size() tagfüggvénye megadja az elemek számát.

3 és a 14.number << '\n'.3. Az itteni definíció azonban elegendõ a könyv példáihoz: template<class T> class Vec : public vector<T> { public: Vec() : vector<T>() { } Vec(int s) : vector<T>(s) { } T& operator[ ](int i) { return at(i). Az at(i) egy vector indexmûvelet. §8. } . ezért a soron következõ fejezetekben a vector egy egyszerû tartományellenõrzõ átalakítását fogjuk használni.hu 68 Bevezetés 3. } // az 1001 kívül esik a tartományon A kezdeti értékadás valószínûleg inkább valamilyen véletlenszerû értéket tesz i-be. Vec néven. void print_entry(int i) // egyszerû használat. hogy out_of_range típusú kivételt vált ki. mely out_of_range típusú kivételt vált ki. azzal a különbséggel. A Vec-hez hasonló típusok megvalósítási módjait és a kivételek hatékony használatát §11.doksi.7. Például: void f() { int i = phone_book[1001].name << ' ' << phone_book[i]. // . biztosítva.3.12. } // tartományellenõrzés // tartományellenõrzés }. ha egy index kifut a tartományából. Tartományellenõrzés A standard könyvtárbeli vector alapértelmezés szerint nem gondoskodik tartományellenõrzésrõl (§16. } const T& operator[ ](int i) const { return at(i). hogy a tartományon kívüli hozzáféréseket elkapjuk: Vec<Entry> phone_book(1000).2. mint a vector..Forrás: http://www.3).3). most már használhatjuk a Vec-et. A Vec olyan. fejezet tárgyalja. Visszatérve a nevek és telefonszámok tárolásának problémájához. ha paramétere kifut a vector tartományából (§16.number.. mint a vektornál { cout << phone_book[i]. Ez nem kívánatos. mint hogy hibát okoz.

3.) { cerr << "ismeretlen kivétel\n". Ehelyett átkutathatjuk a listát. } Ez gondoskodik az alapértelmezett kivételkezelõkrõl. ezért egy egyszerû telefonkönyv ábrázolására egy lista jobban megfelelne. Kirándulás a standard könyvtárban 69 A tartományon kívüli hozzáférés kivételt fog kiváltani.2. mint egy vektor: list<Entry> phone_book. . Lista A telefonkönyv-bejegyzések beszúrása és törlése általánosabb lehet. a cerr szabványos hibakimeneti adatfolyamon hibajelzés jelenik meg (§21. } catch (. 3. Amikor listát használunk. melyet a felhasználó elkaphat: void f() { try { } for (int i = 0.. Ha a felhasználó nem kapja el ezt a fajta kivételt. tehát ha elmulasztunk elkapni egy kivételt.Forrás: http://www. nem folytatja futását és nem vált ki meghatározatlan hibát. A kivételek okozta meglepetések csökkentésének egyik módja.1)..doksi. a program meghatározott módon befejezõdik. amikor a phone_book[i]-re i==1000 értékkel történik hozzáférési kísérlet. i++) print_entry(i). } catch (out_of_range) { cout << "tartományhiba\n". ha a main() törzsében egy try blokkot hozunk létre: int main() try { // saját kód } catch (out_of_range) { cerr << "tartományhiba\n". adott értékû elemet keresve.7. ahogy a vektorok esetében általában tesszük.hu 3. az elemekhez nem sorszám alapján szeretnénk hozzáférni. } A kivétel „dobása” majd elkapása akkor történik. i<10000.

Elemek hozzáadása egy list-hez igen könnyû: void add_entry(const Entry& e. A típus a tároló leírásának része és név szerint lehet hivatkozni rá. } } } Az s keresése a lista elejénél kezdõdik. Ha adott egy i bejáró.vagy számpárokból álló listához keresõ kódot írni valójában igen fáradságos munka. illetve az utolsó utáni elemre (§16. Ezenkívül a sorban történõ keresés – a legrövidebb listák kivételével – nagyon rossz hatékonyságú. Más adatszerkezetek közvetlenül támogatják a beszúrást. és addig folytatódik. Különben a sima iterator típust (§16.3.1) használjuk. Például: map<string.number << '\n'. pontosan milyen típusú egy szabványos tároló bejárója. Asszociatív tömbök Egy név. a következõ elem ++i lesz. Ha nincs szükségünk egy tárolóelem módosítására. .push_front(e).name << ' ' << e. // hozzáadás az ‘i' által mutatott elem elé } 3.3.e). hogy a list egy sorozat (§3.8): void print_entry(const string& s) { typedef list<Entry>::const_iterator LI. list<Entry>::iterator i) { phone_book.end(). ami nekünk kell.int> phone_book.4.insert(i. // hozzáadás a lista végéhez phone_book. Az i változó a *i elemre hivatkozik.4. for (LI i = phone_book. A felhasználónak nem kell tudnia. a törlést és az érték szerinti keresést.push_back(e).Forrás: http://www.7. Minden standard könyvtárbeli tároló tartalmazza a begin() és end() függvényeket. return. míg az s-t megtaláljuk vagy elérünk a lista végéhez.doksi. // hozzáadás a lista elejéhez phone_book.name) { cout << e. ++i) { Entry& e = *i.1).hu 70 Bevezetés Ehhez kihasználjuk azt a tényt. A map egy értékpár-tároló. i != phone_book.begin(). A standard könyvtár nevezetesen a map típust biztosítja erre a feladatra (§17.2). a const_iterator az a típus. melyek egy bejárót (iterátort) adnak vissza az elsõ. // rövidítés referenciával if (s == e.

Kirándulás a standard könyvtárban 71 Más környezetekben a map mint asszociatív tömb vagy szótár szerepel.2.2) . melyben egy érték többször is elõfordulhat (§17. mely az adott alkalmazás igényeit a legjobban kiszolgálja: Szabványos tárolók összefoglalása Vector<T> list<T> Queue<T> Stack<T> Deque<T> Priority_queue<T> set<T> Multiset<T> Map<kulcs. hogy a programozók olyan tárolót válasszanak. } Ha nem talál illeszkedést az s kulcsra.hu 3.3. A vektorokat indexelni „olcsó” és könnyû. 3.4.1) Kétvégû sor (§17. azzal a kivétellel.4.doksi.érték> Változó hosszúságú vektor (§16. hogy a 0 nem érvényes telefonszám. Itt feltételezzük. mapped type) megfelelõ értékét adja vissza: void print_entry(const string& s) { if (int i = phone_book[s]) cout << s << ' ' << i << '\n'.Forrás: http://www.3) Halmaz.7. A map emlékeztet egy (kulcs–érték) párokból álló listára. melyben egy kulcs többször elõfordulhat (§17.érték> Multimap<kulcs. A map alapértéke int típusra 0.2.3. Ha elsõ típusával (a kulccsal.3) Kétirányú láncolt lista (§17.2) Sor (§17.1) Asszociatív tömb. Másrészt két eleme közé egyet beszúrni költségesebb lehet. a phone_book egy alapértéket ad vissza. A lista tulajdonságai ezzel pontosan ellentétesek.3.4. hogy értékei a kulcs szerinti kereséshez a legmegfelelõbbek. a lista és a vektor mind használható telefonkönyv ábrázolására.2) Verem (§17.4) Asszociatív tömb (§17.4. Szabványos tárolók Az asszociatív tömb.3) Halmaz (§17.3) Érték szerint rendezett sor (§17. A standard könyvtár rendelkezik a legáltalánosabb és leghasználhatóbb tárolótípusokkal. ami lehetõvé teszi. Mindegyiknek megvannak az erõs és gyenge oldalai. key) indexeljük. vagyis a hozzárendelt típus. a map a második típus (az érték.5.

<map> stb. objektumokat keresünk bennük és így tovább.end()-ig. hogy a programozók új tárolótípusokat készítsenek. ami éppen a vector összes elemét jelenti.Ez a jelölésbeli és jelentésbeli egységesség lehetõvé teszi. A szabványos tárolók és alapmûveleteik jelölés szempontjából hasonlóak. 3. hogy az egyes tárolótípusoktól függetlenül adjunk meg algoritmusokat. a kezdõ elemet követõ elemek felülíródnak. mely visszaadja az elemek a számát. leírásuk a <vector>.doksi. A tárolók az std névtérhez tartoznak. és minden tárolónak van size() tagfüggvénye. unique_copy(ve. fejezetben találjuk.7. <list>.hu 72 Bevezetés A szabványos tárolókat §16. Használatukhoz olyan alapvetõ hozzáférési mûveletekre van szükség.ve. A példában a sort() rendezi a sorozatot. ve. A következõ kódrészlet például egy vector-t rendez és minden egyedi vector elem másolatát egy list-be teszi: void f(vector<Entry>& ve.begin(). } A szabványos algoritmusok leírását a 18. mint az elemek hozzáadása és eltávolítása. továbbá a mûveletek jelentése a különbözõ tárolókra nézve egyforma. önmagukban nem túl hasznosak.2. melyek az elsõ. §16. Ha több mint egy elemet írunk. A push_back() például – meglehetõsen hatékony módon – egyaránt használható elemeknek egy vektor vagy lista végéhez fûzésére. részhalmazokat vonunk ki belõlük. fejezet bemutatja.ve.8.le. A tárolófelületek egységes volta emellett lehetõvé teszi azt is. Az algoritmusok elemek sorozatával mûködnek (§2.3 és a 17. Rendezzük.begin().Forrás: http://www. Íráshoz csak az elsõ írandó elemet szükséges megadni. mint a list vagy a vector. Ha az új elemeket egy tároló végéhez kívánnánk adni. fejállományokban szerepel. kiíratjuk. az alábbit írhatnánk: . Algoritmusok Az adatszerkezetek. list<Entry>& le) { sort(ve.6) ennek egy példája. melyek a szabványos típusokhoz nagyon hasonló módon használhatók. Az ilyen sorozatok bejáró-párokkal ábrázolhatók.begin()-tõl ve. Az alapmûveletek általában mindenfajta tárolóra alkalmazhatók.7. elemeket távolítunk el. A Vec tartományellenõrzéssel ellátott vektor (§3. hogyan lehet egy hash_map-et a szerkezethez hozzátenni.2). Emiatt a standard könyvtár az általános tárolótípusokon kívül biztosítja a tárolók legáltalánosabb eljárásait is. A 17. fejezet mutatja be. illetve az utolsó utáni elemet adják meg.end().begin()). Emellett ritkán használunk egy tárolót pusztán tárolásra.end()).

Ha a find-ot használjuk.3. megszámlálhatjuk valamely karakter elõfordulásait egy karakterláncban: int count(const string& s.4). amely a megtalált elemre mutat. i = find(i+1.le.c). char c) // c elõfordulásainak megszámlálása s-ben { int n = 0. a legjobb példa a begin() és az end(). Ha a hozzáfûzéskor elfelejtjük a back_inserter()-t használni.ve.end(). hogy helyet csináljon részükre (§19. Kirándulás a standard könyvtárban 73 void f(vector<Entry>& ve. unique_copy(ve. } // hiba: le nem bejáró // rossz: túlír a végén // elemek felülírása 3.doksi.ve.2.end().ve.ve. copy(ve.begin()).end()) { ++n. bõvítve a tárolót.le.begin(). A find szabványos algoritmus például egy sorozatban keres egy értéket és azt a bejárót adja vissza.begin(). A szabványos tárolók és a back_inserter()-ek kiküszöbölik a hibalehetõséget jelentõ. megkaphatjuk néhány hasznos elemére hivatkozó bejáróját (iterátorát).Forrás: http://www. Ezenkívül sok algoritmus ad vissza bejárókat.back_inserter(le)).begin(). Lássuk. copy(ve.end().begin(). while (i != s. mi történik a count egyszerû meghívásakor: .s.begin(). } // hozzáfûzés le-hez A back_inserter() elemeket ad egy tároló végéhez.8.end()).begin().end()). } return n.ve.end(). Bejárók használata Amikor elõször találkozunk egy tárolóval.s.5).c).hu 3. list<Entry>& le) { copy(ve. } A find algoritmus valamely érték egy sorozatban való elsõ elõfordulására vagy a sorozat utolsó utáni elemére mutató bejárót ad vissza. string::const_iterator i = find(s.end().end(). C stílusú realloc()-ot használó tárkezelést (§16. az hibákhoz vezethet: void f(vector<Entry>& ve. list<Entry>& le) { sort(ve.1.le).

vector<string>& vc.val). A bejáró tehát e karakterre mutat és nem az s.hu 74 Bevezetés void f() { string m = "Mary had a little lamb".end()-re. Ezután folytatjuk a ciklust és megtaláljuk a másik három 'a'-t. vagyis eggyel a megtalált 'a' után.doksi. } A find() elsõ hívása megtalálja 'a'-t a Mary-ben.end()) { ++n. közbensõ és végsõ értékeit mutatják. így mondhatjuk: void f(list<complex>& lc. int a_count = count(m.'a'). A find() ekkor elér a sorozat végéhez és az s. } return n. string s) { int i1 = count(lc. Következésképpen ugyanilyen módon általánosíthatnánk a count() függvényt: template<class C.5 int n = 0.val).v. int i3 = count(s. // az elõbb megtalált elem átugrása i = find(i.13.end(). így nem teljesül az i!=s.begin(). class T> int count(const C& v."Diogenész").end() feltétel és kilépünk a ciklusból. ++i. } Ez mûködik.v. Ezt a count() hívást grafikusan így ábrázolhatnánk: M a r y h a d a l i t t l e l a m b A nyilak az i kezdeti.'x').complex(1. Természetesen a find algoritmus minden szabványos tárolón egyformán fog mûködni. A ciklusban i+1-gyel kezdjük a keresést. int i2 = count(vc. while (i != v. } . lásd §C.end().end()-et adja vissza.Forrás: http://www. így beléptünk a ciklusba. // typename. T val) { typename C::const_iterator i = find(v.3)).

melyeket kiszolgálnak. Azonban sok különbözõ típusuk létezik. hogy egy tároló valamely részét számláljuk: void g(char cs[ ]. } A sorozat használata megengedi.vs. vector<string>& vs. mert a bejárónak azt az információt kell tárolnia.complex(1. hogy a számlálást beépített tömbre használjuk és azt is. mint a tárolók és azok az egyedi igények.begin().'z'). melyre az adott tárolótípusnál feladata ellátásához szüksége van.2.'x'). Ezek a bejárótípusok olyan különbözõk lehetnek."Diogenész"). hogy a standard könyvtár tartalmazza ezt a mûveletet. Egy vector bejárója például minden bizonnyal egy közönséges mutató.end(). } // 'z'-k a tömbben // 'z'-k a tömb elsõ felében 3.hu 3.end().Forrás: http://www.&cs[sz]. int sz) { int i1 = count(&cs[0]. Hogy teljesen általános legyen a megoldás. string s) { int i1 = count(lc. int i2 = count(&cs[0].8. Bejárótípusok Valójában mik is azok a bejárók (iterátorok)? Minden bejáró valamilyen típusú objektum.end().s.begin(). Kirándulás a standard könyvtárban 75 A count sablont azonban nem kell definiálnunk. Az elemek elõfordulásainak megszámlálása annyira általános és hasznos. int i2 = count(vs.lc.doksi. int i3 = count(s. mivel az nagyon ésszerû hivatkozási mód a vector elemeire: bejáró: vektor: p P i e t H e i n .&cs[sz/2].'z'). a standard könyvtári count paraméterként tároló helyett egy sorozatot kap: void f(list<complex>& lc.begin().3)).

hu 76 Bevezetés Egy vector-bejáró megvalósítható úgy is. mivel egy ilyen elem általában nem tudja. hogy ismerje egy adott bejárótípusát. mely a következõ elemre hivatkozik. Hasonlóképpen * azt az elemet adja meg.1)..Forrás: http://www. Például a list<Entry>::iterator a list<Entry> általános bejárótípusa. mint a vector-ra hivatkozó mutató. cs. a felhasználó ritkán kell. A ++ alkalmazása bármely bejáróra például olyan bejárót ad.3). Ritkán kell aggódnunk az adott típus meghatározása miatt. az a jelentésük és a mûveleteik elnevezése. . . az elõzõekhez hasonló egyszerû szabálynak eleget tesz (§19.. A listák bejáróinak valamivel bonyolultabbnak kell lenniük. Továbbá. meg egy sorszám: bejáró: (kezdet == p.2. amely néhány. Saját bejáró-típusait minden tároló ismeri és azokat az egyezményes iterator és const_iterator neveken rendelkezésre bocsátja. Valójában bejáró lehet bármely objektum. elemek: P i e t Ami közös minden bejárónál. melyre a bejáró hivatkozik. cs. hol van a lista következõ tagja. mint egy egyszerû mutató a listában tárolt elemre.doksi. A lista-bejáró tehát inkább a lista valamely csomópontjára hivatkozó mutató: bejáró: p lista: csomópont cs. pozició == 3) vektor: P i e t H e i n Az ilyen bejárók használata tartományellenõrzésre is lehetõséget ad (§19.

hu 3. // jelentése cout << "világ!\n" } Ez egy újabb mód szabványos üzenetek kiírására a szabványos kimenetre.3. ami hamarosan magától értetõdõ lesz – ha eddig még nem lenne az. a bemenet végének jelzéséhez egy istream_iterator-ról kell gondoskodni. Kirándulás a standard könyvtárban 77 3. ". // jelentése cout << "Helló. A ++oo célja: utánozni egy tömbbe mutatón keresztül történõ írást. *oo = "világ!\n". A tárolók azonban nem az egyetlen helyet jelentik. Mivel a bejárók mindig párokban ábrázolnak egy sorozatot. " ++oo. és értékek sorozatát írjuk a kimeneti adatfolyamba is. melyik adatfolyamot használjuk és milyen típusú objektumokat írunk bele. ahol elemek sorozatát találjuk.doksi. hogy az értékadó adatot a cout-ra írjuk ki. Az értékadás *oo-nak azt jelenti. A szerzõ elsõnek nem ezt a módot választaná erre az egyszerû feladatra. Megadhatunk például egy bejárót. Egy ostream_iterator létrehozásához meg kell határoznunk. mint egy csak írható tárolót. mint egy csak olvasható tárolót. Itt is meg kell adnunk a használni kívánt adatfolyamot és a várt értékek típusát: istream_iterator<string> ii(cin). Bemeneti és kimeneti bejárók A bejárók fogalma általános és hasznos a tárolók elemeibõl álló sorozatok kezelésénél. hogy a kimenetet úgy kezeljük. hogy a bemeneti adatfolyamot úgy kezeljük. Következésképpen a bejárók hasznosan alkalmazhatók a bemenetnél és kimenetnél is.8.Forrás: http://www. A bemeneti adatfolyamok is értékek sorozatából állnak. Hasonlóképpen az istream_iterator olyasvalami. . Az alapértelmezett istream_iterator a következõ: istream_iterator<string> eos. Például: int main() { *oo = "Helló. mely a cout szabványos kimeneti adatfolyamra hivatkozik: ostream_iterator<string> oo(cout). ami lehetõvé teszi. de ez az eszköz alkalmas arra.

istream_iterator<string> ii(is). cin >> from >> to. sort(b.hu 78 Bevezetés Most újra beolvashatnánk a „Helló. §21. világot!”-ot a bemenetrõl és kiírathatnánk az alábbi módon: int main() { string s1 = *ii. vector<string> b(ii. } cout << s1 << ' ' << s2 << '\n'.3. Valójában az istream_iterator-okat és ostream_ iterator-okat nem közvetlen használatra találták ki. Írjunk például egy egyszerû programot. // kimeneti adatfolyam ostream_iterator<string> oo(os.3) } Az ifstream egy istream.doksi.end()).c_str()). majd az eredményt egy másik fájlba írja: int main() { string from. // kimeneti bejáró az adatfolyam számára unique_copy(b. ifstream is(from.oo).Forrás: http://www. // a kettõzött értékek elvetése // hibaállapot visszaadása (§3. return !is. mely szintén egy fájlhoz kapcsolható. // b tartalmának a kimenetre másolása. az ofstream pedig egy ostream. a kétszer szereplõ elemeket eltávolítja.end(). Jellemzõen algoritmusok paramétereiként szolgálnak.c_str()). // a forrás. melynek a bemenetrõl adunk kezdõértéket // az átmeneti tár (b) rendezése ofstream os(to. string s2 = *ii. Az ostream_iterator második paramétere a kimeneti értékeket elválasztó jel. mely egy fájlhoz kapcsolható. az olvasott adatokat rendezi. istream_iterator<string> eos.7) // bemeneti bejáró az adatfolyam számára // bemenet-ellenõrzés // b egy vektor. lásd §3. ++ii.2. to.begin().eof() || !os."\n").1 és §20.b.eos).és célfájl nevének beolvasása // bemeneti adatfolyam (c_str().b.begin().3. mely egy fájlból olvas.5. .

second << '\n'.Forrás: http://www. A pair elsõ eleme const string.eos. A ciklusok megírása azonban fáradságos lehet. for_each(histogram. // "s'' gyakoriságának rögzítése } Ha beolvastuk a bemenetet. A map (string. Következésképpen szeretnénk meghívni az alábbi függvényt void print(const pair<const string. nem sima string. Bejárások és predikátumok A bejárók lehetõvé teszik.record). második elemét second-nak nevezzük). istream_iterator<string> eos. Tegyük fel. hogy írunk egy programot. szeretnénk az összegyûjtött adatokat a kimenetre küldeni. hogy egy adott függvényt a sorozat minden egyes elemére meghívjunk.doksi.8. for_each(ii. ezért a standard könyvtár módot ad arra.int) párokból álló sorozat. Kirándulás a standard könyvtárban 79 3. A karakterláncok és a hozzájuk tartozó gyakoriságok kézenfekvõ ábrázolása egy map-pel történhet: map<string.end().int>& r) { cout << r. } a map minden elemére (a párok (pair) elsõ elemét first-nek. mely a bemenetrõl szavakat olvas és feljegyzi elõfordulásuk gyakoriságát.print).4.histogram. mert minden map kulcs konstans. Az egyes karakterláncok gyakoriságának feljegyzésére természetes mûvelet a következõ: void record(const string& s) { histogram[s]++.first << ' ' << r. hogy ciklusokat írjunk egy sorozat bejárására.begin().int> histogram. A fõprogram tehát a következõ: int main() { istream_iterator<string> ii(cin). } .hu 3.

begin(). ahányszor a predikátuma true. Hasonló módon a count_if() annyit számlál.end().int>& m) { typedef map<string.gt_42). mely egy bizonyos követelménynek felel meg. . Ennek az ötletnek egy általánosabb változata olyan elemet keres. Ezek minden elemre meghívódnak és logikai értéket adnak vissza. mint 42: void g(const map<string. hogy a kimenet rendezett legyen. } Máskor megszámlálhatnánk azon szavakat.end(). A find algoritmus (§18. hogy a kért elemet megtalálta. amíg a predikátuma true-t nem ad vissza. A map rendezve tárolja az elemeket és a ciklus is (növekvõ) sorrendben járja végig a map-et. melyek gyakorisága nagyobb. A find_if() például addig keres.int>& r) { return r. hogy minden elemen végrehajtanánk egy feladatot.2).int>::const_iterator MI. valamint olyan sablonokat.doksi. } Az olyan függvényeket.hu 80 Bevezetés Vegyük észre. A standard könyvtár néhány hasznos predikátumot is biztosít.int>& m) { int c42 = count_if(m. melyek továbbiak alkotására használhatók (§18.5...4. mint a gt_42().m.Forrás: http://www. Sok programozási feladat szól arról.begin(). Például meg akarjuk keresni egy map elsõ 42-nél nagyobb értékét: bool gt_42(const pair<const string.. MI i = find_if(m. melyet az algoritmus vezérlésére használunk.m. hogy meg kell keresni valamit egy tárolóban. predicate) nevezzük.second>42. hogy nem kell rendeznünk a map-et ahhoz. melyet az algoritmus szándékolt tevékenységének elvégzéséhez felhasznál.gt_42). ahelyett. predikátumnak („állítmány”.. // . // . } void f(map<string. jelezvén. vezérlõfüggvény.2) kényelmes módot ad egy adott érték megkeresésére.

end(). Kirándulás a standard könyvtárban 81 3.end().sh. mely meghívja a tagfüggvényt: void draw(Shape* p) { p->draw(). a mutatót paraméterként átadva.begin(). Tegyük fel. és sokkal inkább a hivatkozott objektum egy tagfüggvényét szeretnénk meghívni.doksi.mem_fun(&Shape::draw)). hogy a Shape::draw() tagfüggvényt akarjuk meghívni egy list<Shape>* elemeire. Gyakran mutatók tárolóival van dolgunk. A mem_fun() azért fontos. amit a Shape::draw() visszaad.4-ben a for_each(ii.5.4. A mem_fun(&Shape::draw) eredménye egy Shape* paramétert kap és visszaadja. } A standard könyvtári mem_fun() sablon (§18. .begin(). hogy a szabványos algoritmusokat többalakú (polimorf) objektumok tárolóira használjuk.draw). mert megengedi. Például §3. nem pedig egy globális függvényt.8.hu 3. } A módszert így általánosíthatjuk: void g(list<Shape*>& sh) { for_each(sh. Tagfüggvényeket használó algoritmusok Sok algoritmus alkalmaz függvényt egy sorozat elemeire.record).sh.2) paraméterként egy tagfüggvény mutatóját kapja (§15. } void f(list<Shape*>& sh) { for_each(sh. a bemenetrõl beolvasott karakterláncra.4.Forrás: http://www.8. meghívja a record()-ot minden egyes. A példa kezelésére egyszerûen egy nem tag függvényt írunk.5) és valami olyasmit hoz létre.eos. amit a tag osztályára hivatkozó mutatón keresztül hívhatunk meg.

1) Keresd meg az összes egyezõ értékû elemet (§18.Forrás: http://www.1].6. hatékonyság” [Knuth. Matematika A C-hez hasonlóan a C++ nyelvet sem elsõsorban számokkal végzett mûveletekre tervezték.4) Másold az elemeket (§18. Íme néhány. mely adott problémahalmaz megoldásához mûveletek sorozatát határozza meg és öt fontos jellemzõje van: végesség.5.5. Az algoritmusok az std névtérhez tartoznak. melyeket különösen hasznosnak találtam: Válogatott szabványos algoritmusok for_each() find() find_if() count() count_if() replace() replace_if() copy() unique_copy() sort() equal_range() merge() Hívd meg a függvényt minden elemre (§18.8.3) Számláld meg az illeszkedéseket a predikátumra (§18.7.2) Keresd meg a predikátumra az elsõ illeszkedést (§18. A C++ standard könyvtárának viszonylatában az algoritmus elemek sorozatán mûveleteket végzõ sablonok (template-ek) halmaza.2) Számláld meg az elem elõfordulásait (§18. bemenet. §1.5.6.9.1) Keresd meg a paraméterek elsõ elõfordulását (§18.7.1968.3) 3.5.3) Helyettesítsd be az elemet új értékkel (§18.1) Másold a csak egyszer szereplõ elemeket (§18. Mindemellett rengeteg numerikus munkát végeztek C++-ban és ez tükrözõdik a standard könyvtárban is.6. meghatározottság. A standard könyvtár több tucat algoritmust tartalmaz.2) Fésüld össze a rendezett sorozatokat (§18.doksi.6.7.1) Rendezd az elemeket (§18.hu 82 Bevezetés 3. kimenet. A standard könyvtár algoritmusai Mi az algoritmus? Általános meghatározása szerint „szabályok véges halmaza. . leírásuk az <algorithm> fejállományban szerepel.5.6.4) Helyettesítsd be a predikátumra illeszkedõ elemet új értékkel (§18.

5.. melyeket komolyabb. } Részletesebben lásd §22. complex<double> db) { complex<long double> ld = fl+sqrt(db). számokkal végzett munkánál gyakran lényegesnek tekintünk.9. Emiatt a standard könyvtárban megtaláljuk a valarray nevû vektort is. 3. db += fl*3. void f(complex<float> fl.2). .doksi. // .. kellõen rugalmas és illeszkedik a tárolók. A szokásos aritmetikai mûveletek és a leggyakrabban használt matematikai függvények komplex számokkal is mûködnek: // szabványos exponenciális függvény a <complex> sablonból: template<class C> complex<C> pow(const complex<C>&. a §2. Az egyszeres pontosságú lebegõpontos (float).. de az általánosság és rugalmasság eleve kizár olyan optimalizálásokat.. bejárók és algoritmusok szerkezetébe..1-ben leírt vector-t általános értéktárolásra tervezték. }. mely kevésbé általános és a számmûveletekhez jobban megfelel: template<class T> class valarray { // . Vektoraritmetika A §3.Forrás: http://www. Komplex számok A standard könyvtár a komplex számok egy típuscsaládját tartalmazza. a kétszeres pontosságú (double) stb.7. scalar im). int).1. Kirándulás a standard könyvtárban 83 3.. ugyanakkor nem támogatja a matematikai vektormûveleteket..hu 3. Ilyen mûveleteket könnyen be lehetett volna építeni a vector-ba.5. skalárokat tartalmazó komplex számok támogatására a standard könyvtárbeli complex egy sablon: template<class scalar> class complex { public: complex(scalar re.. fl = pow(1/fl. }. // .2.2-ben leírt complex osztály alakjában. // . T& operator[ ](size_t).9.

lásd 16. 3.3).1.2.. tárlefoglalás és futási idejû típusinformáció). A standard könyvtár szolgáltatásai A standard könyvtár szolgáltatásait az alábbi módon osztályozhatjuk: 1.fejezet. Karakterláncok és bemeneti/kimeneti adatfolyamok (nemzetközi karakterkészlet és nyelvi támogatással). list és map) és tárolókat használó algoritmusok (általános bejárások. lásd §2.4 3.10. Tárolók (vector..3. Alapvetõ futási idejû támogatás (pl. a = abs(a).9. a típusrendszer megsértésének elkerülésére). Ezenkívül azonban tartalmaz olyan osztályokat is. és 19.2). valarray<double>& a2) { valarray<double> a = a1*3. lásd §16. lásd 20. a2 += a1*3. . 18. és 21. 4. lásd §16.3. Alapszintû numerikus támogatás A standard könyvtár a lebegõpontos típusokhoz természetesen tartalmazza a leggyakoribb matematikai függvényeket (log().2. double d = a2[7]. pow() és cos(). fejezet.doksi. melyet a nyelv tömbök indexelésére használ.1. rendezések és összefésülések) rendszere.. A szokásos aritmetikai mûveleteket és a leggyakoribb matematikai függvényeket megírták a valarray-kre is: // szabványos abszolútérték-függvény a <valarray> sablonból: template<class T> valarray<T> abs(const valarray<T>&). melyek beépített típusok tulajdonságait – például egy float kitevõjének lehetséges legnagyobb értékét –írják le (lásd §22.hu 84 Bevezetés A size_t elõjel nélküli egész típus.14. 2. A szabványos C könyvtár (nagyon csekély módosításokkal. // .. 3.Forrás: http://www. void f(valarray<double>& a1. } Részletesebben lásd: §22.14+a2/a1. 17.

mit tesznek könyvtáraink. lásd 22. [7] Használjunk string-et char* helyett. hogy a standard könyvtár szolgáltatásai az std névtérhez tartoznak.7. §3. . Tanácsok [1] Ne találjunk fel a melegvizet – használjunk könyvtárakat. BLAS-szerû és általánosított szeletek. §3. részesítsük elõnyben a standard könyvtárat más könyvtárakkal szemben. és hogy könnyen megtanulható-e a használata.6. szabványos sablonkönyvtár) [Stepanov. és milyen áron teszik. valamint az optimalizálást megkönnyítõ szerkezetek. a list<T>-t és a map<key. hogy a felhasználók a könyvtár részeként megadottakon kívül könnyen készíthetnek saját tárolókat és algoritmusokat.7. §3. [6] Ne felejtsük el. hogy nem jelent-e jelentõs többletterhelést ugyanennek a szolgáltatásnak valamely egyszerûbb változatához viszonyítva. hogy egy osztály bekerülhet-e a könyvtárba.5.3.4. fejezet. Számokkal végzett mûveletek támogatása (komplex számok és vektorok aritmetikai mûveletekkel). az volt. Ez az egyezményesen STLnek (Standard Template Library.Forrás: http://www. [5] Ne felejtsük el beépíteni (#include) a felhasznált szolgáltatások fejállományait. §3. abban az értelemben. 3.doksi.value>-t a T[ ]-vel szemben. 1994] nevezett váz bõvíthetõ. [9] Részesítsük elõnyben a vector<T>-t.hu 3.11. hogyan teszik. [8] Ha kétségeink vannak.7. Kirándulás a standard könyvtárban 85 5. Minden algoritmus átalakítás nélkül mûködik minden tárolóra. §3. [3] Amikor választhatunk. Értsük meg.7. A C++ standard könyvtára tehát az alapvetõ adatszerkezeteket és az azokon alkalmazható alapvetõ algoritmusokat tartalmazza. hogy valamilyen módon használta-e már majdnem minden C++ programozó (kezdõk és szakértõk egyaránt). hogy általános alakban megadható-e. és ezeket azonnal mûködtethetik is a szabványos tárolókkal és algoritmusokkal együtt. §3. hogy a standard könyvtár mindenre ideális. §3.1. [4] Ne gondoljuk. [2] Ne higgyünk a csodákban.2.3. §3. Annak fõ feltétele.3. használjunk tartományellenõrzõ vektort (mint a Vec).

használjunk push_back()-et vagy back_inserter()-t. §3.7.3.doksi. [12] Az általános kivételeket a main()-ben kapjuk el.7.Forrás: http://www. . §3.2.8. §3. [11] Használjunk vektoron push_back()-et a realloc() tömbre való alkalmazása helyett.hu 86 Bevezetés [10] Amikor elemeket teszünk egy tárolóba.8. §3.

Fejezetek 4. 7.Forrás: http://www. tömbök és struktúrák Kifejezések és utasítások Függvények Névterek és kivételek Forrásfájlok és programok .doksi. A C++-nak a C nyelvre visszautaló része a hagyományos programozási stílusok támogatásával együtt kerül bemutatásra. Típusok és deklarációk Mutatók. amelyekkel C++ programot hozhatunk létre logikai és fizikai elemekbõl.hu Elsõ rész Alapok Ez a rész a C++ beépített típusait és azokat az alapvetõ lehetõségeket írja le. 9. amelyekkel programokat hozhatunk létre. 5. valamint ez a rész tárgyalja azokat az alapvetõ eszközöket is. 6. 8.

Forrás: http://www.doksi.hu .

ami nem tökéletes!” (ismeretlen szerzõ) „A tökéletesség csak az összeomlás pontján érhetõ el.N. Parkinson) Típusok • Alaptípusok • Logikai típusok • Karakterek • Karakterliterálok • Egészek • Egész literálok • Lebegõpontos típusok • Lebegõpontos literálok • Méretek • void • Felsoroló típusok • Deklarációk • Nevek • Hatókörök • Kezdeti értékadás • Objektumok • typedef-ek • Tanácsok • Gyakorlatok .Forrás: http://www.” (C.hu 4 Típusok és deklarációk „Ne fogadj el semmit.doksi.

a velük járó elnevezéseket és formai követelményeket. amelyekre az = (értékadás). amelyre a név hivatkozik) és ezek a mûveletek mit jelentenek. használni lehet aritmetikai kifejezésekben stb.1. és késõbb visszatér. kifejezést. és f nevû egyedek léteznek és olyan típusúak. az x. ahhoz is.9) mutatja be. Ez a fejezet az alapvetõ típusokat (§4. Másfelõl f-et olyan függvényként határoztuk meg. y. Ez a típus határozza meg. hogy megértse a fontosabb fogalmakat.1. Mivel y-t int-ként adtuk meg. hogy megértse a részleteket. azaz a programozónak meg kell adnia. de fõleg azért. float f(int). amikor már többet ismertettünk a C++-ból. milyen mûveleteket lehet végrehajtani a néven (azaz az egyeden. Ismernünk kell ezeket az elemeket. amelyekbõl a C++ programok létrehozhatók. hogy el tudjuk olvasni a mások által írt kódot. kezdõértéke 7 // f egész paramétert váró és lebegõpontos számot visszaadó függvény deklarációk már értelmessé teszik a fenti példát.doksi. // x lebegõpontos változó // y egész típusú változó. hogy valódi C++ programot készíthessünk.1) és deklarációkat (§4. Típusok Vegyük az x = y+f(2). Következésképp az olvasó jobban teszi.Forrás: http://www. int y = 7. nem feltétlenül végeznek hasznos dolgokat. amelynek egy int paramétere van. ha csak átnézi. . A többi fejezet megértéséhez azonban nem szükséges teljesen átlátni ennek a fejezetnek minden apró részletét. hogy ezek az x. a + (összeadás) és a () (függvényhívás) rendre értelmezettek. A terjedelmesebb és valósághûbb példák a késõbbi fejezetekben kerülnek sorra. így meg lehet hívni a megfelelõ paraméterrel. y és f neveket megfelelõen definiálni kell. Például a float x. amint szükséges. Ez a fejezet egyszerûen csak azokat az alapelemeket írja le. A példák csak a nyelv tulajdonságait szemléltetik. Hogy ez értelmes legyen valamely C++ programban. értékül lehet adni. A C++ programokban minden névnek (azonosítónak) van típusa.hu 90 Alapok 4.

7 Adatszerkezetek és osztályok (10. felsoroló típusokat adott értékhalmazok jelölésére (enum) Ezekbõl a típusokból más típusokat is létrehozhatunk.Forrás: http://www. . Típusok és deklarációk 91 4. Azt feltételezzük.5 Referencia-típusok (mint a double&) §5. léteznek alkalmas egyedek lebegõpontos számításokhoz és címek számára. fejezet) A logikai. és a számítási értéktartományt (§4. amelyek megfelelnek a számítógép leggyakoribb tárolási egységeinek és adattárolási módszereinek: §4.3 §4. mert a felhasználónak kell azokat meghatároznia. az integrális és lebegõpontos típusokat pedig közösen aritmetikai típusoknak. a pontosságot.1. lehetõvé téve a programozónak. Alaptípusok A C++ rendelkezik azokkal az alaptípusokkal. fejezet) felhasználói adattípusokként emlegetjük.6). mint az alaptípusok. elõzetes bevezetés nélkül nem állnak rendelkezésre.8 és létezik a §4.5 Logikai típus (bool) Karaktertípusok (mint a char) Egész típusok (mint az int) Lebegõpontos típusok (mint a double) Továbbá a felhasználó megadhat: §4.2 §4.1. A felsoroló típusokat és az osztályokat (10.hu 4.4 §4.doksi. gépi szót az egészek tárolására és az azokkal való számolásra. Ezek a következõk: §5. hogy a számítógép bájtokat biztosít a karakterek tárolásához. melyet az információ hiányának jelzésére használunk.7 void típus is. hogy hivatkozhassunk ezekre az egyedekre. karakter. Az integrális és lebegõpontos típusok többfajta mérettel adottak.és egész típusokat együtt integrális típusoknak nevezzük. hogy kiválaszthassa a felhasznált tár nagyságát.2 Tömbtípusok (mint a char[ ]) §5. A többi típust beépített típusnak nevezzük. A C++ alaptípusai a mutatókkal és tömbökkel együtt az adott nyelvi megvalósítástól függetlenül biztosítják ezeket a gépszintû fogalmakat a programozó számára.1 Mutatótípusok (mint az int*) §5.

int b) { bool b1 = a==b. int i = true. két értéke lehet: true vagy false (igaz.Forrás: http://www. egészekhez int-et. } Egész számra alakítva a true értéke 1 lesz.. Ha az eredmény visszaalakul logikai típusúvá. Ugyanígy az egészek is logikai típusúvá alakíthatók: a nem nulla egészek true.. == egyenlõségvizsgálat Ha a és b értéke ugyanaz. de a régi C és C++ kódok olvasásához ismernünk kell õket. illetve hamis). A bool gyakran használatos olyan függvény visszatérési értékeként. Logikai típusok A logikai típusoknak (bool). az aritmetikai és logikai egész-mûveletek az átalakított értékeken hajtódnak végre. .hu 92 Alapok A legtöbb programban a logikai értékekhez egyszerûen bool-t használhatunk. int b) { return a>b. a false-é pedig 0.doksi. máskülönben hamis. így legjobb azokat elkerülni addig. A többi alaptípus hatékonysági és más egyedi célokra használatos.2. amely valamilyen feltételt ellenõriz (predikátum): bool is_open(File*). } // = értékadás. a 0 false logikai értékké alakulnak: bool b = 7. lebegõpontos értékekhez pedig double-t. // bool(7) igaz. így b igaz // int(true) értéke 1. bool greater(int a. A logikai típusokat logikai mûveletek eredményének kifejezésére használjuk: void f(int a. a nem nulla egészek pedig true értéket kapnak. így i értéke 1 Aritmetikai és logikai kifejezésekben a logikai típusok egésszé alakulnak. 4. amíg ilyen igények fel nem merülnek. // . karakterekhez char-t. akkor b1 igaz lesz. a 0 false lesz.

// a+b értéke 2.3). §D). Biztosan feltehetõ. így 256 különbözõ értéket tárolhat. Karaktertípusok A char típusú változók egy karaktert tárolhatnak az adott nyelvi megvalósítás karakterkészletébõl: char ch = 'a'. az angol ábécé 26 betûjét és néhány általános írásjelet. hogyan programozzunk többnyelvû.6. hogy egy 8 bites karakterkészletben nincs 127-nél több karakter (néhány karakterkészlet 255 karaktert biztosít). több karakterkészletes környezetben. Jelentõsen eltérnek azok a karakterkészletek. amelyek másféleképpen támogatják ugyanazt a természetes nyelvet.2. ami a C++ kód írásához szüksé- . mint az angolban (a legtöbb európai nyelvben több van). bool x = a+b. Nem biztos. §21. A karakterkészlet jellemzõen az ISO-646 egy változata. hogy ezek a különbségek hogyan befolyásolják a C++ szabályait.3. hogy az adott C++-változat karakterkészlete tartalmazza a decimális számjegyeket. amelyek természetes nyelveket támogatnak. hogy az ábécé betûi összefüggõek (az EBCDIC lyukat hagy az „i” és a „j” között). vagy hogy minden karakter rendelkezésre áll. bár számos helyen említésre kerül (§20.doksi. így a billentyûzeten megjelenõ karaktereket tartalmazza. például ASCII.hu 4. így x igaz // a|b értéke 1. 4. így y igaz } Logikai típusúvá mutatókat is alakíthatunk (§C. A char típus általában 8 bites. Sok probléma származik abból. ez azonban a könyv keretein túlmutat. hogy nincs több alfabetikus karakter. a „nulla mutatók” false értékûek lesznek. hogy ezeket a karakterkészleteket csak részben szabványosították (§C. §C3. Itt azonban csak az érdekel minket. bool b = true.7. A nem nulla mutatók true.3.Forrás: http://www. Ennél fontosabb kérdés. és azok is. Típusok és deklarációk 93 void g() { bool a = true. bool y = a|b.2.5).

3. ha többféle char típus szükséges. nem pedig beépített típus.2) is. A különös név még a C-bõl maradt meg. Mérete az adott C++-változattól függ.1. Az _t toldalék a szabványos typedef-ektõl való megkülönböztetést segíti. hogy a karaktertípusok integrális típusok (§4. amely legalább 0-tól 255-ig tud értékeket tárolni. Lásd még a §C. vagy ha char típusú változókban szeretnénk egészeket tárolni. nem szabad semmit feltételeznünk az objektumok ábrázolásáról és ez az általános szabály a karakterekre is vonatkozik. A C-ben a wchar_t egy typedef (§4. amelyekre a kérdés biztosan megválaszolható: a signed char-t (elõjeles karakter). amely a begépelt karakter egész értékét mutatja meg: #include <iostream> int main() { char c. Amikor csak lehetséges. de elég nagy ahhoz. mint 0-tól 255-ig vagy -127-tõl 127-ig terjedõ értékeket. amely önálló típus.Forrás: http://www. } Az int(c) jelölés a c karakter egész értékét adja. §C. hogy karaktert egésszé lehet alakítani. Jegyezzük meg. Szerencsére csak a 0-127 tartományon kívüli értékekben lehet különbség és a leggyakoribb karakterek a tartományon belül vannak.4).3. hogy a szükséges legnagyobb karakterkészletet tárolhassa (lásd §21. A nagyobb karakterkészletek – például a Unicode – karaktereinek tárolására a wchar_t áll rendelkezésünkre.1). nehezen felderíthetõ „hordozhatósági” problémákat okozhatnak.hu 94 Alapok ges (néhány nemzeti karakterkészlet nem biztosítja a { } [ ] | \ karaktereket. §C. Az a lehetõség. felvet egy kérdést: a char elõjeles vagy elõjel nélküli? A 8 bites bájton ábrázolt 256 értéket úgy lehet értelmezni. .3. Azok a 0-127 tartományon túli értékek.7 és §C.doksi. így alkalmazhatóak rájuk az aritmetikai és logikai mûveletek (§6. amely legalább a -127 és127 közti értékeket képes tárolni és az unsigned char (elõjel nélküli karakter) típust. vagyis típus-álnév. a „b”-é például az ASCII karakterkészletben 98.4-et arra az esetre. amelyeket egy sima char tárol. std::cin >> c.1.1). A C++ azonban ad két olyan típust. std::cout << "A(z) ' " << c << " ' értéke " << int(c) << '\n'.3).3. Íme egy kis program. Sajnos az adott fordítóprogram dönti el.7). Minden karakternek van egy egész értéke. melyiket választja egy sima char esetében (§C.9.

hogy a változót unsigned-ként adják meg.3. Az escape karakterekrõl részletesebben lásd §C. A „széles” karakterliterálok L’ab’ alakúak. signed int. Valójában szimbolikus konstansok (jelképes állandók). Az elõjel nélküli (unsigned) egész típusok olyan felhasználásra ideálisak. amelyek úgy kísérlik meg biztosítani valamilyen érték nem negatív voltát. §C. az unsigned az unsigned int. Karakterliterálok A karakterliterál. mivel a széles karakterliterálok típusa wchar_t.2. ezek a \ fordított perjelt használják ún. a signed pedig a signed int szinonimája. mint egy bittömböt. Típusok és deklarációk 95 4.doksi. amely úgy kezeli a tárat.1). melyet gyakran karakterkonstansnak is hívnak. 4.6. hogy egy vagy több bitet nyerjünk pozitív egészek tárolásához. .6. Az egészek három méretben adottak: short int. Egész típusok A char-hoz hasonlóan az egész típusok is háromfélék: „sima” int.2-t. általában meghiúsulnak a mögöttes konverziós szabályok miatt (§C. Szinte soha nem jó ötlet az elõjel nélküli típust használni int helyett. a \t pedig a vízszintes tabulátort (behúzást) jelenti.hu 4.3. a short a short int. egy egyszeres idézõjelek közé zárt karakter. ahol az egyszeres idézõjelek között lévõ karakterek számát és jelentését az adott C++-megvalósítás a wchar_t típushoz igazítja.4.1. amin a C++ program fut. illetve long int. Például a \n az új sort.Forrás: http://www. Néhány karakternek szintén van szabványos neve. A sima char-ral ellentétben a sima int-ek mindig elõjelesek. Azok a próbálkozások.1. a '0' értéke 48 lesz. ha decimális jelölés helyett karakterliterálokat használunk. „sima” int . Ha például ASCII karakterkészlettel rendelkezõ számítógépet használunk. Egy long intre lehet sima long-ként hivatkozni. és unsigned int. A signed int típusok csak világosabban kifejezett szinonimái a nekik megfelelõ sima int típusoknak. melyek értéke annak a számítógépnek a karakterkészletében lévõ karakter egész értéke. Hasonlóan. escape karakterként. például 'a' és '0'. A karakterliterálok típusa char. A program hordozhatóságát javítja.

c.4) vagy felsoroló típusú (§4.Forrás: http://www.5. akkor ez 65 535 lett volna.1. Ha nincs megadva utótag. 14et és 15-öt jelentenek. amelyek túl hosszúak az ábrázoláshoz. Jó ötlet korlátozni a nem maguktól értetõdõ állandók használatát néhány. Az U utótag használatával elõjel nélküli (unsigned) literálokat adhatunk meg.hu 96 Alapok 4. . Ha a literál nullával kezdõdik és számjeggyel folytatódik. Egész literálok Az egész literálok négyféle alakban fordulnak elõ: decimális. oktális (8-as számrendszerbeli) számról van szó: decimális: oktális: hexadecimális: 0 0x0 2 02 0x2 63 077 0x3f 83 0123 0x53 Az a. megjegyzésekkel megfelelõen ellátott const (§5. Meglepetéseket okozhat. ahol az int egy kettes komplemensû 16 bites egészként van ábrázolva. illetve nagybetûs megfelelõik rendre 10-et. 11-et. b. Hasonlóan. Ha több bitet használtunk volna az egész ábrázolására.4. oktális. 13-at. 12-t. 0xffff a -1 negatív decimális szám lesz. 3U egy unsigned int és 3L egy long int.8) kezdeti értékadására. amelynek típusa megfelel az értéknek és a megvalósítás egész-méreteinek (§C.4). hexadecimális és karakterliterálként. 4. Az egészekhez hasonlóan ezek is háromfajta méretûek lehetnek: float (egyszeres pontosságú). ahogy elvárjuk tõlük: 7 1234 976 12345678901234567890 A fordítóprogramnak figyelmeztetnie kell olyan literálok esetében. A nullával kezdõdõ és x-szel folytatódó (0x) literálok hexadecimális (16-os számrendszerbeli) számok. az L utótag használatos a long literálokhoz. Az oktális és hexadecimális jelölés leginkább bitminták kifejezésénél hasznos. double (kétszeres pontosságú). ha ezekkel a jelölésekkel valódi számokat fejezünk ki. Például 3 egy int. A decimális literálok a leginkább használatosak és úgy néznek ki.doksi. a fordító egy olyan egész literált ad. és long double (kiterjesztett pontosságú). e és f betûk. Egy olyan gépen például. Lebegõpontos típusok A lebegõpontos típusok lebegõpontos (valós) számokat ábrázolnak. d.

14159265L 2. ahol fontos a választás.0L 2.1. szánjunk idõt a megtanulására.0 1. Ha nem értünk a lebegõpontos aritmetikához.5.43 e-21 például nem lebegõpontos literál.23 0. 1. vagy használjunk double-t és reméljük a legjobbakat. ha a lebegõpontos literálok az ábrázoláshoz képest túl nagyok. a C++ adott megvalósításától függ (§C.9e-3L 4.9e-3f Ha long double típusú lebegõpontos literált szeretnénk megadni. hanem négy különálló nyelvi egység (ami formai hibát okoz): 65.14159265f 2. akik különbözõ rendszereken programoznak vagy többféle fordí- . használjuk az l vagy L utótagot: 3.Forrás: http://www. Lebegõpontos literálok Alapértelmezés szerint a lebegõpontos literálok double típusúak. Méretek A C++ alaptípusainak néhány jellemzõje. Íme néhány lebegõpontos literál: 1.997925L 2. Típusok és deklarációk 97 Az egyszeres. kérjünk tanácsot. hogy hatásukat csökkentsük.6. a lebegõpontos számítások mély megértését követeli meg.2e10 1.2). Miért kellene ezzel foglalkozni? Azok. hogy szóköz nem fordulhat elõ egy lebegõpontos literál közepén. 4. kétszeres és kiterjesztett pontosság pontos jelentése az adott C++-változattól függ. A fordítónak itt is figyelmeztetnie kell.997925F 2. mint például az int mérete.23 1. Rámutatok ezekre a függõségekre és gyakran ajánlom. akkor azt az f vagy F utótag használatával tehetjük meg: 3. A 65.23 . A megfelelõ pontosság kiválasztása egy olyan problémánál.0f 2. hogy kerüljük õket vagy tegyünk lépéseket annak érdekében.doksi.23e-15 Jegyezzük meg.hu 4.43 e 21 Ha float típusú lebegõpontos literált akarunk megadni.

mert csak egy rendszert használnak és úgy érzik. hogy idõt pazaroljanak nehezen megfogható programhibák megtalálására és kijavítására. Ha ismerjük a gépet. amit a fordítóm megvalósít”. igazán hordozható kódot írni azonban sokkal nehezebb. a short legalább 16. általában azért teszik ezt. Annak. hogy nem érdekli õket a hordozhatóság. a memória hozzáférési idejében.Forrás: http://www. megengedhetik maguknak azt a hozzáállást. hogy a hardver jellemzõit megfelelõen kihasználhassa. hogy ez lehetõséget ad a programozónak. kénytelenek törõdni ezzel. Az egyik módszer az lehet. Az alaptípusok méretére vonatkozóan a következõk garantáltak: 1 ≡ sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) 1 ≤ sizeof(bool) ≤ sizeof(long) sizeof(char) ≤ sizeof(wchar_t) ≤ sizeof(long) sizeof(float) ≤ sizeof(double) ≤ sizeof(long double) sizeof(N) ≡ sizeof(signed N) ≡ sizeof(unsigned N) A fentiekben N lehet char.2). rákényszerülnének arra. mint megpróbálni késõbb kibogozni a problémát. mint a mostani. hogy átviszik más rendszerre. több elõjel nélküli. hogy több egész. a rendszerfüggõ könyvtárakét azonban sokkal nehezebb. A megvalósításból eredõ nyelvi sajátosságok hatását viszonylag könnyû korlátozni. mert ha nem tennék. és valakinek meg kell találnia és ki kell javítania a megvalósítás sajátosságaiból adódó problémákat. akik azt állítják. a long pedig legalább 32 bites. int vagy long int. Az alábbi ábra az alaptípusok egy lehetséges halmazát és egy minta-karakterláncot mutat: . Sok gépen jelentõs különbségek vannak a memóriaigényben. A char a gép karakterkészletébõl egy karaktert tárolhat. így a char mérete 1. Ha egy program sikeres. az az oka. és a többfajta alaptípussal való számolási sebességben. short int. és több lebegõpontos típus van. akkor valószínû.hu 98 Alapok tót használnak. továbbá biztosított. amikor egy programot megírnunk. általában könnyen kiválaszthatjuk például a megfelelõ egész típust egy adott változó számára. Ez beszûkült látásmód. Sokkal egyszerûbb ismerni és korlátozni az adott fordító használatának hatását. hogy a char legalább 8. Azok. A C++ objektumainak mérete mindig a char méretének többszöröse. A programokat továbbá gyakran újra kell fordítani más fordítókkal ugyanarra a rendszerre és még a kedvenc fordítónk késõbbi változata is másképpen csinálhat néhány dolgot. miszerint „a nyelv az.doksi. hogy lehetõleg csak a standard könyvtár elemeit használjuk. Egy objektum méretét a sizeof operátorral kaphatjuk meg (§6.

a char elõjeles == " << std::numeric_limits<char>::is_signed << '\n'. hogy ne legyen adatvesztés (§C. Ha v érték pontosan ábrázolható egy T típusú változóban. az int típusnak a legmegfelelõbbnek kell lennie az egészek tárolására és kezelésére. az adott C++változat egyedi tulajdonságait megtalálhatjuk a <limits> fejállományban (§22. a következõ fejezetek olvasásához ugyanakkor ez nem szükséges.6). az értékek úgy alakítódnak át. ez általában egy 4 bájtos (32 bites) gépi szó. fõleg azért. Nem bölcs dolog többet feltételezni. Nagyobb programok készítéséhez az automatikus konverziókat részletesebben meg kell értenünk.5 cm egy bájt) egy megabájt memória körülbelül öt kilométernyire lógna ki a jobb oldalon. } Az alaptípusok értékadásokban és kifejezésekben szabadon párosíthatók. ha elkerüljük azokat az eseteket. .2). ez jellemzõen egy 8 bites bájt. amikor a konverziók nem értékõrzõk (§C. hogy képesek legyünk értelmezni a mások által írt kódot. akkor v érték T típusúvá alakítása megõrzi az értéket és nincs probléma.hu 4.doksi. Legjobb.6). A char típust az adott nyelvi változatnak úgy kell megválasztania. Ha szükségünk van rá. ahol a char 32 bites. hogy a karakterek tárolására és kezelésére egy adott számítógépen a legmegfelelõbb legyen.6. Például: #include <limits> #include <iostream> int main() { std::cout << "A legnagyobb lebegõpontos szám == " << std::numeric_limits<float>::max() << ".2.Forrás: http://www. Ahol lehetséges. world!\0 Ugyanilyen méretarányban (0. Hasonlóan. Típusok és deklarációk 99 char: bool: short: int: int*: double: char[14]: 'a' 1 756 100000000 &c1 1234567e34 Hello. Például vannak olyan gépek.

amikor egy mutató ismeretlen típusú objektumra mutat: void x. void* pv. Ez viszont a nyelvtant („A” függelék) kevésbé szabályossá tenné és ütközne a C-beli gyakorlattal. AUTO. Vagy arra használjuk. Az alábbi kód például három egész állandót ad meg – ezeket felsoroló konstansoknak nevezzük – és értékeket rendel hozzájuk: enum { ASM. BREAK }.8. Minden felsorolás önálló típus. amely felhasználó által meghatározott értékeket tartalmaz.3) // ismeretlen típusú objektumra hivatkozó mutató (§5. annak jelölésére. hogy meghatározzuk. BREAK==2.6) Amikor egy függvényt bevezetünk. A felsoroló típusok tagjaiként névvel rendelkezõ egész konstansokat adhatunk meg. . Következésképpen a void „látszólagos visszatérési típus”-ként használatos. hogy a függvény nem ad vissza értéket.7. Felsoroló típusok A felsoroló típus (enumeration) olyan típus.doksi. 4. a függvény nem ad vissza értéket. nincsenek void típusú objektumok. hogy a visszatérési típus elhagyásával jelezzük. AUTO==1.hu 100 Alapok 4. Logikailag elvárható lenne. meg kell határozni visszatérési értékének típusát is. // hiba: nincsenek void objektumok // az f függvény nem ad vissza értéket (§7. AUTO. így ASM==0. void f(). de csak egy bonyolultabb típus részeként lehet használni. A felsoroló konstansok típusa a felsorolási típus lesz. BREAK }. vagy akkor. A felsoroló típusnak lehet neve is: enum keyword { ASM. hogy egy függvény nem ad vissza értéket. Az AUTO például keyword típusú.Forrás: http://www. Alapértelmezés szerint az állandók 0-tól növekvõen kapnak értékeket. Void A void formája alapján alaptípus. Meghatározása után az egész típushoz hasonlóan használható.

A felsoroló típus értékhalmaza összes tagjának értékét tartalmazza. felkerekítve a 2 legközelebbi. b = 9 }. azoknál nagyobb hatványánál eggyel kisebb értékig. mind a felhasználónak. flag f1 = 5. mert a három keyword típusú értékbõl csak kettõt kezeltünk. Ha az érték nem esik a felsoroló típus értékhalmazába.1) konstans kifejezéssel (§C. // tartomány: 0:1 // tartomány: 0:15 // tartomány: -1048576:1048575 Egy integrális típus értékét meghatározott módon felsoroló típusúvá alakíthatjuk. flag f2 = flag(5). az értékhalmaz 0-val kezdõdik. enum e3 { min = -10. . miért nincs automatikus konverzió egészrõl felsoroló típusra. a konverzió eredménye nem meghatározott: enum flag { x=1. y=2. z=4. Például: enum e1 { dark. A felsoroló konstans a kezdeti értékadáskor integrális típusú (§4. amely tartalmazhatja a felsoroló konstansok értékét.hu 4. max = 1000000 }.Forrás: http://www. flag f3 = flag(z|e). Ez a szabály azt a legkisebb bitmezõt adja meg. e=8 }. ha negatív. a legtöbb egész érték ugyanis nem ábrázolható egy adott felsoroló típusban. a tagoknál kisebb negatív hatványával. enum e2 { a = 3.1. mind a fordítónak utalunk a változó tervezett használatára: void f(keyword key) { switch (key) { case ASM: // valamit csinálunk break. a 2 legközelebbi.5. case BREAK: // valamit csinálunk break. Típusok és deklarációk 101 Ha keyword típusú változót adunk meg sima int helyett.) is megadható. light }. // tartomány: 0:15 // típushiba: 5 nem flag típusú // rendben: flag(5) flag típusú és annak tartományán belüli // rendben: flag(12) flag típusú és annak tartományán belüli // meghatározatlan: 99 nem esik a flag tartományán belülre Az utolsó értékadás mutatja. Ha a legkisebb felsoroló konstans nem negatív. } } A fordító figyelmeztetést adhat.doksi. flag f4 = flag(99).

int count = 1. Thor }. }. "nyár". int day(Date* p) { return p->d. struct User. enum Beer { Carlsberg. y. amelyek arra építenek. Tuborg.2. } . de nem lehet 8 egy olyan gépen. Alapértelmezés szerint a felsoroló típusok aritmetikai mûveletek esetében egésszé alakítódnak (§6.hu 102 Alapok A felsoroló típusok értékhalmazának fogalma különbözik a Pascal nyelvcsaládba tartozó nyelvek felsoroló típusainak fogalmától. A deklarációk sokféleségét a következõ példák szemléltetik: char ch. string s. A felsoroló típus felhasználói típus. A C-ben és a C++-ban azonban hosszú hagyománya van azoknak a bitkezelõ mûveleteknek. char* name = "Natasa". azaz meg kell határoznunk típusukat. char* season[ ] = { "tavasz". } typedef complex<short> Point. a név miféle egyedre hivatkozik. "tél" }. hiszen akkor nem lehetne intként vagy unsigned int-ként ábrázolni. Deklarációk A C++ programokban a neveket (azonosítókat) használat elõtt be kell vezetnünk.Forrás: http://www. amely képes a felsoroló típus értékhalmazát tárolni és nem nagyobb sizeof(int)-nél. "õsz". például a ++ és << operátorokkal (§11.doksi.1415926535897932385. így a felhasználók a felsorolásra saját mûveleteket adhatnak meg. extern int error_number. } double sqrt(double). struct Date { int d.2). template<class T> T abs(T a) { return a<0 ? -a : a. hogy megmondjuk a fordítónak. 4. hogy a felsoroló típusok tagjain kívüli értékek jól körülhatároltak. ahol sizeof(int)==4. A felsoroló típusok sizeof-ja egy olyan integrális típus sizeof-ja. m. Például sizeof(e1) lehet 1 vagy talán 4. namespace NS { int a. const double pi = 3.3).9.

hogy egyezzen a hivatkozott egyed típusában. és a User típus egy másik deklarációjának kell megadnia. Ugyanakkor a nevet többször is deklarálhatunk (bevezethetjük). */ } int error_number = 1. A Point esetében az egyed a complex<short> típus. A fenti deklarációk többsége definíció is. */ }. a deklaráció többet is jelenthet annál.3-ban).hu 4.Forrás: http://www. hogy a típus hogy nézzen ki. amelyet változóként használunk (vagyis ezt a memóriaterületet fogjuk lefoglalni). amelyre a név hivatkozik. mint hogy egyszerûen egy nevet kapcsol össze a név típusával. a pi állandónál a 3. Az sqrt függvény kódját (törzsét) más deklarációkkal kell meghatározni. // hiba: újbóli definíció // hiba: nem megfelelõ típus A következõben viszont egy sincs (az extern használatáról lásd §9. A ch esetében például ez az egyed a megfelelõ memóriaterület. a Date-nél egy új típus.doksi. struct User.2. extern int error_number. Így a következõ részletben két hiba van: int count. int count. .. így a Point a complex<short> szinonimája lesz. azaz meg is határozza azt az egyedet. extern short error_number. A C++ programokban minden név számára mindig pontosan egy definíció (meghatározás) létezhet (az #include hatásait lásd §9. extern int error_number. A fenti deklarációk közül csak a double sqrt(double).. struct User { /* .2): extern int error_number. Típusok és deklarációk 103 Amint a példákból látható. Például: double sqrt(double d) { /* . Egy egyed minden deklarációja meg kell. az int típusú error_number változó számára az error_number egy másik deklarációjának kell lefoglalnia a memóriát. deklarációk nem definíciók is egyben: azaz máshol kell definiálni (meghatározni) azokat az egyedeket.1415926535897932385 érték. extern int error_number... a day-nél a meghatározott függvény. amelyekre hivatkoznak.

} A definíciók közül csak az alábbi nem határoz meg értéket: char ch. hogy hogyan és mikor kap egy változó alapértelmezett értéket. egy deklarátorból.2.) Minden deklaráció. 4. A leggyakoribb deklarátor-operátorok a következõk (§A. }. A függvény.1415926535897932385..5.és névtér-meghatározásokat kivéve a deklaráció pontosvesszõre végzõdik: char* kings[ ] = { "Antigónusz". int day(Date* p) { return p->d. // .2).Forrás: http://www. sablonok. egy alaptípusból. count = 2. string s.6) és az extern (§9.1.doksi. lásd §4. m.5. A minõsítõ (specifier) egy kulcsszó. typedef complex<short> Point.9. y. Itt az alaptípus char.7.9. és a bevezetett elem néhány.5-öt és §10. (Arról. a deklarátor a *kings[ ].2-t.hu 104 Alapok Néhány definíció valamilyen „értéket” is meghatároz a megadott egyedeknek: struct Date { int d.. "Ptolemaiosz" }. "Szeleukusz".1): . függvények és állandók esetében ez az „érték” nem változik. §12. mint a virtual (§2. name = "Marian".4. egyben definíciónak is minõsül. A deklarátor (declarator) egy névbõl és néhány nem kötelezõ operátorból áll. A deklarációk szerkezete A deklarációk négy részbõl állnak: egy nem kötelezõ minõsítõbõl. char* name = "Bjarne". amely értéket határoz meg. } const double pi = 3. nem a típusra jellemzõ tulajdonságát határozza meg. Nem konstans adattípusok esetében a kezdeti értéket késõbb módosíthatjuk: void f() { int count = 1. a kezdõérték-adó rész pedig az ={…}. Típusok. és egy – szintén nem kötelezõ – kezdõérték-adó kifejezésbõl.

gt(int a. a [ ] és a () operátorokat azonban arra tervezték. Ez az „implicit int” szabály sok félreértés és nehezen megfogható hiba forrása volt. Típusok és deklarációk 105 * *const & [] () mutató konstans mutató referencia tömb függvény elõtag elõtag elõtag utótag utótag Használatuk egyszerû lenne.Forrás: http://www. 4.doksi. // hiba: nincs típus // hiba: nincs visszatérési típus // rendben: 'unsigned' jelentése 'unsigned int' // rendben: 'long' jelentése 'long int' A szabványos C++ ebben eltér a C és a C++ régebbi változataitól. hogy az operátorok csak egyes nevekre vonatkoznak. Két egészet például így vezethetünk be: int x. ha mindegyikük elõtagként (prefix) vagy mindegyikük utótagként (postfix) használt operátor volna. y. Jegyezzük meg. A deklaráció ekkor vesszõvel elválasztott deklarációk listáját tartalmazza. . ha olyasmit akarunk kifejezni. ezért kerülendõk.hu 4. A *. Teljes részletességgel lásd a nyelvtant az „A” függelékben. mint „… függvényre hivatkozó mutató” (lásd az §5. // int x és int y. Az utótagként használt operátorok több megkötéssel járnak.2). amelyek megengedték az elsõ két példát.9.1 példáit). NEM int* y // int x és int* q // int v[10] és int* pv A fentihez hasonló szerkezetek rontják a program olvashatóságát. és zárójeleket kell használnunk. Több név bevezetése Egyetlen deklarációban több nevet is megadhatunk. int x. } unsigned ui. hogy a típus int. az ugyanabban a deklarációban szereplõ további nevekre nem: int* p. azt feltételezve.2. *pv. a [ ] és a () pedig utótag operátorok. Következésképpen a *kings[ ] egy valamire hivatkozó mutatókból álló vektor. int b) { return (a>b) ? a : b. // int* p és int y. így a * elõtag-. y. Jegyezzük meg.2). mint az elõtagként használtak. hogy kifejezésekben is használhatók legyenek (§6. *q. ha nincs típus megadva (§B. hogy a típus nem hagyható el a deklarációból: const c = 7. long li. int v[10].

amely kiadhat egy nevet. Az osztályok (10. ez pedig határokat szab. amelyek csak kicsit különböznek. fejezet) és a névterek (§8. hogy a hatókörök kicsik maradjanak. amelyek csak a kezdõbetûben térnek el. Így a var10 és nem a var név (amit a 10-es szám követ).2) használhatók arra. de nem bölcs dolog olyan neveket választani. mindig a leghosszabb olyan sorozatot keresi. Például a nagybetûs o (O) és a nulla (0) nehezen megkülönböztethetõ. a kis L (l) és az egyes (1) szintén. Példák a nevekre: hello DEFINED var0 ez_egy_szokatlanul_hosszú_név foO bAr u_name LoPatko var1 CLASS _class ___ És néhány példa olyan karaktersorozatokra.esedekes egy bolond foo~bar $sys .hu 106 Alapok 4. és Department_number. Amikor a fordító olvassa a programot. lO.és nagybetûket a nyelv megkülönbözteti. így a Count és a count különbözõ nevek.name class if 3var Az aláhúzással kezdõdõ nevek a nyelvi megvalósítás és a futási idejû környezet egyedi eszközei számára vannak fenntartva. amelyek nem használhatók azonosítóként: 012 fizetes. Az elsõ karakternek betûnek kell lennie. A nagy hatókörû nevek lehetõleg hosszúak és érthetõek legyenek. Az _ (aláhúzás) karaktert betûnek tekintjük. hagyományos stílusú nevük van. Hasznos dolog viszonylag rövidnek hagyni a gyakran használt neveket. Window_with_border. A fordítóprogram írója azonban a megvalósítás egyes részeire nincs befolyással (konkrétan a szerkesztõprogramra. nem pedig az else. a linker-re). mint a new és az int. ha a kis hatókörben használt neveknek rövid. nem használhatók felhasználói egyedek neveként. A kis. Hasonlóan. A kód viszont érthetõbb lesz. Néhány futási idejû környezet ugyancsak szükségessé teszi.3. i és p. az elseif is egy név. mint vector.9. az igazán hosszúakat . A C++ kulcsszavai („A” függelék). Következésképpen azonosítónak a l0. l1 és ll nem szerencsés választás. A bõvítések (például a $ engedélyezése egy névben) nem hordozható programokat eredményeznek. hogy kibõvítsük vagy megszorítsuk az azonosítóban elfogadható karakterkészletet. A legjobb elkerülni azokat a neveket. mint x. Nevek A név (azonosító) betûk és számok sorozatából áll.Forrás: http://www.doksi. A C++ nem korlátozza a névben használható karakterek számát. amit az if kulcsszó követ. így ezeket nem szabadna használni alkalmazói programokban.

azaz egy nevet újra meg lehet adni úgy. Továbbá használjunk csupa nagybetût makrók esetében (ha makrókat kell használnunk. amelyben a deklaráció szerepel. Legyünk következetesek a rövidítések és betûszavak használatában is. Válasszuk meg úgy a neveket. ha az azonosítóban szét akarjuk választani a szavakat. A jó nevek megválasztása is egyfajta mûvészet. // globális x // a lokális x elfedi a globális x-et // értékadás a lokális x-nek // elfedi az elsõ lokális x-et // értékadás a második lokális x-nek . A blokkból való kilépés után a név visszanyeri elõzõ jelentését: int x. Típusok és deklarációk 107 pedig megtartani a kevésbé gyakran használatos egyedeknek. mint a number_list (számok listája). x = 2.9. amelyet a { } kapcsos zárójelek határolnak.hu 4. Hatókörök A deklaráció a megadott nevet egy hatókörbe (scope) vezeti be. void f() { int x. { } int x. x = 1. ha függvényen. nehéz elérni a következetességet. osztályon (10.doksi. amelyben a deklaráció szerepel. Például írjuk nagy kezdõbetûvel a nem standard könyvtárbeli felhasználói típusokat és kisbetûvel azokat a neveket. 4. azaz a nevet csak a programszöveg meghatározott részében lehet használni. mivel a programokat általában különbözõ forrásokból vett részletek alkotják és számos különbözõ ésszerû stílus használatos bennük.4. Egy nevet globálisnak nevezünk. Próbáljunk következetes elnevezési stílust fenntartani. hogy az egyed jelentésére és ne annak megvalósítására utaljanak. A blokkokban szereplõ névdeklarációk a körülvevõ blokkban lévõ deklarációkat és a globális neveket elfedhetik.2) kívül bevezetett.7) tároljuk. hogy egy másik egyedre hivatkozzon egy blokkon belül. A globális nevek hatóköre a bevezetés pontjától annak a fájlnak a végéig terjed. A blokk olyan kódrész. ha a telefonszámokat listában (§3. amelyek nem típusnevek (például Shape és current_token). fejezet) vagy névtéren (§8. A phone_book (telefonkönyv) például jobb. például HACK) és használjunk aláhúzást. Akárhogy is.Forrás: http://www. A függvényeken belül megadott nevek esetében (ezeket gyakran lokális vagy helyi névnek hívjuk) ez a hatókör a deklaráció helyétõl annak a blokknak a végéig tart. még akkor is.

9[9]).. } // a globális x elfedése // értékadás a globális x-nek // értékadás a lokális x-nek Elfedett lokális név használatára nincs mód. } // perverz: kezdeti értékadás x-nek saját maga (nem meghatározott) értékével Ez nem tiltott. Ha valaki olyan neveket használ globális változóként vagy egy hosszabb függvény lokális változójaként. Mivel az ilyen hibák viszonylag ritkán fordulnak elõ.doksi. hogy egy név többször szerepel. void f2() { int x = 1. // értékadás az elsõ lokális x-nek // a globális x címének felhasználása int* p = &x. ha egy változót azelõtt használunk. void f3() { int x = x. akkor maga keresi a bajt. .. // . azaz a teljes deklarátor után és a kezdeti érték(ek)et megadó rész elõtt. csak butaság. nagyon nehezen lehet azokat megtalálni. A nevek elfedése elkerülhetetlen nagy programok írásakor. Következésképpen a névelfedések számát a lehetõ legkisebbre kell csökkenteni. hogy egy nevet saját kezdõértékének meghatározására is használhatunk: int x. A kódot olvasónak azonban könnyen elkerüli a figyelmét. Ez azt jelenti. mielõtt értékét beállítottuk volna (lásd §5.Forrás: http://www. mint i és x. x = 2. ::x = 2. A név hatóköre a név deklarációjának pontjától kezdõdik.hu 108 Alapok } x = 3. Az elfedett globális nevekre a :: hatókörjelzõ használatával hivatkozhatunk: int x. Egy jó fordítóprogram figyelmeztetést ad.

hu 4.9.. névtér (§8. így az alábbi hibás.. mint ha a függvény legkülsõ blokkjában lennének megadva.'' A lokális változóknak (ezeket néha automatikus objektumoknak nevezzük) és a szabad tárban létrehozott objektumoknak (dinamikus vagy „heap” objektumok) alapértelmezés szerint nincs kezdõértékük: void f() { int x.4). void f4() { int y = x. Típusok és deklarációk 109 Egy névvel egy blokkban két különbözõ objektumra a :: operátor használata nélkül is hivatkozhatunk: int x = 11.4) (melyeket együttesen statikus objektumoknak nevezünk) a megfelelõ típus 0 értékét kapják kezdõértékül: int a. mert x-et ugyanabban a hatókörben kétszer adtuk meg: void f5(int x) { int x. } // perverz: // a globális x felhasználása. int x = 22. Ha nincs megadva ilyen.1.0. y = x.2. } // hiba Ilyen hiba gyakran elõfordul. y = 11 // a lokális x felhasználása. y = 22 A függvényparaméterek neveit úgy tekintjük. // . a globális (§4.2.'' // jelentése "double d = 0. érdemes figyelnünk rá.Forrás: http://www.doksi. vagy helyi statikus objektumok (§7. akkor ez határozza meg az objektum kezdeti értékét. §10.5. } // x értéke nem meghatározott . Kezdeti értékadás Ha egy objektumhoz kezdõérték-adó kifejezést adunk meg. 4. // jelentése "int a = 0. double d.9.2).

1.2. 2 }. Következésképp el kellene neveznünk azt.8).6. A bonyolultabb objektumoknak egynél több értékre van szükségük a kezdeti értékadáshoz. Objektumok és balértékek Névvel nem rendelkezõ „változókat” is lefoglalhatunk és használhatunk. Point z(1.Forrás: http://www.4. amikor definíciójához érkezünk. Jegyezzük meg.4.9). egy balérték hivatkozhat állandóra (const) is (§5. Az ilyen objektumokat automatikus objektumoknak nevezzük. hogy az adott szerkezet statikus-e vagy sem. Ez az objektum legegyszerûbb és legalapvetõbb fogalma. A tömbök és struktúrák C típusú feltöltésekor (§5. Az objektumnak ezt az egyszerû és alacsony szintû fogalmát nem szabad összetéveszteni az osztályobjektumok és többalakú (polimorf típusú) objektumok (§15. A nem const-ként megadott balértéket szokás módosítható balértéknek (modifiable lvalue) is nevezni. Az ilyen objektumokat statikus objektumoknak nevezzük. az objektum egy folytonos tárterület. hogy a következõt jelentse: „valami. A felhasználói típusokhoz magunk is megadhatunk alapértelmezett kezdõértéket (§10. egy függvényben bevezetett változó akkor jön létre.2.5). *p[a+10]=7).4). A new és delete operátorokkal olyan objektumok hozhatók létre. hogy a deklarációkban szereplõ () üres zárójelek jelentése mindig „függvény”: int a[ ] = { 1. A tömbelemeknek és a nem statikus struktúrák vagy osztályok tagjainak az élettartamát az az objektum határozza meg.9.4.3). A balérték (lvalue) szót eredetileg arra alkották.2.2. A globális és névtér-hatókörben bevezetett objektumok és a függvényekben vagy osztályokban megadott static objektumok (csak) egyszer jönnek létre és kapnak kezdeti értéket.7) ezt egy { } zárójelek által határolt listával érhetjük el. // tömb kezdeti értékadása // függvény stílusú kezdeti értékadás (konstruktorral) // függvény-deklaráció 4.3) fogalmával. §10. .1. §5. függetlenül attól.4. és a program befejeztéig „élnek”(10. ami egy értékadás bal oldalán szerepelhet”. és akkor szûnik meg.2). hogy „valami a memóriában”.5.2. amelynek részei. int f().6). A konstruktorral rendelkezõ felhasználói típusoknál a függvény stílusú paraméterlisták használatosak (§2.hu 110 Alapok A tömbök és struktúrák tagjai alapértelmezés szerint kapnak kezdõértéket. és értéket is adhatunk nekik furcsa kifejezésekkel (pl.2). Azaz. amely egy objektumra hivatkozik. Hacsak a programozó másképp nem rendelkezik (§7.4.doksi. amikor a neve a hatókörön kívülre kerül (§10. amelyek élettartama közvetlenül szabályozható (§6. a bal oldali érték („balérték”) pedig egy olyan kifejezés. Nem minden balérték lehet azonban az értékadás bal oldalán. §10.

ezért megadhatjuk a szinonimáját. amikor egy viszonylag nagy egészre van szükségünk. Végezetül. fejezet). akik ugyanolyan jelentéssel vagy ábrázolással rendelkezõ önálló típusokat szeretnének. úgy.7. p2. Azok. Typedef Az a deklaráció. hogy a kódban egyszer szereplõ int32-t most másképp határozzuk meg: typedef long int32. az uchar-t: typedef unsigned char uchar. A typedef másik használata az. használják a felsoroló típusokat (§4. Ha most az int32-t használjuk. . a typedef-ek inkább más típusok szinonimái. mint önálló típusok. Típusok és deklarációk 111 4. hogy egy típushoz való közvetlen hozzáférést egy helyre korlátozunk: typedef int int32.8) vagy az osztályokat (10. Például az unsigned char túlságosan hosszú az igazán gyakori használatra.hu 4. melyeknek szinonimái. gyakran typedef-nek (áltípus) nevezett név kényelmes rövidítés lehet egy nehezen használható név helyett. programunkat átvihetjük egy olyan gépre. a típus számára új nevet hoz létre.9. char* p3 = p1. ahol a sizeof(int) 2-vel egyenlõ. typedef short int16. // p1 és p2 típusa char* Az így megadott. amit a typedef kulcsszó elõz meg. Pchar p1. Következésképpen a typedef-ek szabadon felcserélhetõk azokkal a típusokkal. nem egy adott típusú változót: typedef char* Pchar.Forrás: http://www.doksi.

§4. [19] Legyünk óvatosak az elõjelesrõl elõjel nélkülire és unsigned-ról signed-ra való átalakítással. §4. §4. mintsem a megvalósításra utaljon.9.2. §4. §C.9.4.6. [7] Figyeljünk arra.6.3.3. §4.9. [2] Ne használjuk ugyanazt a nevet egy hatókörben és az azt körülvevõ hatókörben is.6. §4. [20] Legyünk óvatosak a lebegõpontos típusról egészre való átalakítással.2. [8] Ha a beépített típus. §4. § C. §4.hu 112 Alapok 4.3. [13] Kerüljük a szükségtelen feltételezéseket a lebegõpontos típusok értékkészletével kapcsolatban is.9.9. §4.7 [9] A typedef-ekkel típusok szinonimáit adjuk meg. §4.6. §4.7. [3] Deklarációnként (csak) egy nevet adjunk meg. a nem helyi és ritkán használt nevek hosszabbak.6.9.2. §4. .2. [11] Kerüljük a karakterek számértékével kapcsolatos szükségtelen feltételezéseket. [12] Kerüljük az egészek méretével kapcsolatos szükségtelen feltételezéseket.1.3.doksi. Tanácsok [1] A hatókörök legyenek kicsik. [6] Elnevezési stílusunk legyen következetes. [14] Részesítsük elõnyben a sima int-et a short int-tel vagy a long int-tel szemben.9. §C.4. §C.3. [5] Kerüljük a hasonlónak látszó neveket. [10] Emlékezzünk arra.1.9.5.9. § C.6.9.6. így a típus számára beszédes nevet adhatunk.Forrás: http://www. új típusok definiálására használjunk felsoroló típusokat és osztályokat.6. §4. [18] Kerüljük az elõjel nélküli aritmetikát. amelyet egy érték ábrázolására használunk. használjunk typedef-et.1. §4. [4] A gyakori és helyi nevek legyenek rövidek.3. [16] Részesítsük elõnyben a sima char-t a signed char-ral és az unsigned char-ral szemben.2.6. megváltozhat. hogy minden deklarációban szükséges a típus megadása (nincs „implicit int”).4. §4.6.6. hogy a névválasztás inkább a jelentésre. [15] Részesítsük elõnyben a double-t a float-tal vagy a long double-lal szemben. [17] Kerüljük az objektumok méretével kapcsolatos szükségtelen feltételezéseket. § 4. [21] Legyünk óvatosak a kisebb típusokra való átalakításokkal (például int-rõl char-ra).10. §4.3.

ha az elsõ minden értéke minden szabványos megvalósításban ábrázolható a másik típus értékeként. Használjuk a sizeof operátort. amely kiírja az alaptípusok.1-et. ahol egy típus egy másik típusra mutat.5) Írjunk programot.2). Ha a program fordítása nem úgy sikerül.hu 4. néhány szabadon választott mutatótípus és néhány szabadon választott felsoroló típus méretét. világ!” programot (§3. írjunk hozzá definíciót. olvassuk el §B.5) Írjunk programot. (*1. amit a C++ programokban használhatunk a rendszerünkben? Van-e megszorítás a nevekben használható karakterekre? 7.3. (*1. 3. (*2) Mi a rendszerünkön a legnagyobb és legkisebb értéke a következõ típusoknak: char. (*2) Rajzoljunk ábrát az egész és alaptípusokról. float. írjunk olyan deklarációt.11. int. (*1) Mi a leghosszabb lokális név.9 minden deklarációjára végezzük el a következõket: ha a deklaráció nem definíció. 5. . ahogy kellene. amit a C++ programokban használhatunk a rendszerünkben? Mi a leghosszabb külsõ név. ami nem az. Rajzoljuk meg az ábrát kedvenc C++változatunk típusaira is. long. valamint a hozzájuk tartozó egész értékeket.Forrás: http://www. amely kiírja az 'a'…'z' betûket és a '0'…'9' számjegyeket. (*2) Futtassuk le a „Helló. Csináljuk meg ugyanezt hexadecimális jelöléssel. Típusok és deklarációk 113 4.doksi. long double és unsigned? 6. short. Ha a deklaráció definíció is. Gyakorlatok 1. Végezzük el ugyanezt a többi kiírható karakterre is. 4. (*1) §4. double. 2.

1.Forrás: http://www. Például: char c = 'a'.doksi. // a p a c címét tárolja .hu 5 Mutatók. azaz egy T* típusú változó egy T típusú objektum címét tartalmazhatja. hogy nehéz õket szétválasztani. Mutatók Ha T egy típus. T* a „T-re hivatkozó mutató” típus lesz.” (Tom Paine) Mutatók • Nulla • Tömbök • Karakterliterálok • Tömbre hivatkozó mutatók • Konstansok • Mutatók és konstansok • Referenciák • void* • Struktúrák • Tanácsok • Gyakorlatok 5. tömbök és struktúrák „A fenséges és a nevetséges gyakran annyira összefüggnek. char* p = &c.

A tömbelemekre hivatkozó mutatókon aritmetikai mûveleteket is végezhetünk (§5. char** ppc. amellyel a bájtokat gépi szavakból nyerik ki. char* p = &c.9. Azok.6).1) használhatunk.hu 116 Alapok Ábrával: p: &c c: 'a' Sajnos a tömbökre és függvényekre hivatkozó mutatók esetében bonyolultabb jelölés szükséges: int* pi. logikai operátorokat (§6. amelyek erre nem képesek. Az indirekció jele az elõtagként használt egyoperandusú * : char c = 'a'. következésképp a legkisebb objektum. hogy közvetlen kapcsolatot teremtsenek annak a gépnek a címzési eljárásaival. . // mutató egészre // mutató char-ra hivatkozó mutatóra // egészre hivatkozó mutatók 15 elemû tömbje // char* paraméterû függvényre hivatkozó mutató. olyan hardverrel rendelkeznek. hogy a bool legalább annyi helyet foglal. ezeket a §7.4) vagy struktúrákban levõ bitmezõket (§C. a char. Másrészrõl kevés gép tud közvetlenül egy bitet megcímezni. hogy a kisebb értékeket tömörebben lehessen tárolni. és az „A” függeléket a teljes nyelvtannal kapcsolatban. A mutatón végezhetõ alapvetõ mûvelet a „dereferencia”. int* ap[15]. amely számára önállóan memóriát foglalhatunk és amelyre beépített típusú mutatóval hivatkozhatunk. a c-ben tárolt érték 'a'. E mûveletet indirekciónak (közvetett használatnak. azaz a mutató által mutatott objektumra való hivatkozás. int (*fp)(char*). int* f(char*).doksi.3).7 pontban tárgyaljuk. egészre hivatkozó mutatót ad vissza Lásd §4. // a p a c címét tárolja // c2 == 'a' A p által mutatott változó c.Forrás: http://www. A mutatók célja. char c2 = *p.8. A legtöbb gép bájtokat címez meg. amin a program fut. hivatkozásnak) is hívják. így c2 értéke 'a' lesz. a függvényekre hivatkozó mutatók pedig végtelenül hasznosak.1-et a deklarációk formai követelményeire vonatkozóan. Jegyezzük meg. mint a char (§4. Ahhoz. egészet ad vissza // char* paraméterû függvény.2.

Nincs olyan objektum.Forrás: http://www. hogy a NULL–t ott is használni lehessen.doksi. vagy „tagra hivatkozó mutató” típusú konstansként is használható.A C-ben a nulla mutatót (nullpointer) szokás a NULL makróval jelölni. használjunk vektort (§3. amely 10 darab.1. A nullát általában (de nem szükségszerûen) a megfelelõ méretû „csupa nulla” bitminta jelöli.1.7. A típust a környezet dönti el. hogy a mutató nem hivatkozik objektumra.1. v[1]. Az elemek sorszámozása 0-tól size-1-ig terjed: float v[3]. A C++ szigorúbb típusellenõrzése miatt az ilyen NULL makrók helyett használjuk a sima 0-t. konstans kifejezés kell. // három lebegõpontos számból álló tömb: v[0]. mérete vagy dimenziója. a T[size] a „size darab T típusú elembõl álló tömb” típus lesz. ez kevesebb problémához vezet. vector<int> v2(i). Nulla A nulla (0) az int típusba tartozik. 5. char* a[32]. mint tömbökbõl álló tömbök: int d2[10][20].3) köszönhetõen a 0 integrális (§4.1)..hu 5. A const minõsítõ megakadályozza. azt jelölve.2. ahol állandóra van szükség. Ha változó méretre van szükségünk. // d2 olyan tömb. A szabványos konverzióknak (§C. amely számára a 0 címmel foglalnánk helyet. v[2] // karakterre hivatkozó mutatók 32 elemû tömbje: a[0] . §16.2. Ha úgy érezzük. muszáj a NULL-t megadnunk. 20 egészbõl álló tömböt tartalmaz . mutató.3): void f(int i) { int v1[i]. Tömbök Ha T egy típus. hogy legyen (§C. } // hiba: a tömb mérete nem konstans kifejezés // rendben A többdimenziós tömbök úgy ábrázolódnak. hogy a NULL-t véletlenül újra definiáljuk és biztosítja. a[31] A tömb elemeinek száma.1. tömbök és struktúrák 117 5. Mutatók. tegyük azt az alábbi módon: const int NULL = 0 .5). Következésképpen a 0 mutató-literálként viselkedik.6. lebegõpontos.

'b'.2]. 4 }.2. 'b'. Ha a méretet megadjuk. // hiba: tömböt nem lehet értékül adni } . a kezdõérték-listában nem szerepelhet annál több elem. a fordítóprogram a tömb méretét a kezdõérték-lista elemeinek megszámlálásával számítja ki.2.doksi. Az elõzõ kód egyenértékû a következõvel: int v5[ ] = { 1.5). 'd'. 0 }. // hiba: konstans kifejezésben nem lehet vesszõ A többdimenziós tömböket a §C.Forrás: http://www. 2. 3.2) és nem megengedett konstans kifejezésekben (§C. 'c'. Amikor egy tömböt úgy adunk meg.hu 118 Alapok A más nyelvekben tömbök méretének meghatározására használt vesszõ jelölés fordítási hibákat eredményez. hogy a méretét nem határozzuk meg. Jegyezzük meg. Következésképp v1 és v2 típusa rendre int[4] és char[4] lesz. 4 . a tömb maradék elemeire 0 lesz feltételezve: int v5[8] = { 1. hogy a kezdõérték-lista nem helyettesíthetõ és nem bírálható felül tömbértékadással: void f() { v4 = { 'c'. 4 }. mert a . Alacsonyszintû kódon kívül a legjobb. Tömbök feltöltése A tömböknek értékekbõl álló listákkal adhatunk kezdõértéket: int v1[ ] = { 1. 0. 0 }. 0. de kezdõértékeket biztosítunk. // hiba: túl sok kezdõérték // rendben Ha a kezdõérték túl kevés elemet ad meg. 3. 0 }. 2. 0 }. 'b'. 0 }. 5. ha kerüljük õket.1. (vesszõ) mûveletsorozatot jelzõ operátor (§6.7 pontban tárgyaljuk. Például próbáljuk ki ezt: int bad[5. char v2[ ] = { 'a'. 2. 3. 0. mert ez hibának számít: char v3[2] = { 'a'. char v4[3] = { 'a'.

3-at.3) vagy valarray-t (§22.4). hogy a karakterliterálok állandók. így a "Bohr" típusa const char[5] lesz. 5.hu 5. az eredmény nem meghatározott } Az effajta hibát általában nem lehet a futási idõig kideríteni.Forrás: http://www. nemcsak magától értetõdõ. Mutatók. Karakterliterálok A karakterliterál egy macskakörmökkel határolt karaktersorozat: "Ez egy karakterlánc" Egy karakterliterál a látszólagosnál eggyel több karaktert tartalmaz. (Lásd még §B. melynek értéke 0: sizeof("Bohr")==5 A karakterliterálok típusa „megfelelõ számú const (állandó) karakterbõl álló tömb”.) Az. A karaktertömböket kényelmi okokból karakterliterálokkal (§5. . hogy mennyire szereznek érvényt ennek a szabálynak. hogy millió sornyi C és C++ kód érvényes maradjon.2. hogy a nyelv adott változata jelentõsen optimalizálhassa a karakterliterálok tárolásának és hozzáférésének módját.2.2) is feltölthetjük.doksi. és a nyelv egyes megvalósításai is különböznek abban. így ez szükséges ahhoz. Ez azért megengedett. tömbök és struktúrák 119 Ha ilyen értékadásra van szükségünk. a '\0' null karakterre végzõdik.2. mert a karakterliterál típusa a C és a C++ korábbi változataiban char* volt. hanem azt is lehetõvé teszi. // hiba: értékadás konstansnak. A karakterliterálokat egy char*-nak is értékül adhatjuk. void f() { char* p = "Platón". p[4] = 'e'. Az ilyen karakterliterálokat azonban hiba ilyen mutatón keresztül módosítani.2. tömb helyett használjunk vector-t (§16.

} A range_error-t tartalmazó memóriaterület tartalma nem törlõdik az error_message() meghívása után. A nem grafikus karakterek jelölésére használt fordított perjel (§C. } // az eredmény az adott C++-változattól függ Jegyezzük meg. // . így egy függvény visszatérési értékeként biztonságosan megadható: const char* error_message(int i) { // . .1): const char* p = "Herakleitosz". Az. karaktereit egy tömbbe kell másolnunk: void f() { char p[ ] = "Zénón"..doksi.. } // p egy 6 karakterbõl álló tömb // rendben A karakterliterál tárolási helye nem változik (statikus). melyekre a mutatók hivatkoznak.Forrás: http://www. const char* q = "Herakleitosz".. return "tartományhiba". amit biztosan módosíthatunk. az adott nyelvi változattól függ (§C.. hogy két egyforma karakterliterál egyetlen memóriaterületen tárolódik-e. void g() { if (p == q) cout << "Egyezik!\n". Ez lehetõvé teszi az idézõjel (" ) és a fordított perjel „escape” karakter (\) karakterláncon belüli ábrázolását is. Az '\n' (új sor) karakter ezek közül messze a leggyakrabban használt: cout<<"csengõ az üzenet végén\a\n". hogy a mutatókra alkalmazott == a címeket (a mutató értékeket) hasonlítja össze.3.hu 120 Alapok Ha olyan karakterláncot szeretnénk. p[0] = 'R'.2) szintén használható egy karakterlánc belsejében. Üres karakterláncot a "" szomszédos macskaköröm-párral írhatunk le (típusa const char[1]). nem azokat az értékeket.

amely alert-ként is ismert. // mutató a kezdõelemre (automatikus konverzió) int* p2 = &v[0].doksi. A "Jens\000Munk" karakterláncot például az olyan standard könyvtárbeli függvények.hu 5. mint egy mutatót. // mutató a kezdõelemre int* p3 = &v[4]. // mutató az "utolsó utáni" elemre .4. amely a tömb elsõ elemére mutat: int v[ ] = { 1. Mutatók. 3. §C3. hogy a programszöveg szebb legyen: char alpha[ ] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ".3). 5. int* p1 = v. így az alpha egyetlen karakterlánccal is megadható lett volna: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ". kiírása pedig valamilyen hangjelzést eredményez. "Jens"-ként fogják kezelni (§20. A karakterláncokban „igazi” sortörés nem szerepelhet: "Ez nem karakterlánc hanem formai hiba" A hosszú láncok „üreshely” (whitespace) karakterekkel széttördelhetõk.1).3. típusuk const wchar_t[ ]. hogy utána is vannak karakterek. A fordítóprogram összefûzi a szomszédos láncokat.3. Az L elõtagú karakterláncok – mint amilyen az L"angst" – „széles” karakterekbõl (wide char) állnak (§4. A null karaktert elvileg a karakterláncok belsejében is használhatnánk.Forrás: http://www. mint a strcpy() és a strlen(). Tömbökre hivatkozó mutatók A C++-ban a tömbök és mutatók között szoros kapcsolat áll fenn. tömbök és struktúrák 121 Az '\a' karakter az ASCII BEL (csengõ). de a legtöbb program nem feltételezi. Egy tömb nevét úgy is használhatjuk. 2. 4 }.

sem definiált módon.hu 122 Alapok Ábrával: p1 p2 p3 v: 1 2 3 4 Az biztosan mûködik. hogy értelmes mûveleteket hajthasson végre rajta.doksi. hogy a null karakter jelzi a karakterlánc végét. §18. strlen(v). A függvénynek azonban valahogy meg kell határoznia a tömb méretét. Szerencsére mutatóról tömbre való átalakítás nem végezhetõ sem automatikusan. ezért az ilyesmi kerülendõ. // char[ ] automatikus átalakítása char*-gá v = p. hogy az automatikus konverziót lehetetlen elkerülni. // char[ ] automatikus átalakítása char*-gá strlen(p). amelynek meghívásakor a v tömb átmásolódik.3). A tömbparaméter automatikus mutatóvá alakítása azt jelenti.7. Az a bökkenõ. Nem meghatározott. Ez sok algoritmus számára fontos (§2.Forrás: http://www. // a <string. // hiba: a tömbnek nem adható érték } A standard könyvtár strlen() függvényének mindkét híváskor ugyanaz az érték adódik át. A C standard könyvtárában levõ más függvényekhez hasonlóan – amelyek karakterre hivatkozó mutatókat kapnak paraméterként – az strlen() is arra számít. amikor egy tömb kezdõeleme elõtt levõ elem címét vesszük. így az „eggyel a kezdõelem elõtti elem” egyszerûen értelmetlen lesz. char* p = v.2. így a strlen(p) . hogy mi történik. vagyis nincs mód olyan függvény bevezetésre. nem szabad írásra vagy olvasásra használni. Egyes számítógépeken a tömbök gyakran a gép címzési határain kerülnek lefoglalásra. Mivel azonban egy ilyen mutató ténylegesen már nem mutat egy tömb elemére. ha a mutatót eggyel a tömb vége utáni elemre állítjuk. A tömbnevek automatikus (implicit) átalakítása mutatóvá széleskörûen használatos a C stílusú kódokban szereplõ függvényhívásoknál: extern "C" int strlen(const char*).h> fejállományból void f() { char v[ ] = "Annemarie". hogy a tömb mérete elvész a függvény számára.

mint p egész értéke. A programozók logikai és esztétikai alapon választhatnak a változatok között. } Az elõtagként használt * indirekció operátor egy mutató-hivatkozást old fel. hogy p+1 egész értéke sizeof(T)-vel nagyobb lesz. p++) use(*p). Ha a +. Hajtsuk végre a következõt: #include <iostream> int main () { int vi[10]. } Ez egyenértékû a mutatóval történõ bejárással: void fp(char v[ ]) { for (char* p = v.Forrás: http://www. short vs[10]. i++) use(v[i]).8. p-1 pedig az elõzõ elemre mutat. így *p a p által mutatott karakter lesz. -. a ++ pedig úgy növeli a mutatót. A modern fordítóprogramoknak ugyanazt a kódot kell létrehozniuk mindkét példa esetében (lásd §5.hu 5. Íme egy példa egy karakterlánc bejárására index használatával: void fi(char v[ ]) { for (int i = 0. hogy az a tömb következõ elemére hivatkozzon.3. ++ vagy -. 18. Ez meglehetõsen alacsony szintû megoldás. illetve egy indexszel vagy egy elemre hivatkozó mutatóval valósítható meg.) és string (20.9[8]-at).3. Tömbök bejárása Sok algoritmus lényege a tömbökhöz és más hasonló adattípusokhoz való hatékony és „elegáns” hozzáférés (lásd §3. 5. A hozzáférés egy tömbre hivatkozó mutatóval.1. akkor p-rõl feltételezzük. az eredmény a mutatók által hivatkozott objektumok típusától függ. Amikor egy T* típusú p mutatóra alkalmazunk egy aritmetikai operátort. fejezet). Ez arra utal. fejezet) esetében nincs ilyen probléma. *p!=0. Nincs olyan eredendõ ok.doksi. v[i]!=0. nem beleértve a null karakter végzõdést . amiért az egyik változat gyorsabb lenne a másiknál. A standard könyvtárban lévõ vector (§16. . tömbök és struktúrák 123 a p karaktereinek a 0 null karakter végzõdésig számolt mennyiségét jelenti. hogy egy T típusú objektumokból álló tömb elemére mutat.aritmetikai mûveleti jeleket mutatókra alkalmazzuk. így p+1 a tömb következõ elemét jelzi. Mutatók.

hogy a tömb elemeinek száma is tárolódik a tömbbel együtt. int v2[10].hu 124 Alapok } std::cout << &vi[0] << ' ' << &vi[1] << '\n'. Amikor kivonunk egy mutatót egy másikból. std::cout << &vs[0] << ' ' << &vs[1] << '\n'.Forrás: http://www. int* p1 = v2+2. hogy bejárjunk egy tömböt. A mutatókhoz egész értéket is adhatunk és ki is vonhatunk belõle egészet. ezért legjobb elkerülni. amelyre az eredeti mutató. A tömbök nem önleírók. ha mindkét mutató ugyanannak a tömbnek az elemeire mutat (bár a nyelvben nincs gyors mód annak ellenõrzésére. az eredményül kapott mutató érték felhasználása kiszámíthatatlan eredményhez vezethet: void f() { int v1[10]. Ha ez az érték nem ugyanannak a tömbnek egy elemére mutat.doksi. Ekkor a következõt kapjuk (a mutatók értékének alapértelmezés szerinti. hogy sizeof(short) az adott megvalósításban 2. int* p2 = v2-2. sizeof(int) pedig 4. hexadecimális jelölését használva): 0x7fffaef0 0x7fffaef4 0x7fffaedc 0x7fffaede Ez azt mutatja. int i1 = &v1[5]-&v1[3]. hogy valóban arra mutatnak). az eredmény mindkét esetben egy mutató érték lesz. int i2 = &v1[5]-&v2[3]. amely nem tartalmaz a karakterláncokéhoz hasonló végzõdést. mert nem biztos. Mutatókat csak akkor vonhatunk ki egymásból definiált módon. hogy ahhoz. Nincs értelme mutatókat összeadni. vagy nem eggyel a tömb mögé. Ez azt jelenti. és ez nem is megengedett. // i1 = 2 // meghatározhatatlan eredmény // p1 = &v2[2] // *p2 nem meghatározott } A bonyolult mutatóaritmetika rendszerint szükségtelen. az eredmény a két mutató között lévõ tömbelemek száma (egy egész típusú érték) lesz. valahogy meg kell adnunk a tömb elemeinek számát: .

kezdeti értékadást kell végeznünk: const int model = 90. } // hiba // hiba . jelezve.1). Sok objektumnak a létrehozás után már nem változik meg az értéke. mint a kódban közvetlenül elhelyezett literálok. v[2]++. i<N. hogy lehetõségünk legyen annak kifejezésére. Mivel egy állandónak késõbb nem lehet értéket adni. Mutatók. A szimbolikus konstansok (jelképes állandók) könnyebben módosítható kódhoz vezetnek. Fejlettebb tömbfogalmat osztályok használatával valósíthatunk meg (§3. i<size. char v2[N]. hogy hatókörén belül értéke nem fog megváltozni: void f() { model = 200. hogy a legtöbb C++-változat a tömbök esetében nem végez indexhatárellenõrzést. 2. hogy egy értéket mutatón keresztül érünk el.4. const int v[ ] = { 1. unsigned int size) { for (int i=0. 4 }. A legtöbb függvényparamétert csak olvassuk. tömbök és struktúrák 125 void fp(char v[ ].Forrás: http://www. az biztosíték arra. hogy egy érték nem változik meg közvetlenül. de az értéket nem változtatjuk meg. const int x. } Jegyezzük meg. Gyakori. i++) use(v2[i]). const int N = 7. Ez számos esetben hasznos lehet. A const kulcsszó hozzáadható egy objektum deklarációjához. Konstansok A C++ felkínálja a const. 5. nem írjuk. A tömb ezen fogalma eredendõen alacsony szintû.hu 5. hogy az objektumot állandóként határozzuk meg. azaz a felhasználói állandó fogalmát. 3.doksi.7. // a model állandó // a v[i] állandó // hiba: nincs kezdeti értékadás Ha valamit const-ként határozunk meg. i++) use(v[i]). for (int i=0.

c3-nak és c4-nek tárhelyet kell foglalni. A konstansokból álló tömböknek általában szükséges helyet foglalni. hogy a fordítóprogram mennyire okos.doksi. lásd §9. ami fordítási idõben kiértékelhetõ. így azokat konstans kifejezésekben felhasználhatjuk. hogy a const kulcsszó módosítja a típust és megszorítást ad arra. c2-nek is helyet kell foglalni. A c1 konstans példa arra az egyszerû és gyakori esetre. Az állandók kezdeti értéke például gyakran (de nem mindig) egy konstans kifejezés (§C. nem kell tárhelyet sem lefoglalnia számára: const int c1 = 1.5). . mert a fordítóprogram nem tudja eldönteni. amikor az állandó értéke fordítási idõben ismert és számára nem szükséges tárat foglalni. Továbbá. hogyan használhatunk egy objektumot. const int* p = &c2. Mivel a c3 és c4 értékek fordítási idõben nem ismertek (ha csak ebben a fordítási egységben levõ információkat használjuk fel. // a val módosítható g(&val).1).. extern const int c4. // . hogy a konstansokból álló tömböt csak olvasható memóriába tesszük. hogyan kell az állandó számára helyet foglalni: void g(const X* p) { // itt *p nem módosítható } void h() { X val.Forrás: http://www. számos módon kihasználhatja egy objektum állandó mivoltát. de nem határozza meg. Mivel c2 címét használjuk. // c3 értéke fordításkor nem ismert // c4 értéke fordításkor nem ismert // c2 számára tárterületet kell foglalni Ekkor a fordítóprogram ismeri c1 és c2 értékét.2). Az extern kulcsszó azt jelöli. const int c2 = 2. Sok gépen azonban még ebben az esetben is növelhetjük a hatékonyságot.. úgy. mely tömbelemekre hivatkoznak a kifejezések.hu 126 Alapok Jegyezzük meg. } Attól függõen. ha a fordítóprogram tud az állandó minden használatáról. hogy a c4-et máshol definiáltuk (§9. const int c3 = my_f(3).

Mutatók. a §10.7 pontokban tárgyaljuk. Ha egy numerikus állandó. case b: } } // . Ilyen esetekben gyakori. A szimbolikus konstansokat rendszeresen használnunk kellene arra. A numerikus konstansok rendszerint valamilyen.. A szimbolikus konstansok használata viszont lokálissá teszi az információt. Ha ezeket az értékeket numerikus állandóként hagyjuk a kódban. int v[max].2. hogy const helyett felsoroló konstansokat (§4. a kódban ismétlõdik.. például egy tömb mérete. amikor a programot átviszik más rendszerre vagy ha más változások aláássák az általuk kifejezett feltételezéseket. minimálisra csökkenthetjük az ilyen jellegû karbantartási problémákat. nagyon nehezen tudja megtalálni és megérteni azokat. a 6.hu 5. const int max = 128. tömbök és struktúrák 127 A const-okat gyakran használjuk tömbök indexhatáraként és case címkéknél is: const int a = 42. Azt. aki a programot karbantartja.8) használunk. void f(int i) { switch (i) { case a: // .24 pedig a dán korona és az amerikai dollár közötti keresztárfolyamot jelölheti. const int b = 99. .2. A 4 például egy egészben lévõ bájtok számát. Ha a feltevéseket megjegyzésekkel megfelelõen ellátott szimbolikus konstansokként valósítjuk meg. hogy a const milyen módon használható osztályok tagfüggvényeivel. és érvénytelenné válnak. akkor az. a 128 a bemenet átmeneti tárba helyezéséhez (puffereléséhez) szükséges karakterek számát. hogy elkerüljük a kódban a „mágikus számokat”..6 és §10.. hogy a megfelelõ módosításkor az állandó minden egyes elõfordulását kicseréljük.doksi. a programmal kapcsolatos feltételezést jelölnek. a programot nehéz lesz átnézni. Ezeket az állandókat gyakran nem veszik észre.Forrás: http://www.

Mutatók és konstansok A mutatók használatakor két objektummal kapcsolatos dologról van szó: magáról a mutatóról és az általa mutatott objektumról. amely egy karakter-konstansra (char const) mutat”. hogy egy mutató-paramétert const-ként adunk meg. pc = p. amely állandó akkor.Forrás: http://www. Ahhoz.4. // mutató állandóra // hiba: pc állandóra mutat // rendben // konstans mutató // rendben // hiba: cp konstans // konstans mutató állandóra // hiba: cpc állandóra mutat // hiba: cpc konstans } A *const deklarátorjelzõ teszi állandóvá a mutatót. hogy módosítható lesz akkor. // *q nem módosítható . és nem a mutatót határozza meg állandóként. char const* pc. const char* pc = s. Ez különösen hasznos a függvényparaméterek esetében. pc[3] = 'g'. Azzal. a függvénynek megtiltjuk. akkor az az objektumot. cp = p. Például: „cp egy konstans (const) mutató. cp[3] = 'a'.hu 128 Alapok 5. ha az ilyen deklarációkat jobbról balra olvassuk ki. amely egy karakterre (char) mutat” és „pc2 egy mutató. const char *const cpc = s. lehet. // konstans mutató karakterre // mutató kostans karakterre // mutató kostans karakterre Általában segítséget jelent. cpc = p.doksi.1. a *const deklarátort kell használnunk a sima * helyett: void f1(char* p) { char s[ ] = "Gorm". const char* q). ha más módon férünk hozzá. és ne az általa mutatott objektumot vezessük be. amikor mutatón keresztül férünk hozzá. így a * elõtt szereplõ const kulcsszót az alaptípus részének tekintjük: char *const cp. Nincs azonban const* deklarátor-operátor. hogy állandóként egy mutatót. Egy objektum. const char* pc2. char *const cp = s. Ha a mutató deklarációját a const szó elõzi meg. cpc[3] = 'a'. hogy módosítsa a mutató által mutatott objektumot: char* strcpy(char* p.

1).doksi. int& r1 = i. tömbök és struktúrák 129 Egy változó címét értékül adhatjuk egy konstansra hivatkozó mutatónak. hogy az objektum értéke megváltozzon: void f4() { int a = 1.4.7.Forrás: http://www. mert ezzel megengednénk. hogy a referencia valaminek a neve legyen (azaz tartozzon hozzá objektum). // r és i itt ugyanarra az int-re hivatkoznak // x = 1 // i = 2 Azt biztosítandó.1 és §15. int* p3 = &c. const int c = 2. 5.2.5. extern int& r3. a hivatkozás célpontját már létrehozáskor meg kell határoznunk: int i = 1.2. } // rendben // rendben // hiba: kezdeti értékadás int*-nak const int*-gal // kísérlet c értékének módosítására A const-ra hivatkozó mutatókkal kapcsolatos megszorításokat meghatározott (explicit) típuskonverzióval küszöbölhetjük ki (§10. // rendben: r1 kapott kezdõértéket // hiba: kezdeti értékadás hiányzik // rendben: r3 máshol kap kezdõértéket . const int* p2 = &a. Lássunk egy példát: void f() { int i = 1. int& r2.hu 5. Az ilyen hivatkozásokat általában függvények és különösen túlterhelt operátorok (11. mert ebbõl még semmi rossz nem következik. int x = r. *p3 = 7. fejezet) paramétereinek és visszatérési értékeinek megadására használjuk. int& r = i. Konstans címét azonban nem lehet értékül adni egy nem konstans mutatónak. } r = 2. Az X& jelölés jelentése „referencia X-re”. Mutatók. Referenciák A referencia (hivatkozás) egy objektum „álneve” (alias). const int* p1 = &c.

de rr++ nem az rr értékét növeli. Egy „sima” T& kezdõértéke T típusú balérték kell. Egy const T& esetében ez nem szükséges (sem balértéknek.hu 130 Alapok A referencia kezdeti értékadása nagyban különbözik a késõbbi értékadástól. } // ii növelése eggyel // pp az ii-re mutat Ez nem helytelen. amelyet mutatóként kezelhetnénk: pp: &ii rr: ii: 1 Egyes fordítóprogramok olyan optimalizációt alkalmazhatnak. hogy nem olyan objektumok. int& rr = ii. ha így gondolunk a referenciákra. Például: void g() { int ii = 0. míg el nem felejtjük. A látszat ellenére a referencián nem hajtódik végre egyetlen mûvelet sem. lásd 4. rr++. a ++ egy int-re hajtódik végre (ami itt ii). amely a referencia számára futási idõben szükségtelenné teszi tárterület lefoglalását.doksi. Az rr által jelölt objektumra hivatkozó mutatót &rr-rel kaphatjuk meg. int* pp = &rr.9.Forrás: http://www. sem T típusúnak nem kell lennie). mindaddig. A referencia magától értetõdõ módon megvalósítható (konstans) mutatóként is. Nem származhat baj abból. amelyre kezdetben beállítottuk azokat. amely minden egyes használatakor automatikusan feloldja a mutató-hivatkozást. mindig arra az objektumra fognak hivatkozni. helyette az alábbiak történnek: .6). Következésképpen a referenciák értéke már nem módosítható a kezdeti értékadás után. A referencia kezdeti értékadása magától értetõdõ. hogy legyen. amelynek címére hivatkozhatunk. ha a kezdõérték egy balérték (vagyis egy olyan objektum.

2. aztán a kapott érték egy T típusú ideiglenes változóba kerül. így az increment meghívásakor az aa paraméter az x másik neve lesz. hogy a program olvasható maradjon. } // x = 2 A paraméterátadás a kezdeti értékadáshoz hasonló.doksi. A referenciákat olyan függvényparaméterek megadására is használhatjuk. tömbök és struktúrák 131 1. végül ez az ideiglenes változó lesz a kezdõérték. } . ha elkerüljük az olyan függvényeket.6-ot). amelyek módosítják paramétereiket.Forrás: http://www. legjobb. 3. Elõször T-re történõ automatikus típuskonverzió megy végbe.6). Vegyük a következõ példát: double& dr = 1. } void f() { int x = 1. Mutatók. A konstansok hivatkozásaival nincs ilyen probléma. melyeken keresztül a függvény módosíthatja a neki átadott objektum értékét: void increment(int& aa) { aa++. increment(x). ami szerencsés. Ehelyett meghatározhatjuk a függvény által visszaadandó értéket vagy mutató paramétert adhatunk neki: int next(int p) { return p+1. A konstansok és változók hivatkozásait azért különböztetjük meg. mert ezek gyakran függvényparaméterként játszanak fontos szerepet (§11. Ha azt szeretnénk. const double& cdr = temp. // hiba: balértékre van szükség // rendben A második a következõképpen értelmezhetõ: double temp = double(1). const double& cdr = 1. // elõször létrehozunk egy ideiglenes változót a jobb oldali // értékkel // majd ezt használjuk a cdr kezdeti értékadására A referencia kezdõértékét tároló ideiglenes változó a referencia hatókörének végéig marad fenn. a változónak való értékadás ugyanis a – nemsokára megszûnõ – ideiglenes tárterületnek adna értéket. ha szükséges (lásd §C.hu 5. } void incr(int* p) { (*p)++. mert a változók esetében nagy hibalehetõségeket rejt magában egy ideiglenes változó bevezetése.

size()-1].7. Az alapötlet az. ha megtaláltuk. visszaadjuk az értékét. hogy az x értéke módosul. } // x = 2 // x = 3 // x = 4 Az increment(x) jelölés az olvasónak semmit sem árul el arról. Könnyû elkészíteni a value() függvényt. double& value(const string& s) /* Vesszük Pair-ek egy halmazát. hogy ezek módosulnak. } // Pair hozzáadása a végéhez (§3. i++) if (s == pairs[i]. }. Pair p = { s.val.Forrás: http://www. pairs. .3) return pairs[pairs. A referenciákat olyan függvények megadására is használhatjuk. Elõször határozzuk meg a Pair adatszerkezetet: struct Pair { string name. Rövidítsük le a példát és használjunk egy nagyon egyszerû (persze nem túl hatékony) megvalósítást: vector<Pair> pairs. i < pairs. */ { for (int i = 0.push_back(p).name) return pairs[i]. megkeressük s-t. Ez a bonyolultabb felhasználói típusok tervezésekor lehet igazán hasznos.val. ha nem.size(). hogy a string-hez tartozik egy lebegõpontos érték. új Pair-t készítünk és visszaadjuk az alapértelmezett 0-át. Következésképpen a „sima” referenciaparamétereket csak olyan esetekben használjuk. ellentétben az x=next(x) és incr(&x) jelölésekkel. double val.hu 132 Alapok void g() { int x = 1. amely egy Pair-bõl álló adatszerkezetet vet össze különbözõ karakterláncokkal. 0 }. incr(&x). Adjunk meg például egy egyszerû asszociatív tömböt. increment(x). x = next(x).doksi. amelyek egy értékadás bal és jobb oldalán egyaránt szerepelhetnek. amikor a függvény neve határozottan utal arra.

end(). 5.Forrás: http://www.). Végül kiírja az eredményül kapott táblázatot. a value() a megfelelõ lebegõpontos objektumot (nem pedig annak értékét) találja meg. és az erre vonatkozó referenciát adja vissza: int main() { string buf. egy void* típusú változót értékül lehet adni egy másik void* típusúnak. aztán növeli a hozzá tartozó számlálót. egy void* típusú változót pedig meghatározott módon más típusúvá lehet alakítani. hogy valódi asszociatív tömböt kapjunk. ehhez egy sablon osztályt kell használnunk a túlterhelt (§11.begin(). a void* típusú változókat össze lehet hasonlítani. p!=pairs. Ha a bemenet például a következõ: aa bb bb aa aa bb aa aa akkor a program eredménye az alábbi: aa: 5 bb: 3 Ezt már könnyû úgy tovább finomítani. hogy egyenlõek-e vagy sem.8. ha a standard könyvtár map (§17. mint egy lebegõpontos értékekbõl álló tömböt. ++p) cout << p->name << ": " << p->val << '\n'.4.1. Void-ra hivatkozó mutatók Bármilyen típusú objektumra hivatkozó mutatót értékül lehet adni egy void* típusú változónak. tömbök és struktúrák 133 Ezt a függvényt úgy foghatjuk fel. // az egyes szavak elõfordulásának megszámlálása a bemeneten while (cin>>buf) value(buf)++.) [ ] indexelõ operátorral.hu 5.) típusát használjuk.doksi.6. Adott karakterlánccal összevetve. Mutatók. A többi mûvelet nem lenne . Még könnyebb a dolgunk. amit karakterláncok indexelnek. } A while ciklus minden esetben beolvas egy szót a cin szabványos bemenetrõl és a buf karakterláncba helyezi (§3. for (vector<Pair>::const_iterator p = pairs. amelyben a bemenetrõl kapott karakterláncok és azok elõfordulásának száma szerepel.6.

4. double* pd2 = pi. mert a fordítóprogram nem tudja.5) nem adhatjuk értékül void* típusú változónak. amely olyan típusra konvertálódik (cast). akkor furcsa mûködés származhat abból. .2). ha a pi egy olyan int-re mutatott.5. explicit típuskonverziót kell alkalmaznunk. amely különbözik a mutató által elõzõleg hivatkozott típustól. A függvényekre hivatkozó mutatókat (§7. hogy mutatókat adjunk át olyan függvényeknek. amely nem így helyezkedett el a memóriában. A gép például feltételezheti. át kell konvertálnunk azt adott típusú mutatóvá: void f(int* pi) { void* pv = pi. Ha így van. hogy minden double 8 bájtos memóriahatáron jön létre. pv++. *pv. double* pd3 = static_cast<double*>(pv).7.) és a tagokra hivatkozó mutatókat (§15. amelyek void* típusú mutatókat használnak. ahol az igazi hardver-erõforrásokat kezelik. amelyek nem feltételeznek semmit az objektumok típusáról. A void* elsõdlegesen arra használatos. jellemzõen a rendszer legalsó szintjén helyezkednek el. hogy valójában miféle objektumra hivatkozik egy ilyen mutató. Ahhoz. } Általában nem biztonságos olyan mutatót használni.Forrás: http://www.doksi. következésképp a használt static_cast jelölést is szándékosan csúnyának tervezték. rejtsük típusbiztos felület mögé (§13. mert tervezési hibát jelezhetnek. hogy függvények nem típusos objektumokat adjanak vissza. // n bájt lefoglalása saját tárterületen A rendszer magasabb szintjein lévõ void* típusú mutatókat gyanakvással kell figyelnünk. §24. // rendben: int* automatikus konvertálása void*-gá // hiba: nem lehet void*-ra hivatkozni // hiba: void* nem növelhetõ (a mutatott objektum mérete ismeretlen) // explicit visszaalakítás int*-ra // hiba // hiba // nem biztonságos int* pi2 = static_cast<int*>(pv). hogy egy void* típusú változót használhassunk. hogy ilyen objektumokat használjunk. ezért a többi mûvelet fordítási idejû hibát eredményez. Az ilyen jellegû explicit típuskényszerítés eredendõen csúnya és nem biztonságos. Azok a függvények. Ahhoz. Ha a void*-ot optimalizálásra használjuk.hu 134 Alapok biztonságos. valamint arra. double* pd1 = pv. Például: void* my_alloc(size_t n).

doksi. Ez egyike azon kevés helyeknek a C++-ban. amely levelek küldéséhez szükséges címzési adatokat tartalmaz. 7974 }. tömbök és struktúrák 135 5. Mivel a karakterláncok a '\0' karakterre végzõdnek. a struct-ok (adatszerkezetek. ami eggyel több.Forrás: http://www. tagkiválasztó) operátorral hivatkozhatunk: void f() { address jd. mint más változókat.state-et nem lehetett volna az "NJ" karakterlánccal feltölteni. mint ami a jd. Mutatók. {'N'. .name = "Jim Dandy". struktúrák) majdnem tetszõleges típusúakból: struct address { char* name.'J'}.hu 5. Ennél azonban rendszerint jobb megoldás konstruktorokat (§10. hogy a jd. "South St".number = 61. long zip. az "NJ" három karakterbõl áll. Struktúrák A tömbök azonos típusú elemekbõl állnak. char* town. long int number. char state[2]. Vegyük észre. }. } A tömbök kezdeti értékadására használt jelölés a struktúra-típusú változók feltöltésére is használható: address jd = { "Jim Dandy". // "Jim Dandy" // 61 // "South St" // "New Providence" // 'N' 'J' // 7974 A fenti kód egy address (cím) nevû új típust hoz létre. ahol pontosvesszõt kell tenni a kapcsos zárójel után. és az egyes tagokra a . (pont.7. "New Providence". jd. char* street.state-be belefér. Vegyük észre a pontosvesszõt a definíció végén.2. Az address típusú változókat pontosan úgy adhatjuk meg. 61. jd. ezért sokan hajlamosak elfelejteni.3) használni.

mint az összehasonlítás (== és !=). és visszaadhatjuk függvények visszatérési értékeként is: address current. Az egészek például gyakran gépi szóhatárokon jönnek létre. ahogy az elvárható lenne. hogy sok gép igényli bizonyos típusú objektumok elhelyezését a felépítéstõl függõ memóriahatárokra. Számos gépen a sizeof(address) például 24. address set_current(address next) { address prev = current. Ennek az az oka. nem pedig 22. A struktúra-típusú objektumok mérete nem feltétlenül a tagok méretének összege. és csak akkor méret szerint. akkor p->m egyenértékû (*p). . } Más lehetséges mûveletek. nem meghatározottak. fejezet). ha olvashatóság szerint rendezzük sorba a tagokat.Forrás: http://www. átadhatjuk függvényparaméterként. A legjobb azonban az.m-mel. } Ha p egy mutató.doksi. ha egyszerûen méret szerint rendezzük a struktúra tagjait (a legnagyobb tag lesz az elsõ). Ezt úgy mondjuk. vagy eleve hatékonyabban kezeli az így létrehozott objektumokat. current = next. Ez a struktúrákon belül „lyukakat” eredményez.hu 136 Alapok A struktúrák objektumaira gyakran hivatkozunk mutatókon keresztül a -> (struktúra-mutató) operátorral: void print_addr(address* p) { cout << p->name << '\n' << p->number << ' ' << p->street << '\n' << p->town << '\n' << p->state[0] << p->state[1] << ' ' << p->zip << '\n'. A struktúra-típusú objektumokat értékül adhatjuk. ha bizonyítottan szükség van optimalizálásra. return prev. hogy az ilyen gépeken az objektumok jól illesztettek. de a felhasználó megadhat ilyeneket (11. Az elpazarolt helyet a lehetõ legkevesebbre csökkenthetjük.

// hiba: rekurzív definíció Ez azért hibás. struct List { Link* head. amely a típus neve: struct List. hogy ez a használat nem igényli egy tag nevének vagy a struktúra méretének ismeretét: class S. Mutatók. struct Link { Link* pre. S* h(S*). Két (vagy több) struktúra-típus kölcsönös hivatkozásához adjunk meg például egy nevet. }. A struktúra teljes deklarációjának végéig viszont nem adhatunk meg újabb ilyen típusú objektumokat: struct No_good { No_good member. }. tömbök és struktúrák 137 Egy típus neve rögtön felhasználható attól a ponttól. }.Forrás: http://www. // 'S' valamilyen típus neve . S f(). }. feltéve.doksi.hu 5. Link* member_of. Link* suc. Link* successor. mert a fordítóprogram nem képes eldönteni a No_good méretét. extern S a. void g(S). A struktúra-típus neve a típus meghatározása elõtt is felhasználható. nem csak a teljes deklaráció után: struct Link { Link* previous. ahol elõször megjelenik. // késõbb meghatározandó A List elsõ deklarációja nélkül a List használata a Link deklarációjában formai hibát okozott volna.

A C történetére visszanyúló okok miatt ugyanazzal a névvel és ugyanabban a hatókörben megadhatunk egy struct-ot és egy nem struktúra jellegû típust is: struct stat { /* .8) kulcsszavak is használhatók. Ebben az esetben a „sima” stat név a nem-struktúra neve.doksi. akkor is.Forrás: http://www.. S* q = h(p). struct stat* buf). a tag neve nem ismert // rendben: a mutatók számára foglalható hely és át is adhatók // hiba: S nem definiált.7. }. Egyenértékû típusok Két struktúra mindig különbözõ típusú. így S1 x. ha nem terheljük túl a neveket. ezekkel elkerülhetjük a kétértelmûséget. az adatszerkezetre pedig a struct elõtaggal kell hivatkoznunk. hacsak meg nem adjuk az S típusát: void k(S* p) { S a. 5. struct S2 { int a. union (§C. S2 y = x. a helyfoglaláshoz méret kell // hiba: S nem definiált. }.2) és enum (§4. f(). Elõtagként a class. q->m = 7. g(a). int stat(char* name. paraméter átadásához méret kell // hiba: S nem definiált. p->m = 7. a tag neve nem ismert } A struct az osztály (10.hu 138 Alapok A fenti deklarációk közül azonban sok nem használható. érték visszaadásához méret kell // hiba: S nem definiált.8. A fenti két típus különbözõ. // hiba: nem megfelelõ típus . // hiba: S nem definiált.1. */ }. ha tagjaik ugyanazok: struct S1 { int a. A legjobb azonban.. fejezet) egyszerû formája.

§5. konstans egészre hivatkozó mutató. ezért S1 x. 10 egészbõl álló tömb.3.1.doksi.5. Ügyeljünk arra. Az alacsonyszintû kódot kivéve kerüljük a void*-ot. hogy legyen a programban (§9.6. Használjunk string-et nulla végzõdésû karaktertömbök helyett.9. §5. §5. Mindegyiknek adjunk kezdeti értéket. §5. (*1.3.3.8. Mutatók. Használjunk 0-át NULL helyett.4. §5. int*. §5.5) Mik a char*. tömbök és struktúrák 139 A struktúra-típusok az alaptípusoktól is különböznek. . // hiba: nem megfelelõ típus Minden struct-nak egyértelmû meghatározása kell. Használjunk helyettük jelképes állandókat.3. §5. hogy ne írjunk egy tömb indexhatárán túlra. 5. 2. és void* mutatótípusokra vonatkozó megszorítások a mi rendszerünkön? Lehetne-e például egy int*-nak furcsa értéke? Segítség: illesztés. §5. karakterre hivatkozó mutatóra hivatkozó mutató. Kerüljük a kódban a nem magától értetõdõ literálokat („mágikus számokat”).). Használjuk a vector-t és a valarray-t a beépített (C stílusú) tömbök helyett. egészre hivatkozó konstans mutató. §4. konstans egész. int i = x. Használjunk a lehetõ legkevesebb egyszerû referencia-paramétert.8. Gyakorlatok 1. 5. Tanácsok [1] [2] [3] [4] [5] [6] [7] [8] Kerüljük a nem magától értetõdõ mutató-aritmetikát.Forrás: http://www.1.hu 5. karakterláncokból álló tömbre hivatkozó mutató.1. (*1) Vezessük be a következõket: karakterre hivatkozó mutató.1. 10 egészbõl álló tömb referenciája.2.3.

Adjuk át a tömböt egy függvénynek. olyat. ahol az egyes adatszerkezetek a hónap nevét és a benne levõ napok számát tárolják. (*1) Használjunk typedef-et a következõk meghatározására: unsigned char. Módosítsuk a programot. egészre hivatkozó mutatókból álló tömb. (*2) Futtassunk le néhány tesztet. hol lenne értelme egy nevet a saját kezdõértékében használni. ami ugyanezt csinálja egy nulla végû karaktertömbben (vagyis egy C stílusú karakterláncban).5) Mi az str tömb mérete a következõ példában? char str[ ] = "rövid karakterlánc".doksi. 12. Ne írjuk ki kétszer ugyanazt a szót. Mi a "rövid karakterlánc" hossza? 6. karaktertömbökre hivatkozó mutató. ami Date-eket ír a kimenetre. (*1. egészre hivatkozó mutatókból álló tömbre hivatkozó mutató. és egy másikat. hogy megnézzük. karakterre hivatkozó mutatóra hivatkozó mutató. másodszor használjunk struktúrákból álló tömböt. Írjunk olyan függvényt. Írjuk ki ezeket. nézzük meg. hat-e és hogyan hat ez a létrehozott kód minõségére. amely kiírja a karakterláncokat. ami Date-eket olvas be a bemenetrõl.5) Adjunk meg egy Date struktúrát dátumok ábrázolásához. c.1). (*2) Írjunk olyan függvényt. g(char&) és h(const char&) függvényeket. 5.Forrás: http://www. 10. uc és sc paraméterekkel. Használjunk int* típust a paraméterek típusaként. 8. amely a hónapok neveibõl és napjaik számából áll. a fordítóprogram tényleg egyenértékû kódot hoz-e létre a mutatók használatával és az indexeléssel való tömbbejáráshoz (§5. (*1) Készítsük el az f(char).5) Találjunk példát. const unsigned char. Az "ab" pár például kétszer szerepel az "xabaacbaxabb"-ben. uc unsigned char és sc signed char típusú. ahol a karakterláncok a hónapok neveit tartalmazzák. 7 elemû. hogy rendezze a szavakat. 11. ahol c char. Hívjuk meg õket az 'a'.5) Készítsünk egy táblázatot. A bemenetet lezáró szóként használjuk a Quit-et. amely megszámolja egy betûpár elõfordulásait egy karakterláncban. . Írjunk egy másik swap-et is. Mely hívások megengedettek? Mely hívásoknál vezet be a fordítóprogram ideiglenes változót? 7.3. Ha különbözõ mértékû optimalizálást lehet használni. Írjuk ki a táblázatot. melynek paraméterei int& típusúak. egészre hivatkozó mutató. (*1. és olyat. (*1) Adjunk meg egy karakterláncokból álló tömböt. 7 elemû.hu 140 Alapok 3. (*1. egészre hivatkozó mutatókat tartalmazó 7 elemû tömbökbõl álló 8 elemû tömb. (*1) Írjunk egy swap nevû függvényt. Írjuk ki a beolvasott szavakat. (*2) Olvassuk be a bemenetrõl szavak egy sorozatát. 49. amely két egészt cserél fel. mielõtt kiírná azokat. 3300. Csináljuk meg mindezt kétszer: egyszer használjunk karaktertömböt a nevek és egy tömböt a napok számára. 4. (*1. ami egy dátummal ad kezdõértéket a Date-nek. 13. 9.

doksi.hu 6 Kifejezések és utasítások „Az idõ elõtti optimalizálás minden rossz gyökere.” (John Bentley) „Asztali számológép” példa • Bemenet • Parancssori paraméterek • Kifejezések (áttekintés) • Logikai és összehasonlító operátorok • Növelés és csökkentés • Szabad tár • Meghatározott típuskonverziók • Utasítások (áttekintés) • Deklarációk • Elágazó utasítások • Deklarációk a feltételekben • Ciklusutasítások • A hírhedt goto • Megjegyzések és behúzás • Tanácsok • Gyakorlatok .” (D. Knuth) „Másrészrõl. nem hagyhatjuk figyelmen kívül a hatékonyságot.Forrás: http://www.

anélkül. Az elemzõ Íme a számológép által elfogadott nyelvtan: program: END expr_list END expression PRINT expression PRINT expr_list // END a bevitel vége expr_list: // PRINT a pontosvesszõ . a szimbólumtábla tartalmazza a nem változó adatokat és a vezérlõ kezeli a kezdeti értékadást. amelyben az elemzõ végzi a szintaktikai elemzést (vagyis a nyelvi utasítások formai elemzését).6[20]). A számológépet számos szolgáltatással bõvíthetjük. hogy még hasznosabbá tegyük (§6.1.hu 142 Alapok 6. hogy további betekintést nyújtana a C++ használatába.1. Valójában ez egy miniatûr fordítóprogram. A számológép négy fõ részbõl áll: egy elemzõbõl (parser).1.5 19. A bemenet legyen a következõ: r = 2. az adatbeviteli függvény kezeli a bemenetet és végzi a lexikai elemzést (vagyis a nyelvi elemek értelmezését). A számológép a négy aritmetikai alapmûveletet hajtja végre lebegõpontos értékeken.doksi.635 a bemenet második sorának eredménye.Forrás: http://www.5 area = pi * r * r A számológép program az alábbiakat fogja kiírni (pi elõre meghatározott): 2. Egy asztali számológép A kifejezéseket és utasításokat egy asztali számológép példáján keresztül mutatjuk be. de a kód így is elég hosszú lesz. a 19. 6. a kimenetet és a hibákat. egy szimbólumtáblából és egy vezérlõbõl. A mûveleti jeleket a számok között (infix operátorként) kell megadni.635 A 2. egy adatbeviteli függvénybõl. A felhasználó változókat is megadhat.5 a bemenet elsõ sorának. és a legtöbb szolgáltatás csak a kódot növelné.

PRINT='. { NUMBER. Egy olyan nyelvben. RP=')' Token_value curr_tok = PRINT. a módszer hatékony is. Kifejezések és utasítások 143 expression: expression + term expression . Az elemzõ a get_token() függvényt használja arra. nevek és a *. amelyeket pontosvesszõk választanak el egymástól. .hu 6. . A lezáró szimbólumokat (például az END. a kifejezés kiértékelõdik – egy igazi fordítóprogram esetében ezen a ponton történhetne a kód létrehozása. akár két operandusú) operátorok.'. Ha egy (rész)kifejezés minkét operandusa ismert. Az általunk használt szintaktikai elemzés módszerét rendszerint rekurzív leszállásnak (recursive descent) nevezik. hogy bemenetet kapjon. Az utolsó get_token() hívás eredménye a curr_tok globális változóban található. A neveket nem kell használat elõtt definiálni. END. MUL='*'. NUMBER. PLUS='+'.primary ( expression ) primary: Más szóval.(akár egy. a nem lezáró szimbólumokat pedig az expr().Forrás: http://www. A curr_tok változó típusa Token_value felsoroló típus: enum Token_value NAME. a program kifejezések sorozata.doksi. amely más függvényeket hív meg. MINUS='-'. és az = . felülrõl lefelé haladó eljárás. amelyben a függvényhívások viszonylag „kis költségûek". }. + és -) a get_token() lexikai elemzõ. ASSIGN='='. /. népszerû és lényegretörõ. A nyelvtan minden szabályára adunk egy függvényt. term() és prim() szintaktikai elemzõ függvények ismerik fel. DIV='/'. A kifejezések alapelemei a számok. mint a C++.term term term: term / primary term * primary primary NUMBER NAME NAME = expression . LP='('. +.

Minden elemzõ függvénynek van egy logikai (bool) (§4. for (. A függvény egyetlen ciklusból áll. és segítheti azokat. miután a számológép kiértékelt egy kifejezést és kiírta annak értékét.Forrás: http://www. Én pedig nem tudok olyan karakterkészletrõl. melynek értékét felsoroló konstansként már használjuk. A break-kel a switch utasításból léphetünk ki. Azért választottam a curr_tok kezdeti értékeként a PRINT-et. default: return left. A case címkéket követõ konstansoknak különbözniük kell egymástól. A switch utasítás azt vizsgálja meg. A programozónak nem kötelezõ megadnia a default részt. Így alapállapotban „indítjuk el a rendszert”.hu 144 Alapok Az. melynek egész értéke egy számjegyû. mert a curr_tok ezt az értéket fogja felvenni. Az expr() függvény kezeli az összeadást és kivonást. kényelmes és hatékony megoldás. break. hogy meg kell-e hívnia a get_token()-t a következõ szimbólum beolvasásához. hogy a switch kulcsszó után zárójelben megadott feltétel értéke megegyezik-e a konstansok valamelyikével. akik hibakeresõt (debugger) használnak.) switch (curr_tok) { case PLUS: left += term(true). amely jelzi. a lehetõ legkisebbre csökkentjük annak az esélyét. Egy nagyobb program magasabb szintû függvényeihez hasonló módon más függvényeket hív meg a feladat elvégzéséhez. case MINUS: left -= term(true). Ez a módszer addig mûködik. amely elemeket (term) keres az összeadáshoz vagy kivonáshoz: double expr(bool get) { double left = term(get). A függvény kiértékeli a „saját” kifejezését és visszaadja az értékét. break. amelyben van olyan kiírható karakter. } // összeadás és kivonás // "örökké" (végtelen ciklus) } Ez a függvény önmagában nem csinál túl sokat. . a default címke választódik ki..2) paramétere. hogy hibák forduljanak elõ és egyedi indítókódra sincs szükségünk. amíg bemenetként olyan karaktert nem adunk meg. Ha a vizsgált érték nem egyezik egyik case címkével sem. hogy minden szimbólumot (token) a karakterének megfelelõ egész értékkel jelölünk.doksi.

double expr(bool). A prim() meghatározása elõtti deklaráció erre való. A kivétel az expr(). |. ami meghívja a prim()-et.-tól különbözõ jelet. amikor is a default címke utáni return utasítás hajtódik végre. Ha @ egy bináris (kétoperandusú) operátor. &. A 8. helyette használhatjuk a while(true) szerkezetet is. hogy „örökké” (forever). ami pedig ismét meghívja az expr()-et. és a 9. a program jelentése nem változna. Ez a for utasítás (§6. . fejezet tárgyalja. VAGY. A következõ kétoperandusú mûveletekhez léteznek értékadó operátorok: + * / % & | ^ << >> Így a következõ értékadó operátorok lehetségesek: = += -= *= /= %= &= |= ^= <<= >>= A % a moduló vagy maradékképzõ operátor.2 foglalja össze. Használhatnánk a left=left+term(true) és left=left-term(true) formát is. A furcsa for( . hanem közvetlenebbül is fejezik ki a kívánt mûveletet. amíg nem talál a +-tól és . úgy mondhatjuk ki. és ^ a bitenkénti ÉS. hogy mindent csak egyszer és használat elõtt adunk meg. Ezt a kört valahol meg kell szakítanunk. ahogy azt a nyelvtanban meghatároztuk. Minden értékadó operátor önálló nyelvi egység.Forrás: http://www.3) végletes formája.hu 6. Kifejezések és utasítások 145 Figyeljük meg. így a + = 1 nyelvtanilag hibás a + és az = közötti szóköz miatt. A switch utasítás végrehajtása addig ismétlõdik. hogyan építsünk fel programot modulokból. hogy x csak egyszer értékelõdik ki. ) jelölés megszokott módja annak. A term() függvény ugyanolyan módon kezeli a szorzást és osztást.3. A left+=term(true) és a left-=term(true) azonban nemcsak rövidebbek. mint ahogy az expr() kezeli az összeadást és kivonást: . hogy végtelen ciklust írjunk. A mûveleti jeleket és jelentésüket §6. A számológép példa deklarációit – egy kivétellel – úgy rendezhetjük sorba. hogy a 2-3+4-hez hasonló kifejezések (2-3)+4-ként értékelõdnek ki. << és >> pedig a balra és jobbra léptetõ operátorok. illetve kizáró VAGY operátorok. akkor x@=y jelentése x=x@y.doksi. Az összeadás és kivonás kezelésére a += és -= operátorokat használjuk. ami meghívja a term()-et. azzal a különbséggel.

) switch (curr_tok) { case MUL: left *= prim(true).1. string string_value. kivéve azt.2. A d változót pontosan azon a ponton vezetjük be a programba. az eredményezett érték pedig a feltétel értéke (§6. Ezért osztás elõtt megnézzük. // elemi szimbólumok kezelése switch (curr_tok) { case NUMBER: // lebegõpontos konstans { double v = number_value. meghívjuk az error()-t. } . for (.3. double prim(bool get) { if (get) get_token(). Egy feltételben bevezetett név hatóköre a feltétel által vezérelt utasítás. amely az elemi szimbólumokat kezeli. break.4-ben ismertetjük. get_token().1). ahol az szükséges.doksi.. A nullával való osztás nem meghatározott és rendszerint végzetes hibát okoz. és ha igen. hogy mivel már lejjebb értünk a hívási hierarchiában. default: } } return left. hogy a nevezõ 0 -e. break.Forrás: http://www. és rögtön kezdeti értéket is adunk neki. nagyban hasonlít az expr()-re és a term()-re. return v. } return error("Nullával nem lehet osztani"). Az error() függvényt a §6. ha d nem nulla.hu 146 Alapok double term(bool get) { double left = prim(get). némi valódi munkát kell végezni és nincs szükség ciklusra: double number_value. // szorzás és osztás case DIV: if (double d = prim(true)) { left /= d. A prim() függvény. Következésképpen a left/=d osztás és értékadás csak akkor megy végbe.

ahogy az utolsó beolvasott NUMBER-t a number_value tárolja. Mielõtt a számológép bármit kezdene egy névvel. A get_token() bemeneti eljárás elhelyezi az értéket a number_value globális változóban. Mindkét esetben a szimbólumtáblához fordul. meg kell néznie. hogy a nevet értékül kell-e adnia vagy csak egyszerûen be kell olvasnia. get_token(). hogy a program szerkezete nem kristálytiszta – valamiféle optimalizációt alkalmaztak rá.6[21]).1): map<string.Forrás: http://www. az eredményül kapott érték az a double lesz. §17. hiba esetén viszont segítheti a felhasználót. Ideális esetben egy nyelvi egység (lexikai szimbólum) két részbõl áll: egy értékbõl. amikor a table-t egy karakterlánccal indexeljük. hogy a felhasználó a következõket írja be: radius = 6378. Globális változó használata a kódban gyakran jelenti. Azaz. } } Amikor egy NUMBER-t (azaz egy egész vagy lebegõpontos literált) találunk. A number_value értékét nem feltétlenül szükséges a v lokális változóba menteni a get_token() meghívása elõtt. Itt csak egy egyszerû curr_tok változó szerepel.4. visszaadjuk az értékét. A számológép a számításhoz minden helyes bemenetnél használatba veszi az elsõ számot. Tegyük fel. // ')' lenyelése return e. hogy az utolsó beolvasott NUMBER értékét tárolja. mielõtt egy másikat olvasna be. E kétes szerepû globális változó kiküszöbölését is a feladatok közé tûzzük ki (§6.4. ha mentjük az értéket és helyesen kiírjuk.doksi. Itt is ez történt. . if (curr_tok != RP) return error(") szükséges"). így a number_value globális változó szükséges ahhoz. ami a karakterlánchoz tartozik. az utolsó beolvasott NAME karakterláncot a string_value tartalmazza. Hasonlóan ahhoz.7.hu 6. A szimbólumtábla egy map (§3.double> table. case LP: { double e = expr(true). Kifejezések és utasítások 147 case NAME: { double& v = table[string_value]. } default: return error("elemi szimbólum szükséges"). if (get_token() == ASSIGN) v = expr(true). amely meghatározza a szimbólum fajtáját (ebben a programban ez a Token_value) és (ha szükséges) a token értékébõl. } case MINUS: // egyoperandusú mínusz return -prim(true).388. return v.

2. ha megpróbáljuk a felhasználót rákényszeríteni. Ezután ezt módosítom egy kevésbé elegáns. hogy az olvasási mûvelet sikerült-e: Token_value get_token() { char ch = 0.Forrás: http://www. Ezek a szimbólumok késõbb a magasabb szintû eljárások bemeneti egységei lesznek.388. Elõször egy megtévesztõen egyszerû változatot készítek. A v referenciát használjuk arra. Kellemetlen dolog (jogosan). hogy az a gép számára megfelelõbb legyen. Itt az alacsonyszintû beolvasást a get_token() végzi. és ellenõrzik.388 értéket. Ez azért van így. expr() kiszámolja az átadandó értéket v = 6378. hogy a radius-hoz tartozó double értékre hivatkozzunk. hogy karaktereket olvasson be és magasabb szintû szimbólumokat hozzon létre belõlük. amíg az expr() a bemeneti karakterekbõl kiszámítja a 6378. Két lépésben építem fel a get_token()-t. A kezdeti utasítások beolvassák az elsõ nem „üreshely” (whitespace. szokásaival és viszonylag véletlenszerû hibáival. cin>>ch. Az ötlet az. hogy úgy viselkedjen. 6.doksi.1. switch (ch) { case 0: return curr_tok=END. tabulátor. új sor stb.. Egy alacsonyszintû beolvasó eljárás feladata az. hogy eldöntsük. milyen szimbólumot kell létrehozni.) karaktert ch-ba. // értékadás és visszatérés . Sok rendszer erre a célra szabványos függvényeket nyújt. majd visszaadjuk a beolvasott token-t ábrázoló Token_value értéket. de jóval használhatóbb változatra. A bemeneti függvény A bemenet beolvasása gyakran a program legrendezetlenebb része. amely komoly terhet ró a felhasználóra. Nem feltétlenül mindennapi feladat alacsonyszintû bemeneti eljárásokat írni. azaz szóköz. hogy beolvasunk egy karaktert. felhasználjuk arra.hu 148 Alapok Ekkor a számológép az alábbiakat hajtja végre: double& v = table["radius"].. // . mert a programnak egy emberrel kell társalognia és meg kell birkóznia annak szeszélyeivel.

hogy a programozó megváltoztatná az egyiket. Nézzünk meg néhány esetet külön-külön.hu 6. megkönnyíti a kód késõbbi módosítását.doksi. . a kód magától értetõdõ. majd a konstanst a number_value változóba helyezzük. mert ez az elrendezés nehezebben olvasható. melynek értéket adunk. ha a bemeneti mûvelet nem sikerül.': '*': '/': '+': '-': '(': ')': '=': return curr_tok=Token_value(ch). Az értékadás egy operátor. de elfelejtené módosítani a másikat. cin >> number_value. hogy egyszerûen visszaadjuk az értéküket: case case case case case case case case '. A case címkéket függõleges helyett vízszintesen egy kupacba tenni általában nem jó ötlet.': cin. A számokat így kezeljük: case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.putback(ch). Az. hogy a curr_tok változónak az END-et adjam értékül. lehet. a zárójeleket és az operátorokat úgy kezeljük. az értékadás eredménye pedig annak a változónak az értéke. Kifejezések és utasítások 149 Alapértelmezés szerint a >> operátor átugorja az üreshely karaktereket és ch értékét változatlanul hagyja. A kifejezések . Ha az értékadást és a visszaadott értéket különválasztanánk a kódban.Forrás: http://www. Elõször a kezdõ karaktert (számjegyet vagy pontot) visszatesszük a cin-be. Ez megengedi. majd a változót ugyanabban az utasításban adjam vissza. return curr_tok=NUMBER. végzõdését. Fárasztó lenne azonban minden számjegyet külön sorba írni. Következésképpen ch==0 a bemenet végét jelzi. mielõtt a teljes függvénnyel foglalkoznánk. Mivel a >> mûveleti jel a lebegõpontos konstansokat szabályszerûen egy double típusú változóba olvassa. hogy egy utasítást használunk kettõ helyett.

putback(ch). mint különbözõ case címkéket. cin>>string_value. switch (ch) { case 0: return curr_tok=END. NAME =.Forrás: http://www. } error("rossz szimbólum"). case case case case case case case case '.4. return curr_tok=NUMBER. Ez nem ideális megoldás. melyek a nevet operandusként használják. ezért erre a problémára még visszatérünk §6. cin>>ch. Íme a teljes bemeneti függvény: Token_value get_token() { char ch = 0. A karakterláncok (ebben az esetben a string_value) >> mûvelete addig olvassa a láncot.putback(ch). return curr_tok=NAME.doksi.': cin. amíg üreshelyet nem talál. vagy hiba if (isalpha(ch)) { cin.1.hu 150 Alapok A neveket hasonlóan kezeljük: default: // NAME. A standard könyvtárban levõ isalpha() függvényt (§20.2) használjuk arra. return curr_tok=PRINT.3-ban. . hogy ne kelljen minden karaktert felsorolnunk. cin >> number_value.': '*': '/': '+': '-': '(': ')': '=': return curr_tok=Token_value(ch). Következésképpen a felhasználónak szóközzel kell befejeznie az adott nevet azon operátorok elõtt. case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.

putback(ch). Kifejezések és utasítások 151 default: } } if (isalpha(ch)) { cin. switch (ch) { case '.3. Fárasztó emlékezni arra. mivel az operátorok token_value értékét az operátor egész értékeként határoztuk meg (§4. return curr_tok=NAME. cin>>string_value. Mindkét problémát úgy oldjuk meg. Alacsonyszintû bemenet Ha a számológépet úgy használjuk. NAME =. amit az = operátor és a 7-es szám követ.8). Elõször is. 6. do { // üreshelyek átugrása az '\n' kivételével if(!cin. hogy a ciklusmag mindig legalább egyszer végrehajtódik. amely egyenként olvassa be a karaktereket. return curr_tok=PRINT. amely egyenértékû a while utasítással. } error("rossz szimbólum"). Például x=7 egy azonosító. A do utasítást használjuk.hu 6. Alapértelmezés szerint a get() nem ugorja át az üreshelyeket . ha ki akarjuk íratni az értékét. ahogy az eddigiekben leírtuk. és nagyon bosszantó tud lenni. kivéve.get(ch)) return curr_tok = END.doksi.': case '\n': return curr_tok=PRINT. A cin. fény derül néhány kényelmetlen dologra. hogy csak üreshellyel lehet egy nevet befejezni. } while (ch!='\n' && isspace(ch)). hogy pontosvesszõt kell tennünk egy kifejezés után. hogy a get_token()-ben a típussal kapcsolatos alapértelmezett bemeneti mûveleteket olyan kódra cseréljük.get(ch) beolvas egy karaktert a szabványos bemeneti adatfolyamból ch-ba. // NAME. az „új sor” karaktert azonosként kezeljük a kifejezés végét jelzõ pontosvesszõvel: Token_value get_token() { char ch.1. vagy hiba Egy operátor átalakítása az operátornak megfelelõ szimbólumra magától értetõdõ.Forrás: http://www. és nem x.

} . melyek javítását. hogy a kódnak csak egyes helyi érvényességû részeit módosítjuk. hogy egy karakter számjegy (isdigit()). return 1.get(ch)) ellenõrzés sikertelen. vagy hiba if (isalpha(ch)) { string_value = ch. ahogy a >> mûvelet teszi. hogy egyszerre egy karaktert olvasunk be. ami nem szám és nem betû: default: // NAME.putback(ch). double error(const string& s) { no_of_errors++. Hasonló függvényekkel nézhetjük meg. úgy oldjuk meg.Forrás: http://www. amit az okoz. Ha c üreshely. ha sikeres. a hibakezeléssel nem kell komolyabban törõdnünk. hogy befejezzük a számológép mûködését. A vizsgálatot táblázatban való keresésként valósítjuk meg. Hibakezelés Mivel a program ennyire egyszerû.doksi. betû (isalpha()). Az if(!cin. az isspace(c) nem nulla értéket ad vissza.4. ha nem olvasható be karakter a cin-bõl. miféle nyelvi egység jön. A ! (NEM) operátort azért használjuk. kiír egy hibaüzenetet. amíg olyan karaktert nem találunk.2) szabványos vizsgálatát. return curr_tok=NAME. a következõ karaktert arra használjuk. Az error függvény egyszerûen megszámolja a hibákat. } error("rossz szimbólum"). más esetben nullát. hogy olyan programokat hozzunk létre.hu 152 Alapok úgy. mert a get() igazat ad vissza. Szerencsére mindkét javítás elvégezhetõ úgy.push_back(ch). Fontos tervezési cél. return curr_tok=PRINT. cin. hogy a >> addig olvassa a karakterláncot. hogy eldöntsük. mint az egyes üreshely karakterek vizsgálata. ebben az esetben END-et adunk vissza. Miután átugrottuk az üreshelyeket. fejlesztését helyi módosításokkal intézhetjük. 6.get(ch) && isalnum(ch)) string_value. amíg üreshelyeket nem talál. while (cin. és visszatér: int no_of_errors. A problémát. A standard könyvtár isspace() függvénye végzi az üreshelyek (§20. cerr << "hiba: " << s << '\n'.1. esetleg betû vagy szám-e (isalnum()). így az isspace() használata sokkal gyorsabb. NAME=.4.

amely rendszerint hibajelzésre használatos (§21. egy 150 soros számológépnek éppen megfelel. // elõre megadott nevek beillesztése Hagyomány szerint a main() 0-át kell. Ha a get_token() nyomon követte volna a sorok számát. ami akkor lenne hasznos. Kifejezések és utasítások 153 A cerr egy átmeneti tárba nem helyezett („nem pufferelt”) kimeneti adatfolyam.Forrás: http://www.7182818284590452354. if (curr_tok == PRINT) continue. így vagy teljesen abba kellene hagynunk a kiértékelést.[19]). ha a számológépet nem interaktívan használnánk (§6. Ebben az egyszerû példában ezt a main() végzi el: int main() { table["pi"] = 3. Ezt tehetjük meg az exit() meghívásával. while (cin) { get_token().6.doksi. hogy további hibákat okozna. Ezen egyszerû számológép esetében az utóbbi megoldás megfelelõ. már csak a vezérlõ kódra van szükségünk ahhoz.1415926535897932385. amely nem valószínû.1). if (curr_tok == END) break. 6.1. table["e"] = 2. az error() tájékoztathatta volna a felhasználót a hiba pontos helyérõl. amely elõször rendbe rakja az adatfolyamokat és hasonló dolgokat.1. ha a program hiba nélkül ér véget. Azért adunk vissza értéket.hu 6. } } return no_of_errors.4. cout << expr(false) << '\n'. hogy elindítsuk a mûködést.1). melynek visszatérési értéke az exit() paramétere lesz (§9. milyen ésszerû módon folytathatná mûködését. hogy visszaadjon. fejezet). mert a hibák jellemzõen valamilyen kifejezés kiértékelése közben történnek. de amit most csináltunk. vagy olyan értéket kellene visszaadnunk. más esetben nem nullát (§3. miután hiba történt.5. Kivételek használatával elegánsabb hibakezelõ eljárások készíthetõk (lásd §8. A vezérlõ Miután a program minden részlete a helyére került. A program futásának gyakran be kell fejezõdne. mert nincs megadva. majd befejezi a programot. A hibák számának visszaadásával ezt szépen megold- .3 és 14.2).2.

hu 154 Alapok juk. hogy a szimbólumtáblába bele kell tennünk az elõre megadott neveket.6. if (curr_tok != PRINT) cout << expr(false) << '\n'. A cin ciklusonként egyszeri ellenõrzése biztosítja. . Ezért a megfelelõ fejállományokat (header) be kell építenünk (#include). hogy a ciklus legvégére ugrunk. így ebben az esetben while (cin) { // . Ezt a következõ sor oldja meg: cout << expr(false) << '\n'. A false paraméter mondja meg az expr()-nek.1. hogy a program befejezõdik. if (curr_tok == PRINT) continue. hogy beolvassa a kifejezéseket és kiírja a választ.') vizsgálata megkönnyíti az expr() dolgát az üres kifejezések kezelésében. ha hiba történik a bemeneti adatfolyammal.. A PRINT (azaz '\n' és '.. az END vizsgálata pedig arról gondoskodik. ha a get_token() a fájl végéhez ér. Fejállományok A számológép a standard könyvtár eszközeit használja. A break utasítás a legközelebbi körülvevõ switch utasításból vagy ciklusból (azaz for. Ebben az esetben az egyetlen szükséges elõkészítés az. hogy nem kell meghívnia a get_token()-t ahhoz. A continue utasítás egyenértékû azzal. } megegyezik a következõvel: while (cin) { // ..Forrás: http://www. hogy a ciklusból megfelelõen lépjünk ki. amellyel dolgozhat.doksi. A fõ ciklus feladata. while vagy do utasításból) lép ki.. stb. hogy befejezzük a programot: #include<iostream> #include<string> #include<map> #include<cctype> // bemenet/kimenet // karakterláncok // asszociatív tömb // isalpha(). hogy egy újabb szimbólumot kapjon. } 6. cout << expr(false) << '\n'.

1. melyet általában argc-nek neveznek. fejezet tárgyalja. §B. A paraméterek karakterláncok. ezért argv típusa char*[argc+1] lesz. 6. vagy az std:: minõsítõt kell használnunk. Ha egy kifejezést meg lehetne adni parancssori paraméterként.3. Kifejezések és utasítások 155 Ezen fejállományok mindegyike az std névtérben nyújt szolgáltatásokat. így argv[argc]==0.2. amely két paramétert kap: az egyik. A szabványos fejállományoknak számos rendszeren .h kiterjesztésû fájl megfelelõjük van.7. Parancssori paraméterek Miután a programot megírtam és kipróbáltam.Forrás: http://www. melyek leírják az osztályokat.hu 6. és a 9. §9. hogy elõször el kell indítani a programot. A leggyakrabban egyetlen kifejezés kiértékelésére használtam a programot. A 8. és a globális névtérbe is behelyezik azokat (§9.1934" .2..1). ezt rendszerint argv-nek hívják.2. aztán be kell gépelni a kifejezéseket. így argc értéke mindig legalább 1. függvényeket stb. így ahhoz.4. Én az utóbbit választottam. §9.) meghívásával kezdõdik. A program a main() (§3. A program neve (ahogy az a parancssorban elõfordul) argc[0]-ként adódik át. milyen módon lehet ezt a számológépet a névterek használatával modulokba szervezni és hogyan lehet forrásfájlokra bontani. a paraméterek (argumentumok) számát adja meg. a másik a paraméterekbõl álló tömb. A paraméterek listáját a null karakter zárja le. hogy ne keverjem össze a kifejezések tárgyalását a modularitás kérdéskörével. hogy az általuk nyújtott neveket felhasználhassuk.doksi. kényelmetlennek találtam.1934 Ekkor a paraméterek értéke a következõ: argc: argv: 2 0 "dc" "150/1. vagy a globális névtérbe kell helyeznünk a neveket a következõképpen: using namespace std.4. Vegyük az alábbi parancsot: dc 150/1.1. jónéhány billentyûleütést megtakaríthatnánk. végül ki kell lépni.

hogyan használjuk azokat úgy. if (curr_tok == PRINT) continue. A parancssori paraméterek beolvasása egyszerû. break. ezért ki kell találnunk. // elõre megadott nevek beillesztése } .doksi. ha bevezetünk egy input nevû globális mutatót. minden bemeneti eljárásban ezt fogjuk felhasználni: istream* input. amely a használandó bemeneti adatfolyamra mutat. milyen parancssori paramétereket adunk meg.Forrás: http://www. attól függõen. Az ötlet a következõ: olvassunk ugyanúgy a parancssori karakterláncból. hogy a számológép bemeneti függvényei hogyan hivatkozzanak vagy az istringstream-re vagy a cin-re.1415926535897932385. default: error("túl sok paraméter"). table["e"] = 2. Egyszerû megoldás. } if (input != &cin) delete input. Sajnos nincs elegáns módja annak. hogy cin-ként az istringstream-re hivatkozhassunk. a probléma csak az. cout << expr(false) << '\n'. while (*input) { get_token(). } table["pi"] = 3. a híváskor C típusú tömbök és karakterláncok használatosak.hu 156 Alapok Mivel a main() meghívására vonatkozó szabályok a C nyelv követelményeivel azonosak. return no_of_errors. return 1. break. char* argv[ ]) { switch (argc) { case 1: // olvasás a szabványos bemenetrõl input = &cin. case 2: // a karakterlánc paraméter beolvasása input = new istringstream(argv[1]). A karakterláncból olvasó adatfolyam neve – micsoda meglepetés – istringstream. mint a bemeneti adatfolyamokból. if (curr_tok == END) break.7182818284590452354. hogy minél kevesebbet kelljen programoznunk. // mutató bemeneti adatfolyamra int main(int argc.

hogy nem sok hagyományos C stílusú. amely kifejezett és egyértelmû módon ábrázolja a számológépet. Más rendszerek szabályai a program indításakor paraméterek megadására vonatkozóan eltérõek. Általánosabb és hasznosabb megoldást készíthetünk. amit csak egyetlen program használ fel. hogy több parancssori paramétert is elfogadjon.6[16]). mint az ostream.2).7. A standard könyvtár és más könyvtárak arra valók. A könyvtárak tervezéskor és megvalósításkor általában nagyobb figyelmet kapnak. de erre nincs szükség.8.3). hogy a bemenet forrása valójában a számológép modul paramétere kell. ha észrevesszük.4. és map (§3. alacsonyszintû kód található benne. Amikor eléri a lánc végét.217/rate" Azért használok idézõjeleket.3. a UNIX rendszerekben parancs-elválasztóként használatos. Az alapvetõ probléma. 17.150/rate. hogy a „számológép” csak függvények és adatok gyûjteménye. §10.doksi. akik nem ismerik az asszociatív tömböket.hu 6.3).5[3]. pontosan ugyanúgy jelzi azt. ha kellõ elõrelátással már a kezdetektõl bevezetünk valamilyen. hogy használják azokat.75/rate.5.1934. De nem az. amikor saját kezûleg olyan kódot ír. amely karakterlánc paraméterébõl olvas (§21. hogy legyen. Számos hagyományos trükköt helyettesítettünk azzal. akkor természetesen meggondoltam volna. §3. string.5.fejezet). hogy olyan standard könyvtárbeli osztályokat használtunk. 6. Nincs olyan modul (§2. Megjegyzés a stílussal kapcsolatban A standard könyvtárbeli map szimbólumtáblaként való használata majdnem „csalásnak” tûnhet azoknak a programozóknak a szemében. Ha megnézzük a számológép kódját (különösen az elsõ változatot).1. Az istringstream használatához be kell építeni az <sstream> fejállományt. §21. amit ezzel a számológéppel érzékeltetni akartam. Nem volt elegáns dolog úgy módosítani a bemeneti eljárásokat.6. A változtatás elkerülhetõ lett volna.5. mert egyetlen paraméterként több kifejezést is átadhatunk: dc "rate=1. milyen paraméterei lehetnek a modulnak/típusnak (§8. mert a . hogy ezzel rugalmasabbak legyenek és különbözõ bemeneti forrásokkal mûködhessenek.4) vagy objektum (§2. az input-hoz hasonló dolgot.Forrás: http://www. . láthatjuk. Ha egy számológép modul vagy számológép típus tervezése lett volna a célom. hogy cin helyett *input-ot használjanak.4. §3. Könnyû lenne úgy módosítani a main()-t.19. Kifejezések és utasítások 157 Az istringstream olyan istream. az. mint amennyit egy programozó megengedhet magának. mint a többi adatfolyam a bemenet végét (§3.

doksi.).1). a mutató egy mutató eredményû kifejezés. amely nem konstans objektumot jelöl. fejezet).5. ()-lel stb. Az itt bemutatott jelentések arra az esetre vonatkoznak. sõt az értékadások is viszonylag ritkán fordulnak elõ. A táblázatokban az osztálynév egy osztály neve. nem pedig (a+b)*c. . a kif egy kifejezés. A felhasználói típusú operandusokra alkalmazott operátorok jelentését magunk határozhatjuk meg (§2.5). amelynek az eredménye osztályobjektum. Általában ilyennek kellene lennie egy olyan kódnak. a ciklusok. A felsõbb cellákban levõ operátorok az alsó cellákban levõkkel szemben elõnyt élveznek. amely nem kezeli a hardvert közvetlenül és nem él alacsonyszintû elvont adatábrázolásokkal. de ahhoz. amikor az operandusok beépített típusúak (§4. Például a+b*c jelentése a+(b*c). A táblázat minden cellájában azonos erõsségû (precedenciájú) operátorok találhatók.2. hogy ezt eldönthessük.1. az objektum egy olyan kifejezés. hogy az aritmetika. ha zárójelek közé van zárva. 11. mint a +. mert a * magasabb precedenciájú. Az egyoperandusú (unáris) és az értékadó operátorok jobbról balra.2. az összes többi balról jobbra értelmezendõ.hu 158 Alapok Vegyük észre. a+b+c jelentése (a+b)+c. máshol megszorítások vonatkoznak rá (§A. a tag egy tag neve. Például a=b<c?d=e:f=g jelentése a=((b<c)?(d=e):(f=g)).Forrás: http://www. Operátorok – áttekintés Ez a rész összefoglalja a kifejezéseket és bemutat néhány példát. Például a=b=c jelentése a=(b=c). A kifejezések formája független az operandusok típusától.5). Minden operátort egy vagy több név követ. A típus csak akkor lehet egy teljesen általános típusnév (*-gal. amely példaként szolgál az általánosan használt megnevezésekre és a szokásos használatra. meg kell néznünk a nyelvtant (§A. *p++ jelentése pedig *(p++). 6. nem (*p)++. és a balérték egy olyan kifejezés. Néhány nyelvtani szabályt nem lehet kifejezni a precedenciával és az asszociativitással (kötéssel).

Forrás: http://www. Kifejezések és utasítások 159 Operátor – áttekintés hatókör-feloldás hatókör-feloldás globális hatókör globális hatókör tagkiválasztás tagkiválasztás indexelés függvényhívás érték létrehozása növelés utótaggal csökkentés utótaggal típusazonosítás futási idejû típusazonosítás futási idõben ellenõrzött típuskényszerítés fordítási idõben ellenõrzött típuskényszerítés nem ellenõrzött típuskényszerítés konstans típuskényszerítés objektum mérete típus mérete növelés elõtaggal csökkentés elõtaggal komplemensképzés (logikai) nem mínusz elõjel plusz elõjel cím operátor indirekció létrehozás (memóriafoglalás) létrehozás (memóriafoglalás és kezdeti értékadás) létrehozás (elhelyezés) létrehozás (elhelyezés és kezdeti értékadás) felszámolás (felszabadítás) tömb felszámolása típuskonverzió osztálynév :: tag névtér_név :: tag :: név :: minõsített_név objektum . tag mutató -> tag mutató [kif] kif (kif_lista) típus (kif_lista) balérték ++ balérték -typeid (típus) typeid (kif) dynamic_cast <típus> (kif) static_cast <típus> (kif) reinterpret_cast <típus> (kif) const_cast <típus> (kif) sizeof kif sizeof (típus) ++ balérték -.hu 6.kif + kif & balérték * kif new típus new (kif_lista) new (kif_lista) típus new (kif_lista) típus (kif_lista) delete mutató delete [ ] mutató (típus) kif .balérték ~ kif ! kif .doksi.

*tagra_hivatkozó_mutató mutató -> *tagra_hivatkozó_mutató kif * kif kif / kif kif % kif kif + kif kif .kif kif << kif kif >> kif kif < kif kif <= kif kif > kif kif >= kif kif == kif kif != kif kif & kif kif ^ kif kif | kif kif && kif kif || kif kif ? kif : kif .Forrás: http://www.hu 160 Alapok Operátor – áttekintés (folytatás) tagkiválasztás tagkiválasztás szorzás osztás moduló (maradékképzés) összeadás (plusz) kivonás (mínusz) balra léptetés jobbra léptetés kisebb kisebb vagy egyenlõ nagyobb nagyobb vagy egyenlõ egyenlõ nem egyenlõ bitenkénti ÉS bitenkénti kizáró VAGY bitenkénti megengedõ VAGY logikai ÉS logikai megengedõ VAGY feltételes kifejezés objektum .doksi.

kif 6.2). relációs (összehasonlító) operátorok logikai értékeket adnak vissza. a számítást lebegõpontos aritmetikával végezzük és az eredmény egy lebegõpontos érték lesz. Az int-nél kisebb operandusok (mint a bool és a char) int-té alakulnak.1. hogy a „legtágabb” operandustípussal megegyezõ eredmény jöjjön létre.Forrás: http://www. <= stb.doksi. Ha egy operátornak balérték operandusa van. az eredmény pedig long érték lesz. A felhasználó által megadott operátorok jelentését és eredményét deklarációjuk határozza meg (§11. a számítás hosszú egész (long) aritmetikával történik.hu 6. Az ==. Kifejezések és utasítások 161 Operátor – áttekintés (folytatás) egyszerû értékadás szorzás és értékadás osztás és értékadás maradékképzés és értékadás összeadás és értékadás kivonás és értékadás balra léptetés és értékadás jobbra léptetés és értékadás ÉS és értékadás megengedõ VAGY és értékadás kizáró VAGY és értékadás kivétel kiváltása vesszõ (mûveletsor) balérték = kif balérték *= kif balérték /= kif balérték %= kif balérték += kif balérték -= kif balérték <<= kif balérték >>= kif balérték &= kif balérték |= kif balérték ^= kif throw kif kif .6.2. mielõtt az operátort alkalmazzuk rájuk. Ha long típusú operandusa van. A fõ cél az. Eredmények Az aritmetikai mûveletek eredményének típusát az a szabályhalmaz dönti el. amelyet „általános aritmetikai átalakítások”-nak nevezünk (§C. amely a balérték operandust jelöli: .3). Ha egy bináris operátor operandusa például lebegõpontos. akkor – ha ez logikailag lehetséges – az operátor eredménye egy olyan balérték lesz.

a túlcsordulás és a nullával való osztás nem vált ki szabványos kivételeket (§14. int y) { int j = x = y.Forrás: http://www. } // x=y értéke az x értékadás utáni értéke // p x-re mutat // hiba: x++ nem balérték // a nagyobb értékû int címe Ha a ? : második és harmadik operandusa is balérték és ugyanolyan típusúak. Az alulcsordulás. ez viszont rendszerint a program hirtelen befejezõdését eredményezi. Hasonlóan. Ez különösen akkor fontos. int* q = &(x++).doksi. int* p = &++x. A fordítónak nem kell ellenõriznie az aritmetikai túlcsordulást és általában nem is teszi meg. nem meghatározott. amit ptrdiff_t-nek hívnak és szintén a <cstddef> fejállomány írja le. amelynek egyformán és hatékonyan kell mûködnie beépített és felhasználói típusok esetében is (például ha olyan sablonokat vagy programokat írunk. amelyek C++ kódot hoznak létre). nagy rugalmasságot ad az operátorok használatában. a mutató-kivonásé pedig egy elõjeles integrális típus. A sizeof eredménye a size_t nevû elõjel nélküli integrális típus.hu 162 Alapok void f(int x. . cout << "Az i negatív lett!" << i << '\n'. int* pp = &(x>y?x:y). Például: void f() { int i = 1. az érték jellemzõen egy negatív számig „ér körbe” (az én gépemen ez -2147483648). a nullával osztás eredménye sem meghatározott. Ami ekkor történik. az eredmény a megfelelõ típusú balérték lesz. hogy ilyen módon megõrizzük a balértékeket. Az. while (0 < i) i++. melynek meghatározása a <cstddef> fejállományban szerepel.10). } A ciklus elõbb-utóbb az i értékét a legnagyobb egész értéken túl növeli. ha olyan kódot írunk.

Nézzük az alábbi példát: f1(v[i]. sajnos. A .doksi. és a || (logikai VAGY) operátorok esetében biztosított. . amit arra használunk. hogy a kifejezés kiértékelése balról jobbra történik: int x = f(2)+g(3).Forrás: http://www. f2( (v[i]. a legtöbb ezt nem teszi meg. hogy f() vagy g() hívódik meg elõször Jobb kódot készíthetünk. // nem meghatározott eredmény A fenti kifejezés vagy v[1]=1-ként.a+1) például a bnek 3-at ad értékül. ha az elsõ operandus értéke false.3-ban találhatók. A fordítóprogramok figyelmeztethetnek az ilyen kétértelmûségekre. // két paraméter // egy paraméter Az f1 meghívásának két paramétere van. nagyon rossz stílusról árulkodik és eredménye nem meghatározott. amely függ a paraméter-kifejezések sorrendjétõl. ha az elsõ operandus true. de a kiértékelési sorrendre vonatkozó megszorítások hiánya elõre nem meghatározott eredményekhez vezethet: int i = 1.2. hogy a függvényhívásoknál elválasszuk a paramétereket. Az f2 meghívásához egy paramétert adtunk meg. a (v[i]. ha a kifejezések kiértékelési sorrendje nem kötött. ezt néha rövid vagy „rövidzáras” kiértékelésnek (short-circuit evaluation) nevezik. amely i++-szal egyenértékû. így nem tételezhetjük fel például azt sem. Jegyezzük meg.i++) ). Az olyan megoldás.2. A b=(a=2. a || második operandusa pedig csak akkor. (vesszõ). a paraméter-kifejezések kiértékelési sorrendje pedig nem meghatározott. (vesszõ) mûveletsor-jelzõ logikailag különbözik attól a vesszõtõl.hu 6. hogy a . v[i] és i++. v[i] = i++. esetleg még furcsábban viselkedik. Kiértékelési sorrend A kifejezéseken belüli részkifejezések kiértékelési sorrendje nem meghatározott. Kifejezések és utasítások 163 6. A || és a && használatára vonatkozó példák a §6. hogy a bal oldali operandus a jobb oldali elõtt értékelõdik ki. // nem meghatározott.2. i++) „vesszõs” kifejezés. a && (logikai ÉS). vagy v[2]=1-ként értékelõdik ki.i++). Beépített típusoknál a && második operandusa csak akkor értékelõdik ki.

Például if (i<=0 || max<i) // . ha a felhasználó nem tud különbséget tenni köztük. ahogy azokat leírtuk. hogy nem kellene-e egy külön változó használatával szétbontanunk a kifejezést. Vannak olyan esetek. így a fordítóprogram pontosan úgy fogja az ilyen kifejezéseket kiértékelni.2.. de szabályos – kifejezéssel viszont nem: if (i <= (0||max) < i) // . ha a programozónak kétségei vannak ezekkel a szabályokkal kapcsolatban. amikor az operátorok sorrendje nem a „magától értetõdõ” értelmezést eredményezi: if (i&mask == 0) // hoppá! == kifejezés & operandusaként Ekkor nem az történik. Az a*(b/c) kifejezés csak akkor értékelõdhet ki (a*b)/c-ként.doksi.3.. . hogy alkalmazzuk a mask-ot az i-re. ha a részkifejezések bonyolultabbak. Ebben az esetben a zárójelek fontosak: if ((i&mask) == 0) // . Mivel az == elõnyt élvez az & (kétoperandusú) mûvelettel szemben. majd megnézzük.. ezért zárójeleket kell használnunk. a kifejezés i&(mask==0)-ként lesz értelmezve. A bonyolult részkifejezések mindig hiba forrásai lehetnek..hu 164 Alapok A csoportosítás kikényszerítésére zárójeleket használhatunk.. hogy az eredmény 0-e. Zárójeleket használni azonban mindig hasznos. Ez egyenértékû az alábbival: if ( (i<=0) || (max<i) ) // .. Az operátorok sorrendje A precedencia és a „kötési” (asszociativitási) szabályok a leggyakoribb használatot tükrözik. Az a*(b/c) és az (a*b)/c számos lebegõpontos számításnál jelentõsen különbözik.. Az alábbi – értelmetlen. hogy szükségünk van zárójelekre – fontoljuk meg. Szerencsére a fordítóprogram könnyen figyelmeztethet az ilyen hibákra. A zárójelek használata még gyakoribb. 6. hogy „ha i kisebb vagy egyenlõ 0-nál VAGY max kisebb i-nél”. ezért – ha úgy érezzük.. azt jelenti. Például a*b/c jelentése (a*b)/c.Forrás: http://www. ha a*(b/c)-t akarunk kapni.

A logikai értéket a fordítóprogram aztán automatikusan 1-re vagy 0-ra alakítja. ahogy egy matematikus elvárná: if (0 <= x <= 99) // . ezek elõjel nélküli (unsigned) megfelelõire és az enum típusokra. >> és << bitenkénti logikai operátorokat integrális (egész típusú) objektumokra és felsorolásokra alkalmazzuk – azaz a bool. A fordítóprogramok általában figyelmeztetnek is erre. Az eredmény típusát a szokásos aritmetikai átalakítások (§C. de értelmezése (0<=x)<=99.4. a ~-t pedig komplemensként értelmezzük.) döntik el. short.. int. melyet az ostream megvalósításából vettünk kölcsön: enum ios_base::iostate { goodbit=0. Ez megengedett. eofbit=1. Íme egy rövid példa.. a ^-ot szimmetrikus differenciaként.6. long típusokra. hogy x a 0.3. a | operátort unióként. 6. amit aztán összehasonlítva 99-cel true-t kapunk.doksi..99 tartományban van-e: if (0<=x && x<=99) // . A bitenkénti logikai operátorok jellemzõ felhasználása a kis halmazok (bitvektorok) fogalmának megvalósítása.. Ebben az esetben egy elõjel nélküli egész minden bitje a halmaz egy elemét jelöli. ahol az elsõ összehasonlítás eredménye vagy true vagy false. . Az & bináris operátort metszetként.Forrás: http://www. |. hogy a feltételekben =-t (értékadást) használnak == (egyenlõ) helyett: if (a = 7) // hoppá! konstans értékadás a feltételben Ez természetes. hogy a következõ nem úgy mûködik. Felsoroló típust arra használhatunk. -. Kifejezések és utasítások 165 Érdemes megjegyezni. badbit=4 }. char.. hogy megnevezzük egy ilyen halmaz elemeit. A következõképpen vizsgálhatjuk meg. Bitenkénti logikai mûveletek Az &.hu 6.2. és a bitek száma korlátozza a halmaz elemeinek számát. ^. failbit=2. mert az = jelentése sok nyelvben „egyenlõ”. Gyakori hiba kezdõknél.

Jegyezzük meg.8. vagy a hordozhatóság érdekében a rendszer alacsonyabb szintjein kell tartanunk. Vegyük például azt az esetet. de a megbízhatóság. || és ! logikai operátorokkal. hogy kezeljék õket. egy másik bitvektorral. // rdstate() az állapotot adja vissza Az adatfolyam-állapotok különbségeinek kiszámítása nem túl gyakori. if (state&(badbit|failbit)) // nem megfelelõ adatfolyam A külön zárójelek azért szükségesek. más hasonló típusoknál viszont alapvetõ mûvelet. amikor léptetéssel és maszkolással veszünk ki bitmezõket egy szóból. // . hogyan különbözik két adatfolyam állapota: int diff = cin. } A bitenkénti logikai operátorokat ne keverjük össze az &&. A |= operátort arra használjuk. Általánosabb halmazfogalomra nézzük meg a standard könyvtárbeli set-et (§17.3). amely a kezelt megszakítások halmazát jelöli. hogy az állapothoz hozzáadjunk valamilyen új információt.rdstate(). Egy 32 bites long középsõ 16 bitjét például így vehetjük ki: unsigned short middle(long a) { return (a>>8)&0xffff.11). Például így nézhetjük meg.Forrás: http://www.doksi. Az utóbbiak vagy true-t.3). Az egyszerû state=eofbit értékadás kitörölt volna minden más bitet. A kényelmes bitkezelés nagyon fontos lehet. mert az & elõnyt élvez a | mûveleti jellel szemben. Ezt természetesen megtehetjük a bitenkénti logikai operátorokkal is.rdstate()^cout. Ezek az adatfolyam-állapotjelzõk megfigyelhetõk a folyam megvalósításán kívül is. A mezõk (§C. melyek arra várnak.3..1) használata igazán kényelmes módon rövidíti le azt a mûveletet. a módosíthatóság. hogy ezt a „zsonglõrködést” a bitekkel az iostream megvalósításából vettük és nem a felhasználói felületbõl. és elsõdlegesen akkor hasznosak. amikor össze kell hasonlítanunk azt a bitvektort. bitset-et (§17. és a vector<bool>-t (§16.5.hu 166 Alapok Az adatfolyam az állapotot így állíthatja be és ellenõrizheti: state = goodbit. amely olyan megszakítások halmazát ábrázolja. Egy függvény így jelezheti. vagy false-t adnak vissza.. hogy elérte a bemenet végét: state |= eofbit.4. amikor egy .

Például az !0 (nem nulla) true érték. while.3) feltételt írunk.5. Mivel a while (*p++ = *q++) .2. Egy nulla végzõdésû karakterláncot például a következõképpen másolhatunk át: void cpy(char* p. ez a kódolási stílus viszont nem ritka a C-ben és a C++-ban. A mutatók összeadásához és kivonásához hasonlóan a mutatókra alkalmazott ++ és -. Definíció szerint a ++lvalue jelentése lvalue+=1. amely a -1 érték kettes komplemensbeli ábrázolása. hogy egy érték növelését közvetlenül. vagy for utasításban (§6. míg a ~0 (a nulla komplemense) egy csupa egyesbõl álló bitminta. p++ a p-t a következõ tömbelemre állítja (§5. megéri közelebbrõl megvizsgálnunk. például az y=++x egyenértékû az y=(x+=1)gyel. ez pedig lvalue=lvalue+1-et jelent. } A C-hez hasonlóan a C++-t is szeretik és gyûlölik azért. for (int i = 0.3. A ++x értéke az x új (megnövelt) értéke lesz. Növelés és csökkentés A ++ operátort arra használjuk. ahol t egy x-szel azonos típusú változó.x+=1. Kifejezések és utasítások 167 if. §6.1). hogy a balértéknek nincs „mellékhatása". amelyekre a mutató hivatkozik. A növelendõ objektumot jelölõ kifejezés (csak) egyszer értékelõdik ki.doksi. mert megengedi az ilyen tömör.mûködését azok a tömbelemek határozzák meg.Forrás: http://www.3. kifejezés meglehetõsen zavaros a nem C programozók számára.operátorokat használhatjuk elõtagként és utótagként is.operátor fejezi ki. és nem az összeadás és értékadás párosításával fejezzük ki. 6. . feltéve. kifejezésközpontú kódolást. változók növelésére vagy csökkentésére.t)-vel. A ++ és -. Vegyük elõször a karaktertömbök másolásának egy hagyományosabb módját: int length = strlen(q).hu 6. const char* q) { while (*p++ = *q++) . A csökkentést ugyanígy a -. Az x++ értéke azonban az x régi értéke: az y=x++ egyenértékû az y=(t=x. A növelõ operátorok különösen a ciklusokban használatosak.3.2. i<=length. i++) p[i] = q[i].

q++. nincs szükségünk az üres blokkra és hogy felesleges a „!=0” vizsgálat. hogy elõször felhasználjuk az értéket. i++) p[i] = q[i]. q[i]!=0 . p++. mint az elõzõ? Egy tapasztalt C vagy C++ programozó számára nem. Így kétszer olvassuk végig a teljes láncot: egyszer azért. // léptetés a következõ karakterre // léptetés a következõ karakterre // lezáró nulla Mivel az utótagként használt növelõ operátor megengedi. mert p és q mutatók: while (*q != 0) { *p = *q. Ezért inkább próbáljuk ezt: int i.Forrás: http://www. Így megkapjuk azt a változatot. while (*p++ = *q++) . // lezáró nulla A *p++ = *q++ értéke *q. a következõképpen írhatjuk újra a ciklust: while (*q != 0) { *p++ = *q++. hogy a nulla végzõdést keresve végigolvassuk azt. } *p = 0. mint az elõzõ? Az elsõ változatot ki- . hogy észrevesszük.doksi. hogy *q nulla.hu 168 Alapok Ez pazarlás. A nulla végzõdésû karakterlánc hosszát úgy határozzuk meg. hogy átmásoljuk. amiben a nulla végzõdést adjuk értékül. mert egy mutató vagy integrális feltétel mindig összehasonlítódik 0-val. hogy meghatározzuk a hosszát. } *p = 0. ezért a példát így módosíthatjuk: while ((*p++ = *q++) != 0) { } Ebben az esetben addig nem vesszük észre. de ki lehet küszöbölni. Következésképpen elhagyhatjuk az utolsó értékadást. Hatékonyabb idõben és tárterületben. p[i] = 0. és csak azután növeljük meg. Végül tovább rövidíthetjük a példát azzal. egyszer pedig azért. amíg be nem másoljuk *p-be és meg nem növeljük p-t. amelyet célul tûztünk ki. for (i = 0. Ez a változat vajon kevésbé olvasható. // lezáró nulla Az i változót indexelésre használjuk.

.1).1) használhatjuk. Enode* right. Enode* left. hogy olyan objektumokat hozunk létre. mielõtt elhinnénk. Szabad tár A névvel rendelkezõ objektumok élettartamát (lifetime) hatókörük (§4. hogy „a szabad tárban vannak” (free store). a gép felépítésétõl és a fordítóprogramtól függ.6.hu 6.9. Kifejezések és utasítások 169 véve. részesítsük elõnyben a standard könyvtár lehetõségeit a mutatókkal és bájtokkal való ügyeskedéssel szemben. hogy valamilyen kézzel írt kódrészlet felülmúlja a könyvtári függvények teljesítményét. gyakran azonban hasznos.2. }.2. A standard könyvtár függvényei lehetnek helyben kifejtett függvények (§7. Az. Az ilyen objektumokat a new operátor hozza létre és a delete operátort használhatjuk felszámolásukra. ha olyan objektumot hozunk létre. Ezért gondosan fontoljuk meg.Forrás: http://www. hogyan írnánk meg egy fordítóprogramot olyan stílusban. const char*). ami meghívta az strlen()-t. ahogy az asztali számológépnél tettük (§6. // a <string. amelyek akkor is felhasználhatók.h> fejállományból Általánosabb másolásra a szabványos copy algoritmust (§2. Nézzük meg.1) vagy egyedi gépi utasításokkal megvalósítottak. ahol létrehoztuk.doksi. a nulla végzõdésû karakterláncok másolásának leghatékonyabb módja viszont általában a standard könyvtárbeli másoló függvény.6.7. Ahol lehetséges. hogy melyik változat a leghatékonyabb. hogy „kupac-objektumok” (heap). amely függetlenül létezik attól a hatókörtõl. A new által létrehozott objektumokra azt mondjuk. §18. vagyis „a dinamikus memóriában vannak”.4) dönti el. miután visszatértünk abból a függvénybõl. 6. char* strcpy(char*. Nevezetesen gyakori. // . ahol létrehoztuk azokat.. vagy azt. .1. A szintaktikai elemzõ függvények felépíthetnek egy kifejezésfát a kódkészítõ számára: struct Enode { Token_value oper. Enode* expr(bool get) { Enode* left = term(get). nem igazán.

break. amíg kifejezetten meg nem semmisítjük a delete-tel. Következésképpen feltételezzük. n->left = left. } } // Enode törlése a szabad tárból A new által létrehozott objektum addig létezik. nem lesz hatása. Ha van szemétgyûjtõ. . Ezután a new újra felhasználhatja az objektum által lefoglalt tárhelyet. A new operátornak egyedi változatait is meghatározhatjuk (§15.6)..9.. a delete-et használva. A delete operátort csak a new által visszaadott mutatóra vagy nullára lehet alkalmazni. hogy a new által létrehozott objektumokat magunknak kell megsemmisítenünk. } default: return left.) } switch(curr_tok) { case PLUS: case MINUS: { Enode* n = new Enode.hu 170 Alapok for (. A C++változatok nem garantálják. hogy van „szemétgyûjtõ” (garbage collector). n->oper = curr_tok. Ha a delete-et nullára alkalmazzuk.. amelyekre nincs már hivatkozás és újra felhasználhatóvá teszi azok helyét. a deleteek a legtöbb esetben elhagyhatók (§C. n->right = term(true).doksi.Forrás: http://www. delete n. } // Enode létrehozása a szabad tárban // csomópont visszaadása A kódkészítõ aztán felhasználná az eredményül kapott csomópontokat (node) és törölné azokat: void generate(Enode* n) { switch (n->oper) { case PLUS: // .1). amely megkeresi azokat az objektumokat. left = n.

. // másolás p-bõl s-be return s. int* q = new int[n]. strcpy(s. delete p. §16. } // önálló objektum // tömb A delete[ ] operátort csak a new által visszaadott mutatóra vagy nullára alkalmazhatjuk... a delete[ ] tömbök felszámolására használatos. nem lesz hatása. delete[ ] p. .. Ebbõl az következik. hogy egyes objektumokat felszámoljuk. Tömbök A new használatával létrehozhatunk objektumokból álló tömböt is: char* save_string(const char* p) { char* s = new char[strlen(p)+1]. ezért létrehozására és felszámolására a sima new-t és delete-et használhatjuk: void f(int n) { vector<int>* p = new vector<int>(n).1. hogy a szabványos new operátorral létrehozott objektumok valamivel több helyet foglalnak.Forrás: http://www. a delete-nek vagy a delete[ ]-nek meg kell tudni állapítani. Ha vissza akarjuk nyerni a new által lefoglalt tárhelyet. // . mekkora a lefoglalt objektum mérete. } int main(int argc. char* p = save_string(argv[1]). Kifejezések és utasítások 171 6.3) valódi objektum. Az objektum méretét általában egy gépi szó tárolja. hogy a vector (§3.2.7. char* argv[ ]) { if (argc < 2) exit(1).doksi. Ha a delete[ ]-et nullára alkalmazzuk.hu 6.p). Jegyezzük meg. // . mint a statikus objektumok. delete[ ] q.6. } A „sima” delete operátort arra használhatjuk.1.

Az operator new() és az operator new [ ]() szabványos megvalósítása a visszaadott memóriát nem tölti fel kezdõértékkel..4. ha a new nem talál lefoglalható helyet? Alapértelmezés szerint a lefoglaló bad_alloc kivételt vált ki (a másik lehetõséget illetõen lásd §19.doksi. new[ ] és delete[ ] operátorai függvényekként vannak megvalósítva: void* operator new(size_t). az operator new[ ]() meghívására kerül sor. Magunk is meghatározhatjuk. delete. } catch(bad_alloc) { cerr << "Elfogyott a memória!\n". void* operator new[ ](size_t). az operator new()-t hívja meg. amelyet a <new> fejállományban bevezetett set_new_handler() függvénnyel elõzõleg beállítottunk (amennyiben ezt megtettük): void out_of_store() { cerr << "Az operator new nem járt sikerrel: nincs tárhely\n". a kód végül meg fogja hívni a bad_alloc eseménykezelõjét. void operator delete[ ](void*).2. throw bad_alloc().6.2.hu 172 Alapok 6. elõször azt a függvényt hívja meg.) new char[10000]. Ha a new nem jár sikerrel. hogy az megfelelõ számú bájtot foglaljon le. Ha tömb számára foglal helyet.5-öt): void f() { try { } for(. Mi történik. } Akármennyi memória áll a rendelkezésünkre. amikor kifogy a memória. void operator delete(void*).Forrás: http://www. mit csináljon a new. } . Memória-kimerülés A szabad tár new. // hely az önálló objektum számára // hely a tömb számára Ha a new operátornak egy objektum számára kell helyet foglalnia.

11. melynek típusa ismeretlen a fordítóprogram számára. void f() { int* p = static_cast<int*>(malloc(100)). Ilyen eset.4.) new char[10000]. mint egy I/O eszköz címét: void* malloc(size_t).4. vagy ha azt akarjuk kifejezni. mert saját operator new()-t és operator delete()-et írtunk –. azaz a tár olyan objektumot tartalmaz vagy fog tartalmazni. mint hogy egyszerûen befejezi a programot. // a new által lefoglalt helyet int-ként használjuk IO_device* d1 = reinterpret_cast<IO_device*>(0Xff00). Meghatározott típuskonverziók Néha „nyers memóriával” kell dolgoznunk. } // out_of_store lesz a new_handler Ez azonban soha nem fog elérni addig. hogyan mûködik a new és a delete – például azért.). bad_alloc-ot vált ki.9. // eszköz a 0Xff00 címen // . cout << "kész\n". a leghasznosabb. 6.a kezelõfüggvény megpróbálhat valamennyi memóriát keresni.5. amikor a memóriafoglaló (allokátor) egy újonnan lefoglalt memóriaterületre hivatkozó void* típusú mutatót ad vissza. Egy new_handler azonban valami okosabbat is tehet.7. hogy egy adott egész értéket úgy kell kezelni.. Kifejezések és utasítások 173 int main() { set_new_handler(out_of_store). A memóriafoglalásnak kétféle útja létezik: vagy gondoskodunk nem szabványos lefoglaló és felszabadító függvényekrõl (§15.2. Azzal.. létezik-e meghívható kezelõfüggvény. ha szerez egy már megírt és ellenõrzött terméket (§C.doksi.. amely megvizsgálja. vagyis a felhasználó gondoskodhat szemétgyûjtõrõl. §19. for (. így elhagyhatóvá teheti a delete-et (bár ez kétségtelenül nem kezdõknek való feladat). felvállaljuk. hogy nekünk kell törõdnünk a memória kimerülésével kapcsolatos problémákkal a new minden használatakor.6) a new szabályos használata számára. és ha nem talál ilyet. hogy új new_handler-t állítunk be. Ehelyett a következõt fogja kiírni: Az operator new nem járt sikerrel: nincs tárhely Lásd §14. Ha tudjuk.1).Forrás: http://www. } . Majdnem mindenkinek.4. hogy kiírja a „kész”-t.hu 6. akinek automatikus szemétgyûjtõre van szüksége.5-öt az operator new() egy olyan lehetséges megvalósításáról. vagy a felhasználó által adott további foglalási adatokra támaszkodunk (§10. hogy a new visszatérhessen.

Azaz a (T)e lehet. például két. ugyanazon osztályhierarchiában lévõ mutatótípus. hogy meggondoljuk.1) operátort szintén használhatjuk. amikor a C-ben szükség lenne rá (§1. és jelentõs hibaforrások.6). Ha nem tudjuk T és e pontos típusát. mint a fent említettek. és §25. amelynek ugyanaz a bitmintája. amelyet az érték meghatározására használtunk. és sok olyan esetben is.4. Ha a cél legalább annyi bites. de nem hordozhatót a nem rokon típusok között. szánjunk idõt arra. vajon a 0Xff00 érvényes cím-e. Ebben a könyvben explicit típuskényszerítést valósághû helyzetekben csak a §6. az eredményt a reinterpret_cast-tal az eredeti típusra alakíthatjuk és használhatjuk azt. esetleg egy mutatótípusról eltávolítja a const minõsítõt. reinterpret_cast és a const_cast kombinációjaként. Azt sem tudja. Számos programban az ilyen típuskonverzió teljesen elkerülhetõ. A reinterpret_cast eredményét csak akkor lehet biztosan felhasználni. hogy „hordozható” átalakítást végez egymással kapcsolatban levõ típusok között. Ez a C stílusú konverzió sokkal veszélyesebb. §13. Az explicit (pontosan meghatározott) típuskényszerítések (casting) néha szükségesek. ha annak típusa pontosan az a típus.2. A reinterpret_class olyan típusok átalakítását hajtja végre. és megkönnyíti.4.4. A C++-ban az explicit típuskényszerítés a legtöbb esetben szükségtelen olyankor. hogy a fordítóprogram elvégezzen bizonyos minimális típusellenõrzést a static_cast esetében.4. A const minõsítõt eltávolító „konstanstalanító” const_cast (§15. például egészrõl mutatóra vagy mutatótípusról egy másik.6. amely bármilyen átalakítást elvégez. mint az eredeti érték.7. §15. a reinterpret_cast-ok közül viszont csak kevés. mert a jelölést nehezebben lehet észrevenni egy nagy programban és a programozó szándéka szerinti átalakítás fajtája nem nyilvánvaló.5. integrális típus és felsoroló típus. §B. Ha kísértést érzünk.3).1 pontokban használunk. hogy a programozó megtalálja a veszélyesebb átalakításokat. A static_cast operátor egymással kapcsolatban levõ típusok közötti konverziót végez. általában új típust hoz létre. vagy lebegõpontos típus és integrális típus közöttit. hogy pontosan meghatározott típuskényszerítést alkalmazzunk. §7. Ez a megkülönböztetés lehetõvé teszi. A futási idõben ellenõrzött konverziók egyik formája a dynamic_cast (§15. Következésképpen az átalakítások helyessége teljes mértékben a programozó kezében van. Néhány static_cast „hordozható”.2. melyeket a reinterpret_cast jelöl. ahol a C++ korai változataiban szükséges volt (§1. . amelyek nincsenek kapcsolatban.7. nem rokon mutatóra konvertál. vajon tényleg szükséges-e.3). A C++ a C-bõl örökölte a (T)e jelölést.Forrás: http://www. de hagyományosan túl sokszor használják azokat. máshol néhány eljárásra korlátozhatjuk a használatát.1). mint a paraméteréé.2.hu 174 Alapok A fordítóprogram nem ismeri a void* által mutatott objektum típusát. ezt nem tudjuk eldönteni.2. Eredményül T típusú érték jön létre (§B. A reinterpret_cast esetében nem sok dolog biztos.2. amit ki lehet fejezni a static_cast.doksi.

.5. Sajnos.4. az egészekrõl felsoroló típusra való átalakításoknál (§4.5). A jelölést megpróbálom kizárólag ott használni. és még egy hosszabb egész típusról egy rövidebbre (például long-ról char-ra) való átalakítás is nem meghatározott viselkedést eredményezhet.doksi.2.7) használunk.hu 6. } // d csonkolása // complex létrehozása d-bõl A T(e) szerkezetet néha függvény stílusú konverziónak nevezik.1.8. azaz a szûkítõ aritmetikai átalakításoknál (§C.8). hogy a T(e) használata nem mindig biztonságos. A mutató-konverziókat a T(e) jelölést használva nem fejezhetjük ki közvetlenül. // . Konstruktorok Egy T típusú érték létrehozása egy e értékbõl a T(e) függvényjelöléssel fejezhetõ ki: void f(double d) { int i = int(d).6).Forrás: http://www. Ekkor a programozó nem tudhatja. Kifejezések és utasítások 175 6. // .3). kikerülhetõ ha a mutatótípusokra typedef neveket (§4..3.2. hogy a sablon (template) paramétere beépített vagy felhasználói típusra vonatkozik-e majd (§16.. .4. ami azt vonja maga után.4. A char*(2) például formai hibának számít. complex z = complex(d).9. Sajnos az a védelem.2). ha létezik ilyen. amit a fordító az adott típusra konvertál (§4. complex z = complex(). és a felhasználói típusok objektumainak létrehozásánál (§2. A konstruktor jelölés használata beépített típusokra sablonok írásakor különösen fontos. beépített T típusokra T(e) egyenértékû (T)e-vel. A T alapértelmezett értékének kifejezésére a T() konstruktor jelölés használatos: void f(double d) { int j = int(). Ezért az int() egy másfajta módja a 0 írásának.2. Aritmetikai típusok esetében az értékek csonkulhatnak.. ahol az érték létrehozása pontosan meghatározott.2) határozza meg. amit a konstruktor jelölés nyújt az ilyen veszélyes átalakítások ellen. §10.9. §17. A T felhasználói típusra T()-t az alapértelmezett konstruktor (§10. } // alapértelmezett int érték // alapértelmezett complex érték A beépített típusok konstruktorának értéke a 0.

Utasítások – áttekintés Íme a C++ utasítások összefoglalása. néhány példával: Az utasítások formai szabályai utasítás: deklaráció { utasítás_listanem kötelezõ } try { utasítás_listanem kötelezõ } kezelõ_lista kifnem kötelezõ . return kifnem kötelezõ . goto azonosító. azonosító : utasítás utasítás_lista: utasítás utasítás_listanem kötelezõ feltétel: kif típusazonosító deklarátor = kif kezelõ_lista: catch (kif_deklaráció) { utasítás_listanem kötelezõ } kezelõ_lista kezelõ_listanem kötelezõ . for (kezdõérték_meghatározó feltételnem kötelezõ .kifnem kötelezõ ) utasítás case konstans_kif : utasítás default : utasítás break .3.Forrás: http://www.hu 176 Alapok 6.doksi. if (feltétel) utasítás if (feltétel) utasítás else utasítás switch (feltétel) utasítás while (feltétel) utasítás do utasítás while (kif). continue .

illetve az olyan egyszeri értékadásos programozási stílusnál.3.3. amíg egy megfelelõ kezdeti érték rendelkezésre nem áll jobb teljesítményhez is vezethet: string s. */ s = "A legjobb a jó ellensége. §6. alapvetõ fontosságú sok konstans esetében.8)..hu 6.1. const char* p) { if (p==0) return. A kivételek kezelésére vonatkozó utasításokat – a try blokkokat – a §8. string s = v[i]. A fenti könnyen elõfordulhat.4. hogy lehetõvé tegyük a programozónak a kezdõérték nélküli változókból származó hibák csökkentését és a változók hatókörének lehetõ legnagyobb szûkítését a kódban. Hacsak egy változót static-ként nem adunk meg. hogy sokkal lassabb. amikor a vezérlés áthalad a deklarációján (lásd még §10. értékadó és eljáráshívó utasítások pedig nincsenek: az értékadások és a függvényhívások kifejezések. ha a változónak utasításra van szüksége a kezdeti értékadáshoz. amit a változónak tartalmaznia kell: void f(vector<string>& v. A deklarációkat azért engedjük meg minden olyan helyen.3. if (i<0 || v. Ilyenek például a bemeneti változók és a tömbök.1 pontban tárgyaljuk.Forrás: http://www. §6. } // . hogy a deklarációkat végrehajtható kód után is elhelyezhetjük.3.1). 6. if (s == p) { // .. Kifejezések és utasítások 177 Jegyezzük meg. int i. ahol egy objektum értéke nem változik meg annak létrehozása és kezdeti értékadása után. Kezdeti érték nélkül általában akkor adunk meg egy változót. mielõtt lenne egy olyan érték. hogy a deklaráció egy utasítás.doksi. ahol utasítást használhatunk (és még pár további helyen. . mint a következõ: string s = "Voltaire"..1.". Deklarációk mint utasítások A deklaráció utasítás. Ritkán van ok új változó bevezetésére... Felhasználói típusoknál a változó meghatározásának elhalasztása addig.3.2. } A lehetõség. /* ..size()<=i) error("rossz index"). minden esetben kezdõértéket fog kapni.

hogy if (x != 0) // . Kiválasztó utasítások Egy értéket az if vagy a switch utasítással vizsgálhatunk meg: if (feltétel) utasítás if (feltétel) utasítás else utasítás switch (feltétel) utasítás Az alábbi összehasonlító operátorok a logikai (bool) típusú true értéket adják vissza.1). Ha nulla. Jegyezzük meg. azt jelenti.. a második utasításra ugrunk (ha megadtunk ilyet). A p mutató esetében az alábbi egy közvetlen utasítás. hogy nem mutat objektumra: if (p != 0) // ..doksi.. Ebbõl az következik. amivel találkoztam. hogy „p egy érvényes objektumra mutat”: if (p) // . és false-t.Forrás: http://www. hogy bármilyen aritmetikai vagy mutató kifejezést lehet feltételként használni. ugyanazt a kódot készítette mindkét vizsgálatra.. hogy összehasonlítja egy olyan értékkel.. Például ha x egy egész. amelyrõl tudjuk. úgy. A következõ közvetett módon ugyanezt a kérdést fogalmazza. akkor if (x) // . ha az összehasonlítás igaz..hu 178 Alapok 6.1. ami azt a vizsgálatot fejezi ki.. . Minden fordítóprogram.2. ha hamis: == != < <= > >= Az if utasításban az elsõ (és egyetlen) utasítás akkor hajtódik végre. hogy a 0 mutatót nem minden gép ábrázolja „csupa nullával” (§5. ha a kifejezés nem nulla.3..

hu 6. A feltétel körül lévõ zárójelek nem szükségesek. Az && és a || mûveletek nem értékelik ki a második paramétert.. Például a switch (val) { case 1: f(). és csak akkor nézi meg. A switch utasítás if utasítások sorozataként is leírható. break.Forrás: http://www. break. Néhány if utasítást kényelmesen feltételes kifejezésekre cserélhetünk. A fenti utasítás például elõször megvizsgálja. Kifejezések és utasítások 179 A && || ! logikai operátorok leggyakrabban feltételekben használatosak. én azonban úgy gondolom.. } . case 2: g(). ha p nem nulla. hogy p nem nulla-e. jobban kifejezhetõ így: max = (a<=b) ? b : a. else max = a. hogy l<p->count teljesül-e.doksi. a kód könnyebben olvasható lesz tõlük. break. Például az if (a <= b) max = b. csak ha szükség van rá: if (p && 1<p->count) // . default: h().

A switch utasítás olvashatóbb olyan példáknál. default: cout << "Alapértelmezés: nincs ilyen eset\n".eset\n". hogy programhiba. mert a mûvelet természete (egy értéket állandók halmazával hasonlítunk össze) így világosabb. } Ha val==1-gyel hívjuk meg. else if (val == 2) g().1). case 2: cout << "2. Vigyázzunk arra. hogy a switch case-ét mindig fejezzük be valahogy. és jobb kódot is hozhatunk létre vele. amelyek nem maguktól értetõdõek.Forrás: http://www. Jó ötlet. hacsak nem akarjuk a végrehajtást a következõ case-nél folytatni. . eset 2. eset Alapértelmezés: nincs ilyen eset Ez az avatatlanokat nagy meglepetésként érheti. A jelentés ugyanaz.doksi. Vegyük a következõt: switch (val) { // vigyázat! case 1: cout << "1. amikor a case-ek közötti továbblépés szándékos.1. de a return is hasznos lehet (§6.hu 180 Alapok így is kifejezhetõ: if (val == 1) f(). else h(). eset\n". A case befejezésének leggyakoribb módja a break használata. így egy nem magyarázott továbblépésrõl feltételezhetjük. a következõket írja ki: 1. de az elsõ (switch) változatot részesítjük elõnyben. ha megjegyzésekkel látjuk el azon (ritka) eseteket.

Kifejezések és utasítások 181 6. A másik hagyományos és kézenfekvõ megoldás. Ha volna egy else ág az if utasításban.3. // hoppá! // . amit a feltétel értékével hasonlítunk össze. Így nem kerülhetünk olyan helyzetbe. break. ha a d-t a feltétel elõtt vezetjük be. d = 2. . // d két. egymástól független használata A változók feltételekben történõ megadásának nemcsak logikai haszna van. Így viszont nagyobb lesz a d használatának hatóköre. Nevezetesen. Vegyük a következõ példát: if (double d = prim(true)) { left /= d. ha a változót egy feltételben adjuk meg.1. amíg kezdeti értéket nem tudunk adni neki. Az említett két elv egyik legelegánsabb felhasználása.Forrás: http://www. mielõtt kezdeti értékét beállítottuk volna. amit a feltétel vezérel. Deklarációk feltételekben A véletlen hibás mûködés elkerülésére általában jó ötlet a változókat a legkisebb lehetséges hatókörben bevezetni. A feltételben lévõ deklarációnak egyetlen változót vagy konstanst kell megadnia és feltöltenie kezdõértékkel. A d hatóköre a deklaráció pontjától annak az utasításnak a végéig terjed.hu 6.doksi. d2 = d.2.. hogy a változót még azelõtt használjuk. } // .. } Itt d deklarált és kezdõértéket is kap. break... tömörebb forráskódot is eredményez.0. if (d = prim(true)) { left /= d.. // . kiterjedhet a kezdeti értékadás elé vagy a d szándékolt hasznos élettartama után is: double d. rendszerint legjobb elhalasztani egy ideális változó bevezetését addig.. a d hatóköre mindkét ágra kiterjedne.

A bemeneti ciklus egy olyan ciklusra példa. mielõtt a feltétel kiértékelõdik. módosítjuk a ciklusváltozót” fajtából való. Ahhoz azonban. hogy a ciklusmag megfelelõen mûködjön. A for utasítás szabályos ciklusok kifejezésére való. amelyben nincs magától értetõdõ ciklusváltozó: while(cin>>ch) // . goto. hacsak a felhasználó kifejezetten kilépésre nem kényszeríti egy break. Ha a kifejezést elhagyjuk.) { // . return. amíg a feltétel hamissá nem válik vagy a programozó más módon ki nem lép a ciklusból. Ha a feltételt elhagyjuk. a kezdõérték_meghatározó (inicializáló) utasítás üres is lehet. de a for is segíthet olyan ciklusok írásánál. } // "örökké" (végtelen ciklus) A while utasítás egyszerûen végrehajtja a ciklusmagot.4.1) meghívásával. hogy egy felté- . amíg feltétele hamissá nem válik. hogy a while-t részesítsem elõnyben a for-ral szemben. valamilyen feltételnek már az elsõ alkalommal is teljesülnie kell. Akkor hajlok arra. A vártnál sokkal gyakrabban vettem észre azt.. melyeknek nincs meghatározott leállási feltétele: for(.3. kifejezésnem kötelezõ ) utasítás Ezen utasítások mindegyike ismételten végrehajt egy utasítást (amit vezérelt (controlled) utasításnak vagy ciklusmagnak nevezünk).hu 182 Alapok 6. ha while utasítással fejezzük ki.doksi. a for utasítás örökké a ciklusban marad.3. vagy valami kevésbé egyszerû módon. Ha nem szükséges kezdeti értékadás. például az exit() (§9. A ciklusváltozót. és a ciklusváltozót módosító kifejezést egyetlen sorban írhatjuk le. a ciklusfeltételt. hogy a ciklusmag mindig végrehajtódik egyszer. for ( kezdõérték_meghatározó feltételnem kötelezõ ... Ciklusutasítások A ciklusokat for.. vagy throw utasítással. a ciklusmagban kell módosítanunk egy ciklusváltozót. Tapasztalatom szerint a do utasítás könnyen hibák és tévedések forrása lehet..1. általában jobb. ami nagyon megnövelheti az olvashatóságot és ezzel csökkentheti a hibák gyakoriságát.Forrás: http://www. amikor nincs magától értetõdõ ciklusváltozó vagy amikor a ciklusváltozó módosítása természetes módon a ciklusmag közepén történik. megvizsgáljuk a feltételt. while vagy do utasítással fejezhetjük ki: while ( feltétel ) utasítás do utasítás while ( kifejezés ) . Ha a ciklus nem az egyszerû „bevezetünk egy ciklusváltozót. Ennek az az oka.

amikor a kódot módosították. i<max.3. például valamilyen valós idejû alkalmazás belsõ ciklusában. §6.4.4. vagy késõbb. .).hu 6. ha a hatékonyság alapvetõ követelmény. 6. A goto akkor is hasznos lehet. azonosító : utasítás A goto az általános magasszintû programozásban kevés dologra használható. Ezenkívül jobban szeretem a feltételt „elöl. Következésképpen én magam próbálom elkerülni a do utasításokat. melyet egy kódkészítõ program (kódgenerátor) hozott létre valamilyen nyelvtan alapján.1. Ha ez deklaráció.3. Kifejezések és utasítások 183 tel nem úgy teljesült. ahogy az elvárható lett volna. ahol jól láthatom”.Forrás: http://www. használhatjuk például olyan elemzõben.3. 6. akkor az általa bevezetett változó (vagy változók) hatóköre a for utasítás végéig terjed: void f(int v[ ]. Goto A C++-ban megtalálható a hírhedt goto : goto azonosító . amikor a C++ kódot program és nem közvetlenül egy személy készíti. int max) { for (int i = 0. int j. i++) v[i] = i*i.3. vagy amikor a programot elõször megírták és tesztelték. de nagyon hasznos lehet. hogy kilépünk egy beágyazott ciklusból vagy switch utasításból (a break csak a legbelsõ ciklusból vagy switch utasításból lép ki): void f() { int i. A goto kevés értelmes használatának egyike a mindennapi kódban az. Deklarációk a for utasításban Változókat a for utasítás kezdõérték-adó részében adhatunk meg.doksi. } Ha az index végsõ értékét tudni kell a for ciklusból való kilépés után. a ciklusváltozót a cikluson kívül kell megadni (pl.

hu 184 Alapok for (i = 0. melyek érthetetlenek. found: // nm[i][j] == a } A ciklus végére ugró continue utasítás mûködésével a §6. j++) if (nm[i][j] == a) goto found. A megjegyzéseket számos módon lehet rosszul használni.1... félreérthetõek. 6. [2] a programmal összhangban álló és [3] idõszerû legyen. akkor tegyük azt.Forrás: http://www.5-ben foglalkoztunk. ami így nagymértékben rontja a program olvashatóságát. hogy egyiket a másikkal szemben elõnyben részesítsük (bár a legtöbb programozóhoz hasonlóan nekem is van választott stílusom – a könyv nyilván tükrözi is azt). vagy egyszerûen hibásak.4. A rossz megjegyzések rosszabbak. hogy biztosítsa azt. Ugyanez vonatkozik a megjegyzések stílusára is. hogy egy megjegyzés [1] értelmes. Számos behúzási stílus használatos és nem látok alapvetõ okot arra. ezért nincs mód arra. // nem található // . i<n. Számos program olyan megjegyzéseket tartalmaz. i++) for (j = 0. A fordítóprogram nem érti a megjegyzések tartalmát. Megjegyzések és behúzás A program olvasását és megértését sokkal kellemesebbé teheti. ha okosan használjuk a megjegyzéseket és a behúzást. Ez az észrevétel az ilyenfajta megjegyzésekre vonatkozik: // a "v" váltózónak kezdõértéket kell adni // a "v" változót csak az "f()" függvény használhatja // az "init()" függvényt minden más függvény elõtt meg kell hívni ebben a fájlban . Ha valamit leírhatunk magával a programnyelvvel.doksi. j<m. mint ha egyáltalán nem használnánk megjegyzést. ne megjegyzésben említsük meg.

ahol a kód nem nyilvánvaló és/vagy más rendszerre nem átültethetõ. Minden osztályhoz. a felhasznált algoritmust (ha az nem nyilvánvaló). Meg kell azonban jegyeznünk. Minden nem magától értetõdõ függvényhez van olyan megjegyzés. hogy az ilyen megjegyzések széleskörûen használatosak tanítási célokra az olyan programozási nyelvekrõl szóló könyvekben. mi a közös a fájlban levõ deklarációkban. Mihelyt valamit világosan leírtunk a nyelvvel. általános ötleteket ad a kód módosításával kapcsolatban stb. másodszor már nem kell megemlítenünk egy megjegyzésben: a = b+c. illetve konstanshoz van megjegyzés. 4. hogy hibásak. Van néhány megjegyzés ott. amely leírja a függvény célját. amiben egy könyvben lévõ program különbözik egy igazi programtól. amely leírja. mint amilyen ez is. ha alkalmazzuk az összeszerkesztési (§9. . Kifejezések és utasítások 185 // a "cleanup()" függvényt meg kell hívni a program végén // a "weird()" függvényt ne használjuk // az "f()" függvénynek két paramétert kell adni A C++ megfelelõ használata az ilyen megjegyzéseket általában szükségtelenné teszi. hogy mit feltételez környezetérõl. 2. // növeljük a számlálót Az ilyen megjegyzések még az egyszerûen feleslegeseknél is rosszabbak.hu 6. A fentieken kívül kevés megjegyzés van.4.doksi. gyakran összezavarják a program szerkezetét.Forrás: http://www. és lehet. Minden forrásfájlban van megjegyzés. A fentieket például kiválthatjuk. 5. 6. Ez az egyik. 3. Minden globális és névtér-változóhoz. Én a következõket szeretem: 1. mert növelik az elolvasandó szöveg hosszát. kezdõérték-adási és felszámolási szabályokat (§10. sablonhoz és névtérhez tartozik megjegyzés.2) vagy az osztályokra vonatkozó láthatósági.1). utal a segédanyagokra. // a-ból b+c lesz count++. és esetleg azt.

. hogy ha kizárólag a // megjegyzéseket használjuk egy függvényben. See Ralston: "A first course . Inc.3.. [3] Ha kétségeink vannak az operátorok precedenciájával kapcsolatban. [7] Kerüljük az olyan kifejezéseket. §6.8. §6.7. All rights reserved *******************************/ A jól megválasztott és jól megírt megjegyzések alapvetõ részét képezik a jó programnak.7.2.2.3.c: Implementation of the symbol table. [5] Ha explicit típuskonverzió szükséges. §6. Jegyezzük meg azt is. [4] Kerüljük a típuskényszerítést (cast).Forrás: http://www. §6. melyet érdemes mûvelni. melyek kiértékelési sorrendje nem meghatáro- .2.1. Olyan mûvészet.2. 6.8. Jó megjegyzéseket írni legalább olyan nehéz. részesítsük elõnyben a jobban definiált konverziós operátorokat a C stílusú átalakítással szemben.doksi." pg 411. [2] Kerüljük a bonyolult kifejezéseket. [6] Kizárólag jól meghatározott szerkezeteknél használjuk a T(e) jelölést. §6. /******************************* Copyright (c) 1997 AT&T. Tanácsok [1] Részesítsük elõnyben a standard könyvtárat a többi könyvtárral és a „kézzel írt kóddal” szemben.2.5. akkor ennek a függvénynek bármely részét megjegyzésbe tehetjük a /* */ jelöléssel (ez fordítva is igaz).hu 186 Alapok Például: // tbl. // swap() assumes the stack layout of an SGI R6000. /* */ Gaussian elimination with partial pivoting. §6. zárójelezzünk. mint megírni magát a programot.

Ezt írjuk át úgy.3. §6. §6.3. Tipp: §6.2)+3 a = . Kifejezések és utasítások 187 zott.2. amíg nincs érték. [9] Kerüljük a do utasítást. amivel feltölthetnénk. (*1) Írjuk meg a következõ for utasítással egyenértékû while utasítást: for (i=0.doksi.2.5 a = b == c ++ a=b=c=0 a[4][2] *= * b ? c : * d * 2 a-b.és érték-) párok sorozatát. i++) if (input_line[i] == '?') quest_count++.hu 6. §6. §6. (*1) Írjuk meg a bitenkénti logikai operátorok (§6. §6.3.4.6. §6. [8] Kerüljük a goto-t.1. [13] A globális operator new() helyettesítésére adjunk meg inkább egy operator new() tagot (§15.2. hogy a vizsgálat *p=='?' alakú legyen. [10] Ne adjunk meg változót addig.6). 6.3. Számítsuk ki és írjuk ki minden névre az értékek összegét és számtani közepét. (*2) Olvassuk be üreshellyel elválasztott (név.c=d 3.1. [14] Bemenet beolvasásakor mindig vegyük számításba a rosszul megadott bemenetet is.1.8. 4.3. ahol a név egyetlen üreshellyel elválasztott szó.1. §6. 2. Gyakorlatok 1.4) értéktáblázatát a 0 és 1 .3.4. §6. valamint az összes névre vonatkozó összeget és számtani közepet.2..Forrás: http://www.2. i<max_length.6. hogy ciklusváltozóként mutatót használunk. azaz úgy.2. §6. [12] Tartsunk fenn következetes stílust. §6. [11] A megjegyzéseket frissítsük rendszeresen.1 + + b -.4.3. az érték pedig egy egész vagy lebegõpontos érték.3.1. (*1) Zárójelezzük teljesen a következõ kifejezéseket: a = b + c * d << 2 & 8 a & 077 != 3 a == b || a == c && c < 5 c = x != 0 0 <= i < 7 f(1.

11. ami egy C stílusú karakterlánc hosszát adja vissza.. Ezután hasonlítsuk össze a függvényeket a standard könyvtárban lévõ változatokkal. ami egy karakterláncot másol egy másikba.2). melynek jelentése a nyelvi megvalósítástól függ (§C.2). amelynek két C stílusú karakterlánc paramétere van és egy olyan karakterláncot ad vissza. 8. (*1) Nézzük meg. (*2) Írjuk meg a rev() függvényt. (*2) Módosítsuk úgy a §6. ha nullával osztunk? Mi történik túlcsordulás és alulcsordulás esetén? 9.4. Gondoljuk meg. 13. (*2) Írjuk meg a következõ függvényeket: strlen(). (*2) Írjunk 5 kifejezést. 6. 12. ahogy azok a <cstring>-ben (<string.) Találjunk 5 különbözõ C++ szerkezetet..5) Mi történik a rendszerünkben.5.6[3] programot. if (a&077 == 0) // . amelynek egy karakterlánc paramétere van és . (*1) Adjunk 10 különbözõ példát nem hordozható C++ kódra. (*2) Írjuk meg a cat() függvényt. mi legyen a paraméterek és a visszatérési érték típusa.m *a[i] 10. hogyan reagál a fordítóprogram. } „Készítsünk” több egyszerû hibát és nézzük meg. (*1. (*1. és strcmp().doksi.Forrás: http://www. melynek jelentése nem meghatározott (§C.. a := b+1. hogy a középsõ értéket (medián) is kiszámítsa. (*1. 7.. Az eredményeknek foglaljunk helyet a new-val. mit csinál velük egy – de lehetõleg több – C++változat.h>-ban) szerepelnek és ahogy a §20.hu 188 Alapok operandusok összes lehetséges párosítására.5) Találjunk 5 különbözõ C++ szerkezetet. strcpy(). amely a paraméterek összefûzésébõl áll elõ. 5. int b) { if (a = 3) // . Hajtsuk õket végre és nézzük meg. melyek kiértékelési sorrendje nem meghatározott. ami két karakterláncot hasonlít össze. 14. (*1) Zárójelezzük teljesen a következõ kifejezéseket: *p++ *--p ++a-(int*)p->m *p. hogyan reagál a fordítóprogramunk ezekre a hibákra: void f(int a.1 pontban leírtuk azokat.

22. (*2) Írjuk meg az atoi(const char*) függvényt. hogy a felhasználó függvényeket adhasson meg a számológéphez. akkor arra külön jelölést kell kitalálnunk. A legtöbbet a „kis buta hibák” kijavításából fogunk tanulni. case 7: *to++ = *from++.doksi. int* from. 20.hu 6. az egyszerû. (*2) Írjunk egy olyan itoa(int i. case 4: *to++ = *from++. case 5: *to++ = *from++. 18. úgy. hogy kezelje a C++ oktális és hexadecimális jelölését is. A megjegyzéseket szándékosan töröltem. int count) // Duff programja.5) Alakítsuk át a számológépet. hogy egy felhasználói függvénynek paraméterei legyenek. tízes számrendszerbeli számokkal együtt. hogy a symbol szerkezetet használja és ne a statikus number_value és string_value változókat. char b[ ]) függvényt. Ezután olvassuk be és hajtsuk végre ezeket a mûveleteket. hogy már begépelt szövegrészeket használunk. (*2) Módosítsuk a számológépet. Azaz a rev(p) lefutása után p utolsó karaktere az elsõ lesz és így tovább. amely létrehozza b-ben i karakterlánc ábrázolását és visszaadja b-t. Ha azt akarjuk. 21. Például atoi("123") 123 lesz. } } 16. case 3: *to++ = *from++. (*3) Tegyük lehetõvé. hogy kezelje a C++ karakterkonstans jelölést is.5) Mit csinál a következõ példa és miért írna valaki ilyesmit? void send(int* to. case 2: *to++ = *from++. hogy kiírja a hibák sorának számát is.5) Írjunk olyan programot. Tipp: adjunk meg egy függvényt mûveletek sorozataként. 19. case 6: *to++ = *from++. Módosítsuk az atoi()-t úgy. Kifejezések és utasítások 189 megfordítja a benne lévõ karaktereket. 15. amely kiveszi a megjegyzéseket a C++ progra- . (*1. (*2) Gépeljük be teljes egészében a számológép példát és hozzuk mûködésbe. (*2. Módosítsuk a függvényt úgy is. 17. case 1: *to++ = *from++. Ne takarítsunk meg idõt azzal. } while (--n>0). { int n = (count+7)/8. amely egy számokat tartalmazó karakterláncot kap és visszaadja a megfelelõ egészet. Az ilyen sorozatokat karakterláncként vagy szimbólumok (tokenek) listájaként tárolhatjuk. amikor a függvény meghívódik. (*1. mintha a felhasználó gépelte volna be azokat. switch (count%8) { case 0: do { *to++ = *from++.Forrás: http://www.

Ne törõdjünk a hibás programokkal. majd írjuk ki az eredményt a cout-ra.hu 190 Alapok mokból. sokkal nehezebb feladat lenne). /* és */ használatától a megjegyzésekben. mind a /* */ megjegyzéseket. hogy elképzelésünk lehessen a mostanság használatos stílusok (behúzások. távolítsuk el mind a //. Ne törõdjünk azzal.doksi. elnevezések és megjegyzések) változatosságáról. . Azaz. 23. Óvakodjunk a //.Forrás: http://www. karakterláncokban és karakterkonstansokban. hogy a kimenet szép legyen (az egy másik. (*2) Nézzünk meg néhány programot. olvassunk a cin-rõl.

Rekurziót írni isteni. void exit(int).doksi.hu 7 Függvények „Ismételni emberi dolog. és a függvény definiciójával írjuk le. Függvénydeklarációk A C++-ban általában úgy végzünk el valamit. hogy elõzetesen nem deklaráltuk. A függvény deklarációja megadja a függvény nevét.” (L. const char* from). hogyan kell azt elvégezni. char* strcpy(char* to.Forrás: http://www. Peter Deutsch) Függvénydeklarációk és -definíciók • Paraméterátadás • Visszatérési értékek • Függvénytúlterhelés • A többértelmûség feloldása • Alapértelmezett paraméterek • stdargs • Függvényekre hivatkozó mutatók • Makrók • Tanácsok • Gyakorlatok 7. amelyeket át kell adni a függvény meghívásakor: Elem* next_elem().1. hogy meghívunk rá egy függvényt. A függvényt azonban nem hívhatjuk meg úgy. . visszatérési értékének típusát (ha van ilyen). és azon paraméterek számát és típusát.

ha szükséges: double sqrt(double). int*). int* q) { int t = *p.Forrás: http://www. Amint §4.1. const char*) { // a harmadik paraméter nem használatos } . amelyben megadjuk a függvény törzsét: extern void swap(int*. melyek segítik a program olvasóját. // sqrt() meghívása double(2) paraméterrel // hiba: sqrt() double típusú paramétert igényel Az ilyen ellenõrzés és konverzió jelentõségét nem szabad alábecsülni. A fordítóprogram ezeket egyszerûen nem veszi figyelembe.hu 192 Alapok A paraméterátadás ugyanazt a szerepet tölti be. A paraméterek nevei azonban nem részei a típusnak. double sr2 = sqrt(2). így nem kell azonosaknak lenniük. amely nem használja fel az összes paramétert: void search(table* t. A függvénydefiníció (függvény-meghatározás) olyan deklaráció. mint a kezdeti értékadás. double sq3 = sqrt("three"). const char* key. Függvénydefiníciók Minden függvényt. *p = *q. A függvénydeklaráció paraméterneveket is tartalmazhat.doksi. hogy a függvény nem ad vissza értéket. valahol (de csak egyszer) meg kell határoznunk. a void visszatérési típus azt jelenti. amit meghívunk a programban. A fordítóprogram ellenõrzi a paraméterek típusát és automatikus típuskonverziót végez.7-ben említettük. *q = t. Nem ritka az olyan függvénydefiníció. } // deklaráció // definíció A függvények definícióinak és deklarációinak ugyanazt a típust kell meghatározniuk. 7.1. void swap(int* p.

ahelyett. de a helyükön hagyjuk a paramétereket. Mindkét esetben azzal. Statikus változók A lokális (helyi) változók akkor kapnak kezdõértéket. ha a függvény kifejtése – és nem csak deklarációja – is a hatókörben szerepel (§9. } Az inline kulcsszó javaslat a fordítóprogram számára. Egy okos fordítóprogram a fac(6) meghívásakor létre tudja hozni a720 konstanst. az egyes függvényhívásoknak pedig saját másolatuk van a változóról.2. A változó csak egyszer kap értéket.hu 7. illetve a bemenettõl függõen magukat újrahívó vagy nem újrahívó helyben kifejtett függvények lehetõsége miatt nem biztos. akkor azt a függvény minden meghívásakor egyetlen. majd a szokásos függvényhívó eljárás szerint hívná meg. Függvények 193 Amint látjuk. Ha nem rendelkezünk kivételesen intelligens fordító. A névtelen paraméterek jellemzõen a program egyszerûsítése folytán vagy annak késõbbi bõvíthetõségét biztosítandó kerülnek a kódba. Alapértelmezés szerint ez a függvény minden meghívásakor megtörténik. a másik 6*fac(5)-öt. biztosítjuk.2). hogy elõbb létrehozná azt. 7. Az inline kulcsszó nem befolyásolja a függvény értelmezését. hogy egy paramétert nem használunk fel. hogy a fac() meghívásánál próbálja meg annak kódját a hívás sorában létrehozni. hogy nem nevezzük meg a paramétert. amikor a végrehajtás eléri annak elsõ definícióját: .Forrás: http://www. hogy egy inline függvény minden hívása a hívás sorában jön létre. amikor a végrehajtás elér a definiciójukhoz. így elõfordulhat.1. A függvényeket a fordító által a hívás sorában kifejtendõként (inline-ként) is megadhatjuk: inline int fac(int n) { return (n<2) ? 1 : n*fac(n-1). azt a tényt.doksi.2. a harmadik pedig a fac(6) nem helyben kifejtett hívást hozza létre. Ha egy lokális változót static-ként vezetünk be.1.) is – ugyanúgy egyedi címük van. Az egymást kölcsönösen meghívó (kölcsönösen rekurzív) helyben kifejtett függvények.és szerkesztõ-programmal. úgy jelölhetjük. hogy bár nem használjuk fel. a hívás sorában történõ létrehozást akkor biztosíthatjuk. hogy a függvényt meghívókat nem érintik a módosítások. Nevezetesen az ilyen függvényeknek – és static változóiknak (§7. állandó címû objektum jelöli majd. hogy az egyik 720-at. A fordítóprogramok intelligenciájának mértéke nem írható elõ.

doksi. Vegyük a következõ példát: void f(int val. Paraméterátadás Amikor egy függvény meghívódik. } int main() { f(3). int& ref) { val++. amelyet más függvények is elérhetnek és módosítással használhatatlanná tehetnek (lásd még §10. } . A paraméterátadás szerepe azonos a kezdeti értékátadáséval. hogy az aktuális paraméterek típusa megegyezik-e a formális paraméterek típusával. A fordítóprogram ellenõrzi.6) és alapértelmezett paraméterek (§7.1).Forrás: http://www. hogy globális változót vezetnének be. ref++. // egyszer kap kezdõértéket int x = 0. // 'a' alkalommal kap kezdõértéket (az f() minden meghívásakor) } cout << "n == " << n++ << ".2. x == " << x++ << '\n'. x == 0 n == 1. } A fenti a következõket írja ki: n == 0.hu 194 Alapok void f(int a) { while (a--) { static int n = 0.2. az egyes formális paraméterek pedig a megfelelõ valódi (aktuális) paraméter-értékkel töltõdnek fel. 7.4). x == 0 n == 2.2. a fordítóprogram a formális paraméterek számára tárterületet foglal le. A tömbök átadására egyedi szabályok vonatkoznak (§7. de van lehetõség nem ellenõrzött (§7.5) átadására is. x == 0 A statikus változók anélkül biztosítanak „emlékezetet” a függvénynek. és végrehajt minden szabványos és felhasználói típuskonverziót.

Forrás: http://www.1). Észrevehetõen hatékonyabb lehet.hu 7. val++ az elsõ aktuális paraméter helyi másolatát növeli. char* strcpy(char* to.2-et). hogy szándékunkban áll a változót módosítani: void g(Large& arg).j). // tételezzük fel. melyek módosítják a referencia szerint átadott paramétereiket. f(i. de i-t nem. const char*). hogy a paraméterátadás szerepe különbözik a (nem kezdeti) értékadásétól. nehezen olvashatóvá teszik a programot és általában kerülendõk (ellenben lásd §21. hogy a paraméter által mutatott objektum értékét a függvény nem változtatja meg: int strlen(const char*). Amint §5.4. Jegyezzük meg. // karakterek száma egy C stílusú // karakterláncban // C stílusú karakterlánc másolása // C stílusú karakterláncok összehasonlítása A const paraméterek használatának fontossága a program méretével együtt nõ. a referencia-paraméterek.3. hogy a hívott függvény módosíthassa az objektum értékét: void f(const Large& arg) { // "arg" értéke nem módosítható. Függvények 195 Amikor f() meghívódik. . és néhány felhasználói típusú paraméter esetében lényeges (§10.5-ben említettük. ref++ pedig a második aktuális paramétert. azok a függvények. ha egy nagy objektumot referencia. int j = 1.doksi. Ebben az esetben a paramétert megadhatjuk const-ként. int strcmp(const char*.4. Az alábbi void g() { int i = 1. nem pedig érték szerint adunk át. } j-t fogja növelni. a második (j) referencia szerint. csak hatékonysági okokból használunk referenciát és nem szeretnénk lehetõvé tenni. hogy g() módosítja arg-ot Hasonlóan. Ez a const paraméterek. const char* from). Az elsõ paraméter (i) érték szerint adódik át. hogy jelezzük. a const-ként megadott mutató paraméter azt jelzi az olvasónak. csak explicit típuskonverzióval } A referencia-paraméter deklarációjában a const elhagyása azt fejezi ki.

doksi. void g(double d. állandót és olyan paramétert. hogy egy ilyen paraméternek pontosan ugyanazokat az értékeket lehet adni.0f). } . át lehet adni const& paraméterként. void f() { char v[ ] = "egy tömb". } // Fortran stílusú sqrt referencia-paraméterrel // a 2. Mivel a const T& paraméterek konverziója megengedett.2. r = fsqrt(r). int j = strlen("Nicholas").0f). amelyek azonnal törlõdtek. // r-re hivatkozó referencia átadása update(d).0f-et tartalmazó ideiglenes változóra hivatkozó // referencia átadása // r-re hivatkozó referencia átadása // a float(d)-t tartalmazó ideiglenes változóra hivatkozó referencia // átadása Mivel a nem const referencia típusú paraméterek konverziója nem megengedett. // hiba: típuskonverzió szükséges } Ha ezek a hívások szabályosak lennének. // hiba: konstans paraméter update(r). a tömb elsõ elemére hivatkozó mutató adódik át: int strlen(const char*).hu 196 Alapok Literált. Tömb paraméterek Ha függvényparaméterként tömböt használunk. float r) { update(2. void g(double d) { float r = fsqrt(2. 7. azáltal. az ideiglenes változók bevezetésébõl adódó buta hibák elkerülhetõk: void update(float& i). az update() csendben módosította volna azokat az ideiglenes változókat. amely átalakítást igényel. Ez rendszerint kellemetlen meglepetésként érné a programozót. hogy az értéket ideiglenes változóban adjuk át (amennyiben ez szükséges): float fsqrt(const float&). biztosított. mint egy T típusú értéknek.Forrás: http://www. de nem const&-ként nem.1. r = fsqrt(d). int i = strlen(v).

"péntek". 7. vagyis a tömbök abban különböznek a többi típustól. "vasárnap" }. A C stílusú karakterláncok nulla végzõdésûek. mint a vector (§3. int size. Ebbõl az következik. Más tömböknél a méretet egy további paraméterrel adhatjuk meg: void compute1(int* vec_ptr. "szerda". a void függvények nem adhatnak vissza értéket: int f1() { } void f2() { } // hiba: nincs visszatérési érték // rendben . így méretük könnyen kiszámítható. hogy nem érték szerint adódnak át (ez nem is lehetséges). Visszatérési érték A main() kivételével(§3. de helyettük gyakran használhatunk mutatókból álló tömböket. Függvények 197 Azaz egy T[ ] típusú paraméter T* típusúvá lesz alakítva. Megfordítva. ha paraméterként adódik át. hogy tömbök helyett olyan típusokat használunk.Forrás: http://www. A tömb mérete nem hozzáférhetõ a hívott függvény számára. A többdimenziós tömbök némileg bonyolultabbak (lásd §C. }. "szombat". alacsonyszintû tömbök és mutatók helyett is használhatók.2) minden nem void-ként megadott függvénynek értéket kell visszaadnia.7). Ez bosszantó lehet.3.hu 7. de több mód van rá. "csütörtök".1. A vector és a hozzá hasonló típusok a beépített.3). struct Vec { int* ptr. int vec_size). a tömb paraméter is módosul. hogy a problémát megkerüljük. "kedd".doksi.7. // másik módszer // egyik módszer Választhatjuk azt is. void compute2(const Vec& v). hogy ha egy paraméterként alkalmazott tömb egy eleméhez értéket rendelünk. melyek nem igényelnek különleges bánásmódot: char* day[ ] = { "hétfõ". §16.

} Az önmagukat meghívó függvényeket rekurzív (újrahívó) függvényeknek nevezzük. hogy lokális változóra vonatkozó hivatkozást adtunk vissza. mint referenciát használó megfelelõje: int& fr() { int local = 1.. A return utasítást úgy tekintjük. */ return &local. return 1. mert a mutatott hely tartalma kiszámíthatatlan módon megváltozhat: int* fp() { int local = 1. } void f6() { return. hogy az egy visszatérési típusú. paramétereinek és lokális (automatikus) változóinak egy új másolata jön létre. } // rossz Ez a hiba kevésbé gyakori.hu 198 Alapok int f3() { return 1. amikor egy függvény meghívódik. ezért lokális változóra hivatkozó mutatót soha nem szabad visszaadni.doksi. } A paraméterátadáshoz hasonlóan a függvényérték visszaadásának szerepe is azonos a kezdeti értékadáséval. } // rossz Szerencsére a fordítóprogram általában figyelmeztet. A fordítóprogram összehasonlítja a return kifejezés típusát a visszatérési típussal és minden szabványos és felhasználói típusátalakítást végrehajt: double f() { return 1.. A tár a függvény visszatérése után ismét felhasználásra kerül. } void f4() { return 1. } int f5() { return. } // 1 automatikusan double(1)-gyé alakul Minden egyes alkalommal.. /* . } // rendben // hiba: visszatérési érték void függvényben // hiba: visszatérési érték hiányzik // rendben A visszatérési értéket a return utasítás határozza meg: int fac(int n) { return (n>1) ? n*fac(n-1) : 1. név nélküli változónak ad kezdõértéket.. Egy függvényben több return utasítás is lehet: int fac2(int n) { if (n > 1) return n*fac2(n-1). /* .Forrás: http://www. */ return local. .

Ez történik például a +. ez a kényelem alapvetõvé válik. a név. Ez a módszer a C++ alapmûveleteinél is használatos.hu 7. 7. A gondolatot a programozó által készített függvényekre is kiterjeszthetjük: void print(int).4. */ return g(p). hogy minden egyes f nevû függvény formális paramétereinek típusát összehasonítja az aktuális paraméterek típusával. void h(int* p) { /* . és mutató típusú értékek összeadására is.2). print. de a nyelv nem korlátozza és nem is segíti a programozót. hogy különbözõ típusokra vonatkozó mûveletekre ugyanazt a nevet használjuk. } // rendben: üres visszatérési érték Ez a fajta visszatérés olyan sablon (template) függvények írásánál fontos..doksi. az mégis használható egész. fejezet) esetében. // egész kiírása // C stílusú karakterlánc kiírása Ami a fordítóprogramot illeti. majd azt a függvényt hívja meg. Azt. 18. Ezért a túlterhelt függvénynevek elsõdlegesen jelölésbeli kényelmet adnak. mint az sqrt. lebegõpontos. csak egyetlen „név” van az összeadásra (+). és fordítási idejû hibát ad. Ez a kényelem az olyan hagyományos nevû függvényeknél igazán lényeges. és open.4. void print(const char*). következésképpen egy void függvény return utasításában szereplõ kifejezésként használhatunk void függvényt: void g(int* p). Ezt úgy teszi. az egyetlen. kényelmesebb lehet ugyanúgy elnevezni azokat. ahol a visszatérési típus egy sablonparaméter (lásd §18. amelynek paraméterei a legjobban illeszkednek. de amikor egyes függvények lényegében ugyanazt a mûveletet végzik különbözõ típusú objektumokon. a fordítóprogramnak ki kell találnia. Amikor a név jelentése fontos. Amikor egy f függvény meghívódik. ha nincs jól illeszkedõ függvény: .4. valamint az általánosított (generikus) programozás (§2.7.. Túlterhelt függvénynevek A legtöbb esetben jó ötlet különbözõ függvényeknek különbözõ neveket adni.Forrás: http://www.2. a konstruktorok (§11. melyik f nevû függvényt hívja meg. Függvények 199 A void függvények nem adhatnak vissza értéket.7). túlterhelésnek (overloading) nevezzük. Azaz. de meghívásuk nem is eredményez ilyet. ami közös az azonos nevû függvényekben. A függvények feltehetõen hasonlóak valamilyen értelemben. * és << operátorok.

Vegyük a következõ példát: void print(int).doksi. void print(double). hogy megkeresi azt a függvényt.6). Ahhoz.6).Forrás: http://www. illetve ezek unsigned megfelelõirõl int-re §C. } // print(long) // print(double) // hiba.0). int i. Szabványos konverziókat használó illeszkedés: int-rõl double-ra. char-ról int-re. double-ról intre. vagy T-t const T-re).1). 4. void print(long). A túlterhelést feloldó szabályok elsõsorban azért ennyire alaposan kidolgozottak. short s.hu 200 Alapok void print(double). Derived*-ról Base*-ra (§12. 3. void h(char c. mert figyelembe kellett venni a C és a C++ beépített numerikus típusokra vonatkozó bonyolult szabályait (§C. Kiterjesztést használó illeszkedés: csak egész értékre kiterjesztés (integral promotion) szükséges (bool-ról int-re. hogy mindez elvárásainknak (közelítõen) megfelelõ módon történjen. print(1). T*-ról void*-ra (§5. float f) { print(c). void print(long). Függvénydeklarációban három pontot (…) használó illeszkedés (§7. amelyiknél a hívás paraméter-kifejezésének típusa a legjobban illeszkedik a függvény formális paramétereire. Pontos illeszkedés: nincs szükség konverzióra vagy csak egyszerû konverziókat kell végezni (például tömb nevét mutatóra.4). // pontos illeszkedés: print(char) meghívása print(i). a hívást a fordító többértelmûként elutasítja. függvény nevét függvényre hivatkozó mutatóra. Ha azon a szinten. két illeszkedést is találunk. void print(char). vagy int-rõl unsigned int-re (§C.6). ahol elõször találunk megfelelõ illeszkedést. valamint float-ról double-ra. short-ról int-re. void print(const char*).6). 2. void f() { print(1L).6. többértelmû: print(long(1)) vagy print(double(1))? A fordítóprogram a túlterhelt függvények halmazából úgy választja ki a megfelelõ változatot. // pontos illeszkedés: print(int) meghívása . print(1. Felhasználói konverziókat használó illeszkedés (§11.2). 5. az alábbiakat kell megkísérelni (ebben a sorrendben): 1.

hogy a neveket helyesen használjuk. hogy viszonylag alacsony .7. // C stílusú karakterlánc void g(int i. char c. print_int(d). print(49). Ez azonban még mindig a kisebbik rossz. print_string(i). Túlterhelés nélkül több függvényt kellene megadnunk. Ez fárasztó lehet.2) irányuló erõfeszítéseket. így a programozó néha meglepõdhet azon.1). különbözõ nevekkel: void print_int(int). const char* p. print('a'). A túlterhelés viszonylag bonyolult szabályrendszeren alapul. nem biztonságos mûveletekkel szemben. Vegyük figyelembe a túlterhelés alternatíváját: gyakran hasonló mûveleteket kell végrehajtanunk többféle típusú objektumon. Függvények 201 print(s).3. A print('a') hívás a print(char)-t. mert 0 egy int. // kiterjesztés egésszé: print(int) meghívása // float kiterjesztése double-lé: print(double) // pontos illeszkedés: print(char) meghívása // pontos illeszkedés: print(int) meghívása // pontos illeszkedés: print(int) meghívása // pontos illeszkedés: print(const char*) meghívása } A print(0) hívás a print(int)-et hívja meg.hu 7. mert elõnyben akarjuk részesíteni a biztonságos kiterjesztéseket (például char-ról int-re) az olyan. // rendben print_int(c). mint az int-rõl char-ra történõ átalakítás. double d) { print_int(i). // rendben? print_int(int(c)) meghívása // rendben? print_char(char(i)) meghívása // hiba // rendben? print_int(int(d)) meghívása } A túlterhelt print()-hez képest több névre és arra is emlékeznünk kell. melyik függvény hívódik meg. meghiúsítja az általánosított programozásra (§2. print("a"). Az átalakítások (konverziók) és a kiterjesztések között azért teszünk különbséget. print(0). A túlterhelés feloldása független a szóba jöhetõ függvények deklarációs sorrendjétõl. print(f). és általában arra ösztönzi a programozót. print_char(i).doksi. // rendben print_char(c). mivel 'a' egy char (§4. void print_char(char).Forrás: http://www. // rendben print_string(p). void print_string(const char*).

2. Ez azért szükséges. fl = sqrt(fla). 7. hogy a fenti példában a négy „hibás” paraméterrel való hívás közül csak egyet vesz észre a fordítóprogram. double sqrt(double). többé nem lenne lehetséges. azzal melyik függvényt hívták meg. double d = sqrt(da). §11.2. A túlterhelés és a hatókör A különbözõ.1. ezen függvények paraméterein bármilyen szabványos konverziót elvégezhetünk. } // f(double) meghívása .4. nem névtér hatókörben megadott függvények nem túlterheltek: void f(int). } // sqrt(double) meghívása // sqrt(double) meghívása // sqrt(float) meghívása // sqrt(float) meghívása Ha a visszatérési típust a fordítóprogram figyelembe venné. float fla) { float fl = sqrt(da). hogy egy nem megfelelõ paramétert a fordítóprogram elutasít. hogy az egyes operátorokra (§11.1. d = sqrt(fla). void g() { void f(double). A túlterhelés és a visszatérési típus A túlterhelés feloldásánál a visszatérési típust nem vesszük figyelembe.4. f(1).Forrás: http://www. 7.2.4) vagy függvényhívásra vonatkozó feloldás környezetfüggetlen maradjon. hogy elszigetelten nézzük az sqrt() egy hívását és eldöntsük. Vegyük a következõt: float sqrt(float). Ebbõl az következik.hu 202 Alapok szintû típusokra irányítsa figyelmét. void f(double da. A túlterhelés növeli annak az esélyét. ami szintén hibákhoz vezethet.doksi. Mivel nincs túlterhelés.

2. de a hatókörben csak f(double) látható. // többértelmû: f2(char*) vagy f2(int*) } Ahol lehetséges.2.hu 7.doksi. a nem szándékos azonban meglepetéseket okozhat.4. // többértelmû: f1(char) vagy f1(long) f2(0). hogy a változatok azonos értelmûek legyenek. ha egy függvénynek túl sok (vagy túl kevés) túlterhelt változatát adjuk meg: void f1(char). A hívások feloldására típuskényszerítést is használhatunk: f2(static_cast<int*>(0)). amely feloldja a többértelmûséget: inline void f1(int n) { f1(long(n)). . Lásd még §8. Ha osztály-hatókörök (§15. using deklarációkat vagy using utasításokat használhatunk (§8. 7.2) között átnyúló túlterhelést szeretnénk. void f2(char*). hogy f(int) lett volna a legjobb illeszkedés f(1)-re.9.Forrás: http://www.2. mint egészet. a szándékos elfedés hasznos módszer lehet. hogy megkapjuk a kívánt viselkedést. hiszen a következõ – hamarosan bekövetkezõ – hasonló függvényhívással ismét foglalkoznunk kell majd.2) vagy névtér-hatókörök (§8.2). void k(int i) { f1(i).6-ot. void f1(long). Ez többnyire úgy oldható meg. Ez azonban általában csúnya és ideiglenes szükségmegoldás. A többértelmûség „kézi” feloldása Többértelmûséghez vezethet. void f2(int*). Az ilyen esetekben helyi deklarációkat adhatunk a kódhoz vagy törölhetjük azokat. } A fenti függvényváltozat hozzáadása feloldja az összes f1(i)-hez hasonló többértelmûséget a szélesebb long int típus javára. ha a függvénynek egy olyan új változatát adjuk hozzá az eddigiekhez. és gondoskodnunk kell róla. az ilyen esetekben úgy kell tekintsük egy függvény túlterhelt változatainak halmazát.2. Függvények 203 Világos. Mint mindig.3.

Az a függvény hívódik meg. complex pow(complex.int) második paraméterére pedig 2 a legjobb illeszkedés.2).int) meghívása // pow(complex. Több paraméter feloldása A túlterhelést feloldó szabályok alapján biztosíthatjuk.Forrás: http://www. .0. a hívást a fordító többértelmûként elutasítja: void g() { double d = pow(2. a tapasztaltabbak viszont becsülik ezeket az üzeneteket.0. complex). mert a pow (double. } // hiba: pow(int(2.0. a §7.int) meghívása // pow(double.2.complex) meghívása // pow(complex. amely az adott paraméterre a legjobban.double) meghívása // pow(double.2). hogy a legegyszerûbb algoritmus (függvény) lesz felhasználva. complex pow(double. a többire pedig jobban vagy ugyanúgy illeszkedik.z).z).double(2))? A függvényhívás azért többértelmû. complex z2 = pow(2. } // pow(int. double). amikor a számítások hatékonysága és pontossága jelentõsen különbözik a szóban forgó típusoknál: int pow(int. complex pow(complex.double) elsõ paraméterére 2.4.doksi. 7. complex pow(complex.0. double).4. double pow(double.2) vagy pow(2.0).4 szabályai alapján.hu 204 Alapok Néhány kezdõ C++ programozót bosszantanak a fordítóprogram által kiírt többértelmûségi hibák. complex). int). mert hasznos jelzõi a tervezési hibáknak. double d = pow(2.0). complex z3 = pow(z.2). int). void k(complex z) { int i = pow(2. a pow(int. Ha nem létezik ilyen függvény. complex z4 = pow(z.complex) meghívása A kettõ vagy több paraméterû túlterhelt függvények közötti választás folyamán minden paraméterre kiválasztódik a legjobban illeszkedõ függvény.

Függvények 205 7. char* =0). Az alapértelmezett paraméter típusának ellenõrzése a függvény deklarációjakor történik és a paraméter a függvény hívásakor értékelõdik ki. inline void print(int value) { print(value. void f() { print(31).Forrás: http://www. milyen számrendszerben írja ki a függvény az egészt. char* =0). // hiba . Ésszerûnek látszik megadni a felhasználónak a lehetõséget. Alapértelmezett paraméterek Egy általános függvénynek általában több paraméterre van szüksége. hogy az a szándékunk. Nevezetesen az objektumokat (§10. char*).5. int =0. Alapértelmezett értékeket csak a záró paramétereknek adhatunk: int f(int. print(31.hu 7. } // az alapértelmezett alap 10 ezt a kimenetet eredményezheti: 31 31 1f 11111 Az alapértelmezett (default) paraméter hatását elérhetjük túlterheléssel is: void print(int value.2. amely egy egészt ír ki. a legtöbb program azonban az egészeket tízes számrendszer szerint írja ki. mint amennyi az egyszerû esetek kezeléséhez kell.10). // hiba int h(int =0. hogy meghatározza.2). print(31. int base). int base =10).10). print(31. } A túlterhelés viszont kevésbé teszi nyilvánvalóvá az olvasó számára.16).doksi. Vegyünk egy függvényt. int =0.3) létrehozó függvények gyakran számos lehetõséget nyújtanak a rugalmasság érdekében. // rendben int g(int =0. hogy legyen egy egyszerû print függvényünk és egy rövidítésünk. int. Például a void print(int value.

hogy a * és az = közötti szóköz lényeges (a *= értékadó operátor. de lehet. Nem meghatározott számú paraméter Néhány függvény esetében nem határozható meg a hívásban elvárt paraméterek száma és típusa. hogy a név elfedi ugyanannak a névnek egy külsõ hatókörben levõ deklarációját. // . 7. §6. kereszt_nev).hu 206 Alapok Jegyezzük meg. hogy a C standard könyvtárának printf() függvénye (§21.. A fenti azt határozza meg. hogy a paraméter-deklarációk listáját a … jelöléssel zárjuk le.. ha egy nevet úgy adunk meg egy beágyazott hatókörben..6.2): int nasty(char*=0).3. Az ilyen függvényeket úgy adhatjuk meg. void g() { void f(int x = 9).5). . melynek jelentése „és talán néhány további paraméter”: int printf(const char* .2. printf("%d + %d = %d\n".doksi. printf("A nevem %s %s\n".). // szintaktikus hiba Az alapértelmezett paraméterek ugyanabban a hatókörben egy késõbbi deklarációval nem ismételhetõk meg és nem módosíthatók: void f(int x = 7). világ!\n").Forrás: http://www. void f(int = 8). } // hiba: az alapértelmezett paraméter nem adható meg kétszer // hiba: különbözõ alapértelmezett paraméterek // rendben: ez a deklaráció elfedi a külsõt Hibalehetõséget rejt magában. hogy van más paramétere is: printf("Helló.. vezetek_nev. void f(int = 7).8) meghívásakor legalább egy char* típusú paramétert vár.

melyekkel hozzáférhetünk az ilyen függvények nem meghatározott paramétereihez.h> int main() { printf("A nevem %s %s\n"..Forrás: http://www. amelynek van egy egész paramétere. hogy minden szót külön karakterlánc paraméterként adunk át. Függvények 207 Az ilyen függvények olyan adatokra támaszkodnak. lehetõvé téve. // a <cstdio> fejállományból // UNIX fejállományból A <cstdarg> fejállományban szabványos makrókat találunk. Elképzelésünk az. amelyek még nem használják ki a C++ által nyújtott újabb lehetõségeket: int fprintf(FILE*. Képzeljük el.2). } A fenti kódot a fordító lefordítja és (a legjobb esetben) furcsának látszó kimenetet hoz létre. a %d pedig azt. amely egyedi karaktersorozatokat tartalmaz.. amelyek nem elérhetõk a fordítóprogram számára. amikor olyan C könyvtári függvényekhez készítünk felületet. int execl(const char* . hogy a várt paraméterek tényleg ott vannak és megfelelõ típusúak-e: #include <stdio. hogy egy olyan hibafüggvényt írunk.doksi. const char* .hu 7.. A printf() esetében az elsõ paraméter egy formátum-vezérlõ. a programozó pedig nem feltétlenül ezt várja. A túlterhelt vagy alapértelmezett paramétereket használó függvények arra használhatók. Ezen paraméterek listáját egy char-ra hivatkozó „nullpointer” kell. hogy úgy hozzuk létre a hibaüzenetet. ha a paraméterek száma és típusa is változik. Egy jól megtervezett programban legfeljebb néhány olyan függvényre van szükség. hogy a printf() helyesen kezelje a többi paramétert: a %s például azt jelenti.). A három pont csak akkor szükséges. Ebben az esetben egy short vagy egy char int-ként adódik át. A fordítóprogram viszont általában nem tudhatja (és nem is biztosíthatja).).. egy float pedig double-ként. hogy lezárja: . a fordítóprogram nem fog elegendõ információval rendelkezni a szabványos típusellenõrzés és -konverzió elvégzéséhez. amikor az a paraméterek listáját értelmezi. (Próbáljuk ki!) Természetesen ha egy paraméter nem deklarált. ami a hiba súlyosságát jelzi. amikor a paraméterek típusát szükségbõl meghatározatlanul hagyjuk. hogy megoldják a típusellenõrzést a legtöbb olyan esetben. Leggyakrabban akkor használjuk. melynek paraméterei nem teljesen meghatározottak. hogy „várj egy char* paramétert”. és ezt tetszõleges hosszúságú (több karakterláncból álló) szöveg követi. hogy „várj egy int paramétert”.

error(1.Null_cp). } .Forrás: http://www.argv[0]. break. amikor a típusellenõrzést „elnyomja” a három pont.. } Az itoa() azt a karakterláncot adja vissza. case 2: error(0.argv[1]. Vegyük észre.) { // a "severity" (súlyosság) után nullával lezárt char*-ok // következnek // kezdeti paraméterek } char* p = va_arg(ap. default: char buffer[8].). } // .hu 208 Alapok extern void error(int . "with".argv[0]. cerr << '\n'. Null_cp). const char* Null_cp = 0. if (severity) exit(severity). amellyel a programozó szembenéz.argv[0].itoa(argc-1. amelyik a függvény egész típusú paraméterének felel meg. a kód nem lett volna hordozható: néhány nyelvi megvalósítás a nulla egészt és a nulla mutatót (nullpointer) nem azonos módon ábrázolja. if (p == 0) break.6.buffer). cerr << p << ' '.[17] int main(int argc.char*). break.. // paraméterek visszaállítása va_end(ap). for (.) { va_list ap.. hogy ha a 0 egész értéket használtuk volna befejezésként.doksi.severity).. va_start(ap.. // lásd §6.. char[ ]). extern char* itoa(int. A hibafüggvényt így adhatjuk meg: void error(int severity . Ez a példa szemlélteti a nehézségeket és azt a többletmunkát. char* argv[ ]) { switch (argc) { case 1: error(0.Null_cp).."arguments".

} // mutató függvényre // efct az error függvényre mutat // error meghívása efct-n keresztül A fordítóprogram rá fog jönni. mint a függvényeknél.doksi. 7. A programozónak minden egyes híváskor meg kell adnia egy típust. A va_start makró paraméterei a va_list neve és az utolsó formális paraméter neve. hogy a va_start() úgy módosíthatja a vermet. efct("error"). hogy efct egy mutató és meghívja az általa mutatott függvényt. hogy sorba rendezzük a nem megnevezett paramétereket. mint az &error-nak // rendben // ez is jó A függvényekre hivatkozó mutatók paramétereinek típusát ugyanúgy meg kell adnunk. Mielõtt még visszatérnénk egy olyan függvénybõl. ahol a va_start()-ot használtuk. void (*f2)(string) = error. hogy ilyen típusú aktuális paraméter került átadásra. A va_arg() makrót arra használjuk. (*f1)("Mary Rose").Forrás: http://www. a va_arg() feltételezi. void g() { f1("Vasa"). egy függvényre hivatkozó mutatót nem kötelezõ a * operátorral feloldanunk.. Függvények 209 Elõször meghatározzuk a va_list-et és a va_start() meghívásával értéket adunk neki. az így kapott mutatót használhatjuk arra. Azaz. Függvényre hivatkozó mutatók A függvényekkel csak két dolgot csinálhatunk: meghívhatjuk õket és felhasználhatjuk a címüket. Ennek az az oka.. meg kell hívnunk a va_end()-et. */ } void (*efct)(string).7. A mutatókat használó értékadásokban ügyelni kell a teljes függvény típusára: . jelentése ugyanaz. hogy a visszatérést nem lehet sikeresen véghezvinni. hogy meghívjuk a függvényt: void error(string s) { /* .hu 7. de általában nincs mód arra. Ugyanígy nem kötelezõ a & használata sem a függvény címének lekérdezésére: void (*f1)(string) = &error. } // rendben // ez is jó. A va_end() helyreállítja ezeket a módosításokat. hogy ezt biztosítani is tudja. Amikor a függvény címét vesszük. void f() { efct = &error.

pf("Héra"). void f1(string). pf = &f2. &close. pf = &f3. Gyakran kényelmes. // a <signal. hogy ne mindig a meglehetõsen nehezen érthetõ deklarációformát használjuk.hu 210 Alapok void (*pf)(string).h> fejállományból A függvényekre hivatkozó mutatókból álló tömbök gyakran hasznosak.doksi. // mutató void(string)-re // void(string) // int(string) // void(int*) // rendben // hiba: rossz visszatérési típus // hiba: rossz paramétertípus // rendben // hiba: rossz paramétertípus // hiba: void értékadás int-nek A paraméterátadás szabályai a közvetlen függvényhívások és a függvények mutatón keresztül történõ meghívása esetében ugyanazok. PF file_ops[ ] = { // fájlkezelés &open. . void f3(int*). &copy. PF edit_ops[ ] = { // szerkesztõmûveletek &cut. hogy a rendszert részletesen ismertessük. Íme egy példa egy UNIX-os rendszer-fejállományból: typedef void (*SIG_TYP)(int). pf(1). void f() { pf = &f1. typedef void (*SIG_ARG_TYP)(int). int f2(string). de az alapvetõ ötlet ez: typedef void (*PF)(). &search }. SIG_ARG_TYP). } int i = pf("Zeusz"). Itt nincs lehetõségünk. Az egér gombjaival kiválasztott menüpontokhoz kapcsolódó mûveleteket vezérlõ mutatókat így határozhatjuk meg és tölthetjük fel értékkel: PF* button2 = edit_ops. &paste. ha nevet adunk egy függvényre hivatkozó mutató típusnak. &write }.Forrás: http://www. SIG_TYP signal(int. Például az egeret használó szövegszerkesztõm menürendszere az egyes mûveleteket jelölõ függvényekre hivatkozó mutatókból összeállított tömbökkel van megvalósítva. PF* button3 = file_ops. &append.

const void*). // szükséges típuskényszerítés char* pj = b+j*sz. Az ilyen változásokat (részben) úgy hajtjuk végre. Függvények 211 A teljes megvalósításhoz több információra van szükség ahhoz. // button2 harmadik függvényének meghívása Akkor tudnánk igazán nagyra értékelni a függvényekre hivatkozó mutatók kifejezõerejét. Amikor a felhasználó kiválaszt egy menüpontot (például a 3-as elemet a 2-es gomb számára). hogy módosítjuk a gombokhoz kapcsolt mutatók értékét.6) nélkül. size_t sz. hogy új függvényeket teszünk a mûvelettáblába. Ahogy a rendszert használjuk. amelyik meghatározza a kiírandó szöveget. kötet. Egy menüt futási idõben úgy módosíthatunk. hogy minden menüelemet meghatározhassunk. 84. 0<=j.Forrás: http://www. a megfelelõ mûvelet hajtódik végre: button2[2](). Az elemek "sz" méretûek. de új menüket is könnyen létrehozhatunk. // &base[j] char* pjg = b+(j+gap)*sz.o. 0<gap. a virtuális függvények (§12. A függvényekre hivatkozó mutatók arra is használhatók. Például tárolnunk kell valahol azt a karakterláncot. 3. i<n. különbözõ típusú objektumra lehet alkalmazni – egyszerû formáját adják: typedef int (*CFT)(const void*. size_t n. pjg[k] = temp. k++) { char temp = pj[k]. CFT cmp) /* A "base" vektor "n" elemének rendezése növekvõ sorrendbe a "cmp" által mutatott összehasonlító függvény segítségével. k<sz.pj)<0) { // base[j] és base[j+gap] felcserélése for (int k=0. hogy a többalakú (polimorf) eljárások – azaz amelyeket több. pj[k] = pjg[k].2. } } } . void ssort(void* base. ha nélkülük próbálnánk ilyen kódot írni – és még jobban viselkedõ rokonaik.doksi. az egérgombok jelentése gyakran megváltozik a környezettel együtt. i++) for (int j=i-gap.) } for (int gap=n/2. // &base[j+gap] if (cmp(pjg. j-=gap) { char* b = static_cast<char*>(base). gap/=2) for (int i=gap. */ { Shell rendezés (Knuth.hu 7.

"Schryer N.static_cast<const User*>(q)->dept.".L. "bwk". és azt a függvényt. "ravi". const void* q) // nevek (name) összehasonlítása { return strcmp(static_cast<const User*>(p)->name.2). int dept.static_cast<const User*>(q)->name). melyet meg kell hívnia. "Szymanski T. vagy egyedi rendezõ eljárást használnak. } . Az ssort() típusát úgy választottuk meg. "dmr". az egyes elemek méretét. Az összehasonlító függvénynek negatív értéket kell visszaadnia. ha az elsõ paramétere kisebb. 11271.". }. hogy megegyezzen a szabványos C könyvtári qsort() rendezõ eljárás típusával. 11274. 11276 void print_id(User* v. nullát. mint a második. "Schryer N.7. "nls".1). a C++ standard könyvtárának sort algoritmusát (§18.name << '\t' << v[i]. char* id. hogy rendezni tudjunk. §13. hogy elvégezze az összehasonlítást.".dept << '\n'. "Sethi R. int n) { for (int i=0. "tgs". amelyeket rendez. const void* q) // osztályok (dept) összehasonlítása { return static_cast<const User*>(p)->dept . "nls". }. i<n.doksi. hogy ezt az algoritmust C++-ban írjuk le (lásd §13. ha paraméterei egyenlõek. de nem a legelegánsabb módja. csak az elemek számát (a tömb méretét). Egy ilyen rendezõ függvényt a következõképpen lehetne egy táblázat rendezésére használni: struct User { char* name. A valódi programok a qsort()-ot.hu 212 Alapok Az ssort() nem ismeri azoknak az objektumoknak a típusát.id << '\t' << v[i].M.W.5. i++) cout << v[i].3. egyéb esetben pedig pozitív számot: int cmp1(const void* p.L. } Elõször meg kell határoznunk a megfelelõ összehasonlító függvényeket. User heads[ ] = { "Ritchie D. 11275. "Kernighan B. 11272.".G. 11273. Ez a kódolási stílus gyakori C-ben.Forrás: http://www.". } int cmp2(const void* p.".

// void f(int) // int f(char) // hiba: nincs void f(char) Egy függvényt egy függvényre hivatkozó mutatón keresztül pontosan a megfelelõ paraméter.sizeof(User).sizeof(User). hogy a cmp3-at mytype* típusú paraméterekkel fogjuk meghívni (lásd még §9. Függvények 213 Ez a program rendez és kiír: int main() { cout << "Fõnökök ábécésorrendben:\n". } Egy túlterhelt függvény címét úgy használhatjuk fel. print_id(heads.Forrás: http://www.5-öt). hogy int cmp3(const mytype*. Ezen típusokra vonatkozóan nincs automatikus konverzió. hogy egy függvényre hivatkozó mutatóhoz rendeljük vagy annak kezdõértékül adjuk. cout << '\n'. Ebben az esetben a cél típusa alapján választunk a túlterhelt függvények halmazából: void f(int).és visszatérési típusokkal kell meghívni.6). Ha cmp3-at elfogadnánk az ssort paramétereként.2. . megszegnénk azt a vállalást.hu 7. void (*pf1)(int) = &f. int f(char).6.const mytype*).cmp1).cmp2). void (*pf3)(char) = &f.doksi. cout << "Fõnökök osztályok szerint:\n". ha függvényekre hivatkozó mutatókat adunk értékül vagy töltünk fel kezdõértékkel. ssort(heads. ssort(heads. print_id(heads. Ez azt jelenti.6. nem megfelelõ paraméter az ssort() számára. int (*pf2)(char) = &f.6).

Egy egyszerû makrót így adhatunk meg: #define NAME a sor maradék része ahol a NAME szimbólum elõfordul.y) argument1: x argument2: y Amikor MAC-ot használjuk. yuk yuk) így alakul át: expanded = argument1: foo bar argument2: yuk yuk . számíthatunk arra.8. Ezek x-et és y-t fogják helyettesíteni. amikor MAC behelyettesítõdik. vagy a programozó gyenge pontját mutatja. Így ha makrót használunk. mint a hibakeresõk. Makrók A makrók nagyon fontosak a C-ben. Kövessük azt a szokást. Például a named = NAME kifejezést a következõ váltja fel: named = a sor maradék része Megadhatunk paraméterekkel rendelkezõ makrót is: #define MAC(x. mielõtt a fordítóprogram látná azt. hogy az olyan eszközök.11 mutatja be. paraméterként meg kell adnunk két karakterláncot. Ha makrót kell használnunk. Majdnem minden makró a programozási nyelv. ha nem szükségesek. számos programozási eszköz számára komoly problémát jelentenek. olvassuk el figyelmesen C++-változatunk elõfordítójának (preprocessor) hivatkozási kézikönyvét és ne próbáljunk túl okosak lenni. Az elsõ makrókra vonatkozó szabály: ne használjuk õket. Például a expanded = MAC(foo bar. a program.Forrás: http://www.doksi. A makrók formai követelményeit az §A.hu 214 Alapok 7. Mivel átrendezik a programkódot. hogy a makrókat úgy nevezzük el. hogy sok nagybetû legyen bennük. de kevesebb a hasznuk a C++-ban. ott kicserélõdik a sor maradék részére. kereszthivatkozás-vizsgálók és hatékonyságvizsgálók gyengébb szolgáltatást fognak nyújtani.

141593 #define BEGIN { #define END } És néhány veszélyes makró: #define SQUARE(a) a*a #define INCR_xx (xx)++ Hogy lássuk.b. void f() { int xx = 0.Forrás: http://www. próbáljuk meg behelyettesíteni ezt: int xx = 0. Függvények 215 A makróneveket nem terhelhetjük túl és a makró-elõfordító rekurzív hívásokat sem tud kezelni: #define PRINT(a.doksi. } // globális számláló // lokális változó // y=xx+2*xx+2 vagyis y=xx+(2*xx)+2 // a lokális xx növelése .case #define FOREVER for(.c) cout<<(a)<<(b)<<(c) /* problémás?: újbóli definíció. így akkor jelzi a makróban lévõ esetleges hibát. nem túlterhelés */ #define FAC(n) (n>1)?n*FAC(n-1):1 /* problémás: rekurzív makró */ A makrók karakterláncokat kezelnek. amikor a makrót kifejtjük. keveset tudnak a C++ nyelvtanáról és semmit sem a C++ típusairól. amikor a makró behelyettesítõdik.b) cout<<(a)<<(b) #define PRINT(a.. A fordítóprogram csak a makró behelyettesített formáját látja. illetve a hatókörök szabályairól.) Néhány teljesen fölösleges makró: #define PI 3. miért veszélyesek. és nem akkor. INCR_xx.hu 7. ami nagyon homályos hibaüzenetekhez vezet. int y = SQUARE(xx+2). Íme néhány lehetséges makró: #define CASE break.

template. Továbbá a C elõfordító egy nagyon egyszerû makró-feldolgozó.b) (((a)<(b))?(a):(b)) Ha bonyolult makrókat kell írnunk. Ha valami nem magától értetõdõt akarunk csinálni. egyéni nyelvünket. az a legtöbb C++ programozó számára érthetetlen lesz.4) hivatkozunk. ha az utasítás elõtt szerepelt ilyen. nem ritka.cah)().b) a##b int NAME2(hack. akkor az vagy lehetetlennek. A const. ahol csak lehetséges: #define MIN(a. hogy X nevû makró nem lesz definiálva – akkor sem. és a makró paraméterek elõfordulásait tegyük zárójelbe. A #undef X utasítás biztosítja. Ez a következõt eredményezi a fordítóprogram számára: int hackcah(). de nem tudhatjuk. hogy egy kódrészletben mit feltételezzünk X hatásairól. használjuk a :: hatókör-jelzõt. enum és namespace megoldásokat arra szánták.Forrás: http://www.9. mert a C++ eszközök részeként néha C elõfordítókat használnak.hu 216 Alapok Ha makrót kell használnunk. amikor globális nevekre (§4. Ha azonban ezt a „kibõvített nyelvet” részesítjük elõnyben a sima C++-szal szemben. bölcs dolog /* */ megjegyzéseket használnunk. amelyek megjegyzésekre szorulnak. } Amikor makrót írunk. template<class T> inline T min(T a.doksi. hogy a hagyományos elõfordított szerkezeteket kiváltsák: const int answer = 42. ezek viszont nem ismerik a // jelölést: #define M2(a) something(a) /* értelmes megjegyzés */ Makrók használatával megtervezhetjük saját. T b) { return (a<b)?a:b. inline. hogy egy új névre van szükségünk valami számára. Ez bizonyos védelmet ad a nem kívánt makrók ellen. vagy szükségtelenül nehéznek bizonyulhat. Két karakterláncot a ## makróoperátorral összefûzve például új karakterláncot hozhatunk létre: #define NAME2(a. .

Ez az ártatlannak látszó forrásszöveg zavart fog okozni.Forrás: http://www.3-at. hogy feltételesen minden bemenetet figyelmen kívül hagyjon. Az #ifdef-et vezérlõ makrók neveit figyelmesen kell megválasztani.doksi. Az #ifdef azonosító direktíva arra utasítja a fordítóprogramot. amíg az #endif utasítással nem találkozik.int b #endif ). Például az int f(int a #ifdef arg_two . kevés kárt okoz. }. . Lásd még §9. Az #ifdef legtöbb felhasználása kevésbé bizarr.3.. és ha mérséklettel használják. amelyek ésszerû viselkedést tételeznek fel a programozóról. Node* arg_two. kódrészletbõl a fordítóprogram ennyit lát (kivéve ha az arg_two nevû makrót a #define elõfordító direktívával korábban definiáltuk): int f(int a ). Ez megzavarja azokat az eszközöket. hogy ne ütközzenek a szokásos azonosítókkal: struct Call_info { Node* arg_one. Feltételes fordítás A makrók egy bizonyos használatát majdnem lehetetlen elkerülni. ha valaki a következõt írja: #define arg_two x Sajnos a szokványos és elkerülhetetlenül beépítendõ fejállományok sok veszélyes és szükségtelen makrót tartalmaznak.hu 7. Függvények 217 7.8. // .1..

[4] Kerüljük a makrókat.3. [7] Akkor használjuk a túlterhelést.9. [8] Amikor egészekre vonatkozik a túlterhelés.8. 2.5. §7. ilyen függvényre hivatkozó mutató.5. ha azt akarjuk.4. 7. int). Tanácsok [1] Legyünk gyanakvóak a nem const referencia paraméterekkel kapcsolatban.Forrás: http://www. [9] Ha függvényre hivatkozó mutató használatát fontolgatjuk. Írjuk meg azt a függvényt. §7. használjunk függvényeket. (*1) Deklaráljuk a következõket: függvény. §7.5) vagy sablon (§2. §7.4. használjunk csúnya neveket. amelynek egy ilyen mutatójú paramétere van és visszatérési értékként paraméterét adja vissza. [3] Használjuk a const-ot széleskörûen. [2] Használjunk const referencia paramétereket. függvény.2. §7. hogy a függvény módosítsa paraméterét. [5] Kerüljük a nem meghatározott számú paraméterek használatát. ha a lehetõ legritkábbra kell csökkentenünk a paraméterek másolását.7. de következesen. ha a függvények elvben ugyanazt a mûveletet hajtják végre különbözõ típusokon. amelynek ilyen mutató paramétere van.8. §7. amelynek egy karakterre hivatkozó mutató és egy egészre mutató referencia paramétere van és nem ad vissza értéket.10.3. (*1) Mit jelent a következõ sor? Mire lehet jó? typedef int (&rifii) (int.7. Gyakorlatok 1. amely ilyen mutatót ad vissza. függvény. §5. [6] Ne adjunk vissza lokális változókra hivatkozó mutatókat vagy ilyen referenciákat. sok nagybetûvel.6. hogy egy virtuális függvény (§2.2) használata nem jobb megoldás-e. . §7. vizsgáljuk meg. §5.5. §7. hogy megszüntessük a többértelmûséget.doksi.hu 218 Alapok 7. [10] Ha makrókat kell használnunk. használjunk inkább mutatókat és érték szerinti visszaadást. Tipp: használjunk typedef-et.

Távolítsuk el a szükségtelen konverziókat. Módosítsuk a Tnode-ot. (*1. (*2) Írjuk újra az ssort()-ot (§7. const-ra vagy inline-ra. 7. Tnode* right. Írjunk függvényt. amely kétdimenziós tömböt invertál. alakítsunk át minden függvényt a C++ függvények formai követelményeinek megfelelõen. amely parancssori paraméterként adott. amit a szabad tár karaktertömbként tárol. Ha nincs megadva key (vagy a paraméter null-karakterlánc). Függvények 219 3. a program ne végezzen titkosítást. hogy (csak) egy mutatót tároljon. . 9. 6. Írjunk függvényt. Tnode* left. (*2. Módosítsuk ezt a programot úgy. világ!”-szerû programot. illetve delete-re.5) Írjunk olyan programot. a new segítségével.Forrás: http://www. }. ahol key egy karakterlánc. (*2. (*1. (*2) Írjunk titkosító programot. melyek nevei parancssori paraméterként vannak megadva.doksi. hogy a Tnode új definicióját használják. amely tetszõleges számú fájlt olvas be. int count. egyszerû titkosító sémát: c karakter titkosított formája legyen c^key[i]. ami egy tetszõlegesen hosszú szóra mutat. hogy minden meghívott függvény deklarálva legyen és határozzuk meg minden paraméter típusát.7) egy hatékonyabb rendezési algoritmus felhasználásával. Használhatjuk a következõ. Cseréljük ki a malloc() és free() hívásokat new-ra.hu 7. hogy megkapja a kimenetet. hogy tetszõleges számú név paramétere lehessen és mondjon hellót minden egyes névvel. ami a cin-rõl olvas és a kódolt karaktereket kiírja a cout-ra. ami parancssori paraméterként vesz egy nevet és kiírja. Ahol lehetséges. amely egy Tnode-okból álló fát úgy ír ki. és ha szükséges. hogy „Helló. Módosítsuk a függvényeket.7. (*2) Alakítsunk egy kis C programot C++ programmá. amellyel új szavakat tehetünk egy Tnode-okból álló fába. Tipp: qsort(). 8. és kiírja azokat egymás után a cout-ra.c fájlokból. 4. A program ciklikus módon használja a key-ben lévõ karaktereket. 5. név!”. Távolítsuk el az extern deklarációkat a . Mivel ez a program összefûzi a paramétereit. Módosítsuk a fejállományokat úgy. Tipp: §C. cseréljük ki a #define utasításokat enum-ra.5) Írjunk egy „Helló.5) Írjunk függvényt.5) Vegyük a következõt: struct Tnode { string word. amíg a teljes bemenetet el nem olvasta. amely kiír egy Tnode-okból álló fát. hogy a szavak ábécésorrendben vannak. elnevezhetjük cat-nek. Írjunk olyan függvényt.

Lásd még §11. egy hónapot. #define MAX(a. (*2) Adjunk hozzá a §6. (*3) Írjunk egy error nevû függvényt. Írjunk függvényt. %c és %d kifejezéseket tartalmazó. ami segít megfejteni a §7. anélkül.141593. ami megadja. mint az sqrt(). és egy évet adnak hozzá egy Date-hez.doksi. 16. amely %s. amelyek egy napot. Macmillan. amit módosíthatunk. . (*2) Írjunk függvényeket. hogy elképzelésünk lehessen a mostanság használatos nevek stílusának változatosságáról. o. mint amilyen az i és x? 14. 17. ami megadja egy adott Date-re következõ elsõ hétfõ Date-jét. 1967.5-bõl. (*1) Írjunk olyan faktoriális függvényt. hogy egy adott Date a hét melyik napjára esik. (*1) Mi a hiba ezekben a makrókban? #define PI = 3. Használjuk a <cstdarg>-ot. Nézzük meg a §21.hu 220 Alapok 10.10[9]-ben leírt módszerrel titkosított üzeneteket. %c és %d.1) tartalmaz egy szimbólumtáblát és egy lexikai elemzõt. mit jelent a %s. ahogy azt a §6. Elõször ne próbáljunk paraméterekkel rendelkezõ makrókat kezelni. (*3. 19.5) Írjunk programot. Olvassunk a cin-rõl és írjunk a cout-ra. Tipp: lásd David Kahn: The Codebreakers. printf stílusú. amely nem hívja meg önmagát. Írjunk olyan függvényt. (*2) Nézzünk meg néhány programot. a függvényeket pedig függvényre hivatkozó mutatókból álló tömbön keresztül hívjuk meg.b) a>b?a:b #define fac(a) (a)*fac((a)-1) 15.8-at. (*1) Hogyan választanánk meg a typedef használatával meghatározott függvényekre hivatkozó mutatótípusok neveit? 13. Ne felejtsük el ellenõrizni a függvényhívások paramétereit. Tipp: az asztali számológép (§6. amely egyszerû makrókat definiál és cserél ki (ahogy a C elõfordító teszi). ha nem tudjuk.1 pontban lévõ asztali számológéphez olyan függvényeket. hogy tudná a kulcsot. (*2) Írjuk meg magunk a print() függvényt a §7.6[13]-ban leírtuk.14[6]-ot. 18. log(). Tipp: adjuk meg elõre a neveket. formázott karakterláncokat vesz paraméterként és ezen kívül tetszõleges számú paramétere lehet. (*3) Írjunk makrófeldolgozót. Hogyan használják a nagybetûket? Hogyan használják az aláhúzást? Mikor használnak rövid neveket. 207-213. Ne használjuk a printf()-et.Forrás: http://www. és sin(). 11. New York. 12.

és a kiírást végzõ I/O rendszerre. Modulok és felületek Minden valóságos program különálló részekbõl áll.” (Robert Burton) Modulok. ami alól ne lenne valamilyen kivétel.Forrás: http://www. .?” (Monty Python) „Nincs olyan általános szabály.hu 8 Névterek és kivételek „– Ez a 787-es év! – I. világ!” program is legalább két részre osztható: a felhasználói kódra. felületek és kivételek • Névterek • using • using namespace • Névütközések feloldása • Nevek keresése • Névterek összefûzése • Névtér-álnevek • Névterek és C kód • Kivételek • throw és catch • A kivételek és a programok szerkezete • Tanácsok • Gyakorlatok 8. világ! kiírását kéri.1. ami a Helló.doksi. Még az egyszerû „Helló.sz.

Az egyszerûsítés kedvéért nem jelöltem.Forrás: http://www. érték) párokat tároló szimbólumtáblából 4. a vezérlõt és a hibakezelõt a teljesség miatt adtam hozzá. Láthatjuk. Az adatbeviteli függvény csak megvalósítja a felületében közzétett szolgáltatásokat. nem szükséges. a (karakterlánc. az adatbeviteli függvénybõl vagy lexikai elemzõbõl (lexer). ami a szintaktikai elemzést végzi.hu 222 Alapok Vegyük a számológép példáját a §6. Következésképpen különbséget teszünk a modul és a modul felülete (interfész) között. hogy mindegyik rész támaszkodik a hibakezelésre. és a hibakezelõbõl Ábrával: vezérlõ elemzõ adatbeviteli függvény szimbólumtábla hibakezelõ A fenti ábrában a nyíl jelentése: „felhasználja”. Amikor egy modul felhasznál egy másikat.1-bõl. A (szintaktikai) elemzõbõl (parser). Az igazat megvallva a számológépet három részbõl állóra terveztem. Ezt ábrával így mutathatjuk be: . a main() vezérlõbõl 5. ami a karakterekbõl szimbólumokat hoz létre 3.doksi. 2. hogy 5 részbõl áll: 1. hogy mindent tudjon a felhasznált modulról. nem pedig a teljes lexikai elemzõre támaszkodik. Ideális esetben a modulok legnagyobb része nem ismert a felhasználó elem számára. A szintaktikai elemzõ például közvetlenül csak az adatbeviteli függvény felületére.

az a feladatunk. hogyan használhatjuk a kivételeket arra. hogy ezen lépések mindegyikét végrehajtanánk. hatékony. A számológép kis program. Most csak azért használjuk ezeket. A tapasztalt programozó már az elején kiválaszthat egy „körülbelül megfelelõ” tervet. százait tartalmazza. hogy nagyobb programok esetében is hasznos módszereket mutassunk be. anélkül.Forrás: http://www. és könnyen módosítható lesz. hogy belefulladnánk a kódba. Amikor egy programot modulokra bontunk vagy egy programot modulokból hozunk létre. ügyelnünk kell arra. A valódi programokban minden modul. gyakran függvények. a „valódi életben” nem használnám olyan mértékben a névtereket és a külön fordítást (§2. hogyan rendezhetjük el úgy a program forrásszövegét. nem ritkák a drasztikus szerkezeti változtatások.doksi.1). hogyan ábrázolhatjuk a modulokat névterekként (§8. Ezért miután tárgyaltuk. Ez tekinthetõ a program valódi felépítésének. amelyet önálló névtér jelöl. osztályok. sablonok stb. Ahogy azonban a program az évek során fejlõdik. A nyelvi eszközök bõ választékának bemutatásához több lépésben bontom modulokra a számológépet. Az igazi programoknál nem valószínû.1. §9. Nekünk. a §9. hogy elkülönítsük a hibák észlelését és jelzését azok kezelésétõl. bemutatjuk. érthetõ. hogy a modularitást tovább javítsuk (§8.2).4. .3). hogyan lehet a számológép program logikai felépítését világosan kifejezni. A következõ részben bemutatjuk. mert közvetlenül fogja tükrözni eredeti elképzelésünket. hogy a hibakezelés okozta modulok közötti függõségekbõl minél kevesebb legyen. A C++ kivételeket nyújt arra a célra.3 pontban pedig azt. a kód egyszerû.hu 8. A hibakezelés mindenütt fontos szerepet tölt be a program szerkezetében. mint itt. hogy ezt hû módon adjuk vissza a kódban. hogy abból elõnyünk származzon. Ha ezt tesszük. programozóknak. Névterek és kivételek 223 vezérlõ szintaktikai elemzõ felülete lexikai elemzõ felülete szimbólumtábla felülete hibakezelõ szintaktikai elemzõ megvalósítása lexikai elemzõ megvalósítása szimbólumtábla megvalósítása A szaggatott vonalak jelentése: „megvalósítja”.

} Token_value get_token() { /* . ha egyes deklarációk valamilyen jellemzõ alapján összetartoznak.. kényelmes és hatékony kapcsolattartás biztosítása jelenti. */ } double term(bool get) { /* . hogy megtörjük a §6. MUL='*'. Programjainkat például részekre bonthatjuk párhuzamosan végrehajtott és egymással kapcsolatot tartó folyamatok segítségével is. */ } double expr(bool get) { /* . Token_value curr_tok.. LP='('.doksi. PLUS='+'.1) deklarációit például a Parser névtérbe tehetjük: namespace Parser { double expr(bool). hogy közös névtérbe helyezzük azokat. */ } } Az expr() függvényt elõször deklaráljuk és csak késõbb fejtjük ki. double prim(bool get) { /* .. */ } NUMBER. A számológép elemzõjének (§6.'. Azaz. amelyeket itt nem tárgyalunk. END. Érdekes módon minden rendszer könnyen modulokra bontható... Névterek A névterek (namespace) mindig valamilyen logikai csoportosítást fejeznek ki. A nehézséget a modulok közötti biztonságos. double number_value.1. string string_value. DIV='/'. 8. Ugyanígy az önálló címterek (address spaces) és a címterek közötti információs kapcsolat is olyan fontos témakörök.Forrás: http://www.1ben leírt függõségi kört. ASSIGN='='. Úgy gondolom. }..2. A számológép bemeneti részét szintén önálló névtérbe helyezhetjük: namespace Lexer { enum Token_value { NAME.. PRINT='. akkor ezt a tényt kifejezhetjük úgy is. MINUS='-'.hu 224 Alapok A modularitás fogalma sokkal több módon értelmezhetõ.1. RP=')' . a modularitás ezen megközelítései nagyrészt egymástól függetlenek és ellentétesek. mint ahogy ebben és a következõ fejezetben tesszük..

általában több oldalas (képernyõs) információn kell átrágnunk magunkat. amely a megvalósítást tartalmazza. és kifejthetjük késõbb. hogy megtaláljuk a felületet. double term(bool). Ezt nem tekintem jó megoldásnak. double expr(bool). ráadásul a felületet sokszor már a megvalósítás részleteinek kidolgozása elõtt megtervezik. ahol a felületet (interfész) elkülönítjük a megvalósítástól (implementáció): namespace Parser { double prim(bool).4-et). mire megtaláljuk. milyen szolgáltatások vannak felkínálva. A névtér tagjait a következõ jelölés használatával kell bevezetni: namespace névtér_név { // deklaráció és definíciók } .hu 8. Láthattuk. */ } double Parser::term(bool get) { /* . a szerkezet zavarossá vált volna.. A felületek meghatározása alapvetõ tervezési tevékenység (lásd §23.. Névterek és kivételek 225 A névterek ilyen használata elég nyilvánvalóvá teszi.4. mit nyújt a lexikai és a szintaktikai elemzõ a felhasználó programelemnek..Forrás: http://www. hogy a felület és a lényegi programrész szétválasztásának eredményeként most minden függvénynek pontosan egy deklarációja és egy definíciója van.doksi. Ha egy valóságos méretû névtér deklarációjába beletesszük a függvénytörzseket is.. hiszen egy modul a különbözõ programelemek számára különbözõ felületeket nyújthat. Íme a Parser egy olyan változata. Ha azonban a függvények forráskódját is a névterekbe helyeztem volna. */ } Vegyük észre. Külön meghatározott felületek helyett olyan eszközöket is biztosíthatunk.. A felhasználó programelemek csak a deklarációkat tartalmazó felületet fogják látni.3. azaz. hogy egy tagot megadhatunk a névtér meghatározásán belül. a névtér_neve::tag_neve jelölést használva.. amelyek kinyerik a felületet egy modulból. */ } double Parser::expr(bool get) { /* . } double Parser::prim(bool get) { /* . A program megvalósítását – ebben az esetben a függvénytörzseket – a felhasználó elem látókörén kívül helyezzük el.

1.doksi. hogy kifejezzük. a globális hatókörök és az osztályok maguk is névterek (§C. double Parser::prim(int).3). hogy könnyen meg lehessen találni minden nevet a névtérdeklarációban. 8. } // figyeljük meg a Parser:: minõsítõt // nem kell minõsítõ // figyeljük meg a Lexer:: minõsítõt // figyeljük meg a Lexer:: minõsítõt // nem kell minõsítõ } // . Minõsített nevek A névterek külön hatókört alkotnak. for (. Az általános hatókör-szabályok természetesen rájuk is vonatkoznak. hogy ez a term() az. így ha egy nevet elõzetesen a névtérben vagy egy körülvevõ blokkban adtunk meg. melyek neve a programban betöltött logikai szerepet jelzi. amelynek globálisnak kell lennie. amelyet a Parser-ben bevezettünk. A kivétel a main().hu 226 Alapok A névtérdefiníción kívül új tagot nem adhatunk meg minõsítõ formában: void Parser::logical(bool). illetve az eltérõ típusokból adódó hibákat észrevegyük: double Parser::trem(bool).. ha minõsítjük névterének nevével: double Parser::term(bool get) { double left = prim(get).. // hiba: nincs trem() a Parser névtérben // hiba: Parser::prim() logikai paraméterû A névtér (namespace) egyben hatókör (scope). minden további nehézség nélkül használhatjuk. // .. és nem valamilyen más globális függvény. annál hasznosabbak a névterek. Ezért – elméletileg – egy bonyolultabb program minden deklarációját önálló névterekbe kellene helyezni.2. Ideális esetben egy program minden eleme valamilyen felismerhetõ logikai egységhez (modulhoz) tartozik. Másik névtérbõl származó nevet viszont csak akkor használhatunk.Forrás: http://www. és hogy a gépelési.. Mivel a term() .10.3). hogy a futási idejû környezet felismerje (§8. A közönséges lokális hatókörök. hogy kifejezzék a program részeinek logikai elkülönítését. vagyis nagyon alapvetõ és viszonylag egyszerû fogalom. A Parser minõsítõre itt azért van szükség.. Minél nagyobb egy program.) switch (Lexer::curr_tok) { case Lexer::MUL: left *= prim(true).3. // hiba: nincs logical() a Parser névtérben A cél az.

// elemi szimbólumok kezelése // a Lexer get_token-jének használata // a Lexer curr_tok-jának használata // az Error error-jának használata . if (Lexer::get_token() == Lexer::ASSIGN) v = expr(true). Vegyük a következõt: double Parser::prim(bool get) { if (get) Lexer::get_token(). mivel a Lexer névtér tagjai nem tartoznak a Parser névtér hatókörébe. using Error::error. default: return Error::error("elemi szimbólum szükséges").hu 8. } case Lexer::END: return 1. a fordítóprogram a curr_tok változót úgy tekinti.2. // elemi szimbólumok kezelése } switch (Lexer::curr_tok) { case Lexer::NUMBER: // lebegõpontos konstans Lexer::get_token(). bosszantó lehet állandóan minõsíteni névterének nevével. 8. } A Lexer minõsítés ismételgetése igen fárasztó. hogy az ebben a hatókörben használt get_token a Lexer get_token-je: double Parser::prim(bool get) { using Lexer::get_token. return v.Forrás: http://www. using Lexer::curr_tok. mintha az nem deklarált lenne.doksi. Ha azonban a Lexer minõsítõt nem tesszük ki.2. return Lexer::number_value. } case Lexer::MINUS: // mínusz elõjel (egyoperandusú mínusz) return -prim(true). Névterek és kivételek 227 a Parser tagja. // ')' lenyelése return e. de ki lehet küszöbölni egy using deklarációval. nem kell minõsítenie a prim()-et. Using deklarációk Ha egy név gyakran használatos saját névterén kívül. if (Lexer::curr_tok != Lexer::RP) return Error::error(") szükséges"). case Lexer::NAME: { double& v = table[Lexer::string_value]. amellyel egy adott helyen kijelentjük. Lexer::get_token(). case Lexer::LP: { double e = expr(true).

get_token(). } case Lexer::END: return 1. if (get_token() == Lexer::ASSIGN) v = expr(true). default: return error("elemi szimbólum szükséges").Forrás: http://www. A mi esetünkben azonban az elemzõ minden függvénye ugyanazokat a neveket használja a többi modulból. } } A using direktíva egy lokális szinonímát vezet be. switch (curr_tok) { case Lexer::NUMBER: // lebegõpontos konstans get_token(). using Error::error. // ')' lenyelése return e. using Lexer::get_token. hogy elkerüljük a tévedéseket.doksi. double expr(bool). case Lexer::NAME: { double& v = table[Lexer::string_value]. A lokális szinonímákat általában célszerû a lehetõ legszûkebb hatókörrel használni. using Lexer::curr_tok. return Lexer::number_value.hu 228 Alapok if (get) get_token(). return v. így a using deklarációkat elhelyezhetjük a Parser névtér meghatározásában is: namespace Parser { double prim(bool). // a Lexer get_token-jének használata // a Lexer curr_tok-jának használata // az Error error-jának használata } . if (curr_tok != Lexer::RP) return error(") szükséges"). case Lexer::LP: { double e = expr(true). } case Lexer::MINUS: // mínusz elõjel return -prim(true). double term(bool).

2.3. double term(bool). 8. for (. hogy pontosan olyanok legyenek.2. A using direktíva majdnem ugyanúgy teszi elérhetõvé egy névtér neveit. break. hogy a lexikai szimbólumok (token. a Parser a Lexer-re támaszkodik. using namespace Error.doksi. } Azt is megtehetnénk.) // szorzás és osztás } switch (curr_tok) { case Lexer::MUL: left *= prim(true). ha célunk az. Azért hagyjuk õket minõsített alakban. hogy emlékeztessenek. Névterek és kivételek 229 Így a Parser függvényeit majdnem az eredeti változatukhoz (§6. hogy a Parser függvényeit annyira leegyszerûsítsük.1. double expr(bool).1) hasonlóra egyszerûsíthetjük: double Parser::term(bool get) { double left = prim(get). } return error("osztás 0-val"). kevésbé moduláris változatát névtereket használva alakítsuk át. Using direktívák Mit tehetünk. nyelvi egység) neveit a Parser névtérbe is bevezetjük.hu 8. default: return left. mint eredeti változataik? Egy nagy program esetében ésszerûnek tûnik. // a Lexer összes nevét elérhetõvé teszi // az Error összes nevét elérhetõvé teszi } . case Lexer::DIV: if (double d = prim(true)) { left /= d.Forrás: http://www. hogy egy elõzõ. mintha azokat a névterükön kívül vezettük volna be (§8. using namespace Lexer.. break.8): namespace Parser { double prim(bool).

} A using direktívák a névterekben más névterek beépítésére használhatók (§8. függvényekben jelölésbeli segítségként vehetõk biztonságosan igénybe (§8. Több felület használata Világos. hogy legyen: namespace Parser { double expr(bool). A Parser felülete a felhasználó elemek számára sokkal egyszerûbb kellene. 8.Forrás: http://www. hogy a Parser függvényeit pontosan úgy írjuk meg.1): double Parser::term(bool get) { double left = prim(get).8).2. } . case DIV: // a Lexer-beli DIV if (double d = prim(true)) { left /= d.2. // az Error-beli error default: return left. ahogy azt eredetileg tettük (§6.9).. break. for (.4. break.doksi.1).) // szorzás és osztás } switch (curr_tok) { // a Lexer-beli curr_tok case MUL: // a Lexer-beli MUL left *= prim(true).2. hogy a Parser számára létrehozott névtér nem a felület. A globális using direktívák a nyelv régebbi változatairól való átállásra szolgálnak (§8.hu 230 Alapok Ez lehetõvé teszi számunkra. egyébként jobb.3.1. amit a Parser a felhasználó programelem számára nyújt.3. } return error("osztás 0-val"). Inkább olyan deklarációhalmaznak tekinthetjük. ha kerüljük õket. ami az egyes elemzõ függvények kényelmes megírásához szükséges.

Névterek és kivételek 231 Szerencsére a két névtér-meghatározás együttesen létezhet. amit az elemzõ a felhasználó programelem rendelkezésére bocsát Ennek értelmében a main() vezérlõkód csak a következõt kell. így mindkettõ felhasználható ott. double expr(bool).Forrás: http://www. . ahol az a legmegfelelõbb. using Error::error.doksi. using Lexer::get_token. using Lexer::curr_tok. } // felhasználói felület Bármelyik felületet is találtuk a legjobbnak az elemzõ függvények közös környezetének ábrázolására.hu 8. // felület a megvalósításhoz } // a Lexer get_token-jének használata // a Lexer curr_tok-jának használata // az Error error-jának használata Ábrával: Parser' Parser Driver Parser megvalósítás A nyilak „a … által nyújtott felületen alapul” viszonyokat fejezik ki. a függvényeknek látniuk kell azt: namespace Parser { double prim(bool). Láthatjuk. double term(bool). hogy lássa: namespace Parser { double expr(bool). hogy a Parser névtér két dolgot nyújt: [1] Közös környezetet az elemzõt megvalósító függvények számára [2] Külsõ felületet.

hogy jelöljem. Az itt bemutatott megoldást használom a fizikai modularitás (§9. mint a felhasználók által látható felület. A felületek megtervezése az egyik legalapvetõbb tevékenység. megtehetnénk. A külön nevek hiánya nem okozhat zavart. de ha akarnánk. valamint hogy a fordítóprogram számára nem áll rendelkezésre elegendõ információ. Fontos. A kisebb felület könnyebben érthetõ rendszerhez vezet. bármitõl függhet.3) tárgyalására is. hogy a két felület neve nem különbözik. Szándékosan választottam.Forrás: http://www. hogy a lehetséges mértékig csökkentsék a programok különbözõ részei között fennálló függõségeket. ami abban a definícióban lett meghatározva”. akkor bármi. amikor nincsenek további logikai megszorítások (lásd még §8. A programozói felület nagyobb a felhasználóknak nyújtottnál. A fordítóprogram azonban rendszerint akkor is megpróbálja ellenõrizni az összefüggéseket.doksi. valójában mit próbálunk megvalósítani. melynek adatrejtési tulajdonságai jobbak.2. A két felület ábrázolására nem kell önálló névtereket használnunk. nem C++ azonosító.7-et). Felülettervezési módszerek A felületek célja az. könnyebben módosítható és gyorsabban lefordítható. fontos emlékeznünk arra. amin a fordítóprogram átsiklott. és mert a program fizikai elrendezése (lásd §9. mert a legtöbb definíció a legtöbb kód számára nem bír jelentõséggel.1. hogy ellenõrizze a névtér két definiciójának következetességét. ami az X pontban van leírva. 8. de kétélû fegyver. hogy a fordítóprogramok és a programozók az alábbi egyszerû hozzáállással viszonyulnak hozzájuk: „ha egy definíció az X pontról látható (a hatókörben van). és több megoldást is kipróbálni.3-at) természetesen különbözõ (fájl)neveket ad.hu 232 Alapok A Parser’ (Parser prime) a felhasználó programelemek számára nyújtott szûk felület. hogy a modulokat használó függvényeket (ebben az esetben a Parser-t használó main()-t) elkülönítsük az ilyen módosításoktól. Persze a helyzet általában nem ennyire rossz. Az itt bemutatott megoldás az általunk megtekintettek közül a legegyszerûbb és gyakran a legjobb. Legfõbb gyengéje. ha erre nincs mindig lehetõsége. sokkal gyakrabban változna.2. és ezt ajánlom arra az esetre is. Következésképpen érdemes végiggondolni. ennek a felületnek nincs külön neve a programban. Ha ez a felület egy valódi rendszer valóságos méretû moduljának felülete lenne. Amikor a függõségeket nézzük. Korábbi definícióinkat adottnak véve vegyük a következõt: .4. mert a programozók az egyes felületek számára különbözõ és maguktól értetõdõ neveket találnak ki. a szerkesztõprogram pedig észreveszi a legtöbb olyan hibát.

} namespace Parser_interface { using Parser::expr.hu 8. Mégis.. a következõt látjuk: . } int main() { // . // .. Következésképpen a valóságos méretû programok esetében a programozók és a fordítási rendszerek többnyire „biztosra mennek” és feltételezik.... double expr(bool). ott elõ is fordul. hogy a Parser_interface-t használó programelemek kizárólag – és csupán közvetett módon – a Parser::expr() függvénytõl függnek. Célunk ezért az. de idõre. hogy a lehetséges függõségek halmazát a valóban érvényben levõ függõségek halmazára szûkítjük. hogy ahol elõfordulhat függõség.. double expr(bool). van szükség ahhoz. gondolkodásra. // . hogy úgy fejezzük ki programunkat.. } // felület a megvalósításhoz // felület a felhasználóknak Nyilvánvaló. ami teljesen ésszerû megközelítés.. } // felület a megvalósításhoz A main() függvény csak a Parser::expr() függvénytõl függ. // . hogy erre rájöjjünk.doksi. ha egy pillantást vetünk a függõségek ábrájára...Forrás: http://www.. Parser::expr(false).. számolgatásra stb. Elõször megpróbáljuk a magától értetõdõt: a már meglévõ megvalósítási felület segítségével az elemzõ számára felhasználói felületet határozunk meg: namespace Parser { // . Névterek és kivételek 233 namespace Parser { // .

hogy a megvalósítási felületnek csak az elemzõ számára lényeges részét (ezt korábban Parser'-nek neveztük) tesszük láthatóvá ott. úgy.doksi.Forrás: http://www. } namespace Parser_interface { using Parser::expr. } // felület a felhasználóknak // eltérõ nevû felület a felhasználóknak Ábrával: Parser' Parser Parser_interface Driver Parser megvalósítás . ahol a Parser_interface-t meghatározzuk: namespace Parser { double expr(bool). így megszorítjuk a Parser_interface függõségét a Parser-tõl.hu 234 Alapok Parser Parser_interface Driver Parser megvalósítás Most a Driver (a vezérlõ) tûnik sebezhetõnek a Parser felület változásaival szemben. Még a függõség ilyen megjelenése sem kívánatos. jól elszigeteltük tõle. pedig azt hittük.

Ha akarnánk.Forrás: http://www. az egyetlen fordítási egységen dolgozó fordítóprogram helyett ismét a fordítási rendszer egészére támaszkodunk. Mindent kifejtettünk és megfelelõen elneveztünk. hogy kiegészül a Parser_interface névtérrel.4-ben szereplõtõl. Csak ott kell „láthatónak” lennie.hu 8. ahol a Parser_interface::expr() függvényt kifejtjük: double Parser_interface::expr(bool get) { return Parser::expr(get). hogy meghatározhassuk a Parser_interface-t. . Ez a megoldás csak abban különbözik a §8. ezt a megoldást a legtöbb esetben túlzónak találhatjuk. } Az utóbbi változatot ábrával így szemléltethetjük: Parser_interface Parser Parser_interface megvalósítás Driver Parser megvalósítás A függõségeket ezzel a lehetõ legkevesebbre csökkentettünk. Mégis. Névterek és kivételek 235 A Parser és a Parser' egységességét biztosítandó.doksi. } Most a Parser-nek nem kell a hatókörben lennie.2. a Parser_interface-t egy saját expr() függvénnyel konkrétan is ábrázolhatnánk: namespace Parser_interface { double expr(bool).

Ez gyakran fontos gyakorlati jelentõséggel bír. A névütközések elkerülése A névterek logikai szerkezetek kifejezésére valók. a nevek ütközni fognak.2.Forrás: http://www.1.. amikor két személy által írt kódot kell megkülönböztetnünk.2) vagy using direktívákat (§8. */ }. nem pedig az. A célunk az. igen nehéz lesz a programot különálló részekbõl létrehozni.. class String { /* .3) használunk. class String { /* . A kézenfekvõ megoldás. egy harmadik személy csak nehezen használhatja egyszerre a my. */ }. } namespace Your { char f(char). Ha csak egyetlen globális hatókört használunk.1). } Most már alkalmazhatjuk a My és a Your deklarációit.h: char f(char). */ }. */ }. int f(int).. így amikor egyetlen programban egyesítjük azokat. Vegyük a következõt: // my.2. hogy a kód helyileg maradjon érvényes.h-t is. ha minõsítõket (§8. 8. class String { /* . double f(double). A legegyszerûbb eset. // your. Ha a fentieket meghatározzuk. double f(double). hogy mindkét deklarációhalmazt saját. hogy felületet nyújtsunk a felhasználóknak: .5. külön névtérbe helyezzük: namespace My { char f(char).2. hogy az önállónak feltételezett részek mindegyike ugyanazokat a neveket használja. class String { /* . using deklarációkat (§8..5. pusztán azért.2...hu 236 Alapok 8. int f(int). Az a probléma merülhet fel. hogy védekezzünk a lehetséges névütközésekkel szemben. Névtelen névterek Gyakran hasznos deklarációk halmazát névtérbe helyezni..h-t és a your.2..doksi.h: char f(char).

*/ } } using namespace $$$.. */ } int g() { /* .Forrás: http://www.2. Ilyen esetben a névteret névtelenül hagyhatjuk: #include "header. hogy a Mine név „ismert” legyen az adott környezeten kívül is. nem érdemes olyan felesleges globális nevet kitalálni. Nevek keresése Egy T típusú paraméterrel rendelkezõ függvényt általában a T-vel azonos névtérben szokás megadni.. 8. nincs mód arra. Ahogy azt szerettük volna.hu 8.6.. . Névterek és kivételek 237 #include "header.h" namespace { int a.. akkor paramétereinek névterében fogjuk keresni: namespace Chrono { class Date { /* . amely véletlenül ütközhet valaki más neveivel. */ } int g() { /* . Következésképpen ha egy függvényt nem találunk meg használati környezetében.doksi. */ } int g() { /* . hogy egy névtelen névtér egy tagját egy másik fordítási egységbõl megnevezhessük... void f() { /* .. ahol a névteret meghatároztuk.. amely egyedi abban a hatókörben. void f() { /* .... A különbözõ fordítási egységekben lévõ névtelen névterek mindig különbözõek. void f() { /* . A névtelen névtérhez tartozik egy rejtett using direktíva is.. Az elõzõ deklaráció egyenértékû a következõvel: namespace $$$ { int a. hogy kell lennie valamilyen módszernek arra is.. */ } } Világos. */ }. */ } } Mivel nem akarjuk.h" namespace Mine { int a. Itt $$$ valamilyen név. hogy kívülrõl férhessünk hozzá egy névtelen névtér (unnamed namespace) tagjaihoz..

2. int i) { std::string s = format(d).3). } // Chrono::format() // hiba: a hatókörben nincs format() Ez a keresési szabály – a minõsítõk használatával ellentétben – sok gépeléstõl kíméli meg a programozót.13.2.hu 238 Alapok bool operator==(const Date&. Létezik egy std::operator==(). std::string s) { if (d == s) { // . // string ábrázolás } void f(Chrono::Date d. ezért a Chrono::operator==()-t használja. std::string format(const Date&). ahol a minõsítõk használata nagyon fárasztó lehet.. amelynek viszont van. std::string t = format(i).doksi. } } Az ilyen esetekben a függvényt a fordítóprogram a szokásos módon. ha elõbb bevezettük. } else if (d == "1914 augusztus 4") { // .2. hogy maga a névtér a hatókörben kell. Természetesen egy függvény több névtérbõl is kaphat paramétereket: void f(Chrono::Date d. .8. const std::string&).4) esetében. mint a using direktíva (§8. és a Chrono névtérben keresi. a hívás hatókörében. a fordító a d==s hívásnál az operator==-t az f()-et körülvevõ hatókörben. // ..4) és a sablonparaméterek (§C.. Alkalmazása különösen fontos az operátorok operandusai (§11. az (==-t stringekre meghatározó) std névtérben.Forrás: http://www. és minden talált függvényre elvégzi a túlterhelés feloldását (§7. de ennek nincs Date paramétere.4-et. és nem is „szennyezi” úgy a névteret. Nevezetesen..4). hogy legyen. Vegyük észre. illetve az egyes paraméterek névterében (beleértve a paraméterek osztályát és alaposztályát is) keresi... a függvényt pedig csak akkor találhatjuk meg és használhatjuk fel. Lásd még §11.

. valójában melyik könyvtárra gondol: namespace Lib = Foundation_library_v2r11. Lib::set s. // . 8.doksi. hogy „a könyvtárra” hivatkozzon és egyetlen deklarációban határozza meg. ha a hosszabb névtérneveknek rövid álneveket (alias) adunk: // használjunk névtér-álneveket a nevek rövidítésére: namespace ATT = American_Telephone_and_Telegraph.1.2. az osztály és bázisosztályának tagjai elõnyben részesülnek azokkal a függvényekkel szemben. Az operátoroknál más a helyzet (§11..4).7.2.2. A dilemmát úgy oldhatjuk fel.hu 8. } // rövid név. . Névtér-álnevek Ha a felhasználók névtereiknek rövid neveket adnak.Forrás: http://www. (elõbb-utóbb) ütközni fog A::String s1 = "Grieg". §11. Névterek és kivételek 239 Amikor egy osztálytag meghív egy névvel rendelkezõ függvényt. } // túl hosszú American_Telephone_and_Telegraph::String s3 = "Grieg". ATT::String s3 = "Grieg". a különbözõ névterek nevei könynyebben ütközhetnek: namespace A { // .... A::String s2 = "Nielsen". ATT::String s4 = "Nielsen".. Lib::String s5 = "Sibelius". Valódi kódban viszont általában nem célszerû hosszú névtérneveket használni: namespace American_Telephone_and_Telegraph { // . A névtér-álnevek azt is lehetõvé teszik a felhasználónak. melyeket a fordítóprogram a paraméterek típusa alapján talált. American_Telephone_and_Telegraph::String s4 = "Nielsen".

....2. Névterek összefûzése Egy felületet gyakran már létezõ felületekbõl akarunk létrehozni: namespace His_string { class String { /* . Azáltal. */ }. String operator+(const String&. String operator+(const String&..hu 240 Alapok Ez nagymértékben egyszerûsítheti a könyvtárak másik változatra történõ cseréjét. */ }. using namespace Her_vector.. } namespace Her_vector { template<class T> class Vector { /* . } namespace My_lib { using namespace His_string... void my_fct(String&)..Forrás: http://www. // . } // megtalálja a My_lib::His_string::String nevet .8. a (bármilyen típusú) álnevek túlzott használata zavart is okozhat.. Az újrafordítás észre fogja venni a forrásszintû összeférhetetlenségeket. // . void fill(char). } using namespace My_lib. void g(Vector<String>& vs) { // . a Lib álnév értékének módosításával és a program újrafordításával a „v3r02” változatra frissíthetjük a könyvtárat.. Másrészrõl. my_fct(vs[5]).. const String&).doksi. hogy közvetlenül Lib-et használunk a Foundation_library_v2r11 helyett. // . } Ennek alapján – a My_lib névteret használva – már megírhatjuk a programot: void f() { My_lib::String s = "Byron".. 8. // . const char*)..

.. Például megadhatnánk a His_string azon változatát..doksi.8.1) együtt – komoly támogatást nyújtanak ehhez. const String&). 3. és nem ró nagy jelölésbeli terhet a felhasználóra.Forrás: http://www. Névterek és kivételek 241 Ha az említett névtérben egy explicit módon minõsített név (mint a My_lib::String) nem bevezetett. // hiba: a My_lib-ben nincs megadva fill() // rendben: fill() szerepel a His_string-ben // rendben.2..2. logikailag összetartozó szolgáltatások halmazát fejezi ki. const char*).. } void My_lib::my_fct(String& v) { } // . Egy elem valódi névterét csak akkor kell tudnunk. */ }. 8. hogy egy névtérbõl csak néhány névhez akarunk hozzáférni.. Ezt meg tudnánk tenni úgy is. Kiválasztás Alkalmanként elõfordul. beépítési módszerek –az #includedal (§9. Az itt és a következõ részekben bemutatott összefûzési. amely csak magát a String-et és az összefûzõ operátort nyújtja: namespace His_string { // csak egy része a His_string-nek class String { /* . hogy olyan névtér-deklarációt írunk. a String jelentése My_lib::String. nem ad hozzáférést a nem kapcsolódó szolgáltatásokhoz. a fordító a nevet a using direktívákban szereplõ névterekben (például His_string) fogja keresni. ha valamit megakarunk határozni: void My_lib::fill(char c) { // .. 2. String operator+(const String&. } void His_string::fill(char c) { // . } . amely csak azokat a neveket tartalmazza.1.hu 8. ami // His_string::String Ideális esetben egy névtér 1. melyeket szeretnénk.. String operator+(const String&.

. Az adott névtérben szereplõ szolgáltatások kiválasztását jobban ki lehet fejezni using deklarációkkal: namespace My_string { using His_string::String. 8..Forrás: http://www. using namespace Her_lib.2. amelyre ez hatással van (lásd még §15. akkor ez a változtatás automatikusan hozzáférhetõ lesz a My_string-et használó elemek számára. */ }.. template<class T> class Vector { /* ..8. hogy feloldjuk az egybeépítésükbõl adódó névütközéseket és többértelmûségeket: namespace His_lib { class String { /* .2. így például egyetlen using deklarációval egy túlterhelt függvény összes változatát bevezethetjük..hu 242 Alapok Ez azonban könnyen zavarossá válhat.. hacsak nem mi vagyunk a His_string tervezõi vagy „karbantartói”. Összefûzés és kiválasztás A (using direktívákkal történõ) összefûzés és a (using deklarációkkal történõ) kiválasztás összekapcsolása azt a rugalmasságot eredményezi. // .. a fordítóprogram fel fogja ismerni a My_string minden olyan használatát. // minden a His_lib-bõl // minden a Her_lib-bõl .. */ }. } // bármelyik His_string-beli + használható A using deklaráció az adott név minden deklarációját a hatókörbe helyezi.doksi. */ }.. hogy egy tagfüggvényt vagy az összefûzõ mûvelet egy túlterhelt változatát adják a String-hez. Fordítva is igaz: ha a His_string-bõl eltávolítunk egy szolgáltatást vagy megváltozatjuk a His_string felületét.2). class String { /* . amelyre a legtöbb valódi programban szükségünk van. */ }. Így ha a His_string-et úgy módosítják. } namespace Her_lib { template<class T> class Vector { /* . } namespace My_lib { using namespace His_lib. Ezek révén úgy adhatunk hozzáférést különféle eszközökhöz.. A His_string „valódi” meghatározásának módosítása ebben a deklarációban nem fog tükrözõdni. using His_string::operator+..2. // ..

. */ }. */ }. Következésképpen a My_lib–et használó elemek számára a String és Vector nevek ütközését a fordítóprogram a His_lib::String és Her_lib::Vector javára fogja feloldani. hogy szerepel-e List a His_lib vagy Her_lib névtérben. kifejezetten megadott nevek (beleértve a using deklarációkkal megadottakat is) elõnyben részesülnek azokkal a nevekkel szemben. Névterek és kivételek 243 using His_lib::String. Továbbá a My_lib::List lesz használatos alapértelmezés szerint. using His_lib::String.2. 8.. using Her_lib::Vector. vagy egyszerûen jó. Néha azonban új névre van szükség. Hogyan használhatjuk a névtereket arra. ha van egy új nevünk: namespace Lib2 { using namespace His_lib. függetlenül attól.Forrás: http://www.hu 8.. using Her_lib::Vector. // az esetleges ütközések feloldása a His_lib javára // az esetleges ütközések feloldása a Her_lib javára // továbbiak } Amikor megvizsgálunk egy névteret. hogy csökkentsük az ilyen kódokban lévõ problémákat? A már létezõ kódok újraírása nem mindig járható út. */ }. a névtérben lévõ..... template<class T> class List { /* ..1-et). // továbbiak // .doksi. // . Szerencsére a C könyvtárakat úgy is . melyeket más hatókörökbõl tettünk hozzáférhetõvé a using direktívával (lásd még §C. } Az átnevezésre nincs külön nyelvi eljárás. amikor új névtérbe teszem azokat.. using namespace Her_lib.9.. Ily módon nem kell ugyanannak az elemnek két különbözõ nevére emlékeznem. // minden a His_lib-bõl // minden a Her_lib-bõl // az esetleges ütközések feloldása a His_lib javára // az esetleges ütközések feloldása a Her_lib javára // átnevezés template<class T> class His_vec // "átnevezés" : public His_lib::Vector<T> { /* . typedef Her_lib::String Her_string. Névterek és régi kódok Sok millió sor C és C++ kód támaszkodik globális nevekre és létezõ könyvtárakra. template<class T> class List { /* . Rendszerint jobban szeretem változatlanul hagyni a neveket.10. Ehelyett az új elemek meghatározására való általános módszerek használatosak.

doksi. Emiatt a névterekre vonatkozó nyelvi szabályokat úgy határozták meg. világ!\n")... ). } Ezt a programot nem lenne jó ötlet széttördelni. Az sem ésszerû.. hogy a nevek automatikusan hozzáférhetõk legyenek... a következõképpen: // stdio..2.1. hogy a lehetõ legcsekélyebb károkozással be lehessen azokat építeni a régebbi C++ programokba is. int printf(const char* . hogy viszonylag könnyedén lehessen egy névterek nélkül megírt program szerkezetét névterek használatával világosabban kifejezni. } using namespace std. másrészrõl viszont a névtereket úgy tervezték.h> int main() { printf("Helló. Ennek megvalósításához a kulcs a using direktíva. a cstdio-t: // cstdio: namespace std { // . mintha azokat egy névtérben deklarálták volna.h: namespace std { // .. ). int printf(const char* . Azoknak viszont.Forrás: http://www.9.. 8.. // . // . Névterek és a C Vegyük a hagyományosan elsõ C programot: #include <stdio.h C fejállományban lévõ szabványos bemeneti/kimeneti szolgáltatások deklarációi például egy névtérbe kerültek. akik nem akarják. A C++-ban írt könyvtárak esetében ez nem így van (§9..1). A stdio.. Tulajdonképpen erre példa a számológép program (§6. ha a szabványos könyvtárakat egyedi megoldásoknak tekintjük.. készítettünk egy új fejállományt is.2.4). Ez megõrzi a visszirányú kompatibilitást. } .hu 244 Alapok használhatjuk.

// .2. hogy a már meglévõ könyvtárakat a forráskód lehetõ legkisebb módosításával fejleszthessük névtereket használóvá.2. sokkal világosabban ki lehet fejezni minõsítésekkel és using deklarációkkal..9.hu 8. A névterek és az összeszerkesztés közötti kapcsolatot a §9.h-ból Ezt a programot anélkül alakíthatjuk névtereket használó változatra. Névterek és túlterhelés A túlterhelés (§7. // old user. hogy a tényleges programkódot megváltoztatnánk: . 8.h: void f(char).h" void g() { } f('a'). Névterek és kivételek 245 A C++ standard könyvtárának azon felhasználói.h: void f(int). Ez alapvetõ ahhoz.. Például: // old A.h: #include<cstdio> using namespace std. hogy beleveszik a cstdio-t: // stdio. amely más névtérben lévõ nevekre hivatkozik.h-t természetesen úgy fogják meghatározni.c: #include "A. A using direktívákat elsõdlegesen a nyelv régebbi változatairól való átállást segítõ eszközöknek tekintem. a stdio. A legtöbb olyan kódot.4 részben tárgyaljuk. // f()-et hívja B. // old B.h" #include "B. akik aggódnak a deklarációk másolása miatt.Forrás: http://www.4) névtereken keresztül mûködik... // .doksi.2.

azaz számos névtér deklarációjából adhatunk hozzájuk neveket: namespace A { int f().hu 246 Alapok // new A.. Hogy ezt megtehessük. // .9. } // new B. a névtér-meghatározásokat szét kell osztanunk számos fejállomány és forrásfájl kö- . ahogy egy régebbi könyvtár vagy alkalmazás élt az egyetlen globális névtéren belül. } namespace A { int g().h" using namespace A.h" #include "B.3.c: #include "A. } // most f() az A tagja // most A két tagja f() és g() Ezáltal úgy hozhatunk létre egyetlen névtéren belül lévõ nagy programrészeket.2.doksi..Forrás: http://www. using namespace B. // f()-et hívja B.c-t. a using direktívákat a fejállományokba tettük volna.. // . } // new user. void g() { } f('a')..h: namespace A { void f(int).h-ból Ha teljesen változatlanul akartuk volna hagyni a user. 8.h: namespace B { void f(char). A névterek nyitottak A névterek nyitottak.

.hu 8. Mivel azonban egy névtéren belül új függvényeket is meghatározhatunk.) mutattuk. // saját függvény // . Amikor egy névtér elõzetesen bevezetett tagját kifejtjük. hogy a deklarációk sorrendjét megváltoztatnánk: // saját fejállomány: namespace Mine { void f(). Névterek és kivételek 247 zött. // saját függvény // . hogy egy adott névtér különbözõ részeit mutatjuk meg nekik. hogy a különbözõ programelemeknek különbözõ felületeket nyújtsunk azáltal.h> namespace Mine { int g().. // saját függvény // ... #include<stdio. // saját függvény // ..4. jobban szeretek sok kisebb névteret használni (lásd §8. Ez a nyitottság szintén a nyelv régebbi változatairól való átállást segíti...h> int g(). mint igazán nagy programrészeket egyetlen névtérbe rakni.. Ez azonban gyakran kivitelezhetetlen. a fordítóprogram a fentivel azonos jellegû hibát az újra megnyitott névterekben már nem érzékeli: .Forrás: http://www. } Amikor új kódot írok.2. Például a // saját fejállomány: void f(). } // hiba: nincs ff() megadva Mine-ban A fordítóprogram ezt a hibát észreveszi. ha nagyobb programrészeket alakítunk át névtereket használó változatra.. a névterek nyitottsága lehetõvé teszi számunkra. biztonságosabb a Mine:: utasításformát használni ahelyett. Ahogy azt a számológép példájában (§8. hogy újra megnyitnánk a Mine-t: void Mine::ff() { // .8).. újraírható anélkül.2. } #include<stdio.doksi.

hogyan szeretné a vezérlõ kezelni a hibákat...hu 248 Alapok namespace Mine { // Mine újra megnyitása függvények meghatározásához void ff() { } } // . hogyan birkózzon meg a hibákkal.7). hogy nincs lehetõségünk a számológép egészét megtervezni és nem akarjuk. Amikor azonban a számológép logikai részeit különbözõ névterekre bontjuk szét. A könyvtárt felhasználó programelem tudhatná. miután hiba történt. melyeket nem lehet helyben megszüntetni. Tegyük fel.Forrás: http://www. hogy nem egy új ff() függvényt akartunk meghatározni. a hibakezelést két különálló részre kell szétválasztanunk: 1. hogy a program egészét egyszerre terveztük meg. 8.. de általában nem tud mit kezdeni velük. hogy a elemzõt és a többi részt úgy írták meg. hogy minden modul megfelelõen viselkedik.. hogy az Error és a többi modul között szoros legyen a kapcsolat. // hoppá! nincs ff() megadva Mine-ban. Kivételek Ha egy program különálló modulokból áll – különösen ha ezek külön fejlesztett könyvtárakból származnak –. hogy minden névtér függ az Error névtértõl (§8. A meghatározásokban szereplõ nevek minõsítésére használhatunk névtér-álneveket (§8.doksi. A könyvtár létrehozója felismerheti a futási idejû hibákat. látjuk.3. az Error-ban lévõ hibakezelõ pedig arra támaszkodik. illetve a máshol észlelt hibák kezelésére. az olyan hibaesemények jelzésére.2. A fordítóprogram nem tudhatja. A számológép példájában ezt a problémát azzal kerültük ki. ezáltal beilleszthettük a hibakezelést a teljes szerkezetbe.2). hogy nem tudták. Ehelyett tegyük fel. de nem képes észlelni azokat – máskülönben a felhasználó kódjában szerepelnének a hibákat kezelõ eljárások és nem a könyvtár találná meg azokat. de az adott névtér újbóli megnyitására nem.2. 2. ezzel a definícióval adjuk hozzá // . .

vagy Range_error kivételt vált ki. } }. amit külön fejlesztett könyvtárakból hoztunk létre.2 A to_char() függvény vagy az i számértékét adja vissza karakterként. hogy ha egy függvény olyan problémát talál. throw). double error(const char* s) { std::cerr << "hiba: " << s << '\n'. return i. amellyel nem képes megbirkózni. Ha egy függvény képes erre.doksi. hogyan lehet meghívni. §10. hogy (közvetett vagy közvetlen) meghívója képes kezelni a problémát. magában foglalt egy hibakezelési módszert: namespace Error { int no_of_errors.3) char to_char(int i) { if (i<numeric_limits<char>::min() || numeric_limits<char>::max()<i) throw Range_error(i). 8. ahogy a számológép példájában lennének használatosak. fejezet átfogóbban tárgyalja a kivételeket és azok használatát. Névterek és kivételek 249 Bár az error() nagyon egyszerû volt.Forrás: http://www. abban a környezetben. illetve mit várhat tõle. kivételt vált ki („kivételt dob”. A hibajelzés és a hibakezelés szétválasztására szánt C++ eszköz a kivétel.hu 8. A 14. Az alapgondolat az. hogy a program minden része tudjon az error() létezésérõl és arról. return 1. hogy folytassa a számolást. olyan alapértelmezett értéket ad.2. } } Az error() függvény egy hibaüzenetet ír ki. „Dobás és elkapás” A kivételeket (exception) arra találták ki. Ebben a részben röviden leírjuk a kivételeket. mely lehetõvé teszi a hívó számára. Fontos. és egy egyszerû hibaállapotot követ nyomon. je- . Range_error(int ii) { i = ii.3. Ez túl sok feltétel lenne egy olyan program esetében. } // lásd §22.1.5. azt remélve. // konstruktor (§2. no_of_errors++.2. hogy segítsenek megoldani a hibák jelzését: struct Range_error { int i.

} } Ha bármilyen try blokkban szereplõ vagy onnan meghívott kód kivételt vált ki. Nem kötelezõ. a következõt írhatjuk: void g(int i) { try { char c = to_char(i). amit esetleg kiválthat. } catch (Range_error) { cerr << "hoppá\n".doksi. a try blokk kezelõit kell megvizsgálni. . } catch (Range_error x) { cerr << "hoppá: to_char(" << x... hogy el akarja kapni (catch) azokat a kivételeket. A zárójelek olyan deklarációt tartalmaznak. A catch szintén kulcsszó.. melyek típusa megegyezik a probléma jelzésére használt típussal. ahogy a függvényparamétereket nevezzük meg: void h(int i) { try { char c = to_char(i).i << ")\n". melyet a kezelõ elkaphat. */ ) { // . } } A catch ( /* .. Ahhoz például. Ha a kivétel típusa megegyezik a kezelõnek megadott típussal...Forrás: http://www. akkor pontosan úgy adhatunk nevet a catch paraméterének. // . amit a try kulcsszó elõz meg. de megnevezheti az elkapott objektumot is. } szerkezetet kivételkezelõnek (exception handler) nevezzük. Csak közvetlenül olyan blokk után használható. A deklaráció határozza meg azon objektum típusát.hu 250 Alapok lezheti. // . Ha például meg akarjuk tudni a kiváltott Range_error értékét. hogy meghívjuk a to_char()-t és elkapjuk azt a kivételt.. vagy közvetlenül egy másik kivételkezelõ után. amely a függvényparaméterek deklarációjához hasonló módon használatos..

2.hu 8. a végrehajtás a kezelõk listájának végétõl folytatódik: . fejezetben tárgyaljuk.1. mint egy közönséges blokk. 8. Én a kivételkezelés céljára külön típusokat szoktam megadni. hogy megtaláljam a más célra használt int kivételeket. mint amilyen az int. Beépített típusokat. A kezelõnek nem kell értéket átadni abból a kódból. Ahol szükséges. a kivételkezelõket figyelmen kívül hagyjuk és a try blokk úgy viselkedik. hogy az int egy efféle eltérõ használata nem okoz-e zavart az én kódomban.7). §10. } }. A kivételek megkülönböztetése Egy program futásakor általában számos hiba léphet fel.1) kétfajta futási idejû hibát kell kezelnie: a formai követelmények megsértését és a nullával való osztás kísérletét. Számológépünknek (§6.2) használatát váltja fel. amelyik felismerte a nullával való osztás kísérletét. A kényelmesebb jelölés végett a szerkezethez hozzáadtam egy konstruktort (§2.5. Egy nagy programban nem lenne hatékony mód arra.2. Ez a lehetõ legkisebbre csökkenti a céljukkal kapcsolatos zavart. „jól viselkedõ” eljárásra. A C programozók úgy gondolhatnak a kivételkezelésre. Másrészt a kezelõ a nyelvi hibákról bizonyára szeretne jelzést kapni. viszont sohasem használok kivételként. Itt egy karakterláncot adunk át: struct Syntax_error { const char* p.2.Forrás: http://www. Syntax_error(const char* q) { p = q. melyeket különbözõ nevû kivételeknek feleltethetünk meg. így a nullával való osztást egy egyszerû üres típussal ábrázolhatjuk: struct Zero_divide { }. ha mindkettõjük számára hozzáadunk egy-egy kezelõt a try blokkhoz. mint egy olyan. így szükség esetén a megfelelõ kezelõbe léphetünk.3. Ha az egyik kezelõ „alján kiesünk”. a program befejezõdik (§14. Ha nem. a hibáról információt adhatunk a hívónak.doksi.3). Az elemzõt használó programrészben megkülönböztethetjük a két kivételt. Névterek és kivételek 251 a kezelõ végrehajtja a megfelelõ mûveletet. amely a setjmp/longjmp (§16. mint a vezérlés átadása a hívó függvény megfelelõ részének. ezért sosem lehetnék biztos abban. Az osztályok és a kivételkezelés közötti kölcsönhatást a 14. Ha a kivételt nem kapja el egyetlen try blokk sem. A C++ kivételkezelése alapvetõen nem más.

amint „belépnek” a kezelõjükbe.. azok csupán „keresztülmennek” a függvényen. hogy jelöljék.. void f() { try { } // . } .... throw Input_overflow(). A nyelv szempontjából a kivételeket rögtön kezeltnek tekintjük. A kezelõk listája némileg egy switch utasításhoz hasonlít. minden kezelõ külön hatókört (§4. részben pedig azért. melyek a kezelõ végrehajtása közben lépnek fel. expr(false).. megfelelõ kezelõvel rendelkezõ hívót keresve.doksi. E listák formai követelményei részben ezért különböznek a case-étõl..Forrás: http://www. // nem váltott ki kivételt. // . } catch (Input_overflow) { // .. A következõ például nem okoz végtelen ciklust: class Input_overflow { /* . // kizárólag akkor jutunk ide.9. ha expr() nem okozott kivételt // . */ }... A függvényeknek nem kell az összes lehetséges kivételt elkapniuk.4) alkot. Az elõzõ try blokk például nem próbálta elkapni az elemzõ bemeneti mûveletei által kiváltott kivételeket. de itt nincs szükség break utasításokra.hu 252 Alapok try { } catch (Syntax_error) { // szintaktikus hiba kezelése } catch (Zero_divide) { // nullával osztás kezelése } // akkor jutunk ide. ezért a try blokkot meghívó programrésznek kell foglalkoznia azokkal a kivételekkel. ha expr() nem okozott kivételt vagy ha egy Syntax_error // vagy Zero_divide kivételt elkaptunk (és kezelõjük nem tért vissza. és más módon sem változtatta meg a vezérlést).

..3. } . Névterek és kivételek 253 A kivételkezelõk egymásba is ágyazhatók: class XXII { /* . struct Syntax_error { const char* p. Elõször kiküszöbölhetjük az error() függvényt. void f() { // . Syntax_error(const char* q) { p = q. lazán kapcsolódó részekbõl létrehozott programokéra. } }.. 8. amely jobban hasonlít a különálló. hogy különválasszuk a futási idõben talált hibák kezelését a számológép fõ programrészétõl..1 részben szereplõ számológépet. Ez a program olyan elrendezését eredményezi. } catch (XXII) { try { } } // ... Helyette az elemzõ függvények csak a hibák jelzésére használatos típusokról fognak tudni: namespace Error { struct Zero_divide { }..hu 8. */ }. try { // .3.Forrás: http://www.doksi. Kivételek a számológépben Az alapvetõ kivételkezelõ eljárásokból kiindulva újraírhatjuk a §6.. // valami bonyolult } catch (XXII) { // a bonyolult kezelõ nem járt sikerrel } Ilyen – gyakran rossz stílusra utaló – egymásba ágyazott kivételkezelõket azonban ritkán írunk.

default: throw Error::Syntax_error("elemi szimbólum szükséges").. isalpha(). return curr_tok=NAME. a throw-t használja arra. } } double Parser::prim(bool get) { // . } Ha az elemzõ ilyen hibát talál.. string_value = ch. . vagy hiba if (isalpha(ch)) { input->putback(ch). } case Lexer::END: return 1. // az input. return curr_tok=NAME. if (curr_tok != Lexer::RP) throw Error::Syntax_error("')' szükséges"). NAME =. a kezelõnek egy Syntax_error objektumot ad át. Például a throw Syntax_error("elemi szimbólum szükséges").1. while (input->get(ch) && isalnum(ch)) string_value. amely a primary expected karakterláncra hivatkozó mutatót tartalmazza. // ')' lenyelése return e.Forrás: http://www. input->putback(ch). get_token().hu 254 Alapok Az elemzõ három szintaktikus hibát ismer fel: Lexer::Token_value Lexer::get_token() { using namespace std. használata miatt (§6.. amelyet valamilyen (közvetett vagy közvetlen) hívó függvény határoz meg. hogy átadja a vezérlést egy kezelõnek..push_back(ch).7) // . default: // NAME.doksi. } throw Error::Syntax_error("rossz szimbólum"). *input >> string_value. A throw operátor egy értéket is átad a kezelõnek. // elemi szimbólumok kezelése } case Lexer::LP: { double e = expr(true). stb.

} catch(Error::Syntax_error e) { cerr << "formai hiba:" << e. case Lexer::DIV: if (double d = prim(true)) { left /= d. a no_of_errors és az input kézenfekvõ választás a Driver névtér számára: . return no_of_errors. hogy eldobja a karaktereket addig. a main() meghívja a skip() helyreállító függvényt. if (Lexer::curr_tok == Lexer::PRINT) continue. } catch(Error::Zero_divide) { cerr << "nullával osztás kísérlete\n". cout << Parser::expr(false) << '\n'. if (Lexer::curr_tok != Lexer::PRINT) skip(). } throw Error::Zero_divide(). if (Lexer::curr_tok == Lexer::END) break.p << "\n". Most már elkészíthetjük a vezérlõt. hogy az kezelje a Zero_divide és Syntax_error kivételeket: int main(int argc. while (*input) { try { Lexer::get_token(). A skip() függvény. } // .Forrás: http://www..... A skip() az elemzõt egy meghatározott állapotba próbálja állítani. if (Lexer::curr_tok != Lexer::PRINT) skip()... Névterek és kivételek 255 A nullával való osztás hibájának jelzéséhez nem szükséges semmilyen adatot átadni: double Parser::term(bool get) // szorzás és osztás { // . amíg sorvégét vagy pontosvesszõt nem talál. break. } } if (input != &cin) delete input. } Ha nem történt hiba a PRINT (azaz sorvége vagy pontosvesszõ) szimbólummal lezárt kifejezés végén. azáltal. char* argv[ ]) { // .hu 8.doksi.

doksi. Gyakran hasznos tudni a hibákról.': return. hogy a program részeit szorosan összekapcsolta. void skip(). Felvetõdhet. és ez a szám lesz a program visszatérési értéke. így a main() egy névtéren belül értelmetlen. Más hibakezelõ módszerek Az eredeti hibakezelõ kód rövidebb és elegánsabb volt. A main()-t nem tesszük a Driver névtérbe. bool in_error = false.Forrás: http://www. while (*input) { char ch. A globális main() a program indító függvénye (§3. mint a kivételeket használó változat. még akkor is.. Így az elemzõben lévõ kivételek nem kapják el.hu 256 Alapok namespace Driver { int no_of_errors. switch (ch) { case '\n': case '.3. hogy a különálló skip() hibakezelõ függvényt a main()-ben. ha a program képes volt helyreállni a hiba után.. Ez a megközelítés nem felel meg olyan programok esetében.2). Ezt azonban úgy érte el. 8. } void Driver::skip() { no_of_errors++. melyeket külön fejlesztett könyvtárakból hoztak létre. egy állapotváltozó bevezetésével küszöböljük ki: int main(int argc.3. Megtartottam azt az ötletet. miközben éppen az elemzõ kivételeinek kezelését végzik. char* argv[ ]) { // . std::istream* input. Egy valóságos méretû programban a main() kódjának legnagyobb részét a Driver egy külön függvényébe tenném át. hogy megszámoljuk a hibákat. // rossz stílus . } // karakterek elvetése sortörésig vagy pontosvesszõig } } A skip() kódját szándékosan írtuk az elemzõ kódjánál alacsonyabb elvonatkoztatási szinten. input->get(ch).1.

doksi. in_error = true. if (Lexer::curr_tok == Lexer::END) break. } catch(Error::Syntax_error e) { cerr << "formai hiba:" << e. if (Lexer::curr_tok == Lexer::PRINT) { in_error = false. Veszélyes. in_error = true. Például nincs ok arra. hogy kivételt használjunk a „túl sok paraméter” hiba fellépésekor: . ++ Driver::no_of_errors. Ezt számos okból rossz ötletnek tartom: 1.Forrás: http://www. §8. 2. a hibakezelõ kód ugyanis megismételheti azt a hibát. akkor majdnem mindig ezt is kell tennünk. ha a hibakezelés elvonatkoztatási szintje megegyezik annak a kódnak az absztrakciós szintjével. ha lehetõséget adunk rá. Névterek és kivételek 257 } while (*Driver::input) { try { Lexer::get_token(). Több munkával jár az egész kódot módosítani a hibakezelés hozzáadásával. mint külön hibakezelõ függvényeket adni a kódhoz. ++ Driver::no_of_errors.hu 8. ami a hibát okozta. return Driver::no_of_errors. (A gyakorlatok között szerepel. amely a hibakezelést elõször kiváltotta. } if (in_error == false) cout << Parser::expr(false) << '\n'. Ha egy hiba helyben kezelhetõ. 4. Az állapotváltozók gyakran zavart okoznak és hibák forrásai lehetnek.5[7]). hogy elszaporodjanak és hatásuk nagy programrészekre terjedjen ki. } catch(Error::Zero_divide) { cerr << "nullával osztás kísérlete\n". ha a main() in_error-t használ. Nevezetesen az in_error-t használó main()-t kevésbé olvashatónak tartom. continue. A kivételkezelés nem helyi problémák megoldására való. } } if (Driver::input != &std::cin) delete Driver::input. különösen akkor. Általában jobb külön tartani a hibakezelést és a „közönséges” kódot. mint a skip() függvényt használó változatot. 3.p << "\n". hogy mi történik.

} } // mint korábban A kivételek további tárgyalása a 14. független névterekhez. §8. [3] A névtereket úgy tervezzük meg. §8.2. [5] Ha szükséges.4. Tanácsok [1] Használjunk névtereket a logikai felépítés kifejezésére.2. break. hogy a szokásos feldolgozást végzõ kódrészt elválasszuk attól a résztõl. [2] A main() kivételével minden nem lokális nevet helyezzünk valamilyen névtérbe.2.7.4.8. [4] Lehetõleg ne adjunk a névtereknek rövid neveket. break.2. hogy véletlenül hozzáférhetnénk más. amelyben a hibákkal foglalkozunk. §8. [8] A using namespace–t csak a C-rõl vagy régebbi C++-változatokról való átálláskor.2. illetve helyi hatókörben használjuk.2. hogy utána kényelmesen használhassuk. §8. §8. . default: cerr << "túl sok paraméter\n".2. [6] Lehetõleg ne rójunk nehéz jelölésbeli terheket névtereink felhasználóira.7. használjunk névtér-álneveket a hosszú névtérnevek rövidítésére. switch (argc) { case 1: // olvasás szabványos bemenetrõl input = &cin. using namespace Driver. 8.Forrás: http://www. char* argv[]) { using namespace std. §8. anélkül.2. return 1.3.9.hu 258 Alapok int main(int argc. case 2: // karakterlánc paraméter beolvasása input = new istringstream(argv[1]). §8. fejezetben történik. [9] Használjunk kivételeket arra.. [7] Használjuk a Névtér::tag jelölést. amikor a névtér tagjait meghatározzuk. §8.3. §8.2.2.2. §8.doksi.

4. (*2) Írjunk programot. (*2) Módosítsuk a számológépet. 7.Forrás: http://www. amely legalább egy olyan könyvtárat használ. Jegyezzük fel.3.1. . (*2) Módosítsuk a §8. Ne felejtsük el azt az esetet. Próbáljuk ki úgy. 8. Névterek és kivételek 259 [10] Inkább felhasználói típusokat használjunk kivételekként. [11] Ne használjunk kivételeket. amelyek ellenõrzik a túlcsordulást és az alulcsordulást. Minden függvénynek adjunk egy paramétert. Gyakorlatok 1. (*1) Találjuk meg a hibát a §8.9.5) Írjunk string elemeket tartalmazó kétirányú láncolt lista modult a §2.3. A main()-nel kapjuk el a kivételeket és írjuk ki. Adjunk erre listára egy sort() függvényt és egy olyat.5[3]-ban lévõ számológépet kivételek használatával. 6. Adjunk minden függvényhez egy karakterlánc objektumot és mérjük meg újra a különbséget. 3. (*2) Készítsünk modult a számológép programból névterek felhasználásával a §2. (*2. (*1) Írjunk programot. hogy a stack osztályon belül hol jött létre kivétel. 9. ami nem használ névtereket.5[10] függvényeit használja.hu 8. amikor a helyi vezérlési szerkezetek is megfelelõek. Jegyezzük fel. minus().5[5] programját úgy. amelyben egy függvény kivételt „dob”. ha ilyen hibák történnek. ahol a hívás mélysége 10. Módosítsuk úgy. milyen hibákat vétettünk. ami megfordítja a listában szereplõ karakterláncok sorrendjét. amely eldönti. Mérjük meg a két módszer futási idejének különbségét. melyik szinten lépett fel a kivétel. §8. amely vagy visszaad egy értéket. miként kerülhetnénk el az ilyen hibákat a jövõben.5.doksi. Tegyünk javaslatokat arra. §8. egy másik pedig elkapja. hogy a §8. 2. és kivételeket váltanak ki. vagy egy paraméter alapján eldobja azt. multiply() és divide() függvényeket.3.2. amely olyan egymást hívó függvényekbõl áll. 5. Tipp: §8.1-ben szereplõ main() elsõ változatában. (*2) Írjunk függvényt. Ne használjunk globális using direktívákat. hogy létrehozunk egy programnyelvekbõl álló listát. miként kerülhetnénk el az ilyen hibákat a jövõben. hogy a könyvtár névtereket használjon. mint beépített típusokat. 11. (*2) Módosítsuk a §8.3. amikor a kivételt a kiváltó függvényben kapjuk el.4-ben található Stack modul stílusában. milyen hibákat vétettünk. melyiket kaptuk el.4 pont stílusában. (*2. hogy megmérjük. 8.2. van-e különbség a kivételek elkapásának nehézségében attól függõen.3. Tegyünk javaslatokat arra. (*2) Vegyünk egy nem túl nagy programot. 10.5) Írjuk meg a plus().

Vannak olyan rendszerek. . Külön fordítás A fájl (az egyes fájlrendszerekben) a tárolás és fordítás hagyományos egysége.doksi. és a programok sem fájlok formájában jelennek meg a programozó számára. Ez a leírás azonban csak azokra a rendszerekre összpontosít.1.Forrás: http://www. amelyek a fájlok hagyományos használatára támaszkodnak.” (Le Corbusier) Külön fordítás • Összeszerkesztés • Fejállományok • A standard könyvtár fejállományai • Az egyszeri definiálás szabálya • Összeszerkesztés nem C++ kóddal • Az összeszerkesztés és a függvényekre hivatkozó mutatók • Fejállományok használata a modularitás kifejezésére • Egyetlen fejállományos elrendezés • Több fejállományos elrendezés • Állományõrszemek • Programok • Tanácsok • Gyakorlatok 9.hu 9 Forrásfájlok és programok „A formának a rendeltetéshez kell igazodnia. amelyek a C++ programokat nem fájlok halmazaként tárolják és fordítják.

A szerkesztõt néha (zavaró módon) betöltõnek (loader) is szokták nevezni.1. hogy késõbb új kódot adjunk a programhoz („dinamikus szerkesztés”). Ez az a program. Az újrafordításra használt idõ még egy közepes méretû program esetében is jelentõsen csökkenthetõ. és amit a fordítóprogram figyelembe vesz. hogyan lehet fájlokra szétválasztani a számológépet (§6. §8. nevezetesen a szerkesztõprogram (linker). amelytõl az elõzõ függ. A program forrásfájlokra való fizikai szétválasztását a program logikai felépítése kell. amit a programozó lát. Amikor a fordítási egység a fájl.1).8).2. hogy a programozó lehetõvé tegye az elkülönített fordítást. ha a programot megfelelõ méretû fájlokra bontjuk. ha a felhasználó saját kódját egyetlen fájl tárolja. A rendszerünkben vannak olyan eszközök. ami ahhoz szükséges.1. olyan deklarációkat kell megadnia. A teljes összeszerkesztést el lehet végezni a program futása elõtt. A felhasználó a fordítóprogramnak egy forrásfájlt (source file) ad át. Elõször áttekintünk néhány. Hasznos lehet például több forrásfájlt használni egyetlen névtér függvényeinek tárolására. amely számos következetlenséget képes észrevenni. Ezután a fájl elõfordítása történik: azaz végrehajtódik a makrófeldolgozás (§7. hogy a fordítóprogram kikényszerítse ezt a logikai szerkezetet. vagy egy névtér definícióit több fájl között szétosztani (§8. amelyek biztosítják mindazt az információt. hogy irányítsa. A program logikai és fizikai szerkezetének azonban nem kell megegyeznie.doksi. az összeszerkesztéshez kapcsolódó részletet és szakkifejezést. mert a szabványos könyvtárak és az operációs rendszer forráskódja általában nem szerepel a program forrásában.4. A programok forrásfájlokba rendezését is ugyanaz a függõségi kapcsolat vezérli.2). ahol meg kell különböztetni azt. hogy a fordítási egységet a program többi részétõl elkülönítve lehessen elemezni. Ahhoz. A program fizikai szerkezetén általában a forrásfájlokba szervezett programot értik. Az elõfeldolgozás eredményét fordítási egységnek (translation unit) hívják. majd kétféle módját ismertetjük annak.hu 262 Alapok Egy teljes programot rendszerint lehetetlen egy fájlban tárolni. A több fordítási egységbõl álló programok deklarációinak ugyanúgy következetesnek kell lenniük. A valóságos méretû alkalmazásokban az sem kényelmes és célszerû. amelyek segítenek ezt biztosítani. segítheti az olvasót a program megértésében és segíthet abban is. Emellett lehetõség van arra is. az #include utasítások pedig beépítik a fejállományokat (§2. akkor a teljes fájlt újra kell fordítani. . névtér-meghatározások egy gyûjteményét egyetlen fájlban tárolni. mint azok névterekbõl való összeállítását. már csak azért sem.4). mint az egyetlen forrásfájlból álló programokénak. A fordítóprogram valójában csak ezekkel dolgozik és a C++ szabályai is ezek formáját írják le. ami összekapcsolja a külön fordított részeket. A program elrendezési módja segíthet kihangsúlyozni a program logikai felépítését. ha (bármilyen kis) változtatást hajtottak végre rajta.2. §9. Ebben a könyvben csak ott teszek különbséget a forrásfájl és a fordítási egység között.Forrás: http://www. vagy egy másik fájlon.

Ha x már rendelkezne kezdõértékkel. } A file2. extern double b. Az effajta hibákat (szerkesztési hiba.c-ben lévõ g() által használt x és f() meghatározása a file1.9). Vegyük például a következõ két fájlt: // file1. hogy a globális vagy névtér-hatókör- . és hogy minden deklaráció. int b = 1. c-t pedig kétszer deklaráltuk. Összeszerkesztés A függvények.Forrás: http://www. // jelentése int x = 0. ha kifejezetten lokálisként nem határoztuk meg azokat. változók.doksi.c-ben szerepel. de egyszer sem definiáltuk. a szerkesztõ azonban a legtöbbet igen. a fordítóprogram az extern kulcsszót egyszerûen figyelmen kívül hagyná. extern int c. amelyben szerepel.c: int x = 1. hogy minden névtér. osztályok. névterek.2. kivéve. egységes legyen. Jegyezzük meg. hogy a file2. Deklarálni többször is lehet.c: int x. Itt három hiba van: x-et kétszer definiáltuk. sablonok.c: extern int x. b-t kétszer deklaráltuk különbözõ típusokkal. de a típusoknak pontosan meg kell egyezniük: // file1. függvény stb. Az extern kulcsszó jelzi. mert a kezdõértéket is meghatározó deklarációk egyben definíciónak is minõsülnek. A programozó feladata biztosítani. megfelelõen legyen deklarálva minden olyan fordítási egységben. amely ugyanarra az egyedre vonatkozik. void g() { x = f(). extern int c. Egy objektumot a programban csak pontosan egyszer határozhatunk meg. osztály. int f() { /* csinálunk valamit */ } // file2. // file2. Forrásfájlok és programok 263 9.c-ben az x deklarációja (csak) deklaráció és nem definíció (§4.c: int x = 1.hu 9. felsorolások és felsorolók neveit következetesen kell használni az összes fordítási egységben. int f(). linkage error) a fordítóprogram – ami egyszerre csak egy fájlt néz – nem ismeri fel.

hu 264 Alapok ben kezdõérték nélkül megadott változók alapértelmezés szerint kapnak kezdõértéket. §10.c: inline int f(int i) { return i+1. } // hiba: g() nincs definiálva ebben a fordítási egységben // file2. } Sajnos.9.5.2.1.9) minden olyan fordítási egységben definiálni kell – azonos módon (§9. Ezenkívül a szerkesztõ nem fogja összeszerkeszteni a programot. amelyekre csak abban a fordítási egységben lehet hivatkozni. } A file2.c: extern inline int g(int i) { return i+1.c: extern inline int g(int i).2) vagy a szabad tárban létrehozott objektumokra (§6. Ez nem vonatkozik a lokális változókra (§4.2. §10. } .1.4.2. A helyben kifejtett (inline) függvényeket (§7. mert x-et kétszer definiáltuk.3) –. amelyeket a nevet meghatározó fordítási egységtõl különbözõ fordítási egységben is használhatunk.2). } // file2. Az elõzõ példákban szereplõ összes név külsõ név. hogy az f() meghívása a C nyelvben nem lenne hiba (§B.c-ben az f() meghívása hiba. hanem szabálytalan is: // file1.c nem deklarálja. Ezért a következõ példa nem csak rossz stílusra vall.c: inline int f(int i) { return i. int f() { return x. } // file2. amelyben használatosak. Az olyan neveket. belsõ szerkesztésû névnek nevezzük.Forrás: http://www. ahol meghatározásuk szerepel.2.doksi. A következõ programrészlet két hibát tartalmaz: // file1.6).c: int x. int h(int i) { return g(i). külsõ szerkesztésûnek (external linkage) nevezzük.c: int x. Jegyezzük meg. hogy a fordítóprogram-írók élete könnyebb legyen: // file1. int g() { return f(). ezt a hibát a C++ egyes változatai nehezen veszik észre. mert f()-et a file2. ezért a helyben kifejtett kód és a külsõ szerkesztés következõ – különben teljesen logikus – párosítása tiltott. Az olyan neveket.

.c: namespace { class X { /* . Következésképpen ez a példa szabályos (bár zavaró lehet): // file1. Forrásfájlok és programok 265 Alapértelmezés szerint a const-ok (§5. void f(). hogy biztosítsuk a következetességet..c: typedef int T..c: typedef void T... // . A konstansokat kifejezett utasítással tehetjük külsõ szerkesztésûvé: // file1. */ }. const int x = 8. gyakran okoznak zavart...Forrás: http://www.c: extern const int a = 77.hu 9. const int x = 7. hogy a neveket egy adott fordítási egységre nézve lokálissá tegyük. int i.2.doksi. A névtelen névterek és a belsõ szerkesztés hatása nagyon hasonló: // file 1.. void f().7) belsõ szerkesztésûek.5) arra használhatjuk.9. // file2. Az olyan globális változók.1) szabadna tennünk.2. Itt g() 77-et fog kiírni. ezért legjobb elkerülni õket. } // file2. void g() { } cout << a << '\n'. A globális konstansokat és a helyben kifejtett függvényeket rendszerint csak fejállományokba (§9. A névtelen névtereket (§8. amelyek egy adott fordítási egységben lokálisnak számítanak. // .c: class X { /* . // file2. */ }.c: extern const int a..4) és a typedef-ek (§4. int i.

ami arra való. A fájl tartalmának C++ forrásszövegnek kell lennie. 9. A C nyelvû és a régebbi C++ programokban a static kulcsszót használták (zavaróan) annak a kifejezésére.c-ben lévõ f() függvény nem azonos a file2. akkor magunk keressük a bajt.2. Az #include szövegkezelõ eszköz. A különbözõ fordítási egységekben lévõ deklarációk egységességének elérésére nem tökéletes. hogy a végrehajtható kódot és/vagy adatleírásokat tartalmazó forrásfájlokba beépítjük (#include) a felületre vonatkozó információkat tartalmazó fejállományokat (header).c-ben lévõ f() függvénnyel.Forrás: http://www.h" // a szabványos include könyvtárból // az aktuális könyvtárból Sajnos a beépítõ utasításban a szóközök mind a < >. amelyet a fordítóprogramnak alaposan elemeznie kellene. de egyszerû módszer.doksi. Továbbá a leg- .1) és osztályokon (§10. hogy „használj belsõ szerkesztést” (§B. de a beépített fájlok jellemzõen csak deklarációkat tartalmaznak. ha valahová máshová beépítjük. Az #include "beépítendõ" utasítás a beépítendõ fájl tartalmára cseréli azt a sort. mind a " " belsejében fontosak: #include < iostream > // nem fogja megtalálni az <iostream>-et Furcsának tûnhet. Ha van egy adott fordítási egységre nézve lokális nevünk és ugyanazt a nevet használjuk máshol egy külsõ szerkesztésû egyed számára is.hu 266 Alapok A file1. osztálynak stb. A static kulcsszót lehetõleg csak függvényeken (§7.2. mert a fordítóprogram ennek olvasásával halad tovább. amelyben az #include elõfordul.3). és nem olyan kódot. függvénynek.4) belül használjuk.2. következésképpen a fordítónak átadott és késõbb összeszerkesztett forráskódnak is. A standard könyvtárbeli fejállományok beépítéséhez a fájl nevét idézõjelek helyett a < > zárójelpárok közé kell foglalni: #include <iostream> #include "myheader.1. hogy egy fájlt minden egyes alkalommal újra kell fordítani.2. minden deklarációjában egységesnek kell lenniük. Fejállományok A típusoknak ugyanannak az objektumnak. hogy a forráskód-részeket egyetlen egységbe (fájlba) gyûjtsük össze a fordításhoz.

Forrásfájlok és programok 267 több modern C++-változat valamilyen formában támogatja az elõfordított fejállományokat. .c.c fájlok”-nak. const float pi = 3.h fájlok”-nak és „. inline char get(char* p) { return *p++. .h kiterjesztésûek. class Matrix. template<class T> class V { /* … */ }. short tbl[ ] = { 1. } extern int a. extern int strlen(const char*).cpp és . hogy egy fejállományban a következõk szerepelhetnek: Nevesített névterek Típusdefiníciók Sablondeklarációk Sablondefiníciók Függvénydeklarációk Helyben kifejtett függvények definíciói Adatdeklarációk Konstansdefiníciók Felsorolások Névdeklarációk Beépítõ utasítások Makródefiníciók Feltételes fordítási utasítások Megjegyzések namespace N { /* … */ } struct Point { int x. green }. template<class T> class Z.és adatdefiníciókat tartalmazó fájlok kiterjesztése pedig . 2. namespace { /* … */ } export template<class T>f(T t) { /* … */ } A fejállományok hagyomány szerint . y.Forrás: http://www. Ezzel ellentétben egy fejállomány sohasem tartalmazhatja a következõket: Közönséges függvénydefiníciók Adatdefiníciók Agregátum-definíciók Névtelen névterek Exportált sablondefiníciók char get(char *p) { return *p++.cxx. mint a . . } int a. Más szokásos jelöléseket is találhatunk. }. amit ugyanannak a fejállománynak az ismételt fordítása jelent.141593.hu 9. a függvény.doksi. enum Light { red.C. hogy csökkentse a munkát. Fordítóprogramunk dokumentációja ezt jól meghatározza. csak ésszerû módja az #include használatának a logikai szerkezet kifejezésére. ezért gyakran hívják ezeket „. yellow. Alapszabályként fogadjuk el. 3 }. #include <algorithm> #define VERSION 12 #ifdef __cplusplus /* check for end of file */ Mindez nem nyelvi követelmény.cc.

. Célszerû elkerülni a makrókkal való ügyeskedést is. számomra teljesen ismeretlen fejállományban szereplõ makró helyettesít. Bölcs dolog nem túl okosnak lenni az #include használatánál.doksi. ezért jó kód készítéséhez fontosabbak.9) extern "C" { // az stdio függvények C szerkesztésûek (§9. ami anélkül teszi elérhetõvé a szabványos matematikai függvényeket.hu 268 Alapok Az egyszerû állandókat ajánlatos fejállományokba tenni.).2.2. Az #include <cstdio> például azt nyújtja. hogy a több fordítási egységben elõforduló egyedeibõl másodpéldányt készítsenek.h>. hogy kihasználják a standard könyvtár definícióinak ismeretét és ezáltal optimalizálják annak megvalósítását..h nevû szövegfájlban tárolódik a szokásos könyvtárban.1. Az agregátumokat azonban nem. csak teljes deklarációkat és definíciókat építsünk be és csak globális hatókörben.2.h> fejállományához létezik megfelelõ szabványos <cX> C++ fejállomány. 9. A standard könyvtár fejállományai A standard könyvtár eszközeit szabványos fejállományok halmazán keresztül mutatjuk be (§16..4) namespace std { // a standard könyvtárat az std névtér írja le (§8. amikor régi kódot alakítunk át (§9.2) tegyük ezt.Forrás: http://www.4) #endif // .. // az stdio elérhetõvé tétele a globális névtérben #endif .2).. amelyet egy közvetetten beépített. Azt ajánlom. illetve a szabványos fejállományok kezelésének módját. Ezenkívül az egyszerû esetek sokkal gyakoribbak. // . mert az egyes C++-változatok nehezen tudják elkerülni.h fájl általában valahogy így néz ki: #ifdef __cplusplus // csak C++ fordítók számára (§9. #ifdef __cplusplus } } using namespace std. A C standard könyvtárának minden <X. Egy olyan fejállomány. Az egyik legkevésbé kedvelt foglalatosságom olyan hibát nyomon követni. hogy fejállományok. szerkesztési blokkokban. tudjuk róluk.2.h kiterjesztés hiánya nem utal semmire a fejállomány tárolásával kapcsolatban. A . mint egy kapcsolót. A nyelv adott megvalósítása ismerheti a beépített szabványos matematikai könyvtárat (§22.2. mint a <map>. mert beillesztésükhöz az #include <…> formát használjuk az #include "…" helyett.. int printf(const char* . Másfelõl a szabványos fejállományokat nem muszáj hagyományos módon tárolni. amit egy olyan név okoz.3) és úgy kezelheti az #include <cmath> utasítást. Az egyes C++-változatok számára megengedett. vagy olyan névtérdefinícióknál. hogy bármilyen fájlt beolvasnánk. amit az #include <stdio. valószínûleg a map. A standard könyvtárbeli fejállományokat nem kell utótaggal ellátnunk.2. A stdio.

Sajnos. sablon stb. amelyet valahol egy fájlban tárolunk. ha 1. hogy például egy osztálynak. }. one-definition rule) nevezik. amelyek a programokat nem forrásfájlokban tárolják. a nyelvi szabály nem lehet ennyire egyszerû.Forrás: http://www.2. felsorolást. hogy a C és C++ osztozzanak a fejállományon.. hogy az ott szereplõ S az S egyetlen definiálása és szabadon megváltoztathatja azt. sablon. . így vannak olyan változatok.. ami nehezen felfedezhetõ hibát okozhat. de #include utasításokkal (§9. szimbólumról szimbólumra megegyeznek és 3. }. mindig csak egyszer definiálhatunk egy programban.3. hogy egy osztály. Forrásfájlok és programok 269 Azaz a deklarációk (nagy valószínûséggel) közösek. Gyakorlati szempontból ez azt jelenti. hogy lehetõvé tegyük. */ } Az ODR értelmében a fenti példa helyes és S ugyanarra az osztályra vonatkozik mindkét forrásfájlban. különbözõ fordítási egységben szerepelnek és 2. void f(S* p) { /* .c: struct S { int a. Ha valaki módosítja a file2. char b. de az összeszerkesztéssel és névterekkel kapcsolatos dolgokra oda kell figyelnünk. Például: // file1.c-t. azt feltételezheti. pontosan egy kifejtéssel kell rendelkeznie. // file2.1) szöveges formában két forrásfájlban is el lehet helyezni. char b. 9. ezen szimbólumok jelentése mindkét fordítási egységben ugyanaz. Még ennél is nagyobb baj. Ezt a szabályt gyakran az „egyszeri definiálás szabályának” (ODR.doksi. Nem bölcs dolog azonban egy definíciót ilyen módon kétszer leírni. Az egyszeri definiálás szabálya Egy adott osztályt. hogy a „fájl” fogalma nem része a C és C++ nyelvnek.c: struct S { int a. vagy helyben kifejtett függvény kétféle definiálása kizárólag akkor fogadható el ugyanazon egyed két példányaként. sablont stb.hu 9. void f(S*). Azaz egy osztály. definíciójának egyedinek kell lennie – valamelyest bonyolultabb és ravaszabb módon fogalmaztuk meg. Egy osztály definícióját például össze lehet állítani makrók behelyettesítésével is.2. Következésképpen a szabványban lévõ szabályt – amely azt mondja.

char b}.Forrás: http://www. }.h" void f(S*p) {/*. char b.c: #include "s.c: struct S1 { int a.. mert egy struct-ot egyetlen fordítási egységben nem lehet kétszer definiálni. char b.c: #include "s. }.h" void f(S* p) { /* . }. // file1. char b. // file1. void f(S*).// hiba .c: #include "s.*/} Nézzünk példákat az ODR szabály megsértésének mindhárom módjára: // file1.doksi.h" // f() használata itt // file2.hu 270 Alapok Az ODR szándéka az.h: struct S {int a. void f(S*)..c: #include "s.h" // f() használata itt file2. char b. // hiba: két definíció Ez azért hiba.c: struct S2 { int a. // file2.. struct S1 { int a.c: struct S2 { int a. }. }. */ } Ábrával: s.. hogy megengedje egy osztálydefiníció beillesztését különbözõ forrásfájlokba egy közös forrásfájlból: // file s.h: struct S { int a. char bb. file1.

}. de a példa hibás.c: class Point { /* . // file2. amíg ez nem sérti az ODR-t.. amikor a közös definiciókat fejállományokba tesszük és aztán azokat építjük be. Az ilyen kódmódosulás ellen úgy védekezhetünk a legjobban.h" // . Például ha a Point osztályt az s.c: typedef char X. ezért az ODR-t megsértõ deklarációk nehezen észrevehetõ hibákat okozhatnak. hogy csak a deklarációjukat adjuk meg: // file1.. char b.c: export template<class T> T twice(T t) { return t+t.Forrás: http://www.c: #define Point int #include "s. // file2.c: typedef int X. A sablondefiníciókat több fordítási egységbe is beépíthetjük. struct S3 { X a. a fordítóprogram felismerte volna a hibát. struct S3 { X a. az exportált sablonokat pedig úgy is használhatjuk.doksi.. mert S2 olyan osztályokat nevez meg. A legtöbb C++-változat nem képes a különbözõ fordítási egységekben lévõ osztálydefiníciók következetességét ellenõrizni. Forrásfájlok és programok 271 Ez azért hiba. #include "s. amennyire csak lehetséges. char b. */ }. Sajnos az a módszer sem képes az ODR utolsóként bemutatott megszegése ellen védelmet nyújtani.. // file1. char b. }.h állományban vezettük volna be. // file1. } . ha a fejállományokat annyira különállóvá tesszük. }. // hiba Itt az S3 két definiciója szimbólumról szimbólumra megegyezik.h" // . amelyek egy tag nevében különböznek. A helyi typedef-ek és makrók ugyanis módosíthatják a beépített deklarációk jelentését: // file s..h: struct S { Point a.hu 9. mert az X név (trükkös módon) mást jelent a két fájlban..

hogy C++ kódrészletet használnak más nyelven megírt programok részeként. Az extern "C"-ként megadott függvényekre is a C++ típusellenõrzési és paraméter-átalakítási szabályai vonatkoznak. } // deklaráció Az export kulcsszó azt jelenti. 9. const char*). a függvényhívások szerepét nem befolyásolja. Az extern "C" utasítás különösen fontos a C és a C++ közötti szoros kapcsolat miatt.Forrás: http://www. összeszerkesztési szabályt határozhatunk meg az extern deklarációkra. Az extern "C" utasítás (csak) az összeszerkesztési szabályt határozza meg. extern char* strcpy(char*. const char*).7).hu 272 Alapok // file2. Összeszerkesztés nem C++ kóddal A C++ programok általában más nyelven megírt részleteket is tartalmaznak. . Az extern "C"-t gyakran használják olyan Fortran vagy assembler eljárásokkal való összeszerkesztéshez. de különbözõ fordítóprogrammal lefordított kódrészletek között sem. hogy a C összeszerkesztési szabályainak megfelelõen kell azt hozzászerkeszteni a kódhoz: extern "C" char* strcpy(char*. A következõ példa bevezeti a C és a C++ standard könyvtáraiban levõ strcpy() függvényt. melyeket a fordítóprogram a szerkesztõnek átad.doksi. nem pedig a gyengébb C szabályok. Hogy segítsünk. hogy „más fordítási egységbõl elérhetõ” (§13. Hasonlóan gyakori az is.c: template<class T> T twice(T t). Jegyezzük meg. Az együttmûködés a különbözõ nyelven megírt programrészek között nem mindig könnyû – sõt még az azonos nyelven írt. és meghatározza. például a karakterláncok és egészek szerkezete. melyek véletlenül éppen megfelelnek a C követelményeinek.4. A különbözõ nyelvek és ugyanazon nyelv különbözõ megvalósításai például különbözõképpen használhatják a gépi regisztereket a paraméterek tárolására.2. különbözõ lehet a beépített típusok. nem pedig a programnyelvet jelöli. és a szerkesztõtõl megkövetelt típusellenõrzések. illetve azon nevek formája. hogy az extern "C"-ben szereplõ "C" az összeszerkesztési szabályt. int g(int i) { return twice(i). A deklaráció hatása a „sima” deklarációkétól csak az strcpy() hívására használt összeszerkesztési szabályban tér el. másképpen helyezhetik azokat a verembe.

int strcmp(const char*.hu 9. melyet gyakran szerkesztési blokknak (linkage block) neveznek. úgy is használhatjuk. Forrásfájlok és programok 273 Például: extern "C" int f(). hogy belefoglalunk egy teljes C fejállományt és így alkalmassá tesszük azt a C++-ban való használatra: extern "C" { #include <string.. ha sok deklarációhoz kell hozzáadnunk az extern "C"-t. hogy C fejállományokból C++ fejállományokat hozzanak létre. mellyel deklarációk egy csoportjának összeszerkesztését határozhatjuk meg: extern "C" { char* strcpy(char*. const char*). hogy közös C és C++ fejállományt készítsünk: #ifdef __cplusplus extern "C" { #endif char* strcpy(char*. } Ezt a szerkezetet. #ifdef __cplusplus } #endif . int strlen(const char*). ezért bevezettünk egy eljárást. } // hiba: nem vár paramétert Kényelmetlen lehet.1) használunk. // .. Egy másik lehetõség.Forrás: http://www.h> } A fenti módszer gyakran használatos arra.doksi. int g() { return f(1). int strlen(const char*). int strcmp(const char*. const char*). const char*).. // .. ha feltételes fordítást (§7.8. const char*).

az extern kulcsszót közvetlenül a deklaráció elõtt kell megadnunk: extern "C" int g3. nem pedig definiálni akarunk. Ez lehetõvé teszi számunkra. } // bármilyen deklaráció jöhet ide. Ennek az az oka. ez még mindig ugyanaz a régi C printf() (§21. és definiciója is lesz. nem definíció Ez elsõ látásra furcsának tûnik. hogy a C++ szerkezetek eltûnjenek.doksi. nem csak deklarációja. A névtér azt befolyásolni fogja. hogyan lehet a névhez hozzáférni C++ programokból. } // rendben // hiba: nincs globális printf() Még ha std::printf()-nek nevezzük is. pl: // definíció // deklaráció. amikor a fájlt C fejállományként használjuk. amelyek C++ szerkesztésû függvényeket határoznak meg a globális névtérben. Ha egy változót csak deklarálni.8). de azt nem. Egy C szerkesztésû nevet névtérben is megadhatunk. így g1 globális változó marad. amikor egy extern deklarációhoz "C"-t adunk (és a fájl jelentése is. Egy szerkesztési blokkon belül bármilyen deklaráció szerepelhet: extern "C" { int g1. hogy a deklaráció jelentése változatlan maradjon. hogy a szerkesztõ hogyan fogja látni a nevet. printf("világ!\n"). // deklaráció. hogy C szerkesztésû könyvtárakat építsünk be egy általunk választott névtérbe. pedig célja csak annyi. ahelyett. . Sajnos ugyanez a rugalmasság nem áll rendelkezésünkre az olyan fejállományok esetében. "). amikor majd a szerkesztési blokkba foglaljuk).Forrás: http://www. hogy a C++ egyedek összeszerkesztésénél figyelembe kell venni a névtereket is. így a létrehozott tárgykód (object fájl) tükrözni fogja a névterek használatát vagy annak hiányát. extern int g2. hogy a globális névteret „szennyeznénk". nem definíció Ez a változók hatókörét és tárolási osztályát nem érinti. Egy jellemzõ példa erre az std névtér printf() függvénye: #include<cstdio> void f() { std::printf("Helló.hu 274 Alapok A „készen kapott” __cplusplus makró használatával azt biztosíthatjuk.

&ccmp). hogy az egyik nyelven megírt függvényekre hivatkozó mutatókat a másik nyelven definiált függvényeknek szeretnénk átadni. const void*).sz. // compare() C++ szerkesztésû // ccmp() C szerkesztésû // hiba // rendben // rendben // hiba } Egy olyan nyelvi változat. FT cmp).Forrás: http://www.hu 9.doksi. elõfordulhat. const void*). int sz) { qsort(v. illetve a függvényhívási eljárások közösek. Ennyi közös tulajdonság azonban általában nem tételezhetõ fel.&compare). akkor az minden olyan függvénytípusra. const void*). isort(v. és változónévre vonatkozni fog. qsort(v. hogy biztosítsuk a függvények oly módon történõ meghívását.1. extern "C" int ccmp(const void*. size_t sz. Ha egy deklaráció számára meghatározzuk az összeszerkesztési módot. függvénynévre.1.&compare).&ccmp).1. . } // FT C++ szerkesztésû // CFT C szerkesztésû // cmp C szerkesztésû void isort(void* p.2. Forrásfájlok és programok 275 9.1. size_t n. Az összeszerkesztés és a függvényekre hivatkozó mutatók Ha egy programban a C és C++ kódrészleteket keverjük. size_t n. size_t n. // cmp C++ szerkesztésû int compare(const void*. CFT cmp). CFT cmp). size_t sz. Ez mindenféle furcsa – de néha alapvetõ – összeszerkesztési módot lehetõvé tesz. Például: typedef int (*FT)(const void*. nyelvi kiterjesztésként elfogadhatja a hibaként megjelölt eseteket. size_t sz. void qsort(void* p. Ha a két nyelv adott változatainak összeszerkesztési szabályai. ahogy azt a függvény elvárja.sz. size_t n. amit a deklaráció(k) bevezet(nek). a függvényekre hivatkozó mutatók átadása egyszerû. size_t sz. FT cmp). amelyben a C és C++ ugyanazt a függvényhívási módot használja. const void*). // cmp C++ szerkesztésû void xsort(void* p. void f(char* v. isort(v.5.sz. így figyelnünk kell arra. // cmp C szerkesztésû extern "C" void ysort(void* p.sz. extern "C" { typedef int (*CFT)(const void*.

hu 276 Alapok 9. a . END. ASSIGN='='.2) néhány lehetséges fizikai elrendezését.c fájlok közötti kapcsolatot biztosító típusok deklarációit pedig egyetlen .3.h fájlba tesszük. §8. extern double number_value.3.c fájlt – lexer. DIV='/'. struct Syntax_error { const char* p.c és main. NUMBER.c.h: namespace Error { struct Zero_divide { }.1. PRINT='.doksi.Forrás: http://www. table. } Token_value get_token().h fejállomány így nézne ki: // dc. melyet minden . Egyetlen fejállományos elrendezés Egy programot úgy bonthatunk a legegyszerûbben több fájlra. }.c – használhatnánk a függvények és adatleírások tárolására.c fájlba.c.1. } }. és a dc. A számológép program esetében öt .'. LP='('. parser. Fejállományok használata A fejállományok használatának illusztrálására most bemutatjuk a számológép program (§6. MUL='*'. PLUS='+'. MINUS='-'. error. A dc.c fájl beépít (#include). hogy a definíciókat megfelelõ számú . RP=')' extern Token_value curr_tok.c. } #include <string> namespace Lexer { enum Token_value { NAME. extern std::string string_value. . amelyek egynél több fájlban használatosak.h fejállományban tárolhatnánk azoknak a neveknek a deklarációit. Syntax_error(const char* q) { p = q. 9.

amikor a dc. extern std::istream* input.c valahogy így néz ki: // lexer.h" #include <iostream> #include <cctype> Lexer::Token_value Lexer::curr_tok. double term(bool get). namespace Driver { extern int no_of_errors..h-t a fájlokba beépítjük.. */ } . hogy a benne lévõ minden deklaráció valamilyen ponton be legyen építve abba a fájlba. hogy egy meghatározás ne forduljon elõ többször is. using Lexer::get_token.. double Lexer::number_value.h-ból // ..doksi..double> table. Forrásfájlok és programok 277 namespace Parser { double prim(bool get). A lényegi kód elhagyásával a lexer.c fordításakor például a fordítóprogramnak a következõ kód adódik át: namespace Lexer { // . A lexer.hu 9. Lexer::Token_value Lexer::get_token() { /* .c: #include "dc. Token_value get_token(). */ } A fejállomány ilyen használata biztosítja. Lexer::Token_value Lexer::get_token() { /* .. void skip(). // elemi szimbólumok kezelése // szorzás és osztás // összeadás és kivonás } #include <map> extern std::map<std::string..Forrás: http://www. using Lexer::curr_tok. std::string Lexer::string_value. } // a dc. } Minden változódeklarációban az extern kulcsszót használjuk annak biztosítására.c fájlban szerepelnek. Az egyes definíciók a megfelelõ .. amely a hozzá tartozó kifejtést tartalmazza. double expr(bool get).

.c: #include "dc.hu 278 Alapok Így a fordítóprogram biztosan észreveszi. A main. A fenti definíció a table-t globálisként határozza meg.. */ } .. Ha a get_token()-t például Token_value típusú visszatérési értékkel vezettük volna be. a szerkesztõ fogja észrevenni a problémát. char* argv[ ]) { /* . Ha egy definíció hiányzik.c: #include "dc. akkor valamelyik .c fájl így fog kinézni: // parser. std::istream* Driver::input = 0. A szimbólumtábla nem más. */ } double Parser::term(bool get) { /* . */ } A table.double> table. */ } double Parser::expr(bool get) { /* . de int visszatérési értékkel definiáltuk volna. a lexer..h" #include <sstream> int Driver::no_of_errors = 0. hogy lehetõségem legyen figyelmeztetni rá.doksi.c: #include "dc. void Driver::skip() { /* .. mint egy standard könyvtárbeli map típusú változó.h" std::map<std::string. Csak azért voltam itt ilyen hanyag. Egy valóságos méretû programban a globális névtér efféle kis „szennyezõdései” felhalmozódnak és végül problémákat okoznak.c pedig így: // table.c fájl fordítása hiúsul meg. ha egy névhez megadott típusok nem egységesek.c fordítása típusütközési vagy „nem megfelelõ típus” (type mismatch) hiba miatt nem sikerült volna.Forrás: http://www... A parser.. */ } int main(int argc..c fájl végül így fog kinézni: // main.h" double Parser::prim(bool get) { /* .. ha egy deklaráció.

Hacsak nem fektetnek hangsúlyt a névterekkel és osztályokkal kapcsolatos programozási stílusra.c Észrevehetjük. bár ezen a megjegyzések segíthetnek.doksi. hogy amikor névtereket használunk.c parser. mert széleskörûen ismertek és stabilak. Forrásfájlok és programok 279 Ahhoz. hogy a felül lévõ fejállományok mind a standard könyvtár fájljai. ha minden #include utasítást közös fejállományba teszünk.c lexer. a dc. Az egyetlen fejállományos fizikai részekre bontás akkor a leghasznosabb. ha több programozó is módosítja az egyetlen fejállományt. hogy a program main() függvényének ismerjék fel. a logikai felépítés a program növekedésével együtt romlani fog. A közös fejállomány módosítása maga után vonja az egész program újrafordítását és nagy a hibalehetõség.hu 9. a main()-nek globális függvénynek kell lennie. Jegyezzük meg. A kis programoknál az elrendezés egyszerûsíthetõ.c table.h-ban egyben a program logikai felépítését is ábrázoljuk.h main. a szerkezet homályos lesz. ha a program kicsi és részeit nem áll szándékunkban külön használni. . fájl alapú fejlesztõkörnyezetekben. A program elemzésekor ezek a könyvtárak számos esetben kihagyhatók. ezért itt nem használtunk névteret. A program fizikai elrendezését valahogy így lehet bemutatni: <sstream> <map> <string> <cctype> <iostream> dc. A nagyobb programok egyetlen fejállományos elrendezése nem mûködik a hagyományos.Forrás: http://www. Ha nem használunk névtereket.

h" double Parser::prim(bool get) { /* . A felhasználóknak szánt felületet a . Több fejállományos elrendezés Egy másik fizikai elrendezés szerint minden logikai modulnak saját fejállománya lenne.h felhasználói fejállományt azért építjük be.h végzõdésû fájlban szerepel. amely leírja a modul által nyújtott szolgáltatásokat.h fájl tartalmazza.Forrás: http://www.3. felhasználói felületét pedig a parser.h" #include "table. hogy a fordítóprogram ellenõrizhesse a következetességet (§9.. double expr(bool get).3.c: #include "parser_impl. Minden .1). */ } double Parser::expr(bool get) { /* . Ily módon az elemzõt három fájl képviseli.2.c fájlokban vannak elhelyezve.doksi.c fájlhoz tartozik egy megfelelõ .h nyújtja: // parser. és rendszerint további olyan . } // felület a felhasználóknak Az elemzõt megvalósító függvények közös környezetét a parser_impl.. using Lexer::get_token.h" #include "error. */ } . double term(bool get). hogy megvalósítsa a felületében közzétett szolgáltatásokat.h: namespace Parser { double expr(bool get).és változódefiníciói stb.h fájlját. */ } double Parser::term(bool get) { /* .h" #include "lexer. Ekkor minden .h fájlokat is. melyekre a Parser függvényeinek szüksége van: // parser.. Az elemzõ függvényeket a parser. a modul függvény. pedig a .. a programozói felület egy _impl..c szolgáltatásait (felületét)..h fájl. amelyek meghatározzák. Ez a fizikai elrendezés megegyezik a modul logikai felépítésével.h adja: // parser_impl. using Lexer::curr_tok. mire van szüksége más modulokból ahhoz.h" namespace Parser { double prim(bool get). ami meghatározza a .h: #include "parser.hu 280 Alapok 9.c fájlban együtt tároljuk azokra a fejállományokra vonatkozó #include utasításokkal.c fájl beépíti a saját . } // felület a megvalósításhoz A parser.

Forrás: http://www.c fájlba és csak oda építsük be a table.c prim.3-ban leírt logikai szerkezettel.h parser_impl.3.h parser.h table.h azonban valami olyasmire példa.c parser. így ha a függõségeket valóban a lehetõ legkevesebbre szeretnénk csökkenteni. a prim()-et tegyük külön .doksi. Ha a table.h-t: parser. a szerkezetet még tovább egyszerûsíthettük volna.h parser_impl.c helyett. csak a függvények megvalósításainak van szüksége rá. ez elég jól egyezik a §8.h lexer.hu 9. a prim() használja.h error.h main. Forrásfájlok és programok 281 Az elemzõ és annak a vezérlõ általi használata ábrával így mutatható be: parser. A table.h-ba építettük volna be a parser. ami nem szükséges az elemzõ függvények közös környezetének kifejezéséhez.h error.h-t a parser_impl. Tulajdonképpen egyetlen függvény.c Ahogy vártuk.c .h table.h lexer.

A lényeg. hogy további fájlokat építenek be ott. hogy a fordítási idõ tizedrészére csökken. A több fejállományos elrendezés megengedi.h fájl van. A több fejállományos elrendezés olyan modulok és programok esetében hatásos. mert egy egyszerû függõségelemzés a fejállományok jobb használatához vezet.h használata nem szabványos és még csak nem is gyakori megoldás – én egyszerûen így szeretek elnevezni dolgokat. Az egyetlen fejállományos elrendezés rákényszerít minket. ahol egy globális információtár van a középpontban – felülrõl lefelé haladó megközelítést igényel. ahol egyes függvények számára azok szükségesek.2. hogy a programozó viszonylag kis kódrészletre összpontosíthasson. A valóságos méretû modulok esetében gyakori. A hatás drámai lehet. A több fejállományos elrendezés segít. Miért törõdünk ezzel a bonyolultabb több fejállományos elrendezéssel? Nyilván sokkal kevesebb gondolkodást igényel. így örökké gondolkodnunk kell azon. Továbbá nem ritka. amelyet valamelyik modul használ. mert jobban azonosítja a kapcsolatokat. hogy sikeresen dolgozzunk „belülrõl kifelé”. Ezek a modulok azonban olyan kicsik. és eldöntsük.hu 282 Alapok Ilyen alaposságra a nagyobb modulokat kivéve nincs szükség. Alapvetõen azért használtuk ezt az elrendezéstípust. hogy a kód módosítása mindig hiányos információk és helyi nézõpont alapján történik. pusztán azért. csak helyi szemszögbõl. hogy minden olyan deklarációt megnézzünk.h fájlokat. . hogy odaillõ-e. A jobb összpontosítás azt eredményezi. hogy egynél több _impl. hogy nem igényelnek saját _impl. hogy pontosan mi függ egy másik dologtól. hogy pontosan eldönthessük.h-nál tettük. mint a mi apró elemzõnk és számológépünk. 9. Az ilyen fájlok csak ott kellenek. hogy az _impl.1. így az gyorsabban történik. Az egyetlen fejállományos elrendezés – mint minden más elrendezés. A számológép egyéb moduljai A számológép többi modulját az elemzõhöz hasonlóan rendezhetjük el. ahol egy logikai modul sok függvénybõl áll. amelyeknek közös környezetre van szükségük. és hogy figyelmen kívül hagyhassuk a program többi részét. Elõfordul. hogy kevesebb információ kell a modul lefordításához. mitõl függ az elemzõ kód. ha egyszerûen minden deklarációt bedobunk egy fejállományba.3. amelyek nagyságrendekkel nagyobbak. mint ahogy azt a dc. Egy nagy program elemzésekor vagy módosításakor alapvetõ.doksi.Forrás: http://www. mivel a modul függvényeinek részhalmazai különbözõ közös környezetet igényelnek. Meg kell jegyeznünk.

END. LP='('.c: #include #include #include #include "lexer. Syntax_error(const char* q) { p = q.h" <iostream> <cctype> .Forrás: http://www. A lexer. MUL='*'. extern std::string string_value.h: #include <string> namespace Lexer { enum Token_value { NAME. RP=')' extern Token_value curr_tok.hu 9. DIV='/'. NUMBER.'. lexer) meglehetõsen nagy és rendezetlen felületet nyújt: // lexer. a karakterek fajtáit eldöntõ függvényekre támaszkodik: // lexer. extern double number_value. } }.h" "error.h: namespace Error { struct Zero_divide { }.h-ra. }. az <iostream>-re és a <ctype>-ban megadott. így nincs szükségünk az error.c-re: // error. PRINT='. } Token_value get_token(). PLUS='+'. ASSIGN='='. struct Syntax_error { const char* p.h-n kívül a lexikai elemzõ az error.doksi. Forrásfájlok és programok 283 A hibakezelõt a kivételtípusok halmazára egyszerûsítettük. } Az adatbeviteli kód (lexikai elemzõ. MINUS='-'.

h" std::map<std::string. A szimbólumtábla alapvetõen önálló.doksi.h #include utasításait külön tehettük volna. még akkor is.c fájlba is bele lehetnek építve..h" #include "error.h" . ha a table. A modul megvalósításában szokásos módon építjük be (#include) a modul által nyújtott felületet – ebben az esetben a lexer. Lexer::Token_value Lexer::get_token() { /* .h-t –. Mivel feltesszük. hogy az egyes fejállományok több ..double> table.h közötti különbség csak az extern kulcsszó: // table. std::string Lexer::string_value.h fájlba.double> table. a table deklarációját külön kell választanunk annak kifejtésétõl.c és a table.h: #include <map> #include <string> extern std::map<std::string. hogy a fordítóprogram ellenõrizhesse a következetességet.h" #include "table.hu 284 Alapok Lexer::Token_value Lexer::curr_tok. bár a standard könyvtárbeli <map> fejállomány használatával számos érdekes dolog kerülhet bele. A vezérlõ tulajdonképpen mindenre támaszkodik: // main.h" #include "lexer. double Lexer::number_value. a Lexer-hez tartozó _impl. hogy hatékonyan valósíthassa meg a map sablonosztályt: // table.Forrás: http://www.c: #include "table. */ } Az error. ez azonban túlzás egy ilyen kis program esetében.c: #include "parser.

Forrásfájlok és programok 285 namespace Driver { int no_of_errors. Ha azonban egy nagy program deklarációit logikailag a lehetõ legkisebb fejállományokra bontjuk (például úgy.6[8]). amit a main() tesz. A fejállományok használata A programban használt fejállományok (header file) száma több tényezõtõl függ. de az állományok kezelése sziszifuszi feladattá válik. ha 20 darab 50 soros fájl olvasása észrevehetõen több idõt igényel. akkor nem elõnyös sok fejállományt használnunk. hogy minden szerkezet deklarációját külön fájlba tesszük). std::istream* input. hogy különbözõ függvényekbõl hívják meg kódunkat (§9. Például..2. Néhány figyelmeztetés: egy tucatnyi fejállomány (természetesen a szokásos fejállományokkal együtt. Ez különösen fontos olyan kódok esetében. amelyeket könyvtárként akarunk használni. amikor elérik az ezres nagyságrendet. Az igazi bonyodalom ott kezdõdik. void skip(). mielõtt egy kis projektben a több fejállományos stílust használjuk. hogy . char* argv[ ]) { /* . akkor kétszer is gondoljuk meg.h fájlként. a main. hogy a vezérlõnek kevesebb közvetlen függõsége legyen. kezelhetetlen zûrzavar lehet az eredmény. melyeket gyakran százas nagyságrendben számolhatunk) a program végrehajtási környezete számára rendszerint még kezelhetõ. hogy több fájlt nézzünk vele egyszerre.c-be tesszük azt.3. Ezen tényezõk közül sok inkább a rendszer fájlkezelésébõl adódik és nem a C++-ból. Nagy projekteknél persze elkerülhetetlen a sok fejállomány. Ekkor ugyanis nem támaszkodhatunk a main()-ben megírt kódra és fel kell készülnünk arra is. Hasonlóan. ha szövegszerkesztõ programunk nem képes arra. */ } Mivel a Driver névteret kizárólag a main() használja. Gyakran ésszerû az olyan mûveletekbõl is minél kevesebbet alkalmazni. könnyen egy több száz fájlból álló. amit az #include utasítással beépítünk.Forrás: http://www. A nagyobb programokat rendszerint megéri úgy elrendezni. mint egyetlen 1000 soros fájlé. } #include <sstream> int main(int argc. Emlékezzünk.doksi..2.hu 9. nevezetesen hogy meghív egy külön forrásfájlban lévõ vezérlõ függvényt. Az ilyeneknél több száz fájl (nem számolva a szokásos fejállományokat) az általános. 9. A fent tárgyalt alapvetõ módszerek ekkor is alkalmazhatók. Külön is szerepelhetne driver.

A kétfajta elrendezési módszer között a program alkotórészeinek létrehozásakor kell (néha többször is) választanunk. hogy csökken a fordítási idõ. Igazán nem is a mi ízlésünkre van bízva. 1. melyeket mindig figyelembe kell vennünk a lényegi modulok tervezésekor. Két választásunk lehet. hogy a fejállományok többszöri beépítése megengedett legyen. jobban szeretek átlagos felhasználó lenni. ahogy a rendszer fejlõdik. Ezenkívül sok nagyobb program szerkezete olyan. Ahol nem muszáj szakértõnek lennem. Így ugyanis kevesebb a veszekedés.3. Nagyobb programoknál az ilyen fölösleg (redundancia) hibákhoz vezethet.doksi. Az „átlagos felhasználó” kifejezés nem lekicsinylõ. hogy egy felület nem szolgálhat minden célra ugyanolyan jól.Forrás: http://www. melyek ahhoz kellenek. Átszervezhetjük a programunkat. a tapasztaltabb felhasználóknak pedig terjedelmesebb felületet nyújtani. hogy nem építjük be azokat a fejállományokat. mint amennyirõl egy átlagos felhasználónak tudnia kell. hogy a program egyes egységei elkülönülten is érthetõek legyenek. mert az ilyen programok rendszerint eleve több fejállományt tartalmaznak. mind azáltal. hogy minden logikai modult következetes. találunk valamilyen módot arra. és újra kell gondolnunk azokat. Rendkívül fontos emlékeznünk arra. hogy eltávolítsuk a fölösleget. hogy az egyetlen és a több fejállományos elrendezés közül válasszunk. önálló egységként ábrázoljunk. amelyek ismeretlenek lennének az átlagos felhasználó számára. Az összes . hogy célszerû a felhasználók többségének egyszerû. A tapasztalt felhasználók felületei (a „teljes felületek”) sokkal több szolgáltatást építenek be. Ezek olyan egymást kiegészítõ módszerek. A fölös #includeok kiszûrése és az ennek eredményeképpen létrejött egyszerûsített program nagyon elõnyös lehet mind logikai szempontból. Rendszerint megéri különbséget tenni a fejlesztõi és a felhasználói felület között. 9.hu 286 Alapok a valódi méretû programoknál az egyetlen fejállományos elrendezést általában nem választhatjuk. „Állomány-õrszemek” A több fejállományos megközelítés gondolata az. amelyek olyan szolgáltatásokat írnak le.2. A program egészének szempontjából nézve viszont azon deklarációk többsége. A fölöslegre azért is szükségünk van. amint egy osztályleírást vagy helyben kifejtett függvényeket tartalmazó fejállományt ugyanabban a fordítási egységben (§9.3) kétszer építünk be az #include-dal. Az elsõ megközelítés – ami a számológép végsõ változatához vezetett – fárasztó és valóságos méretû programoknál gyakorlatilag kivitelezhetetlen. Valójában az átlagos felhasználó felületét úgy határozhatjuk meg.3. vagy 2. felesleges. hogy minden logikai egység teljes legyen.

hu 9. mert nem tudhatjuk. a makrónevek ütközése ellen pedig nincs névtér védelem. Amint a programozó hozzászokik a fejállományokhoz és az állomány-õrszemekhez. hogy a fejállományokba „állomány-õrszemeket” (beépítésfigyelõket. Ez makrókkal való ügyeskedés. beolvassa annak tartalmát.Forrás: http://www. Ez nem kívánatos.. másodszor már nem fogja figyelembe venni.h: #ifndef CALC_ERROR_H #define CALC_ERROR_H namespace Error { // . A fejállományokat mindenféle környezetben használják. A standard könyvtár fejállományainak mindegyike tartalmaz állomány-õrszemeket. a CALC_ERROR_H pedig értéket kap. még azoknál a C++-változatoknál sem. include-guards) illesztünk: // error. Az állomány-õrszemeknek ezért hosszú és csúnya neveket szoktam választani. ami megengedi a fölös #include-ok jelenlétét. amikor tényleg szükség van rá. Szükségtelenül hosszú fordítási idõt okozhatnak és számos deklarációt és makrót elérhetõvé tehetnek. Lehetõleg szisztematikusan kell használnunk.. ami kiszámíthatatlanul és kedvezõtlenül befolyásolhatja a program jelentését. ha a CALC_ERROR_H már definiált. } #endif // CALC_ERROR_H A fájlnak az #ifndef és az #endif közötti tartalmát a fordítóprogram nem veszi figyelembe. melyek optimalizálják a fejállományok feldolgozását. de mûködik és mindenütt jelen van a C és C++ világában. Ha ismét találkozna vele a fordítás során.h-val elõször találkozik. így alkalmaznunk kell valamilyen eszközt. hajlamos közvetlenül vagy közvetve sok fejállományt beépíteni. Ezért amikor a fordítóprogram az error. . hogy a felhasználó mennyire alapos elemzést tart érdemesnek.doksi. Forrásfájlok és programok 287 elõfordulás megtalálása azonban ritkán sikerül. A hagyományos megoldás az. Csak akkor építsünk be fejállományokat.

Fõleg a dinamikus csatolású könyvtárak (DLL) nem képesek . a fordítási egységben definiciójuk sorrendjében kapnak kezdõértéket (§10. A main() által visszaadott int érték lesz a program visszatérési értéke. így az sqrt(2) hívódik meg.9). amit a main()-t meghívó rendszer megkap. amit egy globális változó kezdeti értékadása váltott ki (§14. fõleg a bonyolult kezdeti értékadást igénylõ globális változók használatát kell korlátoznunk.2).4. Kezdeti értékadás nem lokális változóknak Elvileg a függvényeken kívül megadott.1. // nem lokális változók Itt az x és az y az sqx elõtt kap kezdõértéket. Továbbá nem lehetséges olyan kivételt sem elkapni. A program által végzett fõ tevékenység a main() meghívásával kezdõdik és az abból való visszatéréssel ér véget.7).doksi. double y. Ha egy ilyen változónak nincs pontosan meghatározott (explicit) kezdõértéke. double sqx = sqrt(x+y). ebben a gyûjteményben használt függvénynek. akkor a típusának megfelelõ alapértelmezett értékkel töltõdik fel (§10.2). vagy static osztályváltozók) a main() meghívása elõtt. Minden. amely egyszerre „hordozható” és hatékony is lenne. névtér-. §9. egyedi meghatározással (definícióval) kell rendelkeznie (§4.3) és pontosan egy main() nevû függvényt kell tartalmaznia (§3. melyet a szerkesztõprogram egyesít. nem lokálisnak számító változók (azaz a globális.hu 288 Alapok 9.4. Programok A program külön fordított egységek gyûjteménye.7) kiváltó programok esetében finomítanunk kell. Általában az a legjobb. objektumnak.9.4. A különbözõ fordítási egységekben lévõ globális változók kezdeti értékkel való feltöltésének sorrendjét számos módon kényszeríthetjük ki. A különbözõ fordítási egységekben lévõ globális változók kezdõértékkel való ellátásának sorrendje nem kötött. ha minél kevesebb globális változót használunk.4. következésképpen nem bölcs dolog ezeknél a kezdõértékek között sorrendi függõségeket létrehozni. Ezen az egyszerû meghatározáson a globális változókat tartalmazó (§10.9) vagy el nem kapott kivételt (§14. de nincs köztük olyan.Forrás: http://www. típusnak stb.4. 9.2. A beépített típusok és felsorolások esetében az alapértelmezett kezdõérték a 0: double x = 2.

. hogy a kezdõértéket konstans kifejezésektõl kapó változók (§C.9.4. A program befejezése A programok futása számos módon érhet véget: ♦ ♦ ♦ ♦ A main()-bõl való visszatéréssel Az exit() meghívásával Az abort() meghívásával El nem kapott kivétel kiváltásával Továbbá többféle hibás felépítés és nyelvi változattól függõ módszer létezik arra. Jegyezzük meg. hogy egy program összeomoljon. // . 9. } // növelés és kiírás A nem lokális statikus változók kezdeti értékadását bármilyen eljárás vezérelheti. Ha azonban a program a standard könyvtár abort() függvényét használja.2. §10. hogy elindítsa a C++ programot.4). } A use_count() hívás most globális változóként mûködik.. return uc. ezért el kell kerülnünk azon nem lokális változók használatát. amit az adott nyelvi változat arra használ. . hogy a módszer megfelelõen mûködik.hu 9. ha a main() végrehajtására sor kerül. Ha a program befejezésére a standard könyvtárbeli exit() függvényt használjuk. Csak akkor garantált. Forrásfájlok és programok 289 a bonyolult függõségekkel rendelkezõ globális változókkal boldogan együtt élni. melyek futási idejû kezdeti értékadást igényelnek olyan C++ kódban.5) nem függhetnek más fordítási egységben levõ objektumok értékétõl és nem igényelnek futási idejû kezdeti értékadást.1. kivéve.5): void f() { cout << ++use_count(). hogy elsõ használatakor kap kezdõértéket (§5.1.Forrás: http://www.doksi. akkor meghívódnak a létrehozott statikus objektumok destruktorai (§10.4. így minden esetben biztonságosan használhatók. amit nem C++ program használ. Globális változók helyett gyakran használhatunk referenciát visszaadó függvényeket: int& use_count() { static int uc = 0.

Az exit(). és atexit() függvények deklarációját a <cstdlib> fejállomány tartalmazza.hu 290 Alapok a destruktorok meghívására nem kerül sor. Az atexit függvények számát az adott nyelvi változat korlátozza. A C (és C++) standard könyvtárának atexit() függvénye lehetõséget ad arra. void somewhere() { if (atexit(&my_cleanup)==0) { // normál programbefejezéskor a my_cleanup hívódik meg } else { // hoppá: túl sok atexit függvény } } Ez nagyban hasonlít a globális változók destruktorainak a program befejezõdésekor történõ automatikus meghívásához (§10. mint amilyennek elsõ pillantásra látszik. Ezért gyakran az a legjobb.4. a függvény nem nulla érték visszaadásával jelzi. §10. A main() visszatérési értékéhez (§3. hogy megoldja a problémát. Az exit() függvény típusa void exit(int). Az exit() meghívása azt jelenti.9.Forrás: http://www. . A nulla sikeres befejezést jelent. ha ezt a korlátot elérték. az atexit(f) meghívása után létrehozott objektum destruktora pedig az f meghívása elõtt. Jegyezzük meg: ez azt is jelenti.4).7) egy kivétel „dobása” és elkapása biztosítja.doksi. mi legyen a továbbiakban. Jegyezzük meg.2) hasonlóan az exit() paramétere is visszaadódik „a rendszernek” a program visszatérési értékeként. hogy az exit() nem fejezi be rögtön a programot.2. Az atexit(f) meghívása elõtt létrehozott objektum destruktora az f meghívása után fog meghívódni. A lokális objektumok megfelelõ megsemmisítését (§14. és megengedjük egy kivételkezelõnek. ha a környezetet egy kivétel kiváltásával elhagyjuk.4. hogy eldöntse. abort(). hogy kódot hajthassunk végre a program befejezõdésekor: void my_cleanup(). hogy a hívó függvény lokális változóinak és az azt hívó függvények hasonló változóinak destruktorai nem hívódnak meg. hogy az atexit() paraméterének nem lehet paramétere és nem adhat vissza értéket. Emellett az exit() meghívása úgy fejezi be a programot. hogy az exit() hívójának nem ad lehetõséget arra. Ezek a korlátozások az atexit()-et kevésbé használhatóvá teszik. destruktorban való meghívása végtelen ciklust eredményezhet.

[2] Abban a forrásfájlban építsük be õket (#include). Tanácsok [1] Használjuk fejállományokat a felületek ábrázolására és a logikai szerkezet kihangsúlyozására.hu 9. §9. [8] A C fejállományokat névterekben építsük be. (*3) Módosítsuk az elõbbi programot.2.1.3. [10] Különböztessük meg a fejlesztõi és a felhasználói felületet. Van-e olyan nem szabványos fejállomány. . hogy minden beépített fájlra kiírja a megjegyzések és a nem megjegyzések sorainak számát. §9. §9. (*2. [6] Csak teljes deklarációkat építsünk be. §9. §9.3.1. Használjunk behúzást a beépített fájlok által beépített fájlok kiírásakor. illetve a nem megjegyzésként szereplõ.2. [4] Kerüljük a fejállományokban a nem helyben kifejtendõ függvényeket. Próbáljuk ki a programot néhány valódi forrásfájlon (hogy elképzelésünk legyen a beépített információ nagyságáról). üreshelyekkel elválasztott szavak számát. Gyakorlatok 1.2. hogy elkerüljük a globális neveket. de különbözõ jelentéssel különbözõ fordítási egységekben.2. a befoglalás mélységének jelölésére.1 [7] Használjunk „állomány-õrszemeket”. §9. 4. amely beolvas egy forrásfájlt és kiírja a beépített fájlok neveit.Forrás: http://www.4.3.3. amely ezekkel együtt tárolódik? Be lehet-e építeni nem szabványos fejállományokat a <> jelölést használva? 2.2. [11] Különböztessük meg az átlagos és a tapasztalt felhasználók felületét.1.2. §9. (*2) Találjuk meg. [5] Csak globális hatókörben és névterekben használjuk az #include-ot.2. Forrásfájlok és programok 291 9.5) Írjunk programot. §9.6.3. §9. (*2) Hol tárolódnak a nem szabványos „foundation” könyvtárak fejállományai? 3. §9.2.3.1. [12] Kerüljük az olyan nem lokális objektumok használatát.doksi. §9. amelyek futási idejû kezdeti értékadást igényelnek olyan kódban. [9] Tegyük a fejállományokat különállóvá.3. [3] Ne adjunk meg globális egyedeket ugyanazzal a névvel és hasonló. §9.1. amelyben függvényeiket kifejtjük. hol tárolja rendszerünk a szabványos fejállományokat.3. §9.2. amit nem C++ program részeként szándékozunk felhasználni.5. Írassuk ki neveiket. 9.

doksi. hogy dinamikusan csatolható legyen? 7. hogy megfeleljen a kód.2. melyek mindegyike 1500 karaktert tartalmaz. és fordításonként csak egyszer végez beépítést. Tipp: nézzük meg a példát a §21. amely a megfigyelt fájlon kívül végzi az ellenõrzést. (*2. Van-e eltérés a teljesítményben? Hány fájl lehet egyszerre megnyitva rendszerünkben? Válaszoljuk meg ezeket a kérdéseket a beépített fájlok használatával kapcsolatban is.2). Nyissunk meg és olvassunk be egy 150 000 karakterbõl álló fájlt. és fejtsük ki elõnyeit és hátrányait a §9.3. (§8. Készítsünk egy ilyen szerkezeti elemet.5) A külsõ beépítésfigyelõ olyan programelem. tervezzünk módszert a tesztelésére.5.hu 292 Alapok 5. hogy meg lehessen hívni a main()-bõl vagy más függvénybõl is. egy egyszerû függvényhívással. 9.Forrás: http://www. (*3) Hogyan valósul meg a dinamikus csatolás (szerkesztés) a rendszerünkben? Milyen megszorítások vonatkoznak a dinamikusan szerkesztett kódra? Milyen követelményeknek kell. 8. melyek az error()-t használták kivételek helyett. .3. (*2) Módosítsuk a számológépet.2) a számológép azon változataira. Van-e a külsõ beépítésfigyelõknek bármilyen jelentõs futási idõbeli elõnye a rendszerünkben? 6.1 pontban. (*3) Nyissunk meg és olvassunk be 100 fájlt. (*2) Rajzoljuk meg a „modulfüggõségi diagramokat” (§9.3-ban leírt „állomány-õrszemekkel” szemben.

15. 13.hu Második rész Absztrakciós módszerek Ebben a részben azzal foglalkozunk. 12. milyen lehetõségeket nyújt a C++ nyelv új típusok meghatározására és használatára. 14. Osztályok Operátorok túlterhelése Származtatott osztályok Sablonok Kivételkezelés Osztályhierarchiák . Fejezetek 10. 11.doksi. illetve bemutatjuk az összefoglaló néven objektumorientált programozásnak és általánosított (generikus) programozásnak nevezett eljárásokat.Forrás: http://www.

.. nincs nehezebb. akiknek az új rendelkezések szolgálnak hasznukra.Forrás: http://www..” Niccolo Machiavelli (A fejedelem (§vi). Mert ellenségei azok.hu „. azok pedig.. pusztán lagymatag védelmezõi. akiknek a régi törvények hasznára vannak.doksi. mint új törvények bevezetéséért síkraszállni. kétesebb kimenetelû. Lutter Éva fordítása) . veszélyesebb dolog.

fejezet) segítségével úgy szervezhetjük az egymással kapcsolatban álló osztályokat.hu 10 Osztályok „Ezek a típusok nem „elvontak”. mint az int és a float. Ezenkívül az öröklõdés (12. hogy a programozó a beépített adattípusokkal azonos kényelmi szinten használható új adattípusokat hozhasson létre. Bevezetés A C++ nyelv osztályai azt a célt szolgálják. hogy kapcsolataikat hatékonyan használhassuk ki.1. .Forrás: http://www. ugyanannyira valóságosak.” (Doug McIlroy) Fogalmak és osztályok • Osztálytagok • Az elérhetõség szabályozása • Konstruktorok • Statikus tagok • Alapértelmezett másolás • const tagfüggvények • this • struct-ok • Osztályon belüli függvénydefiníciók • Konkrét osztályok • Tagfüggvények és segédfüggvények • Operátorok túlterhelése • A konkrét osztályok használata • Destruktorok • Alapértelmezett konstruktorok • Lokális változók • Felhasználói másolás • new és delete • Tagobjektumok • Tömbök • Statikus tárolás • Ideiglenes változók • Uniók • Tanácsok • Gyakorlatok 10.doksi. fejezet) és a sablonok (13.

például az adatokat elérõ függvények teljes listájától. illetve az osztályba tartozó objektumok létrehozásának és használatának fõbb eszközeit mutatjuk be. ha az adott típus adatszerkezetét érintõ összes külsõ használatot és belsõ rendrakó függvényt csak az adott típusra vonatkozó programozási felületen keresztül tesszük elérhetõvé.2.1.2. ha abban az általa kezelt fogalmaknak megfelelõ típusok szerepelnek. Az osztály egy felhasználói típus. // ábrázolás . Ez az elválasztás legjobban úgy fejezhetõ ki. Ideális esetben ezek csak létrehozásuk módjukban különböznek a beépített típusoktól. hogy meghatározzunk egy fogalmat. A C++ beépített float típusa például a +. Osztályok Az osztály (class) a programozó által meghatározott. m. A fordítóprogram például felderítheti az objektumok nem szabályos használatát. 10. Ez a fejezet a viszonylag egyszerû.doksi. a program tömörebb lesz. -. „konkrét” felhasználói típusokkal foglalkozik. 10. amelynek nincs közvetlen megfelelõje a nyelv beépített típusai között. y. hogyan ábrázolnánk a „dátum” fogalmát egy Date adatszerkezettel (struktúrával. más néven felhasználói típus. esetleges részleteinek (például a tárolandó adatok elrendezésének) elválasztása a típus helyes használatához alapvetõen szükséges tulajdonságoktól. Új típus definiálásakor az alapvetõ szempont a megvalósítás véletlenszerû. struct) és egy sor.Forrás: http://www. Ha a programozó alkalmas osztályokat használ. Explosion típusunk egy videójáték számára. Az alábbiakban az osztályok meghatározásának. mûveleteivel együtt a valós szám matematikai fogalmának egy megközelítése. ráadásul sokféle kódelemzõ eljárás használata válik lehetõvé. * stb. ilyen változókat kezelõ függvénnyel: struct Date { int d. Tagfüggvények Vizsgáljuk meg. Egy programot könnyebb megérteni és módosítani. Lehet például Trunk_line típusunk egy telefonos kapcsolatokat kezelõ programban.hu 296 Absztrakciós módszerek A típus egy fogalom konkrét ábrázolása. Azért tervezünk új típust. vagy list<Paragraph> típusunk egy szövegszerkesztõ programban. amit másként csak egy alapos ellenõrzés során fedezhetnénk fel. a használat módjában nem. }.

// kezdeti értékadás d-nek // n évet ad d-hez // n hónapot ad d-hez // n napot ad d-hez Az adattípus és ezen függvények között nincs kifejezett kapcsolat.8) tagfüggvényeknek hívjuk.10. tomorrow.doksi. void add_year(int n). int yy).2. int n). Osztályok 297 void init_date(Date& d.init(30. void add_year(Date& d. // kezdeti értékadás // n év hozzáadása // n hónap hozzáadása // n nap hozzáadása }. int.1950). void add_day(int n). void f() { Date today.. int mm. today. Ilyen kapcsolatot azáltal hozhatunk létre. int yy) { d = dd. my_birthday. int n). } Minthogy a különbözõ adatszerkezeteknek azonos nevû függvényeik is lehetnek.hu 10. int n). void init(int dd. y = yy. int). void add_day(Date& d. m = mm. y. int. hogy a függvényeket tagfüggvényekként adjuk meg: struct Date { int d. // . m. void add_month(Date& d. a tagfüggvények meghatározásakor meg kell adnunk az adatszerkezet nevét is: void Date::init(int dd.Forrás: http://www. int mm.. } .12. Az osztálydefiníción belül bevezetett függvényeket (a struct is osztály.init(16. §10. Date tomorrow = today.1996).add_day(1). A tagfüggvényeket az adatszerkezetek tagjainak elérésére vonatkozó szokásos formában alkalmazhatjuk és csak megfelelõ típusú objektumra: Date my_birthday. void add_month(int n).

A második. A megszorítást úgy fejezhetjük ki. int yy).2. Ha ugyanezt a tagfüggvényt a my_birthday változóra alkalmaznánk. nyilvános (public) rész az osztály nyilvános felülete. void add_month(int n).m változóra vonatkozik. y.Forrás: http://www. hogy kizárólag ezek lehetnek mindazok a függvények. Történeti okokból az osztálydefiníciót néha osztálydeklarációként említik. az osztálydefiníciók az #include utasítás felhasználásával több forrásállományban is szerepeltethetõk. amelyre a tagfüggvényt meghívtuk. int mm.3). public: void init(int dd. Az elérhetõség szabályozása A Date elõzõ pontbeli deklarációja azon függvények halmazát adja meg. A public címke két részre osztja az osztály törzsét. hogy milyen objektumra hívták meg.2.2. amelyek közvetlenül függnek a Date típus ábrázolásától és közvetlenül elérhetik az ilyen típusú objektumokat. m. 10.2.m változóra vonatkozna. ha struct helyett class-t használunk: class Date { int d. A class X { . az m=mm értékadás a today. Ekkor a név azon objektum megfelelõ tagjára vonatkozik. Az elsõ. A struct szerkezetek egyszerûen olyan osztályok.doksi. A tagfüggvényeket a megszokott módon definiálhatjuk és használhatjuk: . az m=mm értékadás a my_birthday. }. // kezdeti értékadás // n év hozzáadása // n hónap hozzáadása // n nap hozzáadása }. mert egy új típust határoz meg. Ebbõl azonban nem derül ki. kifejezést osztálydefiníciónak hívjuk. Amikor például a Date::init() tagfüggvényt alkalmazzuk a today változóra. melyekben a tagok alapértelmezett elérhetõsége nyilvános (§10. amelyek nem definíciók. void add_day(int n). feltéve.8). hogy nem sértjük meg az egyszeri definiálás szabályát (§9. void add_year(int n). Azon deklarációkhoz hasonlatosan. A tagfüggvény mindig „tudja”.. privát (private) részbeli neveket csak a tagfüggvények használhatják. melyekkel a Date típusú objektumot kezelhetjük.hu 298 Absztrakciós módszerek A tagfüggvényekben a tagokat az objektum kifejezett megadása nélkül is használhatjuk..

ha egy hiba miatt a Date érvénytelen értéket kap (mondjuk 1985. mert az adattagok priváttá tétele miatt erre kényszerültünk. Egy általános célú nyelvben csak hardverszinten lehetne a rosszindulatú használat ellen védekezni. hogy az osztály viselkedésének bármilyen módosítása csakis a tagfüggvények megváltoztatásával érhetõ el. részben pedig azért. ezért nem kell újraírni (bár lehet. ezért a címek megfelelõ kezelésével vagy pontosan meghatározott típuskonverzióval megkerülhetõ. az objektumnak értéket adó függvényünk. december 36. A privát tagok védelme az osztálytagok név szerinti elérhetõségének korlátozásán múlik. Az osztályt használó kód közvetlenül csak az osztály nyilvános felületétõl függ.-át). Például ha megváltoztatjuk egy osztály adatábrázolását. a hiba helyének behatárolása már azelõtt megtörténik. hogy megtudja. ha a tagok elérhetõségét egy pontosan megadott lista függvényeire korlátozzuk. nem a védelmi rendszer tudatos megkerülése.2. hogy ez a hiba csak valamelyik tagfüggvényben lehet. Ez egyik esete annak az általános megfigyelésnek. a csalás ellen. ha van egy. hogy egy objektumnak kezdõértéket kell adni. akkor biztosak lehetünk abban. hogy a leendõ felhasználónak elég a tagfüggvények meghatározását tanulmányoznia ahhoz. } Mindazonáltal a nem tag függvények a privát tagokat nem használhatják: void timewarp(Date& d) { d. Ez persze csalás. hogy a hibakeresés elsõ szakasza. hogy újra kell fordítani). Például. A másik elõny.3. mert általában célszerû. Konstruktorok Az init()-hez hasonló függvények használata az objektumok kezdeti értékadására nem elegáns és hibák forrása lehet.y -= 200. 10. Ebbõl következik. // hiba: Date::y privát } Számos elõnnyel jár. és igazi rendszerekben még ez is nehezen kivitelezhetõ. Minthogy sehol sincs lefektetve. A C++ a véletlen hibák ellen véd. Osztályok 299 inline void Date::add_year(int n) { y += n. Az init() függvényt részben azért vettük fel. hogy a program egyáltalán lefutna. hogyan lehet használni az osztályt.hu 10. akkor elég a tagfüggvényeket ennek megfelelõen módosítanunk.Forrás: http://www.doksi. a programozó elfelejtheti azt – vagy éppen többször is megteheti (mind- .

. m. int). nap // nap. azokat meg kell adni: Date today = Date(23. constructor) hívjuk. Date now. y. Date(int).1990). Amíg a konstruktorok kellõen különböznek a paraméterek típusaiban. Ezt úgy érhetjük el.1983). Date guy("5 Nov"). Ha a konstruktornak paraméterekre van szüksége. konstruktornak (vagyis létrehozónak. Date(const char*). Date xmas(25. int. melynek célja kifejezetten az objektumok elõkészítése.doksi. Jobb megoldás. hogy megadjon egy olyan függvényt. Date release1_0(10. mondván. A programozó egy osztály tervezésekor mindig kísértést érez új és új függvényekkel bõvíteni azt.. Date(int. aktuális hónap és év // alapértelmezett Date: mai dátum // a dátum karakterlánccal ábrázolva A konstruktorokra ugyanazok a túlterhelési szabályok vonatkoznak.. a fordítóprogram ki fogja tudni választani. // konstruktor Ha egy osztály rendelkezik konstruktorral. // nap.hu 300 Absztrakciós módszerek két esetben egyformán végzetes következményekkel). Date(int. hónap.12). aktuális év // nap. melyiket kell az egyes meghívásokkor alkalmazni: Date today(4). ha lehetõvé tesszük a programozónak. Mivel az ilyen függvény létrehozza az adott típusú értékeket.4). Több gondot igényel ugyanis . hogy ugyanaz a neve.12. public: // . // rövidített forma // hiba: nincs kezdõérték // hiba: a harmadik paraméter hiányzik Gyakran célszerû. // alapértelmezett kezdeti értékadás az aktuális dátummal A Date példa esetében megfigyelhetjük a konstuktorok „elburjánzását”. ha a kezdeti értékadás többféleképpen is lehetséges. akkor minden. hónap. Date my_birthday. int). mint magának az osztálynak: class Date { // . Date july4("July 4. int). }. mint más függvényekre (§7.6.. Date(int. Date().Forrás: http://www. A konstruktort arról ismerjük meg. ha többféle konstruktor áll rendelkezésre: class Date { int d. }. 1983"). valakinek úgyis szüksége lesz rájuk. ami általános jelenség. ebbe az osztályba tartozó objektum kap kezdõértéket. int.

class Date { int d. melynek jelentése: „vegyük az alapértelmezett today-t”. // . A day (nap) és month (hónap) paraméterek esetében ez a halmaz világosan meghatározható.y. Az egymással rokon függvények számának csökkentésére az egyik mód az alapértelmezett paraméter-értékek használata (§7. mire van igazán szükség és arra szorítkozni. Így az osztály csak akkor használható.2. statikus tagnak nevezzük.5). Ez az odafigyelés ugyanakkor általában kisebb és érthetõbb programokhoz vezet. annak kívül kell esnie a lehetséges értékek halmazán. int yy) { d = dd ? dd : today. int mm =0. int yy =0). de annak objektumaihoz nem. az idõszámításunk utáni elsõ év (year==1) közvetlenül az idõszámításunk elõtti elsõ év (year==-1) után következik. m. Az olyan változókat. } // ellenõrizzük. hogy Date érvényes dátum-e Ha egy paraméter-értéket az alapértelmezett érték jelzésére használunk. de ez a stílus vezet az eredeti programozón kívül más számára használhatatlan kódhoz. A felhasználóknak túl sok kellemetlen meglepetésben lesz részük.. y = yy ? yy : today. melyek egy osztályhoz tartoznak.d. Statikus tagok A Date típushoz tartozó kényelmes alapértelmezett értéket egy jelentõs rejtett probléma árán hoztuk létre: Date osztályunk a today nevû globális változótól függ.m. Kerüljük el! Szerencsére a kívánt célt elérhetjük a nyilvánosan elérhetõ globális változó jelentette tehertétel nélkül is.Forrás: http://www.4. public: Date(int dd =0. Szerencsére az európai naptárban nincs 0-dik év. amikor ilyen környezetfüggõ osztályokat próbálnak használni és a kód módosítása is problematikus lesz.doksi. y. 10. a year (év) mezõnél azonban a zéró érték nem esik nyilvánvalóan a halmazon kívülre. int mm.hu 10. Osztályok 301 mérlegelni. Ezzel az egy „kis globális változóval” még talán megbirkózunk. }. Ez a fajta megszorítás az osztályt az eredeti környezeten kívül használhatatlanná teszi.. m = mm ? mm : today. A statikus tagokból mindig pontosan egy példány . ha a today változót definiáltuk és minden kódrészletben megfelelõen használjuk. A Date osztály paramétereinek például egy olyan alapértelmezett értéket adhatunk. Date::Date(int dd.

d.doksi. Egy statikus tagra ugyanúgy hivatkozhatunk. } .1770). int mm =0.hu 302 Absztrakciós módszerek létezik. static void set_default(int.12. } // ellenõrizzük. Ehhez hasonlóan az olyan függvényeket. nem statikus adattagokból. int). nem pedig objektumonként egy. int mm. } A statikus tagokat – mind az adattagokat. }. ekkor az osztály nevével minõsíthetjük: void f() { Date::set_default(4..m. mind a függvényeket – definiálni kell valahol: Date Date::default_date(16. int yy =0). int yy) { d = dd ? dd : default_date. akár egy objektum megnevezése nélkül is. mint a közönséges. hogy megõrizzük az alapértelmezett konstruktor-értékek szerepét.m.y). static Date default_date. sõt. mint bármilyen más tagra. de közben elkerüljük a globális változó használatának hátrányát: class Date { int d. de nem szükséges objektumra meghívni azokat. m. y = yy ? yy : default_date. y. A Date konstruktort immár így határozhatjuk meg: Date::Date(int dd.. m = mm ? mm : default_date. Tervezzük át az osztályt úgy. void Date::set_default(int d.1945). hogy Date érvényes dátum-e Amikor szükséges.y. statikus tagfüggvénynek hívjuk.Forrás: http://www. módosíthatjuk az alapértelmezett értéket. melyek egy adott osztálytaghoz hozzáférnek. int.5. int y) { Date::default_date = Date(d. public: Date(int dd =0. // . int m.

4. (Erre a §10. } int month() const { return m. de az érték lekérdezésére sajnos nem adtunk lehetõséget. amíg valaki át nem állítja valami másra. Osztály típusú objektumok másolása Alapértelmezés szerint az osztály típusú objektumok másolhatók és kezdõértékként egy azonos típusú osztály egy objektumának másolatát is kaphatják. Ha nem ez a megfelelõ viselkedés egy X osztály számára. m. }. } int year() const. // kezdeti értékadás másolással Alapértelmezés szerint az osztály objektum másolata minden tag másolatából áll.. az X::X(const X&) másoló konstruktorral megváltoztathatjuk azt.4. a programozó megadhatja a megfelelõ értékadó operátort (§10. .1). melyek értéket adnak egy Date objektumnak vagy megváltoztatják azt. a hónapot és a napot: class Date { int d.5.4. y. 10.. ha készítünk néhány függvényt. ha konstruktorokat is megadtunk: Date d = today. amelyekkel kiolvashatjuk az évet. Osztályok 303 Az alapértelmezett érték itt Beethoven születési dátuma. // . Ezen könnyen segíthetünk. Következésképpen nincs szükség külön függvényre az alapértelmezett dátum lekérdezéséhez. hogy a Date() jelölés a Date::default_date értéket szolgáltatja: Date copy_of_default_date = Date().2. még akkor is. 10.1 pontban részletesebben is visszatérünk.doksi. } Az alapértelmezett viselkedés itt is a tagonkénti másolás.6. Vegyük észre.hu 10.) Ennek megfelelõen az osztály objektumokat alapértelmezés szerint értékadással is másolhatjuk: void f(Date& d) { d = today. Konstans tagfüggvények A Date osztályhoz eddig olyan tagfüggvényeket adtunk. Ha ez nem megfelelõ egy osztály számára. public: int day() const { return d.4.Forrás: http://www.2.

a frissített objektumra mutató referenciát. // rendben int j = cd.Forrás: http://www. hogy azok nem adnak vissza értéket. } inline int Date::year() { return y.year(). ha visszaadunk egy.2. értéke nem módosítható } 10. mert a mûveleteket ekkor láncba kapcsolhatjuk („láncolhatjuk”).hu 304 Absztrakciós módszerek Vegyük észre a const minõsítõt a függvénydeklarációkban az (üres) paraméterlista után. } // hiba: a const minõsítõ hiányzik a tagfüggvény típusából Vagyis a const minõsítés része a Date::day() és Date::year() függvények típusának.year().doksi. cd. hogy véletlenül megszegjük ezt az ígéretet: inline int Date::year() const { return y++. és add_year() állapotfrissítõ függvényeket úgy határoztuk meg. const Date& cd) { int i = d.7. Természetesen a fordítóprogram megakadályozza. a nem konstans tagfüggvényeket viszont csak nem konstans objektumokra: void f(Date& d.add_year(1). Önhivatkozás Az add_year(). // rendben // hiba: cd konstans. Ez azt jelenti. a const utótagot ki kell írnunk: inline int Date::year() const // helyes { return y. egymással kapcsolatban levõ frissítõ függvények esetében sokszor hasznos. Az ilyen. // hiba: kísérlet tag értékének módosítására konstans függvényben } Ha egy konstans tagfüggvényt osztályán kívül határozzuk meg. Egy konstans tagfüggvényt alkalmazhatunk állandó (konstans) és változó (nem konstans) objektumokra is. add_month(). .add_year(1). hogy ezek a függvények nem változtatják meg az objektum állapotát. // rendben d.

m = 3. d. Ehhez viszont minden függvényt úgy kell megadnunk. } y += n. amelyre a függvényt meghívták.add_month(1).4. Osztályok 305 Tegyük fel. Date& add_day(int n).. (Egyenértékû a Simula nyelv THIS és a Smalltalk self kifejezésével. egy hónapot és egy évet adunk d-hez. így nem lehet a címét felhasználni vagy értéket adni neki. Date& add_year(int n). hogy azok egy Date típusú referenciát adjanak vissza: class Date { // .) Egy nem statikus tagfüggvényben a this kulcsszó egy mutatót jelent arra az objektumra. Date& add_month(int n). // n év hozzáadása // n hónap hozzáadása // n nap hozzáadása }.hu 10. hogy a következõt szeretnénk írni: void f(Date& d) { // ..Forrás: http://www.. amelyre a tagfüggvényt meghívták... } Ezzel egy napot. } // figyeljünk február 29-re! A *this kifejezés azt az objektumot jelenti. Az X osztály egy nem const tagfüggvényében a this típusa X*.1). Mindazonáltal a this nem közönséges változó. return *this. Az X osztály egy konstans tagfüggvényben a this típusa const X* lesz.add_day(1). melyik objektumra hívták meg. hogy ne lehessen megváltoztatni magát az objektumot (lásd még §5. Minden (nem statikus) tagfüggvény tudja. . így pontosan hivatkozhat rá: Date& Date::add_year(int n) { if (d==29 && m==2 && !leapyear(y+n)) { d = 1.add_year(1). // .doksi..

public: // .doksi. ám fáradságos módon így is megadhattuk volna: Date& Date::add_year(int n) { if (this->d==29 && this->m==2 && !leapyear(this->y+n)) { this->d = 1. hogy egy tagfüggvény logikailag állandó. 10. void compute_cache_value(). Az add_year függvényt például egyenértékû. hogyan mûködhetne ez a Date osztály esetében: class Date { bool cache_valid. amíg a Date értéke meg nem változik. } A this-t meghatározott (explicit) módon gyakran láncolt listák kezelésére használjuk (például §24. // gyorstár feltöltése // ábrázolás karakterlánccal . this->m = 3. Fizikai és logikai konstansok Esetenként elõfordulhat. hogy a megfelelõ objektum tagját érje el.4). mégis meg kell változtatnia egy tag értékét. de nézzük meg..2. Ilyen belsõ gyorsítótár (gyorstár.1. A Date osztályt például egy függvény visszatérési értéke egy karakterlánccal ábrázolhatja.Forrás: http://www.7.. return *this.7. melyet a felhasználó a kimenetben felhasználhat. Egy ilyen ábrázolás felépítése idõigényes feladat. ezért érdemes egy példányt tárolni belõle. }. de valamilyen. A felhasználó számára a függvény nem módosítja az objektum állapotát. string string_rep() const. a felhasználó által közvetlenül nem látható részlet megváltozik. } this->y += n. Az ilyen helyzetet gyakran hívják logikai konstans mivoltnak. string cache.. cache) inkább bonyolultabb adatszerkezeteknél használatos.hu 306 Absztrakciós módszerek A this használata legtöbbször automatikus.3. Például minden nem statikus tagra való hivatkozás tulajdonképpen a this-t használja.. // . amit az egymást követõ lekérdezések mind felhasználhatnak.

const Date d2. } return cache. th->compute_cache_value(). Osztályok 307 A felhasználó szemszögébõl a string_rep függvény nem változtatja meg az objektum állapotát.doksi.hu 10.4.Forrás: http://www.string_rep().2. public: // . hogy pontosan meghatározható. Ezt elérhetjük típuskényszerítés alkalmazásával is: string Date::string_rep() const { if (cache_valid == false) { Date* th = const_cast<Date*>(this). Másrészt a gyorsítótárat fel kell tölteni a használat elõtt. ezért világos.string_rep() hívás nem biztos. string s2 = d2. string string_rep() const. Ezért a d2. }.string_rep(). A mutable minõsítõ Az elõbb leírt típuskényszerítés (a const minõsítõ átalakítása) és a vele járó.. Ám d2-t konstansként adtuk meg és az adott nyelvi változat esetleg valamilyen memória-védelmet alkalmaz az állandó értékek megõrzésére. string s1 = d1.2. így a dolog mûködik. és nem biztos. Ez aligha elegáns megoldás. az adott nyelvi változattól független eredménnyel fog járni. void compute_cache_value() const.7.2.. hogy egy Date* típusú mutatót kapjunk a this-re.. hogy konstans tagfüggvénynek kell lennie. hogy egy eredetileg is állandóként megadott objektum esetében is mûködik: Date d1. 10. th->cache_valid = true. megvalósítástól függõ viselkedés elkerülhetõ. ha a gyorsítótárba kerülõ adatokat „változékony”-ként (mutable) adjuk meg: class Date { mutable bool cache_valid. // nem meghatározott viselkedés A d1 változó esetében a string_rep() egyszerûen az eredeti típusra alakít vissza. mutable string cache. // .1) használtuk.. // (változékony) gyorstár feltöltése // ábrázolás karakterlánccal . } // konstans elvetése Vagyis a const_cast operátort (§15.

jobb a változó adatrészt külön objektumba tenni és közvetett úton elérni. cache_valid = true. Ha az objektum logikailag változatlan marad. Ezzel a módszerrel a gyorsítótárba helyezett karakterláncot tartalmazó program így írható meg: struct cache { bool valid. string string_rep() const. class Date { cache* c. } // kezdeti értékadás a konstruktorban (§10. ha az ábrázolásnak csak egy része változhat.. }.hu 308 Absztrakciós módszerek A mutable tárolási minõsítés azt jelenti. } return cache. hogy akkor is módosítható legyen.Forrás: http://www.6) // a gyorstár által mutatott elem feltöltése // ábrázolás karakterlánccal . const Date d4. }. string rep. } return c->rep. ha konstans objektum.string_rep(). c->valid = true. hogy „soha nem állandó”. } Ezáltal a string_rep()-et megfelelõen használatba vehetjük: Date d3. string Date::string_rep() const { if (!c->valid) { compute_cache_value().doksi. void compute_cache_value() const. string s4 = d4... de a tagok többsége módosulhat.string_rep(). public: // .. string s3 = d3. // . // rendben! A tagok változékonyként való megadása akkor alkalmas leginkább. hogy a tagot úgy kell tárolni.4. Ezt felhasználva egyszerûsíthetünk a string_rep() meghatározásán: string Date::string_rep() const { if (!cache_valid) { compute_cache_value(). Vagyis a mutable azt jelenti.

egyszerûen rövidítése az alábbinak: class s { public: .7. 10. Osztályok 309 A gyorsítótárat támogató eljárások az ún.8. }. y. int mm. .1). olyan osztály. A private: elérhetõségi minõsítés annak jelzésére használható. // n év hozzáadása struct Date2 { private: int d. A konstruktorok és lekérdezõ függvények nagyon hasznosak lehetnek a struktúrák számára is. int yy). public: Date2(int dd. hogy a nevek különböznek. Struktúrák és osztályok Definíció szerint a struktúra (struct). m. int mm.. m. „lusta” vagy takaros kiértékelés (lazy evaluation) különféle formáira is átvihetõk. Attól eltekintve.Forrás: http://www. amelyekben minden tag nyilvános.. mintsem a típus tulajdonságait garantálják (mint az invariánsok. void add_year(int n). csak adatszerkezetek. Én általában azokat az osztályokat adom meg struct-ként.3. }. public: Date1(int dd. a public: pedig azt mondja. int yy).. mint amik nem igazi típusok. Vagyis a struct s { . az alábbi deklarációk egyenértékûek: class Date1 { int d. hogy a következõ tagok privát elérésûek. hogy a következõ tagok nyilvánosak. melynek tagjai alapértelmezés szerint nyilvánosak. // n év hozzáadása A választott stílust csak a körülmények és az egyéni ízlés határozza meg.doksi.hu 10. void add_year(int n). de inkább csak jelölésbeli könnyebbséget jelentenek. lásd §24.2. Ezekre az osztályokra úgy gondolok. y..

de gyakran használt függvények számára hasznos. private: int d. // n év hozzáadása Ha azonban a deklaráció több nyilvános részt is tartalmaz (mint a Date4 osztálynál). Az elérhetõségi minõsítéseket az osztálydeklarációkon belül többször is használhatjuk: class Date4 { public: Date4(int dd. Mindazonáltal a számítógép által elkészített kódok számára kedvezõ.hu 310 Absztrakciós módszerek Az osztályokban nem szükséges elõször az adattagokat megadni.3).doksi. rendszerint a Date3 stílusát részesítem elõnyben. int mm. amelyben szerepel. int mm. m. sõt.2. Ahhoz az osztály-definicióhoz hasonlóan. azaz a fordítóprogram a függvény meghívása helyett közvetlenül beilleszti a kódot. mint a tankönyvi példákban. Persze az osztályhoz hasonlóan jelentésének minden felhasználásakor azonosnak kell lennie (§9.Forrás: http://www. public: void add_year(int n). .2. y. int yy). y. }. Több privát rész használata szintén ezt eredményezi. ahol általában mind a nyilvános felület. }. hogy kihangsúlyozzuk a nyilvános felhasználói felületet alkotó függvényeket: class Date3 { public: Date3(int dd. m. sokszor jobb azokat a deklaráció végére tenni. akkor a kód zavarossá válhat. hogy az elérhetõségi minõsítések ismétlõdhetnek. int yy). mind a tényleges megvalósítás terjedelmesebb. private: int d. // n év hozzáadása Valódi kódban. Vagyis az osztály meghatározásán belüli kifejtés kicsi. Osztályon belüli függvénydefiníciók Az osztályon belül definiált (nem csak deklarált) függvények helyben kifejtett (inline) tagfüggvénynek számítanak. az osztályon belül kifejtett függvény is szerepelhet több fordítási egységben (az #include utasítással beépítve). 10. void add_year(int n).9.

koordinátákat. hogy egy program közvetetten támaszkodik ezen típusok némelyikére és még többre közvetlenül. A kódot olvasó programozót azonban ez megzavarhatja. kapcsolatokat. vektorokat és tömböket. komplex számokat..3.. m. mely szerint az adattagokat az osztály definiciójának végére helyezzük. de sûrûn elõforduló elvont fogalmakat. (érték–egység) párokat. } 10. ezért én vagy elõreveszem az adatokat. Gyakran elõfordul. mintha az osztály definiciója már a tagfüggvény-törzsek beolvasása elõtt teljes lett volna. Hatékony felhasználói típusok Az elõzõ Date osztály példáján bemutattuk az osztályok meghatározásához szükséges alapvetõ nyelvi elemeket. Osztályok 311 Az a stílus.Forrás: http://www. // zavaró lehet // return Date::d Ez szabályos C++-kód. csomópontokat. lemezcímeket.. forráskód-helyeket. (mutató–eltolás (offset)) párokat. vagy az „inline” tagfüggvényeket az osztály után fejtem ki: class Date { public: int day() const. inline int Date::day() const { return d. } // . kisebb gondhoz vezet az adatábrázolást felhasználó nyilvános „inline” függvények tekintetében. }.hu 10. hogy az egyes nyelvi elemek hogyan támogatják ezt. Számos program használ egyszerû. törtrésszel bíró számokat. vonalakat. y. BCD karaktereket. Most az egyszerû és hatékony tervezésre helyezzük a hangsúlyt és azt mutatjuk be. pénznemeket. idõpontokat. könyvtárak közvetítésével.doksi. konkrét típusokkal ábrázolva: latin vagy kínai karaktereket. karakterláncokat. lebegõpontos számokat.. mutatókat. rögzített pontos számokat. private: int d. mivel egy osztály egy tagfüggvénye az osztály minden tagjára hivatkozhat. pontokat. értékkészleteket. y. téglalapokat. // . . private: int d. }. dátumokat. m. Vegyük ezt a példát: class Date { public: int day() const { return d.

3). mint a bonyolult és körmönfont.2. m. // függvények a Date módosításához Date& add_year(int n). sep. Date(int dd =0. hogy az ilyen felhasználói típusok megadását és hatékony használatát is támogassa. feb. Month. class Bad_date { }. A C++ nyelv egyik kifejezett célja volt. static void set_default(int. Tehát szükség van olyan eljárásokra. Az ilyen típusokat konkrét típusoknak vagy konkrét osztályoknak hívjuk.Forrás: http://www. int year() const. Month month() const. Date& add_month(int n). static Date default_date.hu 312 Absztrakciós módszerek A C++ más programozási nyelvekkel egyetemben közvetlenül támogat néhányat a fenti típusok közül. string string_rep() const. // függvények a Date vizsgálatához int day() const. jul. Month mm =Month(0). hogy megkülönböztessük õket az absztrakt (elvont) osztályoktól (§12. melyekkel a felhasználó adott célú típusokat adhat meg. mert ezek az „elegáns” programozás alapkövei.doksi. jun. Egy általános célú programozási nyelv tervezõje nem is láthatja elõre az egyes alkalmazások igényeit. private: int d. may.4 és §12. nov. Mint általában.4). Ennek fényében készítsünk egy jobb dátumosztályt: class Date { public: // nyilvános felület enum Month { jan=1. hogy az egyszerû és földhözragadt sokkal jelentõsebb. itt is érvényes. // n év hozzáadása // n hónap hozzáadása // n nap hozzáadása // ábrázolás // ábrázolás karakterlánccal // ábrázolás C stílusú karakterlánccal // kivételosztály // 0 jelentése "vedd az // alapértelmezettet" . dec }. illetve az osztályhierarchiák osztályaitól (§12. int yy =0). }. void char_rep(char s[ ]) const. ám számuk miatt nem lehetséges az összeset közvetlenül támogatni. oct. aug. Date& add_day(int n). mar. int). y. apr.

Egy konstruktor. mint önálló típusokat bevezessem.2.1-ben említett gyorsítótáras (cache) módszer egy ilyen egyszerû típusnál felesleges.Forrás: http://www. Ezek const minõsítése jelzi. A Month (hónap) típust azért vezettem be.jan. Gondolkodtam azon. A következõk szerepelnek benne: 1.6)-nak kell-e írnunk.7.jul. hogy nem módosítják annak az objektumnak vagy változónak az állapotát. kiegészíthetjük vele az osztályt. A Bad_date osztály. Lekérdezõ függvények. melyekkel egy Date-et megvizsgálhatunk. Ezek a típusok azonban nem lennének annyira hasznosak. így kihagytam. Osztályok 313 A végezhetõ mûveletek ilyen halmaza meglehetõsen jellemzõ a felhasználói adattípusokra. mint a 27-ik év július 26-ika.1. hogy emlékeznünk kell rá: vajon június 7-ét amerikai stílusban Date(6.hu 10.1995) összekeveredésének veszélyét elkerüljem.doksi. melyek az ábrázolás vagy a konkrét megvalósítás ismerete. Ha mégis szükséges.) Az alapértelmezett dátumot mint érvényes Date objektumot definiálni kell valahol: Date Date::default_date(22. Az alapértelmezett paraméter-értékek kezelésére is gondoltam. melyek segítségével a Date-ek szabadon másolhatók. amelyre meghívták õket. 4. mellyel a hibák mint kivételek jelezhetõk. Majdnem minden ilyen hiba amúgy is kiderül futási idõben – nemigen dolgozom olyan dátumokkal.7. hogy jobb történész szakértõkre bízni. mint a felhasználói felületet nem érintõ megvalósítási részlettel.jul. . A Date objektumokat és változókat kezelõ függvények. Az 1800 elõtti történelmi dátumok kezelése annyira bonyolult. mint a Month. (Egy alkalmas Year típus meghatározására nézve lásd: §11. 3. Automatikusan definiált mûveletek. illetve az egyes elemek szerepével való bajlódás nélkül is meghívhatók. hogy a napok és évek ábrázolására a Day-t és a Year-t. hogy kezeljem azt a problémát.27) és a Date(27. Ezenkívül pedig egy „valahanyadikát” nem lehet rendesen ellenõrizni a hónap és az év ismerete nélkül. amit az okoz. 5.1901). A §10. hogy a Date(1995.7)-nek vagy európai stílusban Date(7. amely kezdõértéket ad az objektumoknak és változóknak 2. ezzel külön eljárás foglalkozik.

hogy a hónapok különbözõ hosszúságúak és így tovább (lásd a §10.[2]). hogy a dátum fogalma „szétszóródna”. Az év-hónap-nap adatábrázolás ráadásul sok program számára szegényes. month. csak a kijelölt függvényeket kell módosítanunk.. Ez pedig azzal járna. hogy lehet Date-eket használni: void f(Date& d) { Date lvb_day = Date(16.add_day(1). mégis gondot igényel úgy megírni. vagy külön függvényekben. } if (midnight()) d. akkor minden felhasználónak magának kellene a Date-ek összetevõit kezelnie: vagy közvetlenül. (ezt a §10. }. így meg kell adni.és hagynánk.. Az f() nem tagfüggvénye Date-nek.3-ban valóban meg is tesszük).hu 314 Absztrakciós módszerek Íme egy kicsi elméleti példa arra. az szükségszerûen külön munkát igényel tõlük.Forrás: http://www. azzal a ténnyel.day()==29 && d. De ha ezt tennénk. hogy megváltoztatjuk. így azt nehezebb lenne megérteni.doksi.d. Ha viszont úgy döntünk. Ha a Date-et például az 1970. struct Date { int day. Miért éri meg egy külön típust megadni egy olyan egyszerû dolog számára.6.. . dokumentálni és módosítani.. hogy a Date-nek és nem valami másnak a feb-jérõl van szó. .3..6[1]-es feladatot). hogy a << kimeneti és a + összeadó mûvelet a Date-ekre definiált. } cout << "A következõ nap:" << d+1 << '\n'.. if (d. Ezenkívül bár a Date típus látszólag egyszerû. mint egy dátum? Végül is beérhetnénk egy egyszerû adatszerkezettel. Ha egy fogalmat egyszerû adatszerkezetként bocsátunk a felhasználók rendelkezésére.year()). csak a Date tagfüggvényeit kellene megváltoztatnunk (§10. Feltételezzük. year. Figyeljük meg a Date::feb jelölést. január elseje utáni vagy elõtti napok számával akarnánk ábrázolni.Date::dec.month()==Date::feb) { // . mit csinálnak vele. Például egy Date objektum növeléséhez szökõévekkel kell törõdni. hogy a programozók döntsék el. hogy helyesen mûködjék.

fejezet). amit nem lehet figyelmen kívül hagyni. Ha nem. mint például a Date(30. if (dd == 0) dd = default_date. Más szóval a konstruktor felállítja az osztályra jellemzõ invariánst (ebben az esetben azt. Ez meglehetõsen jellemzõ eljárásmód. hogy olyan jellegû hiba történt. default: throw Bad_date().Date::Feb. 14. Osztályok 315 10.1-es pontot).day().hu 10. int max. . a kezdeti értékadás megtörténik. hogy egy érvényes dátumról van szó). break.month(). int yy) { if (yy == 0) yy = default_date. switch (mm) { case feb: max = 28+leapyear(yy). Ez a tervezési módszer óriási mértékben leegyszerûsítheti a kódot (lásd a 24. case jan: case mar: case may: case jul: case aug: case oct: case dec: max = 31. break.1. Tagfüggvények Természetesen minden tagfüggvényt ki kell fejteni valahol. case apr: case jun: case sep: case nov: max = 30. Másfelõl ha a Date objektum már létrejött. Íme a Date konstruktorának definíciója: Date::Date(int dd. A többi tagfüggvény számíthat erre az állapotra és kötelessége fenntartani azt. amely jelzi. } A konstruktor ellenõrzi.3. d = dd. akkor az további ellenõrzés nélkül felhasználható és másolható. break. y = yy.doksi. hogy a kapott adatok érvényes dátumot adnak-e.3.year().1994) esetében. Ha a kapott adatok elfogadhatóak. // valaki csalt } if (dd<1 || max<dd) throw Bad_date().7.Forrás: http://www. kivételt vált ki (§8. m = mm.3. if (mm == 0) mm = default_date. Month mm.

hogy 13 hónap van egy évben.8). de jobb egy nyilvánvalóan érvénytelen értéket használni erre a célra.Forrás: http://www. int mm = m+n%12. egy is_date() függvénybe teszem. Gondolkodtam azon. try { cin >> d. mm -= 12.hu 316 Absztrakciós módszerek A Month(0) értéket (amely nem jelent igazi hónapot) a „vegyük az alapértelmezett hónapot” jelzésére használjuk. amely bonyolultabb és kevésbé hatékony. a tagfüggvények meghatározása a triviális és a nem túl bonyolult között mozog. mint hogy olyan látszatot keltsünk.doksi. } Date& Date::add_month(int n) { if (n==0) return *this. mert az a Month felsorolás biztosított garantált értéktartományba esik (§4. Tegyük fel például. A Month felsorolásban megadhatnánk egy értéket kifejezetten ennek jelzésére. de ez olyan kódhoz vezetne. } } // lásd §3. mint a kivételek elkapásán alapuló.7.push_back(d). } catch (Date::Bad_date) { // saját hibakezelõ continue. hogy a 0 értéket azért használhatjuk. hogy az adatellenõrzést külön. if (12 < mm) { delta_y++. } aa. } // megjegyzés: int(dec)==12 . Vegyük észre. if (n>0) { int delta_y = n/12. hogy a >> mûvelet értelmezett a Date osztályra: void fill(vector<Date>& aa) { while (cin) { Date d. Például: inline int Date::day() const { return d.3 Mint az ilyen egyszerû konkrét osztályok esetében szokásos.

Forrás: http://www. Segédfüggvények Egy osztályhoz általában számos olyan függvény tartozhat..3. int diff(Date a. bool leapyear(int y). Hogyan kapcsolódnak az ilyen segédfüggvények a Date osztályhoz? Hagyományosan a deklarációjukat az osztály deklarációjával azonos fájlba tennénk.a] tartományban bool leapyear(int y). } // negatív n kezelése } return *this. akiknek szükségük van a Date osztályra. m = Month(mm). 10. az bonyolultabbá tenné az osztály felületét és a belsõ adatábrázolás esetleges módosításakor több függvényt kellene ellenõrizni. hogy az osztályt és segédfüggvényeit egy névtérbe foglaljuk (§8.b] vagy [b. Date b). melyeket nem szükséges magában az osztályban tagként megadni. így azon felhasználók számára. Date next_saturday(Date d).h fejállomány használata mellett vagy helyett a segédfüggvények és az osztály kapcsolatát úgy tehetjük nyilvánvalóvá.2): namespace Chrono { class Date { /* . // napok száma az [a. amikor Month(mm)-nek nincs d napja y += delta_y.h" A Date. Ha ezeket a függvényeket magában az osztályban fejtenénk ki.2.hu 10.. */}. mert nincs szükségük a belsõ adatábrázolás közvetlen elérésére: int diff(Date a. // dátumkezelõ szolgáltatások .1): #include "Date.doksi. rögtön ezek is rendelkezésre állnának a felületet leíró fejállomány beépítése után (§9. Date b). return *this. Osztályok 317 // most azok az esetek jönnek. Date next_weekday(Date d).2.

Egy egyetlen osztályt tartalmazó névtér használata általában csak túlbonyolított.doksi. int n).1) és a függvényszerû objektumok (§18.month() && a. Date). Ám sok típus – például a komplex számok (§11. Date& operator-=(Date& d. Date operator-(Date d. Date b) // egyenlõség { return a. Az operátorok túlterhelésével a 11.. bool operator>(Date. ostream& operator<<(ostream&.Forrás: http://www. Date& operator++(Date& d). például a Time (Idõ) és Stopwatch (Stopper) osztályokat és azok segédfüggvényeit is.3). A Chrono névtér természetesen a többi kapcsolódó osztályt is tartalmazná.3. Date).year(). Date d). } Egyéb kézenfekvõ jelöltek: bool operator!=(Date. Operátorok túlterhelése Gyakran hasznos lehet olyan függvényeket felvenni.3. int n). // . bool operator<(Date. Date& d).month()==b. // .7. kényelmetlen kódhoz vezet.year()==b. int n). amelyek a hagyományos jelölésmód használatát biztosítják.hu 318 Absztrakciós módszerek } Date next_weekday(Date d)..day()==b. a vektorok (§3. Date& operator--(Date& d). Az operator== függvény például lehetõvé teszi az == egyenlõségi operátor használatát a Date objektumokra: inline bool operator==(Date a. hogy szinte kötelezõ megadni õket. fejezet foglalkozik. Date& operator+=(Date& d. Date).4) – esetében ezek használata annyira beidegzõdött a felhasználóknál. int n).. // egyenlõtlenség // kisebb // nagyobb // Date növelése egy nappal // Date csökkentése egy nappal // n nap hozzáadása // n nap kivonása // n nap hozzáadása // n nap kivonása // d kiírása // beolvasás d-be A Date osztály számára ezen operátorok használhatósága pusztán kényelmi szempontnak tûnik. istream& operator>>(istream&.. Date operator+(Date d.day() && a. 10. Date next_saturday(Date d). .

3. mint az int vagy a float. §12. public: Date_and_time(Date d.. Így a konkrét osztályokat nem szánjuk arra sem.5. A 12. a típust pontosan úgy használhatjuk fel az új típus megvalósításához.2. A Vec osztályt például a vector alapján készíthetjük el (§3.4. mint egy int-et: class Date_and_time { private: Date d. hogy csak az eltéréseket kell leírnunk. Time t. Time t). viszonylag egyszerû dolgot jól és hatékonyan csináljanak. Ha nem tetszik egy konkrét típus viselkedése. külön erõfeszítés nélkül megoldható. fejezetben tárgyalt öröklõdési eljárást úgy használhatjuk fel egy új típus meghatározására. Ez az adott típus „újrahasznosításával” is elérhetjük.hu 10.2). amit gyakran objektum-orientált programozásnak hívnak (§2. Általában nem cél. hogy többalakú (polimorf) viselkedést tanúsítsanak (§2.7. Használati modelljük és mögötte levõ „filozófia” nagyon különbözik attól. ami a kívánalmaknak megfelelõen mûködik. a Date-hez hasonló konkrét osztály használata nem jár a szükséges tárolóhely vagy a futási idõ rejtett növekedésével. használatukat pedig értékközpontú programozásnak (value-oriented programming). azaz a szabad tárat érintõ mûveletek nélkül. hogy a felhasználónak eszközt adjunk a kezébe egy konkrét osztály viselkedésének megváltoztatására.6).doksi.5. hogy megkülönböztessem azokat az absztrakt osztályoktól (§2.3). Egy valamirevaló fordítóprogrammal egy.5.Forrás: http://www. A konkrét osztályok mérete fordítási idõben ismert. például a C-vel vagy a Fortrannal való összeegyeztetése is hasonlóan könnyen. Time t)..6. Ezeket értéktípusoknak (value types) is nevezik. így a helyben fordítás egyszerû feladat. Date_and_time(int d. A konkrét osztályok jelentõsége Azért hívjuk a Date és más egyszerû felhasználói típusokat konkrét típusoknak. }. Osztályok 319 10. A konkrét osztályok dolga az. illetve hogy hangsúlyozzam az olyan beépített típusokkal való hasonlóságukat.4) és az osztályhierarchiáktól (12. A memóriakiosztásnak más nyelvekkel. . // . ezért az objektumok számára helyet foglalhatunk a futási veremben is. akkor írhatunk egy másikat. Date::Month m. int y. hogy egyetlen.2). A memóriakiosztás is ismert.

Következésképpen némelyik osztálynak szüksége van egy olyan függvényre. amely biztosan meghívódik. A konkrét típusok hiánya másfelõl zavaros programokat eredményez.11). Esetenként az ilyen környezet létrehozása valamilyen erõforrás – fájl. amit a használat után fel kell szabadítani (§14. zár. amikor egy automatikus változót tartalmazó blokk lefut. Ha a Table objektum bármilyen módon törlõdik. A destruktorok automatikusan meghívódnak. akkor a túl általános és „költséges” osztályok használata komoly futási idõbeli és tárfelhasználás-beli pazarláshoz vezethet. amikor egy objektum létrejön: ezek a destruktor (megsemmisítõ. Vegyünk például egy valamilyen Name típusú elemek táblázatát tartalmazó Table osztályt. Ha egy alkalmazásban nincsenek meg a megfelelõ kicsi. }. A destruktor legjellemzõbb feladata. a rájuk vonatkozó szabályokat. illetve azt. amikor egy objektum megsemmisül.1.4. Ezt úgy érhetjük el.doksi. hogy máshol fel lehessen majd használni. 10. memóriaterület – lefoglalásával jár. Objektumok Objektumok többféleképpen jöhetnek létre: lehetnek automatikus vagy globális változók. Feladatuk általában a rendbetétel és az erõforrások felszabadítása.4. A konstruktornak le kell foglalnia az elemek tárolásához szükséges memóriát.hu 320 Absztrakciós módszerek Az ilyen egyszerû típusok megfelelõ halmaza teljes programok alapjául szolgálhat. amelyben a tagfüggvények mûködnek. Destruktorok Az objektumok kezdõállapotát a konstruktorok állítják be. // . hogy a programozó kifejezetten meghívja a destruktort (§10. destructor) függvények. ahogy a konstruktor meghívására is biztosan sor kerül. 10.7).4. hogy megírjuk a konstruktort kiegészítõ függvényt: class Name { const char* s.. a memóriát fel kell szabadítani.. Nagyon különleges esetben van csak szükség arra. hasonlóan ahhoz. hogy minden programozó megírja az „egyszerû és sûrûn használt” adatszerkezeteket közvetlenül kezelõ kódot.4. hogy felszabadítsa a konstruktorban lefoglalt memóriaterületet. az objektumok kezdõállapotát beállító konstruktorokat és a használatból kikerülõ objektumok „eltakarítására” szolgáló destruktorokat tárgyaljuk. vagyis a konstruktorok hozzák létre azt a környezetet. de hatékony típusok. .Forrás: http://www. Az alábbiakban ezeket a lehetõségeket. egy dinamikusan létrehozott objektumot törölnek és így tovább. osztályok tagjai stb.

2) alapértelmezett konstruktorát: struct Tables { int i. // konstruktor // destruktor }. . Table t1.Forrás: http://www.) A leírtak alkalmazhatóak a destruktor nélküli osztályokra is. mint amelyeknél egy olyan destruktorunk van. } Name* lookup(const char *). Osztályok 321 class Table { Name* p.2.2. A destruktort jelentõ ~Table() jelölés a komplemensképzést jelölõ ~ szimbólumot használva utal a destruktornak a Table() konstruktorhoz való viszonyára. amely nem csinál semmit. Table vt[10]. size_t sz. máskülönben szükség esetén megpróbál létrehozni egyet. a Table::Table(size_t) függvény alapértelmezett konstruktor. amelyiket paraméter nélkül hívhatjuk meg. ezért a programozó a következõkben leírtakra támaszkodik. A standard könyvtár tárolói. Az összetartozó konstruktor–destruktor pár meghatározása a C++-ban szokásos eljárás változó méretû objektumok megvalósítására. például a map. amikor valamelyik standard könyvtárbeli tárolót használja. mint amelynek van alapértelmezett konstruktora. A fordítóprogram által létrehozott alapértelmezett konstruktor automatikusan meghívja az osztály típusú tagok és a bázisosztályok (§12.(Így viselkedik például a szabványos string osztály is. }.doksi. Az alapértelmezett konstruktor az.4. bool insert(Name*). Minthogy a fenti példában a 15 mint alapértelmezett érték adott. Ha a programozó megadott alapértelmezett konstruktort. 10.hu 10. } ~Table() { delete[ ] p. hogy az elemeik számára tárolóhelyet biztosítsanak. Tables tt. ennek a módszernek valamelyik változatát használják. akkor a fordítóprogram azt fogja használni. int vi[10]. public: Table(size_t s = 15) { p = new Name[sz = s]. Ezekre úgy tekinthetünk. Alapértelmezett konstruktorok Hasonlóképpen a legtöbb típust úgy tekinthetjük.

t1-re és tt. hogy kapjanak (§5. amelyen belül a deklaráció szerepelt.6.3.4. 10.i és tt.9 . illetve semmisül meg. amely a new operátor használatával jön létre és a delete operátor használatával semmisül meg. amikor a program végrehajtása során elõször találkozik a deklarációjával és egyszer semmisül meg: a program befejezésekor. amikor a program végrehajtása során deklarációja kiértékelõdik.4. Nem statikus tagobjektumként. §5. X x.4. const int& r. amikor a program kilép abból a blokkból.4. }.2. az ilyeneket tartalmazó tagoknak nem lehet alapértelmezett konstruktora. Lokális statikus objektumként. amely akkor keletkezik.5.1): struct X { const int a. Másrészt tt.4).5 §10. hacsak a programozó kifejezetten nem gondoskodik konstruktorról (§10.6 §10.4.vi elemei nem kapnak kezdõértéket. a program indulásakor jön létre és a program befejezésekor semmisül meg.hu 322 Absztrakciós módszerek Itt tt kezdõértékkel való feltöltése fordítás közben létrehozott alapértelmezett konstruktor segítségével történik. amely egy másik osztály objektum tagjaként jön létre és azzal együtt keletkezik. Létrehozás és megsemmisítés Tekintsük át a különbözõ módokat: hogyan hozhatunk létre objektumot és késõbb az hogyan semmisül meg. amely akkor keletkezik és semmisül meg. Mivel a const-ok és a referenciák kötelezõen kezdõértéket kell. amikor a tömb. amely egyszer. Az osztályok és a beépített típusok egymástól eltérõ kezelésmódjának a Cvel való egyeztetés és a futási idõ növelésétõl való tartózkodás az oka.10).4.4.7 §10. Objektum a következõ módokon hozható létre: §10. Tömbelemként.doksi. // hiba: nincs alapértelmezett konstruktor X számára Az alapértelmezett konstruktorok közvetlen módon is hívhatók (§10. melynek eleme. és akkor semmisül meg. Globális.4. §10. mert ezek az objektumok nem osztály típusúak.8).Forrás: http://www. Szabad tárbeli objektumként.8 §10. amely a Table(15)-öt hívja meg tt. amely akkor jön létre.4 Névvel ellátott automatikus objektumként. névtérbeli vagy statikus osztály-objektumként.vt minden egyes elemére. A beépített típusoknak szintén van alapértelmezett konstruktoruk (§6.4.

aa sorrendben semmisülnek meg..Forrás: http://www.4.2. §10. A tagonkénti másolás rendszerint nem megfelelõ olyan objektumok számára.4.doksi.5). semmisül meg. sem destruktora..hu 10. melyben elõfordult.10 Ideiglenes objektumként. Ez a felsorolás nagyjából a fontosság sorrendjében készült. Table bb. Ha nem bíráljuk felül ezt az alapértelmezett viselkedést.4. } Itt aa. a memóriába helyezett objektumként. és dd létrejötte elõtt semmisül meg. amelynek nem lehet sem konstruktora. Objektumok másolása Ha t1 és t2 a Table osztályba tartozó objektumok.4. t2=t1 alapértelmezés szerint t1-nek tagonkénti átmásolását jelenti t2-be (§10. amely egy kifejezés kiértékelésekor jön létre és a teljes kifejezés végén. valahányszor a vezérlés fonala „keresztülhalad” a változó deklarációján.4.1. bb. Lokális változók A lokális változók konstruktora minden alkalommal végrehajtódik. } Table dd. §10. A lokális változók destruktorai konstruktoraik sorrendjéhez viszonyítva fordított sorrendben hajtódnak végre: void f(int i) { Table aa. 10.. bb és dd ebben a sorrendben keletkeznek az f() meghívásakor és a dd. amelyek egy konstruktor–destruktor pár által kezelt erõforrásokat tartalmaznak: . melynek mutató tagjai vannak. a destruktor végrehajtására pedig akkor kerül sor. paraméterekkel vezérelt lefoglalási mûvelet segítségével nyert. 10.12 Unió tagjaként.4. // . A következõ alpontokban részletesen elmagyarázzuk az objektumok létrehozásának ezen változatait és használatukat.11 Felhasználó által írt függvénnyel végzett. a cc a bb után jön létre. amikor kilépünk a változó blokkjából. Ha egy hívásnál i>0. // .4. if (i>0) { Table cc. Osztályok 323 §10.. ha olyan osztály objektumaira alkalmazzuk. amikor a vezérlés kilép az f()-bõl. meglepõ (és rendszerint nemkívánatos) hatás léphet fel.

. így az általa elfoglalt tárterület a program számára örökre elvész. // másoló konstruktor // másoló értékadás A programozó bármilyen alkalmas jelentést meghatározhat ezen másoló mûveletek számára. Alapértelmezés szerint az értékadás tagonkénti másolást jelent. A t2-re nem hívódik meg. Table t3. i<sz. t2-re és t3-ra is. t2 és t3 mindegyike arra a névtömbre hivatkozó mutatót fogja tartalmazni. ha megadjuk. mert ez a változó a t1-bõl való másolással kapott kezdõértéket. } t3 = t2. A Table destruktor viszont háromszor hívódik meg: t1-re.hu 324 Absztrakciós módszerek void h() { Table t1. Table t2 = t1.4. nem marad meg. tehát háromszor is törlõdik. Az ilyen anomáliák elkerülhetõk. mert a t3=t2 értékadás következtében felülíródik.Forrás: http://www. // másoló konstruktor // értékadás // óvakodjunk az ön-értékadástól: t = t . amely t1 létrejöttekor kapott helyet a szabad tárban. A mutató.. lásd §11. mely a t3 létrejöttekor kijelölt névtömbre mutat. for (int i = 0. Ez nem meghatározott és valószínûleg katasztrofális eredményhez vezet.12): Table::Table(const Table& t) { p = new Name[sz=t. Table& operator=(const Table&). t2-ben és t3-ban egyaránt megjelenik. hacsak nincs automatikus szemétgyûjtés (§10. mit jelent egy Table objektum másolása: class Table { // . // kezdeti értékadás másolással: problémás // értékadás másolással: problémás Itt a Table alapértelmezett konstruktora kétszer hívódik meg: egyszer t1-re és egyszer t3-ra.p[i]. mintha ez a másolás megtörtént volna. Table(const Table&).doksi.sz]. Másrészt a t1 részére létrehozott tömb t1-ben. így a h() függvény végén t1. hogy lemásolja a tartalmazott elemeket (vagy legalábbis a felhasználó számára úgy tesz. de az ilyen típusú tárolók esetében a másoló mûvelet hagyományos feladata az. } Table& Table::operator=(const Table& t) { if (this != &t) { delete[ ] p.5). }. i++) p[i] = t.

a szabad tárban létrehozott objektumok konstruktorát a new operátor hívja meg. de az értékadó operátor általános célja egyszerû: védekezni kell a saját magával való értékadás ellen.p[i]. a program viselkedése nem meghatározott és nagy valószínûséggel katasztrofális lesz.Forrás: http://www.6. p = new Name[sz=t.5. hogy a másoló konstruktor le nem foglalt memóriát készít fel a felhasználásra. és ezek az objektumok addig léteznek. mindössze a memória pazarlása. A p által mutatott objektum kétszeri törlése súlyos hiba. . Ennek alapvetõ oka az. míg az értékadó mûveletnek egy már létrehozott objektumot kell helyesen kezelnie.4. elõkészíteni és bemásolni az új elemeket. i++) p[i] = t. delete p. delete p. Table* q = new Table.sz]. a másoló konstruktor és az értékadó mûvelet itt is jelentõsen eltér.doksi. // valószínûleg futási idejû hibát okoz } A Table::Table() konstruktort kétszer hívjuk meg. Az értékadást bizonyos esetekben optimalizálni lehet. Osztályok 325 } } return *this. for (int i = 0. mint a Table::~Table() destruktort. csakúgy. A szabad tár A dinamikusan kezelt memóriaterületen.) 10.3. Nyelvi szempontból egy objektum nem törlése nem hiba. Általában minden nem statikus tagot másolni kell (§10. i<sz.hu 10. Szerencsére léteznek az ilyesfajta memóriaszivárgást keresõ eszközök is. mindazonáltal egy hosszan futó programnál az ilyen „memóriaszivárgás” vagy „memórialyuk” (memory leak) súlyos és nehezen felderíthetõ hiba. Sajnos azonban ebben a példában a new-k és delete-ek nem felelnek meg egymásnak: a p által hivatkozott objektumot kétszer töröltük. míg a q által mutatottat egyszer sem.4. törölni kell a régi elemeket. amíg a rájuk hivatkozó mutatóra nem alkalmazzuk a delete operátort: int main() { Table* p = new Table. Mint majdnem mindig.

A programozó megszabhatja. A legtöbb esetben az objektumok ezen viselkedése csak apróbb kényelmetlenséget jelent. Ennek az egyszerûsítésnek a hordozhatóság elvesztése az ára... amely egy kisebb cégrõl tárolhat adatokat: class Club { string name. founded(fd) { // .2.4.9.1). ahol van szemétgyûjtés. illetve annak a delete-tel való felszabadítása (§6.6.doksi. }. A szabad tárban levõ tömböket a §10.7. A Club osztály konstruktoránál paraméterként meg kell adni a nevet és az alapítás dátumát.5). ott is a csak memória-felszabadítást végzõ destruktorokat lehet megtakarítani. hogyan történjék a new használata esetén a memória lefoglalása. Lehetséges a lefoglalás. members(). a konstruktorok és a kivételek együttmûködésének a megadása is (§14. bármilyen hozzáférési kísérlet az objektumhoz hibának számít. // . Ha van is szemétgyûjtés. Date fd).. } . de viselkedésük nem szabványosított.4.2 és §15.4. tárgyalja. Club(const string& n. ami ilyenkor is súlyos hiba.5 és 19. Table officers.Forrás: http://www. sõt bizonyos programoknál a futási idõ növekedése és a viselkedés megjósolhatatlansága is (§C. 10.hu 326 Absztrakciós módszerek Bizonyos C++-változatok automatikusan újrahasznosítják az elérhetetlen objektumok által elfoglalt memóriát (ezek a szemétgyûjtést alkalmazó megvalósítások). Jelesül. Sajnos az egyes nyelvi változatok nem képesek megbízható módon jelezni az ilyen hibákat.4. Table members.6. így az objektum kétszer törlõdik. officers(). Az osztálytagok konstruktorainak paramétereit a tartalmazó osztály konstruktordefiniciójának tag-kezdõérték listájában (member initializer) adjuk meg: Club::Club(const string& n. Miután egy objektumot a delete mûvelettel töröltünk. Date fd) : name(n).6).. Osztály típusú tagok Nézzünk egy osztályt. Date founded. a delete operátor kétszeri meghívása egyben a destruktor (ha van ilyen) kétszeri meghívását fogja eredményezi.

pc(c) { } }..hu 10.6. A tagok szükségszerû kezdeti értékadása Azon tagok feltöltése kezdõértékkel szükségszerû. X(int ii.doksi.. majd a tagok destruktorai a deklarációval ellentétes sorrendben. const string& n. A konstruktor alulról felfelé haladva (a tagokat elõször) építi fel a tagfüggvények végrehajtási környezetét. } A Table::Table konstruktor a Club::officers tagot mindkét esetben a 15-tel. A tagok destruktorai a konstruktorok sorrendjével ellenkezõ sorrendben hívódnak meg. Club& c) : i(ii). a destruktor pedig felülrõl lefelé (a tagokat utoljára) bontja le azt. 10. az osztály megsemmisítésekor elõször saját destruktor függvényének (ha van ilyen) törzse hívódik meg. Club c. A tagok konstruktorainak végrehajtása megelõzi a tartalmazó osztály saját konstruktora törzsének végrehajtását. Osztályok 327 A tagok kezdõérték-listáját kettõspont elõzi meg és az egyes tagoknak kezdõértéket adó kifejezéseket vesszõk választják el. amelyeknél a kezdeti értékadás különbözik az egyszerû értékadástól – azaz az alapértelmezett konstruktor nélküli osztályba tartozó.. mint alapértelmezett paraméterrel hozza létre. Ha egy tag konstruktorának nincs szüksége paraméterre. Ha egy osztálynak osztály típusú tagjai vannak. A konstruktorok a tagoknak az osztály deklarációjában elfoglalt sorrendjében és nem a kezdõértéket adó kifejezéseknek a listában való felsorolási sorrendjében hajtódnak végre.1. // . Az esetleges zavarok elkerülése érdekében nyilván célszerû a tagokat a deklarációban elfoglalt sorrendjükben felvenni a kezdõérték-adó kifejezések listájára. nem szükséges felvenni a listára. így a következõ kódrészlet egyenértékû az elõzõ példabelivel: Club::Club(const string& n. a const és a referencia típusú tagoké: class X { const int i.. c(n. Club& pc.Forrás: http://www. Date d. Date fd) : name(n). .4.d). founded(fd) { // .

.2. de ne felejtsük el a meghatározást // hiba: nem állandó // hiba: nem statikus // hiba: a kezdõérték-adó nem állandó // hiba: a kezdõérték-adó nem egész értékû Akkor és csak akkor. // rendben: Curious::c1 meghatározott . Person(const string& n.Forrás: http://www. szükséges. hogy egyértelmû legyen a kezdeti értékadás ténye. } Itt a name az n egy másolatával kap kezdõértéket. hogy az ilyen tag (de csak egy helyen) definiált legyen. static const int c4 = f(17). Másfelõl az address elõször egy üres karakterlánccal töltõdik fel. const string& a). const int c3 = 13. // rendben.. A legtöbb típus esetében azonban a programozó választhat a kezdeti és a „sima” értékadás közül. // szükséges. }..doksi. Person(const Person&). ha a kezdõértéket kapott tagot memóriában tárolt objektumként használjuk. }. static int c2 = 11. de ott nem szabad megismételni a kezdõérték-adó kifejezést: const int Curious::c1.6. static const float c5 = 7. Ez a módszer ráadásul hatékonyabb is: class Person { string name. Ilyenkor én általában a tag-kezdõérték listás megoldást választom. és hiba azt nem megtenni is.hu 328 Absztrakciós módszerek Ezen tagok kezdeti értékadására nincs egyéb lehetõség. de a kezdõérték-adó nem szerepelhet itt még egyszer const int* p = &Curious::c1..0. // . const string& a) : name(n) { address = a. Person::Person(const string& n.4. egész típusú konstans tagot lehetséges a deklarációban egy kezdõérték-adó konstans kifejezéssel is feltölteni: class Curious { public: static const int c1 = 7. string address. // . majd értékül az a egy másolatát kapja. Konstans tagok Egy statikus. 10.

. lebegõpontos számoknak stb.doksi. Ha ez nem lehetséges. Unique_handle a. az ilyen osztályú objektum másolási kísérlete hiba: class Unique_handle { private: // a másoló mûveleteket priváttá tesszük. §15.1) az osztály összes tagját másolja. ha szükséges: class X { enum { c1 = 7.2) Unique_handle(const Unique_handle&).. hogy az osztályban változóknak.4. megelõzendõ az // alapértelmezett másolást (§11. Osztályok 329 Másik megoldásként. jelképes állandóként használhatunk felsoroló konstanst (§4.. konstans.. // .hu 10. adjon kezdõértéket.6. // explicit kezdõértéket igényel // hiba: Y::a nem másolható Ezenkívül az alapértelmezett értékadás nem jöhet létre a fordításkor. §14. struct Y { // . Y y2 = y1. c2 = 11.8. Y y1. Így a programozó nem fog kísértésbe esni.3) is az osztály deklarációján belül.Forrás: http://www.4. vagy olyan felhasználói típus melynek nincsen másoló értékadása.4.4.2. c4 = 17 }. }. 10.3. }.. Unique_handle& operator=(const Unique_handle&).. ha az osztály egy nem statikus tagja: referencia. }. c3 = 13.6. public: // . Tagok másolása Az alapértelmezett másoló konstruktor és az alapértelmezett másoló értékadás (§10. .

így az alapértelmezés szerinti üres karakterláncot kapja kezdõértékként.2. . de sokszor nem erre van szükség egy másoló konstruktorban: Person::Person(const Person& a) : name(a. írjunk olyan alapértelmezett konstruktort. hogy vannak-e olyan felhasználó által megadott konstruktorok.Forrás: http://www.hu 330 Absztrakciós módszerek Jegyezzük meg. void f() { Ibuffer words[100]. azaz paraméter nélkül hívható konstruktora. Ha másoló konstruktort írunk.doksi.. Ha új taggal bõvítünk egy osztályt. Ha feltétlenül szükséges. }.. hogy minden tagot másoljunk. public: Ibuffer() { cin>>buf.1. a 15 értékû alapértelmezett paraméterrel tölt fel. Ez gond lehet. 10. // . } // minden elem a cin-rõl kap kezdõértéket Az ilyen trükköket azonban általában jobb elkerülni.. ne felejtsük el ellenõrizni. A fenti egy 10 Table elembõl álló tömböt hoz létre és minden elemet a Table::Table() konstruktorral. Alapértelmezés szerint az elemek alapértelmezett módon kapnak kezdõértéket. hogy egy tömb tagjai különbözõ kezdõértéket kapjanak. ha a hivatkozott objektumot törölni kell. akkor ilyen osztályú objektumok tömbjét is meghatározhatjuk: Table tbl[10]. §18. } // . hogy a referencia típusú tagok ugyanarra az objektumra hivatkoznak az eredeti objektumban és a másolatban is. amely elõállítja a kívánt értékeket: class Ibuffer { string buf. amelyeket az új tagok kezdeti értékadására és másolására való tekintettel meg kell változtatni.4.6.7) alkalmazásán kívül nincs más mód egy tömb elemeinek konstruktorai számára (nem alapértelmezett) paramétereket megadni. ügyeljünk arra.. Tömbök Ha egy osztály egy tagjának van alapértelmezett. A kezdõérték-lista (§5. amelyet szükséges.name) { } // vigyázat! Itt elfelejtettem az address tagot másolni.7.

Ekkor a nyilvántartás terhét levennénk a programozó válláról.és futási idõ-többletet jelentene.3): void g() { vector<Table>* p1 = new vector<Table>(10). probléma // helytelen. hogy egyedi elemet vagy tömböt kell-e törölni: void f(int sz) { Table* t1 = new Table. ezért a programozónak meg kell adnia. Table* t3 = new Table.doksi. Osztályok 331 Amikor egy tömb megsemmisül.7. // helyes // helyes // helytelen.1. delete t1. mint az elõzõ példa. §16. A C nyelvhez hasonlóan a C++ sem különbözteti meg az egyedi elemre és a tömb kezdõelemére hivatkozó mutatót (§5. de ez a kötelezettség egyes C++-változatokban jelentõs memória. Egyszerû és érdektelen esetekben. az összes elemére meghívódik a destruktor. hogy minden objektumról tartsa nyilván. delete t4. Table* t2 = new Table[sz]. akkor ez automatikusan történik. } . delete p2. ha hibásan használjuk a delete és delete[ ] operátorokat. itt is használhat helyettük olyan osztályokat. delete p1. Ezért a különbözõ változatok különbözõképpen fognak viselkedni. a fordító észreveheti a hibát.hu 10. Table* t4 = new Table[sz].3).Forrás: http://www. de általában futtatáskor fog valami csúnya dolog történni. hogy egyedi objektum avagy tömb. Ha nem new mûvelettel létrehozott tömbrõl van szó. delete[ ] t2. hogy a szabad tártól megköveteljük. A kifejezetten tömbök törlésére szolgáló delete[ ] logikailag nem szükséges. Table* p2 = new Table. probléma } A tömbök és egyedi elemek dinamikus tárterületen való elhelyezése az adott nyelvi változattól függ. mint a vector (§3. Elképzelhetõ lenne. Ha az olvasó túl nehézkesnek találja a C stílusú tömbök használatát. delete[ ] t3.

f(2). } Itt tbl konstruktora f() elsõ meghívásakor hívódik meg. 10. A dinamikus könyvtárak használata (dinamikus csatolás) kissé bonyolultabbá teszi ezt. Nem lokális adatok A függvényeken kívül meghatározott (azaz globális.1... így nem semmisül meg.Forrás: http://www. tbl2 is csak f(1) végrehajtásakor jön létre. és minden létrehozott objektum destruktora a main() függvénybõl való kilépés után végre fog hajtódni. amikor a végrehajtási szál elõször halad keresztül az objektum meghatározásán: void f(int i) { static Table tbl.4.doksi. f(1).. A lokális statikus objektumok destruktorai akkor hívódnak meg.9. Mivel tbl-t statikusként adtuk meg. amikor a program leáll (§9. Hogy pontosan mikor. // .hu 332 Absztrakciós módszerek 10.1).. Mivel a tbl2 változó deklarációját tartalmazó blokk nem hajtódik végre az f(0) meghíváskor.4.4. . az nincs meghatározva. Lokális statikus adatok A lokális statikus objektumok (§7. hiszen ilyenkor a kezdeti értékadásra akkor kerül sor.1.. amikor f()-bõl visszatér a vezérlés és nem jön újra létre f() második meghívásakor. if (i) { static Table tbl2. amikor a dinamikus kód a futó programhoz kapcsolódik. a blokk újbóli végrehajtásakor nem.. // . // . } } int main() { f(0).8.2) konstruktora akkor hajtódik végre. névtérbeli és osztályhoz tartozó statikus) változók a main() függvény meghívása elõtt jönnek létre (és kapnak kezdõértéket).

Nincs nyelvi változattól független meghatározása annak. hogy lefusson a konstruktora és a destruktora: class Zlib_init { Zlib_init().c: Table tbl2.. Vegyük észre. A destruktorok végrehajtási sorrendje is hasonlóan változatfüggõ. Table X::memtbl. X::memtbl..Forrás: http://www. ~Zlib_init(). // Zlib elõkészítése használatra // Zlib utáni takarítás .hu 10. hogy az egyes fordítási egységek nem lokális objektumai milyen sorrendben jönnek létre: // file1. hogy tbl1 vagy tbl2 fog elõbb létrejönni. namespace Z { Table tbl2. A destruktorok a konstruktorokkal ellentétes sorrendben hajtódnak végre: Z::tbl2. Az. static Table memtbl. Dinamikus csatolás használata vagy akár a fordítási folyamat kis módosítása is megváltoztathatja a sorrendet.doksi. } A konstruktorok végrehajtási sorrendje a következõ: tbl. Könyvtárak tervezésekor szükséges vagy egyszerûen kényelmes lehet egy olyan. // file2. hogy a definíció és nem a deklaráció sorrendje számít. Ilyen típusú adatot csak arra célra fogunk használni. Osztályok 333 A fordítási egységeken belül a nem lokális objektumok konstruktorainak végrehajtása a definiciójuk sorrendjében történik: class X { // . hogy egy statikus objektum számára memóriaterületet foglaljunk le azért. }.c: Table tbl1. konstruktorral és destruktorral bíró típus elkészítése. amely kizárólag a kezdeti értékadás és rendrakás célját szolgálja. Table tbl. X::memtbl. }. de a sorrend azon belül is változhat. a C++ adott változatától függ. tbl. Z::tbl2.

de a legtöbb nem. fokozatosan kell létrehozni azokat. } // ..1. . return x. Ha sok függvényben kell lekérdezni az elsõ használatot figyelõ kapcsolót.1 és §21.hu 334 Absztrakciós módszerek class Zlib { static Zlib_init x. hogy az egyes objektumokat függvényekkel helyettesítjük (§9. Egy másik probléma.. } // kezdeti értékadás elsõ használatkor Az elsõ használatot figyelõ kapcsolók nem kezelnek minden elképzelhetõ helyzetet...2). }.4. ahol az ellenõrzés és szükség esetén a kezdeti értékadás túl sok idõt vehet igénybe. hogy a konstruktor nélküli statikus objektumok 0 kezdõértéket kapnak. Lehetséges például olyan objektumokat megadni.doksi.5. ha az objektum elsõ használata egy végrehajtási idõre érzékeny függvényben történik. Ez a módszer azon alapul. Ha mégis ilyen objektumokra van szükség.. de megoldható. az elsõ használatot figyelõ kapcsolót: class Zlib { static bool initialized. A dolog akkor válik igazán problematikussá.2. Egy lehetséges másik megközelítés. Ilyenkor további trükkökre van szükség (§21.4. amelyek a kezdeti értékadás alatt egymásra hivatkoznak – az ilyesmit jobb elkerülni. az fárasztó feladat lehet.. } public: // nincs konstruktor void f() { if (initialized == false) initialize(). hogy egy ilyen objektum kezdeti értékadása az elsõ használat elõtt megtörténik és a destruktor az utolsó használat után fut le. Egyes C++-változatok biztosíthatják ezt. akkor óvatosan. // .5. amit a nyelvi változatok általában a lokális statikus objektumokra alkalmaznak: egy-egy. static void initialize() { /* kezdeti értékadás */ initialized = true. // . Ehelyett lásd §9. Programozói szinten azonban lehetséges azt a megoldást alkalmazni.Forrás: http://www. }. hogy az utolsó használatot nem tudjuk egy jelzõvel jelezni.1): int& obj() { static int x = 0. Sajnos egy több fordítási egységbõl álló program esetében nincs garancia arra.

§22. hogyan kell azt értelmezni.6. maga az if feltétele.1. A cout << cs kimeneti mûvelet mûködhet a várt módon.hu 10.6). következésképpen cs felszabadított memóriaterületre mutat. és annak megsemmisülése után nem biztos. habár ez is elõfordul (§11. Osztályok 335 10. amely az s1+s2 mûvelet eredményét tárolja. így érdemes tudni.c_str(). nullkarakterrel lezárt karaktertömböt ad vissza (§3. A szabványos string osztály c_str() nevû tagfüggvénye egy C stílusú. amelyben az s2+s3-at tartalmazó ideiglenes objektum létrejön.1).4. hogy nem semmisül meg az a terület is.c_str())<8 && cs[0]=='a') { // cs használata } } Az olvasó valószínûleg azt mondja erre.doksi. és egyetértek vele. Vajon hol foglalt helyet a fordító a C stílusú karaktertömb számára? Valószínûleg az s1+s2-t tartalmazó ideiglenes objektumban. §20. de ez puszta szerencse kérdése. hogy az ideiglenes objektumokkal törõdjék. törlõdik a tartalmazó teljes kifejezés kiértékelése végén. Például az x*y+z kifejezés kiértékelése során egy ponton az x*y részeredményt valahol tárolni kell. string osztályú objektum jön létre. Ezek nagyon hasznos dolgok a karakterláncok kezelésekor. A fordítóprogram esetleg felderítheti az ilyen problémát és figyelmeztethet rá.4. Hacsak nem a program gyorsításán dolgozik (§11. A teljes kifejezés olyan kifejezés.10. hacsak nincs referenciához kötve vagy nem egy nevesített objektumnak ad kezdõértéket. a programozó ritkán kell. Az if utasításos példa egy kicsit ravaszabb. de együttes használatuk furcsa problémákhoz vezethet: void f(string& s1. Egy ideiglenes objektum. Elõször egy ideiglenes.5. string& s3) { const char* cs = (s1+s2).4. if (strlen(cs=(s2+s3). Maga a feltétel a várakozásnak megfelelõen fog mûködni. hogy mûködik. . majd a kifejezés végén az ideiglenes objektum törlõdik. amely nem részkifejezése más kifejezésnek.7).Forrás: http://www. mert a teljes kifejezés. de ilyen kódot szoktak írni. Ideiglenes objektumok Ideiglenes objektumok legtöbbször aritmetikai kifejezésekbõl jönnek létre. string& s2. Mindazonáltal az ideiglenes objektum a feltételesen végrehajtandó utasítás végrehajtásának megkezdése elõtt megsemmisül. hogy „nem kell ilyet csinálni”. cout << cs. Ettõl az objektumtól aztán elkérjük a C stílusú karaktertömböt. így a cs változó bármiféle ottani használata nem biztos. A + operátor karakterláncok esetében összefûzést jelöl.

.y)). if (s. string& s3) { cout << s1+s2.Forrás: http://www..length()<8 && s[0]=='a') { // s használata } } Ideiglenes változót használhatunk konstans referencia vagy nevesített objektum kezdõértékeként is: void g(const string&. Az ideiglenes változó megsemmisül. az ideiglenes objektumokkal kapcsolatos probléma abból adódik. string ss = s1+s2. int x. Emlékezzünk arra. amikor az „õ” hivatkozását vagy nevesített objektumát tartalmazó kódblokk lefut.doksi. const string&). } g(s. . } Az ilyen módon létrehozott ideiglenes változók is ugyanolyan szabályok szerint semmisülnek meg.3) és hogy ideiglenes objektumot nem adhatunk egy nem konstans referencia kezdõértékéül (§5. string& s2) { const string& s = s1+s2. hogy egy magasabb szintû adatot alacsony szinten használtunk. Egy tisztább programozási stílus nem csak jobban olvasható programrészletet eredményezett volna.ss). string s = s2+s3. Ideiglenes változót létrehozhatunk kifejezett konstruktorhívással is: void f(Shape& s. mint sok más esetben is. de az ideiglenes objektumokkal kapcsolatos problémákat is teljesen elkerülte volna: void f(string& s1. hogy ebben az esetben. // Point létrehozása a Shape::move() számára // .move(Point(x.5).hu 336 Absztrakciós módszerek Vegyük észre. mint az automatikusan létrehozottak. int y) { s. string& s2. void h(string& s1. // s és ss itt használható Ez a kódrészlet jól mûködik. hogy hiba egy lokális változóra mutató referenciát visszaadni egy függvénybõl (§7.

azt a szokásos paraméter-egyeztetési szabályok fogják eldönteni (§7..hu 10. Hogy melyik operátort fogja egy adott hívás elérni.buf) meghívásával Ezen használat miatt a new (buf) X utasításforma. Osztályok 337 10.6). az operator // new(sizeof(X).4.11. Az objektumokat tetszés szerinti helyre tehetjük. void* p) { return p. } void* buf = reinterpret_cast<void*>(0xF00F). Legtöbbször egyszerûen a paraméterének megfelelõ bitsorozatú értéket.7).Forrás: http://www. X* p2 = new(buf)X. // . elhelyezõ utasításként (placement syntax) ismert. mint a kívánt típust adja vissza. // explicit elhelyezõ operátor // fontos cím // X létrehozása a 'buf-ban'. minden new() operátornak egy size_t típusú elsõ paramétere van. és a new operátor használatakor megadjuk ezeket a paramétereket: void* operator new(size_t. hogy egy objektum létrejöjjön? Vegyünk példaként egy egyszerû osztályt: class X { public: X(int). mint a létrehozandó objektum méretét. automatikusan megkapja (§15. }.. amely az operator new-nak további paramétereket ad. Jegyezzük meg. ha máshol szeretnénk.doksi. így aztán a lényegébõl fakadóan nyelvi változattól függõ.2. Az objektumok elhelyezése A new operátor alapértelmezés szerint a szabad tárban hozza létre az objektumokat.4). Mit tegyünk. hogy egy bizonyos helyrõl (Arena objektumtól) foglaljunk memóriát: . Az „elhelyezõ” operator new() a legegyszerûbb ilyen lefoglaló függvény. veszélyes és esetenként feltétlenül szükséges egészek és mutatók közötti átalakításra használható. amelynek további paraméterei vannak. és ezt. Az elhelyezõ new operátor felhasználható arra is. A reinterpret_cast a „legdurvább” és a legnagyobb károkozásra képes a típuskonverziós operátorok közül (§6. hogy minden new operátor a méretet várja elsõ paraméterként. ha megadunk egy memória-lefoglaló függvényt. és definiciója a <new> szabványos fejállományban szerepel.

Ennek alapvetõ módja az. X* q = new(Shared) X(i). mielõtt közvetlenül . // memória felszabadítása } Jegyezzük meg.Forrás: http://www.. Arena* a) { return a->alloc(sz).7.1.. // destruktor meghívása a->free(p). virtual void free(void*) =0. extern Arena* Shared.. void g(int i) { X* p = new(Persistent) X(i). §16.. amelyet nem (közvetlenül) a szabványos szabadtár-kezelõ kezel. } // X állandó tárterületen // X megosztott memóriában Ha egy objektumot olyan helyre helyezünk. hogy közvetlenül meghívjuk a destruktort: void destroy(X* p.doksi. void* operator new(size_t sz. } A különbözõ Arena objektumokban szükség szerint tetszõleges típusú objektumokat hozhatunk létre: extern Arena* Persistent.8) típusa nyomán. }. mint az egyedi igényeket kielégítõ globális memória-lefoglalók – használatát inkább kerüljük el. hogy a destruktorok közvetlen meghívását – csakúgy. // . ha lehet. Mindazonáltal egy kezdõ C++-programozó inkább háromszor gondolja meg.3. Arena* a) { p->~X().hu 338 Absztrakciós módszerek class Arena { public: virtual void* alloc(size_t) =0. közvetlen destruktorhívás nélkül. némi óvatosságra van szükség annak megsemmisítésekor. Esetenként mégis alapvetõ szükségünk van rájuk: például nehéz lenne egy hatékonyan mûködõ általános tárolóosztályt készíteni a standard könyvtár vector (§3. // .

amelyben minden tag címe azonos (lásd §C. [3] A konkrét típusok a legegyszerûbb osztályok. Az elhelyezõ operátor és a kivételkezelés kapcsolatáról lásd a §14.4.3.6. hogy nyilvánvalóvá tegyük egy osztálynak és segédfüggvényeinek összetartozását.2.5.1.12. amelynek konstruktorral vagy destruktorral rendelkezik.3. [7] Egy függvény. Uniók Egy nevesített unió (union) olyan adatszerkezet (struct). §10. Egy uniónak lehetnek tagfüggvényei. hogy milyen típusú objektum van az unióban. §10.4. §10. Ezért egy uniónak nem lehet olyan tagja.Forrás: http://www. amelynek hozzá kell férnie az osztály ábrázolásához.2.hu 10. 10. mert akkor nem lehetne a helyes memóriakezelést biztosítani. amelyek nyilvántartják. hogy az unió melyik tagja van használatban. ha közvetlenül kell hozzáférnie az osztály ábrázolásához. amikor tényleg csak adatok vannak és nincs rájuk nézve invariánst igénylõ feltétel. hogy mi van az unióban (§10.doksi.2.3. hogy az unió megsemmisülésével a megfelelõ destruktor hívódik meg.6[20]). de nincs is szükség rá. de nem lehetnek statikus tagjai. A tömböknél nincs megfelelõje az elhelyezõ operátornak. Az uniók felhasználása leginkább alacsony szinten vagy olyan osztályok belsejében történik. [2] Csak akkor használjunk nyilvános adatokat (struct-okat). [4] Egy függvény csak akkor legyen tagfüggvény. 10. Osztályok 339 meghívna egy destruktort és akkor is inkább kérje elõtte tapasztalt kollégájának tanácsát.2). mert az elhelyezõ operátort tetszõleges típusokra alkalmazhatjuk. Hacsak lehet. §10.8. ha nem változatja meg az objektumának az értékét. legyen const tagfüggvény. §10. A fordítóprogram általában nem tudhatja. Tanácsok [1] A fogalmakat osztályokra képezzük le. Tömbökre vonatkozóan azonban megadhatunk például egyedi operator delete()-et (§19. mint bonyolultabb osztályokat vagy egyszerû adatszerkezeteket. [6] Egy tagfüggvény. de nem .5). [5] Használjunk névteret arra.2. illetve azt.8. §10.4-es pontot.4. vagyis nem ismert. használjunk inkább konkrét típust.

valószínûleg szüksége lesz konstruktorra. legyen statikus tagfüggvény. §10. Ha egy osztálynak szüksége van másoló mûveletre vagy destruktorra. Ha egy osztálynak van referencia tagja.Forrás: http://www. hogy az adatábrázolásra az 1970. Használjunk elsõ használatot jelzõ kapcsolókat. (*2) Határozzuk meg a Histogram osztályt.3.1. hogy minden szükséges elemet másoljunk (ügyeljünk az alapértelmezett kezdeti értékadásra).2.4. hogy a végrehajtási sorrendtõl való függést a lehetõ legkisebbre csökkentsük. amelyekben kezdõértéket kell adni az új tagnak. (*1) Hogyan érjük el a Chrono névtér Date osztályának set_default függvényét (§10.4. Használjunk felsoroló konstansokat.4.9.6. 3.2.7-beli változatban.hu 340 Absztrakciós módszerek [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] szükséges. vitassuk meg az osztályt egy tényleges felhasználóval.2.4. Ha egy osztálynak van mutató tagja.4. 4. 10. §10.1. valószínûleg szüksége lesz másoló mûveletekre (másoló konstruktorra és másoló értékadásra) is. §10.6. A másoló értékadásnál ügyeljünk az önmagával való értékadásra.4. akkor legyenek másoló mûveletei (másoló konstruktora és másoló értékadása). destruktorra. §10. Az osztályra állapotbiztosítóit (invariáns) a konstruktorban állítsunk be.4. 5.4.2)? Adjunk meg legalább három változatot. (*1) Találjuk meg a hibát a §10.6.4. Gyakorlatok 1.1.2-beli Date::add_year() függvényben. 2.4. (*2) Keressünk egy kereskedelmi használatban levõ Date osztályt. nincsenek-e felhasználói konstruktorok.3.4. §10.6. ha egész konstansokra van szükség egy osztály deklarációjában. amelyik felszabadítja azt.4. Elemezzük az általa nyújtott szolgáltatásokat.1. ellenõrizzük. Ha egy konstruktor lefoglal valamilyen erõforrást. §10. §10. másoló konstruktorra és másoló értékadásra is.10. Másoló konstruktor írásakor ügyeljünk arra. Gondoljunk arra. óta eltelt napokat használjuk. 10. §10. (*2.2. amelyben létrejöttek. Írjuk újra úgy. hogy egy objektumon keresztül hívjuk meg. Globális vagy névtérhez tartozó objektumok használatakor kerüljük a végrehajtási sorrendtõl való függést.4. §10. akkor legyen destruktora az osztálynak.1.3.01. amely a konstruktorában paraméter- . Ha lehetséges.5) Fejezzük be és próbáljuk ki a Date osztályt. §10. Aztán találjunk még két további hibát a §10. hogy az ideiglenes objektumok annak a teljes kifejezésnek a végén megsemmisülnek.9. §10.1.4.doksi. Ha új taggal bõvítünk egy osztályt.01. §10.3.4.4.

void print(). Határozzuk meg az Expr osztályt kétféleképpen: egyszer mint csomópontok láncolt listáját.10[7]-beli Tnode-ot. mint olyan osztályt. hogy csomópontok (Node objektumok) halmazát jelentse. készítsük el és ellenõrizzük az Intset osztályt. Hasonlítsuk össze a két változatot. Legyen meg az unió. * és / mûveletekbõl álló egyszerû aritmetikai kifejezéseket képes elemezni. Osztályok 341 ként megadott idõtartományokra vonatkozó gyakoriságokat tartja nyilván. másszor egy karakterlánccal ábrázolva. 11. a mûveleti jelet utótagként használva. }.5) Készítsük el a Table osztályt. amelyek bizonyos (például egyenletes vagy exponenciális) eloszlások szerinti véletlen számokat adnak. 7. 6.hu 10.5) Módosítsuk az Intset osztályt. Az Expr::Expr() konstruktor karakterlánc paramétere a kifejezés. a metszet. az Expr::print() pedig ábrázolja azt a cout-on. Ezután módosítsuk a számológép programot (§6. amely (név–érték) párokat tárol. Az Expr::eval() függvény visszaadja a kifejezés értékét. A nyilvános felület ilyesmi legyen: class Expr { // . 9. public: Expr(const char*).1).eval() << "\n". assembly kóddal stb. és egy draw függvénye. vannak. amely a következõ értéket adja vissza. amely az eloszlást megadja. Adjuk meg a Tnode-ok egy fáját. (*2) Írjuk újra a §7. (*3) Hozzunk létre egy olyan osztályt.. amely egészek halmazát ábrázolja. amely egész konstansokból és a +. 10. cout << "x = " << x. mint osztályt (konstruktorokkal és destruktorokkal). hogy az a map helyett a Table osztályt használja. destruktorai stb.Forrás: http://www. -. amelynek konstruktorai. kiértékelni. . (*2) Határozzunk meg osztályokat. x.print().doksi. Kísérletezzünk a kifejezés különbözõ kiíratásaival: teljesen zárójelezve. A program így nézhet ki: Expr x("123/4+123*4-3"). Biztosítsunk mûveletet a grafikon kiíratására és kezeljük az értelmezési tartományon kívül esõ értékeket is. 8. tárolni és kiírni. (*2.. (*1. int eval(). (*3) Határozzuk meg. ahol a Node egy meghatározott adatszerkezet. Mindegyik osztálynak legyen egy konstruktora. és a szimmetrikus differencia mûvelet is.

hogy kiküszöböljük a végrehajtási sorrendtõl való függést? 18. mindegyikben egy-egy statikus taggal. a parancssori paraméterekre és a programban tárolt karakterláncokra. amely legalább 1000 sort tartalmaz. (*2) Módosítsuk a 10. hogy változókat is kezelni tudjon. hogy a következõ kimenetet adja: Kezdeti értékadás Helló. hogyan néznek ki ott az igazi szimbólumtáblák. hogy a nyilvános felület ne függjön az ábrázolástól. amilyet a §6.9[13] és a §7. 14. Tegyük lehetõvé a kimenetnek a bemenethez hasonló módon többféle helyre való irányítását. hogy mindegyik létrehozásához a másikra hivatkozunk. világ!\n". 13. Használjuk a 10. és legalább 10 . illetve (b) vektort. (*1) Adott a következõ program: #include <iostream> int main() { std::cout << "Helló. milyen különbségekkel kell számolni a két osztály módosításakor.6[11]-beli kifejezésosztályt. Futtassuk a programot egy olyan szövegfájllal. Hol fordulhat elõ ilyesmi igazi kódban? Hogyan lehet módosítani az osztályokat. (*2) Határozzunk meg egy olyan Calculator osztályt.hu 342 Absztrakciós módszerek 12.3) az §5. 17. (*2. (*3) Tervezzünk egy szimbólumtábla és egy szimbólumtábla-elem osztályt valamely nyelv számára. amely egy istream-bõl és egy vector<string>-bõl kiindulva elkészít egy map<string. Hozzunk létre Calculator objektumokat és alkalmazzuk azokat a cin-bõl származó bemenetre. valamint a = értékadó mûveletet is. (*2) Határozzuk meg a Char_queue osztályt. (*2) Határozzunk meg két osztályt.6[131]-beli szimbólumtábla osztályt. Értékeljük a megtalált hibákat és gondoljuk meg. 19. világ! Takarítás A main() függvényt semmilyen módon nem változtathatjuk meg. 16.5) Hasonlítsuk össze a Date osztályt (§10. úgy. } Módosítsuk úgy.1-beli függvények nagyrészt megvalósítanak. (*3) Írjunk olyan függvényt.10[19] feladatra adott megoldással. Készítsük el a Char_queue-t mint (a) láncolt listát.Forrás: http://www.vector<int> > objektumot.doksi. Nézzük meg az adott nyelv egy fordítóprogramjában. 15. amely minden karakterláncot és azok elõfordulásának sorszámát tartalmazza.

azt értem alatta.hu 11 Operátorok túlterhelése „Amikor én használok egy szót.és kétoperandusú mûveleti jelek • Az operátorok elõre meghatározott jelentése • Az operátorok felhasználói jelentése • Operátorok és névterek • Komplex szám típusok • Tag és nem tag operátorok • Vegyes módú aritmetika • Kezdeti értékadás • Másolás • Konverziók • Literálok • Segédfüggvények • Konverziós operátorok • A többértelmûség feloldása • Barát függvények és osztályok • Tagok és barát függvények • Nagy objektumok • Értékadás és kezdeti értékadás • Indexelés • Függvényhívás • Indirekció • Növelés és csökkentés • Egy karakterlánc osztály • Tanácsok • Gyakorlatok .doksi.Forrás: http://www. amit én akarok – se többet.” (Humpty Dumpty) Jelölés • Operátor függvények • Egy. se kevesebbet.

complex operator*(complex). im(i) { } complex operator+(complex). azonban nincs megfelelõje a beépített típusok között. }. Ha például b és c complex típusúak. Például ha komplex számokkal akarunk számolni. class complex { // nagyon leegyszerûsített complex típus double re. im. amelyre mûveleteket szoktak alkalmazni. public: complex(double r. megszokottabb és kényelmesebb jelölés felhasználásával kezelhetjük az objektumokat. Ezek után közelítõleg meghatározhatjuk a complex számokat tartalmazó kifejezések megszokott jelentését: . akkor a b+c a b.1. Egy complex értéket egy kétszeres pontosságú lebegõpontos számpár ábrázol. osztályokat használunk. Itt például a komplex szám fogalmának egy egyszerû megvalósítását láthatjuk. A legtöbb fogalomnak. a beépített típusokra vonatkozó mûveletet. double i) : re(r). Bevezetés Minden mûszaki szakterületnek – és a legtöbb nem mûszakinak is – kialakultak a maga megszokott rövidítései. amelyek kényelmessé teszik a gyakran használt fogalmak kifejezését. mint a vegyük y-t z-szer és az eredményt adjuk x-hez Nem lehet eléggé megbecsülni a szokásos mûveletek tömör jelölésének fontosságát. hogy értelmezze a + és * mûveleteket. A legtöbb nyelvvel együtt a C++ is támogat egy sor.hu 344 Absztrakciós módszerek 11.Forrás: http://www. Ha ezekre az osztályokra vonatkozó mûveleteket definiálunk. hogy ezeket a fogalmakat ábrázoljuk.doksi. melyet a + és a * mûveletek kezelnek. így felhasználói típussal kell azokat ábrázolni. A felhasználó adja meg a complex::operator+() és complex::operator*() operátorokat. logikai jelölésekre vagy karakterláncokra van szükségünk a C++-ban. ha mátrix-mûveletekre. Az alábbi például x+y*z világosabb számunkra. mintha csak az alapvetõ függvény-jelölést használnánk. tárgyalását.operator(c)-t jelenti.

§10.2) jelentését meghatározó függvényeket megadhatjuk: + | -= << >= -> ~ *= >> && [] * ! /= >>= || () / = %= <<= ++ new % < ^= == -new[ ] ^ > &= != ->* delete & += |= <= . Az operátorok túlterhelésének legnyilvánvalóbb alkalmazásai közül sok konkrét típusokra vonatkozik (§10. 11.2.4. a = b+c.2. és nem azt.doksi. c = a*b+complex(1. §4.* (tagkiválasztás a tagra hivatkozó mutatón keresztül.2. §5. hogy b=b+(c*a). complex b = complex(1.2) sem terhelhetõ túl.4.1). §15. } A szokásos kiértékelési szabályok érvényesek. mint ahogy a sizeof (§4.hu 11.5) Ezek olyan operátorok (mûveleti jelek). 3. . 2). b = b+c*a. Operátor függvények A következõ operátorok (§6. hanem nevet várnak és a tagokra való hivatkozás alapvetõ módjai. Ha túl lehetne terhelni ezeket – azaz ha a felhasználó határozhatná meg jelentésüket – akkor ez érdekes mellékhatásokkal járhatna [Stroustrup.3. a ?: (§6.9. (tagkiválasztás.2). a [ ] és a (). delete[ ] A következõknek viszont nem lehet felhasználói jelentést tulajdonítani: :: (hatókör-feloldás.3). Általános és absztrakt felületek felépítésénél például gyakran használunk olyan operátorokat. Az operátorok túlterhelése azonban nemcsak konkrét típusoknál hasznos.4) sem. A háromparaméterû feltételes-kifezés operátor. mint a ->. Operátorok túlterhelése 345 void f() { complex a = complex(1.4) . complex c = b. amelyek második operandusként nem értéket. így a második kifejezés azt jelenti.7) . 1994].6) és a typeid (§15.Forrás: http://www. hogy b=(b+c)*a.

operator+(b). 1+a.a) // ::operator+(a. a túlterhelés-feloldási szabályok (§7. void operator+(X.X). ehelyett a függvényhívási jelölés használható. mint a Fortranban. vagy operator@(aa. } // a.1. de gondoljunk csak meg: a ** mûveleti jel balról kössön.operator+(1) // ::operator+(X(1).0) . vagy jobbról. Elsõ pillantásra nyilvánvalónak és egyszerûnek tûnhet a ** operátort használni a hatványozásra. Ezek a megszorítások túl szigorúnak tûnhetnek. mint a többi függvényt. például operator <<. } // rövid forma // explicit hívás A complex elõzõ definicióját adottnak véve a fenti két kezdeti értékadás jelentése azonos.doksi. de rugalmasabb szabályok könnyen az egyértelmûség elvesztéséhez vezetnének. a+1.és kétoperandusú mûveletek Kétoperandusú mûveleti jelet egyparaméterû nem statikus tagfüggvényként vagy kétparaméterû nem tag függvényként definiálhatunk. void operator+(X. 11.operator@(bb)-t. Ha @ kétoperandusú mûveletet jelöl. mint az Algolban? Az a**p kifejezést hogyan értelmezzük: mint a*(*p)-t vagy mint (a)**(p)-t? Az operátor függvények neve az operator kulcsszóból és azt követõen magából az operátorból áll. illetve hogy egyáltalán bármelyik alkalmazható-e: class X { public: void operator+(int).1. hogy pow().0. Az operátorral való jelölés csak az operátor függvény közvetlen meghívásának rövidítése: void f(complex a.Forrás: http://www. Egy.hu 346 Absztrakciós módszerek Új mûveleti jeleket sem adhatunk meg. ha a rendelkezésre állókon kívül további operátorokra is szükség van. }.2. Az operátor függvényeket ugyanúgy deklarálhatjuk és hívhatjuk meg. hanem azt. Így például ne **-ot használjunk.bb)-t jelöli. melyik alkalmazható. Ha mindkettõ értelmezett.double). akkor aa@bb vagy aa. void f(X a) { a+1.4) döntik el. complex d = a. X(int). complex b) { complex c = a + b.

operator@()-t. // nem tag függvények : X operator-(X). Az operátorok elõre meghatározott jelentése A felhasználói operátorok jelentésének csak néhány elõírásnak kell megfelelniük.2.9.6. X operator-(X.hu 11. hogy legyen. a §10. X operator-(X. Az operator=.X). illetve hogy egyáltalán bármelyik alkalmazható-e. X operator%(X). Ha mindkettõ értelmezett. Ha @ utótag és egyoperandusú mûveletet ad meg. X operator&(X). X operator++(int). vagy operator@(aa)-t jelöli. // elõtagként használt egyoperandusú mínusz (mínusz elõjel) // kétoperandusú mínusz (kivonás) // utótagként használt csökkentõ operátor // hiba: nincs operandus // hiba: háromoperandusú // hiba: egyoperandusú % A [ ] operátort a §11.doksi.9. 11. .X.4.6 pontokban írjuk le. (Ezt részletesebben a §11. // elõtagként használt egyoperandusú & (cím) // kétoperandusú & (és) // utótagként használt növelõ operátor (lásd §11.operator@(int)-et. Ha @ elõtag és egyoperandusú mûveletet jelöl.11.X).int).2. Operátorok túlterhelése 347 Az egyoperandusú (akár elõ-. X operator&(X. ez biztosítja. melyik alkalmazható.11 pont írja le. akkor aa@ vagy aa. akár utótagként használt) mûveleti jelek paraméter nélküli nem statikus tagfüggvényként vagy egyparaméterû nem tag függvényként definiálhatók.2. akkor @aa vagy aa.6). a túlterhelés-feloldási szabályok (§7. így nem lehet például egyoperandusú % vagy háromoperandusú + mûveletünk: class X { // tagok (a 'this' mutató automatikus): X* operator&().4) döntik el. melyik alkalmazható. X operator/().11 és a §15.4) döntik el. ismét csak a túlterhelés-feloldási szabályok (§7. a ++ és -.int)-et jelöli. X operator-(). a () operátort a §11. illetve hogy egyáltalán bármelyik alkalmazható-e. operator() és az operator-> nem statikus tagfüggvény kell. X operator--(X&.1) // hiba: háromoperandusú // hiba: egyoperandusú / }. hogy elsõ operandusuk balérték (lvalue) lesz (§4. a memóriafoglaló és felszabadító operátorokat a §6.2.) Ha mindkettõ értelmezett.operátorokat a 11.10. vagy operator@(aa.X).8.5).Forrás: http://www. operator[ ]. Operátort csak a nyelvi szabályoknak megfelelõen definiálhatunk (§A. a -> operátort a §11.

Ebbõl adódóan nem definiálható olyan operátor.b. a. de nem változtatható meg. hogy a programozó egy kifejezés értelmét csak akkor módosíthassa.2. void operator. Vegyük például azt az esetet. void operator&(). } // hiba: az értékadó operátor privát // hiba: a cím operátor (&) privát // hiba: a vesszõ operátor (. ha privátként adjuk meg azokat: class X { private: void operator=(const X&).. X b) { a = b. amikor egy complex változót akarunk a 2 egészhez hozzáadni: az aa+2 kifejezést alkalmas tagfüggvény . }. Hagyományosan az = (értékadó). 11. Az olyan operátorok. void f(X a. hogy megadtuk a Z::operator+() és Z::operator=() mûveleteket. Például ha a egy int. (vesszõ. Operátorok és felhasználói típusok Az operátoroknak tagfüggvénynek kell lenniük vagy paramétereik között legalább egy felhasználói típusnak kell szerepelnie (kivételek ez alól a new és delete operátorok jelentését felülbíráló függvények. §6.) privát Alkalmas módon definiálva azonban új jelentés is tulajdonítható nekik. hogy elsõ paraméterként valamilyen alaptípust fogadjanak el. melyeket arra szánunk. ha legalább egy felhasználói típus elõfordul benne. (az osztályba tartozó objektumokra vonatkozó =. operátorokat kivéve).) Ez a szabály biztosítja.hu 348 Absztrakciós módszerek Bizonyos beépített operátorok jelentése megegyezik más operátoroknak ugyanazon paraméterre összetetten gyakorolt hatásával.doksi. amely kizárólag mutatókkal mûködik. ilyen összefüggések nem állnak fenn a felhasználói operátorokra. Hacsak a felhasználó nem gondoskodik róla.3.. a & (címképzõ) és a . így a fordítóprogram például nem fogja kitalálni a Z::operator+=() mûvelet jelentését pusztán abból. &a. ami pedig azt jelenti. & és . A C++ tehát bõvíthetõ. // . akkor ++a jelentése megegyezik a+=1-gyel. nem lehetnek tagfüggvények.2) operátorok elõre definiáltak. ha osztályba tartozó objektumra alkalmazzuk azokat.(const X&). hogy a=a+1.Forrás: http://www.2. Ezeket az elõre meghatározott jelentéseket az általános felhasználó elõl elrejthetjük.

Operátorok túlterhelése 349 megléte esetén értelmezhetjük aa. 11. } A fordítóprogram minden kifejezést ellenõriz. De ha lenne is. Az ilyesmit rendszerint nem tag függvényekkel kezelhetjük (§11. Névterek operátorai Az operátor mindig valamilyen osztály tagja vagy valamilyen névtérben (esetleg a globálisban) definiált. }..2.. Ha egy felhasználói operátor is biztosít lehetséges értelmezést.hu 11.4 pontban leírtak szerint történik.3.4. class string { // . hogy nem lép-e fel többértelmûség.operator(aa) eredményre jussunk. const string&). §11. } ostream& operator<<(ostream&.operator+(2)-ként. nem tételezheti fel róla a felcserélhetõséget (kommutativitást). de a 2+aa kifejezést nem.2. wed. thu. amelynek + olyan tagfüggvénye lehetne. Minthogy a fordítóprogram nem ismeri a felhasználói + mûvelet jelentését. Vegyük például a standard könyvtár karakterlánc-kiírási mûveletének egyszerûsített változatát: namespace std { // egyszerûsített std class ostream { // .. mert nincs int osztály. akkor is két tagfüggvény kellene ahhoz. hogy annak alapján 2+aa-t mint aa+2-t kezelje. fri.Forrás: http://www. Day& operator++(Day& d) { return d = (sat==d) ? sun : Day(d+1). hogy a 2. mon. hogy 2+aa-val és aa+2-vel is megbirkózzunk. extern ostream cout. tue. A felsorolások felhasználói típusok. így rájuk is értelmezhetünk operátorokat: enum Day { sun. ostream& operator<<(const char*).. a kifejezés ellenõrzése a §7. sat }.5). }.doksi. .

így std::cout << p jelentése definíció szerint: std::cout. amikor a << számára alkalmas definíciót keresünk. const std::string&) . hogy „Helló. std::string s = "világ".6). mint ahogy a függvényeket paramétereik típusa szerint (§8. világ!”.2. " << s << "!\n".Forrás: http://www. A C stílusú karakterláncok (char*) kimeneti mûvelete az std::ostream egy tagja. De miért? Vegyük észre. így std::cout << s jelentése: operator<<(std::cout. hogy nem tettem mindent elérhetõvé az std névtérbõl azáltal. Ehelyett az std:: elõtagot alkalmaztam a string és a cout elõtt.hu 350 Absztrakciós módszerek int main() { char* p = "Helló". Vagyis a legrendesebben viselkedve nem „szennyeztem be” a globális névteret és egyéb módon sem vezettem be szükségtelen függéseket. így az std is szóba kerül. std::cout << p << ". Így aztán a fordítóprogram megtalálja és felhasználja a következõ függvényt: std::operator<<(std::ostream&. } Ez természetesen azt írja ki.doksi. Minthogy a cout az std névtérben van.s) A névtérben definiált operátorokat ugyanúgy operandusuk típusa szerint találhatjuk meg.operator<<(p) Mivel azonban az std::ostream-nek nincs olyan tagja. amelyet az std::string-re alkalmazhatnánk. hogy azt írtam volna: using namespace std.

Komplex szám típusok A komplex számoknak a bevezetõben említett megvalósítása túl keveset nyújt ahhoz. hogy a beépített operátorok nem válnak elérhetetlenné.3.3. hogy bárkinek is tessék. és hogy a felhasználó a meglevõ osztálydeklarációk módosítása nélkül adhat meg új jelentéseket.6).) Az egyoperandusú mûveletek feloldása hasonlóan történik. complex e = -b-c.2. hogy a felhasználói konverziókat (§11. keresünk egy operator@-t. complex d = 2+b. hogy a legjobb egyezést megtaláljuk. Ha x az X típusba. Egy matematika tankönyvet olvasva azt várnánk. az y pedig az Y típusba tartozik. §11. } . b = c*2*c.7). az operator@-t az M névtérben keressük. ha legalább egy felhasználói típus szerepel az operandusok között. ♦ Ha X az N névtér tagja. complex b = 3. nem felhasználói típusok (§4.hu 11.4). ♦ Keresünk egy operator@ deklarációt az x@y kifejezést körülvevõ környezetben.2. complex c = a+2.2). Ez eltér a névvel megadott függvények keresésétõl (§8.doksi. amely az X osztálynak vagy valamelyik bázisosztályának tagfüggvénye. hogy az operátorok feloldásában a tagfüggvények nem élveznek elõnyt a nem tag függvényekkel szemben. Ha az operator@ többféle deklarációját is megtaláltuk. 11. (A typedef-fel megadott nevek csak szinonimák.Forrás: http://www. Operátorok túlterhelése 351 Jelöljön @ egy kétoperandusú mûveletet. az operator@-t az N névtérben keressük. A szabványos iostream könyvtár például definiálja a << tagfüggvényeket a beépített típusokra. ha egyáltalán van ilyen.3. Jegyezzük meg. a felhasználó viszont a felhasználói típusoknak a << mûvelettel való kimenetre küldését az ostream osztály (§21.9. Ez a keresési eljárás csak akkor alkalmazandó. hogy a következõ függvény mûködik: void f() { complex a = complex(1. a feloldási szabályokat kell alkalmazni (§7. akkor x@y feloldása.1) módosítása nélkül definiálhatja. ami azt is jelenti.4) is figyelembe vesszük. azaz a paraméterek típusának megfelelõ függvény megkeresése a következõképpen történik: ♦ Ha X egy osztály.2. ♦ Ha Y az M névtér tagja. Az operátorok el nem rejtése biztosítja.

Azokat az operátorokat.. // hozzá kell férni az ábrázoláshoz complex operator+(complex a. ha csak azokat az operátorokat adjuk meg magában az osztályban.Forrás: http://www. Ezt úgy érhetjük el.operator+=(z) Esetleges hatékonysági különbségektõl eltekintve r1 és r2 kiszámítása egyenértékû. mint például a +. complex r2 = x. complex y.hu 352 Absztrakciós módszerek Ráadásul elvárnánk. complex z) { complex r1 = x+y+z. } // r1 = operator+(operator+(x.operator+=(y) // r2. hogy létezzék néhány további mûvelet is. complex b) { complex r = a. A complex osztály egy konkrét típus..z) // r2 = x // r2. return r += b.1. }. amelyek csak egy új értéket állítanak elõ paramétereik alapján.3-beli elveknek.3. Tag és nem tag operátorok Elõnyös lenne. r2 += z. r2 += y.doksi. 11. amelyek értelmüknél fogva módosítják elsõ paraméterüket. // . // az ábrázolás elérése a += operátoron keresztül } Ezen deklarációk alapján már leírhatjuk a következõt: void f(complex x. mint például a +=. ha minél kevesebb függvény férne hozzá közvetlenül egy adott objektum belsõ adatábrázolásához. például a == az összehasonlításra és a << a kimenetre. az osztályon kívül definiálom és az alapvetõ operátorok segítségével valósítom meg: class complex { double re. így felépítése megfelel a §10. hogy a complex osztály definiálása az operátor-túlterhelésre vonatkozó szinte valamennyi szabály alkalmazását igényli.y). . public: complex& operator+=(complex a). Ráadásul a komplex aritmetika felhasználói olyan nagy mértékben építenek az operátorokra. és még a matematikai függvények (mint a sin() és a sqrt()) megfelelõ készletét is igényelnénk. im.

Forrás: http://www. ezért a §11.re. return *this. hogy a complex d = 2+b. pedig pusztán abból következik. public: complex& operator+=(complex a) { re += a. hogyan adhatunk meg olyan operátorokat. Ezt könnyen megvalósíthatjuk. Ez többnyire meglepést kelt. im += a. 11. A Fortran kifejezésével élve tehát „vegyes módú aritmetikára” (mixedmode arithmetic) van szükség. kódot kezelni tudjuk. Operátorok túlterhelése 353 Az összetett értékadó operátorokat. im += a. mint egyszerû megfelelõiket. Vegyes módú aritmetika Ahhoz.hu 11.im. Egy jó fordítóprogram az optimálishoz közeli kódot készít a sima + operátor használata esetén is. ha nem használunk ideiglenes változókat: inline complex& complex::operator+=(complex a) { re += a. hogy az összeadásnál 3 objektum játszik szerepet (a két összeadandó és az eredmény).im. például a += -t és a *= -t általában könnyebb definiálni. amelyek hozzáférhetnek az osztály ábrázolásához.3. } . Az utóbbi esetében hatékonyabb a megvalósítás.2. } A fenti megoldásnál nincs szükség ideiglenes változóra az eredmény tárolására és a fordítóprogram számára is könnyebb feladat a teljes helyben kifejtés. im. De nincs mindig jó optimalizálónk és nem minden típus olyan „egyszerû”.5 pont tárgyalja.doksi. amely különbözõ típusú paramétereket is elfogad. ha megadjuk az operátor megfelelõ változatait: class complex { double re. return *this. a + és * operátorokat. mint a complex. olyan + operátorra van szükségünk.re. míg a += operátornál csak kettõ.

complex)-et hívja meg } 11.hu 354 Absztrakciós módszerek complex& operator+=(double a) { re += a. return r += b.im=0-t kell jelentenie . b. double b) { complex r = a. // operator+(complex.doksi. // .double)-t hívja meg complex r3 = 2+x.3. // operator+(complex. // complex::operator+=(complex)-et hívja meg } complex operator+(complex a. így hatékonyabbak lesznek.complex)-et hívja meg complex r2 = x+2. complex b) { complex r = b. ezt tükrözik a fenti definíciók is. complex y) { complex r1 = x+y. // operator+(double. // complex::operator+=(double)-t hívja meg } complex operator+(double a..re=3. return r += a.Forrás: http://www. // complex::operator+=(double)-t hívja meg } Egy double hozzáadása egy komplex számhoz egyszerûbb mûvelet. } }. Kezdeti értékadás A complex változóknak skalárokkal való kezdeti és egyszerû értékadás kezeléséhez szükségünk van a skalárok (egész vagy lebegõpontos (valós) értékek) complex-szé átalakítására: complex b = 3. A fenti deklarációk mellett most már leírhatjuk a következõt: void f(complex x. // b. mint egy komplex szám hozzáadása.3. complex b) { complex r = a. return *this. complex operator+(complex a. return r += b. A double operandust kezelõ mûveletek nem érintik a komplex szám képzetes részét..

im(0) { } complex(double r. A fenti egyenértékû a következõvel: complex b = complex(3).doksi. akkor konstruktort alkalmazunk.. ha az egyértelmû (§7. Ezért az egyparaméterû konstruktorokat nem kell explicit meghívnunk: complex b = 3. }.7.. Arra nézve. Felhasználói konverzióra csak akkor kerül sor automatikusan. public: complex() : re(0). .0) kezdõértéket adó alapértelmezett konstruktor is hasznos: class complex { double re. im. im. Operátorok túlterhelése 355 Az olyan konstruktor. Ez a konstruktor a valós számegyenesnek a komplex síkba való szokásos beágyazását jelenti. lásd a §11. im(0) { } // . double i) : re(r).4). im(0) { } complex(double r) : re(r). hogyan adhatunk meg csak explicite meghívható konstruktorokat. amely egyetlen paramétert vár.1 pontot.hu 11. és a (0. hogyan hozhatunk létre egy adott típusú értéket.. Egy konstruktor mindig azt írja elõ. konverziót jelent a paraméter típusáról a konstruktor típusára: class complex { double re. }. Ha egy adott típusú értéket kell létrehozni egy (kezdeti vagy egyszerû) értékadó kifejezés értékébõl és ebbõl egy konstruktor létre tudja hozni a kívánt típusú értéket. im(i) { } // ..Forrás: http://www. amelynek két double típusú paramétere van. Természetesen szükségünk lesz egy olyan konstruktorra is. public: complex(double r) : re(r).

A mûködést pontosan így határozhatnánk meg: class complex { double re. Másolás A megadott konstruktorokon kívül a complex osztálynak lesz egy alapértelmezett másoló konstruktora (§10.3. public: complex(const complex& c) : re(c. Ezenkívül pedig sok tag esetén fárasztó dolog kézzel kiírni a tagonkénti másolást és könnyû közben hibázni (§10. im. 4 }. Rövidebb lesz a kód. im(c.4. im(c.re). és a kód olvasójáról feltételezem. .5): complex z1 = { 3 }.2. im(i) { } // .4. Az alapértelmezett másoló konstruktor egyszerûen lemásolja a tagokat. }. hogy ismeri az alapértelmezett mûködést.re).5) is. A másoló konstruktor paramétereként referenciát kell használnom. }.3). public: complex(double r =0.6.im) { } // hiba hibás. a kezdeti értékadásra nem használhatunk kezdõértéklistát (§5. hogyan lehet azt optimalizálni..im) { } // .doksi. A másoló konstruktor határozza meg a másolás jelentését – beleértve a paraméter másolásáét is – így a complex::complex(complex c) : re(c.hu 356 Absztrakciós módszerek Alapértelmezett paraméter-értékeket használva így rövidíthetünk: class complex { double re. // hiba: complex rendelkezik konstruktorral // hiba: complex rendelkezik konstruktorral 11.Forrás: http://www.7. Én elõnyben részesítem az alapértelmezett másoló konstruktort azon osztályok esetében.. A fordítóprogram is ismeri és azt is. double i =0) : re(r). Ha egy típusnak van konstruktora.9.. §4.. mintha bármi mást írnék. amelyeknél ez megfelelõ. mert a függvény meghívása végtelen rekurzióhoz vezet. complex z2 = { 3. im.

érték visszaadásakor és a kivételkezeléskor is (lásd §11.1). // ..1).2) vagy ha egy konstruktort explicit-ként adunk meg (§11.2. complex y(2. Ha a másoló konstruktort priváttá tesszük (§11. // x kezdõértéke 2 // y kezdõértéke (2. complex operator+(complex.0). akár a programozó írta – nemcsak a változók kezdõértékének beállítására használatos. de ilyen egyszerû osztály esetében erre nincs ok.2.3.3.5. §7.double). hanem paraméter-átadáskor. Írhattuk volna így is: complex x(2). mert az alapértelmezett mûködés pont megfelelõ. 11.5).2. Operátorok túlterhelése 357 Más.complex). ezzel adunk kezdõértéket x-nek // complex(2.0) létrehozása.7).1. ezzel adunk kezdõértéket y-nak A fordítóprogram azonban optimalizál és elhagyja a másoló konstruktor meghívását.7. A kezdeti értékadáshoz hasonlóan a két azonos osztályba tartozó objektum közötti értékadás alapértelmezés szerint tagonkénti értékadást jelent (§10. Konstruktorok és konverziók A négy alapvetõ aritmetikai mûveletnek eddig három-három változatát határoztuk meg: complex operator+(complex. Errõl bõvebben ír a §11. A másoló konstruktor – akár a fordítóprogram hozta létre. Elvileg a másoló konstruktort az ilyen egyszerû kezdeti értékadásoknál használjuk: complex x = 2..6 pont.doksi. az = stílusú értékadás által elfogadható értékek körét a () stílusú értékadás által elfogadotthoz képest korlátozhatjuk.Forrás: http://www. Ezek szerepét a nyelv a kezdeti értékadáséval azonosként határozza meg (§7. .hu 11. complex paraméterû függvények esetében én érték és nem referencia szerinti paraméter-átadást használok.0). complex operator+(double. complex y = complex(2. A complex osztálynál erre a célra megadhatnánk kifejezetten a complex::operator= mûveletet.0) A complex-hez hasonló aritmetikai típusok esetében jobban kedvelem az = jel használatát. §14. A felhasználó szemszögébõl nézve nincs sok különbség egy complex és egy const complex& paramétert kapó függvény között.complex). Mindig az osztály készítõje dönt. // complex(2) létrehozása.

implicit (a kifejezésbe beleértett): void g(complex z) { 3+z. Egyes esetekben például a konverzió túl bonyolult mûvelet lehet. Ha egy függvény vagy operátor több változattal rendelkezik. } // rendben: complex(3)+z // hiba: 3 nem egy osztály objektuma // hiba: 3 nem egy osztály objektuma .10).y) x==3. // jelentése operator==(complex(3). ideiglenes változónak számítanak és amint lehetséges. hogy a paramétereket egy közös típusra alakítja. Valójában majdnem mindegyik úgy mûködik. Tegyük fel. Ahol ilyen okok nem lépnek fel jelentõs mértékben.doksi. // jelentése operator==(x. melyek miatt jobb külön függvényeket megadni. ott a függvény legáltalánosabb formáját megadva (esetleg néhány kritikus változattal kiegészítve) és a konverziókra hagyatkozva elkerülhetjük.complex(3)) 3==y.complex).operator+=(z). a fordítóprogram feladata a legalkalmasabb változat kiválasztása.hu 358 Absztrakciós módszerek Ez fárasztóvá válhat. és ami fárasztó.Forrás: http://www. hogy a paraméterek minden lehetséges párosítására megadnánk egy függvényt. ott könnyen elõfordulhatnak hibák. így a complex osztály számára elég egyetlen egyenlõség-vizsgáló mûveletet megadnunk: bool operator==(complex. 3. Ez akkor is így van. a paramétertípusok és a lehetséges (szabványos vagy felhasználói) konverziók alapján. complex y) { x==y. Mi lenne. megsemmisülnek (lásd §10. Ahelyett. ha minden paraméter háromféle típusú lehetne? Minden egyparaméterû mûveletbõl három változat kellene. 3+=z. void f(complex x. a kifejezés többértelmû és hibás (lásd §7.4. a háromparaméterûekbõl huszonhét és így tovább. ha maga a . Az olyan objektumok. máskor bizonyos paramétertípusokra egyszerûbb algoritmusok alkalmazhatók. amely egy double értéket alakít complex-szé. hogy a vegyes módú aritmetikából adódóan nagyon sokféle függvényt kelljen megírnunk. Ha nincs legjobb változat. A . melyeket a konstruktor közvetlen meghívása vagy automatikus használata hozott létre.4). // jelentése operator==(x. hogy complex osztályunknak van egy olyan konstruktora. majd egy szabványos algoritmust hajt végre. Ezek a változatok gyakran nagyon hasonlóak.y) } Lehetnek azonban okok. a kétparaméterûekbõl kilenc. és -> operátorok bal oldalán nem történik automatikus felhasználói konverzió. típuskonverziókra hagyatkozhatunk.

Az egyparaméterû konstruktorok általános eljárást biztosítanak erre a célra.2e3 double típusú literálok. mint egy complex értékû literált.imag(). 11.3.real() && a..2 és 1. 11. ahogyan 1. noha a szó „technikai” értelmében véve nem az. complex b) { return a. A complex osztály többi tagfüggvényével ellentétben a real() és az imag() nem változtatja meg egy complex objektum értékét. hogy azoknak hozzáférést kellene adnunk a complex osztály adatábrázolásához: inline bool operator==(complex a.hu 11.3.7.. írnunk sokkal ritkábban kell. így const-ként adható meg.Forrás: http://www.real()==b. A complex(3) kifejezést például én úgy tekintem. hogy a valós és a képzetes részt elég olvasnunk. }.6. im. } // . A valós és a képzetes rész lekérdezése például sûrûn használatos: class complex { double re. } Vegyük észre. Az alapvetõ típusokba tartozó literálokat viszont gyakran használhatjuk. } double imag() const { return im. . public: double real() const { return re. A real() és imag() függvények alapján egy sor hasznos függvényt definiálhatunk anélkül. Egyszerû és helyben kifejtett (inline) konstruktorok esetében ésszerû a literál paraméterû konstruktorhívásokra mint literálokra gondolni. hogy a mûvelet bal oldali operandusként balértéket vár. ha a tagfüggvények fel vannak készítve a kezelésükre. Kiegészítõ tagfüggvények Eddig csak konstruktorokat és aritmetikai mûveleteket adtunk a complex osztályhoz. Literálok Osztály típusú literálokat nem definiálhatunk abban az értelemben.imag()==b.doksi. Operátorok túlterhelése 359 Ezt kihasználva egy mûveletet tagfüggvénnyé téve kifejezhetjük. A tényleges használathoz ez kevés.

3.complex). z = complex(z.hu 360 Absztrakciós módszerek Ha „részleges frissítésre” van szükségünk. a következõt írhatjuk: void f(complex& z.real().complex). Kiegészítésként egy sor segédfüggvényt kell biztosítanunk: complex operator+(complex.. im(i) { } double real() const { return re. complex osztályunk így alakul: class complex { double re. public: complex(double r =0. complex operator+(double. double i =0) : re(r). // -=. bool operator!=(complex. // bemenet // kimenet // egyoperandusú mínusz // egyoperandusú plusz .. } double imag() const { return im.im-hez Egy jól optimalizáló fordító ebbõl egyetlen értékadást készít. Segédfüggvények Ha mindent összerakunk. és / complex operator-(complex).complex). im. és /= }.double).Forrás: http://www. } // d hozzárendelése z. complex& operator+=(double).complex). complex operator+(complex. ostream& operator<<(ostream&. double d) { // . *. } complex& operator+=(complex).d).complex&). bool operator==(complex.doksi. istream& operator>>(istream&. complex operator+(complex). // -.8.complex). *=. 11.

átalakítást megadni egy újabban megadott osztályról egy régebbire.. double abs(complex). double imag(complex).Forrás: http://www.4. amelyek a polár-koordinátás jelölést támogatják: complex polar(double rho. Ha T egy típus neve. // . akkor az X::operator T() függvény hatá- . complex conj(complex). Megadhatnánk olyan függvényeket is.. a régebbi osztály deklarációjának megváltoztatása nélkül. complex asin(complex). complex atan(complex). double theta). Felhasználói szemszögbõl nézve az itt bemutatott complex osztály szinte azonos a complex<double>-lal (lásd a standard könyvtárbeli <complex>-et. §22. double real(complex). double arg(complex). // a kényelmesebb jelölésért // a kényelmesebb jelölésért Végül szükségünk lesz a további alapvetõ matematikai függvényekre: complex acos(complex). hogy a real() és imag() függvények szerepe alapvetõ az összehasonlító függvények definiálásában. Konverziós operátorok Konstruktorok használata típuskonverzió céljára kényelmes lehet. Ezeket a feladatokat az átalakítandó osztály konverziós (átalakító) operátorának definiálásával oldhatjuk meg. automatikus átalakítást megadni felhasználói adattípusról beépített adattípusra (mert a beépített adattípusok nem osztályok) 2. Operátorok túlterhelése 361 Vegyük észre.doksi. double norm(complex). de nemkívánatos következményei vannak. A következõ segédfüggvények is nagyrészt ezekre építenek. 11. Egy konstruktor nem tud 1.hu 11.5).

hogy a Tiny változókra is lehetõvé tegyük az egészeknél szokásos mûveleteket. mint a konverziós függvény visszatérési értékét: Tiny::operator int() const { return v. határozzuk meg a Tiny-rõl int-re való automatikus konverziót. Definiálhatunk például a 6 bites. hogy a konverzió céltípusa az operátor nevének része és nem szabad kiírni. } int Tiny::operator int() const { return v. Tiny(int i) { assign(i). akkor arra a helyre a megfelelõ int érték fog kerülni: int main() { Tiny c1 = 2. // c3 = 60 // nincs tartományellenõrzés (nem szükséges) // i = 64 // tartományhiba: c1 nem lehet 64 // i = -4 // tartományhiba: c2 nem lehet -4 // nincs tartományellenõrzés (nem szükséges) } . Tiny c3 = c2-c1. a Tiny::operator int()-et.hu 362 Absztrakciós módszerek rozza meg az X típus T-re való konverzióját. hogy az érték a megengedett tartományba esik-e. } // konverzió int típusra Amikor egy Tiny egy egésztõl kap értéket vagy kezdõértéket. return *this. Ha egy int helyén egy Tiny szerepel. nem negatív egészeket ábrázoló Tiny osztályt. } Tiny& operator=(int i) { assign(i). az alapértelmezett másoló konstruktor és értékadás éppen megfelelõ. Jegyezzük meg. i = c3-64. int i = c1+c2. } public: class Bad_range { }. v=i. c3 = c4.Forrás: http://www. operator int() const { return v. ellenõrizzük. Ahhoz. Minthogy egy Tiny másolásakor nincs szükség az értékellenõrzésre. melynek objektumait aritmetikai kifejezésekben szabadon keverhetjük egészekkel: class Tiny { char v. } // helyes // hiba Ilyen tekintetben a konverziós operátor a konstruktorra hasonlít. c1 = c1+c2. } }. void assign(int i) { if (i&~077) throw Bad_range(). c2 = c3-64. Tiny c4 = c3.doksi. Tiny c2 = 62.

Általában azonban nem jó ötlet adatvesztéssel járó automatikus konverziót meghatározni két típus között. az a kifejezések többértelmûségéhez vezethet. Célszerû takarékoskodni a konverziós operátorok bevezetésével.).Forrás: http://www. amely automatikusan a cin objektum állapotát tükrözõ értékre alakítódik.3. ha van olyan X::operator=(Z) értékadó operátor. Az istream és ostream típusok egy konverzió segítségével támogatják az alábbihoz hasonló vezérlési szerkezeteket: while (cin>>x) cout<<x. Ezt azután a while utasítás ellenõrzi (§21.doksi.Tiny(i)) vagy int(t)+i ? } Ezért vagy felhasználói konverziókra építsünk. Ha túl sok van belõlük. Ha késõbb valamelyik ilyen függvény annyira népszerû lesz.3. A többértelmûséget mint hibát jelzi ugyan a fordítóprogram. // hiba. void f(Tiny t.hu 11. int i) { t+i. amely szerint V típus egyben Z is.Tiny). Talán a legjobb eljárás az. A kezdeti értékadásnál hasonló a helyzet.4. hogy alkalmazása nem „elegáns” többé. A cin>>x bemeneti mûvelet egy istream& referenciát ad vissza. . 11. akkor kicserélhetjük az X::operator int() konverziós operátorra. de ne mindkettõre. de kiküszöbölni fáradságos lehet.1. hogy többértelmûség lép fel a felhasználói és a beépített operátorok között: int operator+(Tiny. Többértelmûség Egy X osztályú objektum értékadása egy V típusú értékkel akkor megengedett. többértelmû: operator+(t. ellentétben az értékadással és a kezdõérték-adással. vagy ha van egy egyedi konverzió V-rõl Z-re. lehetséges. Ha vannak felhasználói konverziók és felhasználói operátorok is. ha kezdetben nevesített függvényekkel végeztetjük az átalakítást (például X::make_int()). Operátorok túlterhelése 363 A konverziós függvények különösen hasznosak olyan adatszerkezetek kezelésekor. vagy felhasználói operátorokra. amelyeknél az adatoknak (a konverziós operátor által definiált) kiolvasása egyszerû feladat.

. */ XX(int).. } // h(double(1)) vagy h(XX(1))? h(double(1))! A h(1) hívás a h(double(1)) hívást jelenti. */ Z(X). Z g(Z). se nem a legegyszerûbben leírható. A konverziós szabályok se nem a legegyszerûbben megvalósítható. de nem is az elképzelhetõ legáltalánosabb szabályok. g(Z("Suzy")).. // hiba: többértelmû f(X(1)) vagy f(Y(1))? // rendben // rendben // hiba: két felhasználói konverzió szükséges.. .. amit egy nem sejtett konverzió alkalmazása okoz. mert ehhez csak egy szabványos (és nem felhasználói) konverzióra van szükség (§7. ez pedig hiba: class X { /* . g("Mack"). void k1() { f(1). }.4). class Y { /* . }. Viszont viszonylag biztonságosak és alkalmazásukkal kevésbé fordulnak elõ meglepõ eredmények. mint megtalálni egy hibát. f(Y(1)). g(X("Doc"))..doksi. void h(double).. Ezt a helyzetet közvetlen konverzióval kell megoldani. Y f(Y). ha szükségesek egy hívás feloldásához: class XX { /* . f(X(1)). X f(X).hu 364 Absztrakciós módszerek Bizonyos esetekben a kívánt típusú értéket konstruktorok és konverziós operátorok ismételt alkalmazásával állíthatjuk elõ. az automatikus felhasználói konverzióknak csak egy szintje megengedett. }. X(char*).Forrás: http://www. void h(XX). g(Z(X("Mack")))-et // nem próbáltuk // rendben: g(Z(X("Doc"))) // rendben: g(Z(X("Suzy"))) } A felhasználói konverziókat a fordítóprogram csak akkor veszi figyelembe. */ X(int). A programozónak sokkal könnyebb egy többértelmûséget feloldani. }. Néha a kívánt típusú érték többféleképpen is létrehozható. void k2() { h(1).. */ Y(int). class Z { /* .

Quad r2 = Quad(a1)+a2.doksi.. }.Quad). egyszerre csak egy operátornak és paramétereinek figyelembe vételével.int(). // d = a. a másik pedig az. // i = a. operator int().. akkor az értékadás feloldása ezen típusok figyelembe vételével történik: class Real { public: operator double(). } // kétszeres pontosságú összeadás // Quad aritmetika kikényszerítése Ezen tervezési mód választásának egyik oka.double(). void f(double a1. }.int(). Quad operator+(Quad. } Az elemzés itt is alulról felfelé történik.hu 11. double a2) { Quad r1 = a1+a2. // . // d = a... milyen fokú pontosságot akar a programozó egy összeadásnál. i = a. // . hogy a visszatérési értéket nem vesszük figyelembe a túlterhelések feloldásakor: class Quad { public: Quad(double). int i = a. . Ha egy kezdeti vagy egyszerû értékadás mindkét oldalának eldõlt a típusa. // i = a.double(). Operátorok túlterhelése 365 Az elemzés során alkalmazott szigorúan „alulról felfelé” való haladás elvébõl az is következik. hogy nem a fordítóprogram dolga eldönteni.Forrás: http://www. hogy a szigorú „alulról felfelé” való haladás elve érthetõbb. void g(Real a) { double d = a. d = a.

Ha egy tagfüggvényt static-ként határozunk meg (§10. akkor ez csak az elsõ két tulajdonságot jelenti. hogy minden felhasználó írhassa és olvashassa a Matrix és Vector osztályok teljes adatábrázolását.hu 366 Absztrakciós módszerek 11. j<4. j++) r. }. logikailag különbözõ dolgot jelzünk: 1. A szorzást megvalósító függvény azonban nem lehet mindkét osztály tagja. Vector operator*(const Matrix& m. class Matrix { Vector v[4]. Nem is akarunk általános. // . Definiáljunk például egy Matrix-ot egy Vector-ral szorzó operátort. for (int i = 0. A függvényt az osztály egy objektumára kell meghívni (egy this mutató áll a rendelkezésére). három. } . alacsonyszintû hozzáférést megengedni. for (int j = 0. i++) { // r[i] = m[i] * v. Természetesen mind a Matrix.v[i].v[i] = 0. és csak tagfüggvényeiken keresztül kezelhetjük õket. i<4. 2. const Vector&). hogy ezt elkerüljük. Barát függvények Amikor egy függvényt egy osztály tagjaként adunk meg. a * operátort mindkét osztályban friend („barát”) függvényként határozzuk meg: class Matrix.v[i] += m... A függvény hozzáférhet az osztály deklarációjának privát részeihez. 3. // . class Vector { float v[4].v[j] * v. A függvény az osztály hatókörébe tartozik. friend Vector operator*(const Matrix&.Forrás: http://www. const Vector&)..5. const Vector& v) { Vector r.v[j]. r. }.4). mind a Vector osztály alkalmazza az adatrejtés elvét. friend Vector operator*(const Matrix&.. ha friend-ként („barátként”).2. } return r.doksi. Ahhoz. csak az elsõt.

11.4).. int* next().... // . hogy egy osztályt tag (beágyazott osztály) vagy nem tag barátként adunk meg (§24.hu 11. E deklaráció hatására a List_iterator osztály összes tagfüggvénye a List osztály barát függvénye lesz. mint a tagfüggvények.1. A barát függvények elérése A tagfüggvények deklarációjához hasonlóan a friend deklarációk sem vezetnek be új nevet a tartalmazó hatókörbe: class Matrix { friend class Xform.. Világos. }. Egy osztály tagfüggvénye lehet egy másik osztály barát függvénye: class List_iterator { // . // hiba: a hatókörben nincs Xform // hiba: a hatókörben nincs invert() .Forrás: http://www.5. Számos esetben viszont választhatunk. }. hogy friend osztályokat csak szorosan összetartozó fogalmak kifejezésére szabad használnunk. így ugyanolyan mértékben hozzátartoznak az osztály felületéhez. friend Matrix invert(const Matrix&). Operátorok túlterhelése 367 A friend deklarációt az osztály privát és nyilvános részébe is tehetjük. }. // . Ennek jelzésére egy rövidítés szolgál: class List { friend class List_iterator... class List { friend int* List_iterator::next(). }.doksi. Matrix (*p)(const Matrix&) = &invert. // . A tagfüggvényekhez hasonlóan a barát függvényeket is az osztály deklarációjában adjuk meg.. Xform x. Nem szokatlan helyzet. hogy egy osztály összes tagfüggvénye egy másik osztály „barátja”.

ez kifejezetten fontos. még akkor is. h(x). friend class AE.. */ }. // Y "barátja" } A barát függvényeket ugyanúgy megadhatjuk pontosan. // X h() "barátja" } // értelmetlen // paramétere alapján megtalálható // a hatókörben nincs f() . mint a barát osztályokat. azoknál a sablon osztályoknál pedig. ha egy osztály nem ad hozzá titokban új neveket a tartalmazó hatókörhöz. máskülönben nem hívhatjuk meg: // a hatókörben nincs f() class X { friend void f(). */ }. class Z { /* . hogy egy barát függvényt vagy egy tartalmazó hatókörben kell közvetlenül megadnunk.6). void g(const X& x) { f(). }. vagy az osztályának megfelelõ paraméterrel kell rendelkeznie. // nem "barátja" Y-nak namespace N { class X { /* . amelyek több különbözõ környezetben példányosíthatók (13. A közvetlenül tartalmazó névtér hatókörén kívüli neveket nem veszünk figyelembe: class AE { /* . // Y "barátja" class Y { friend class X..hu 368 Absztrakciós módszerek Nagy programok és osztályok esetében elõnyös. */ }. fejezet).. friend class Z.2.. // a Matrix "barát" invert()-je } Ebbõl következik.. de elérhetjük paramétereik alapján is (§8.doksi. friend void h(const X&). ha nem a közvetlenül tartalmazó hatókörben adtuk meg: void f(Matrix& m) { invert(m).Forrás: http://www.. }. A barát (friend) osztályt elõzõleg meg kell adnunk a tartalmazó hatókörben vagy ki kell fejtenünk az osztályt közvetlenül tartalmazó nem osztály típusú hatókörben.

m1(). 99.5. Operátorok túlterhelése 369 11. Mivel egy tagfüggvény neve az osztályra nézve lokálisnak számít. statikus tag vagy barát?”.Forrás: http://www. hacsak nem szól valamilyen érv amellett. friend int f2(const X&).2.. amely egy mûvelet különféle módozatait jeleníti meg: class X { // ..5). friend int f1(X&).doksi. A tagfüggvényeket csak az adott osztály objektumaira alkalmazhatjuk.3. mert nem const referencia paraméterekre a fordító nem alkalmaz felhasználói átalakítást (§5. hogy „tényleg szüksége van-e az ábrázolás elérésére?” Általában kevesebb függvénynek van erre ténylegesen szüksége. §11. Vegyünk egy X osztályt. int m2() const. Az f1() globális függvény hasonló tulajdonsággal rendelkezik. destruktoroknak és a virtuális függvényeknek (§12. hogy minél kevesebb függvény érje el közvetlenül az adatábrázolást és hogy az adatlekérdezést a segédfüggvények megfelelõ körével támogassuk.5. hogy nem tag függvény legyen.m2().6). friend int f3(X).m1()-et // hiba: nem próbáltuk X(99). hogy „ez a függvény tag legyen. }. X(int).2. } // hiba: nem próbáltuk X(99). Bizonyos mûveleteknek tagoknak kell lenniük: például a konstruktoroknak. Barátok és tagfüggvények Mikor használjunk barát függvényt és mikor jobb választás egy tagfüggvény egy mûvelet számára? Az elsõ szempont egy osztálynál az. mint elsõ ránézésre gondolnánk. hanem az. Sokszor azonban van mérlegelési lehetõség. Az f2() és f3() paramétereire azonban alkalmazható ilyen: .hu 11. felhasználói átalakítást a fordító nem végez: void g() { 99. Ezért az elsõdleges kérdés nem az. int m1(). hogy a 99-bõl X típusú objektumot csináljon.m2()-õt Az X(int) átalakítást a fordító nem alkalmazza. a függvényt inkább tagfüggvényként adjuk meg.

hu 370 Absztrakciós módszerek void h() { f1(99). Ezenkívül. hogy valaki nem ad-e majd meg valamikor egy konverziós operátort. amelynek balértékre van szüksége. Ha azonban az inv() azt a Matrix-ot invertálja. Nem tudhatjuk. Az ilyen mûveleteknek gyakran az operandus-osztály ábrázolásának elérésére van szükségük. a külsõ függvények neve hosszabb szokott lenni. amelyikre alkalmaztuk és nem egy új Matrix-ként adja vissza az inverzt. f3(99). míg a tagfüggvény automatikusan használhatja a this mutatót. hogy egy jövõbeli módosítás nem változtatja-e meg az objektum állapotát.). mint a másik lehetséges m. és azt sem láthatjuk elõre. ha egy m Matrix inverzérõl van szó. Megfordítva: ha egy mûvelet összes operandusa automatikusan konvertálható. az objektum állapotát megváltoztató mûvelet vagy tag legyen. akkor nincs kényszerítõ ok arra sem. . ha alapvetõ adattípusra alkalmazzuk (=. amely paraméterként const referencia vagy nem referencia típust vár. ha alapvetõ adattípusra alkalmazzuk azokat (+. } Ezért egy. A tagfüggvényhívási forma világossá teszi a felhasználó számára. Ez gyakori eset olyan mûveleteket megvalósító függvényeknél. hogy az objektum állapota megváltozhat. || stb. a legtermészetesebb módon felhasználói típus tagfüggvényeként definiálhatunk. // hiba: nem próbáltuk f1(X(99))-et f2(99). Ha más szempontok nem játszanak közre. akkor persze csak tagfüggvény lehet.inv() jelölés. Olyan mûveletet. ++ stb.). akkor a megvalósító függvény csak olyan nem tag függvény lehet. Ilyenkor a programozó aszerint dönthet. -. mivel a tagfüggvények neve az osztályra nézve lokálisnak számít. hogy melyik formát részesíti elõnyben. Továbbá sokkal rövidebbek a kifejezések egy tagfüggvény törzsében.Forrás: http://www. // rendben: f2(X(99)).doksi. ezért aztán a kétoperandusú operátorok a friend függvények leggyakoribb forrásai. A legtöbb embernek például jobban tetszik az inv(m) jelölés. melyeknek nincs szükségük balértékre. Ha nincs típuskonverzió. *=. egy nem tag függvénynek meghatározott paraméterre van szüksége. hogy válasszunk a tagfüggvény és a referencia paramétert váró barát függvény közül. // rendben: f3(X(99)). referencia paraméter használata esetén ez sokkal kevésbé nyilvánvaló. válasszunk tagfüggvényt. mint a külsõ függvénybeli megfelelõik. vagy pedig nem const referencia (vagy nem const mutató) paraméterû globális függvény.

j++) sum. de objektum-értéket ad vissza. } Ez az operator+() az operandusokat referenciákon keresztül éri el. Nagy objektumok A complex osztály mûveleteinek paramétereit complex típusúként határoztuk meg. friend Matrix operator*(const Matrix&. Referenciát visszaadni hatékonyabbnak tûnhet: class Matrix { // .3). }. Minthogy a függvénybõl az eredményre vonatkozó referenciát adjuk vissza. const Matrix&). megadhatunk referencia típusú paramétereket kezelõ függvényeket: class Matrix { double m[4][4]. friend Matrix operator+(const Matrix&.m[i][j]. const Matrix& arg2) { Matrix sum. nagymérvû másolások nélkül is. Mutatókat nem használhatunk. de valószínûleg „olcsóbb”.doksi.m[i][j] = arg1. friend Matrix& operator+(const Matrix&. j<4. az eredmény maga nem lehet automatikus változó (§7.hu 11. Ez azt jelenti. const Matrix&). const Matrix&).6. }. i++) for (int j=0. de egy memória-lefoglalási problémát okoz. hogy a paraméterek minden mûveletnél lemásolódnak.m[i][j] + arg2. A nagymérvû másolásokat elkerülendõ. az . public: Matrix(). for (int i=0. Nem minden osztálynak van azonban kényelmesen kicsi ábrázolása.. Operátorok túlterhelése 371 11. mint egy pár mutatóé. i<4. Két double másolása „költséges” mûvelet lehet ugyan. A referenciák alkalmazása nagy objektumokra is lehetõvé teszi a szokásos aritmetikai mûveletek használatát. Ez szabályos kód. const Matrix&). mert a mutatóra alkalmazott operátorok jelentését nem változtathatjuk meg. friend Matrix& operator*(const Matrix&. Mivel egy mûveletet többször is alkalmazhatunk egy kifejezésen belül. return sum. Az összeadást így definiálhatnánk: Matrix operator+(const Matrix& arg1..Forrás: http://www.

4. mint a=b+c vagy a+b*i (§21.Forrás: http://www. hogy közben a lehetõ legkevesebb helyfoglalás és másolás történik (§11. Az eredmény másolásának elkerülésére vannak módszerek. ha a mátrix típust csak a tényleges adatot tároló típus leírójaként (handle. Ezért aztán az eredménynek jellemzõen a szabad tárban foglalnánk helyet. amikor olyan kifejezések kiértékelése történik. és programozni is sokkal egyszerûbb.12 és §11.. §25. Egy másik módszer háromváltozós mûveletek meghatározására és azok olyankor automatikusan történõ meghívására támaszkodik.7).6.4. return res. return buf[nbuf++].14[18]). Így aztán a mátrixleírók úgy képviselhetik az objektumokat. } Matrix& operator+(const Matrix& arg1. Ez az eljárás azonban a visszatérési értékként objektumot és nem referenciát vagy mutatót használó operátorokon alapul. mint az objektum szabad tárba helyezése és onnan eltávolítása. amelyhez max_matrix_temp-nél több ideiglenes érték kell! Hibákra kevesebb lehetõséget adó módszer. . // . A legegyszerûbb ezek közül egy statikus objektumokból álló átmeneti tár használata: const max_matrix_temp = 7. ha olyan kifejezést találnánk írni. const Matrix& arg2) { Matrix& res = get_matrix_temp(). De az ég legyen irgalmas. } Így egy Matrix másolása csak egy kifejezés értékén alapuló értékadáskor történik meg.3 és §22. static Matrix buf[max_matrix_temp]. Matrix& get_matrix_temp() { static int nbuf = 0. if (nbuf == max_matrix_temp) nbuf = 0.7) határozzuk meg. kódés adatméretben mérve) gyakran olcsóbb.hu 372 Absztrakciós módszerek eredmény nem lehet lokális statikus változó sem.doksi.. A visszatérési érték másolása (végrehajtási idõben.

. } int main () { string s = "Newton". hogy a kezdeti és az egyszerû értékadás különbözõ mûveletek (§10. Nem nehéz feladat az s értékének egy másolatát az arg formális paraméterbe másolni. }. Nem lehet eléggé hangsúlyozni.1). amikor a destruktorral is számolnunk kell. amikor egy X típusú objektumnak egy ugyanilyen objektumot adunk kezdõértékül. Operátorok túlterhelése 373 11. // konstruktor: objektumok létrehozása // másoló konstruktor // másoló értékadás: takarítás és másolás // destruktor: takarítás Ezenkívül még háromféle helyzetben másolódik egy objektum: átadott függvényparaméterként. hogy az s változó értékének "Newton"-nak kell lennie a g() meghívása után. illetve kivételként. akkor az X(const X&) másoló konstruktor kezeli azt az esetet.hu 11. X& operator=(const X&). a string osztály másoló konstruktorának hívása ezt megteszi. s = g(s). amikor egy olyan ideiglenes . Ennek szerepe azonos az egyéb kezdeti értékadásokéval. Ilyen esetben a másoló konstruktor végzi a munkát: string g(string arg) { return arg. még ha kevésbé nyilvánvaló is. a string(const string&) újabb hívása következik. egy addig kezdõérték nélküli változó. Amikor g() visszaadja a visszatérési értéket.7. ha X egy típus. Alapvetõ operátorok Általánosságban. Ugyanez igaz a visszatérési értékre és a kivételre is. } // string érték szerint átadva (másoló konstruktor használatával) // string visszaadása (másoló konstruktor használatával) // string kezdõértéket kap (másoló konstruktor használatával) Világos. Ez különösen fontos akkor. X(const X&).4.Forrás: http://www. X(Sometype). akkor az osztálynak valószínûleg szüksége lesz az objektum létrehozását.doksi. a formális paraméter kap kezdõértéket. függvény visszatérési értékeként. Ha paraméterként kerül átadásra.4. megsemmisítését és másolását végzõ összes függvényre: class X { // . ~X().. Ha az X osztálynak van valamilyen nem magától értetõdõ feladatot – például a szabad tárban lefoglalt memória felszabadítását – végzõ destruktora.

// z kezdeti értékadása complex(2)-vel Máskor viszont nem kívánatos és hibák forrása lehet: string s = 'a'. Explicit konstruktorok Alapértelmezés szerint az egyparaméterû konstruktor egyben automatikus konverziót is jelent.hu 374 Absztrakciós módszerek változó kap értéket.4.. Bizonyos típusok számára ez ideális: complex z = 2. Vagyis egy explicit-ként megadott konstruktort csak közvetlen módon lehet meghívni.2. String s4 = "Brian". Az ideiglenes változók aztán persze a string::~string() destruktor segítségével megsemmisülnek (§10.2. Ha a programozó nem ad meg másoló konstruktort vagy másoló értékadást egy osztály számára. explicit String(int n).doksi.7. hogy a másoló mûveletek nem öröklõdnek (§12. // n bájt lefoglalása String(const char* p). a fordítóprogram hozza létre a hiányzó függvényt vagy függvényeket (§10.3. String s5("Fawlty").Forrás: http://www. String s2(10). amely aztán az s-nek ad értéket. String s1 = 'a'. Így ahol elvileg egy másoló konstruktorra van szükség (§11. hogy az s-et megadó programozó ezt akarta volna. // s karakterlánc. ott az explicit konstruktor nem hívódik meg automatikusan: class String { // .10). Ez egyben azt is jelenti.// a kezdõérték (p) egy C stílusú karakterlánc }. 11. Az automatikus konverziókat az explicit kulcsszó alkalmazásával akadályozhatjuk meg. String s3 = String(10).1. void f(String).3).5). Hatékonysági okokból az egyik (de csak az egyik) másolást gyakran elhagyhatjuk.. int('a') számú elemmel Nagyon valószínûtlen. String g() { // hiba: nincs automatikus char->String átalakítás // rendben: String 10 karakternyi hellyel // rendben: String 10 karakternyi hellyel // rendben: s4 = String("Brian") .4).

feb.21). hogy String s2(10). Az operator int()-nek köszönhetõen a Year automatikusan mindenhol int-té alakul. Ha a Date osztály létfontosságú szerepet játszott volna. hogy az int-nek Year-ré va- . // hiba: nincs automatikus char ->String átalakítás és aközött. hogy a konstruktort explicit-ként adtuk meg.Year(1978)). Date d3(1978. hogy fordítási idõben szigorúbb ellenõrzések történjenek: class Year { int y.3). }. Month m. // . // hiba: nincs automatikus int ->String átalakítás // rendben: f(String("Arthur")) String* p1 = new String("Eric").. mint kitalált példákban. ahol szükséges. A Date osztályban egy sima int-et használtunk az év ábrázolására (§10. class Date { public: Date(int d. } }. Operátorok túlterhelése 375 f(10). hogy String s1 = 'a'. biztosítottuk. // hiba: nincs automatikus int ->String átalakítás A különbség aközött.feb. Azáltal.. String* p2 = new String(10).hu 11. f("Arthur"). akkor bevezethettük volna a Year osztályt. Year y). f(s1).doksi. wrapper) osztály az int körül. // hiba: a 21 nem Year típusú // rendben // Year létrehozása int-bõl // átalakítás Year-rõl int-re A Year egy egyszerû „csomagoló” (beburkoló. Date d4(21. f(String(10)). public: explicit Year(int i) : y(i) { } operator int() const { return y. } return 10. // rendben: karakterlánc 10 karakternyi hellyel csekélynek tûnhet. de igazi kódban kevésbé az.Forrás: http://www.

ha nem. is definiálhatunk. új Pair-t hozunk // létre és az alapértelmezett 0 értéket adjuk vissza { . visszaadjuk az értékét. void print_all() const. vector<Pair> vec. most egy asszociatív tömb típust: class Assoc { struct Pair { string name. val(v) { } }. ha megtaláltuk. Assoc(const Assoc&). double v =0) :name(n). Minthogy a Year tagfüggvényeit könnyû helyben kifejtve (inline) fordítani.8. asszociatív tömböket stb.5 pontban: double& Assoc::operator[ ](string& s) // megkeressük s-t. 11. mint az §5. Assoc& operator=(const Assoc&). így aztán vektorokat.1) is alkalmazható. Hasonló módszer tartomány (intervallum) típusokra (§25.Forrás: http://www.6. Indexelés Osztály típusú objektumoknak az operator [ ] (subscripting) függvény segítségével adhatunk „sorszámot” (indexet). double& operator[ ](string&).hu 376 Absztrakciós módszerek ló alakítása csak ott történik meg. A megvalósításban ugyanazt az egyszerû és nem túl hatékony keresési módszert használjuk. }.5-beli példát. Pair(string n ="". double val. Példaként írjuk most újra a §5. ahol ezt kérjük és a „véletlen” értékadások a fordításkor kiderülnek. amelyben egy asszociatív tömb segítségével írtunk egy fájlban a szavak elõfordulását megszámoló kis programot. // a másolást megakadályozandó privát // a másolást megakadályozandó privát Az Assoc típusú objektumok Pair-ek vektorát tartalmazzák. a futási idõ és a szükséges tárhely növekedésétõl sem kell tartanunk. public: Assoc() {} const double& operator[ ](const string&). Akkor egy függvényt használtunk. Az operator[ ] második paramétere (az index) bármilyen típusú lehet.doksi.

Operátorok túlterhelése 377 for (vector<Pair>::iterator p = vec.hu 11. szükség van egy kimeneti függvényre: void Assoc::print_all() const { for (vector<Pair>::const_iterator p = vec. vec. ahol a kifejezés a bal oldali. 11. Az operator()() paraméterlistájának kiértékelése és ellenõrzése a szokásos paraméter-átadási szabályok szerint történik. ++p) if (s == p->name) return p->val.begin(). . A függvényhívó operátor túlterhelése elsõsorban olyan típusok létrehozásakor hasznos.0)).3. Függvényhívás A függvényhívás (function call).1 pont.val.3) Minthogy az Assoc objektum ábrázolása kívülrõl nem érhetõ el. ++p) cout << p->name << ": " << p->val << '\n'. p!=vec. mint egy kétoperandusú mûvelet.back(). vec.doksi. // kezdõérték: 0 // az utolsó elem visszaadása (§16.4. } Végül megírhatjuk a fõprogram egyszerû változatát: int main() // szavak elõfordulásának megszámlálása a bemeneten { string buf.begin(). Az operator[ ]() függvényeknek tagfüggvénynek kell lenniük. while (cin>>buf) vec[buf]++.end(). amelyeknek csak egy mûveletük van vagy általában csak egy mûveletük használatos.Forrás: http://www.end().9.push_back(Pair(s. A () hívó operátor a többi operátorhoz hasonló módon túlterhelhetõ. p!=vec. vagyis a kifejezés(kifejezés-lista) jelölés úgy tekinthetõ. } Az asszociatív tömb ötletét továbbfejleszti a §17.print_all(). } return vec. a kifejezés-lista pedig a jobb oldali operandus. Assoc vec.

begin().i). Az ilyen függvényobjektumok fontosak. Mi lenne. } }. hogy a valamiképpen függvényként viselkedõ objektumokat függvényként hívhassuk meg. public: Add(complex c) { val = c. hogy olyan kódot írjunk.add23).doksi.negate).3)-at akarnánk hozzáadni? Ezt könnyen megtehetjük: void add23(complex& c) { c += complex(2. for_each(ll. melyet többször meghívva egy-egy tetszõleges értéket adhatunk az elemekhez? Olyasmire van szükségünk.begin().begin().end().hu 378 Absztrakciós módszerek A ( ) hívó mûvelet legnyilvánvalóbb és talán legfontosabb alkalmazása az. Vegyük az alábbi példát: void negate(complex& c) { c = -c. } // az érték mentése Add(double r.ll. double i) { val = complex(r.aa.3).end(). amelyben valamilyen nem magától értetõdõ mûveletet paraméterként adunk át. } void g(vector<complex>& aa. } Hogyan tudnánk egy olyan függvényt írni. Egy függvényként viselkedõ objektumot függvényszerû vagy egyszerûen függvényobjektumnak hívunk (§18.begin().Forrás: http://www. } // a paraméter növelése az értékkel . list<complex>& ll) { for_each(aa. mert lehetõvé teszik. melyek egy függvényt hívnak meg egy tároló minden elemére. ami nem „tiszta” megoldás.4). for_each(ll. A standard könyvtárban például sok olyan algoritmus található. void operator()(complex& c) const { c += val. ha a lista minden eleméhez complex(2. Ez a függvényeknek nem természetes tulajdonsága. list<complex>& ll) { for_each(aa.end(). Jellemzõ megoldásként valahova a függvényt körülvevõ környezetbe helyezve adjuk át az értéket. } // a vektor összes elemének negálása // a lista összes elemének negálása Ez a vektor és a lista minden elemét negálja.end().ll. amely a megfelelõ módon mûködik: class Add { complex val. aminek megadhatjuk a kívánt értéket és utána ezt az értéket használja fel minden hívásnál.aa. } void f(vector<complex>& aa.negate).add23). Viszont írhatunk egy osztályt.

Forrás: http://www.4). Iter e. utótagként használt operátorként definiálhatjuk.. amely a ( ) mûveletet alkalmazza a harmadik paraméterére. Operátorok túlterhelése 379 Egy Add osztályú objektum kezdõértékének egy komplex számot adunk. Az operator()() további népszerû alkalmazásai a részláncok képzésére vagy több dimenziós tömbök indexelésére (§22. Mindez azért mûködik. és nagyon hasznos (lásd §3. majd a ( ) mûveletet végrehajtatva ezt a számot hozzáadjuk a paraméterhez: void h(vector<complex>& aa. Az operator()()-nak tagfüggvénynek kell lennie.Add(2. list<complex>& ll. }.Add(z)).end(). §18. } Ez a tömb minden eleméhez complex(2.doksi. mi is igazából a harmadik paraméter: template<class Iter. de egyszerû. for_each(ll. mert a for_each egy sablon (template). a lista elemeihez pedig z-t. amelyet aztán a for_each ismételten felhasznál. return f. .aa.5) való használat.begin(). Vegyük észre. class Fct> Fct for_each(Iter b. X* operator->().8. Nem egyszerûen egy egyszer vagy többször meghívott függvényrõl van szó. anélkül. dereferencing) operátort egyparaméterû.. complex z) { for_each(aa. hatékony. hogy Add(z) egy olyan objektumot hoz létre. Legyen adott egy osztály: class Ptr { // . hogy törõdne vele.end().10. Indirekció A -> indirekció („hivatkozástalanítás”.begin().hu 11.3)-at fog adni.5.4. Fct f) { while (b != e) f(*b++).ll.3)). } Elsõ pillantásra ez a módszer furcsának tûnhet. 11. A többször meghívott függvény az Add(z) operator()() függvénye.

public: Rec_ptr(const char* p) : identifier(p).operator->(). return in_core_address.. amelyek mutatóként viselkednek. amellyel a lemezen tárolt Rec osztályú objektumok érhetõek el. X* q2 = p. a Rec_ptr::operator->() függvény a memóriába tölti az objektumot. a Rec_ptr destruktora pedig szükség esetén a megváltozott objektumot a lemezre írja: class Rec_ptr { const char* identifier. azaz olyan objektumoké. valahányszor egy objektumot érnek el rajtuk keresztül. a mutatókhoz nagyon hasonló módon: void f(Ptr p) { p->m = 7. } // szintaktikus hiba // rendben A ->() operátor túlterhelésének fõ alkalmazása az okos vagy intelligens mutató (smart pointer) típusok létrehozása. A Rec_ptr konstruktora egy nevet vár. de ráadásul valamilyen tennivalót végeznek. }.identifier). hogy milyen m tagra mutat. Rec* Rec_ptr::operator->() { if (in_core_address == 0) in_core_address = read_from_disk(identifier). Az operator->() ebben az értelemben egyoperandusú utótag-operátor. Például létrehozhatunk egy Rec_ptr osztályt. } // (p.hu 380 Absztrakciós módszerek Ekkor a Ptr osztályú objektumokat az X osztály tagjainak elérésére használhatjuk.. így a tagnevet ki kell írni utána: void g(Ptr p) { X* q1 = p->. in_core_address(0) { } ~Rec_ptr() { write_to_disk(in_core_address.operator->() mutatóvá való átalakítása nem függ attól.Forrás: http://www. formai követelményei viszont nem újak. Rec* in_core_address. amikor azt a Rec_ptr-en keresztül el akarjuk érni.operator->())->m = 7 A p objektumnak a p. melynek segítségével a keresett objektum a lemezen megkereshetõ. } .doksi. // . } Rec* operator->().

. public: Y* operator->() { return p. akkor tanácsos lehet ezt úgy tenni. // . amelyben a ++. Ha egy osztályban több ilyen operátort határozunk meg. a Rec típus pedig paraméter.. }. + mûveletek értelmezettek. hogy a fenti egyenértékûség teljesüljön. Közönséges mutatók esetében a -> használata egyenértékû az egyváltozós * és [ ] használatával. Ha adott egy típus: Y* p. // . amire Rec_ptr mutat // Rec_ptr elõállítása s-bõl // s módosítása. ugyanúgy.Forrás: http://www. } Y& operator*() { return *p. // a Rec típus. +=. mint ahogy ++x és x+=1 is jó. elõször beolvassa a lemezrõl } Természetesen az igazi Rec_ptr egy sablon lenne. ha x egy olyan osztályú változó. a felhasználói operátorokra nézve ez nem biztosított. } }.m == p[0]. p->name = "Roscoe". ha szükséges. Egy valóságos program hibakezelést is tartalmazna és kevésbé naív módon kezelné a lemezt.hu 11. akkor teljesül a következõ: p->m == (*p).m Ahogy már megszokhattuk. Operátorok túlterhelése 381 A Rec_ptr-t így használhatjuk: struct Rec { string name. . Szükség esetén persze gondoskodhatunk errõl: class Ptr_to_Y { Y* p. ha x=x+1-gyel azonos hatással jár...doksi. void update(const char* s) { Rec_ptr p(s). } Y& operator[ ](int i) { return p[i].

Növelés és csökkentés Amint a programozó kitalál egy „intelligens mutatót”. ++p. Azt is el szeretnénk érni. 11. . ha tömbön belüli objektumra mutat. a -> operátor túlterhelése pedig tiszta. hogy korlátozott. ha mutatót vagy olyan típust ad vissza. A bejárók (iterátorok) (19. sokszor elõfordul.11. hogy az indirekció (dereferencing) kulcsfogalom. *p = a. a beépített típusokra értelmezett növelés és csökkentés mintájára. amelyre a -> alkalmazható. }. Az operator-> tagfüggvény kell. hogy legyen. azon oknál fogva.. p--. amely azonos jelentés mellett csak némi futási idejû hiba-ellenõrzéssel van kiegészítve. amikor a cél egy közönséges mutatónak egy okosra való kicserélése. hogy ehhez a ++ növelõ (increment) és -. közvetlen és hatékony módja annak egy programban való megjelenítésére. ezért ésszerû e megszorítás ellenõrzését a tényleges használatig elhalasztani. hogy p-t csak úgy lehessen növelni vagy csökkenteni. Ez különösen nyilvánvaló és szükséges olyankor. A -> operátor másik haszna. } // hagyományos használat // hoppá: 'p' tartományon kívüli és nem kaptuk el // rendben A p mutatót ki szeretnénk cserélni valamilyen Ptr_to_T osztályú objektumra. hanem érdekes programok egy osztálya számára fontos is. T* p = &v[0]. Vegyük például az alábbi – egyébként problematikus – hagyományos programot: void f1(T a) { T v[200]. fejezet) jellemzõ és lényegi példát adnak erre.csökkentõ (decrement) mûvelet is hozzátartozik. még a növelés vagy csökkentés hatására is.hu 382 Absztrakciós módszerek A -> operátor túlterhelhetõsége nem csak kis különlegesség.Forrás: http://www. Csak úgy használható.doksi.4). amelyre csak akkor tudjuk alkalmazni az indirekció operátort.. Valami ilyesmit szeretnénk tehát: class Ptr_to_T { // .2. de hasznos módon lehetõvé teszi a C++ nyelvben a delegációt (§24. Ha egy sablon osztály számára adjuk meg. hogy nem is kerül tényleges felhasználásra. sokszor dönt úgy. *p = a. ha tényleg egy objektumra mutat.

amelyek elõtagként (prefix) és utótagként (postfix) egyaránt használhatók. // csatolás s méretû v tömbhöz. melyik melyik.v. a kezdõérték p Ptr_to_T& operator++(). . T* v. // futási idejû hiba: 'p' tartományon kívüli ++p. Ptr_to_T p(&v[0].v. public: Ptr_to_T(T* p.az elõtagként. // csatolás önálló objektumhoz. A Prt_to_T osztályt használva a példa egyenértékû az alábbival: void f3(T a) // ellenõrzött { T v[200].200). Ptr_to_T operator++(int). hogy a többi (aritmetikai és logikai) egy paraméterû operátorhoz hasonlóan az ál-paraméter nélküli ++ és -. int s).Forrás: http://www. *p = a. p--. Ptr_to_T& operator--(). Magát az int-et nem használjuk. Operátorok túlterhelése 383 void f2(T a) // ellenõrzött { T v[200].és utótagként való használat között tesz különbséget. Ptr_to_T p(&v[0]. T& operator*(). Ezért a Ptr_to_T típus számára mindkét fajta növelõ és csökkentõ operátort definiálnunk kell: class Ptr_to_T { T* p. // elõtag // utótag // elõtag // utótag // elõtag Az int paraméterrel jelezzük a ++ utótagként való alkalmazását. p. }. csak ál-paraméter. *p = a. ha arra gondolunk. amely az elõ. Könnyen megjegyezhetjük.operator--(0). int size. a kezdõérték p Ptr_to_T(T* p). Ptr_to_T operator--(int).200). T* array.doksi. a paraméteres változat a „furcsa” utótagként való mûködéshez kell. // rendben } A növelõ és csökkentõ operátorok az egyetlenek a C++ nyelv operátorai között.hu 11.

Átdolgozása olyan sablonná.12[12]). Egy karakterlánc osztály Íme a String osztály egy valóságosabb változata.3 egy mutatósablont mutat be.doksi. ha azonos az értékük.. a karakterliterálokat. Egy többet tudó és/vagy több szolgáltatást nyújtó string osztály írása jó gyakorlat (§11.és bemenetet. az adatfolyam ki. a másolások számának csökkentésére pedig hivatkozásszámlálót használ.6. hogy több azonos értékû String is használhassa ugyanazt az eltárolt adatot.. }. // . // futási idejû hiba: 'p' tartományon kívüli // rendben A Prt_to_T osztály kiegészítése gyakorlatnak marad (§11. Ha megvagyunk vele. Ez a karakterlánc-osztály támogatja az érték szerinti mûködést (value semantics. // adatábrázolás // referencia char-ra // kivételkezeléshez . public: class Cref. nullával lezárt karaktertömbként tárolja. az ellenõrzött és ellenõrizetlen elérést. class Range { }. amely öröklõdés használata mellett is jól mûködik. érték-szemantika). és a Cref-et. p. hogy egy írás és olvasás között különbséget tevõ index-operátort támogasson: class String { struct Srep. fejezet). A §13. Az én majdnem valóságos String osztályom három segédosztályt használ: az Srep-et.operator++(). eldobhatjuk a gyakorlatainkat és használhatjuk a standard könyvtárbeli string-et (20. A karakterláncokat C stílusú. amely a céljainknak még éppen megfelel.operator*() = a.operator*() = a. az egyenlõségvizsgáló és összefûzõ mûveleteket. amely kivételeket is használ a futási idõben fellépõ hibák jelzésére. a Range-et az értéktartomány-megsértési hibákat jelzõ kivételek kiváltásához.14[19]). a karakteríró és -olvasó mûveleteket.14[7-12]).12. egy másik gyakorlat (§14.Forrás: http://www.hu 384 Absztrakciós módszerek } p. 11. Srep *rep. p.

Srep& operator=(const Srep&). const char* p) { if (sz != nsz) { delete[ ] s. sz = nsz. amit gyakran hívnak beágyazott osztálynak.. Operátorok túlterhelése 385 A többi taghoz hasonlóan a tagosztályokat (member class. } strcpy(s. ha szükséges // a másolás megakadályozása A String osztálynak megvannak a szokásos konstruktorai. s = new char[sz+1]. return new Srep(sz.Forrás: http://www. . } private: Srep(const Srep&). strcpy(s. n--. destruktora és értékadó mûveletei is: class String { // . // hely a lezáró nulla számára is // másolás. } ~Srep() { delete[ ] s. int sz. } void assign(int nsz. nested class-nak is) deklarálhatjuk az osztályban. } Srep* get_own_copy() { if (n==1) return this. int n..p).p).doksi. }. const char* p) { n = 1. majd késõbb kifejthetjük: struct String::Srep { char* s.hu 11. sz = nsz. // mutató az elemekre // karakterek száma // hivatkozásszámláló Srep(int nsz. s = new char[sz+1].s).

rep = x.Forrás: http://www..doksi. én elõnyben részesítem az érték szerinti mûködést.rep. } String& String::operator=(const String& x) { x. } String::String(const String& x) { x. hogy ennek támogatása ne kerüljön túl sokba.""). String& operator=(const String&). Ekkor az s1=s2 értékadás után s2 megváltoztatása s1-et is érintené. String& operator=(const char *). azaz egy s1=s2 értékadás után s1 és s2 két teljesen különbözõ karakterlánc lesz. vagyis ha késõbb az egyiket módosítjuk. Ha egy osztálynak megvannak a hagyományos aritmetikai mûveletei. ~String(). // . // x = "" // x = "abc" // x = másik_karakterlánc A String osztály érték szerint mûködik. amit csak szükség esetén kell másolni: String::String() // az alapértelmezett érték egy üres karakterlánc { rep = new Srep(0. rep = x. } // másoló értékadás // védelem az "st = st" ellen // az ábrázolás megosztása // másoló konstruktor // az ábrázolás megosztása A const char* paraméterû ál-másoló mûveletek bevezetésével a karakterliterálokat is megengedjük: .rep.rep->n++. A másik megoldás az lenne. ha a String osztály mutatókkal dolgozna. akkor annak nem lesz hatása a másikra. mint a komplex számokkal. mátrixokkal..rep->n++. }. return *this. } String::~String() { if (--rep->n == 0) delete rep. karakterláncokkal végzettek. Ahhoz viszont. if (--rep->n == 0) delete rep. vektorokkal. amely az adatábrázolásra mutat. String(const char*). a String-et leíróként ábrázolom.hu 386 Absztrakciós módszerek String(). String(const String&).

de a felhasználónak legyen módja egyszerre végignézni a teljes tartományt és a gyorsabb.hu 11.s). Sajnos. void check(int i) const { if (i<0 || rep->sz<=i) throw Range(). Operátorok túlterhelése 387 String::String(const char* s) { rep = new Srep(strlen(s). mert az ideális megoldás az lenne. char c) { rep=rep->get_own_copy(). } int size() const { return rep->sz. return rep->s[i]. // . } }. } char operator[ ](int i) const { check(i).s). ellenõrizetlen elérést használni: . rep = new Srep(strlen(s). hogy hatékony ellenõrizetlen mûveleteket adtam meg (egy kicsit kényelmetlenebb jelöléssel). else { rep->n--. Az ötlet az. ez a három követelmény nem teljesíthetõ egyszerre.. ha ezek a szokásos jelölést (azaz a [ ]-t) használnák. return Cref(*this. } char read(int i) const { return rep->s[i].Forrás: http://www. hogy a hagyományos [ ] jelöléssel az ellenõrzött elérés legyen biztosított a közönséges felhasználás számára. Én úgy készítettem el az osztályt. a lehetõ leghatékonyabbak lennének és a paraméter értékét is ellenõriznék. illetve kevésbé hatékony ellenõrzött eljárásokat (a hagyományos jelöléssel): class String { // . } void write(int i. } // Srep újrahasznosítása // új Srep használata Az egyes karakterláncokat elérõ operátorok megtervezése nehéz.. rep->s[i]=c.. } Cref operator[ ](int i) { check(i).s). } String& String::operator=(const char* s) { if (rep->n == 1) rep->assign(strlen(s).i).doksi.. } return *this.

} }. String& s. hogy írásakor meghívja a String::Sref::get_own_copy()-t: class String::Cref { friend class String. A tényleges másolást a String::get_own_copy() végzi. Abból a célból. amit aztán a felhasználó kedve szerint felhasználhat. // s.doksi.c). Cref(String& ss. hogy egyszerûen egy referenciát adunk vissza. A Cref úgy viselkedik. for (int i = 1.operator char() s[1] = 'c'. i<max.Forrás: http://www.read(i). a String::operator[ ]() egy Cref-et ad vissza.read(0). mert a String-et úgy határoztam meg. const String& r) { char c1 = s[1]. Itt például ez nem lehetséges. ha nem const objektumra hívták meg. return h. } void operator=(char c) { s. int ii) : s(ss). i++) h ^= s. hogy az elérõ függvényeket helyben kifejtve (inline) lehessen fordíttatni. Ezt a módszert általában íráskori másolásnak vagy „másolás íráskor”-nak (copy-on-write) hívják. // c1 = s.read(i)>>1. // hivatkozás s[i]-re // érték kijelölése // érték módosítása Például: void f(String s. ha nem fogadható el az a megoldás. például a [ ]-t úgy meghatározni. paraméter-átadással stb. const int max = s. olyan helyre kell elhelyezni definiciójukat.hu 388 Absztrakciós módszerek int hash(const String& s) { int h = s. ahonnan az Srep osztályé elérhetõ.operator[ ](1). hogy az az író és az olvasó jellegû hozzáférést is támogassa. megadott értékû String-ek ugyanazt a belsõ ábrázolást használják. vagy pedig az elérõ függvényeket kell inline-ként meghatározni a String-en kívül és az String::Srep után (§11. mint a char&. hogy az egyes értékadással.write(i. i(ii) { } public: operator char() const { return s. int i.operator=('c') .operator[ ](1).size(). azzal a különbséggel. } // ellenõrzés nélküli hozzáférés s-hez Nehéz dolog egy operátort. Tehát vagy az Srep-et kell a String osztályon belül megadni.14[2]). Megkülönböztetendõ az írást és az olvasást. míg az egyik String-et ténylegesen nem írják: az érték másolása csak ekkor történik meg.

const char* s) { return strcmp(x. s) != 0.rep->s.és bemeneti operátorokat. } friend bool operator!=(const String& x. Ahhoz.rep->s.Forrás: http://www. y. return b. String& operator+=(const String&). Hely-megtakarítás céljából a ki. Operátorok túlterhelése 389 } char c2 = r[1]. const String&). const char* s) { return strcmp(x.hu 11. s) == 0. // c2 = r. hogy egy nem const objektumra az s. String b) { a[2] = 'x'... char c = b[3].operator[ ](1) // hiba: char értékadás. friend ostream& operator<<(ostream&.rep->s.rep->s) == 0. String operator+(const String&. hogy teljessé tegyük a String osztályt. } .operator[ ](1) értéke Cref(s. friend istream& operator>>(istream&.operator[ ](1) = 'd' Vegyük észre. String& operator+=(const char*). String&). r. r[1] = 'd'. const char*). String operator+(const String&. } friend bool operator==(const String& x.rep->s. meghatározunk még egy sor hasznos függvényt: class String { // . A fõprogram pusztán egy kissé megdolgoztatja a String operátorokat: String f(String a. const String&).1) lesz. cout << "Az f-ben: " << a << ' ' << b << ' ' << c << '\n'.doksi. const String& y) { return strcmp(x. y.rep->s) != 0. } friend bool operator!=(const String& x. friend bool operator==(const String& x. } }. illetve az összefûzést meghagytam gyakorlatnak. const String& y) { return strcmp(x.

[5] Bíráljuk felül vagy tiltsuk meg az alapértelmezett másolást. [11] Az egyetlen „méret” paraméterû konstruktorok legyenek explicit-ek.14[10].13.Forrás: http://www. ha az egy adott típus számára nem megfelelõ.5.7. §11. y = f(x. cout << "Bemenet: " << x << ' ' << y << '\n'. inkább ne legyen tagfüggvény.9. fejezet).2. inkább tagfüggvény legyen. §11.2. [2] Nagy operandusok esetében használjunk const referencia paramétereket. [7] Ha egy függvénynek nincs szüksége az adatábrázolás elérésére. hogy a szokásos használati módot támogassuk.2. if (x != z) cout << "Az x sérült!\n". if (x == z) cout << "Az írás nem sikerült!\n". [8] Használjunk névteret. 11. String z = x.5. cout << "Kilépés: " << x << ' ' << y << ' ' << z << '\n'. 11. 11. [10] Többdimenziós tömb indexelésére használjunk ()-t. 11.2. [3] Nagy értékeket adó eredményeknél fontoljuk meg az eredmény-visszaadás optimalizálását. y.1. 11.y).hu 390 Absztrakciós módszerek int main() { String x. hogy a segédfüggvényeket osztályukhoz rendeljük.1. cout << "Adjon meg két karakterláncot!\n".3.4. 20. . §11. §11.6. 11. } Ebbõl a String osztályból még hiányoznak fontosnak vagy akár alapvetõnek tekinthetõ dolgok. [4] Hagyatkozzunk az alapértelmezett másoló mûveletekre.2. ha azok megfelelõek az osztályunk számára. §11. [9] Szimmetrikus mûveletekre használjunk nem tag függvényeket. x[0] = '!'.4. §11.3. Tanácsok [1] Operátorokat elsõsorban azért adjunk meg. cin >> x >> y. [6] Ha egy függvénynek szüksége van az adatábrázolás elérésére.doksi.2.6. például nincs az értéket C stílusú adatként visszaadó mûvelet (11.

12-beli String osztályt. extern int f(X). operator+(int). ne a gyakorlatok megoldása révén kapott saját változatot. struct Y { int i. Y operator+(X).doksi. }.hu 11. Y(X).12. extern X operator*(X. int i = 2. §11.5. X x = 1.Forrás: http://www. y + y. Y). y + 10 * y. f(y). X X(int). }. (*2) Milyen konverziókat használunk az alábbi program egyes kifejezéseiben: struct X { int i. [13] Legyünk óvatosak az automatikus konverziók bevezetésekor. 106 + y. hogy futás közben kiírjon minden megengedett kifejezést. int main() { i + 10.3. §11. (*2) Fejezzük be és teszteljük a §11. y + 10.4. . fejezet). 11. [14] Bal oldali operandusként balértéket váró mûveleteket tagfüggvényekkel valósítsunk meg. f(7). Y y = x.14. 2. x + y + i. x * x + i. operator int(). §11. Gyakorlatok 1. Operátorok túlterhelése 391 [12] Általános célra használjuk a szabványos string-et (20. } Módosítsuk a programot úgy.

5) Tervezzünk és készítsünk egy egyszerû reguláris kifejezés illesztõ eszközt a String osztály számára. (*2. hogy az mûködjön a standard könyvtárbeli string-gel. amelyiknél eltérõre. hogy a részlánc-operátort egy értékadás bal oldalán is fel lehessen használni. (Segítség: a String osztályéhoz hasonló társzervezésre lesz szükség. // bejáró s számára char& next(). (*4) Definiáljunk egy tetszõleges pontosságú aritmetikát támogató osztályt.) 8.Forrás: http://www. hogy ez egy konverziós operátor. (*2) Definiáljuk a String osztály számára az értéket C stílusú adatként visszaadó mûveletet. . mint egy int. 11. (Vagyis adott egy kurrens elemünk a String-ben. amelyiknél egy részláncot egy azonos hosszúságú teljes karakterláncra lehet cserélni. (*3) Tervezzük meg úgy a String osztályt. mint a RINT. hogy csak a következõ mûveletek engedélyezettek: (egy és két paraméterû) +. *. // hivatkozás a következõ elemre }. / és %. (egy és két paraméterû) -. amely pontosan úgy viselkedik. stílus és hatékonyság szempontjából a String egy belsõ bejárójával. (Segítség: ne definiáljuk a RINT::operator int()-et).5) A () operátor túlterhelésével határozzunk meg egy részlánc-mûveletet egy karakterlánc-osztály számára. (*2) Határozzunk meg a String osztály számára egy külsõ bejárót (iterátort): class String_iter { // hivatkozás karakterláncra és annak elemére public: String_iter(String& s). 10.5) Módosítsuk úgy a §11. 12. amely pontosan úgy viselkedik. Milyen más mûveletet szeretnénk egy karakterláncon végezni? 9. (Segítség: definiáljuk az INT::operator int()-et). // igény szerint további mûveletek Hasonlítsuk ezt össze hasznosság. de legalább 64 bites pontosságú. amely pontosan úgy viselkedik. (*1. aztán egy olyat.14[11]-beli eszközt. azzal a különbséggel.) 7. (*1. Vitassuk meg a C stílusú adat számára szükséges memória lefoglalásának különféle módjait. Vitassuk meg annak elõnyeit és hátrányait. (*3) Definiáljuk a LINT osztályt. (*1) Definiáljuk a RINT osztályt. és a vele kapcsolatos mûveletek. 6. mint egy int.doksi. (*2) Definiáljuk az INT osztályt. 4. A string definícióját nem változtathatjuk meg. 5. Teszteljük az 1000 faktoriálisának kiszámíttatásával.hu 392 Absztrakciós módszerek 3. Elõször egy olyan változatot írjunk.

mint négy float-ból álló vektort. mint négy Vec4-bõl álló vektort. melynek segítségével majdnem úgy használhatjuk valamely S és T x-ét és p-jét. hogy . 18. Néhány félrevezetõ megjegyzéssel is nagy zavart lehet kelteni. 22. (*2) Definiáljunk egy Vector típust. Bíráljuk felül a „népszerû” függvényeket referencia típusú paramétereket használó függvényekként. de Vector::Vector(int) konstruktorának paraméterként megadhatjuk a méretet. 14. Ezután egy makróval határozzuk meg úgy az intet. (*3) Definiáljuk a Mat4 típust. 20.Forrás: http://www. hogy INT-et jelentsen. +=. 15. 16. ->.mûveletek. *= és /= mûveleteket a vektorok és float-ok együttes használatára. /. Legyenek meg legalább a *. amely a Mat4-hez hasonlít. Definiáljuk a Complex osztályt ennek alapján.5) Definiáljuk az Index osztályt a mypow(double. csak ha egy hibás mutatót ténylegesen felhasználnak. amit operátor-túlterheléssel és a makrók használatával olvashatatlanná teszünk. -.14[13] feladatra adott megoldásunkat. de Matrix::Matrix(int. (*3) Definiáljunk egy Matrix típust. struct T { char* p. =. 17. 21. hogy 2**I meghívja mypow(2. char* q. +. Készítsünk függvényt. mint ha x és p C tagja lenne. 19. Operátorok túlterhelése 393 13.11 pont Ptr_to_T osztályát és ellenõrizzük. *. mit csinál. Egy ötlet: határozzuk meg + -t úgy az INT-ekre. (*2) Fejezzük be a §11.doksi.-t jelentsen és fordítva. mint ami Vec4-et ad vissza. Írjunk egy C osztályt. (*3) Cseréljük ki egy barátunkkal a §11. A gyakorlat végére meg fogjuk tanulni.Index) hatványozó függvény kitevõje számára. és próbáljuk meg futtatása nélkül kideríteni. Definiáljuk a [ ] mûveletet.I)-t. ++ és -. Találjunk módot arra. (*1. (*2) Definiáljuk az Imaginary osztályt képzetes számok ábrázolására. Adjuk meg a szokásos mátrix-mûveleteket. Készítsük el az alapvetõ aritmetikai mûveleteket. amely a Gauss-féle kiküszöbölés módszerét alkalmazza egy Mat4-re. ha a Mat4-re alkalmazzuk. . (*2) Definiáljuk a Vec4 típust. =. }. Adjuk meg a +. }. y.hu 11.int) konstruktorának paraméterként megadhatjuk a dimenziókat. hogy mit ne tegyünk többé. -=. (*1) Adott két struktúra: struct S { int x. (*2) Írjunk egy programot. Ne okozzunk futási idejû hibát. amely a Vec4-hez hasonlít. Definiáljuk a [ ] mûveletet.

Bevezetés A C++ a Simula nyelvtõl kölcsönözte az osztály.1. valamint a rendszertervezés azon elvét. Occam) Fogalmak és osztályok • Származtatott osztályok • Tagfüggvények • Létrehozás és megsemmisítés • Osztályhierarchiák • Típusmezõk • Virtuális függvények • Absztrakt osztályok • Hagyományos osztályhierarchiák • Absztrakt osztályok mint felületek • Az objektumok létrehozásának adott helyre korlátozása • Absztrakt osztályok és osztályhierarchiák • Tanácsok • Gyakorlatok 12. mint felhasználói típus. A C++ nyújtotta nyelvi szerkezetek közvetlenül támogatják ezeket a tervezési elveket.Forrás: http://www. ha nem szükséges. hogy a programban használt fogalmak modellezésére osztályokat használjon. Aki a nyelvi elemeket csak a hagyományosabb programozás jelölésbeli alátámasztására használja. illetve az osztályhierarchia fogalmát. . ha a nyelvi lehetõségeket a tervezési elvek támogatására használjuk. Megfordítva is igaz: akkor használjuk a C++ nyelvet hatékonyan.hu 12 Származtatott osztályok „Ne szaporítsuk az objektumokat.” (W. a C++ valódi erõsségének használatáról mond le.doksi.

Minthogy a fogalmakat osztályokként ábrázoljuk. mentõk. mi következik ebbõl az egyszerû elvbõl – ami valójában az általában objektumorientáltnak nevezett programozási elv alapja. . Az objektumorientált programozást támogató további nyelvi eszközöket. gyorshajtás. hogy aztán okos és kiegyensúlyozott módon tudjon majd választani közülük. utak. például a többszörös öröklõdést vagy a futási idejû típusazonosítást a 15. hanem más fogalmakkal együtt és erejének egy részét is a rokon fogalmakkal való kapcsolatból meríti. amikor igazi feladatokat kell megoldania.Forrás: http://www. Ha egy programban úgy szerepeltetünk köröket és háromszögeket. mindig adott problémák kapcsán teszem. felmerül a kérdés: hogyan ábrázoljuk a fogalmak közötti kapcsolatokat? Persze tetszõleges kapcsolatot egy programozási nyelvben nem tudunk közvetlenül kifejezni. rossz módszerektõl” az „egyedüli igaz út” felé. hiszen osztályainkat a mindennapi életben használt fogalmaknál szûkebben és precízebben akarjuk meghatározni. fejezet tárgyalja. hogy mindkettõ síkgörbe-alakzat. bírság. mi a közös az osztályokban. így a Circle (Kör) és Triangle (Háromszög) osztályokat úgy írhatjuk le. De ha tudnánk. A kör és a háromszög fogalmában például közös.hu 396 Absztrakciós módszerek Egy fogalom sohasem önmagában létezik. majd egy hosszabb példa kapcsán azt tárgyalom. A cél az. Próbáljuk csak megmagyarázni. De ez nem csupán egyszerû utazás a „régi. Használható programokat az itt tárgyalt módszerek mindegyikének felhasználásával írtak már. akkor valami lényegeset mulasztunk el. teherautó. elvontabb felé halad. hogy a programozót az új felé tereljem. azaz hogy kifejezzék.doksi. hogy az olvasó megismerje az összes eljárást. hogy a korábbi módszer alkalmazása a jobb választás. Hamarosan bevezetjük a következõ fogalmakat: kerék. motel stb. A származtatott osztály fogalma és a vele kapcsolatos nyelvi eljárások célja a viszonyok kifejezése. olaj. vezetõ. motor. hogy nem vonjuk be a síkidom fogalmát. hogy autó. A fejezetben elõször az objektumorientált programozást támogató alapvetõ nyelvi eszközöket mutatom be. hogyan lehet ezek alkalmazásával jól szerkesztett programot írni. hogy pontosan meghatározott (explicit) módon megmondjuk. akkor sem akarnánk. Sokak számára ez a megszokottól a kevésbé ismert felé való haladást is fogja jelenti. Ez a fejezet azt feszegeti. Amikor rámutatok egy megközelítés korlátaira. mi az. hogy a Shape (Alakzat) osztály a közös bennük. más problémák kapcsán vagy más összefüggésben lehetséges. kifinomultabb. A nyelvi lehetõségek és módszerek bemutatása az egyszerûtõl és konkréttól a bonyolultabb. gyalogos.

hu 12.doksi. Egy efféle programban lehet egy ilyen adatszerkezet: struct Employee { // alkalmazott string first_name. hogy kifejezetten megmondjuk...2. amelyek az Employee-nek is (first_name. short department. // . level stb. char middle_initial. set<Employee*> group. de a fordítóprogram és az egyéb eszközök sehonnan nem fogják tudni. A helyes megközelítés az.). A Manager az Employee-ból származik. short level. a Manager-ek egyben Employee-k is. az Employee a Manager bázisosztálya. // . Egyik megoldás sem elegáns és zavaró is lehet.. Ez nyilvánvaló lehet a programozó (különösen egy figyelmes programozó) számára. department stb. family_name. csak további adatokat is tartalmaznak: struct Manager : public Employee { set<Employee*> group. Származtatott osztályok 397 12. A Manager osztálynak megvannak azok a tagjai. ezért az Employee (Alkalmazott) adatokat a Manager (Fõnök. vagy az emp tag címét kellene az alkalmazottak listájára tennünk. Vagy típuskényszerítést kellene alkalmaznunk a Manager*-ra. Ezután „meghatározhatunk” egy fõnököt is: struct Manager { Employee emp. amely egy cég dolgozóit kezeli. }. Date hiring_date.. }. short level. Származtatott osztályok Vegyünk egy programot. hogy a Manager egyben Employee is. . és fordítva. }. vezetõ) objektum emp tagjában tároljuk. // . Egy Manager* nem Employee* is egyben.) és ezekhez jönnek hozzá a saját tagok (group. így a kettõ nem cserélhetõ fel..Forrás: http://www.. // fõnök // a fõnök mint alkalmazott // beosztottak A fõnök egyben alkalmazott is. Egy Employee-ket tartalmazó listára nem vehetünk fel egy Manager-t a megfelelõ kód megírása nélkül.

mivel annál több adatot tárol és több függvényt biztosít.hu 398 Absztrakciós módszerek A származtatást gyakran úgy ábrázolják grafikusan. hiszen a származtatott osztály bizonyos értelemben „szélesebb” a bázisosztálynál. hogy a származtatott osztályból egy nyilat rajzolnak a bázisosztály felé. Ennek alapján ezt a kapcsolatot öröklõdésnek (öröklés. A származtatás népszerû és hatékony megvalósítása a származtatott osztályú objektumot a bázisosztály olyan objektumaként ábrázolja. inheritance) is hívják. illetve derived class (subclass)... amely kiegészül a származtatott osztályra egyedileg jellemzõ adatokkal is: Emplyee: first_name family_name . . subclass – alosztály) azonban zavaró lehet. group level . Az angol kifejezések a bázisposztályra és a származtatott osztályra: base class (vagy superclass). jelezve.doksi... Az utóbbi szóhasználat (superclass – fõosztály. a származtatott osztály tulajdonságokat örököl a bázisosztálytól. hogy a származtatott osztály a bázisosztályra hivatkozik (és nem fordítva): Employee Manager Általában úgy mondják.Forrás: http://www. Manager: first_name family_name ...

így Employee*-ot nem használhatunk Manager*-ként. A másik irányban (Base*-ról Derived*-ra) explicit konverzió szükséges: void g(Manager mm. A static_cast és a dynamic_cast használatát a §15. egy Manager is megfelel. akkor egy Base* változó típuskényszerítés nélkül kaphat Derived* típusú értéket.hu 12. Employee ee) { Employee* pe = &mm.Forrás: http://www.4. ha egy Derived (származtatott) osztálynak egy Base (bázis) osztály nyilvános bázisosztálya (§15. Általában.. elist. ha definiáltuk (§5. // "nyers erõvel": mûködik..doksi. Employee e1) { list<Employee*> elist.3).7): .push_front(&e1). Manager* pm = &ee. // . elist. pm->level = 2. ahol Employee objektum használható. Származtatott osztályok 399 A Manager osztálynak az Employee-ból ilyen módon való származtatása a Manager típust az Employee altípusává teszi.2 pont írja le. így tehát mindenhol. A másik irányban ez nem áll.push_front(&m1). mert pe // a Manager típusú mm-re mutat pm->level = 2. // amelynek van 'level' tagja Vagyis mutatók és referenciák használatakor a származtatott osztály objektumait úgy kezelhetjük. // rendben: minden Manager egyben Employee is // hiba: nem minden Employee fõnök // katasztrófa: ee nem rendelkezik 'level' taggal pm = static_cast<Manager*>(pe). } Ne feledjük. Következésképpen egy osztályt csak akkor használhatunk bázisosztályként. } // ez is jó: pm a Manager típusú mm-re mutat. Az Employee viszont nem feltétlenül Manager. mint a bázisosztály objektumait. akiknek egy része vezetõ beosztású (Manager): void f(Manager m1. Egy osztály bázisosztályként való használata egyenértékû az osztály egy (névtelen) objektumának deklarálásával. egy Manager egyben Employee is. így egy Manager*-ot használhatunk Employee*-ként is. Most már készíthetünk egy listát az alkalmazottakról (Employee).

3) – tagjait. mintha maguk vezették volna be azokat: void Manager::print() const { cout << "A keresett név " << full_name() << '\n'. family_name. // ..1. } // . }. A származtatott osztályok tagfüggvényei ugyanúgy elérhetik a bázisosztály nyilvános (public) – és védett (protected. string full_name() const { return first_name + ' ' + middle_initial + ' ' + family_name.. class Manager : public Employee { // . Az információt megfelelõ típusként kell megadnunk.doksi.. melyhez az elvégezhetõ mûveletek is hozzátartoznak.. // . class Manager : public Employee { // .2. // .. // ...Forrás: http://www. } A származtatott osztály azonban nem éri el a bázisosztály privát (private) tagjait: void Manager::print() const { cout << "A keresett név " << family_name << '\n'.. public: void print() const. hogy közben nem kötõdünk az adott ábrázoláshoz: class Employee { string first_name. } // hiba! . char middle_initial. nem definíció // hiba: Employee nem definiált 12. }.. §15... }. public: void print() const...hu 400 Absztrakciós módszerek class Employee.. Tagfüggvények Az egyszerû adatszerkezetek – mint a Manager és az Employee – nem túl érdekesek és sokszor nem is különösebben hasznosak. // csak deklaráció. és ezt úgy kell megtennünk.

A privát tag fogalma értelmetlenné válna azáltal. A származtatott osztálynak nincs különleges engedélye a bázisosztály privát tagjainak elérésére. mi lenne fordított esetben: ha a származtatott osztály tagfüggvénye elérhetné a bázisosztály privát tagjait. majd a származtatott osztályokból származtatott további osztályokat és azok függvényeit és így tovább. a többiek számára azonban privátnak minõsül (§15. A legtisztább megoldás általában az. így a Manager::print() számára a family_name nem érhetõ el.3). mert a print() függvényt a Manager osztály újradefiniálja.. . Egy védett tag a származtatott osztályok számára olyan.doksi. inkább védett (protected) és ne privát (private) tagokat használjunk.hu 12. ha a származtatott osztály a bázisosztálynak csak a nyilvános tagjait használja: void Manager::print() const { Employee::print(). de gondoljuk el. Az egész program minden forrásállományát át kéne nézni: a származtatott osztályokat és azok függvényeit keresni. Továbbá nem lehetne többé egy privát tag használatát a tagés barát (friend) függvények átnézésével megkeresni. mint egy nyilvános (public). // a fõnökökre vonatkozó adatok kiírása } Vegyük észre. // alkalmazottak adatainak kiírása cout << level.. hogy a programozó hozzáférhetne az osztály privát részéhez egy osztályt származtatva belõle. // . Származtatott osztályok 401 A Manager::print() második változatát a fordító nem fogja lefordítani.Forrás: http://www. // hoppá! } // a fõnökökre vonatkozó adatok kiírása a program váratlan módon újra és újra meg fogja hívni önmagát. A függvénynevek ilyen módon való újrafelhasználása igen általános. hogy a :: hatókör operátort kellett használni. Némelyek meglepõdnek ezen. ahol ez elfogadható. Ez legjobb esetben is fárasztó és sokszor kivitelezhetetlen is. Ha óvatlanul ilyet írunk: void Manager::print() const { print(). Ott.

.4. family_name.Forrás: http://www. int d) : family_name(n). int lvl). }...d).. // . A bázisosztály konstruktora a származtatott osztály konstruktorának definiciójában kap paramétereket.. public: Employee(const string& n.. Ebbõl a szempontból a bázisosztály konstruktora úgy viselkedik. // . mintha a származtatott osztály tagja lenne (§10. department(d) { // . } . // beosztottak short level.. akkor az egyiket meg is kell hívni. short department. }.2. Az alapértelmezett konstruktorokat automatikusan is meghívhatjuk.2... class Manager : public Employee { set<Employee*> group. int d. Ha a bázisosztálynak vannak ilyen függvényei...hu 402 Absztrakciós módszerek 12.6): Employee::Employee(const string& n. int lvl) : Employee(n. public: Manager(const string& n. int d. } // tagok kezdeti értékadása Manager::Manager(const string& n.. int d). // a bázisosztály kezdeti értékadása level(lvl) // tagok kezdeti értékadása { // . // .doksi. de ha a bázisosztály minden konstruktora paramétert igényel. Vegyük a következõ példát: class Employee { string first_name. Konstruktorok és destruktorok Egyes származtatott osztályoknak konstruktorokra van szükségük. akkor a megfelelõ konstruktort csak explicit módon lehet meghívni. // .

2.2. a bázisosztály tagjainak nem: Manager::Manager(const string& n. int lvl) : family_name(n).1). // hiba: family_name nem deklarált a Manager osztályban department(d). Az objektumnak ez a „felszeletelõdése” (slicing).. aztán a tagok. A felszeletelõdés megakadályozása az egyik oka annak. }. 12. azaz a tény. . Az osztályba tartozó objektumok „alulról felfelé” épülnek fel.1): class Employee { // .6). } A fenti definíció három hibát tartalmaz: nem hívja meg az Employee konstruktorát.4 és §12. A megsemmisítés fordított sorrendben történik: elõször a származtatott osztály.hu 12.2.4. hogy megõrizzük a többalakú (polimorfikus) viselkedést (§2.4. Másolás Egy osztályba tartozó objektum másolását a másoló konstruktor és az értékadások határozzák meg (§10. hogy osztályhierarchiába tartozó objektumok esetében célszerûbb mutatókat és referenciákat használnunk. // e létrehozása m Employee részébõl e = m. // m Employee részének másolása e-be } Minthogy az Employee osztály másoló függvényei nem tudnak a Manager osztályról. végül maga a származtatott osztály..5. Employee(const Employee&). meglepõ lehet és hibákhoz vezethet. Származtatott osztályok 403 Egy származtatott osztály konstruktora csak a saját tagjai és a bázisosztály konstruktora számára adhat meg kezdõértéket.4.6 és §15.3.Forrás: http://www. // hiba: department nem deklarált a Manager osztályban level(lvl) { // . A tagok a deklaráció sorrendjében jönnek létre és fordított sorrendben semmisülnek meg (§10.doksi. hogy ekkor az objektumnak csak egy szelete másolódik le. végül a bázisosztály. aztán a tagok. A hatékonysági megfontolások mellett további ok.. elõször a bázisosztály.. és két ízben is megpróbál közvetlenül kezdõértéket adni az Employee tagjainak. a Manager objektumnak csak az Employee része fog lemásolódni. void f(const Manager& m) { Employee e = m. Employee& operator=(const Employee&). int d.4.

*/ }. ..4..doksi...Forrás: http://www.. Ebbõl következik.. de lehet ennél általánosabb gráf is. */ }.. class Director : public Manager { /* ..: class Temporary { /* .. public Manager { /* . */ }. hogy az értékadó operátorok nem öröklõdnek. akkor a fordítóprogram fog létrehozni egyet. */ }. */ }. */ }.. */ }. Az egymással kapcsolatban álló osztályok ilyen halmazát hagyományosan osztályhierarchiának hívjuk. hogy ha nem határozunk meg másoló értékadó operátort. a C++ nyelv képes az osztályoknak egy irányított körmentes gráfját kifejezni.hu 404 Absztrakciós módszerek Jegyezzük meg. Osztályhierarchiák Egy származtatott osztály lehet maga is bázisosztály: class Employee { /* . class Secretary : public Employee { /* . (A konstruktorok soha. public Secretary { /* . class Consultant : public Temporary. Ábrával: Employee Temporary Secretary Tsec Manager Consultant Director Vagyis ahogy a §15...) 12. A hierarchia leggyakrabban fa szokott lenni.2 pont részletesen elmagyarázza..2.. class Manager : public Employee { /* . class Tsec : public Temporary.

. char middle_initial. megoldás. // . } set<Employee*> group. Bázisosztályra hivatkozó mutatókat gyakran használunk olyan tároló osztályokban (container class). Employee() : type(E) { } string first_name. a 4.Forrás: http://www. Helyezzünk egy típusmezõt a bázisosztályba és a függvények ezt kérdezzék le. 13. Érjük el.2. Típusmezõk Ha a deklarációkban alkalmazott kényelmes rövidítésnél többre akarjuk használni az osztályok származtatását. // beosztottak }.6). fejezet).5.5. Használjuk a dynamic_cast-ot (dinamikus típuskonverzió. }. megoldás a 2. megoldás párosításai különösen érdekesek és erõsek.. .2. akkor milyen származtatott osztályba tartozik az objektum.7.4. E }. short level. a vektor (vector) és a lista (list). §15.hu 12. Ekkor az elsõ megoldás homogén listákat. Nézzünk meg elõször egy típusmezõs megoldást. family_name. hogy csak egyféle objektum jöhessen szóba (§2. és a 4. pedig a 2. short department. azaz olyanokat. meg kell válaszolnunk a következõ kérdést: ha adott egy Base* mutató. megoldásnak a nyelv által támogatott változata. hogy lássuk.doksi. Az 1.. amelyre mutat? A problémának négy alapvetõ megoldása van: 1. Date hiring_date. A többi megoldás lehetõvé tesz heterogén listákat is.. legtöbbször miért kerülendõ. Származtatott osztályok 405 12. §15. struct Manager : public Employee { Manager() { type = M. típusbiztos átalakítása. §12.-nak egyedi. majdnem mindig világosabb kódot eredményeznek. mint a 2. azaz azonos típusú objektumokat eredményez. vagy a 3. ahol különbözõ típusú objektumok (vagy ilyenekre hivatkozó mutatók) lehetnek. A „fõnök – alkalmazott” példát így írhatnánk át: struct Employee { enum Empl_type { M. Használjunk virtuális függvényeket (§2. // . 3.4.2. Empl_type type.5. 4. mint a halmaz (set). A 3.5). 2.

doksi.. mint a print_employee. hogy az olyan függvények. break. ++p) print_employee(*p)... hogy a programozó a megfelelõ (és a fordítóprogram által nem ellenõrizhetõ módon) kell. Alapvetõ gyengéje. case Employee::M: { cout << e->family_name << '\t' << e->department << '\n'.. amely minden Employee-rõl ki tudja írni az információt: void print_employee(const Employee* e) { switch (e->type) { case Employee::E: cout << e->family_name << '\t' << e->department << '\n'.. // . } } . hogy az a feltétele.. cout << " szint " << p->level << '\n'. } } } Az alkalmazottak listája így írható ki: void print_list(const list<Employee*>& elist) { for (list<Employee*>::const_iterator p = elist.. } Ez jól mûködik.hu 406 Absztrakciós módszerek Most már írhatunk egy függvényt. // .Forrás: http://www. hogy kezelje a típusokat.. break. // .begin(). a szóba jöhetõ osztályok közös vonásait használják ki: void print_employee(const Employee* e) { cout << e->family_name << '\t' << e->department << '\n'.. // . különösen az egyetlen programozó által fenntartott kis programokban. cout << " szint " << p->level << '\n'. A problémát általában súlyosbítja. // . const Manager* p = static_cast<const Manager*>(e). p!=elist. if (e->type == Employee::M) { const Manager* p = static_cast<const Manager*>(e)..end().

De ha megtaláltuk is.és betöltõprogram gondoskodik az objektumtípusok és az alkalmazott függvények összhangjáról: class Employee { string first_name. Ahogy a rendszer mérete nõ. // .. hogy mi is történik. nehéz lehet az összes ilyen típusmezõ-ellenõrzést megtalálni. a probléma súlyosbodik. virtual void print() const. Ez aztán a bázisosztály és a származtatott osztályok megvalósításának legkevésbé kívánatos összefonódásához vezet. A típuskényszerítés árulkodó jele annak. family_name.. // . A fordító. a kölcsönös függéseket pedig kerüljük el. int dept). public: Employee(const string& name. ha a rendszer új Employee-vel bõvül. nehéz lehet megérteni. Továbbá. A tisztább felépítés és könnyebb módosíthatóság kedvéért a különálló dolgokat kezeljük külön. A közös bázisosztály így mindenféle „hasznos információk” gyûjteményévé válik.doksi.6. Minden olyan függvényt meg kell vizsgálni. az összes fontos függvényt módosítani kell – vagyis az összes olyat. amely minden származtatott osztályból elérhetõ. mert a típusmezõ alkalmazása ellentmond a modularitás és az adatrejtés elvének. Származtatott osztályok 407 Egy nagy függvényben.2. hogy javítani lehetne a kódon. akkor a programozó hajlamos arra... Minden típusmezõt használó függvénynek ismernie kell az összes olyan osztály ábrázolását (és megvalósításuk egyéb részleteit).Forrás: http://www. Vagyis a típusmezõs megoldás hibákra ad lehetõséget és nehezen módosítható kódot eredményez. hogy további ilyen mezõket hozzon létre. ahol sok származtatott típust kell kezelni. amelyiknek egy változtatás után szüksége lehet a típusmezõ ellenõrzésére. amelyeket a származtatott osztályok felülbírálhatnak. amelyik ellenõrzi a típusmezõt. Virtuális függvények A virtuális függvények azáltal kerülik el a típusmezõs megoldás problémáit. }. 12. Ez azt jelenti. Ezenkívül ha van egy olyan adat (például egy típusmezõ). hogy segítségükkel a programozó olyan függvényeket deklarálhat a bázisosztályban. hogy hozzá kell férni a kritikus forráskódhoz és külön munkát jelent a változtatás utáni teszt is. amely a típusmezõs osztály leszármazottja.hu 12. . short department.

// . int lvl). void Manager::print() const { Employee::print().Forrás: http://www. illetve a származtatott osztályokban definiált print() függvényekhez. void print() const. }. cout << "\tszint " << level << '\n'. ha azoknak ugyanolyan típusú paramétereik vannak... ha osztályából nem is származtatunk további osztályt. hacsak nem tisztán virtuális (pure virtual) függvényként adtuk meg (§12. Ha a származtatott osztályokban szerepelnek ilyenek. short level.. amelyben elõször deklaráltuk. public: Manager(const string& name...3): void Employee::print() const { cout << family_name << '\t' << department << '\n'. annak a függvénybõl nem kell feltétlenül saját változat. Egy virtuális függvény akkor szolgálhat felületként a származtatott osztályokban definiált függvényekhez. a fordítóprogram mindig az adott Employee objektumnak megfelelõ függvényt fogja meghívni. A virtuális tagfüggvényeket néha metódusoknak (method) is hívják. // .. ha pedig származtatunk. } Virtuális függvényt akkor is használhatunk. és a visszatérési érték is csak nagyon csekély mértékben különbözik (§15.doksi. mint a bázisosztálybelinek. // .2).hu 408 Absztrakciós módszerek A virtual kulcsszó azt jelenti. int dept. Osztály származtatásakor csak akkor írjunk egy megfelelõ változatot a függvénybõl. A virtuális függvényt mindig definiálnunk kell abban az osztályban. hogy a print() függvény felületként szolgál az ebben az osztályban definiált print() függvényhez.. } .. // .6. ha valóban szükséges: class Manager : public Employee { set<Employee*> group.

++p) (*p)->print().2 De akár így is: void print_list(const list<Employee*>& s) { for_each(s. list<Employee*> empl. empl. Az alkalmazottak (Employee) listáját így írathatjuk ki: void print_list(const list<Employee*>& s) { for (list<Employee*>::const_iterator p = s.1234).mem_fun(&Employee::print)).push_front(&e). Hacsak közvetlen módon meg nem mondjuk.2. A globális print_employee() függvény (§12. mert a helyébe a print() tagfüggvények léptek. } // lásd §2. hogy a virtuális függvény melyik változatát akarjuk használni – mint az Employee::print() hívásnál –. mielõtt a Manager osztályt egyáltalán kitaláltuk volna! Ez az osztályoknak egy kulcsfontosságú tulajdonsága. p!=s.doksi.8. Manager m("Smith".end().1234. A int main() { Employee e("Brown".2). . Ha helyesen alkalmazzuk.begin().4 például az alábbi kimenetet eredményezi: Smith 1234 szint 2 Brown 1234 Ez akkor is mûködik. az objektumorientált tervezés sarokköve lesz és a programok fejlesztésénél bizonyos fokú stabilitást ad. } // lásd §3. ha a print_list() függvényt azelõtt írtuk meg és fordítottuk le.5.7. az objektumhoz leginkább illõ felülíró függvény lesz meghívva. Származtatott osztályok 409 A származtatott osztály azonos nevû és azonos típusú paraméterekkel bíró függvénye felülírja vagy felülbírálja (override) a virtuális függvény bázisosztálybeli változatát.5) szükségtelen.end().Forrás: http://www. print_list(empl).5 Minden Employee a típusának megfelelõen íródik ki. } // §2. empl.s.hu 12.push_front(&m).begin().

polymorphism) nevezzük. A virtuális függvényekkel bíró típus neve többalakú típus (polimorfikus típus). Minthogy a Manager::print() meghívásakor meghatározzuk az objektum típusát. A minõsített név használatának van még egy elõnye: ha egy virtuális függvény inline (ami elõ szokott fordulni). hogy a virtuális függvényhívás hagyományos és nyilvánvaló megvalósítása az egyszerû közvetett függvényhívás (indirekció) (§2. Ehhez rendszerint egyetlen mutatónyi hely is elég.doksi. hogy pontosan milyen fajta Employee-re hívtuk meg azokat. és erre is csak azon osztályokban van szükség. a fordítóprogram felismeri annak pontos típusát. hogy a többalakúság támogatása érdekében a fordítóprogramnak minden Employee típusú objektumban valamilyen. A típusmezõs megoldás választása esetén ehhez képest jelentõs mennyiségû tárterületet kellett volna a típusmezõ számára biztosítanunk. melynek segítségével képes a megfelelõ print() függvényt meghívni. többalakúságnak (polimorfizmus. . amikor mondjuk egy virtuális függvény ugyanarra az objektumra egy másik függvényt is meghív. Ha egy függvényt (miként a Manager::print()-et is) a :: hatókör-feloldó operátor segítségével hívunk meg. Ennek segítségével a programozó hatékonyan képes azokat az egyedi eseteket kezelni. Ha közvetlenül kezelünk egy objektumot és nem mutató vagy referencia segítségével.hu 410 Absztrakciós módszerek Azt.5. így a futási idejû többalakúságra nincs szükség. így a hatékonyság elvesztésétõl való félelem ne riasszon vissza senkit a virtuális függvények használatától ott. akkor a fordítóprogram a :: minõsítõvel jelzett hívásokat képes helyben kifejteni. Érdemes megemlíteni. az Employee::print() ezt követõ meghívásakor a típusról már nem kell újra dönteni. akkor ezáltal kikapcsoljuk a virtualitást. amelyeknek van virtuális függvényük. tehát nem minden osztályban és még csak nem is minden származtatott osztályban. A Manager::print() függvény ennek példája. A C++ nyelvben a többalakú viselkedést virtuális függvények használatával vagy az objektumoknak mutatókon vagy referenciákon át való kezelésével érhetjük el. Világos.Forrás: http://www. hogy az Employee függvényei attól függetlenül „helyesen” viselkednek.5). a típusra vonatkozó információt (típusinformációt) kell nyilvántartania. ahol egy közönséges függvényhívás elfogadhatóan hatékony. Máskülönben a Manager::print() végtelen rekurziót idézne elõ.

mert az s minden mûvelete hibát fog eredményezni. Ez abból is látható.. hogy önmagában és származtatott osztályok bázisosztályaként is hasznos. De nem minden osztály ilyen. ha a Shape osztály virtuális függvényeit tisztán virtuális (pure virtual) függvényként deklaráljuk. // .doksi.Forrás: http://www. } // . // hiba: s az absztrakt Shape osztály változója lenne .3. amelyekhez nem létezhetnek objektumok. de nem sok értelme van: Shape s. Absztrakt osztályok Sok osztály hasonlít az Employee osztályra annyiban. A Shape-nek csak mint bázisosztálynak van értelme. ilyen osztályba tartozó objektumot pedig nem hozhatunk létre: Shape s. }. abstract class) hívjuk.. Származtatott osztályok 411 12. például a Shape (Alakzat). } virtual void draw() { error("Shape::draw").. akkor absztrakt osztálynak (elvont osztály.hu 12. olyan elvont fogalmakat jelenítenek meg. // absztrakt osztály // tisztán virtuális függvény // tisztán virtuális függvény // tisztán virtuális függvény Ha egy osztály legalább egy tisztán virtuális függvénnyel rendelkezik. virtual void draw() = 0.. Bizonyos osztályok. A virtuális függvények az =0 „kezdeti értékadástól” lesznek tisztán virtuálisak: class Shape { public: virtual void rotate(int) = 0. Jobb megoldás. // nem "elegáns" Egy ilyen meghatározatlan alakzatot meg tudunk ugyan adni (a nyelv megengedi). }. // butaság: "alak nélküli alakzat" A dolog azért értelmetlen. Az ilyen osztályok számára elegendõek az elõzõ pontban bemutatott módszerek. hogy nem tudunk hozzá virtuális függvényeket értelmesen definiálni: class Shape { public: virtual void rotate(int) { error("Shape::rotate"). virtual bool is_closed() = 0.

*/ }. // .. // a Shape::rotate felülírása // a Shape::draw felülírása // a Shape::is_closed felülírása Ha egy tisztán virtuális függvényt a származtatott osztályban nem definiálunk..hu 412 Absztrakciós módszerek Az absztrakt osztályokat csak felületként (interface). Egy operációs rendszer például egy absztrakt osztály mögé rejtheti eszközmeghajtóinak tulajdonságait: class Character_device { public: virtual int open(int opt) = 0. . private: Point center... // absztrakt osztály // a Shape::is_closed felülírása Polygon b. } // . Irregular_polygon poly(some_points). virtual int close(int opt) = 0. akkor az tisztán virtuális függvény marad. }. void rotate(int). int r). hogy segítségükkel a megvalósítás egyéb részeinek elérhetõvé tétele nélkül biztosíthatunk felületet. bool is_closed() { return true. illetve más osztályok bázisosztályaként használhatjuk: class Point { /* .doksi. sõt. Ez a megvalósítás lépcsõzetes felépítését teszi lehetõvé: class Polygon : public Shape { public: bool is_closed() { return true.. }. a származtatott osztály is absztrakt osztály lesz. public: void draw(). a draw és a rotate nincs felülírva .. }. // hiba: a Polygon osztálynak nem lehet objektuma class Irregular_polygon : public Polygon { list<Point> lp. // a Shape::draw felülírása // a Shape::rotate felülírása // jó (megfelelõ konstrukort feltételezve) Az absztrakt osztályok fontos képessége. class Circle : public Shape { public: void rotate(int) { } void draw().Forrás: http://www.. int radius. } Circle(Point p..

Azt az általános megközelítést alkalmazzuk. hogy egy virtuális felhasználói felületet” bocsátunk az alkalmazás rendelkezésére. int n) = 0. hogy milyen értékeket fogadhat el. E felület számos rendszeren elkészíthetõ.4. virtual ~Character_device() { } // virtuális destruktor Az egyes eszközmeghajtókat a Character_device-ból származtatott osztályként definiálhatjuk. virtual int write(const char* p. A tényleges felhasználói felület elkészítésének részleteit késõbbre halasztjuk. hogy lesz egy Ival_box (értékmezõ) osztályunk. hogy moduláris módon.hu 12. kezdjük a munkát ezen egyszerû adatbeviteli mûvelet modelljének felállításával. amely a létezõ felhasználói felületek szolgáltatásainak egy részét biztosítja. abból kell kiindulnunk. virtual int read(char* p. Minthogy ez az alapötlet sokféleképpen megvalósítható. így kódja „hordozható” lesz. amely tudja.doksi. építõkövekként osztályokat használva egy teljes programot írjunk. virtual int ioctl(int . Természetesen vannak más módok is arra. Az alapötlet az. számtárcsák. A virtuális destruktorok fontosságát a §12.Forrás: http://www. és sokféle eszközmeghajtót kezelhetünk ezen felületen keresztül.. mert a kapcsán egy sor eljárást és tervezési szempontot lehet bemutatni. hogy sokféle különbözõ Ival_box lesz: csúszkák. Osztályhierarchiák tervezése Vegyük a következõ egyszerû tervezési problémát: hogyan lehet egy program számára lehetõvé tenni egy egész érték bekérését a felhasználói felületrõl? Zavarbaejtõen sokféle módon. szöveges adatbeviteli mezõk. Ahhoz. hogy az érték megváltozott-e a legutóbbi kérés óta. hogy egy alkalmazást elválasszunk a felhasználói felülettõl. Azt is megkérdezheti.4. ha még nem áll rendelkezésre. hogy elszigeteljük programunkat ettõl a sokféleségtõl és felderíthessük a különbözõ tervezési módokat. Származtatott osztályok 413 }. mert ezeket a módszereket alkalmazzák a valódi felhasz- . hogy kérje be ezt az értéket a felhasználótól.2 pont magyarázza el. Azért választottam ezt. 12.. ahová a felhasználó beírhatja az értéket. hanggal vezérelhetõ eszközök. A program elkérheti egy Ival_box objektum értékét és felszólíthatja arra is. Az absztrakt osztályok bevezetésével immár minden eszköz a kezünkben van arra.) = 0. int n) = 0. mert általános.

Az Ival_box osztály az összes Ival_box által használatos felületet írja le és egy olyan alapértelmezett megvalósítást ad. A függvények alapértelmezett változatai meglehetõsen vázlatosak.1. val = low = ll. if (pb->was_changed()) { // új érték. high = hh. } virtual int get_value() { changed = false. Smalltalk és régebbi C++-programokban találkozhatunk. int hh) { changed = false.Forrás: http://www. int low. hogy illusztrálják a megközelítést. int i = pb->get_value(). ilyennel a Simula. Hagyományos osztályhierarchia Elsõ megoldásunk egy hagyományos osztályhierarchia. (Egy „valódi” osztály például értékellenõrzést is végezne. melyet az egyes Ival_box-ok sajátjaikkal felülbírálhatnak.. public: Ival_box(int ll. val = i.doksi. 12.hu 414 Absztrakciós módszerek nálói felületeteket kezelõ rendszerekben. céljuk leginkább az. „pongyolák”. és végül – a leglényegesebb ok –. Ezenkívül megadjuk az alapmegoldáshoz szükséges adatokat is: class Ival_box { protected: int val. bool changed. } virtual void prompt() { } virtual bool was_changed() const { return changed.) Az „ival osztályokat” egy programozó így használhatná fel: void interact(Ival_box* pb) { pb->prompt(). } // felhasználók számára // alkalmazások számára }. high. } virtual void set_value(int i) { changed = true.4. mert ezek a módszerek a felhasználói felületetek szûk tartományánál jóval szélesebb körben is alkalmazhatók.. val = i. // jelzés a felhasználónak // . } virtual void reset_value(int i) { changed = false. return val. valamit csinálunk vele } .

. void prompt().hu 12. Az ilyen döntések alapvetõ fontosságúak a felhasználói felületet kezelõ rendszerek tervezésekor. eltekintek attól a kérdéstõl. Hogy egyszerûsítsem a tárgyalást. amelyek ilyen objektumokat létrehoznak.12). hogyan vár a program bemenetre. Ez a felhasználókat elszigeteli a származtatott osztályok esetleges módosításaitól. illetve azokra hivatkozó mutatókat használna. Származtatott osztályok 415 else { } // . // az Ival_slider az Ival_box osztályból származik } A programkód legnagyobb része az interact() függvény stílusában íródna. jóval szélesebb körben is alkalmazhatók.doksi. A kód legnagyobb részének még arról sem kell tudnia. interact(p2). int). }. hogy egyáltalán különbözõ Ival_box-ok léteznek.Forrás: http://www. és egyszerû Ival_box-okat. Az itt bemutatott tervezési módszerek és az azokat támogató nyelvi eszközök nem kötõdnek adott felhasználói felülethez. Ival_box* p2 = new Ival_dial(1. Lehetséges megoldás. megoldható úgy is. elvonnánk a figyelmet a programozási eljárások és nyelvi eszközök tárgyalásától. // a régi érték jó volt. esetleg a programmal külön végrehajtási szálat indíttatunk el az Ival_box számára.. ezt is felhasználjuk valahogy } void some_fct() { Ival_box* p1 = new Ival_slider(0. majd a szál állapotát kérdezzük le. Az Ival_box adattagjait védettként (protected) vezettük be. Így a programnak nem kellene tudnia az esetleg nagy számú különbözõ Ival_box-változatokról. int get_value(). hogy a származtatott osztályok- . viselkedését meghatározó grafikai elemek public: Ival_slider(int. hogy az Ival_box-ot egy eseményhez kapcsoljuk és egy visszahívás (callback) segítségével válaszolunk. de ha itt a valóságot akár csak megközelítõ részletességgel tárgyalnánk ezeket. csak annak a viszonylag kis számú függvénynek kellene ismernie azokat.5). A különbözõ Ival_box-okat az Ival_box-ból származtatott osztályokként határozhatjuk meg: class Ival_slider : public Ival_box { // a csúszka kinézetét. interact(p1). hogy a program a get_value() függvényben ténylegesen vár a felhasználói bemenetre.

Így aztán az összes osztályunk a BBwindow-ból származtatott lesz... amely leírja a képernyõn levõ objektumok alapvetõ tulajdonságait.hu 416 Absztrakciós módszerek ból elérhetõek legyenek. amelynél egy gomb forgatásával adhatunk meg egy értéket. Ábrával: BBwindow Ival_box Ival_slider Ival_dial Popup_ival_slider Flashing_ival_slider ..3). De vajon honnan vegyük a grafikus elemeket? A legtöbb felhasználói felületet kezelõ rendszer biztosít egy osztályt. class Flashing_ival_slider : public Ival_slider { /* .doksi. */ }. megjelenése igazodik majd a rendszer többi grafikus elemének megjelenéséhez. a felhasználótól szinte kikövetelve egy érték megadását.Forrás: http://www. Így aztán az Ival_slider::get_value() függvény elhelyezheti az értéket az Ival_box::val adattagban. */ }. amely felvillan.” („Sok Pénz Rt. Ezek között ott lehet az Ival_dial. akkor az Ival_slider. átméretezhetõ.. a Flashing_ival_slider. */ }. Az Ival_box-ból az Ival_slider mellett más változatokat is származtathatunk. class Popup_ival_slider : public Ival_slider { /* . osztályok mindegyike egy-egy fajta BBwindow (Big Bucks window) kell. */ }. ha a prompt() függvénnyel erre kérjük. Ezt a legegyszerûbben úgy érhetjük el.. // újraírva a BBwindow használatára class Ival_slider : public Ival_box { /* .. a BBwindow rendszer szabályainak megfelelõen. amely a prompt() hatására valamilyen feltûnõ helyen jelenik meg. áthelyezhetõ lesz stb.. tehát elhelyezhetõ lesz a képernyõn. ha Ival_box-unkat úgy írjuk át. A védett tagok elérhetõk az osztály és a származtatott osztályok függvényei számára is.. hogy legyen..”) rendszerét használjuk. Ha például a „Big Bucks Inc. hogy a BBwindow-ból származtatott osztály legyen. de az általános felhasználó számára nem (§15. és a Popup_ival_slider. class Ival_dial : public Ival_box { /* . az Ival_dial stb. Osztályhierarchiánk tehát így fog kinézni: class Ival_box : public BBwindow { /* . */ }...

” által biztosított környezetet használjuk. hogy a BBwindow osztály változásakor az Ival_box felhasználóinak újra kell fordítaniuk.. class Ival_box : public LSwindow { /* . Származtatott osztályok 417 12. Bírálat Ez így sok tekintetben jól fog mûködni és az ilyesfajta osztályfelépítés számos problémára jó megoldás. változatkövetésük rémálommá válhat. ha Ival_box-unkat olyan rendszerekre is át szeretnénk ültetni. ez azzal az elõnnyel jár. mert így a származtatott osztályok írói nem zavarhatják össze azokat. */ }. class Ival_box : public IBwindow { /* . Az Ival_slider esetében például nem szükséges az adat külön tárolása. Ám van néhány hátulütõje.doksi. mert akkor pontosan meg tudnak felelni a követelményeknek és nem keseríthetik meg az egymással nem rokon származtatott osztályok életét. esetleg újra kell írniuk a kódjukat. típusokat és konstansokat tartalmazzon. Elõbb-utóbb valaki eléri. Egy másik probléma.. De mi történik. hogy az Ival_box a BBwindow osztály leszármazottja. . hogy mindig a megfelelõ adatot kapjuk. minthogy ez a csúszka állásából meghatározható. ezt a részletkérdést elsõrendû tervezési döntéssé emeli. Ez abban az esetben helyes. melyek miatt más tervezési lehetõségek után fogunk nézni.. ha az adatok a származtatott osztályokban vannak. Általában is problematikus két rokon. // BB változat // CW változat // IB változat // LS változat Ha ennyi változatunk van.4.1. de eltérõ adathalmaz tárolása. hogy ne legyenek többé összhangban. melyek az „Imperial Bananas”. hogy nem biztosított. A védett felület szinte mindig csak függvényeket. Ezek az adatok megint csak egy „apró” részletet jelentenek. A BBwindow osztály nem alapvetõ része az Ival_box-ra épített rendszernek. hogy az Ival_box felhasználói a BBwindow minden szolgáltatását igénybe vehetik. hogy ha egy bázisosztály mérete megváltozik... */ }. Ha az Ival_box a BBwindow-ból származik.. */ }. módosításuk. A tapasztalat is azt mutatja. Jobb. hogy az Ival_box-ban deklarált adatok minden származtatott osztály rendelkezésére állnak. ha cégünk kulcsfontosságú üzleti döntése. A BBwindow osztályt utólag tettük az Ival_box bázisosztályává. a „Liberated Software” vagy a „Compiler Whizzles”-tõl származnak? Ekkor programunkból négy változatot kellene készítenünk: class Ival_box : public BBwindow { /* . Még jobb. class Ival_box : public CWwindow { /* . valahányszor végrehajtják a get_value()-t. Az. mégis bekerültek az Ival_box felületbe..Forrás: http://www. megléte csupán részletkérdés. hogy a „Big Bucks Inc. Ez gyakorlati szempontból azt is jelenti. ami nem egészen helyes.hu 12. A legtöbb C++-változat úgy mûködik. hogy kezdõ programozók szükségtelen és nehezebb módosíthatóságot eredményezõ módon szeretnek a védett (protected) adattagokkal ügyeskedni.1. ami sajnos azt is jelenti.. ha az adattagok privátok. */ }. akkor az összes származtatott osztályt újra kell fordítani.

2. ha a felhasználói felületet az egyetlen Ival_box felületünk bázisosztályaként „bedrótozzuk”. amelyrõl nem kell tudomást venniük azon felhasználóknak. mint az Ival_box osztály eredeti deklarációja volt. Ha megváltozik a felhasználói felületet kezelõ rendszer. Elmaradt a konstruktor is. Különbözõ felhasználói felületekhez tartozó Ival_box-ok tudjanak egyszerre létezni a programban. A felhasználói felület valóban olyan részletkérdés legyen.hu 418 Absztrakciós módszerek Végül lehetséges. mert valahogy ezek egy képernyõn tudnak osztozni. 2. ne legyen szükséges az Ival_box családot felhasználó kód újrafordítása. amely biztosítja az öröklõ osztályok adatainak helyes „eltakarítását”. ahol különbözõ felhasználói felületek ablakai léteznek egyidejûleg. hogy megoldjuk a hagyományos felépítés bírálatában felvetett problémákat: 1. Absztrakt osztályok Nos. 3. akiket nem érdekel. az Ival_box osztályt puszta felületként (pure interface) határozzuk meg: class Ival_box { public: virtual int get_value() = 0. virtual bool was_changed() const = 0. mivel nincs kezdõértékre váró adat. Ehhez pedig nem elég rugalmas megoldás. Az Ival_box osztály ne tartalmazzon adatokat. Vagy azért. kezdjük újra a tervezést. Többféle megoldás kínálkozik erre. Elhagytuk az adattagokat és a tagfüggvények egyszerûsített kifejtését is. 4. most egy olyat mutatok be. virtual void prompt() = 0.Forrás: http://www.doksi. 12. virtual void reset_value(int i) = 0. Ez sokkal világosabb.4. virtual ~Ival_box() { } }. amely tisztán megvalósítható a C++ nyelvvel. . virtual void set_value(int i) = 0. Ehelyett egy virtuális destruktorunk van. hogy programunknak olyan vegyes környezetben kell futnia. vagy mert programunknak különbözõ rendszerek felhasználóival kell kapcsolatot tartania. Elõször is.

mint azelõtt. hogy az Ival_slider-bõl származtatott osztályok számára a BBwindow-t elérhetõvé tegyük. . A BBwindow osztályból is származik. de ez nemkívánatos mellékhatásokkal jár. BBwindow::draw(). meg kell valósítania annak tisztán virtuális (pure virtual) függvényeit. A több osztályból való közvetlen öröklõdést általában többszörös öröklõdésnek (multiple inheritance) hívják (§15.4. hogy az Ival_slider-t felhasználó programozó nem használhatja közvetlenül a BBwindow által nyújtott eszközöket. mind a BBwindow függvényei közül felül kell írnia néhányat. Mint a §12. melyekkel ezt megteheti. ezért onnan valók az eszközei. Érdekes módon az Ival_slider ilyen módon való deklarálása esetén ugyanolyan kódot írhatunk.2).1. Mivel az Ival_slider osztály az absztrakt Ival_box osztályból származik. Az Ival_box adja a származtatott osztály felületét. az az út. hogy az Ival_box a BBwindow-ból származik).1 pontban láttuk. illetve abból. hogy a szerkezet logikusabb módon tükrözze a megvalósítást. protected BBwindow { public: Ival_slider(int. Hasonlóan.3. mert egy osztály nem írhatja felül tagjainak virtuális függvényeit (§24. hogy a BBwindow „megvalósítási osztály” tagja legyen az Ival_box-nak. Vegyük észre. Azért használunk védett származtatást a szigorúbb megkötést jelentõ (és általában biztonságosabb) privát helyett.doksi.7). Az Ival_slider felülete az Ival_box-tól örökölt részbõl áll.int). void set_value(int i). lehetséges ugyan az Ival_slider közvetett származtatása a BBwindow-ból (azáltal. nem járható.Forrás: http://www. int get_value(). protected: // a BBwindow virtuális függvényeit felülíró függvények // pl. Mivel a BBwindow osztályból való származása mindössze segítséget nyújt a megvalósításhoz.7[14]. hogy Ival_slider-nek mind az Ival_box. amit maga az Ival_slider kifejezetten deklarál. BBwindow::mouse1hit() private: // a csúszka adatai }. ezért közvetve vagy közvetlenül mindkét osztályból származnia kell. melynek megvannak a maga elõnyei és hátrányai (§12.hu 12. Az ablaknak az Ival_box osztály egy BBwindow* típusú tagjaként való ábrázolása teljesen eltérõ szerkezethez vezet.2).. §25. Származtatott osztályok 419 Az Ival_slider definíciója így alakulhat: class Ival_slider : public Ival_box.3. Ebbõl következik. onnan védett (protected) módon származik (§15. ~Ival_slider().. Csak azért változtattunk. ezért nyilvános (public) módon származik onnan. // .4).

. .. */ }. de mivel az Ival_box-nak virtuális destruktora van...hu 420 Absztrakciós módszerek Számos osztálynak szüksége van valamilyen „rendrakásra”.. hogy igenis szükség van rá. class Ival_dial : public Ival_box. class Popup_ival_slider : public Ival_slider { /* . Nem tudhatjuk. */ }.. delete p. Mivel az absztrakt Ival_box osztály nem tudhatja.. } A delete operátor megsemmisíti az objektumot. hogy egy származtatott osztálynak nincs-e szüksége erre. a megfelelõ destruktor fog meghívódni. Az általános felhasználók számára ezek csak részletkérdések. amelyre p mutat. protected BBwindow { /* . class Ival_slider : public Ival_box. */ }. mielõtt egy objektuma megsemmisül. fel kell tételeznie. (ha az adott osztálynak van ilyen)..doksi.. */ }. A rendrakást úgy biztosítjuk. */ }... hogy pontosan milyen osztályú objektumról van szó. class Flashing_ival_slider : public Ival_slider { /* . protected BBwindow { /* . Egyszerû rövidítésekkel pedig így ábrázolhatjuk: BBwindow Ival_box BBwindow Ival_slider Ival_dial Popup_slider Flashing_slider A szaggatott nyilak a védett (protected) módú öröklõdést jelölik.. hogy a bázisosztályban definiáljuk az Ival_box::~Ival_box() virtuális destruktort és a származtatott osztályokban megfelelõ módon felülírjuk azt: void f(Ival_box* p) { // . Az Ival_box hierarchiát most így írhatjuk le: class Ival_box { /* .Forrás: http://www.

// BB class Ival_slider : public Ival_box.... */ }.. */ }.4.Forrás: http://www. */ }. class BB_ival_slider : public Ival_box.. // CW // .. mint a hagyományos. protected BBwindow { /* . protected CWwindow { /* . // . class Ival_slider : public Ival_box { /* .. protected CWwindow { /* .. class CW_ival_slider : public Ival_box. // . de nem kevésbé hatékony. */ }.. */ }.. származtathatunk egy absztrakt Ival_slider osztályt az Ival_box-ból. hogy különbözõ nevû Ival_slider osztályokat hozunk létre: class Ival_box { /* . */ }.hu 12.. A nyilvánvaló megoldás az.... protected BBwindow { /* . Ráadásul a BBwindow-hoz és a CWwindow-hoz írt Ival_slider-ek nem létezhetnek együtt. */ }. Ábrával: BBwindow Ival_box CWwindow BB_ival_slider CW_ival_slider Hogy programunk Ival_box osztályait jobban elszigeteljük a megvalósítás egyéb részleteitõl.. // közös class Ival_slider : public Ival_box.. */ }. protected BBwindow { /* . protected CWwindow { /* . */ }. ha egyébként maguk a BBwindow és CWwindow felhasználói felületek igen.. Egyéb megvalósítások Ez a szerkezet tisztább és könnyebben módosítható.doksi. class CW_ival_slider : public Ival_slider. . class BB_ival_slider : public Ival_slider... még akkor sem..3. A változatkövetési problémát azonban nem oldja meg: class Ival_box { /* .. */ }. majd ebbõl örököltethetjük az egyes rendszerfüggõ Ival_slider-eket: class Ival_box { /* ... Származtatott osztályok 421 12....

doksi..” rendszerében van egy csúszka (slider) osztály. ha a hierarchiában egyedibb osztályokat használunk. alkalmazásközpontú rendszer származtatott osztályokként megvalósított felületeinek viszonyrendszerébõl fog állni: class Ival_box { /* .Forrás: http://www..hu 422 Absztrakciós módszerek Ábrával: Ival_box BBwindow Ival_slider CWwindow BB_ival_slider CW_ival_slider Általában még ennél is jobban járunk...... class Ival_dial : public Ival_box { /* . */ }.. */ }. */ }.. és a BBwindow-hoz hasonló általános bázisosztályokból való öröklõdés ritkán fordul elõ. */ }.. */ }. akkor a mi Ival_slider-ünket közvetlenül a BBslider-bõl származtathatjuk: class BB_ival_slider : public Ival_slider. Ekkor a programozás tulajdonképpen a rokon fogalmak közötti leképezésre egyszerûsödik. class Flashing_ival_slider : public Ival_slider { /* . protected BBslider { /* . ha a mi fogalmaink nem esnek távol a megvalósítás céljából felhasznált rendszer fogalmaitól. class Popup_ival_slider : public Ival_slider { /* . Ha például a „Big Bucks Inc.. Ábrával: BBwindow Ival_box CWwindow BBslider Ival_slider CWslider BB_ival_slider CW_ival_slider Ez a javítás jelentõs lehet abban a (sûrûn elõforduló) esetben. */ }. A teljes hierarchia egyrészt az eredeti. class CW_ival_slider : public Ival_slider. ... class Ival_slider : public Ival_box { /* .. */ }. protected CWslider { /* .

doksi.4. hogy majdnem az egész kód változtatás nélkül és ugyanúgy mûködik mindkét esetben.1-beli interact() függvényt például nem kell újraírnunk..3. protected BBwindow_with_bells_and_whistles { /* .hu 12. 12. */ }. A §12. ha az egyik szerkezetrõl a másikra váltunk. A program szempontjából ezek a szerkezetek egyenértékûek abban az értelemben. csak a konkrét megvalósítást végzõ osztályok veszik körül. Bírálat Az absztrakt osztályokat használó osztályszerkezet rugalmas és majdnem ugyanolyan egyszerûen kezelhetõ... . Az utóbbiban a fa gyökere a megfelelõ ablakosztály. az elõbbiben viszont változatlanul az alkalmazás osztályhierarchiája marad a tényleges megvalósítást végzõ osztályok alapja. és mindkettõnél az alkalmazott felhasználói felülettõl függõ elemekre való tekintet nélkül vizsgálhatjuk az Ival_box család osztályait.1. */ }. class BB_flashing_ival_slider : public Flashing_ival_slider. // . */ }. class BB_popup_ival_slider : public Popup_ival_slider. Származtatott osztályok 423 Illetve a hierarchiát – szintén az öröklõdés segítségével – többféle grafikus felhasználói felületre leképezõ származtatott osztályokból: class BB_ival_slider : public Ival_slider. */ }.Forrás: http://www. protected BBslider { /* . class CW_ival_slider : public Ival_slider. A kapott felépítményt egyszerû rövidítések segítségével így ábrázolhatjuk: Ival_box Ival_slider Ival_dial ipopup iflash BBslider BBslider CWsl CWsl BBb&w CWsl BBislider BBipop CWipop CWifl BBifl CWislider Az eredeti Ival_box hierarchia változatlan marad..4. mint a konkrét felhasználói felületet bázisosztályként szerepeltetõ. protected CWslider { /* ..... protected BBslider { /* ...

Egyszerû megoldás lehet például egy olyan osztály bevezetése. hacsak nem szisztematikusan járunk el. akkor nagyrészt használhatjuk az Ival_box. . de az absztrakt osztályokat használó szerkezet esetében szinte az egész kód védett a megvalósítás változásától és egy ilyen változás után nem kell újrafordítani. Az Ival_maker osztály az Ival_box hierarchia minden olyan felülete számára rendelkezik az adott típusú objektumot létrehozó függvénnyel.4. Ezt többféleképpen is megtehetjük. Az elvont Ival_box osztályokra épített programot választva nem használhatjuk „véletlenül” a megvalósító osztályok nyújtotta lehetõségeket.4.doksi. Ez különösen akkor fontos. mint a sima Ival_box. ha az ilyen rendszerfüggõ nevek minél kevesebb helyen fordulnának elõ.6. Az objektumokat azonban az adott rendszerre jellemzõ nevek (például CW_ival_dial és BB_flashing_ival_slider) felhasználásával kell létrehozni. mert csak az Ival_box hierarchiában kifejezetten megadott lehetõségek érhetõk el..Forrás: http://www. függvényeiket pedig – némiképp félrevezetõ módon – virtuális konstruktoroknak (§15. mely felületrõl a felhasználók tudhatnak. 12. ha a felhasználói felület nyilvános felülete megváltozik. Ival_slider stb. Az objektumok létrehozásának adott helyre korlátozása A program legnagyobb része megírható az Ival_box felület felhasználásával. // elõugró csúszka (popup slider) // készítése // . máshol nem használható megvalósítás csapdájába való bezáródás veszélyének. ha a megvalósítást végzõ elemek készítõje egy új. semmi sem öröklõdik automatikusan egy rendszerfüggõ bázisosztálytól. int) =0. int) =0. „majdnem kompatibilis” változatot bocsát ki. Ráadásul az absztrakt osztályos megoldást választók a klasszikus hierarchia híveinél kevésbé vannak kitéve az egyedi. }. Ha a származtatott felületek továbbfejlõdnek és több szolgáltatást nyújtanak.2). Az ilyen osztályokat gyárnak (factory) hívják. felületeket. amely az objektumokat létrehozó mûveletekért felelõs: class Ival_maker { public: virtual Ival_dial* dial(int. // tárcsa (dial) készítése virtual Popup_ival_slider* popup_slider(int. Jó lenne. Szokás szerint az indirekció (közvetett hivatkozás) bevezetése a megoldás.hu 424 Absztrakciós módszerek Mindkét esetben újra kell írnunk az egyes Ival_box osztályokat. mivel az objektumok létrehozása nehezen köthetõ helyhez..

99). Popup_ival_slider* popup_slider(int. // ...hu 12. int). user(&LS_impl). // . akkor a programozó ennek segítségével úgy hozhat létre objektumokat.. int).Forrás: http://www. LS_maker LS_impl. void driver() { user(&BB_impl)..b)..doksi. } Ival_dial* LS_maker::dial(int a. int b) { return new BB_ival_dial(a. int). } // megfelelõ tárcsa létrehozása // BB-felhasználóknak // LS-felhasználóknak // BB használata // LS használata .. } BB_maker BB_impl. }. }.b). int). Származtatott osztályok 425 Az egyes különbözõ felhasználói felületeket kezelõ rendszereket most az Ival_maker osztályból származtatott osztályokként ábrázoljuk: class BB_maker : public Ival_maker { public: Ival_dial* dial(int. pontosan milyen rendszerû felhasználói felület van használatban: void user(Ival_maker* pim) { Ival_box* pb = pim->dial(0. hogy nem kell tudnia. // BB-változatok készítése // LS-változatok készítése Minden függvény a kívánt felületû és megvalósítási típusú objektumot hozza létre: Ival_dial* BB_maker::dial(int a. } Ha adott egy mutató egy Ival_maker objektumra. int b) { return new LS_ival_dial(a. Popup_ival_slider* popup_slider(int. class LS_maker : public Ival_maker { public: Ival_dial* dial(int. // .

anélkül hogy a megvalósítás részleteivel keveredne vagy jelentõsen növelné a program futási idejét. mint bármely más virtuális függvényt. amelynek egyes osztályai hasznos szolgáltatásokat kínálnak a felhasználóknak. §12. Természetesen minden osztály ad egy felületet a programozó számára. Tanácsok [1] Kerüljük a típusmezõk alkalmazását. de „felület” és „építõkõ” szerepük alapvetõen az absztrakt osztályoknak és az osztályhierarchiáknak van. hogy miféle elvonatkoztatási réteg határát lépi át. hogy a felületeket elválasszuk a megvalósítási részletektõl.Forrás: http://www. A klasszikus felépítés a tényleges megvalósítást sokszor összekapcsolja a felhasználóknak nyújtott felülettel. Az ilyen felépítés ideálisan támogatja a lépésenkénti finomítással való fejlesztést. hogy a világos felületek elkészítésére összpontosíthassunk. Az osztályhierarchia annak eszköze. §12.hu 426 Absztrakciós módszerek 12.doksi. amennyiben ezek megfelelõen illeszkednek a hierarchiába. Klasszikus hierarchiának azt a felépítést nevezzük. Ez ügyben az absztrakt osztályok segíthetnek.2.2. de klasszikus hierarchiaként építsünk fel. §12.3. némely absztrakt osztály pedig jelentõs szolgáltatásokat kínál. [3] Használjunk absztrakt osztályokat. Az absztrakt osztályok segítségével felépített rendszer tisztább és hatékonyabb módot ad a fogalmak kifejezésére. hogy fokozatosan építsünk fel osztályokat. [5] Használjunk absztrakt osztályokat. §12. Egy absztrakt osztály virtuális függvényét meghívni semmivel sem kerül többe.2. .5. §12. [2] Az objektumok felszeletelõdését (slicing) elkerülendõ használjunk mutatókat és referenciákat. A fentiekbõl adódó végkövetkeztetés az. [4] Használjunk absztrakt osztályokat.5. Osztályhierarchiák és absztrakt osztályok Az absztrakt osztályok felületek (interface). hogy minél kisebb felületeket használhassunk. 12. A virtuális függvények meghívása egyszerû és független attól. illetve az új osztályok létrehozását. amelyekre építhetünk.2.4. hogy egy rendszert a felhasználók felé mindig absztrakt osztályok hierarchiájaként mutassunk. illetve egyben a fejlettebb vagy egyedi feladatot végzõ osztályok számára építõkõül szolgálnak.4.3.6.

12. [11] Az önálló fogalmakat külön ábrázoljuk.1.current() – . } }. (*1) Ha adott a következõ: class Base { public: virtual void iam() { cout << "Bázisosztály\n". Dot (Képernyõpont).4. (*3. §12. akkor legyen virtuális destruktora is.1. [10] Az absztrakt osztályoknak általában nincs szükségük konstruktorra.3. §12.Forrás: http://www. [7] Használjunk absztrakt osztályokat. mondjuk a * megfelelõ pozícióra való helyezését jelenti. A Window(n. Circle (Kör). Származtassunk két osztályt a Base-bõl. akkor készíthetünk egy egyszerû.) Ebben a feladatban és a továbbiakban a következõ osztályokat használjuk: Window (Ablak). hogy a program többféle rendszeren is mûködjön. Square (Négyzet) és Triangle (Háromszög). hogy késõbb új megvalósítást készíthessünk a meglevõ felhasználói kód befolyásolása nélkül. (Ha nincs ilyen vagy nincs tapasztalatunk ilyesmivel.4.4. ASCII karakterekbõl felépített megvalósítást. ahol egy pont egy karakterpozíciónak felel meg.2. Line (Vonal). 12.2.7. [8] Használjunk absztrakt osztályokat. 2. Gyakorlatok 1. hogy minél kevesebbszer kelljen a felhasználói kódot újrafordítani. A képernyõ pontjait az (x. mindegyiknek legyen egy iam() függvénye. §12.4. Rectangle (Téglalap). §12. Hozzunk létre egy-egy ilyen osztályú objektumot és hívjuk meg rájuk az iam() függvényt.2.y) derékszögû (descartes-i) koordináták segítségével címezzük meg.1. Shape (Alakzat. Point (Pont).4.doksi. és az írás a megfelelõ karakter. Idom). Rendeljünk a származtatott osztályok objektumaira hivatkozó mutatókat Base* típusú mutatókhoz és hívjuk meg ezeken keresztül az iam() függvényt.hu 12.4.m) hozzon létre egy n-szer m méretû területet a képernyõn. Származtatott osztályok 427 [6] Használjunk virtuális függvényeket. [9] Ha egy osztálynak van virtuális függvénye. amely kiírja az osztály nevét.5) Készítsünk egy egyszerû grafikus rendszert a rendelkezésünk álló grafikus felhasználói felület felett. A Window osztályba tartozó w aktuális helye – w. §12.

Háromféle dolog lehet színes: a háttér.5) Gondoskodjunk arról. (*3. amíg a draw() függvényt meg nem hívjuk. Ezt a jelenséget gyakran hívják „levágásnak” (clipping).0).thickness(n) a 0.current(). amely megtalálja két Shape egymáshoz legközelebbi pontjait és összeköti azokat.5) Írjuk meg a Line::arrowhead() függvényt. Ezt a Shape::outline() függvény állítsa be. (*2. 7. amely egy házat ábrázol tetõvel. ne (északkelet). 8. Rectangle(w.current(p) hívással állíthatjuk be.doksi. 5. A Text legyen egy téglalap alakú Shape. Ha egy Shape-re alkalmaztuk a draw() függvényt.draw(Circle(w. amely karaktereket tud megjeleníteni. Az s.1.current(). A Dot(p)-ként létrehozott Dot egy Point p-t jelent a képernyõn. hogy tudják fejezni ezt a négyféle lehetõséget. Minthogy egy vonalnak két vége van és a nyíl a vonalhoz képest kétféle irányba mutathat. kezdetben Point(0.2.Point(10. A Line(x. ahol a 2 az alapértelmezett érték és a 0 érték azt jelenti. Alapértelmezés szerint egy karakter a koordináta-tengelyen minden irányban egy egységnyi helyet foglaljon el. w (west – nyugat). így az arrowhead() függvény paramétere vagy paraméterei ki kell. A pozíciót a w. s (south – dél). E célból – gyakorlatként – ne hagyatkozzunk a felhasznált grafikus felhasználói felületre. a Rectangle. sw (délnyugat) és c (center – középpont).y).nw()) például egy vonalat húz az x közepétõl az y bal felsõ sarkához. Egy Point nem Shape is egyben. szaggatott vagy pontokból álló is. Adjunk meg olyan mûveleteket. 4. közös felülete. (*3) Vezessük be grafikai rendszerünkbe a szín fogalmát. a Line objektumokat egy Point pár – Line(w.5) Egészítsük ki grafikai rendszerünket a Text típussal. (*2) Egy Shape fontos részei szakaszokként jelennek meg a képernyõn. 6.y. A Shape-ek nem láthatók. Minden Shape-nek 9 érintkezési pontja van: e (east – kelet). például: w. A vonal lehessen tömör. ne jelenjenek meg. A Point objektumokat egy koordináta-pár adja meg: Point(x.Forrás: http://www. amely egy vonal végére egy nyilat rajzol. amelyek segítségével meg tudjuk változtatni ezen szakaszok kinézetét. egy zárt Shape belseje és egy Shape határa. (*2) Határozzunk meg egy függvényt. ahol p egy Point. n (north – észak). amelyek kívül esnek egy Window-n. hogy azon pontok és vonalszakaszok. Egy Rectangle-t a bal alsó és a jobb felsõ csúcsával adunk meg. se (délkelet). az aktuális pozíció a Shape se()-je lesz. a Line. (*2. nw (északnyugat).10)).c(). hogy a vonal láthatatlan.p2) –. . a Circle stb. és egy ajtóval.10)).3 értékek valamelyikére állítsa be a vonalszélességet. két ablakkal.hu 428 Absztrakciós módszerek 3.current(). Egyszerû tesztként jelenítsünk meg egy gyermekrajzot. a Shape osztály a Dot.

Kezeljük a futási idejû hibákat azonos módon. }.. Ne írjuk meg az átfedés tényleges megállapítására szolgáló kódot. csak arra ügyeljünk. Segítség: nézzük meg a <task. Hogyan lehetne egy ilyen könyvtárat használó programban hibakeresést végezni? 12. (*2. Egy új task számára legyen lehetséges paramétereket megadni konstruktora(i)nak paramétereként. Az egyes elvégzendõ feladatokat a task osztályból öröklõ osztályok objektumaiként adhassuk meg. a fõ tervezési döntések egyike. olyasmi. Legyen egy task::delay(long) függvény. határozzuk meg az intersect() függvényt. Milyen körülmények között okoz ez a trükk komoly problémákat? 10. char element[1]. hogy a megfelelõ függvényeket hívjuk meg.hu 12. amely megvalósítja a virtuális idõ fogalmát. (*2) Határozzuk meg egy kalandjáték számára a Warrior (harcos). el lehet dobni.) osztályok felületét. Egy task-nak legyen lehetõsége több forrás felõl érkezõ bemenetre várakozni. amely két Shape* paramétert vesz és a megfelelõ függvények meghívásával megállapítja. az olvasó jobbat tud írni. hogy kiegészítõ eljárásként (co-routine) mûködhessenek. Származtatott osztályok 429 9. hogy a két Shape átfedõ-e.h> fejállományt. A task-ok által végrehajtandó programokat virtuális függvényekkel határozzuk meg. char& operator[ ](int i) { return element[i]. Ezt az eljárást angolul általában „double dispatch” vagy „multi-method” néven emlegetik. amit fel lehet kapni.doksi. public: static Char_vec* new_char_vec(int s). Az. metszi-e egymást. Ez egy régi program. Legyen egy ütemezõ. Erre a célra tervezzünk egy queue osztályt. hogy egybefüggõ memóriaterületet foglalhassunk le egy Char_vec objektum számára. amely „fogyasztja” ezt a virtuális idõt. . A task osztályú objektumok legyenek képesek állapotuk mentésére és visszaállítására (mondjuk a task::save() és a task::restore() függvényekkel). (*2) Vegyük az alábbi osztályt: class Char_vec { int sz. így elemeit az element() függvénnyel indexelhetjük. Monster (szörny) és Object (tárgy. hogy ez az ütemezõ a task része vagy önálló osztály lesz-e. Legyen egy task nevû osztály. Ehhez szükséges lesz az osztályok megfelelõ (virtuális) függvényekkel való bõvítése. Definiáljuk a new_char_vec()-t. használni lehet stb. Square és Triangle osztályok. (*5) Tervezzünk és írjunk meg egy eseményvezérelt szimulációkat végzõ könyvtárat. } // .5) Ha adottak a Shape osztályból származó Circle.. 11. A task-oknak kapcsolatot kell tartaniuk egymással.Forrás: http://www.

4) egy eltérõ megvalósítási módját: minden. ha változott a rendszerfüggõ elemek megvalósítása.7[2]-ben Point és Dot osztály is? Milyen körülmények között lenne jó ötlet a Shape osztályokat a kulcsosztályok. mennyire könnyû a megvalósító osztályok és a felületek újrahasznosítása. és szükség van-e újrafordításra. Gondoljuk át a következõ szempontokat: a használat könnyûsége. (*3) Vázoljuk az Ival_box példa (§12. hogy bemutassák a típuskonverziókból adódó lehetséges problémákat. a program által elérhetõ osztály egyszerûen egy mutatót tartalmazzon a megvalósító osztályra. (*1.doksi.hu 430 Absztrakciós módszerek 13. . Ilyen módon minden „felületosztály” egy megvalósító osztály leírója (handle) lesz. ha új fogalmat vezetünk be a hierarchiába. mennyire könnyû változtatásokat eszközölni a felületekben vagy a megvalósításban.Forrás: http://www. a programozás könnyûsége.5) Miért van a §12. például a Line konkrét változataival bõvíteni? 14. Írjunk olyan részkódokat.és egy megvalósítási hierarchiával. amelyek elég részletesek ahhoz. és két hierarchiával fogunk rendelkezni: egy felület.

hu 13 Sablonok „Az Olvasó idézetének helye. hogy általános fogalmak széles körét ábrázoljuk és egyszerû módon használjuk együtt. .doksi. akkor vagy nem rokon fogalmakat kapcsolunk össze vagy szükségtelen függéseket teremtünk. Stroustrup) Sablonok • Egy karakterlánc sablon • Példányosítás • Sablonparaméterek • Típusellenõrzés • Függvénysablonok • Sablonparaméterek levezetése • Sablonparaméterek meghatározása • Függvénysablonok túlterhelése • Eljárásmód megadása sablonparaméterekkel • Alapértelmezett sablonparaméterek • Specializáció • Öröklõdés és sablonok • Tag sablonok • Konverziók • A forráskód szerkezete • Tanácsok • Gyakorlatok 13. így kevésbé rugalmas részekbõl vagy összetevõkbõl kell majd a programokat összeállítanunk. Az így létrejövõ osztályok futási idõ és tárigény tekintetében felveszik a versenyt a kézzel írott és egyedibb feladatot végzõ kóddal.1. Ha megsértjük ezt az elvet. Bevezetés Független fogalmakat függetlenül ábrázoljunk és csak szükség esetén használjunk együtt.” (B. A sablonok (template) egyszerû módot adnak arra.Forrás: http://www.

vagy a rendezést (sort()). ha valóban szüksége van rájuk. A sablon által felhasznált paramétertípusok nem kell. A standard könyvtár nagyobb mértékû általánosságot. Ezért aztán e könyvnek az említett könyvtárral foglalkozó fejezetei (a III. Egy adott v-hez a fordítóprogram automatikusan választja ki a legalkalmasabb rendezõ függvényt. megvalósításához és használatához szükséges módszerekre esik. hogy rokonságban álljanak egymással. hogy az elsõdleges hangsúly a standard könyvtár tervezéséhez. hogy egy osztály vagy függvény definiálásakor egy típust paraméterként adjunk meg. amelyeket ténylegesen ki is használ. ostream. Következésképpen a tárgyalandó eljárások széles körben használhatóak és igen sokféle probléma megoldásához biztosítanak hatékony segítséget.2 Az osztálysablonok létrehozására és használatára szolgáló alapvetõ eljárások §13. complex. list és map). illetve a sablonok formai követelményeire vonatkozó részletes szabályok a §C. rugalmasságot és hatékonyságot követel. A sablon a felhasznált típusnak csak azon tulajdonságaitól függ. így nem szükséges az sem.5 Sablon többféle megvalósítása különbözõ definiciókkal §13. hogy egyszerû felületek mögé kifinomult megvalósításokat rejtsünk és csak akkor „mutassuk be” a bonyolult részleteket a felhasználónak.hu 432 Absztrakciós módszerek A sablonok közvetlenül támogatják az általánosított (generikus) programozást (§2. függvények túlterhelése.4 Általánosított algoritmusok eljárásmódjának megadása sablonparaméterekkel §13. típusok levezetése §13. Lehetõvé teszik. rész) gazdag forrásai a sablonokra és az azokra építõ programozási módszerekre vonatkozó példáknak. azaz a típusoknak paraméterként való használatát. Ebben a fejezetben a sablonokat úgy mutatjuk be. de a legfõbb mûveleteket is.7 A forráskód szerkezete A sablon (template) fogalmát a §2. A C++ sablonjai lehetõvé teszik. A standard könyvtár minden fõbb fogalmat egy-egy sablonként ábrázol (például string. a << kimeneti mûveletet.Forrás: http://www. A sort(v) például sokféle tároló objektum tartalmazta sokféle típusú elemnek sokféle rendezõ algoritmusához adhat felületet.1 és a §3. . Következésképpen ez a fejezet a sablonok fogalmát járja körül és csupán a használatuk alapvetõ módjait bemutató kisebb példákra összpontosít: §13. a komplex számok (complex) összeadását. például a karakterláncok (string-ek) összehasonlítását. hogy egyazon öröklõdési hierarchia tagjai legyenek. A sablonnevek feloldására.13 pontban vannak.8 pont vezette be.).7.3 Függvénysablonok.7.6 Öröklõdés és sablonok (futási és fordítási idejû többalakúság) §13. mint a legtöbb program.doksi. egy lista (list) következõ elemének vételét.

// . class Jchar { // japán karakter }.doksi. hogy a template<class C> elõtag azt jelenti.hu 13. karakterekbõl álló láncok számos összefüggésben hasznosak lehetnek. A template<class C> elõtag azt jelenti. általánosabb karakterlánc-osztályt kapunk: template<class C> class String { struct Srep. A karakterlánc definiciója arra épít. összefûzési és összehasonlítási mûveleteket nyújt. Srep *rep. A C hatóköre a template<class C> elõtaggal bevezetett deklaráció végéig terjed. Például az elõjeles és elõjel nélküli.2. kínai vagy görög stb. String<Jchar> js. Egy egyszerû karakterlánc sablon Vegyünk egy karakterláncot. hogy egy sablon deklarációja következik és abban a C típusparamétert fogjuk használni.2-beli char-okból felépülõ string osztályban a karakterek típusát paraméterré tesszük.. hogy minél kevésbé függjünk egy adott karakterkészlettõl. nem feltétlenül kell osztálynévnek lennie. ezen kívül nem sok egyébre. public: String(). amelyeket rendesen a „karakterlánc” fogalmához kötünk. Ezeket különféle karakterkészletek számára szeretnénk biztosítani. Bevezetése után a C-t ugyanúgy használhatjuk. a sablon által meghatározott osztály nevét adja és ugyanúgy használható. hogy egy karaktert le lehet másolni. A string (karakterlánc) olyan osztály. String(const String&). amely karaktereket tárol és olyan indexelési. }.. String<wchar_t> ws. C read(int i) const. mint bármely más osztálynév: String<char> cs. Sablonok 433 13. Ezért ha a §11. Jegyezzük meg. hogy C egy típusnév. . String(const C*).Forrás: http://www. String<unsigned char> us. Ezért úgy szeretnénk a karakterlánc fogalmát ábrázolni. Az osztálysablon neve a <> jelpár közé írott típusnévvel együtt egy. mint bármely más típusnevet.

map<string.12-beli String-definícióval definiáltuk volna. mintha a §11. akkor egy typedef elrejti elõlünk. Ez lehetõvé teszi.int> m. // eredmény kiírása } A Jchar japán karaktereket használó változat ez lenne: int main() // szavak elõfordulásának megszámlálása a bemeneten { String<Jchar> buf. ha nem érdekel bennünket egy típus pontos definiciója. hogy a char-okból álló karakterláncok szolgáltatásait más típusú karakterekbõl álló String-ek számára is elérhetõvé tegyük. Ráadásul.doksi. // eredmény kiírása } A standard könyvtárban szerepel a sablonná alakított String-hez hasonló basic_string sablon is (§11.int> m.hu 434 Absztrakciós módszerek A névre vonatkozó sajátos formai követelményektõl eltekintve a String<char> pontosan ugyanúgy mûködik. §20.8 pont szószámláló példája így írható át: int main() // szavak elõfordulásának megszámlálása a bemeneten { String<char> buf. // eredmény kiírása } A typedef-ek általában is hasznosak a sablonokból létrehozott osztályok hosszú neveinek lerövidítésére. map<String<Jchar>.3).Forrás: http://www. hogy sablonból létrehozott típusról van szó.12. A String sablonná tétele lehetõvé teszi. while (cin>>buf) m[buf]++. a §11. hogy a szószámláló példát így írjuk át: int main() // szavak elõfordulásának megszámlálása a bemeneten { string buf. map<String<char>. Például ha a standard könyvtárbeli map és String sablonokat használjuk.int> m. . while (cin>>buf) m[buf]++. A standard könyvtárban a string mint a basic_string<char> szinonimája szerepel: typedef basic_string<char> string. while (cin>>buf) m[buf]++.

hu 13.. ugyanúgy. Általában jó ötlet hibakereséssel ellenõrizni egy osztályt. Sablonok 435 13. például a String-et. } A sablonparaméterek – mint a C – inkább paraméterek. paramétereik pedig ugyanazok. Ha egy ilyen tagot az osztályán kívül írunk le. mint a sablon osztályéi. akkor hasznos annak viselkedését elõször egy konkrét típusú paraméterrel (például a char-ral) elképzelni. Egy tagot nem szükséges magában az osztályban definiálni. kifejezetten sablonként kell megadnunk: template<class C> struct String<C>::Srep { C* s. mint a sablonon kívül definiált típusok. mint egy egyenértékû „kézzel írott” osztályé. mint a közönséges osztályokét. A String<C> hatókörén belül a <C>-vel való minõsítés felesleges. // mutató az elemekre int sz. a kódhibáknak pedig a legtöbbjét egy adott osztály összefüggésében kezelhetünk. ha meg akarunk érteni egy sablont. így a konstruktor neve String<C>::String lesz. mint egy nem sablon osztálytag esetében (§C. Késõbb aztán anélkül foglalkozhatunk a típus általánosításából esetleg adódó problémákkal.doksi. Egy sablon osztály (template class) tagjait ugyanúgy deklaráljuk és definiáljuk. // hivatkozásszámláló // .).2.7. Ezáltal számos tervezési hibát. hiszen a sablon neve már tartalmazza azt. Ezt a fajta hibakeresést (debugging) a legtöbb programozó jól ismeri. }. mielõtt megpróbáljuk a viselkedését teljes általánosságában megérteni.Forrás: http://www.. De ha jobban tetszik. ahogyan az azokat használó sablonkódot írjuk.C()). valahol máshol is elég.1. A sablon osztályok tagjai maguk is sablonok. mielõtt sablont készítünk belõle (String<C>). de ez nem érinti azt a módot. és a legtöbben jobban boldogulnak egy konkrét példával. Hasonlóan. Sablonok meghatározása A sablonból létrehozott osztályok teljesen közönséges osztályok. de nem feltétlenül jelenti a létrehozott kód mennyiségének csökkenését sem.13. } template<class C> String<C>::String() { rep = new Srep(0. // elemek száma int n. hogy a hagyományosabb hibák elvonnák a figyelmünket. template<class C> C String<C>::read(int i) const { return rep->s[i]. meg is adhatjuk a minõsítést: . ezért a sablonok használata semmivel sem igényel hosszabb futási idõt. mint egy elvonttal.

4.2. */ }.3. class String { /* . Az osztálysablonok neve nem terhelhetõ túl. 13. A String sablon paramétereként használt típusnak például támogatnia kell a szokásos másoló mûveleteket (§10.7): String<char> cs. A sablon adott paramétertípus számára megadott változatát specializációnak (specialization) nevezzük.. ott nem lehet ugyanolyan néven másik egyedet bevezetni (lásd még §13.. hogy kitalálja. // hiba: két meghatározás A sablonparaméterként használt típusnak biztosítania kell a sablon által várt felületet. */ }. gyakran sablon-példányosításnak (template instantiation) hívják (§C. így ha egy hatókörben már megadtunk egy osztálysablont.2. melynek során egy sablon osztályból és egy sablonparaméterbõl egy osztálydeklaráció keletkezik.hu 436 Absztrakciós módszerek template<class C> String<C>::String<C>() { rep = new Srep(0.7. Ha függvényt hozunk létre egy sablon függvénybõl és egy sablonparaméterbõl.13. De amíg a függvényeket csak túlterhelni lehet (§13. az a függvény-példányosítás. Sablonok példányosítása Az eljárást. hogy minden felhasznált paramétertípus számára létrehozza a megfelelõ sablon függvényt (§C. Általában az adott C++ fordító és nem a programozó dolga. Jegyezzük meg: az nem követelmény.Forrás: http://www..1).). hogy egy sablonnak több változatát is elkészíthessük.doksi..1. .4. } Egy programban egy tagfüggvényt csak egyetlen függvény definiálhat.2.2).5): template<class T> class String { /* . Ugyanígy a sablon osztályok tagfüggvényeit is csak egy függvénysablon definiálhatja. §20. void f() { String<Jchar> js.".C()).13. milyen kódot kell létrehozni. } cs = "Az adott nyelvi változat feladata.5) használata lehetõvé teszi. hogy egy sablon különbözõ paraméterei öröklõdési viszonyban álljanak egymással. addig a specializációk (§13.

Sablonok 437 A fenti esetben a String<char> és a String<Jchar>. így az osztályokra vonatkozó szokásos szabályok érvényesek rájuk. így ilyeneket nem kell készítenie (remélhetõleg nem is teszi).5).doksi. illetve a String<char>::operator=(char *) deklarációit a fordító hozza létre. Ahogy a példa mutatja. . a destruktorok és az alapértelmezett konstruktorok. }. és sablon típusú paramétereik (§C.hu 13. Mivel a sablon a méretet paraméterként megkapja.6. közönséges típusú (pl. a kifejtésben el lehet kerülni a szabad tár használatát.2. Egy másik példa erre a Range osztály a §25. Ugyanígy a létrehozott függvények is közönséges függvények és a függvényekre vonatkozó szokásos szabályok szerint viselkednek..Forrás: http://www. Buffer<Record. int sz.13.3. hogy a sablonok hatékony eszközt adnak arra. public: Buffer() : sz(i) {} // .. */ }. Sablonparaméterek A sablonoknak lehetnek típust meghatározó. T def_val> class Cont { /* . Természetesen egy sablonnak több paramétere is lehet: template<class T. int i> class Buffer { T v[i]..127> cbuf. A létrehozott osztályok közönséges osztályok.1-ben. és ahol ezért nem lehet az általánosabb string-et vagy vector-t használni. egy sablonparamétert felhasználhatunk a további sablonparaméterek meghatározásában is. Buffer<char.3). hogy viszonylag rövid definíciókból hozzunk létre kódot. Az egész típusú paraméterek méretek és korlátok megadásánál hasznosak: template<class T. Más tagfüggvényeket nem használunk. 13. ahol a futási idejû hatékonyság és a program tömörsége elsõdleges szempont. a megfelelõ Srep típusok. int). hogy elkerüljük a memóriának csaknem azonos függvény-definiciókkal való elárasztását (§13.8> rbuf. A Buffer-hez hasonló egyszerû és korlátozott tárolók ott lehetnek fontosak. Ezért aztán nem árt némi óvatosság. Nyilvánvaló..

hogy „azonos”? Szokás szerint. 13. hogy a Buffer<char. mint a String<unsigned char>. hogy legyen.5).i> bx. a typedef-ek nem vezetnek be új típust. azok ugyanarra a létrehozott típusra fognak hivatkozni. A mutató. String<char> s5. külsõ szerkesztésû objektum vagy függvény címe (§9. így a Buffer<char. ahol f egy függvény neve. illetve egy tagra hivatkozó.20-10> b3. így a String<Uchar> ugyanaz.2). így a paraméter értékének módosítására tett kísérlet hibának számít. String<Uchar> s4. Az egész típusú paramétereknek konstansnak kell lenniük: void f(int i) { Buffer<int. a nem típusba tartozó paraméterek a sablonon belül állandók.5).10> b1. Buffer<String<char>.Forrás: http://www. ahol of egy objektum vagy függvény neve. Buffer<char.10>-zel azonos típus. mivel a char és az unsigned char különbözõ típusok (§4.10> b2. A tagra hivatkozó mutatókat &X::of alakban kell megadni. A fordítóprogram ki tudja értékelni a konstans kifejezéseket is (§C. De mit is jelent itt az. ha sablon paramétereként akarjuk használni. . akkor különféle paramétertípusok megadásával különféle típusokat hozhatunk létre belõle: String<char> s1.hu 438 Absztrakciós módszerek A sablon paramétere lehet konstans kifejezés (§C. túl nem terhelt mutató (§15.5). Karakterlánc literált nem használhatunk sablonparaméterként.2010>-rõl felismeri. String<unsigned char> s2.doksi. Típusok egyenértékûsége Ha adott egy sablon. a String<char> és a String<unsigned char> is különbözõek lesznek. String<int> s3.4.3). Megfordítva. Buffer<char. } // hiba: konstans kifejezés szükséges Megfordítva. illetve f alakú. Ha azonos paraméterekkel adunk meg sablonokat.2. &of alakú kell. ahol of a tag neve. typedef unsigned char Uchar.

lr. } . Típusellenõrzés A sablonokat paraméterekkel definiáljuk és késõbb így is használjuk. A T sablonparamétertõl való legközönségesebb és legkézenfekvõbb függés egy T típusú tag vagy T típusú paraméter használata. val(v) { } } // szintaktikus hiba: hiányzik a pontosvesszõ Link* head. az o változó (amely persze egy hibásan írt nulla) nem lehet a List<T>::Link konstruktor paramétere.Forrás: http://www. A sablon definiciójában használt névnek vagy ismertnek kell lennie. Link* suc. illetve az olyanokat. amelyek a konkrét paraméterek ismerete nélkül felderíthetõek: template<class T> class List { struct Link { Link* pre. public: List() : head(7) { } // hiba: kezdeti értékadás mutatónak int-tel List(const T& t) : head(new Link(0. void f(const List<int>& li. a használatnál.const T& v) : pre(p).. Link(Link* p.2.print_all()..hu 13. ha a hibák hamar kiderülnek.print_all(). mert ilyen név az adott pontról nem elérhetõ.1). } }. néha azonban csak késõbb. Ebben a példában három hibát vétettem (szándékosan).t)) { } // hiba: 'o' nem definiált azonosító // . de nem minden „egyszerû” hibát könnyû felderíteni. const List<Rec>& lr) { li..8. Hasonlóan. Sablonok 439 13. */ }. T val. A List<T>::print_all() példában a cout << p->val kifejezés használata némileg „kifinomultabb” példa. A sablonparaméterek használatával összefüggõ hibák csak a sablon használatának helyén deríthetõk fel: class Rec { /* . A sablon paraméterétõl függetlenül egy T* típusú mutatónak nem adhatjuk a 7 kezdõértéket. suc(s).5. vagy valamilyen ésszerû és nyilvánvaló módon kell függnie valamelyik sablonparamétertõl (§C. void print_all() const { for (Link* p = head.o. A sablondefinícióban a fordítóprogram ellenõrzi a formai hibákat. A fordítóprogram az egyszerû nyelvi hibákat már a definíciónál kiszûrheti.. p=p->suc) cout << p->val << '\n'.13.doksi. Link* s. p. A felhasználók jobban szeretik.

4. Knuth: A számítógép-programozás mûvészete III. A típusellenõrzés azonos szabályok szerint történik.7). 84. ) 2 Magyarul: D. sort(vs). Keresés és rendezés.13. de a lr. hogy a sablon melyik példányát használjuk. Budapest. oldal . a sablonnak az adott paraméterrel való elsõ használata. 13. Ezt a pontot rendszerint elsõ példányosítási pontnak (first point of instantiation) vagy egyszerûen példányosítási pontnak hívják (§C. A sablonparaméterekre vonatkozó megszorításokat a tagfüggvények segítségével is kifejezhetjük (§13. a vector (§16. hogy melyik ponton megy végbe.9[16]). III. Késõbb azonban felmerül a sablonként használt függvények szükségessége. hogy az adott fordítónak el is kell halasztania a típusellenõrzést a program összeszerkesztéséig (§13. 95. lehetséges. A sablon függvények (template function) meghívásakor a függvény paraméterei határozzák meg. 1988.2) vagy a map (§17.hu 440 Absztrakciós módszerek Itt a li. ahol a sablonparaméterek használatával összefüggõ hiba kiderülhet.doksi. Függvénysablonok A legtöbb programozó számára a sablonok elsõ számú és legnyilvánvalóbb felhasználása olyasféle tároló osztályok létrehozása és használata.13. // sort(vector<string>&). Mûszaki könyvkiadó. Ha ebben a fordítási egységben a print_all()nak csak a deklarációja és nem a definíciója ismert.size().1). mert a Rec típusnak nincs << kimeneti mûvelete.3. kötet.3). o.print_all() rendben van. kötet.3. Természetesen a sablon függvényt valahol definiálnunk kell (§C.Forrás: http://www.7): template<class T> void sort(vector<T>& v) { const size_t n = v.3).1). vagyis a sablonparamétereket a függvényparaméterekbõl vezetjük le (deduce) (§13.print_all() típushibás. } // deklaráció // sort(vector<int>&).2. Az adott C++-változat – megengedett módon – ezt az ellenõrzést a program összeszerkesztéséig elhalaszthatja. void f(vector<int>& vi. vector<string>& vs) { sort(vi). A legelsõ pont. // definíció 2 // Shell rendezés (Knuth. a list (§17. A felhasználók itt is a minél korábbi ellenõrzést szeretik. függetlenül attól. mint a basic_string (§20.7). E. Nézzük például egy tömb rendezését: template<class T> void sort(vector<T>&).

Sablonok 441 } for (int gap=n/2.v[j+gap]).8. . v[j] = v[j+gap]. de ez a korlátozás könnyen megkerülhetõ (§13. ki lehet következtetni (deduce) a függvény paramétereibõl.7-belivel.2. }. class Record { const char[12]. Ez a kód hatékonyságát semmilyen módon nem rontja.3. 13. } // v[j] és v[j+gap] felcserélése Hasonlítsuk össze a sort() ezen definicióját a §7. v[j+gap] = temp. // . mert nincs szüksége az összehasonlító függvényre hivatkozó mutatóra. a < összehasonlítást pedig könnyen lehet helyben kifejtve (inline) fordítani. int i> T& lookup(Buffer<T.Forrás: http://www.1. 0<=j. mellyel az értékcserét természetes formára alakíthatjuk: if (v[j+gap]<v[j]) swap(v[j].4): template<class T.. Valószínûleg gyorsabb is.4). fejezet) széles körére alkalmazható általános algoritmusok írásához. 18. gap/=2) for (int i=gap.. mert a rendezendõ elemek típusára vonatkozóan több információra támaszkodhat. Ebbõl következik. A fordítóprogram akkor tudja levezetni egy hívás típusos és nem típusba tartozó paramétereit. 0<gap. ami korlátozza a sort() ezen változatának használhatóságát. i<n. A függvénysablonok paraméterei A függvénysablonok alapvetõ fontosságúak a tároló típusok (§2. hogy egy függvényhíváskor a sablonparamétereket le lehet vezetni. hogy nincs szükség közvetett függvényhívásra.8). §3. i++) for (int j=i-gap. További egyszerûsítést jelenthet a standard könyvtárbeli swap() sablon használata (§18.7. Alapvetõ jelentõségû. Ez a sablonná alakított változat világosabb és rövidebb.6.13.i>& b. ha a függvény paraméterlistája egyértelmûen azonosítja a sablonparaméterek halmazát (§C.hu 13. Ebben a példában a < mûveletet használtuk összehasonlításra.doksi. const char* p). Nem minden típusnak van azonban < operátora. j-=gap) if (v[j+gap]<v[j]) { T temp = v[j].

int>(i). Megjegyzendõ.4). Ennek az az oka. mint ahogy egy sablon osztály számára közvetlenül megadjuk a sablonparamétereket: template<class T> class vector { /* . hogy a létrehozást egy függvény meghívásával hajtatjuk végre (lásd a §17. akkor közvetlenül meg kell adnunk. az i-rõl pedig azt.1.128>& buf. ezt sokszor megtehetjük úgy. hogy a fordítóprogram az osztálysablonok paramétereit soha nem vezeti le (§C. . esetleg áttekinthetetlenné tenné. hogy Record.5). Ha egy paramétert nem lehet levezetni a sablon függvény paramétereibõl (§C. hogy az osztályok többféle konstruktora nyújtotta rugalmasság ezt sok esetben megakadályozná.p). int* p = create<int>(). // lookup() használata.doksi. sablonparamétere 'int' A közvetlen meghatározás (explicit specification) egyik szokásos használata a sablon függvény visszatérésiérték-típusának megadása: template<class T. U típusa int // T típusa char. U típusa double // T típusa char*. } // osztály. U típusa int. class U> T implicit_cast(U u) { return u. az explicit megadott sablonparaméterek közül is csak az utolsókat lehet elhagyni. implicit_cast<double>(i). } // hiba: T nem vezethetõ le // T típusa double. implicit_cast<char*. Ha egy levezett típusú objektumot kell létrehoznunk. hogy értéke128. implicit_cast<char.13..4. const char* p) { } return lookup(buf.double>(i).hu 442 Absztrakciós módszerek Record& f(Buffer<Record.13.. ahol T egy Record és i értéke 128 Itt T-rõl azt állapítja meg a fordítóprogram. // T létrehozása és rá hivatkozó mutató visszaadása void f() { vector<int> v.5).Forrás: http://www. sablonparamétere 'int' // függvény.2 pontbeli make_pair()-t). Ezt ugyanúgy tehetjük meg. Egy osztály különféle változatai közötti választásra a specializált változatok használata ad eszközt (§13. */ }. } void g(int i) { implicit_cast(i). template<class T> T* create().4). hiba: int // nem alakítható char*-ra Az alapértelmezett függvényparaméter-értékekhez hasonlóan (§7.

akkor lehetne-e valamilyen sablonparaméterrel alkalmazni. amelyik a paramétereknek a legjobban megfelel. . amelyek részt fognak venni a túlterhelés feloldásában. sõt ugyanolyan néven több közönséges függvény is. A túlterhelt (vagyis azonos névvel mást és mást jelentõ) függvények meghívásakor a megfelelõ meghívandó függvény vagy függvénysablon kiválasztásához a túlterhelés (overloading) feloldása szükséges: template<class T> T sqrt(T). formai követelményei megfelelnek az explicit minõsítésû sablon függvényekéinek.Forrás: http://www.0). A módszer alapvetõen a következõ: megkeressük minden sablonhoz azt a specializált változatot. amelyeket nem fejezhetünk ki más nyelvi elemmel.5).1.3. Ha két sablon függvény is meghívható lenne és az egyik specializáltabb a másiknál (§13. Ehhez az összes függvénysablont megvizsgáljuk. template<class T> complex<T> sqrt(complex<T>). ugyanúgy a sablon függvényekre alkalmazandó túlterhelés-feloldási szabályok is a függvényekre alkalmazandó túlterhelés-feloldási szabályok általánosításai. // sqrt(double) sqrt(z). // sqrt<int>(int) sqrt(2.2).2.6) explicit változatai. 2. A beépített típuskonverziós operátorok azonban olyan mûveleteket támogatnak. 13. például az implicit_cast() idõnként igen hasznosak lehetnek.2. Ezután ezekre a példányokra és az összes közönséges függvényre is a szokásos túlterhelés-feloldási szabályokat alkalmazzuk: 1.1).13. §C. Meg kell keresni azokat a specializált sablon függvény változatokat (§13.3. Az sqrt(z) hívás esetében például a következõ jelöltek adódnak: sqrt<double>(complex<double>) és sqrt<complex<double>>(complex<double>). hogy ha más ugyanilyen nevû függvény vagy sablon függvény nem lenne elérhetõ. Függvénysablonok túlterhelése Azonos néven több függvénysablon is szerepelhet.hu 13.13. void f(complex<double> z) { sqrt(2). double sqrt(double).doksi.2. A dynamic_cast.5. // sqrt<double>(complex<double>) } Ahogy a sablon függvény fogalma a függvény fogalmának általánosítása. static_cast stb. Az automatikus (implicit) konverziók (§C. Sablonok 443 A sablonparaméterek közvetlen megadása átalakító függvénycsaládok és objektum-létrehozó függvények definicióját teszi lehetõvé (§13. akkor a következõ lépésekben csak azt vesszük figyelembe. §C.

4. Emiatt a sqrt(2. és nem a sqrt<double>(double)-t. illetve szabványos vagy felhasználói konverziót. } inline double max(double d.9).0)-hoz a sqrt(double)-t választjuk. akkor a hívás többértelmû és ezért hibás: template<class T> T max(T.d2).3. 3.1).9) // max<int>(int(s). 5.4. akkor arra nem alkalmazhatunk kiterjesztést (promotion).T).i). const int s = 7. max(2. Az sqrt(2) hívás pontosan megfelel az sqrt<int>(int)-nek.7. int i) { return max<double>(d.d).1). így azt választjuk a sqrt(double) helyett.2).4).double(4)) Vagy megfelelõ deklarációk alkalmazásával: inline int max(int i. } inline double max(int i.2) // max<char>('a'. max('a'. max<double>(2.7. Ha nem találunk megfelelõ függvényt. max(s. double d) { return max<double>(i. } // max<int>(int('a'). Ha egy függvény és egy specializált változata ugyanolyan mértékben megfelelõ. Ha több ugyanolyan mértékben megfelelõ függvényt is találunk. Ha egy sablon függvény paraméterét a sablonparaméterekbõl vezettük le (§13. akkor a hívás hibás.7) (egyszerû konverzió) // hiba: többértelmû (nincs szabványos konverzió) // hiba: többértelmû (nincs szabványos konverzió) } A fenti példa két nem egyértelmû hívását explicit minõsítéssel oldhatjuk fel: void f() { max<int>('a'.1) // max<double>(2.7.1). // max<int>(1. void k() { max(1. megfelel sqrt<T>(T)-nek is.hu 444 Absztrakciós módszerek Az sqrt(z) hívás esetében az sqrt<double>(complex<double>)-t választjuk az sqrt<complex<double>>(complex<double>) helyett: minden hívás. max(2. double d2) { return max<double>(d1. akkor a függvényt választjuk. int j) { return max<int>(i. ami megfelel sqrt<T>(complex<T>)-nek.Forrás: http://www.4.j). Ezek után végezzük el a közönséges túlterhelés-feloldást ezen függvényekre és a közönséges függvényekre (§7.4). max('a'.).7.7).doksi. } inline double max(double d1.4.7.'b').'b') // max<double>(2. } .

f(B<int>*)-ként. de a sablon specializált használata könnyû és általánosan használható módja az ilyen túlterhelés-feloldó függvények írásának. max(2. szabványos átalakítás D<int>*-ról B<int>*-ra } Ebben a példában az f() sablon függvény minden T típusra elfogadja B<T>*-ot.1) // max(2. így explicit módon is írhattuk volna. // f<int>(pb) f(pd). és a helyben kifejtés (inline) biztosítja. template<class T> class D : public B<T> { /* .7. Sablonok 445 void g() { max('a'. így a fordítóprogram könnyen jut arra a következtetésre. Egy D<int>* típusú paraméterünk van.. D<int>* pd) { f(pb).. hogy a hívás nem jár külön „költséggel”. void g(B<int>* pb. így a szokásos átkonverziók megengedettek: template<class T..4). // az n-edik elem Ez a függvény feltételezhetõen a C típusú tároló n-edik elemét adja vissza. int n). template<class T> void f(B<T>*).doksi. }. az elsõ paraméterre nem alkalmazható konverzió.1).. */ }.4).hu 13. így a szokásos konverziók mindegyike tekintetbe vehetõ: class Index { public: operator int(). a második paraméter azonban teljesen közönséges.double(4)) Közönséges függvényekre a közönséges túlterhelés-feloldási szabályok érvényesek (§7.Forrás: http://www. pontosan úgy kezelhetjük. . amelyek nem vesznek részt a sablonparaméter levezetésében. hogy T-t int-nek véve a hívás egyértelmûen feloldható. // . class C> T get_nth(C& p. */ }. } // max(int('a'). mint egy nem sablon függvény paraméterét. A túlterhelés-feloldási szabályok biztosítják. Az olyan függvényparamétereket. hogy a sablon függvények helyesen mûködnek együtt az öröklõdéssel: template<class T> class B { /* . // f<int>(static_cast<B<int>*>(pd)).. A max() függvény igen egyszerû..7. Minthogy C-t a hívás aktuális paraméterébõl kell levezetni.

// pontos illeszkedés int i2 = get_nth<int>(v. Nem „betonozhatjuk be” a rendezési elvet a tárolóba.és nagybetûk közötti különbségtõl vagy figyelembe vegye a program nyelvi környezetét (locale. Milyen rendezési elvet alkalmazzunk. hogy a rendezõ eljárást ne csak egy adott típusra adhassuk meg. rendezõ stb. hanem adott típusra való adott alkalmazáskor is. const String<T>& str2) { for(int i=0. akkor ezt a C::eq() és a C::lt() függvények megfelelõ definiálásával teheti meg.length(). hogyan rendezhetjük a karakterláncokat. return str1. short s. // szabványos konverzió: short-ról int-re int i3 = get_nth<int>(v.str2[i])) return C::lt(str1[i].doksi. helyi sajátosságok). class C> int compare(const String<T>& str1.length()-str2. // felhasználói konverzió: Index-rõl int-re } 13. mert az általában nem szabhatja meg.i). Általánosítsuk például a C standard könyvtárának strcmp() függvényét tetszõleges T típusból álló String-ekre (§13.length() && i< str2. Eljárásmód megadása sablonparaméterekkel Gondoljuk meg. Index i) { int i1 = get_nth<int>(v. de az elemek típusába sem. Három dolog játszik szerepet: a karakterlánc. ezért bármely általános megoldás megköveteli.s). mert az elemeket sokféle módon rendezhetjük. Ezzel minden (összehasonlító. Ehelyett a megfelelõ mûvelet végrehajtásakor kell megadni az alkalmazandó feltételeket. az elemek típusa és a lánc elemeinek összehasonlításakor alkalmazott szempont.4. } Ha valaki azt szeretné. i<str1. mire van szüksége az elemek típusával kapcsolatban. sem egy általános rendezõ algoritmus nem tudhat a nevek rendezésének „svéd szokásairól”. Természetesen sem egy általános string típus.hu 446 Absztrakciós módszerek void f(vector<int>& v. i++) if (!C::eq(str1[i]. hogy a compare() eltekintsen a kis.length().2).) eljárást leírhatunk.2): template<class T.str2[i]) ? -1 : 1.Forrás: http://www. ha például svéd neveket tartalmazó karakterláncokat akarunk rendezni? A svéd nevek rendezése számára a karakterek két különbözõ numerikus megfeleltetési módja (collating sequence) használatos. ha az a tároló és a „C-mûveletek” nyelvén megfogalmazható: .

9[14]) }. Természetesen összehasonlító mûveleteket nemcsak a beépített. Sablonok 447 template<class T> class Cmp { // szokásos. class Literate { // svéd nevek összehasonlítása public: static int eq(char a.4). hogy általános algoritmusokat olyan típusokra alkalmazhassunk. Túlterheléssel szerencsére könnyen megadhatunk olyan alapértelmezést. Minden osztálysablonból létrehozott osztály saját példányokat kap a sablon statikus változóiból (§C. például a függvénymutatók alkalmazásához képest.13.swede2). a futási idõ növekedése nélkül. Másrészt. Egyrészt több mûvelet megadható egyetlen paraméterként.4. T b) { return a==b. } static int lt(char. alapértelmezett összehasonlítás public: static int eq(T a. A sablonparaméterek megadásakor most már pontosan megadhatjuk az összehasonlítási szabályokat: void f(String<char> swede1.Forrás: http://www.swede2). az eq() és az lt() összehasonlító mûveleteket könnyû helyben kifejtve (inline) fordítani. } Az összehasonlító mûveletek sablonparaméterként való megadásának két jelentõs elõnye van az egyéb lehetõségekhez. hanem a felhasználói típusokra is megadhatunk. míg egy függvénymutatón keresztüli hívás ilyen módú fordítása különleges mértékû figyelmet követel a fordítóprogramtól.doksi. compare< char. hogy csak a szokásostól eltérõ összehasonlítási szempontot kelljen megadni: . Ez alapvetõ fontosságú feltétele annak. // kikeresés táblázatból karakterérték alapján (§13. } }. String<char> swede2) { compare< char. amelyeknek nem maguktól értetõdõ összehasonlítási feltételeik vannak (§18. Alapértelmezett sablonparaméterek Fáradságos dolog minden egyes hívásnál közvetlenül meghatározni az összehasonlítási feltételeket. } static int lt(T a.1.char). T b) { return a<b.hu 13. char b) { return a==b. 13.Cmp<char> >(swede1.Literate >(swede1.1).

s2). compare<char.length() && i< str2. Az eljárásmódot kifejezõ sablonparamétereket gyakran nevezik „jellemvonásoknak” (traits) is. class C> int compare(const String<T>& str1. */ }. } Így már leírhatjuk a következõt: void f(String<char> swede1. void f(String<char> s1.str2[i]) ? -1 : 1.length(). // összehasonlítás C // használatával // összehasonlítás // Cmp<T> használatával De a szokásos rendezést megadhatjuk.1). return str1. . const String<T>& str2).2). compare<char. class C = Cmp<T> > int compare(const String<T>& str1.2. String<char> swede2) { compare(swede1. a standard könyvtárbeli tárolók pedig a memóriafoglalókét (allokátor. 20.length().és nagybetûk közötti különbséget figyelembe vevõ.. String<char> s2) { compare(s1.doksi.. template<class T> int compare(const String<T>& str1. hogy az alkalmazandó eljárásmódot (policy) egy sablonparaméter adja meg. illetve elhanyagoló rendezés: class No_case { /* . és ennek a legáltalánosabb eljárásmód az alapértelmezett értéke (például §18.swede2). const String<T>& str2).Forrás: http://www.s2).hu 448 Absztrakciós módszerek template<class T. const String<T>& str2) { for(int i=0. i<str1.str2[i])) return C::lt(str1[i].) használják fel.swede2). } // Cmp<char> használata // Literate használata Egy (nem svédek számára) kevésbé ezoterikus példa a kis. §19.4). fejezet) összehasonlításaira ez nem áll. Például a standard könyvtárbeli string a char_traits-re épül (§20. Eléggé furcsa módon azonban a basic_string (§13. mint alapértelmezett sablonparaméter-értéket is: template<class T.length()-str2. a szabványos algoritmusok a bejárók (iterátorok) jellemvonásait (§19. i++) if (!C::eq(str1[i].2.2. } // kisbetû-nagybetû különbözik // kisbetû-nagybetû nem különbözik A standard könyvtár széles körben alkalmazza azt a módszert.Literate>(swede1.No_case>(s1.4.

Sablonok 449 Egy alapértelmezett sablonparaméter értelmi ellenõrzése ott és csak akkor történik meg. Specializáció Alapértelmezés szerint egy sablon (template) egyetlen definíciót ad a felhasználható által elképzelhetõ összes paraméterérték (vagy paraméterértékek) számára. Vegyük egy Vector sablon valószínû felhasználásait: template<class T> class Vector { T* v. egyedi célú felhasználói változatok használata. int sz. Vector(int).. void swap(Vector&). ahol és amikor az alapértelmezett paramétert ténylegesen felhasználjuk. Vector<char*> vpc. hogy olyasmit szeretnénk kifejezni. hogy a sablonnak többféle definíciót adunk és a fordítóprogram az alkalmazott paramétertípusok szerint választ közülük. használd ezt. vagy hogy „hiba.4). Elõfordulhat.doksi. szakosítás) hívjuk. Vector<Shape*> vps. public: Vector(). // . Sok hasonló tervezési szempontot figyelembe lehet úgy venni. Ez azonban nem minden sablon írásakor kedvezõ. A sablon ilyenféle többszörös meghatározását specializációnak (specialization. hiszen ezek sablonparamétert használnak az alapértelmezett értékek megadására (§16. Így ha nem használjuk fel az alapértelmezett Cmp<T> paramétert.hu 13. használd azt”. hogy „ha a sablonparaméter egy mutató. 13.3. } T& operator[ ](int i). ha nem. Ez döntõ jelentõségû a szabványos tárolók tervezésénél. T& elem(int i) { return v[i].5. ha a sablonparaméter nem a My_base osztály egy leszármazottjára hivatkozó mutató”. Vector<int> vi. Vector<Node*> vpn. Vector<string> vs. mert mondjuk az X-re a < nem értelmezett.Forrás: http://www. . // általános vektortípus }. amelyekre a fordító nem fordítaná le aCmp<X>-et.. akkor olyan X típusokra is használhatjuk a compare()-t.

}. mert a többalakú (polymorph) viselkedés megõrzése céljából mutatókat kell használnunk (§2.doksi. Elõször definiáljuk a Vectornak a void mutatókra vonatkozó változatát („specializációját”): template<> class Vector<void*> { void** p.Forrás: http://www. az biztosan számos mutatót tartalmazó tárolót fog használni. Szerencsére létezik egyszerû megoldás. de kritikus esetekben (mint az iménti Vector-nál) a kód „felfúvódásával” jár. Ezt a változatot aztán az összes.4. a név utáni <> jelpár között adjuk meg: vagyis a <void*> azt jelenti. Vector() : Base() {} explicit Vector(int i) : Base(i) {} .2. Azt. hogy ezt a definíciót kell minden olyan Vector esetében használni. §12. ami mutatókat tartalmazó Vector-ok. a Vector<void*>-ot a következõ módon deklarált Vector-ok számára használjuk: Vector<void*> vpv. A template<> elõtag azt jelenti. A mutatókat tartalmazó tárolóknak elég egyetlen megvalósítás. void*& operator[ ](int i). A legtöbb C++-változat alapértelmezés szerint lemásolja a sablon függvények kódját.. mutatót tartalmazó vektor közös megvalósításaként használhatjuk. Ezért aki objektumorientált programozást folytat és típusbiztos.hu 450 Absztrakciós módszerek A legtöbb Vector valamilyen mutatótípus Vector-a lesz. de fõleg azért. // . amit meg kellene adni vagy le kellene vezetni. Ez jó a végrehajtási sebesség szempontjából.. és csak azok esetén használandó. azaz ezen változat használatakor nincs sablonparaméter. Ha olyan változatot akarunk megadni. például standard könyvtárbeli tárolókat használ. A Vector<void*> egy teljes specializáció.5. hogy ennél a specializált változatnál nem kell sablonparamétert megadnunk. Több okból is.6). Ezt specializált változat készítésével érhetjük el. részleges specializációra van szükségünk: template<class T> class Vector<T*> : private Vector<void*> { public: typedef Vector<void*> Base. amelyikre a T típusa void*. hogy milyen típusú sablonparaméterre használjuk.

. Fontos. hogy a Vector ezen finomítása a felhasználói felület megváltoztatása nélkül történt. Ez a módszer a gyakorlatban a kód felfúvódásának megakadályozásában volt sikeres. Természetesen az általános Vector-t és a mutatókra vonatkozó változatot hívhattuk volna különbözõképpen is.. Az általános sablont az összes specializált változat elõtt kell megadni: template<class T> class List<T*> { /* . így T is Shape // <T*> most <int**>. hogy a közös kódot a lehetõ legjobban növeljük. } }. } T*& operator[ ](int i) { return reinterpret_cast<T*&>(Base::operator[ ](i)).. hogyan lehet a kódfelfúvódást megakadályozni úgy.hu 13. Ebben az esetben sokkal jobb a fontos részleteket egy közös felület mögé rejteni. template<class T> class List { /* . hogy részleges specializáció használata esetén a sablonparaméter a specializációra használt mintából adódik. a sablonparaméter nem egyszerûen az aktuális sablonparaméter. A Vector<T*> osztály egyszerûen egy felület a void*-os változathoz. A vektormûveletek különbözõ változatainak lefordításához szükséges idõ megtakarításával ez a módszer a fordítási és szerkesztési idõt is drámai módon csökkenti. // hiba: általános sablon a specializált után . Amikor ezt kipróbáltam. akinek tudnia kellett volna róla. Sablonok 451 T*& elem(int i) { return reinterpret_cast<T*&>(Base::elem(i)). */ }. Így például a Vector<Shape*> esetében T típusa Shape és nem Shape*. akár egyéb típus-paraméterezési lehetõségeket tartalmazó nyelvekben). Akik nem alkalmaznak ilyen módszereket (akár a C++-ban.Forrás: http://www. mégsem a mutatós változatot használta és a kapott kód a vártnál sokkal nagyobb lett. Ha adott a Vector ezen részlegesen specializált változata. azaz minden olyan sablonparaméternél. így T is int* Jegyezzük meg.. Az összes mutatót tartalmazó lista egyetlen specializált változattal való megvalósítása jó példa arra.. hogy ezt a változatot kell minden mutatótípus esetében használni.. könnyen azt vehetik észre. A specializáció a közös felület többféle meghatározásának eszköze. */ }. hogy sok felhasználó. // . kiderült. A név utáni <T*> specializáló minta azt jelzi. hogy az ismétlõdõ kód közepes méretû programok esetében is megabájtokra rúghat. // <T*> most <Shape*>. akkor ez az összes mutatótípusra vonatkozó Vector közös megvalósítása. Vector<int**> vppi.doksi. ami T* alakba írható: Vector<Shape*> vps. melyet kizárólag az öröklõdés és a helyben kifejtés eszközével valósítottuk meg.

7). Ezért elég az általános sablont a specializált változat deklarációja vagy definiciója elõtt megadni: template<class T> class List.3. List<int*> li. Egy sablon minden specializált változatát ugyanabban a névtérben kell megadni. Ha valahol szerepel egy felhasználói specializált változat. hogy számára a fordító nem hoz létre definíciót. 13. template<class T> class List<T*> { /* . illeszkedik a másikra is. illetve a túlterhelés feloldásakor (§13. amely illeszkedik az egyikre. template<> class Vector<void*>..Forrás: http://www.doksi. ha minden olyan paraméterlista. Specializációk sorrendje Az egyik változat specializáltabb egy másiknál. template<class T> class List<T*> { /* . hogy legyen: template<class T> class List { /* .. Ez fordítva nem áll fenn: template<class T> class Vector. */ }. Ha használják. . // hiba Itt a List-et az int*-ra a List<int*> használata után specializálunk.. akkor egy explicit deklarált (azaz nem egy általánosabból létrehozott) sablonnak valahol szintén explicit definiáltnak kell lennie (§13. hogy milyen paramétereket kell a felhasználás vagy a specializáció során megadni.5. */ }. // általános // mutatókhoz // void*-okhoz Minden típust használhatunk a legáltalánosabb Vector paramétereként. */ }.. mint magát a sablont.1. Az objektumok és mutatók stb.2) a leginkább specializált változat részesül elõnyben.5) deklarációjában.hu 452 Absztrakciós módszerek Az általános sablon által adott létfontosságú információ az.7). (§13. a Vector<void*> paramétereként pedig csak void* mutatót.. akkor az általános sablont valahol definiálnunk kell (§13. akkor annak deklarációja a specializált használat minden helyérõl elérhetõ kell.. Ha használjuk is. Vagyis a specializált változat explicit megadásából következik. template<class T> class Vector<T*>. de a Vector<T*> paramétereként csak mutatót.

b)<0. Vegyük a §7. A sablon függvény neve utáni <const char*> azt jelenti. const char* b) { return strcmp(a. Erre a less()-nek egy egyszerû. azaz az elsõ karakter címét fogja az összehasonlítás alapjául venni.1) használt típusokból összeállított típusok használhatók fel. de lehetõséget nyújt a megvalósítás javítására.Forrás: http://www. nem kell explicit megadnunk.5. Sablonok 453 A specializáló minta megadásánál a sablonparaméterek levezetésénél (§13.v[j+gap]). i<n. for (int gap=n/2. mert két char*-ot a < segítségével hasonlít össze. } Mint az osztályoknál is (§13. a <> sablon-elõtag azt jelzi. hogy ez egy sablonparaméter megadása nélküli specializált változat. gap/=2) for (int i=gap. j-=gap) if (less(v[j+gap].2.3 példabeli Shell rendezést.5).v[j])) swap(v[j]. Minthogy a sablonparaméter közvetlenül levezethetõ a függvény paraméterlistájából. Ehelyett a mutatott karakterek szerinti összehasonlítást szeretnénk.doksi. } .3. } template<class T> void sort(Vector<T>& v) { const size_t n = v. T b) { return a<b. amikor a sablonparaméter const char*. const char* b) { return strcmp(a. Függvénysablon specializáció A specializáció természetesen a sablon függvényeknél (template function) is hasznos. } Ez nem javítja magát az algoritmust. 0<gap. const char*-ra vonatkozó specializációja fog ügyelni: template<> bool less<const char*>(const char* a. Eredeti formájában egy Vector<char*>-ot nem rendez jól. Így egyszerûsíthetünk a specializáció megadásán: template<> bool less<>(const char* a.size(). Ez az elemeket a < segítségével hasonlítja össze és a részletezett kód segítségével cseréli fel. 0<=j. Jobb definíció lenne a következõ: template<class T> bool less(T a.b)<0.hu 13. hogy a függvényt azokban az esetekben kell alkalmazni. 13. i++) for (int j=i-gap.7 és §13.

hu 454 Absztrakciós módszerek Minthogy adott a template<> elõtag.sz). // y másolása x-be y = t.5): template<class T> void Vector<T>::swap(Vector & a) { swap(v.swap(b).v). §13. a második. T& y) { T t = x. Ezek a „szabálytalan” típusok többnyire a beépített mutató.9. } // ábrázolások cseréje A swap() tagot felhasználhatjuk az általános swap() specializált definiciójában: template<class T> void swap(Vector<T>& a.a.16) is. adjunk meg egy swap() függvényt a Vector osztály számára (§13. Így aztán általában így írnánk: template<> bool less(const char* a.3. üres <> felesleges. ha Vector-ok vektoraira hívjuk meg.b)<0. Vegyük a swap() kézenfekvõ meghatározását: template<class T> void swap(T& x.12. hiszen nem hordoz új információt. Ráadásul ezek a példák széles körben használt módszereket mutatnak be. §20. De ezt is megoldhatjuk megfelelõ specializációval. A specializáció akkor hasznos. ha a paramétertípus valamilyen szabálytalansága miatt a szabványos algoritmus valamilyen nem kívánt módon mûködne (mint a less() esetében). const char* b) { return strcmp(a.és tömbtípusok. Vector<T>& b) { a. } Én jobban kedvelem ezt a rövidebb deklarációs formát.3.doksi. hogy az elemeihez való közvetett hozzáférést támogassa (mint a string §11. // x másolása az ideiglenes változóba x = y. // ideiglenes változó másolása y-ba } Ez nem túl hatékony. Maga a Vector objektum csak annyi adatot tartalmaz. ha a sablonparaméterek egy adott halmazára egy általános algoritmusnál létezik hatékonyabb megvalósítás (itt a swap()). Így aztán a cserét a megfelelõ ábrázoló adatok cseréjével lehet megoldani. Ezenkívül akkor is hasznos. swap(sz. Hogy lehetõvé tegyük az ábrázoló adatok kezelését. az összes elem másolásával cseréli fel a Vector-okat.Forrás: http://www.2).a. } A less() és a swap() ezen változatait használja a standard könyvtár (§16. .

A sablon függvények túlterhelés-feloldási szabályai biztosítják a függvények helyes mûködését az ilyen származtatott típusokra (§13. Öröklõdés és sablonok Az öröklõdés és a sablonok olyan eszközök. . hogy további osztályok számára építõkõül szolgál.8.5 és §13. amelyekkel meglevõk alapján új típusok építhetõk. */ }. Egy sablon osztálynak (template class) egy nem sablon osztályból való származtatása módot nyújt arra.7. és amelyekkel általánosságban a közös vonások különféle kihasználásával hasznos kód írható. } }. A bázisosztály egyik haszna az.Forrás: http://www. hogy sablonok egy halmaza közös megvalósításon osztozzék.1. */ }. Ha a bázisosztály adatai vagy mûveletei valamely származtatott osztály sablonparaméterétõl függenek. // . Érdekes.és a származtatott osztályban is. template<class T> class Vec : public vector<T> { /* . §3.5 pontbeli vektor jó példa erre: template<class T> class List<T*> : private List<void*> { /* .doksi.. A §13. Természetesen a sablon osztályok közötti öröklõdés is hasznos. // minden elem összehasonlítása bool operator!=(const C&) const.2). Másként nézve a példa azt mutatja. bár ritkábban alkalmazott módszerek alapulnak azon. */ }. akkor magának a bázisosztálynak is paramétereket kell adni (lásd például a §3. amikor ugyanaz a sablonparaméter szerepel a bázis. Mint a §3... // hozzáférés biztosítása C mûveleteihez const C& derived() const { return static_cast<const C&>(*this).6. ezen eszközök párosítása sok hasznos módszer alapja.. Az az eset a leggyakoribb. hogy a sablon elegáns és típusbiztos felületet ad egy másként nem biztonságosan és nem elegánsan használható eszközhöz. Sablonok 455 13.hu 13..3. hogy magát a származtatott osztályt adjuk át a bázisosztálynak: template <class C> class Basic_ops { // tárolók alapmûveletei public: bool operator==(const C&) const.2 pontbeli Vec osztályt): template<class T> class vector { /* ..7..5 pontokban láttuk.. de ez nem szükségszerû.

. Mcontainer< double. public: T& operator[ ](size_t i) { return elements[i]. friend bool operator!=(const Mcontainer&.. akkor a Basic_ops egy tagja valahogy így nézhet ki: template <class C> bool Basic_ops<C>::operator==(const C& a) const { if (derived(). } A tárolók és a mûveletek elválasztására szolgáló másik módszer öröklõdés helyett sablonparaméterekkel kapcsolja össze azokat: template<class T. mint az == és a != mind a tárolónak. . Ám mivel az olyan mûveletek definíciójához. } . i<derived(). egyszeri meghatározása.My_array<double> > mc. class C> class Mcontainer { C elements. // . a bázisosztályt át kell adni a tárolósablonnak. // elemek // összehasonlítása }.Forrás: http://www... // .size()) return false.size() != a.hu 456 Absztrakciós módszerek template<class T> class Math_container : public Basic_ops< Math_container<T> > { public: size_t size() const.size(). Ha feltesszük. // .doksi.. const Mcontainer&). return true. for (int i = 0. Ezáltal lehetõvé válik a tárolók alapvetõ mûveleteinek az egyes tárolóktól elkülönített... hogy a Math_container egy hagyományos vektorra hasonlít. T& operator[ ](size_t). mind annak elemeinek típusára szükség van. const T& operator[ ](size_t) const. template<class T> class My_array { /* . friend bool operator==(const Mcontainer&. */ }.. }. ++i) if (derived()[i] != a[i]) return false.. const Mcontainer&).

különféle megvalósításaik kódrészeit az osztályhierarchiák közösen használhatják. Paraméterezés és öröklõdés A sablonok egy típust vagy függvényt egy másik típussal paramétereznek. A tervezés szempontjából a két megközelítés annyira közeli. Ha nem szükséges egymással alá.. és az absztrakt osztályt használó kód legnagyobb része nem is függ a megvalósítástól. im. Ha a futási idõ nagyon fontos. Végül is mikor válasszunk absztrakt osztályt és mikor alkalmazzunk sablont? Mindkét esetben olyan objektumokat kezelünk. Ha az objektumok aktuális típusa a fordítási idõben nem ismert. Az absztrakt osztályok felületeket írnak le. amelyek maguk is sablonok: template<class Scalar> class complex { Scalar re. im(c. A sablon kódja minden paramétertípus esetében azonos. Tag sablonok Egy osztálynak vagy osztálysablonnak lehetnek olyan tagjai is.2).6. Sablonok 457 Egy sablonból létrehozott osztály teljesen közönséges osztály.3. amelyeknek azonos mûveleteik vannak. Errõl részletesebben a §24. néha mindkettõt többalakúságnak (polimorfizmus.2.6. Ilyen esetekben meg lehetne fontolni azt is. Minthogy mindkettõ azt teszi lehetõvé.1. polymorphism) hívják. hogy a C paraméterként tároló helyett sablont használjunk. public: template<class T> complex(const complex<T>& c) : re(c. akkor legyenek sablonparaméterek. azaz a mûveletek helyben kifejtve történõ fordíthatósága alapvetõ szempont.és fölérendeltségi viszonyban állniuk. }. hogy a == és a != számára a két paraméter szokásos felcserélhetõségét biztosítsam (§11. mint a sablont használó legtöbb kódrész. hogy egy algoritmust egyszer írjunk le és aztán különféle típusokra alkalmazzuk. hogy közös nevet érdemel.1 pont ír.2) is.real())..imag()) { } // . a virtuális függvények által biztosítottat futási idejû (run-time) többalakúságnak.Forrás: http://www. . Hogy megkülönböztessük õket.4.doksi. csakúgy.13. használjunk sablont. 13. a sablonok által nyújtottat pedig fordítási idejû (compile-time) többalakúságnak vagy paraméteres (parametric) többalakúságnak hívják.hu 13. Ebben a példában azért használtam barátokat. ezért lehetnek barát (friend) függvényei (§C. 13. akkor legjobban egy közös absztrakt osztályból öröklõdõ osztályként ábrázolhatjuk azokat.

A teljesség kedvéért alapértelmezett és másoló konstruktort is megadtam. complex<double> cd = cf..3.. ami ésszerû megszorításnak tûnik. Ha megengedett lenne. Sajnos azonban a C++ nyelv elfogad a beépített típusok közötti bizonyos ésszerûtlen konverziókat is. complex<int> ci = cq.2. // hiba: virtuális sablon Ez szabálytalan. template<class T> virtual bool intersect(const T&) const =0. implicit_cast (§13.Forrás: http://www. public: complex() : re(0). A szerkesztõnek az intersect() függvény minden új paramétertípussal történõ meghívása esetén egy újabb elemmel kellene bõvítenie a Shape osztály virtuálisfüggvény-tábláját..6. im. ami ebben az esetben azonos lett volna az általam megadottal.5. ha T1-nek kezdõértékül adhatjuk T2-t.real())).hu 458 Absztrakciós módszerek complex<float> cf(0. im(0) { } complex(const complex<Scalar>& c) : re(c.0). // rendben: float-ról double-ra alakítás használata // nincs átalakítás int-re // hiba: nincs átalakítás Quad-ról int-re Vagyis kizárólag akkor tudunk complex<T2>-bõl complex<T1>-et építeni. .real()).2) stílusú ellenõrzött konverzióval: template<class Scalar> class complex { Scalar re.doksi. így közvetlenül deklarált másoló konstruktor híján a fordító alapértelmezett konstruktort hozott volna létre. például double-ról int-re.imag())) { } // .imag()) { } template<class T2> complex(const complex<T2>& c) : re(checked_cast<Scalar>(c. im(checked_cast<Scalar>(c. }.. Elég különös módon egy sablon konstruktorból a fordító soha nem hoz létre másoló konstruktort. class Quad { }. }.5) nem lenne alkalmazható. A végrehajtási idõben a csonkításból eredõ hibákat el lehetne kapni. akkor a virtuális függvények megvalósításának hagyományos virtuálisfüggvény-táblás módja (§2. Egy sablon tag nem lehet virtuális: class Shape { // . im(c.6.1) vagy checked (§C. complex<Quad> cq.

. Öröklõdési viszonyok Általában úgy gondolnunk egy sablonra. . ami hibás érvelésen nyugvó súlyos logikai hiba: az „egy Circle egyben Shape is. hogy egy set<Circle*> csak Circle*-okat tartalmaz..hu 13. akkor ez már nem lenne biztosított. Az a garancia.3. hogy a Circle-ök halmaza kizárólag Circle típusú objektumokat tartalmaz. A C++ nyelv szabályai szerint két. // . void f(set<Shape*>& s) { // . ha a set<Shape*> egy set<Circle*> lehetne. */ }. lehetõvé teszi. Sablonok 459 13. tehát a Circle-ök halmazát Shape-ek halmazaként is kezelhetem” érvelés „tehát” pontja hibás... // típushiba: s típusa set<Circle*>. Más szóval a sablon egy olyan eszköz. } void g(set<Circle*>& s) { f(s). nem set<Shape*> } A fenti példát a fordítóprogram nem fogja lefordítani. Nagyon helyesen. hogy a set<Circle*> elemei Circle-ök.. mint új típusok létrehozását segítõ általánosításra. akkor nem lenne többé igaz. azonos osztálysablonból létrehozott osztály nem áll rokonságban egymással: class Shape { /* . */ }. például a sugár lekérdezését.. Ilyenkor egyesek megpróbálják a set<Circle*>-ot set<Shape*>-ként kezelni.insert(new Triangle()). Ezért az osztálysablonokat néha típuskészítõknek vagy típusgenerátoroknak hívják.Forrás: http://www. így a Circle-ök halmaza egyben Shape-ek halmaza is. Például az f() függvény egy Triangle* elemet tesz set<Shape*> paraméterébe. hogy az elemekre biztonságosan és hatékonyan végezzünk Circle-ökre jellemzõ mûveleteket.. s. mert nincs beépített konverzió set<Circle*>&-rõl set<Shape*>&-re. Oka. amely szükség esetén a felhasználó elõírásai szerinti típusokat hoz létre.... */ }. class Circle : public Shape { /* .6. Ha megengednénk a set<Circle*>-öknek set<Shape*>-ként való kezelését.doksi. míg a Shape-ek halmaza esetében ez egyáltalán nem biztos: class Triangle : public Shape { /* .

Ptr<Circle> pc2 = ps.Forrás: http://www. ha p (ami T* típusú) a Ptr<T2>(T2*) konstruktor paramétere lehet. a Ptr<T>rõl Ptr<T2>-re történõ konverzió mûködni fog: void f(Ptr<Circle> pc) { Ptr<Shape> ps = pc. Ezért ha a T* automatikusan T2*-ra konvertálható.hu 460 Absztrakciós módszerek 13. template<class T2> operator Ptr<T2> ().1. hogy Ptr-jeinkre fennálljanak a beépített mutatóknál megszokott öröklõdési kapcsolatok: void f(Ptr<Circle> pc) { Ptr<Shape> ps = pc.3.. hogy a Ptr<T>-rõl Ptr<T2>-re történõ konverzió csak akkor legyen engedélyezett. miért nem lehet semmilyen alapértelmezett kapcsolat két. hogy az elsõ kezdõérték-adás csak akkor legyen engedélyezett. Ptr<Circle> pc2 = ps. } // mûködnie kell // elvileg hibát eredményez Azt szeretnénk.6. } A return utasítást a fordító csak akkor fogadja el. public: Ptr(T*).6. Tag sablonok (§13. Sablonok konverziója Az elõzõ példa azt mutatta be. szeretnénk tükrözni a mutatott objektumok közötti öröklõdési viszonyokat. } // rendben: Circle* átalakítható Shape*-ra // hiba: Shape* nem alakítható Circle*-gá . Ezt így tehetjük meg: template<class T> template<class T2> Ptr<T>::operator Ptr<T2> () { return Ptr<T2>(p). Egyes osztályoknál azonban mégiscsak szeretnénk ilyen kapcsolatot kifejezni.. // . ha egy T2* típusú mutató értékül kaphat egy T* típusú mutatót.doksi. azonos osztálysablonból létrehozott osztály között. Például ha egy mutató sablont készítünk.2) igény esetén sokféle hasonló kapcsolatot kifejezhetnek: template<class T> class Ptr { T* p. Általában úgy szeretnénk a konverziós operátorokat megadni. }. // mutató T-re // Ptr<T> konverziója Ptr<T2>-re Ezután szeretnénk a konverziós operátorokat úgy definiálni. ha a Shape valóban közvetett vagy közvetlen nyilvános bázisosztálya a Circle-nek.

Hogy lássuk a kétféle megközelítés közötti különbséget. valahányszor szükségünk van az out()-ra: // user1. class T2> // hiba Ptr<T>::operator Ptr<T2> () { return Ptr<T2>(p).hu 13. .c" // out() használata Vagyis az out()-ot és a hozzá szükséges valamennyi deklarációt több különbözõ fordítási egységbe is beépítjük. illetve annak szintén sablon tagjának sablonparaméter-listáit egyesíteni: template<class T. vegyünk egy egyszerû példát: #include<iostream> template<class T> void out(const T& t) { std::cerr << t. A használat elõtt a sablonoknak csak a deklarációját emeljük be. hogy csak akkor hozzon létre kódot. mielõtt használnánk azokat.doksi. Jegyezzük meg.7. ha szükséges. hogy nem lehetséges egy sablonnak. 2. } Hívjuk ezt a fájlt out. Sablonok 461 Legyünk óvatosak és csak logikailag értelmes konverziókat definiáljunk. } 13.c" // out() használata // user2. ami azt is jelenti. hogy a sablon függvényeket ugyanúgy kezeli.Forrás: http://www. és hogy a felesleges információk feldolgozását optimalizálja.c: #include "out. majd használjuk és csak végül definiáljuk. A fordítóprogram dolga.c: #include "out. Ezenkívül lehetséges. A sablonok definícióját beépítjük (#include) egy fordítási egységbe. hogy a sablonfüggvényeket egy fordítási egységen belül elõször csak deklaráljuk. A forráskód szerkezete A sablonokat használó kód szerkezete alapvetõen kétféle lehet: 1. definiciójukat külön fordítjuk. mint a helyben kifejtett (inline) függvényeket.c-nek és építsük be.

h" // out() használata Ezen a módon a sablon függvényeket a nem helyben kifejtett függvényekhez hasonlóan kezeljük. Jegyezzük meg. Egy másik gond. E gondolatmenet logikus folyománya a külön fordítás: ha a sablon nem épül be a felhasználói kódba. Ezt a veszélyt névterek használatával. Az out. (Ez úgy történik. azok az elemek.c: #include "out. hogy minden olyan információ.c: #include<iostream> #include "out. // out.h: template<class T> void out(const T& t). A felhasználó csak a deklarációt (vagyis a felületet) építi be: // user1. és ilyen módon megnõ a fordítóprogram által feldolgozandó adat mennyisége. hogy a definícióhoz vagy egy megelõzõ deklarációhoz hozzáadjuk az export szót. hogy a felhasználók esetleg véletlenül „rákapnak” az eredetileg csak az out() definiciójához szükséges deklarációk használatára. melyektõl függ. nem is befolyásolhatják azt. hogy a sablon definíciója csak akkor érhetõ el más fordítási egységbõl. Így az eredeti out. és az adott fordító dolga szükség esetén az out() leírásának megkeresése. hiszen a sablon-meghatározás fölös példányainak kiszûrése helyett szükség esetén meg kell találnia az egyetlen definíciót. a definíciónak minden használat helyérõl elérhetõnek kell lennie.c: #include "out.Forrás: http://www.h" export template<class T> void out(const T& t) { std::cerr << t. Ez némi terhet ró a fordítóra.c fájl most az out() definiálásához szükséges összes információt tartalmazza.c állományt két részre bontjuk: // out. ha kifejezetten export-ként adjuk meg (§9.h csak a meghívásához szükségeset.hu 462 Absztrakciós módszerek Ezzel az a nyilvánvaló gond.c-beli definíciót külön fordítjuk.doksi. a makrók használatának elkerülésével és általában a beépítendõ információ mennyiségének csökkentésével háríthatjuk el.h" // out() használata // user2. az out()-ot felhasználó valamennyi fordítási egységbe belekerül.3). .2.) Ha nem így teszünk. amelyre az out()-nak szüksége van. az out. } Az out.

[2] A tárolókat sablonként készítsük el. 13. Ebbõl következõen ilyen tagokat lehetõleg ne használjunk olyan sablonoknál. [3] A tárolókat specializáljuk mutatótípusokra.13. mert ezek nem kívánatos mellékhatásokkal járhatnak a sablon felhasználásának helyén. Sablonok 463 A fordító. Általában a helyben kifejtett függvényeket és az egyéb. akár egyetlen egységben szerepel. §13.1) csak egyetlen definiciója lehet. a fejlesztett program fajtájától. hogy csökkentsük a kód méretét.Forrás: http://www. Ha a definíciót beépítjük. [4] A specializáció elõtt mindig adjuk meg a sablon általános formáját.2. alapvetõen más sablon függvényeket hívó függvényeket érdemes minden olyan fordítási egységben elhelyezni. . hogy melyik módszer vagy azok milyen párosítása a legjobb. illetve a fejlesztés külsõ feltételeitõl függ. Bármelyik megközelítést válasszuk is. hogy a környezetbõl minél többet átviszünk a példányosítás folyamatába. hogy csökkentjük a definíció függését a környezetétõl. a sablon-példányosítás terén átlagos támogatást nyújtó szerkesztõprogram esetében ez meggyorsítja a fordítást és pontosabb hibaüzenetekhez vezet. ahol felhasználják azokat. hogy a kód ugyanúgy mûködjék.5.8.5. §13. akár külön fordított egységekbe elosztva. ha a sablondefiníciókat külön fordítjuk. Fontos cél. Ezt inkább úgy érhetjük el. amelyek sokféle paramétertípusra alkalmazhatók. valamelyik fordítási egységben. §13. Ezért a nagyobb vagy bonyolultabb függéseket igénylõ sablonokat jobb külön fordíttatni.és szerkesztõprogramtól.doksi. amelyek sok fordítási egységben szerepelnek. a felhasználói kódban pedig csak deklarációjukat szerepeltetjük.hu 13. a sablonok külön fordítása pedig egyes nyelvi változatok esetében költséges mulatság lehet. sebezhetõvé tesszük azt.3. Én azt tekintem ideális megoldásnak. nem pedig úgy. ha a sablon definiciója sok deklarációt igényel. a nem helyben kifejtett statikus tagoknak (§C. Tanácsok [1] Olyan algoritmusok leírására. Ezen elvek alkalmazását azonban mindig az adott helyzethez kell igazítanunk. mert így értelmét a beépítés helyén érvényes makrók és deklarációk befolyásolhatják. Egy. sablont használjunk. §13. de akkor is ez az eljárás követendõ.

6.5.4.7. §13.5. [16] Szükség esetén egy constraint() függvény segítségével korlátozzuk. . §13. [19] Ha fontos szempont. amelyeknek a korábbi változatokkal összeegyeztethetõnek kell maradniuk.13.6.1. §13. §13.10. [13] Ne felejtsük el kitenni az export kulcsszót azoknál a definícióknál. [9] Paraméterezzünk eljárásmód objektummal. amelyeket más fordítási egységbõl is el kell érni. öröklõdés helyett használjunk sablonokat.6. §13.5. §13.Forrás: http://www. hogy a sablonnak nincs-e szüksége C stílusú karakterláncokra és tömbökre vonatkozó specializációkra.1.1. §13.6. §13.doksi. [7] Definiáljunk minden deklarált specializációt. [11] Egyszerû esetekre vonatkozóan egyszerû felületet adjunk.10.2. [15] Konverziókat sablonokkal fejezzünk ki.6. öröklõdés helyett használjunk sablonokat. §13. §13.7. [17] A fordítási és összeszerkesztési idõvel való takarékoskodás céljából explicit példányosítást használjunk. §13. §13.13.5.1. [14] A nagy vagy nem magától értetõdõ környezeti függõségû sablonokat külön fordítási egységben helyezzük el. mielõtt használnánk.5. §C. hogy új változatokat újrafordítás nélkül vezethessünk be. §C. §13.8. §13.hu 464 Absztrakciós módszerek [5] A specializációt deklaráljuk. végezzünk hibakeresést egy konkrét példán. sablonok helyett használjunk öröklõdést. öröklõdés helyett használjunk sablonokat. §13.1. §C. [8] Gondoljuk meg. [12] Mielõtt sablonná általánosítanánk valamit.5.4. [20] Ha nem lehet közös bázisosztályt megadni.2. a ritkább eseteket túlterheléssel és alapértelmezett paraméter-értékekkel kezeljük. [6] A sablonok függését a példányosítás módjától csökkentsük a lehetõ legkisebbre. [21] Ha olyan beépített típusokat és adatszerkezeteket kell használnunk.2. §13.1. §13.3.13. milyen paraméterei lehetnek a sablonnak. [10] Specializáció és túlterhelés segítségével adjunk azonos felületet ugyanazon fogalom különbözõ típusokra vonatkozó megvalósításának. [18] Ha a futási idõ döntõ szempont. de ezeket nagyon óvatosan definiáljuk.9[16].

hu 13. 5. (*1.value) párokat olvas be és az egyes kulcsokhoz (key) tartozó értékek (value) összegét írja ki. a count és a price. Milyen mûveletek szükségesek az egyszeresen láncolt lista mûveletein felül? 4.5) A §11. (*2) Javítsuk ki a hibákat a List §13. A Link típus tartalmazza az elemek összekapcsolásához szükséges információkat. hasonlítsuk össze a kétféle kódot. valamint akkor is. amely egy Link típusból származtatott típusú elemeket tud tárolni. Az ilyen listát „tolakodó” (intrusive) listának nevezik. Hasonlítsuk össze a két lista hatékonyságát. elõnyeiket és hátrányaikat. 3. amely az összehasonlítási feltételt sablonparaméterként veszi át. Futtassunk le egy egyszerû programot a saját.8 pontbeli szószámláló program hatékonyságát egy asszociatív tömböt nem használó programéval. 9. melynek két tagja van. Adjuk meg.9[8]-beli Map-et valamilyen alkalmasabb adatszerkezet – például vörös-fekete fa (red-black tree) vagy S-fa (splay tree) – felhasználásával. 10. A Map mûködjék helyesen kulcsként használt C stílusú karakterláncokkal és string-ekkel is.5) Használjuk fel a Map-et topologikus rendezést megvalósító függvény írására. oldaltól kezdõdõen.1987] elsõ kötete írja le. (A topologikus rendezést a [Knuth.2 pontbeli String sablont a §11. (*2) Fejezzük be a §13. Gyakorlatok 1. Határozzunk meg egy Record osztályt. ha az alkalmazott típusnak van alapértelmezett konstruktora. 6. Amennyiben módunk van rá. hogy szóközöket is tartalmazó nevekre is helyesen mûködjön. illetve értékek.9[7]-beli összegzõ programot javítsuk ki. . (*3) Írjuk át a §13. Ezen lista felhasználásával írjunk egy bármilyen típusú elemeket tartalmazni képes (azaz nem tolakodó) egyszeresen láncolt listát. illetve a sablonból létrehozott változat ellenõrzésére. 11. amely (key.és kimeneti mûveleteket használjunk mindkét esetben. (*2) Írjunk egy programot. (*2.5 pontbeli definiciójában és írjuk meg a fordítóprogram által az f() függvény és a List számára létrehozott kóddal egyenértékû kódot.2. 8. Rendezzünk egy set<Record> halmazt mindkét tag szerint. Azonos stílusú be.9.12 pontbeli String osztály alapján.doksi. 2. (*3) Hasonlítsuk össze a §11.Forrás: http://www. a 280. 7. milyen típusok lehetnek kulcsok. és akkor is.) 12. (*2. (*3) Írjunk osztálysablont egy egyszeresen láncolt lista számára. ha nincs. Sablonok 465 13.5) A §13. (*2. (*2) Határozzunk meg egy olyan sort()-ot. (*2) Készítsünk egy qsort() sablont.8 pontbeli Assoc osztály alapján készítsünk egy egyszerû Map osztályt.5) Írjunk tolakodó és nem tolakodó kétszeresen láncolt listákat.

szám. és ott is.doksi.Forrás: http://www. ár)). ahol unsigned.hu 466 Absztrakciós módszerek 13. 15. (*2) Tervezzünk egy módszert.4 pontbeli Literate-ben vázolt módszert karakterláncok fordított ábécésorrendû rendezésére. Csak „a T paraméternek egy My_base-bõl származtatott osztálynak kell lennie” típusú megszorítások ellenõrzése nem elég! . amelyeknek megfelelõ típusú objektum létrejön a programban. (*2) Használjuk a §13. amellyel biztosítható. ahol a char elõjeles (signed). Adjunk egy. 16. amely legalább háromféle eltérést mutat be egy függvénysablon és egy makró között (a formai követelmények különbségén felül). hogy a módszer olyan C++-változatokkal is mûködjön. (*2) Írjunk readline() sablonokat különféle sorfajták számára (például (cikk. 14. a kis.5) Szerkesszünk egy példát. hogy a fordítóprogram minden olyan sablon minden paraméterére ellenõrizzen bizonyos megszorításokat.és nagybetûk közötti különbséget elhanyagoló rendezést támogató változatot is. (*1. Gondoskodjunk róla.

A kivételek (exception) az ilyen problémák kezelését segítik. Churchill) Hibakezelés • A kivételek csoportosítása • A kivételek elkapása • Minden kivétel elkapása • Továbbdobás • Az erõforrások kezelése • auto_ptr • A kivételek és a new operátor • Az erõforrások kimerülése • Kivételek konstruktorokban • Kivételek destruktorokban • Olyan kivételek. Hibakezelés Ahogy a §8. amelyet nem tud kezelni. egy könyvtár szerzõje felfedezheti a futási idejû hibákat. akkor egy kivételt vált ki („kivételt dob”. hogy ha egy függvény olyan hibát talál.” (Winston S.Forrás: http://www. hogyan kell az ilyen hibákat kezelni. de általában fogalma sincs róla.3 pontban rámutattunk. Az alapötlet az.hu 14 Kivételkezelés „Ne szóljon közbe. throw) . amikor éppen közbeszólok.doksi. de nem tudja felderíteni azokat – máskülönben a felhasználói kódban kezelné és nem a könyvtárnak kellene megtalálnia õket. A könyvtár felhasználója tudhatja.1. mit kezdjen velük. amelyek nem hibák • Kivételek specifikációja • Váratlan kivételek • El nem kapott kivételek • A kivételek és a hatékonyság • A hibakezelés egyéb módjai • Szabványos kivételek • Tanácsok • Gyakorlatok 14.

amely feltétel nélkül befejezi a program futását. §8. egy „hiba” jelentésû értéket adhat vissza. hogy a hívó esetleg nem veszi észre. A második eset („hiba jelentésû érték visszaadása”) nem mindig kivitelezhetõ. sokszor akkor is kényelmetlen. hogy minden hibát észleljenek vele.Forrás: http://www.doksi. Az adott problémát kezelni tudó függvények jelezhetik. Tekintsük át a többi lehetõséget: ha a program észreveszi. nem használhatunk olyan programban.3). A hibakezelés ezen módja felveszi a versenyt a hagyományosabb módszerekkel. a programok azonban jellemzõen elmulasztják az errno változó kellõen következetes vizsgálatát. A C standard könyvtárának számos függvénye például az errno globális változót állítja be. amelyet nem lehet helyben megoldani. Ezenkívül az egyidejû hozzáférésnél (konkurrencia. De ha alkalmazható is ez a módszer. Ezért aztán ezt a módszert ritkán alkalmazzák annyira következetesen. A legtöbb hiba kezelésére ennél jobb megoldás szükséges és lehetséges. hogy el akarják kapni (catch) a kivételt (§2. akkor 1. egy normális értéket adhat vissza és a programot „szabálytalan” állapotban hagyhatja. „hiba” esetén meghívandó függvényt. Alapértelmezés szerint az elsõ eset. nem hajthatnak végre egyszerûen egy abort()ot vagy exit()-et. A harmadik módszer („normális érték visszaadása és a program szabálytalan állapotban való hagyása”) azzal a gonddal jár. 3. Olyan könyvtárat. mert minden hívást ellenõrizni kell. Azok a könyvtárak. ha olyan kivétel lép fel. hogy a vezérlés visszakerüljön a hívóhoz.2. . amelyek nem ismerik a befoglaló program célját vagy annak általános mûködését. amelynek nem szabad „elszállnia”. meghívhat egy. Ha egy függvény például int típussal tér vissza. amelyet nem kap el a program. A kivételek szerepét tehát úgy is megfogalmazhatnánk. 4. hogy lehetõséget adnak arra. 2. hogy a program nem megengedett állapotba került. mert nem mindig létezik elfogadható „hibát jelentõ érték”. hogy hibát jelezzen (§14. hogy olyan probléma lépett fel. befejezheti a program futását. ami megakadályozná a hibás hívások halmozódását. ami a program méretét akár kétszeresére is növelheti (§14. hogy a (közvetett vagy közvetlen) hívó képes kezelni a problémát. akkor minden int érték hihetõ visszatérési érték lehet.4.8). azaz a program futásának befejezése történik.hu 468 Absztrakciós módszerek abban a reményben.8). concurrency) a globális változók hibajelzésre való használata nem mûködik jól. ha a megfelelõ mûvelet helyben nem végezhetõ el.

hogy a hibaüzenet esetleg nem is a megfelelõ nyelven jelenne meg.hu 14.5 pont tárgyalja.Forrás: http://www. vagy akár a fejlesztés lezárulta után. mondjuk egy grafikus felhasználói felületrõl érkezett rossz adat hatására „bad argument to atan2”. mint amelyekre a negyedik. hogy a hibát. Egy jó könyvtár nem „halandzsázik” így. A kivételek használata lehetõvé teszi a hibakezelõ kódnak a „közönséges” kódtól való elválasztását. A kivételek lehetõvé teszik az adott kódrészlet számára. hogy valahogy átevickéltünk a hibán. és a hibaüzenetek a végfelhasználó számára úgysem mondanának semmit. A hagyományos kezelés az volt. Ezenkívül sokszor nincs is hová értesítést küldeni a hibáról (például ha a program olyan környezetben fut.doksi. ahol a cerr nem vezet a felhasználó által elérhetõ helyre). A kivételkezelõ eljárás szabályosabb hibakezelést tesz lehetõvé és megkönnyíti a külön megírt kódrészek közötti együttmûködést. hogy a hibák (különösen a könyvtárakban fellépett hibák) alapértelmezett kezelése a program leállítása. A kivételkezelést akkor célszerû használnunk a hagyományos módszerek helyett. .4. Ennél rosszabb. A C++ kivételkezelésének a Pascal. A programnak csak olyan része lehet képes értelmes hibaüzenet küldésére. majd feltétel nélküli leállás történne. aztán reménykedtünk. Ez pedig jobb. kivételek híján viszont a hibakezelõ függvénynek csak a többi három lehetõsége van a hiba kezelésére. Nem fejlesztõk számára az esetleg jelen sem levõ felhasználó/kezelõ segítségét kérõ könyvtár elfogadhatatlan. nem „elegánsak” vagy hibákat okozhatnak. amely talán boldogul vele. Ha a leállás az adott programnál elfogadhatatlan. amelynek van fogalma a program összefüggéseirõl. A hibakezelõ függvények és a kivételek témáját a §14. amikor a felhasználó a program szerkezetét ismerõ programozó. mondjuk finnül egy angol felhasználó számára. a „hibakezelõ függvény meghívása” mód alkalmas. Egyesek a hibákon való „átevickélés” nem vonzó tulajdonságait hibaüzenetek kiíratásával. hogy a hibaüzenet jellemzõen a könyvtár fogalmaival lenne megfogalmazva. próbálták enyhíteni. így a kivétel csak akkor állítja le a programot. Az ilyesmi fõleg a program hibakeresésénél (debugging. ha azok nem elégségesek.és C-programozók számára új vonása.6. a kód olyan része számára továbbítsa. ezáltal a programot olvashatóbbá és a kódelemzõ eszközök számára kezelhetõbbé teszi. a felhasználó segítségét kérõ ablakokkal stb. amikor a program már a mit sem sejtõ felhasználók kezében van. mintha késõbb a fejlesztés során lépnének fel hibás eredmények. mint ha a hiba hagyományos módon történt „nem teljes” kezelését követõen végzetes hiba.2).3. Az a legkevesebb. környezetérõl.2) vagy az összes adott fajtájút (§14. hogy több gondot és figyelmet kell fordítani arra. Mindazonáltal ez elõnyösebbnek tûnik. amit nem tud kezelni. ha a programozó ezt hagyja. „a program belövése”) hasznos. A kivételkezelés törékenyebbé teszi a programot abban az értelemben. Kivételkezelés 469 A kivételkezelés nem az olyan esetek kezelésére szolgál. hogy a program elfogadhatóan fusson. akkor el lehet kapni az összes kivételt (§14.

Ám az eredmény egy olyan kód. Ezen a ponton célszerû újraolvasni a §8. hogy hibák és más kivételes jelenségek kezelését támogassa – innen a neve – és hogy támogassa a hibák kezelését függetlenül fejlesztett összetevõkbõl álló programokban. de vigyázzunk. A C++ nyelv kivételkezelése a programozónak a hibák azon helyen való kezelésére ad lehetõséget. mint a billentyûzet felõl érkezõ megszakítások vagy bizonyos aritmetikai hibák nem feltétlenül kivételek és nem közvetlenül ezzel az eljárással kezelendõk. leírásuk itt nem szerepel. amelyek különbözõ emberek számára különbözõ jelentéssel bírnak. §14.5). hogy az elvárt módon fusson. A tervezési folyamatra nagyobb hangsúlyt helyez és megnövelheti egy kezdeti (még hibás) változat elõállításához szükséges munka mennyiségét. hogy más programozók számára is érthetõ legyen. Az aszinkron események világos és hatékony kezeléséhez a kivételkezelés itt leírt módjától alapvetõen különbözõ eljárásokra van szükség. amelynek sokkal nagyobb az esélye arra. amely a kivételkezelés alapvetõ vonásait mutatja be. Az aszinkron események. ahol ez a rendszer szerkezetébõl adódóan a legtermészetesebb.Forrás: http://www. signal) használata). 14. amelyeket helyettesít – a vezérlést kizárólag helyben szabályozó nyelvi elemekhez képest kevésbé hatékony. Ezért kivételeket olyan esetekben is szabályosan alkalmazhatunk. Más nyelveken (C vagy Pascal) a jó stílus csak egyes szabályok „megkerülésével” és nem is tökéletesen érhetõ el. például a tömbindex-hibák vagy ki. A C++ nyelv kivételkezelõ rendszerét úgy tervezték. mint azok a módszerek. .1.doksi. Meg kell azonban jegyeznünk. A kivételek más megközelítései A „kivétel” (exception) azon szavak egyike. hogy egy nagyobb program része lehessen. Sok rendszernek vannak az aszinkronitás kezelésére szolgáló eljárásai (például szignálok (jelzések.4) alapuló vezérlési szerkezet. hogy a hibakezelés ezután is nehéz feladat marad és a kivételkezelõ eljárás – jóllehet rendszerezettebb. amelyeknek semmi közük a hibákhoz (§14. de mivel ezek rendszerfüggõek szoktak lenni. amelyet alternatív visszatérési eljárásként is tekinthetünk. A kivételkezelés csak a szinkron kivételek. Ennek megfelelõen a kivételek nyelvileg támogatott kezelése kifejezetten támogatja a „jó stílusú” programozást. a (végrehajtási) verem „visszatekerésén” (stack unwinding. A kivételek nyilvánvalóvá teszik a hibakezelés bonyolultságát.1.és bemeneti hibák kezelésére szolgál. A kivételkezelés egy nem lokális. mint a fordítási idejû típusellenõrzés és többértelmûség-kiszûrés futási idejû megfelelõjére. A kivételkezelésnek azonban a hibakezelés és a hibatûrõ viselkedés támogatása az elsõdleges célja és ez a fejezet is ezekre összpontosít.3 pontot. mint ahogy a C++ nyelv más eszközei támogatják azt. hogy eszközökkel lehessen kezelni.hu 470 Absztrakciós módszerek A kivételkezelésre úgy is tekinthetünk. hogy a rossz hírért ne annak hozóját hibáztassuk.

hu 14. A hibát kezelni képes kódrészlet beavatkozási szándékát egy „elkapó” (catch) záradékkal jelzi. ezért az egyidejû hozzáféréssel összefüggõ kivételes helyzeteket itt nem tárgyaljuk. hogy egy adott programban mi számít kivételesnek. Ebbõl következõleg a kivételek csoportosításához és kezeléséhez az öröklõdés hasznos segítséget nyújthat. Kivételek kiváltása („dobása”) a függvényhívásokhoz képest ritkán forduljon elõ.. Kivételkezelés 471 A szabványos C++ nem ismeri a végrehajtási szál (thread) és a folyamat (process. hogy a programozó vagy a rendszer betart bizonyos alapvetõ szabályokat. amely a program legtöbb futásakor fellép? Lehet-e egy tervezett és kezelt esemény hiba? Mindkét kérdésre igen a válasz. A használt rendszer dokumentációja leírja az ezeket kezelõ eszközöket. class Zerodivide: public Matherr { }. hogy szabályosan zárolja (lock) a megosztott adatszerkezeteket a használat elõtt. A hibát észlelõ kód (általában egy könyvtár) „eldobja” (throw) az objektumot (§8.5). amíg egy megfelelõ (vagyis a kivételt kiváltó függvényt közvetve vagy közvetlenül meghívó) függvényben catch-et nem találunk. A kivételek csoportosítása A kivétel olyan objektum. hogy „a rendszer valamely része nem tudta megtenni. különben a rendszer szerkezete áttekinthetetlen lesz. egészen addig. Egy matematikai könyvtár kivételeit például így csoportosíthatjuk: class Matherr { }. hogy a C++ nyelv kivételkezelõ rendszerét úgy tervezték. A „kivételes” nem azt jelenti. Ez nem mindig könnyû (§14. 14. A kivételek gyakran természetes módon családokra oszthatók. hogy „szinte soha nem történhet meg” vagy hogy „végzetes”. processz) fogalmát. itt csak azt jegyzem meg. Szokás szerint ilyenkor valami mással próbálkozunk. A legtöbb nagy program normális és sikeres futtatása során azonban néhány kivétel kiváltása és elkapása biztosan elõ fog fordulni.3). hogy konkurens programban is hatékony legyen. A kivétel „eldobása” „visszatekeri” a végrehajtási vermet. de a programozónak kell eldöntenie. feltéve. például azt. . melynek osztálya valamilyen kivétel elõfordulását írja le. amire kérték”.. class Overflow: public Matherr { }. Jobb úgy értelmezni a kivételt. A C++ kivételkezelõ eljárásainak a hibák és kivételes események jelentése és kezelése a céljuk.doksi. // . Tekintsünk-e kivételesnek egy olyan eseményt.Forrás: http://www.2. class Underflow: public Matherr { }.

. nem biztos. az összes kivételt kezelni kívánó kódrészletet módosítani kellene.. de egy kivétel könnyen ki is maradhat.. hogyan lehetne a matematikai könyvtár összes kivételét kezelni. } catch (Overflow) { /* . hogy a pontos típusra való tekintet nélkül kezeljünk bármilyen Matherr-t: void f() { try { } // . Ezek az újrafordítási és módosíthatósági szempontok ahhoz az irányelvhez vezetnének. ha nem volnának csoportosítva.. ez pedig a legtöbb könyvtár számára elfogadhatatlan.6.Forrás: http://www. Gondoljuk meg például.. Általában a könyvtár elsõ változatának kibocsátása után ilyen teljes körû módosításokra nincs mód. De ha van is. */ } catch (Zerodivide) { /* .hu 472 Absztrakciós módszerek Ez a szerkezet lehetõvé teszi. */ } Ez nemcsak fárasztó. Ezért tehát a kivételeket célszerû könyvtárankénti vagy alrendszerenkénti csoportokban megadni (§14. minden. A kivételeknek hierarchiába való szervezése fontos lehet a kód tömörsége céljából. } catch (Overflow) { // az Overflow (túlcsordulás) vagy más onnan származó hiba kezelése } catch (Matherr) { // nem Overflow matematikai (Matherr) hibák kezelése } Itt az Overflow-t külön kezeltük. Gondoljuk meg. ha nem csoportosítanánk a matematikai kivételeket.. hogy az elsõ változat kibocsátása után a könyvtár nem bõvülhetne további kivételekkel.2). Az összes többi Matherr kivételt az általános záradék fogja kezelni. Az összes kivételt fel kellene sorolni: void g() { try { } // . mi lenne.. Ha a matematikai könyvtár egy új kivétellel bõvülne. hogy tényleg hajlandóak vagyunk átdolgozni. vagy ha igen.doksi.. hogy minden kód a rendelkezésünkre áll. . nem biztos. */ } catch (Underflow) { /* ...

. int a. Ebbõl következik. public: Int_overflow(const char* p. void f() { try { } g().1. int b) { op = p. 14.. int a1. a2 = b. nem saját osztályának kezelõje.2. amelyeket a kivételek hordozta információnak csak egy része érdekel. például a nullával való osztást.2.' << a2 << ')'.10 írja le. még ha g() egy Int_overflow-t váltott is ki.3): class Matherr { // .2). hogy a kivételnek csak az elkapott osztálynak megfelelõ része másolódik le (felszeletelõdés. a1 = a.. A standard könyvtárbeli kivételeket a §14. Ennek egyik oka. hogy számos utasításcsövet alkalmazó (pipelined) rendszer aszinkron módon észlel bizonyos aritmetikai hibákat. . virtual void debug_print() const { cerr << "Matematikai hiba". Vagyis egy kivételt általában egy bázisosztályának a kezelõje kap el. } catch (Matherr m) { // .doksi. Kivételkezelés 473 Jegyezzük meg. sem a (C-vel közös) alapvetõ matematikai könyvtár nem kivételek formájában jelzik az aritmetikai hibákat. Származtatott kivételek Az osztályhierarchiák kivételkezelésre való használata természetesen vezet olyan kivételkezelõkhöz. }.. Vagyis a formális paraméter a paraméter-értékkel kap kezdõértéket (§7.. } virtual void debug_print() const { cerr << op << '(' << a1 << '. a2. } }. hogy sem a beépített matematikai mûveletek. A kivétel elkapásának és megnevezésének mûködése a paraméteres függvényekével azonos. Következésképpen az Int_overflow-ban levõ többlet információ elérhetetlen.hu 14.Forrás: http://www. A Matherr hierarchia ezért itt csak illusztrációul szolgál. } A Matherr kezelõbe való belépéskor az m egy Matherr objektum. } // . §12. slicing.. class Int_overflow: public Matherr { const char* op.

2). mutatók vagy referenciák használatával megakadályozhatjuk az információvesztést: int add(int x.y). m.... } return x+y. int i2 = add(INT_MAX.-2). // ez az! Az utolsó add() hívás kiváltotta kivétel hatására Int_overflow::debug_print() fog végrehajtódni. Egy ilyen Netfile_err -t el lehet kapni a hálózati kivételekkel törõdõ függvényekben: void f() { try { } // valami } catch(Network_err& e) { // .Forrás: http://www.2). // x+y nem fog túlcsordulni void f() { try { } } catch (Matherr& m) { // . Egy kivétel gyakran két csoportba is tartozik: class Netfile_err : public Network_err.. int y) { if ((x>0 && y>0 && x>INT_MAX-y) || (x<0 && y<0 && x<INT_MIN-y)) throw Int_overflow("+". Összetett kivételek Nem minden kivételcsoport fa szerkezetû. Ha a kivételt érték és nem referencia szerint kaptuk volna el. */ }. int i3 = add(INT_MAX. public File_system_err { /* .x. } int i1 = add(1.debug_print().. } .2. 14..doksi.hu 474 Absztrakciós módszerek Mint mindig. akkor ehelyett Matherr::debug_print()-re került volna sor.2.

Forrás: http://www. . H ugyanaz a típus. vagy 2. Kivételkezelés 475 De akár a fájlrendszer-kivételekkel foglalkozó függvényekben is: void g() { try { } // valami más } catch(File_system_err& e) { // .doksi.. H egyértelmû bázisosztálya E-nek. 4. 2..3. hogy hálózat is szerepet játszik (lásd még §14. amikor a szolgáltatások (például a hálózati szolgáltatások) a felhasználó számára láthatatlanok. } catch(H) { // mikor jutunk ide? } A vezérlés akkor kerül a kezelõhöz. mint E.6). ha 1. Ebben a példában a g() írója esetleg nem is tudott arról. A kivételek elkapása Vegyük az alábbi kódrészletet: void f() { try { } throw E().. 3. vagy 2. 14. } A hibakezelés ilyen nem hierarchikus szervezése akkor fontos. H és E mutatótípusok és a mutatott típusokra teljesül 1. H referencia és típusára teljesül 1.hu 14.

14. ami nem másolható. majd továbbdobja a kivételt. terminate() hívás fog bekövetkezni (§14. A kivételek továbbdobása Gyakran elõfordul. ha nincs is kivétel.doksi.5) számára van hely. mint egy függvény paraméterénél.7). Ettõl az elkapható kivételek típusa nem változik meg. hogy a memória elfogyásakor szabványos módon kiváltandó kivétel. Ilyenkor a kezelõ általában megteszi helyben. a kezelõ pedig az eredeti kivétel másolatát kapja meg. ezért nem lehet olyan kivételt kiváltani. ha a kivétel kezeléséhez szükséges információ nem egyetlen helyen áll rendelkezésre és a hiba következményeit legjobban több kezelõ között elosztva küszöbölhetjük ki: void h() { try { // esetleg matematikai hibákat kiváltó kód } catch (Matherr) { if (teljesen_le_tudjuk_kezelni) { // Matherr kezelése } else { return. amit lehet throw. hogy a kivételrõl kiváltásakor másolat készül. hogy egy kivételrõl elkapása elõtt több másolat is készül. . Ez akkor is igaz. Lehetséges. A fordítóprogram az ilyen esetek egy részét – de nem mindet – felderítheti és figyelmeztethet rájuk. hogy miután egy kezelõ elkapott egy kivételt.3. de az biztosított. hogy az elkapott kivételt módosíthassuk.4.Forrás: http://www. Ha akkor próbálunk meg kivételt továbbdobni. amit lehet. Az adott nyelvi változat nagyon sokféle módszert alkalmazhat a kivételek tárolására és továbbítására.1. // megtesszük. úgy dönt.hu 476 Absztrakciós módszerek Ezenkívül egy kivétel elkapásánál ugyanúgy alkalmazhatjuk a const minõsítõt. // a kivétel továbbdobása } } } A kivétel továbbdobását az operandus nélküli throw jelzi. hogy nem tudja teljes egészében kezelni azt. a bad_alloc (§14. Az az elv. Így a hibát végül is a legalkalmasabb helyen lehet kezelni. csak megakadályozza.

sor kerül a kezelõ függvény rendrakó tevékenységére. Így a „minden kivételt elkapó” kezelõ tetszõleges invariánsok kezelésére alkalmas hely lehet.6. akkor a kivételkezelõbe beírhatjuk a nekik elfogadható értéket adó kódot. hogy továbbdobja. ha a program Int_overflow-t váltott ki.) jelentése is a tetszõleges kivétel elkapása: void m() { try { } // valami } catch (. Amint a helyi rendrakás megtörtént.4). tetszõleges paramétert jelent (§7.doksi..2.3.7. ami Matherr-ként rendelkezésre áll. Minden kivétel elkapása Az elkapási és továbbdobási módszer egy végletes esetével is érdemes megismerkednünk. A hibakezelésnek általában (a kivételkezelésnek pedig különösen) fontos szempontja a program által feltételezett állapot érvényességének megõrzése (invariáns. A §14. Sok fontos esetben azonban egy ilyen kivételkezelõ nem a legelegánsabb megoldás (lásd §14. Más szóval. . 14. } // minden kivételt elkapunk Így ha m() fõ részének végrehajtása során bármilyen kivétel lép fel..3. akkor a h() hívója is Int_overflow-t kap.6). ahogy azokat találta. §24.. nem csak annak elkapott részére..3.hu 14. kezelõ által elkapott kivételrõl.. amelyet h() egy Matherr-ként kapott el és úgy döntött.2 pont ír arról. a catch(... Kivételkezelés 477 A továbbdobás az eredeti kivételre vonatkozik.) { // takarítás throw. hogyan lehet információhoz jutni a . Mint ahogy függvényekre a ..1). az arra okot adó kivétel továbbdobódik a további hibakezelés kiváltása céljából. Például ha m() bizonyos mutatókat olyan állapotban hagy hátra.Forrás: http://www.

.) { // egyéb kivételek kezelése (§14.. a try utasításban a kezelõk sorrendje lényeges. Még ha el is távolítjuk a mindent elkapó kezelõt.4.3. a bad_cast akkor sem kerül szóba..10) } catch (std::bad_cast) { // dynamic_cast hibák kezelése (§15. A kezelõk kipróbálására ebben a sorrendben kerül sor: void f() { try { } // .Forrás: http://www..2..2) } catch (std::exception& e) { // standard könyvtárbeli kivételek kezelése (§14..2) } Itt az exception soha nem jut szerephez.3..doksi. sok logikai hibát kiszûrhet: void g() { try { } // .10) } catch (.1.2) } Mivel a fordítóprogram ismeri az osztályhierarchiát.hu 478 Absztrakciós módszerek 14. } catch (. .. mert az exception leszármazottja. A kezelõk sorrendje Mivel egy származtatott osztályú kivételt több típusú kivétel kezelõje is elkaphat. } catch (std::ios_base::failure) { // i/o adatfolyam hibák kezelése (§14.) { // minden kivétel kezelése (§14.10) } catch (std::exception& e) { // standard könyvtárbeli kivételek kezelése (§14.3.

amíg észre nem vesszük. . lefoglal valamennyi memóriát a szabad tárban. Ez mûködõképesnek tûnik. –. throw.4. hogy ha valami hiba történik az fopen() meghívása után. Erõforrások kezelése Amikor egy függvény lefoglal valamilyen erõforrást – például megnyit egy fájlt. amely minden hibát elkap. gyakran fontos feltétel a rendszer további mûködése szempontjából. bezárja a fájlt.Forrás: http://www. Egy elsõ kísérlet a use_file() hibatûrõvé tételére így nézhet ki: void use_file(const char* fn) { FILE* f = fopen(fn."r").) { fclose(f). try { // f használata } catch (."r"). hogy az erõforrást rendben fel is szabadítsa a hívóhoz való visszatérés elõtt: void use_file(const char* fn) { FILE* f = fopen(fn. } fclose(f). // f használata } fclose(f).. akkor egy kivétel miatt a use_file() függvény az fclose() végrehajtása nélkül térhet vissza.doksi.. de az fclose() meghívása elõtt. Még egy közönséges return utasítás miatt is visszatérhet a use_file() az fclose() végrehajtása nélkül. zárol valamit stb. Például a C standard könyvtárának longjmp() függvénye ugyanilyen problémát okozhat. Ugyanez a probléma kivételkezelést nem támogató nyelvek esetében is felléphet. } A fájlt használó kód egy try blokkban van. Kivételkezelés 479 14. majd továbbdobja a kivételt.hu 14.

// f használata } . amely egy FILE*-hoz hasonlóan viselkedik: class File_ptr { FILE* p..hu 480 Absztrakciós módszerek Ezzel a megoldással az a gond. fáradságos és esetleg lassú is lehet.. destruktora pedig bezárja a fájlt. vagy az fopen()-hez szükséges paraméterekkel hozhatunk létre. // n-edik erõforrás lekötése // erõforrások felhasználása // n-edik erõforrás felszabadítása // . } File_ptr(FILE* pp) { p = pp. bármelyik esetben élettartama végén a File_ptr objektum megsemmisül. hogy feleslegesen hosszú. } }. operator FILE*() { return p.a)."r").. hogy az erõforrásokat megszerzésükkel ellentétes sorrendben szabadítsuk fel. hiszen a programozók elfáradnak. Ráadásul minden túl hosszú és fáradságos megoldás nyomában ott járnak a hibák. } ~File_ptr() { fclose(p).. A megoldandó helyzet általános formájában így néz ki: void acquire() { // elsõ erõforrás lekötése // . Ez erõteljesen emlékeztet a konstruktorok által felépített és destruktorok által megsemmisített helyi objektumok viselkedésére.Forrás: http://www. Szerencsére van jobb megoldás. Programunk mérete így minimálisra csökken: void use_file(const char* fn) { File_ptr f(fn. // elsõ erõforrás felszabadítása } Általában fontos szempont. } Egy File_ptr objektumot vagy egy FILE* mutatóval. public: File_ptr(const char* n.doksi. Például megadhatjuk a File_ptr osztályt. const char* a) { p = fopen(n. Így aztán az ilyen erõforrás-lefoglalási és -felszabadítási problémákat konstruktorokkal és destruktorokkal bíró osztályok objektumainak használatával kezelhetjük.

amelynek konstruktora két erõforrást igényel: egy x állományt és egy y zárolást. hogy a dolog bonyolultságával a programozónak törõdnie kelljen. amelynek konstruktora már lefutott. hogy az állományt lefoglalta. Az X osztály konstruktorának semmilyen körülmények között nem szabad úgy visszatérnie. és kivételt válthat ki. Ahogy a vermet „visszatekerik”. mint hagyományos megfelelõje. a File_ptr-t és a Lock_ptr-t. amilyen mértékben részobjektumainak konstruktorai lefutottak. amíg konstruktora le nem fut. Destruktora csak ebben az esetben fog a verem visszatekerésekor meghívódni.doksi. A konstruktor azt próbálja elérni. egy jól megírt konstruktor – amennyire csak lehetséges – olyan állapotban hagyja a rendszert. A megszerzett erõforrások ábrázolására két osztályt használunk. Ideális esetben a „naív módon” megírt konstruktorok teljesítik ezt és nem hagyják az objektumot valamilyen „félig létrehozott” állapotban. Konstruktorok és destruktorok használata Az erõforrások lokális objektumok használatával való kezelését szokás szerint úgy hívják. amikor a kivételek kezelésekor a hívási láncban megkeressük a megfelelõ kezelõt. valamint a kivételkezelõ rendszerrel való együttmûködésükön alapul. Így a kivételkezelõ eljárás lehetõvé teszi. de a zárat nem. Ezek megszerzése nem biztos. hogy „kezdeti értékadás az erõforrás megszerzésével” (resource acquisition is initialization). egy tömböt pedig addig az eleméig. úgy hívják meg a létrehozott lokális objektumok destruktorait. hogy sikerül.1. hogy a függvénybõl „normálisan” vagy kivétel kiváltása folytán léptünk-e ki. rendszerint a „verem visszatekerésének” hívják. Az adott erõforrás megszerzését az azt jelölõ lokális objektum kezdeti értékadása ábrázolja: . Ez az általános eljárás a konstruktorok és destruktorok tulajdonságain. Ezt a „kezdeti értékadás az erõforrás megszerzésével” módszernek a tagokra való alkalmazásával érhetjük el.Forrás: http://www. Kivételkezelés 481 A destruktor attól függetlenül meghívódik. Vegyünk egy X osztályt. A destruktor a verem visszatekerésekor csak a teljes egészében létrehozott elemekre fut le. hogy a hibakezelést eltávolítsuk a fõ algoritmusból. Az így adódó kód egyszerûbb és kevesebb hibát okoz. hogy az objektum teljesen és szabályosan létrejöjjön. mint meghívása elõtt volt. Azt a mûveletet.hu 14. Egy részobjektumokból álló objektumot olyan mértékben tekintünk létrehozottnak. Egy objektumot addig nem tekintünk létezõnek. 14.4. Ezenkívül ezt anélkül kell elérni. Ha ez nem érhetõ el.

} // .. Lock_ptr bb. }. Az alkalmi módon kezelt erõforrások között a leggyakoribb a memória: class Y { int* p. } // ..Forrás: http://www. A felhasználónak nem kell ezzel törõdnie. public: X(const char* x. így destruktora nem fut le. init(). akkor az aa destruktorának meghívására sor kerül. Például ha az aa létrehozása után. mivel az objektum nem jött létre teljesen. bb(y) {} // . public: Y(int s) { p = new int[s]. } ~Y() { delete[ ] p. de a bb-ére nem. ott a konstruktor írójának nem kell kifejezett kivételkezelõ kódot írnia..doksi.hu 482 Absztrakciós módszerek class X { File_ptr aa. itt is az adott fordító dolga a szükséges nyilvántartások vezetése. void init().. }. a lefoglalt memória nem fog felszabadulni.. public: Z(int s) : p(s) { init(). void init(). A fenti módszer használata általános gyakorlat – és a „memória elszivárgásához” (memory leak) vezethet."rw"). const char* y) : aa(x. Ebbõl következõleg ahol az erõforrások megszerzése ezen egyszerû modell szerint történik. }. . // 'x' lefoglalása // 'y' lefoglalása Csakúgy mint a lokális objektumos példában. de a bb-é elõtt kivétel váltódik ki. Íme egy biztonságos változat: class Z { vector<int> p.. Ha az init()-ben kivétel keletkezik.

mint a Shape* // .Forrás: http://www. } // segédosztály // throw() jelentése "nem vált ki kivételt". public: typedef X element_type. Point p2... konstans (const) auto_ptr-eket nem lehet másolni. // lásd §14. X* ptr.hu 14.. } ~auto_ptr() throw() { delete ptr. Kivételkezelés 483 A p által használt memóriát a vector kezeli. // az auto_ptr<Shape> pont úgy használható.doksi. Shape* pb) { auto_ptr<Shape> p(new Rectangle(p1. Minthogy az auto_ptreket a másolás megváltoztatja. Ezenkívül amikor az auto_ptr megsemmisül. akár váltódott ki kivétel. Ha az init() kivételt vált ki. */ }..p2)). explicit auto_ptr(X* p =0) throw() { ptr=p. Az auto_ptr sablont a <memory> fejállomány deklarálja. akár nem. auto_ptr<Circle> pc. 14. Íme egy lehetséges kifejtése: template<class X> class std::auto_ptr { template <class Y> struct auto_ptr_ref { /* . az általa mutatott objektum is automatikusan törlõdik: void f(Point p1. auto_ptr<Shape> pbox(pb)..2. Itt a Rectangle. Ezen tulajdonos szerinti kezelés (vagy destruktív másolás) támogatása céljából az auto_ptreknek a közönséges mutatóktól gyökeresen eltérõ másolási módszerük van: egy auto_ptrnek egy másikba való másolása után a forrás nem mutat semmire.6 .4. a pb által mutatott Shape és a pc által mutatott Circle egyaránt törlõdni fog. mint egy hagyományos mutatónál.. a lefoglalt memóriát p (automatikusan meghívott) destruktora fogja felszabadítani. // kilépéskor ne felejtsük // el törölni pb-t // p téglalapra mutat } p->rotate(45). Egy auto_ptr-nek alapvetõen egy mutatóval adhatunk kezdõértéket és ugyanúgy hivatkozhatunk a mutatott objektumra. if (in_a_mess) throw Mess(). Auto_ptr A standard könyvtár auto_ptr sablon osztálya támogatja a „kezdeti értékadás az erõforrás megszerzésével” módszert. // .

majd a.doksi. } X* release() throw() { X* t = ptr. // veszélyes: auto_ptr használata tárolóban // . Ha egy D*-ot B*-gá lehet alakítani.ptr=0 template<class Y> auto_ptr(auto_ptr<Y>& a) throw(). mi történik.ptr=0 auto_ptr& operator=(auto_ptr& a) throw().hu 484 Absztrakciós módszerek // figyeljük meg: másolás és értékadás nem konstans paraméterekkel auto_ptr(auto_ptr& a) throw(). majd a. } X* get() const throw() { return ptr.ptr=0 template<class Y> auto_ptr& operator=(auto_ptr<Y>& a) throw().ptr=0 X& operator*() const throw() { return *ptr.. ptr=0. } void reset(X* p =0) throw() { if (p!=ptr) { delete ptr. majd a. hogy megvalósítsa a közönséges auto_ptr számára a destruktív másolást. } } // mutató kinyerése // tulajdonosváltás }. Shape* ps = p3.. akkor a sablonkonstruktor és a sablon-értékadás (meghatározott módon vagy automatikusan) egy auto_ptr<D>-t auto_ptr<B>-vé tud alakítani: void g(Circle* pc) { auto_ptr<Circle> p2 = pc.Forrás: http://www.end()). // Ezt ne tegyük: a rendezéstõl v sérülhet .v. // másol. ugyanakkor megakadályozza egy konstans auto_ptr másolását. de az a legvalószínûbb. p2->m = 7. // másol. auto_ptr<Circle> p4 = pc. // másolás auto_ptr_ref-bõl template<class Y> operator auto_ptr_ref<Y>() throw(). sort(v. // destruktív másolás auto_ptr-bõl Az auto_ptr_ref célja. hogy az objektum kétszer törlõdik – ami hiba. Jegyezzük meg. return t. } X* operator->() const throw() { return ptr. // másolás auto_ptr_ref-be template<class Y> operator auto_ptr<Y>() throw(). majd a.get()==0 // mutató kinyerése auto_ptr-bõl // tulajdonosváltás és típuskonverzió // programozói hiba: most p4 is felel a // törlésért Az nem meghatározott. // másol. hogy az auto_ptr destruktív másolási módszere miatt nem teljesíti a szabványos tárolók elemeire vagy a sort()-hoz hasonló szabványos eljárásokra vonatkozó követelményeket: vector< auto_ptr<Shape> >& v. auto_ptr<Circle> p3 = p2.begin(). // másol. auto_ptr(auto_ptr_ref<X>) throw().get(). } // most p2 felel a törlésért // most p3 felel a törlésért (p2 már nem) // programozói hiba: p2. ptr=p. ha több auto_ptr is mutat egy objektumra. auto_ptr<Shape> aps = p3.

) alkalmazásának fáradságát. amelyeknél a hibák ilyesféle egyszerûsített kezelése elfogadhatatlan. } X* p6 = new(a) X[10]. 14.4.4. De amire tervezték – az automatikus mutatók kivételbiztos kezelésére – arra lényeges „költség” nélkül megfelel. hogy egy alkalmas hibaüzenet kiadása után a programot leállítjuk. A kivételek és a new operátor Vegyük a következõt: void f(Arena& a. X* p5 = new(a) X. Ezzel a rendszerre bízzuk a program által lefoglalt összes erõforrás felszabadítását. X* p2 = new X[10]. az auto_ptr-nek. A „kezdeti értékadás az erõforrás megszerzésével” módszer a kivételeknek a hibák jelzésére való felhasználásával együtt sok ilyen könyvtár számára megfelelõ. // X átmeneti tárba helyezése (nem // szükséges felszabadítás) // tárfoglalás az 'a' Arena-tól (a-t kell // felszabadítani) . így aztán el kell kerülnie minden feltétel nélküli futási idejû hibát és minden erõforrást fel kell szabadítania. egyszerûen a bemenetet olvasó és azzal le is futó programnál a súlyosabb futási idejû hibákat úgy is kezelhetjük... X* p4 = new(buffer[11]) X[10]. X* p3 = new(buffer[10]) X. így a felhasználó újrafuttathatja a programot egy jobb bemenettel. hogy az auto_ptr nem egy általános „okos” vagy „intelligens” mutató (smart pointer). 14. Például sok.4. Kivételkezelés 485 Világos. illetve a catch(. X* buffer) { X* p1 = new X.Forrás: http://www.3.doksi. Egy könyvtár tervezõje például általában nem élhet feltevésekkel a könyvtárat használó program hibatûrési követelményeit illetõen. Figyelmeztetés Nem minden programnak kell mindenfajta hibával szemben immunisnak lennie és nem minden erõforrás annyira létfontosságú.hu 14. mielõtt egy könyvtári függvény visszatér. hogy a védelme megérje a „kezdeti értékadás az erõforrás megszerzésével” módszernek. Az itt leírt módszer olyan alkalmazások számára hasznos.

ahelyett. a válasz nem ennyire egyszerû. §10. Újrakezdés: kérjünk segítséget valamelyik hívó függvénytõl és folytassuk a program futását. Jegyezzük meg. hogy mi történjék. hogy van ilyen. csakúgy.6. A második esetben a hívónak arra kell felkészülnie. hogy a „befejezés” választásakor nem a program futása fejezõdik be. hogy segítséget adjon egy ismeretlen kódrészletnek annak erõforrás-lefoglaló problémájában.1). akkor a rendszer meghívja a Z::operator delete()-et. amely egy „sikertelen” számításból a hívó által adott hibakezelõbe tér vissza (ami aztán újra megpróbálhatja a számítást elvégeztetni). ha nem sikerül egy erõforrás lefoglalási kísérlete. így aztán jellemzõen nem szabványos módon is kell felszabadítani azt. amit aztán fel kellene szabadítani. Ha az elhelyezõ utasítást (placement syntax. .Forrás: http://www. anélkül.11). Ez az eljárás helyesen kezeli a standard könyvtárbeli elhelyezõ new operátort (§10. ha X konstruktora kivételt vált ki? Felszabadul-e a new() operátor által lefoglalt memória? Közönséges esetben a válasz igen. A második eset a legtöbbször sokkal egyszerûbb és a rendszer elvonatkoztatási szintjeinek jobb szétválasztását teszi lehetõvé. Következésképpen a továbbiak a felhasznált memóriafoglalótól (allokátortól) függnek. mint minden olyan esetet. így p1 és p2 kezdeti értékadása nem okoz memória-elszivárgást. de némelyik nem. más módon nem próbál felszabadítást végezni. ha nincs meg a fájl vagy hogy kifogytunk-e a szabad tárból. Befejezés: hagyjuk abba a számítást és térjünk vissza a hívóhoz. például mert korábban meggondolatlanul nyitogattunk fájlokat (az fopen()nel) és foglaltunk le memóriát a szabad tárban (a new operátorral). Ha egy Z::operator new() memóriafoglaló szerepelt.4. hanem csak az adott számítás.11) használjuk. Ezenkívül az elhelyezõ utasítás alkalmazásának éppen a memória nem szabványos lefoglalása a lényege. A tömbök kezelése ezzel azonos módon történik (§15. Ilyen problémával szembesülve a programozók kétféle megoldást szoktak alkalmazni: 1. hogy törõdtünk volna vele.hu 486 Absztrakciós módszerek Mi történik. hogy kezelje az erõforrás-lefoglalás sikertelenségébõl adódó problémát. 2. 14. Az erõforrások kimerülése Visszatérõ programozási dilemma. mi van.4. hogy megpróbálná maga orvosolni a helyzetet és a hiba felléptének helyérõl folytatni a számítást.4. feltéve.doksi. Az elsõ esetben a hívónak fel kell készülnie arra. A „befejezés” (termination) azon eljárás hagyományos neve. amikor a programozó összeillõ lefoglaló és felszabadító függvénypárt írt.5. Az elhelyezõ utasítás némely felhasználása lefoglal némi memóriát.

doksi.hu 14. a szabványos set_new_handler() függvény által fenntartott függvénymutató. az operator new() más változatai más módokat választhatnak.) { if (void* p = malloc(size)) return p. a befejezõ modellt a kivételkezelõ eljárás támogatja. Mindkettõt szemlélteti a standard könyvtárbeli new() operátor egy egyszerû megvalósítása és felhasználása: void* operator new(size_t size) { for (. } } // megpróbálunk memóriát találni // nincs kezelõ: feladjuk // segítséget kérünk Itt a C standard könyvtárának malloc() függvényét használtam a szabad memóriahely tényleges megkeresésére. Ha saját my_new_handler()ünket szeretnénk _new_handler-ként használni. ha nem. if (_new_handler == 0) throw bad_alloc(). if (no_of_bytes_found < min_allocation) throw bad_alloc(). Ha az talál a malloc() számára lefoglalható memóriát. } // feladjuk Valahol lennie kell egy try blokknak is. } catch (bad_alloc) { // valahogy reagálunk a memória kifogyására } try { A new() operátor ezen változatában használt _new_handler egy. .. a new() operátor visszaadhatja az arra hivatkozó mutatót. a new() meghívja a _new_handler-t. _new_handler(). a megfelelõ kivételkezelõvel: // . Ha sikerült memóriát találni.. Kivételkezelés 487 A C++-ban az újrakezdõ modellt a függvényhívási eljárás. akkor minden rendben.. Ha nem. akkor a kezelõ nem térhet vissza a new() operátorba végtelen ciklus okozása nélkül. ilyen módon valamelyik hívóra hagyva a helyzet tisztázását: void my_new_handler() { int no_of_bytes_found = find_some_memory().Forrás: http://www. ezt írhatjuk: set_new_handler(&my_new_handler). A _new_handler ekkor kivételt válthat ki.

4) a _new_handler-re való alkalmazásával elkerüljük (§14. . A _new_handler alkalmazásával a hiba észlelésének helyétõl semmiféle további információ nem jut el a segédfüggvényig. ám minél több jut el belõle egy futási idejû hiba észlelésének helyétõl a kijavítást segítõ helyig. Nagyobb rendszerek esetében a tapasztalat azt mutatja. A kivételek kiváltásához szükség van egy „eldobandó” objektumra.. Az egyes C++változatoktól elvárjuk.Forrás: http://www.) { set_new_handler(oldnh). Ebbõl adódóan az egyik kódrészleten csak a másikat értve és esetleg azt módosítva lehet változtatni. Általában célszerû az erõforrások megszerzését rétegekbe. hogy a memória elfogyásakor is maradjon annyi tartalék. ha az ilyen függéseket a legkisebb mértékre szorítjuk vissza. elvonatkoztatási szintekbe szervezni. } catch (.. } } set_new_handler(oldnh).doksi. A kivételkezelõ eljárás jobban támogatja az elszigetelést.12[1]).. mint a hívó által biztosított segédfüggvények meghívása. try { // kezelõ újbóli beállítása // továbbdobás // kezelõ újbóli beállítása Még jobb. a két kód annál inkább függ egymástól..hu 488 Absztrakciós módszerek Ha el akarjuk kapni a bad_alloc kivételt is. amennyi egy bad_alloc kiváltásához kell. hogy valamilyen más kivétel dobása a memória elfogyásához vezet.. hogy a sikeres rendszerek ezt az utat követik. ezt írhatjuk: void f() { void(*oldnh)() = set_new_handler(&my_new_handler). Könnyû több információt közvetíteni. } catch (bad_alloc) { // . ha a catch(. és elkerülni.. // .) kezelõt a „kezdeti értékadás az erõforrás megszerzésével” módszernek (§14.. A különbözõ helyen levõ programrészek elszigetelésére jó módszer.. throw. hogy egy réteg a hívó réteg segítségére szoruljon. de lehetséges.

a hagyományos (azaz kivételeket nem alkalmazó) lehetõségek a következõk: 1. 3.. hogy ellenõrzi a változót. Kivételek konstruktorokban A kivételek adnak megoldást arra a problémára. és bízzunk a felhasználóban. megbízva a hívóban. hogy a létrehozás sikertelenségére vonatkozó információt a konstruktorból átadjuk. Minthogy a konstruktorok nem adnak vissza külön visszatérési értéket. 2. hogy az elsõ használat elõtt elvégzi azt. Vector(int sz) { if (sz<0 || max<sz) throw Size(). amelyet a hívó megvizsgálhatna. A kivételkezelés lehetõséget ad arra. // . enum { max = 32000 }. return p. hogy jelezze a létrehozás sikertelenségét.. hogyan jelentsünk hibát egy konstruktorból. Jelöljük meg az objektumot „kezdõérték nélküli”-ként. } }. Ne végezzünk kezdeti értékadást a konstruktorban és bízzunk meg a felhasználóban. és végezze az elsõ meghívott tagfüggvény a kezdeti értékadást. Állítsunk be egy nem lokális változót (például az errno-t)... Egy egyszerû Vector osztály például így védekezhet a túlzott igények ellen: class Vector { public: class Size { }. hogy az majd ellenõrzi az állapotot. // .. 4. Kivételkezelés 489 14.. // . ha a kezdeti értékadás nem sikerült. } .4. Ez a függvény jelenti majd a hibát. Adjunk vissza egy hibás állapotú objektumot.doksi.6. és megpróbálhatunk valami értelmeset kezdeni velük: Vector* f(int i) { try { Vector* p = new Vector(i). A Vector-okat létrehozó kód most elkaphatja a Vector::Size hibákat.Forrás: http://www.hu 14.

Ez lényegében a sok erõforrás kezelésének problémáját az egy erõforrást kezelõ egyszerûbb eljárás ismételt alkalmazására vezeti vissza. és ezt ráadásul megbízhatóan és kényelmesen tegyük. // . }. ahol elegendõ ismeret áll rendelkezésre a hiba következményeinek elhárításához. } // v kezdõértéke s // a v által kiváltott kivételt itt kapjuk el . maga a hibakezelõ az alapvetõ hibakezelõ és -helyreállító módszerek szokásos készletét alkalmazhatja. ha egy tag kezdeti értékadása közvetlenül vagy közvetve kivételt vált ki? Alapértelmezés szerint a kivételt az a hívó függvény kapja meg.. a hibakezelõ módszerek alapvetõ célja az. A „kezdeti értékadás az erõforrás megszerzésével” eljárás az egynél több erõforrást igénybe vevõ konstruktorok kezelésének legbiztosabb és legelegánsabb módja (§14. } catch (Vector::Size) { // .. a probléma kezeléséhez rendelkezésre álló ismeretek halmaza akár bõvülhet is.hu 490 Absztrakciós módszerek } catch(Vector::Size) { // mérethiba kezelése } Mint mindig. ha a teljes függvénytörzset a tag kezdõérték-listájával együtt egy try blokkba zárjuk: class X { Vector v. amelyik a tag osztályának konstruktorának meghívta.6.4. de maga a konstruktor is elkaphatja azt. Valahányszor a hívó egy kivételt kap. // . Más szóval. X::X(int s) try :v(s) { // . hogy a hiba felfedezésének eredeti helyérõl olyan helyre jutassunk el információt.4). hogy mi volt a hiba... Ha a kivétellel együtt a megfelelõ információ is továbbítódik....1. Kivételek és a tagok kezdeti értékadása Mi történik. 14.Forrás: http://www. public: X(int).. megváltozik annak értelmezése.doksi.

6. Kivételek és másolás Más konstruktorokhoz hasonlóan a másoló konstruktor is jelezheti a hibás futást kivétel kiváltásával..4..doksi. és meghívódik az std::terminate() (§14. az a kivételkezelõ eljárás hibájának számít.4) a kivételkezelõ eljárás elhagy egy blokkot.2. Ez lehetõvé teszi a destruktor attól függõ programozását. Kivételkezelés közbeni meghívás: a verem visszatekerése közben (§14.hu 14. akkor ez ellen védekezhet: X::~X() try { f(). . A vector másoló konstruktora például memóriát foglal le és átmásolja az elemi objektumokat (16.. Ebben az esetben az objektum nem jön létre. 14. hogy mindkét operandusát azonos állapotban hagyja.3. Normál (szabályos) meghívás: a hatókörbõl való szabályos kilépéskor ((§10. az értékadásnak biztosítania kell.3) vagy egy delete hívás folytán (§10. Kivételkezelés 491 14.2. ha van olyan „eldobott” kivétel.3.7.2. // kivételt válthat ki } catch (. 2. hogy az objektum szabályos vagy a verem visszatekerése közbeni megsemmisítésérõl van-e szó. Az utóbbi esetben a kivétel nem léphet ki a destruktorból.4. amely destruktorral bíró objektumot tartalmaz. Ha megteszi.3. Ha a destruktor olyan függvényt hív meg.5) stb.4. hogy elfogadható-e az egyik kivételnek a másik kedvéért való elhanyagolása.). Végülis a kivételkezelõ eljárásnak és a destruktornak általában nincs módja eldönteni. melynek következménye nem meghatározott viselkedés lehet.4. függelékekben részletesen tárgyaljuk a kivételkezelés és a tárolók erõforrás-gazdálkodásának kapcsolatait. amelyet még nem kaptak el. E. Mielõtt ez utóbbit tenné.) { // valamit csinálunk } A standard könyvtárbeli uncaught_exception() függvény true-val tér vissza. Kivételek destruktorokban A kivételkezelõ eljárás szemszögébõl nézve egy destruktort kétféleképpen lehet meghívni: 1.4. Az E. Más esetben megszegjük a standard könyvtárbeli elõírásokat. és E. A kivétel kiváltása elõtt a másoló konstruktor fel kell szabadítsa a lefoglalt erõforrásokat.7).Forrás: http://www. ami kivételt válthat ki. A másoló értékadó operátor ugyanígy lefoglalhat erõforrásokat és kiválthat kivételt. amely kivételt válthat ki.

mint az if–hez vagy a for-hoz hasonló helyi vezérlési szerkezetek és azoknál gyakran kevésbé hatékony is. mintha vezérlõszerkezetek lennének: void f(Queue<X>& q) { try { for (.doksi. } } // 'Empty' kivételt vált ki. } Tree* find(Tree* p. így ebben az esetben tényleg nem teljesen világos. kivételek alkalmazása nélkül (§17. mi számít hibának és mi nem. const string& s) { if (s == p->str) throw p. A kivételkezelés kevésbé rendezett eljárás.3.Forrás: http://www.) { X m = q. A standard könyvtár például tartalmaz egy tetszõleges elemekbõl álló queue-t (sor) is.. // .get(). A keresõfüggvényeknél – különösen a rekurzív hívásokat nagymértékben alkalmazó függvényeknél (például egy fában keresõ függvénynél) – jó ötlet befejezésként kivételt alkalmazni: void fnd(Tree* p. Ezért kivételeket csak ott használjunk.2)..s).. a kivételeket úgy is tekinthetjük.5. const string& s) { // megtalálta s-t . if (p->left) fnd(p->left. ahol a hagyományosabb vezérlési szerkezetek nem elegánsak vagy nem használhatóak. ha a sor üres Ez egészen jónak tûnik. } } catch (Queue<X>::Empty) { return. if (p->right) fnd(p->right. Kivételek. ha a kivételre ténylegesen sor kerül. akkor miért lenne hiba? Csak mert a programozó a kivételre mint hibára és a kivételkezelõ eljárásokra pedig mint hibakezelõ eszközökre gondol? Nos.hu 492 Absztrakciós módszerek 14. amelyek nem hibák Ha egy kivételre számítunk és elkapjuk és így annak nincs a program mûködésére nézve rossz következménye.s).

Sajnos a „való” világ nem ilyen tiszta.1 pontban. amelyeket a függvény kiválthat. } catch (Tree* q) { return q.4. Ha a függvény futása közben valamit olyasmit próbál tenni. void f(int a) throw (x2. Becsüljünk meg mindent.6. Kivételek specifikációja A kivétel kiváltása vagy elkapása befolyásolja a függvénynek más függvényekhez való viszonyát. Kivételkezelés 493 } fnd(p.hu 14. Ekkor a kód világosan két részre különül: közönséges és hibakezelõ kódra.Forrás: http://www. (Részletesen lásd a §9. x3) { // törzs } . ami segít egy világos modellt kialakítani arról. Ha ésszerû. Ez érthetõbbé teszi a kódot. Ha egy függvény megadja. a program szerkezete pedig ezt (bizonyos fokig kívánatos módon) tükrözni fogja. x3). ragaszkodjunk „a kivételkezelés hibakezelés” elvhez. Ezért érdemes lehet a függvény deklarációjával együtt megadni azon kivételeket is. milyen kivételek léphetnek fel végrehajtása közben. try { // q->str==s Ugyanakkor a kivételek ilyen használata könnyen túlzásba vihetõ és áttekinthetetlen kódhoz vezethet.) Valójában a void f() throw (x2.doksi.s). ami szokványos esetben meghívja az abort()-ot. akkor ezzel valójában garanciát nyújt a használóinak. akkor ez a kísérlet az std::unexpected() hívássá fog átalakulni. } return 0. 14. Az unexpected() alapértelmezett jelentése std::terminate(). mi számít hibának és hogyan kezeljük. ami ezt a garanciát érvénytelenné tenné.1. A hibakezelés lényegénél fogva nehéz.

hogy bármilyen kivételt kiválthatnak: int f(). } catch (. Ekkor azonban alapvetõen minden függvény részére szükséges lenne kivételeket meghatározni.Forrás: http://www. emiatt pedig sokszor kellene újrafordítani és meg is akadályozná a más nyelveken írt programokkal való együttmûködést. ez pedig az aknamunkát észre nem vevõknek hamis biztonságérzetet adna. A kivételek ellenõrzése Egy felületnek fordítási idõben nem lehet minden megsértési kísérletét ellenõrizni.. A kivétel-specifikációk nélküli függvényekrõl azt kell feltételeznünk.1. hogy „aknázzák alᔠa kivételkezelõ rendszert és hogy hibás kódot írjanak a kivételek elfojtására. } // továbbdobás // továbbdobás // az unexpected() nem fog visszatérni Ennek legfontosabb elõnye. hogy a függvény nem váltana ki kivételt. 14.hu 494 Absztrakciós módszerek egyenértékû a következõvel: void f() try { // törzs } catch (x2) { throw. A kivétel-specifikációk fordítási idejû ellenõrzésének szabályai a könnyen felderíthetõ hibákat tiltják. Ez aztán arra ösztönözné a programozókat.doksi. az lenne a jó alapértelmezés.. határozottan nem szeretünk sûrûn beléjük nézegetni. . } catch (x3) { throw. Ha hozzá is férünk az összes könyvtár forrásához. ráadásul a megkivétel-specifikációkkal (exception specification) ellátott függvénydeklarációk sokkal rövidebbek és világosabbak. // nem vált ki kivételt Azt gondolhatnánk. hogy a függvény azok mindegyikét ki fogja váltani. A függvénydefiníciók viszont nem általánosan elérhetõek. de azért fordításkor sok ellenõrzés történik. // bármilyen kivételt kiválthat A kivételt ki nem váltó függvényeket üres listával adhatjuk meg: int g() throw (). A specifikált (tehát „engedélyezett”) kivételeket a fordító úgy értelmezi.) { std::unexpected().6. hogy a függvény deklarációja a hívók által elérhetõ felület része. mint az egyenértékû kézzel írott változat.

de a nagyobb rendszerek legtöbbje számára fontos. ahol a nagyobb frissítések költségesek és nincs is meg az összes forrás. mint B::g() // hiba: D::h() megengedõbb. vagy ha mégis. csak akkor jelezzen végzetes hibát..Y). hogy új kivétellel való bõvítés ne kényszerítse ki a kapcsolódó kivételspecifikációk kijavítását és az esetleg érintett összes kód újrafordítását. amely legalább annyira szigorúan határozza meg a kivételeket..doksi. } // hiba: a kivétel-meghatározás hiányzik Fontos szempont. amit az eredeti függvény nem adott meg lehetségesként. virtual void h() throw(X). A lényeg az. Ha egy származtatott osztály olyan kivételt váltana ki. betartja a felülírt függvény kivétel-specifikációjában felállított szabályokat. annak elkapását nem várhatnánk el a hívótól. // bármit kiválthat // rendben // rendben: D::g() szigorúbb. void h() throw(X. . mint B::h() Ezt a szabályt a józan ész diktálja. hogy a kivételek fordítási egységek határain átnyúló ellenõrzése nem kötelezõ. }. ami alapvetõ az olyan nagy rendszereknél.Y). A rendszer így egy félig felújított állapotban tovább mûködhet és a váratlan kivételek dinamikus (futási idõbeli) észlelésére hagyatkozhat. amely kevesebb kivételt vált ki. Kivételkezelés 495 Ha egy függvény valamely deklarációja megad kivételeket is. void g() throw(X). hogy ez ne történjen meg. Természetesen az adott nyelvi változat ellenõrizhet így. akkor mindegyik deklarációjának (és definiciójának is) tartalmaznia kell pontosan ugyanazokat a kivételeket: int f() throw (std::bad_alloc). virtual void g() throw(X. Másrészt egy felülíró függvény. int f() { // .Forrás: http://www. class D : public B { public: void f() throw(X).hu 14. Egy virtuális függvényt csak olyan függvénnyel lehet felülírni. ha a kivétel-specifikációk megsértését nem lehet majd futási idõben elkapni. mint maga az eredeti függvény: class B { public: virtual void f(). }.

exception).Y) = &f. . Váratlan kivételek Ha a kivételek köre specifikált. A standard könyvtár által kiváltott összes kivétel az exception osztály (§14. Így tehát f() a Some_Yerr osztályú hibát is továbbítani fogja és f()-ben semmilyen Yerr nem fog unexpected() hívást kiváltani.. A kivétel-specifikáció nem része a függvény típusának.hu 496 Absztrakciós módszerek Ugyanígy egy. // hiba 14.doksi. minden Yerr-t továbbítani fog a hívójának. void (*pf2)() throw() = &f.. mint pf2 Kivétel-specifikáció nélküli függvényre hivatkozó mutatót kivételeket meghatározó függvénymutatóhoz különösképpen nem rendelhetünk: void g(). Egy megfelelõen kidolgozott Y alrendszer gyakran az Yerr osztályból származtatja a kivételeit: class Some_Yerr : public Yerr { /* . a typedef-ek pedig nem is tartalmazhatnak ilyet: typedef void (*PF)() throw(X). Az ilyen hívások a tesztelésen kívül általában nem kívánatosak.Forrás: http://www. */ }. Yerr. de fordítva nem: void f() throw(X). // bármit kiválthat // hiba: g() megengedõbb. mint pf3 void (*pf3)() throw(X) = &g.2. az ott nem szereplõ kivételek az unexpected() meghívását válthatják ki.6. // rendben // hiba: f() megengedõbb. A kivételek gondos csoportosításával és a felületek megfelelõ meghatározásával viszont elkerülhetõk. de az unexpected() hívásokat is el lehet fogni és ártalmatlanná tenni. void (*pf1)() throw(X.10) leszármazottja. a kivételeket szigorúbban meghatározó függvényt hozzárendelhetünk egy megengedõbb függvénymutatóhoz. A fenti esetében a void f() throw (Xerr.

amelyet a set_new_handler() állít be. A memória elfogyását a _new_handler kezeli. void f() throw(X. elvész.6. hogy az újraírás nem lehetséges vagy nem kívánatos. hogy g() csak a saját „Y alrendszerével” kapcsolatos kivételek kiváltását engedélyezi. // bad_exception-t vált ki } A kivételkezelõ ekkor elkapja az elfogadhatatlan Y kivételt és bad_exception típusút vált ki helyette. Ugyanígy a váratlan kivételre adott választ egy _unexpected_handler határozza meg. A bad_exception-nel semmi baj. hogy a standard könyvtárbeli std::bad_exception-t felvesszük a specifikált kivételek közé.3. Tegyük fel. hogy milyen kivétel okozta a problémát. Ennek az a legegyszerûbb módja. Kivételek leképezése A programot leállítani kezeletlen kivétel fellépése esetén néha túl könyörtelen eljárás. amelyet az <exception> fejállományban megadott std::set_unexpected() állít be: .1.doksi.Forrás: http://www. Kivételek felhasználói leképezése Vegyünk egy g() függvényt.. void g() throw(Yerr). Természetesen g() nem fog tudni semmit a hálózati kivételekrõl és az unexpected()-et fogja meghívni. Ekkor a problémát az unexpected() jelentésének felülbírálásával oldhatjuk meg. amely hálózati kivételeket kezel. A g() hálózati környezetben való használatához olyan kódra van szükségünk. throw Y(). 14. Tegyük fel.hu 14. ha ilyennel találkozik.6.3. amely nem hálózati környezethez készült. a terminate()-nél kevésbé drasztikus – de azért így is meglehetõsen durva beavatkozás. Kivételkezelés 497 14. Ekkor az unexpected() egyszerûen egy bad_exceptiont fog kiváltani egy kezelõfüggvény meghívása helyett: class X { }. class Y { }. Tételezzük fel. vagy át kell írnunk g()-t. ráadásul az információ.std::bad_exception) { // . hogy g()-t hálózati környezetben kell meghívnunk.. Ilyenkor az unexpected() viselkedését kell elfogadhatóbbá tenni.

A váratlan kivételek megfelelõ kezeléséhez létre kell hoznunk egy osztályt. // az unexpected() most Yunexpected-et vált ki g(). Ezután definiálunk egy függvényt az unexpected() erre az esetre kívánt jelentésével: class Yunexpected : public Yerr { }. több alrendszer számára tettük lehetõvé – egymásra való hatás nélkül – a váratlan kivételek kezelését. void throwY() throw(Yunexpected) { throw Yunexpected(). hogy a „kezdeti értékadás az erõforrás megszerzésével” módszert használhassuk az unexpected() függvényekben: class STC { // tárol és visszaállít unexpected_handler old. Ha a throwY() olyan kivételt váltott volna ki.doksi. public: STC(unexpected_handler f) { old = set_unexpected(f). hogy mentettük és visszaállítottuk az _unexpected_handler-t. amit a specifikáció nem engedélyez. . A váratlan kivételek engedélyezetté alakításának e módszere alapvetõen a rendszer által a bad_exception-nal nyújtott szolgáltatás rugalmasabb változata. a kivétel-specifikáció nem sérül. } Mivel az Yunexpected az Yerr-bõl származik. Végül elkészíthetjük g()-nek egy hálózati környezetben alkalmazható változatát: void networked_g() throw(Yerr) { STC xx(&throwY). } ~STC() { set_unexpected(old). } }. akkor a terminate() hajtódott volna végre.hu 498 Absztrakciós módszerek typedef void(*unexpected_handler)(). unexpected_handler set_unexpected(unexpected_handler). Azáltal.Forrás: http://www. } A throwY()-t unexpected()-ként használva bármilyen kivételbõl Yunexpected lesz.

Azt azonban nem fogja tudni. Ha megpróbál így tenni.7. Így tehát biztosan van továbbdobható kivétel.hu 14. Például így tudnánk információt gyûjteni a Network_exception-ökrõl: class Yunexpected : public Yerr { public: Network_exception* pe. } catch(.) { throw Yunexpected(0).. 14.6. Ez a másolat túléli a verem „visszatekerését”.2.) kezelõ. Egy egyszerû eljárással lehetõvé tehetjük ennek megjegyzését és továbbítását. azt pedig elvileg egy catch(.doksi. A clone() függvényt arra használjuk. akkor az std::terminate() meghívására kerül sor. hogy megtudja: egy váratlan kivételt képeztünk le az Yunexpected-re. a verem visszatekerése során meghívott destruktor kivétel kiváltásával próbál véget érni. Yunexpected(Network_exception* p) :pe(p?p->clone():0) { } ~Yunexpected() { delete p. Egy unexpected() függvény nem tekinthet el a hibától és nem térhet vissza. } }. Kivételkezelés 499 14. hogy azon típusok összes kivételét kezeljük. El nem kapott kivételek Ha egy kivételt nem kapnak el. Kivételek típusának visszaállítása A váratlan kivételek Yunexpected-dé alakítása lehetõvé tenné a networked_g() felhasználójának. hogy a kivétel egy másolata számára helyet foglaljon a szabad memóriában. .. A terminate() fog meghívódni akkor is. } } // A továbbdobott kivételt azonnal el kell kapni! // leképezett kivétel kiváltása A kivételek továbbdobása és elkapása lehetõvé teszi.3).3.6.. ha a kivételkezelõ eljárás a vermet sérültnek találja. A throwY() függvényt az unexpected() hívja meg. az unexpected() maga fog bad_expection-t kiváltani (§14.. hogy melyik kivétel leképezése történt meg. amelyeket meg tudunk nevezni. vagy ha egy. void throwY() throw(Yunexpected) { try { throw.Forrás: http://www. } catch(Network_exception& p) { throw Yunexpected(&p).

hogy a programból szabályosan vagy nem szabályosan léptünk-e ki (§9. } . A terminate() meghívása drasztikus intézkedés: akkor kell használni. Ez az alapértelmezés a legtöbb felhasználó számára megfelelõ választás. hogy idõnként a kivételkezeléssel fel kell hagyni kevésbé kifinomult hibakezelési módszerek javára..hu 500 Absztrakciós módszerek A váratlan kivételeket az _unexpected_handler kezeli. Bizonyos rendszereknél alapvetõ. Ha az adott függvény megpróbálja.3.1.4.Forrás: http://www. A terminate() meghívásának oka. amelyet az std::set_unexpected() állít be. hogy a program futtatását folytatni lehessen a hibakeresõbõl. a main() függvényben a ténylegesen elkapni kívánt kivételek mellé írhatunk egy minden kivételt elkapó kezelõt is (§14.1.1).1). az el nem kapott kivételek kezelését az _uncaught_handler végzi.doksi. A visszatérési érték a set_terminate()-nek elõzõleg adott függvény. különösen a program hibakeresése (debugging) alatt.. terminate_handler set_terminate(terminate_handler). hogy a destruktorok ne hívódjanak meg. Az adott nyelvi változat határozza meg. hogy a program futásának el nem kapott kivétel miatti befejezõdésénél a destruktorok meghívódnak-e. Az exit() függvény visszatérési értékével jelezhetjük a rendszernek. Más rendszerek számára felépítésükbõl adódóan szinte lehetetlen nem meghívni a destruktorokat a kivétel kezelõjének keresésekor. amelyet az <exception> fejállományban megadott std::set_uncaught() állít be: typedef void(*terminate_handler)().4.2): int main() try { // . A terminate()-et például használhatjuk arra. a terminate() meg fogja hívni az abort()-ot. A terminate() alapértelmezés szerint az abort()-ot hívja meg (§9. hogy nem fognak visszatérni a hívójukhoz. hogy az abort() a programból való nem szabályos kilépést jelent. Ha biztosítani akarjuk a rendrakást egy el nem kapott kivétel esetében. hogy megszakítsunk egy folyamatot vagy esetleg újraindítsunk egy rendszert (új kezdeti értékadással). Ehhez hasonlóan. Az _uncaught_handler függvényekrõl a rendszer feltételezi. Jegyezzük meg. amikor a kivételkezelõ eljárás által megvalósított hibakezelõ stratégia csõdöt mondott és ideje a hibatûrés más szintjére áttérni.

illetve a kivételkezelést a C hívási sorrendjével és a hibakeresõk szabványos eljárásaival is össze szeretnénk egyeztetni. amelyeknél a kód felét a hibakeresésre szánták. Ha azonban a memóriaigényt is csökkenteni szeretnénk. Ezért bizonyos C++ fejlesztõkörnyezetek..2) segítségével kaphatjuk meg a vezérlést. vagyis kevesebb információt kapunk. } Ez a globális változók konstruktorai és destruktorai által kiváltottak kivételével minden kivételt elkap.doksi.6. ha nem is lehetetlen. hogy egy kivétel kiváltása ne legyen különösebben költséges egy függvényhíváshoz képest.hu 14. 14.. mint amit egy hibakeresõ (debugger) tudhat a program állapotáról. } catch (std::bad_alloc) { cerr << "A new kifogyott a memóriából. A kivételek és a hatékonyság Elvileg lehetséges a kivételkezelést úgy megtervezni. A globális változók kezdeti értékadása alatt fellépõ kivételeket nem lehet elkapni.\n". Nem szokatlan olyan hagyományos rendszerekkel találkozni.) { // . programok vagy fejlesztõk számára elõnyösebb lehet nem elkapni azokat a kivételeket. ami újabb ok arra.8. Ráadásul ezt úgy is meg lehet tenni. hogy lehetõség szerint kerüljük a globális változókat. A kivételek kiváltásánál a kivétel fellépésének pontos helye általában nem ismert. Egy nem lokális statikus objektum kezdeti értékadása közbeni throw esetében csak a set_unexpected() (§14. amelyek következményeit a programban nem küszöböljük ki.. hogy az ne járjon a futási idõ növekedésével. ha nem kerül sor kivétel kiváltására. hogy a kivételek használatának alternatívái sincsenek ingyen. de nehéz feladatra vállalkozunk – de emlékezzünk arra. . } catch (.Forrás: http://www.. Kivételkezelés 501 catch (std::range_error) { cerr << "Tartományhiba: már megint!\n".

.hu 502 Absztrakciós módszerek Vegyünk egy egyszerû f() függvényt..Forrás: http://www. például az atexit() és a qsort(). amely kivételek helyett hibákat kezel. if (g(1)) if (g(2)) return true. else return false. Ha az alábbi módon kijelentettük volna. hogy a C-nek csak néhány standard könyvtárbeli függvénye vált ki kivételt. és az nem is mindig létfontosságú. bool f() { string s. valamilyen más módon kellett volna a hibát jeleznie. így f()-nek tartalmaznia kell a kivétel fellépte esetén s-et megsemmisítõ kódot. amelynek látszólag semmi köze nincs a kivételkezeléshez: void g(int). Ha g() nem váltott volna ki kivételt. és ennek ismeretében jobb kódot készíthet. } Ám g() kivételt válthat ki. Érdemes megjegyezni.doksi. void f() { string s. akkor az f() számára létrehozott kódon javíthattunk volna: void g(int) throw().. De ha gondos és módszeres hibakezelés szükséges. vagyis a kivételkezelõ rendszerre hagyni. // . . akkor jobb azt a számítógépre. } A programozók szokás szerint persze nem kezelik ennyire módszeresen a hibákat. // . Az adott nyelvi változat tudhatja. A kivételek specifikációja (§14. hanem valami ilyesmi: bool g(int). g(2). hogy g() nem vált ki kivételt. g(1). Így a fentivel összehasonlítható hagyományos kód. nem a fenti egyszerû kód lenne. else return false..6) nagyon hasznos lehet a fordító által létrehozott kód javítására. így a legtöbb programban az összes C függvény az üres throw() kivétel-meghatározással adható meg. hogy a hagyományos C függvények nem váltanak ki kivételt.

A kivételkezelõ eljárás lényegénél fogva nem lokális. amely ugyanezt teheti. mint a hibakezelés. hogy vagy teljes sikerrel járjon vagy egy pontosan meghatározott módon legyen sikertelen. Hogy ez miért nem lehetséges. Vagyis a program különbözõ részeinek egyet kell érteniük abban. Kivételkezelés 503 Mielõtt egy „C függvényt” ellátnánk az üres kivétel-meghatározással (a throw()-val). gondoljuk meg. Nem minden függvény kell. Ebbõl következik. nem válthat-e ki kivételt. A feltevés az. az programról programra és programozóról programozóra változik. Nagyobb programok esetében a következõ okokat sorolhatjuk fel: . A hibakezelés egyéb módjai A kivételkezelõ eljárás célja. amennyivel csak tud. A legtöbb rendszerben nem lehet minden függvényt úgy megírni. hogy túlságosan megszenvedne vele. Az unexpected() célja ugyanígy az. anélkül. hogy a program más részeit értesítse egy kivételes körülmény észlelésérõl. A kivételkezelõk hatékony használatához átfogó stratégiára van szükségünk. ha a kivételek specifikációjára épülõ hibakezelõ rendszer mégsem jelentene a kivételek számára áthatolhatatlan falat. ez csak bonyolítaná a helyzetet. a lényegénél fogva annyira kényes területen. a maradék kezelését viszont a magasabb szintekre hagyja. Elõször is fel kell adni azt a tévhitet. hogy menekülési utat hagyjon arra az esetre. hogy a két rész függetlenül készült és a kivételt kezelõ rész tud valami értelmeset kezdeni a hibával. hogy a new operátort használja.Forrás: http://www. így alapvetõ jelentõségû. hogy az összes hibát egyetlen eljárással kezelhetjük. A terminate() ezt a kezelési módot azzal támogatja. ha maga a kivételkezelõ rendszer sérülne vagy nem tökéletes használata miatt maradnának nem elkapott kivételek. Például átírhatták. hogy a hibakezelés módjára legjobb a rendszer tervezésének legkorábbi szakaszában gondolni. egy bonyolult módszerhez nem tudnánk következetesen alkalmazkodni. hogy átfogó stratégia érvényesüljön. A sikeres hibatûrõ rendszerek többszintûek.9. hogy „tûzfalként” viselkedjen. hogy lehetõvé tegye egy programrész számára. hogy menekülési utat hagy arra az esetre. 14. Egy.hu 14. és hogy az alkalmazott módszernek (a teljes program összetettségéhez képest) egyszerûnek és pontosan meghatározottnak kell lennie. amely viszont bad_alloc-ot válthat ki vagy olyan C++-könyvtárat hívhat meg. hogyan használják a kivételeket és hol kezelik a hibákat.doksi. Mindegyik szint annyi hibával birkózik meg.

amely végül aláásná a teljes rendszer megbízhatóságát. Ez a pusztán helyi értelemben vett „megbízhatóság” olyan bonyolultsághoz vezetne. alapvetõ.Forrás: http://www. vagy meghatározott módon lesznek sikertelenek. minden programrészre kiterjedõ hibakezelési stratégia megalkotásakor figyelembe kell vennünk az egyes. } } extern "C" void call_from_C() throw() { . ha valójában eltérõ belsõ eljárást alkalmaz. Alkalmasint szükséges lehet az egyik stílusú hibajelzésrõl a másikra áttérni. c_function(). Más nyelven írt függvények nem alkalmazkodnának a szabályokhoz. Ehhez pedig tekintetbe kell vennünk olyan kérdéseket. 4. 3. hogy egy rendszer összes kódját „a nulláról” készíthessük el.doksi. Túl sok munka kellene ennek a „megbízhatóságnak” olyan biztosításához. 2. A szükséges tárterület és futási idõ növekedése nagyobb lenne az elfogadhatónál (mert rendszeresen ugyanazon hibák – például érvénytelen paraméterek – ismételt ellenõrzésére kerül sor). A kivételeket ilyen könyvtárak vagy részrendszerek felületei számára célszerû meghatározni. hogy következetesen mindenhol érvényesüljön. Például egy C könyvtári függvény meghívása után ellenõrizhetjük az errno-t és kivételt válthatunk ki. Az a cél. vagy milyen állapotba kerül egy hibája után a rendszer. hogy a programrészlet látszólag akkor is az általános hibakezelési módszer szerint kezelje a hibákat. amelyek vagy teljes sikerrel járnak.hu 504 Absztrakciós módszerek 1. mielõtt a C++-könyvtárból visszatérünk egy C programba: void callC() throw(C_blewit) { errno = 0. elkaphatunk egy kivételt és az errno-t beállíthatjuk. vagy fordítva. if (errno) { // takarítás (ha lehetséges és szükséges) throw C_blewit(errno). részrendszert vagy kulcsfontosságú függvényt így kell megtervezni. Egy fõbb könyvtárat. Rendszerint nem adatik meg az a luxus. Ugyanakkor egy program olyan különálló részrendszerekre bontása. mint hogy a programrészlet hogyan kezeli az erõforrásokat. megtehetõ és gazdaságos. Ezért egy általános. a miénktõl eltérõ módszert alkalmazó programrészleteket is.

Alkalmazzunk olyan egyszerûsítõ módszereket.. ahol elmagyarázzuk.2. } Ilyenkor fontos.1. 14. Kivételkezelés 505 try { } c_plus_plus_function(). ne kérjen segítséget vagy erõforrást a hívójától.6. hogy a kivételek kiváltása szabályosabb legyen.6. mint a „kezdeti értékadás az erõforrás megszerzésével”.) { // takarítás (ha lehetséges és szükséges) errno = E_CPLPLFCTBLEWIT. általános eszközöket mutatja be: Szabványos (a nyelvbe beépített) kivételek Név bad_alloc bad_cast bad_typeid bad_exception Kiváltó new dynamic_cast typeid kivétel_specifikáció Hivatkozás §6.4. Ezzel a hibakezelõ kódot szabályosabbá tehetjük.doksi. Szabványos kivételek A következõ táblázat a szabványos kivételeket és az azokat kiváltó függvényeket.4.10.2. hogyan lehet állapotbiztosítókat (invariánsokat) és feltevéseket használni. Lásd még a §24. A hibakezelés – a lehetõségekhez mérten – hierarchikus legyen.7.1 §15.5 §15.hu 14. ami a programot áttekinthetetlenné teszi. Az ilyen kérések az egymástól függõ elemek között körbe-körbe járó végtelen ciklusokat okoznak.4 §14. hogy következetesen járjunk el.4. hogy a hibajelentõ stílusok átalakítása teljes legyen. illetve olyan egyszerûsítõ feltevéseket.. a végtelen ciklusok lehetõségével pedig a hibákat kezelõ kódba egy nem kívánatos lehetõséget épít. operátorokat. mint „a kivételek hibákat jelentenek”. } catch (. §19.3 Fejállomány <new> <typeinfo> <typeinfo> <exception> . Ha egy függvény futási idejû hibát észlel.Forrás: http://www.1 pontot.3.

private: // .6 Fejállomány <stdexcept> <stdexcept> <stdexcept> <stdexcept> <ios> at() bitset<>::operator[ ]() invalid_argument bitset konstruktor overflow_error bitset<>::to_ulong() ios_base::failure ios_base::clear() A könyvtári kivételek a standard könyvtár exception nevû – az <exception> fejállományban megadott – kivételosztályból kiinduló osztályhierarchia tagjai: class exception { public: exception() throw()..3 §21. exception(const exception&) throw().3.3. exception& operator=(const exception&) throw().5.Forrás: http://www.. A hierarchia így néz ki: exception logic_error runtime_error lenght_error domain_error out_of_range invalid_argument bad_alloc bad_exception bad_cast bad_typeid range_error overflow_error underflow_error ios_base::failure .1 §17.doksi. virtual ~exception() throw().3.hu 506 Absztrakciós módszerek Szabványos (a standard könyvtár által kiváltott) kivételek Név out_of_range Ki dobja Hivatkozás §16.5.5.§20.3 §17.3. }.3.3. virtual const char* what() const throw().3 §17.

. Így az alábbit írhatjuk: void f() try { // a standard könyvtár használata } catch (exception& e) { cout << "standard könyvtári kivétel " << e. amit ha meghívunk.) { cout << "másik kivétel\n". Kivételkezelés 507 Ez meglehetõsen körülményesnek tûnik ahhoz képest.vagy konstruktorparaméterek ellenõrzésével el lehetne kapni. csak megfelelõen definiálják a megkívánt virtuális függvényeket.. hogy nyolc szabványos kivételt ölel fel. A kivételkezelõ rendszer fenntart a saját céljaira egy kis memóriát (például a veremben). amelyeket elvileg a program indulása elõtt. } // talán A standard könyvtárbeli kivételek az exception-bõl származnak. A logikai hibák (logic error) azok.. hogy a függvényhívásnál vagy a kivételkezelésnél fogy-e ki elõször a memória: void perverted() { try { throw exception(). } catch (.. amely a rendszer összes memóriáját elfogyasztja és így hibát kényszerít ki. Jegyezzük meg. Ebbõl következõen egy standard könyvtárbeli kivétel kiváltása nem vált ki bad_alloc kivételt. Íme egy függvény. az exception-bõl származó kivétel standard könyvtárbeli kivétel. hogy az expection-mûveletek maguk nem váltanak ki kivételt. a felhasználók ugyanis hozzáadhatják saját kivételeiket az exception hierarchiához. Természetesen írhatunk olyan kódot. hogy ott tárolhassa a kivételeket. hogy az összes.what() << '\n'. a többi viszont nem feltétlenül. A többi futási idejû hiba (run-time error).Forrás: http://www. } // ismétlõdõ kivétel . Némelyek ezt az összes hiba és kivétel számára hasznos keretrendszernek látják – én nem. így hiba lenne az összes kivételt az exception elkapásával megpróbálni lekezelni. // . hogy a hierarchia megpróbál a standard könyvtárban meghatározott kivételeken kívüli kivételek számára is használható keretrendszert adni.doksi. Hasonlóan hiba lenne feltételezni. // . kipróbálja... Oka. A standard könyvtárbeli kivételek az exception által meghatározott függvényeket nem bõvítik újakkal. függvény.hu 14.

1. 14.4. [2] Ne használjunk kivételeket. ha a helyi vezérlési szerkezetek elégségesek. [4] Nem minden programnak kell „kivételbiztosnak” lennie.2. hogy minden kivétel az exception osztály leszármazottja.what(). §14.11. §14. §14.4. §14.1. §14.2.3. [16] Legyünk résen. §14. [14] Az erõforrások kezelése hierarchikus legyen. [8] A konstruktorhibák jelzésére váltsunk ki kivételt. [5] Az invariánsok érvényességének megõrzésére használjuk a „kezdeti értékadás az erõforrás megszerzésével” módszert és a kivételkezelõket. [10] A destruktorokban kerüljük a kivételek kiváltását.3. } // ismétlõdõ függvényhívás A kimeneti utasítás egyedül azt a célt szolgálja.1. §14.doksi.4.6.9. §14. Ehelyett váltson ki kivételt és hadd döntsön a hívó.6. §14. ha a konstruktor kivételt vált ki.4. [6] Minél kevesebb try blokkot használjunk. [15] A fõbb felületekben határozzuk meg az engedélyezett kivételeket. §14. Meghatározott kezelõkód helyett a „kezdeti értékadás az erõforrás megszerzésével” módszert használjuk. §14. [18] Ne tételezzük fel. [11] A main() függvény kapja el és jelentse az összes hibát.Forrás: http://www. cout << e. [12] Különítsük el a közönséges kódot a hibakezeléstõl. [13] Gondoskodjunk arról.2. hogy egy konstruktorban minden lefoglalt erõforrást felszabadítsunk. [3] Az erõforrások kezelésére a „kezdeti értékadás az erõforrás megszerzésével” módszert használjuk.7.1.4.5 és §14.4. §14.4.9. [17] A függvényekrõl tételezzük fel.4. §14. . §14. hogy minden kivételt kiválthatnak.10. amit megengedtek nekik. hogy megakadályozza a fordítóprogramot az e nevû kivétel által felhasznált memória újrahasznosításában.5.7.4. [7] Nem minden függvénynek kell minden lehetséges hibát kezelnie.hu 508 Absztrakciós módszerek } catch (exception& e) { perverted().9. nehogy a new által lefoglalt és a kivételek fellépte esetén fel nem szabadított memória elszivárogjon. §14. §14.4. §14. [19] Egy könyvtár ne fejezze be egyoldalúan a program futását.4.5. §14. Tanácsok [1] Hibakezelésre használjunk kivételeket.4. §14. elõtte gondoskodjon paramétereinek következetes állapotba hozásáról. §14.9. §14. §14. §14. [9] Ha egy értékadás vált ki kivételt.

5) Vegyük a felhasznált operációs rendszer C felületének állományok megnyitására. Vigyázat: a §14. (*2) Írjunk visszahívások (callback) megvalósítására alkalmas osztályt vagy sablont. (*3.hu 14. 6. lezárására.5) Írjunk egy Lock osztályt. 5.doksi. 4. Ehelyett váltson ki kivételt és hadd döntsön a hívó. amely kivételekkel jelzi a futási idejû hibákat. hibaüzenetté alakítsa azokat. [21] A tervezés során minél hamarabb alakítsuk ki a hibakezelési stratégiát. és írjunk hozzájuk egyenértékû C++ függvényeket.) Hozzunk létre egy Int osztályt. olvasására. 10.5) Írjunk egy teljes Vector sablont Range és Size kivételekkel. Gyakorlatok 1. 7. 11. */ } Változtassuk meg úgy. (*2. amely valamely rendszer számára a konkurens hozzáférést támogatja. 3. írására való alapvetõ mûveleteit. hogy ezt az osztályt kell használni? 9. csak túl.9 pontbeli call_from_C() függvény nem teljesen kezel minden esetet.12[6]-beli Vector összegét a Vector méretének lekérdezése nélkül. (*2) Általánosítsuk a §14. amely a „kezdeti értékadás az erõforrás megszerzésével” módszert használja különféle típusú függvények tárolására és visszaállítására. amely egy bináris fa csúcsaiban keres egy char* típusú mezõ alapján. (*2. §14. ami kiszámítja a §14. amelyek a megfelelõ C függvényeket hívják meg. §14. (*2. (*1) Írjunk egy ciklust.. mi lenne.6. majd meghívja az abort()-ot.1.11 pontbeli Ptr_to_T osztályt mint egy olyan sablont. (*3) Írjunk függvényt. mint az elemi int típus. A hello-t tartalmazó csúcs megtalálásakor a find(”hello”) a csúcsra hivatkozó mutatóval térjen vissza. . (*1) Adott a következõ függvény: int main() { /* .és alulcsordulás esetén kivételt vált ki. Hogyan nézne ki? Hogyan lehetne használni? Mire lenne jó? Milyen hátrányok származnának abból a megkötésbõl.12. Ez miért nem jó ötlet? 8.5) Gondoljuk meg.. de hiba esetén kivételt váltanak ki.1 pontbeli STC osztályt sablonná. amely pontosan úgy viselkedik. ha egy Exception osztályt használnánk az összes kivételként használt osztály õseként (bázisosztályaként).Forrás: http://www. 2. Kivételkezelés 509 [20] Egy könyvtár ne bocsásson ki a végfelhasználónak szánt diagnosztikai üzeneteket.3. (*2. a keresés sikertelenségét kivétellel jelezzük. (*3) Egészítsük ki a §11. hogy minden kivételt elkapjon.9 14.

Bevezetés és áttekintés Ez a fejezet a származtatott osztályoknak és a virtuális függvényeknek a más nyelvi elemekkel.doksi. Öt fõ részbõl áll: §15.2 Többszörös öröklõdés §15. a szabad tár kezelésével.3 Hozzáférés-szabályozás . a névfeloldással.1. a mutatókkal és a típuskonverziókkal való kölcsönhatását tárgyalja.Forrás: http://www.hu 15 Osztályhierarchiák „Az absztrakció szelektív tudatlanság. a konstruktorokkal. például a hozzáférés-szabályozással.” (Andrew Koenig) Többszörös öröklõdés • A többértelmûség feloldása • Öröklõdés és using deklarációk • Ismétlõdõ bázisosztályok • Virtuális bázisosztályok • A többszörös öröklõdés használata • Hozzáférés-szabályozás • Védett tagok • Bázisosztályok elérése • Futási idejû típusinformáció • dynamic_cast • Statikus és dinamikus konverzió • typeid • Kiterjesztett típusinformáció • A futási idejû típusinformáció helyes és helytelen használata • Tagra hivatkozó mutatók • Szabad tár • „Virtuális konstruktorok” • Tanácsok • Gyakorlatok 15.

delay(10). Így például a virtuális függvények meghívási módja biztosítja. Mivel a legtöbb ilyen háló hagyományosan fa szerkezetû. A Satellite-okra saját mûveleteiken kívül a Task-ok és Displayed-ek mûveleteinek uniója is alkalmazható: void f(Satellite& { s.5 Tagokra hivatkozó mutatók §15. Ekkor olyan szimulált egyedeket határozhatunk meg. mint a Satellite (Mûhold): class Satellite : public Task. Többszörös öröklõdés Ahogy a §2.transmit(). ahol a párhuzamos tevékenységeket a Task (Feladat) osztállyal..4 Futási idejû típusazonosítás §15. függetlenül attól. s.5..3 pontokban láttuk. azaz több osztályt is megadhatunk a : jel után az osztály deklarációjában. public Displayed { // . illetve az osztályhálók fordítási és futási idejû bejárásának eszközeirõl szól.6 A szabad tár használata Az osztályokat általában bázisosztályok „hálójából” hozzuk létre.draw(). az adatgyûjtést és -megjelenítést pedig a Displayed (Megjelenítés) osztállyal ábrázoljuk. Több közvetlen bázisosztály használatát szokás szerint többszörös öröklõdésnek (multiple inheritance) nevezik. s. Az osztályokat célszerû úgy megtervezni. Vegyünk egy szimulációs programot.Forrás: http://www. } s) // Displayed::draw() // Task::delay() // Satellite::transmit() . hogy ha egy f() függvényt meghívunk egy objektumra. }.doksi. hogy a felhasználóknak ne kelljen indokolatlan mértékben azzal törõdniük. hogy egy osztály milyen módon épül fel más osztályokból. Az egyszeres öröklõdésnél (single inheritance) csak egy közvetlen bázisosztály van.hu 512 Absztrakciós módszerek §15. akkor mindig ugyanaz a függvény hajtódik végre.2. 15.4 és §12. Ez a fejezet az osztályhálók összeállításának és az osztálytagok elérésének módjairól. egy osztálynak több közvetlen bázisosztálya is lehet. az osztályokból álló hálókat vagy osztályhálókat (class lattice) gyakran nevezik osztályhierarchiának (class hierarchy) is. hogy a hierarchia melyik osztálya deklarálta a függvényt a meghíváshoz.

Volt – és talán még mindig van – egy olyan program. }. public Displayed { // . illetve Satellite::pending() fog meghívódni. void g(Satellite* p) { highlight(p). Jegyezzük meg. ha egy Satellite-ot Displayed-ként.doksi.. A program mûholdakat. // mutató átadása a Satellite Task részére } A program létrehozása nyilván valamilyen egyszerû eljárás alkalmazását követeli meg a fordítóprogramtól. class Displayed { // . virtual void draw() = 0. a Satellite példát a valóságból merítettük. bármilyen meglepõ. class Satellite : public Task. amely a többszörös öröklõdés leírására itt használt minta szerint épült fel. // mutató átadása a Satellite Displayed részére suspend(p).Forrás: http://www. hogy ha csak egyszeres öröklõdést használhatnánk. Egy Satellite vagy Task. Osztályhierarchiák 513 Hasonlóan. Ezen lehetõségek mindegyike csökkenti a rugalmasságot.. // felülírja a Task::pending() függvényt void draw(). illetve Task-ként kezelünk. Displayed és Task osztályok megvalósításának megválasztásában. vagy Displayed lehetne. void pending(). // felülírja a Displayed::draw() függvényt }. hogy Satellite::draw(). mint a Displayed-et várók. Mi szüksége lehet bárkinek egy Satellite osztályra? Nos. virtual void pending() = 0. hogy a Task-ot váró függvények más részét lássák egy Satellite-nak.. A virtuális függvények a megszokott módon mûködnek: class Task { // . .hu 15. }. de nem mindkettõ (hacsak a Task nem a Displayed-bõl származik vagy fordítva)... akkor ez a körülmény korlátozná a programozót a Satellite. ha egy függvény Task vagy Displayed paramétert vár. Ez biztosítja.. void suspend(Task*). akkor adhatunk neki egy Satellite-ot is: void highlight(Displayed*).

A különbözõ körülmények utánzása számos megjelenítési és hibakeresési mûveletet igényel. dip = sp->Displayed::get_debug().hu 514 Absztrakciós módszerek földi állomásokat stb. dip = sp->Task::get_debug().. Egy ilyen szimuláció birtokában meg tudjuk válaszolni a forgalmi adatokra vonatkozó kérdéseket....2. Ha egy Satellite-ot használunk. class Displayed { // . akkor ezeket a függvényeket egyértelmûen kell megneveznünk: void f(Satellite* sp) { debug_info* dip = sp->get_debug(). ha egy földi állomást vihar akadályoz.. virtual debug_info* get_debug().doksi. } // hiba: többértelmû // rendben // rendben Az explicit megnevezés azonban zavaró. meg tudjuk határozni. Ennek legegyszerûbb módja. A többértelmûség feloldása Két bázisosztálynak lehetnek azonos nevû tagfüggvényei is: class Task { // . debug_info* get_debug() { // felülírja a Task::get_debug() és // Displayed::get_debug() függvényeket . illetve a hibakeresés és -elhárítás céljából. az elemzés.. }. mi történik. illetve részegységeik állapotának tárolására is. public Displayed { // . mûholdas és földi kapcsolatok elõnyeit-hátrányait tudjuk elemezni stb. }. ezért általában legjobb elkerülni az ilyen problémákat. virtual debug_info* get_debug(). és szükség van a Satellite-okhoz hasonló objektumok. ha a származtatott osztályban készítünk egy új függvényt: class Satellite : public Task. magába foglaló hírközlési rendszerek szerkezetének tanulmányozására szolgált.1. 15.Forrás: http://www.

Amikor az ilyen nevek ütköznek.. debug_info* dip2 = Displayed::get_debug(). akkor azt fogja használni. ez gyakran meglepetésként éri a felhasználót: . 15.4).2. valahányszor get_debug()-ot hívunk meg egy Satellite objektumra. Egymással alapvetõen nem rokon osztályok egyesítésekor. Osztályhierarchiák 515 }. Displayed::draw(). Ha csak egyet talál.Forrás: http://www. void draw() { draw(). // hoppá!: rekurzív hívás // megtalálja a Displayed::draw-t // felesleges kétszeri minõsítés Vagyis ha a Satellite::draw nem a Satellite osztályban bevezetett draw-t jelenti.hu 15. Mivel a Satellite::get_debug() elfedi mindkét bázisosztályának get_debug() függvényét. például a Task és Displayed osztályok Satellite-té való „összegyúrásánál” az elnevezésekben megmutatkozó hasonlóság általában nem jelent közös célt. Ezáltal a Satellite bázisosztályaira vonatkozó információk felhasználását helyhez kötöttük. } debug_info* dip1 = Task::get_debug(). ha többet vagy egyet sem.. a Satellite::get_debug() hívódik meg. akkor a fordítóprogram végignézi a bázisosztályokat. Öröklõdés és using deklarációk A túlterhelés feloldására nem kerül sor különbözõ osztályok hatókörén keresztül (§7. return dip1->merge(dip2).doksi.2. Satellite::Displayed::draw(). vagyis Task::draw-t és Displayed::draw-t keres. } }. a Satellite::draw ismeretlen vagy többértelmû lesz. A Telstar::draw minõsített név a Telstar-ban vagy valamelyik bázisosztályában megadott draw-ra vonatkozhat: class Telstar : public Satellite { // . Satellite::draw(). a különbözõ bázisosztályok függvényei közötti többértelmûségek feloldása pedig nem történik meg a paramétertípus alapján.

. void g(Satellite* p) { p->debug(1). többértelmû: Displayed::debug(int) vagy // Task::debug(double) ? // rendben // rendben De mi van akkor. void debug(int v). ha a prioritás // alacsonyabb p-nél // minél nagyobb 'v.. // elfedi A::f(char)-t AB f(AB).. void debug(double p)..Forrás: http://www.. public Displayed { // . class AB: public A. p->Task::debug(1). és a felhasználó számára kívánatos lenne a paramétertípus alapján választani közülük? Ebben az esetben a függvényeket a using deklarációval (§8..2) hozhatjuk közös hatókörbe: class A { public: int f(int). }. char f(char).. }. }. // . using B::f. char f(char).hu 516 Absztrakciós módszerek class Task { // . .. class Displayed { // .' annál több hibakeresési információ íródik ki class Satellite : public Task. }. ha a különbözõ bázisosztályokban tudatos tervezési döntés következtében szerepelnek azonos nevek. public B { public: using A::f. } // hiba. p->Displayed::debug(1).doksi. // . class B { public: double f(double). }. // információ kiírása csak akkor..2. }..

15.f(1).3. Ismétlõdõ bázisosztályok Azáltal.2.Forrás: http://www.doksi.2.f('a'). annak származtatott osztályán. a using direktívák (§8. csak az egyébként is hozzáférhetõ információk kényelmesebb használatát teszik lehetõvé (§15... hogy több bázisosztály lehet.és származtatott osztályok függvényeibõl túlterhelt függvények halmazát állítsuk elõ. }. elõfordulhat.3.2. class Task : public Link { // a Link-et a Task-ok listájához (az ütemezõ listához) használjuk // .3. }. A származtatott osztályban megadott függvények elfedik (hide) a bázisosztály függvényeit. hogy egy osztály kétszer fordul elõ a bázisosztályok között. a Satellite-oknak két Link-je lenne: struct Link { Link* next.2. ab.2. ab. ab. illetve annak tagfüggvényein kívül.f(ab)..3) pedig nem szerepelhetnek egy osztály definiciójában és nem vonatkozhatnak osztályra. Egy osztálydeklaráció using deklarációjának (§8.hu 15.2) egy bázisosztály tagjára kell vonatkoznia. class Displayed : public Link { // a Link-et a Displayed objektumok listájához (a megjelenítési listához) használjuk // . Például ha mind a Task. A bázisosztály virtuális függvényei ugyanúgy felülírhatók (override).0). mint egyébként (§15.f(2. A using deklarációk nem szolgálhatnak kiegészítõ információ elérésére sem.. Egy osztály tagjára vonatkozó using deklaráció nem szerepelhet az osztályon. Osztályhierarchiák 517 void g(AB& ab) { ab. }. mind a Displayed a Link (Kapcsolat) osztályból származott volna.1). hogy a bázis. . amelyek egyébként elérhetõek lennének.2). } // A::f(int) // AB::f(char) // B::f(double) // AB::f(AB) A using deklarációk lehetõvé teszik.

3. a többértelmûség elkerülése érdekében a hivatkozást minõsíteni kell: void mess_with_links(Satellite* p) { p->next = 0. Természetesen a Link osztály tagjaira nem hivatkozhatunk a kétértelmûség veszélye nélkül (§15. } // hiba: többértelmû (melyik Link?) // hiba: többértelmû (melyik Link?) // rendben // rendben Ez pontosan ugyanaz az eljárás. Két külön Link objektum szolgál a listák ábrázolására és a két lista nem zavarja egymást.1).3. 15.doksi. ahonnét annak több példánya is látható. .Forrás: http://www.4) alkalmazhatunk.hu 518 Absztrakciós módszerek Ez nem gond. Egy objektumnak saját magát egy fájlból kiolvasni vagy oda visszaírni való képességét például így ábrázolhatjuk: class Storable { public: virtual const char* get_file() = 0.1. p->Displayed::Link::next = 0. Felülírás A többször szereplõ bázisosztályok valamely virtuális függvényét a származtatott osztály (egyetlen) függvénye felülírhatja (felülbírálhatja. A Link-hez hasonlóan többször szereplõ bázisosztályok olyan elemek. // . mint amit a tagokra való többértelmû hivatkozások feloldására használunk (§15.2.2.2. Ha egy ilyen osztályra olyan pontról kell hivatkozni. Egy Satellite objektumot így rajzolhatunk le: Link Link Task Satellite Displayed Ha a közös bázisosztályt nem szabad két külön objektummal ábrázolni.. p->Task::Link::next = 0. p->Link::next = 0.1). virtuális bázisosztályokat (§15. virtual void read() = 0. override).2. amelyeket nem szabad a közvetlenül öröklõ osztályon kívül használni..

. }. virtual void write() = 0. public Receiver { public: const char* get_file(). // . hogyan lehet az egyes write() függvényeket a származtatott osztályok külön függvényeivel felülírni. }. Például leállíthatunk és újraindíthatunk egy szimulációt. Osztályhierarchiák 519 }. void write(). class Receiver : public Storable { public: void write().4. void read(). }. Arról.. // .. amelyek függetlenül vagy együtt használva jobban kidolgozott osztályokat adnak.Forrás: http://www. . a §25.. ha mentjük az alkotóelemeket és késõbb visszaállítjuk azokat.2 pont írja le. virtual ~Storable() { } Természetesen több felhasználó építhet erre.. A felülíró függvény általában meghívja a bázisosztálybeli változatokat és a származtatott osztályra jellemzõ tennivalókat végzi el: void Radio::write() { Transmitter::write(). hogy olyan osztályokat írjon.6 pont szól. // kiírja a Radio-ra jellemzõ adatokat } Az ismétlõdõ bázisosztályokról származtatott osztályokra való típuskonverziót a §15.doksi. Ezt az ötletet így valósíthatjuk meg: class Transmitter : public Storable { public: void write()..hu 15. Receiver::write(). // . class Radio : public Transmitter.

hogy osztozzék. kényelmesen és hatékonyan lehet többszörözni.2.4. Storable(const Storable&).Forrás: http://www..hu 520 Absztrakciós módszerek 15. // . private: const char* store. Virtuális bázisosztályok Az elõzõ pont Radio példája azért mûködik. }. class Receiver : public virtual Storable { public: void write(). public Receiver { public: void write(). A származtatott osztály minden virtuális bázisosztályát ugyanaz a (megosztott) objektum ábrázolja: class Transmitter : public virtual Storable { public: void write(). Az objektum összes része a Storable azonos példányán kell. // . mint ami tartalmazza az objektum mentésére használt fájl nevét: class Storable { public: Storable(const char* s).. A Storable osztályt például úgy is meghatározhatnánk. mert a Storable osztályt biztonságosan. virtual void write() = 0. Storable& operator=(const Storable&). }.. amelyek jó építõkövei más osztályoknak. }. . különben szükségtelenül nehéz feladat lenne az objektum többszöri tárolásának megakadályozása. virtual void read() = 0.. A virtuális bázisosztályok (virtual base class) ezt a megosztást segítik. A Storable ezen látszólag csekély módosítása után meg kell változtatnunk a Radio szerkezetét is. Ez azonban az olyan osztályok esetében rendszerint nem igaz... virtual ~Storable(). }. class Radio : public Transmitter. // .doksi.

a nem virtuális bázisosztályokat viszont saját részobjektumuk. }.1.. Osztályhierarchiák 521 Ábrával: Storable Receiver Radio Transmitter Hasonlítsuk össze ezt az ábrát a Satellite objektum §15..2. azaz a legtávolabbi származtatott osztály konstruktora hívja meg (automatikusan vagy közvetlenül): class A { // . hogy a bázisosztályt meg kell-e osztani egyéb származtatott osztályokkal. hogy lássuk a különbséget a közönséges és a virtuális öröklõdés között. 15.2. például mert a nyelv elõírja.doksi. Virtuális bázisosztályok programozása Amikor a programozó függvényeket készít egy olyan osztály számára.Forrás: http://www..hu 15. ami gondot jelenthet. A virtuális bázisosztály konstruktorát a teljes objektum.. }. Az öröklõdési gráfban egy adott nevû osztály minden virtuálisként megadott bázisosztályát az osztály egyetlen objektuma ábrázolja.3-beli rajzával. // .4. nem tudhatja. hogy egy virtuális bázisosztály konstruktora csak egyszer futhat le. class B { public: B(). ha egy szolgáltatást úgy kell megvalósítani. // nincs konstruktor // alapértelmezett konstruktor // nincs alapértelmezett konstruktor . class C { public: C(int). }. amelynek virtuális bázisosztálya van. hogy a bázisosztály egy adott függvényének meghívására pontosan egyszer kerüljön sor.

. */ } // hiba: C-nek nincs alapértelmezett konstruktora D(int i) : C(i) { /* . ha a virtuális bázisosztály függvényét csak a legtávolabbi származtatott osztályból hívja meg. // a szegély megjelenítése void draw(). }. Szükség esetén a programozó ezt a mûködést utánozhatja is. }. // rendben // . Az ablakokat emellett többféle módon díszíthetjük és szolgáltatásokkal egészíthetjük ki: class Window_with_border : public virtual Window { // a szegély kódja void own_draw().. Tegyük fel. */ }..hu 522 Absztrakciós módszerek class D : virtual public A.. Az own_draw() függvényeknek nem kell virtuálisaknak lenniük. hogy van egy alapvetõ Window osztályunk. mert egy virtuális draw() függvénybõl akarjuk meghívni azokat.Forrás: http://www. class Window_with_menu : public virtual Window { // a menü kódja void own_draw().. Ebbõl egy mûködõképes Clock osztályt állíthatunk össze: class Clock : public Window_with_border. virtual public C { D() { /* . }. }. }. // a menü megjelenítése void draw(). ami pontosan ismeri az objektum típusát.. A virtuális bázisosztály konstruktora a származtatott osztályok konstruktora elõtt hívódik meg. public Window_with_menu { // az óra kódja void own_draw().. virtual public B. amely ki tudja rajzolni tartalmát: class Window { // alapkód virtual void draw(). amelyre meghívták. // az óralap és a mutatók megjelenítése void draw().doksi.

A Task és Displayed osztályokból a §15. Window_with_menu::own_draw(). // az óralap és a mutatók megjelenítése } A virtuális bázisosztályokról származtatott osztályokra való konverziót a §15. 15. hogy bármelyik draw() meghívása pontosan egyszer hívja meg a Window::draw()-t.hu 15.Forrás: http://www.5. // a szegély megjelenítése } void Window_with_menu::draw() { Window::draw(). own_draw(). own_draw().2. milyen fajta Window-ra hívták meg: void Window_with_border::draw() { Window::draw(). . own_draw(). függetlenül attól. A többszörös öröklõdés használata A többszörös öröklõdés legegyszerûbb és legnyilvánvalóbb felhasználása két egyébként egymással rokonságban nem álló osztály „összeragasztása” egy harmadik osztály részeként.4.2 pontban összerakott Satellite osztály is ilyen. Osztályhierarchiák 523 Ábrával: Window Window_with_border Clock Window_with_menu A draw() függvényeket most már úgy írhatjuk meg az own_draw() függvények felhasználásával. Window_with_border::own_draw().2 pont írja le.doksi. // a menü megjelenítése } void Clock::draw() { Window::draw().

mert a származtatott osztálynak mind a felület.4 pont Ival_box osztályait.4. hatékony és fontos – de nem túl érdekes. Vegyük például újra a §12. A többszörös öröklõdés szerepe itt lényegbevágó. Az egyik egy nyilvános absztrakt osztály. hogy egyetlen közös õs jelentette függés bevezetése nélkül osztozhassanak adatokon. A többszörös öröklõdés lehetõvé teszi a testvérosztályok számára. protected BBslider // megvalósítás { // az 'Ival_slider' és a 'BBslider' által igényelt függvények megvalósítása // a 'BBslider' szolgáltatásainak használata }.Forrás: http://www. Ez az eset.doksi. amely a megvalósításról gondoskodik.3-beli BB_ival_slider osztály egy példa erre: class BB_ival_slider : public Ival_slider // felület . A többszörös öröklõdés használata absztrakt osztályok készítésére már nagyobb jelentõségû.2. Egy módszernek azonban nem kell „okosnak” lennie ahhoz. hogy a káró alakú öröklési háló akkor kezelhetõ a legjobban. A kétféle szerep az osztályok stílusában és az alkalmazott hozzáférési szabályokban tükrözõdik.4. Ebben a példában a két bázisosztály logikailag különbözõ szerepet játszik. ha vagy a virtuális bázisosztály vagy a belõle közvetlenül származó osztályok absztraktak. a másik pedig egy védett (protected) konkrét osztály. vagyis hogy kizárólag felületek. és az egyes részeken való osztozás is csak a megvalósítás céljára használt ablakozó rendszer klasszikus hierarchiájában történt. (Lásd a Radio (§15. Ott végül az összes Ival_box osztályt absztrakttá tettük. így az adatbekérõ mezõk kezelésén . mind a megvalósítás virtuális függvényeit felül kell írnia. hiszen alapjában véve csak a továbbító függvények megírásától kíméli meg a programozót. hogy hasznos legyen. Ez lehetõvé tette. annyiban. Az eljárás nem befolyásolja számottevõen a program általános szerkezetét és alkalmasint ütközhet azzal a kívánalommal. hogy a Popup_ival_slider-t megvalósító osztály nagy része közös legyen a sima Ival_slider-t megvalósító osztályéval. hogy a lényegi programkód minden részét a megfelelõ megvalósító osztályokba rejtsük. A §12.hu 524 Absztrakciós módszerek A többszörös öröklõdés ilyen módon való használata egyszerû.2. hogy kifejezzük szerepüket. Az én véleményem az.4) és a Clock (§15. hogy a megvalósítás részletei maradjanak rejtve.) Ha a bázisosztály nem ismételhetõ. hogy befolyásolja a program tervezésének módját. amikor az úgynevezett káró alakú öröklõdés (diamond-shaped inheritance) lép fel. Persze lenne értelme annak. akkor virtuális (és nem közönséges) bázisosztályra van szükség. amely a felületet nyújtja.1) példákat.

Ahogy a §15. egy változókat nem tartalmazó absztrakt osztály veszély nélkül ismételhetõ. Így a virtuális bázisosztály helyett közönségeset alkalmazhatunk: class BB_ival_slider : public Ival_slider. Ha az ötletet végigvisszük.. Ehhez az Ival_slider-t virtuálissá tesszük: class BB_ival_slider : public virtual Ival_slider..Forrás: http://www.. akkor a program felületét képezõ absztrakt osztályokból való minden származtatást virtuálissá teszünk. annak egyrészt a hagyományok figyelembe vétele az oka.2. protected BB_ival_slider { /* .és idõigényesek.. protected BBslider { /* . Ekkor az is természetes lenne.. . Ábrával: Ival_slider BBslider Popup_ival_slider BB_ival_slider BB_popup_ival_slider Könnyen elképzelhetjük a Popup_ival_slider-bõl származó további felületeket és az azokból és a BB_popup_ival_slider-bõl származó további megvalósító osztályokat.. class Popup_ival_slider : public Ival_slider { /* . class BB_popup_ival_slider : public Popup_ival_slider. hogy az Ival_box-ot ábrázoló objektum általában csak egy mutatót tartalmaz a virtuális táblára. hogy az elõálló csúszka-osztályban elkerüljük az Ival_slider objektumok ismétlõdését..doksi. Ez valóban a leglogikusabb. */ }. és legrugalmasabb megoldásnak tûnik.hu 15. Mielõtt ez a tárigényben és futási idõben mért költség visszariasztana bennünket egy más szemszögbõl vonzó szerkezet választásától. hogy kiterjedt használatuk egy osztályon belül nem vonzó. Hogy miért nem tettem ezt. protected BB_ival_slider { /* .. */ }. protected BBslider { /* . */ }.. */ }... Osztályhierarchiák 525 kívül azonosak lennének. class BB_popup_ival_slider : public virtual Popup_ival_slider. másrészt a virtuális bázisosztályok megvalósításának legnyilvánvalóbb és leggyakoribb módszerei annyira hely.4 pontban láttuk. legáltalánosabb. gondoljuk meg. */ }.. class Popup_ival_slider : public virtual Ival_slider { /* . */ }.

}. void set_color(Color). A virtuális bázisosztályok függvényeinek felülírása A származtatott osztályok felülírhatják (override) közvetlen vagy közvetett virtuális bázisosztályaik virtuális függvényeit.. virtual void set_color(Color) = 0. a Window_with_menu pedig a prompt()ot..5.. 15. Ekkor a Window_with_border felülbírálhatja a set_color()-t. mint a felhasználói felület kezelésének részét: class Window { // .Forrás: http://www.2.doksi. // felhasználói tevékenységek kezelése }. . virtual void prompt() = 0.. bevallottan világosabb szerkezetnek. class Window_with_menu : public virtual Window { // . mint a színellenõrzõ séma részét. // háttérszín beállítása class Window_with_border : public virtual Window { // .hu 526 Absztrakciós módszerek Ábrával: Ival_slider Ival_slider BBslider Popup_ival_slider BB_ival_slider BB_popup_ival_slider Ez valószínûleg megvalósítható... void prompt(). // háttérszín kezelése }.1. optimalizált változata az elõzõekben bemutatott. A Window osztálynak lehetnek például set_color() és prompt() függvényei. így több származtatott osztály járulhat hozzá a virtuális bázisosztály által adott felület megvalósításához. Két különbözõ osztály akár a virtuális bázisosztály különbözõ függvényeit is felülírhatja.

vagyis egy függvénynek az összeset felül kell írnia. public Window_with_border { // .Forrás: http://www. public Window_with_border { // . akkor a Receiver és Transmitter osztályokbeli write() deklarációk a Radio-ban hibát okoztak volna. .4. ha különbözõ származtatott osztályok ugyanazt a függvényt írják felül? Ez csak akkor megengedett.. }. az ilyen konfliktust a felülíró függvénynek a legtávolabbi származtatott osztályból való meghívásával oldhatjuk meg. A My_window például felülírhatja a prompt()-t. prompt() } Window_with_border { set_color() } Window_with_menu { prompt() } My_window { prompt() } Ha két osztály felülír egy bázisosztálybeli függvényt. Ábrával: Window { set_color().. ha az egyik származtatott osztály minden olyan osztály örököse.. Mint a Radio esetében is. de a két függvény egyike nem írja felül a másikat. gyakran „mixin”-nek nevezik. hogy a Window_with_menu-belinél jobb változatot adjon: class My_window : public Window_with_menu. Az olyan osztályokat. amelyek egy virtuális bázisosztály némelyik (de nem mindegyik) függvényének megvalósítását tartalmazzák. Ilyenkor nem lehet virtuális függvénytáblát építeni. Osztályhierarchiák 527 class My_window : public Window_with_menu. akkor az osztályhierarchia hibás. Például ha a §15. mert a teljes objektumra vonatkozó függvényhívás kétértelmû lenne.2.doksi. Mi történik.hu 15. void prompt(). amely felülírja a függvényt. pontbeli Radio nem adta volna meg a write() függvényt..// a felhasználói tevékenységek kezelését nem hagyjuk a bázisosztályra }.

1) listaosztálynak valószínûleg szüksége van az elemeket nyilvántartó adatszerkezetekre. Az ilyen információ legjobb.doksi. típusok.3. ♦ Ha védett. valamint az osztályból származtatott osztályok és barátaik tagfüggvényeiben lehet felhasználni. ha privát: . Hozzáférés-szabályozás Egy osztálytag lehet privát (private). a nevét mindenhol fel lehet használni. az érdektelen a hozzáférés szempontjából. §16. védett (protected) vagy nyilvános (public): ♦ Ha privát. a nevét csak a tagfüggvényekben és a deklaráló osztály barátaiban (friend-jeiben) lehet felhasználni. egy származtatott osztályt megvalósító függvények (azaz a származtatott osztályok barátai és a tagok). hogy egy osztályt háromféle függvény érhet el: az osztályt megvalósító függvények (azaz a barátok és a tagok). ♦ Ha nyilvános. a nevét csak a deklaráló osztály és barátai tagfüggvényeiben. és az egyéb függvények. állandók stb. Ez azt jelenti. hogy ugyanúgy lehetnek privát tagfüggvények.hu 528 Absztrakciós módszerek 15. Ez azt a nézetet tükrözi. Hogy a név mit jelöl. Ezt így ábrázolhatjuk: általános felhasználók származtatott osztály tagfüggvényei és barátai saját tagfüggvények és barátok nyilvános: védett: privát: A hozzáférési szabályok egyöntetûen vonatkoznak a nevekre.Forrás: http://www.2. Például egy hatékony nem tolakodó (non-intrusive. mint privát adattagok..

// kivételosztály void insert(T). Link* head. free = p. Chunk* allocated. free = free->next. template<class T> void List<T>::insert(T val) { Link* lnk = get_free().hu 15. Link* next. lnk->next = head. } template<class T> List<T>::Link* List<T>::get_free() { if (free == 0) { // új Chunk lefoglalása és Link-jeinek a szabad listára helyezése } Link* p = free.doksi. return p->val. public: class Underflow { }. head = p->next. struct Chunk { enum { chunk_size = 15 }. T get().. }. Link* get_free(). } . }. } template<class T> T List<T>::get() { if (head == 0) throw Underflow(). return p.Forrás: http://www.. Osztályhierarchiák 529 template<class T> class List { private: struct Link { T val. Link v[chunk_size]. lnk->val = val. }. Link* p= head. Link* free. Chunk* next. // . p->next = free. head = lnk.

. nem kényelmesek. // hiba: List<T>::free privát // . Mivel a get_free() visszatérési értékét elõbb említjük.. protected: void own_draw().Forrás: http://www. q = p->free. }. Védett tagok A védett (protected) tagok használatának bemutatására vegyük a §15..1 pontbeli Window példát. if (List<T>::Chunk::chunk_size > 31) { // hiba: List<T>::Chunk::chunk_size privát // . // .hu 530 Absztrakciós módszerek A List<T> hatókörbe azáltal lépünk be.3.. Arra a célra terveztük õket.4. egy védett és egy nyilvános felületre való szétválasztásával lehet kifejezni: class Window_with_border { public: virtual void draw().8).. a struktúrák (struct) tagjai nyilvánosak (public. // hiba: List<T>::Link privát // . §10. Ezt a különbséget a Window osztály felületének két. A nem tag függvényeknek – a barát (friend) függvények kivételével – nincs ilyen hozzáférésük: void would_be_meddler(List<T>* p) { List<T>::Link* q = 0. az általános felhasználás számára nem biztonságosak. . hogy a tagfüggvényben List<T>::-t írunk. a Link rövidítés helyett a teljes List<T>::Link nevet kell használnunk. // egyéb kirajzoló kód private: // ábrázolás stb.2. hogy csak a származtatott osztályok számára szolgáljanak építõkövekül.1.doksi. Az own_draw() függvények (akarattal) nem teljes körû szolgáltatást adnak.2. mint a List<T>::get_free() nevet. } } Az osztályok (class) tagjai alapértelmezés szerint privátok (private)... 15. Másfelõl a draw() mûveletek az általános felhasználást szolgálják..

A védett tagok használata Az adatok elrejtésének egyszerû privát/nyilvános modellje jól szolgálja a konkrét típusokat (§10. A protected-ként deklarált tagokkal sokkal könnyebb visszaélni. hogy az egyik származtatott osztály összezavarja a másik származtatott osztály adatait. de a származtatott osztályok megfelelõ kezelését nem.1. 15. mint a nyilvánosakat – nem könnyû átszervezni. akkor kétféle felhasználója lesz egy osztálynak: a származtatott osztályok és „a nagyközönség”. jelentõs mennyiségû adat közös osztályban való elhelyezése helyett mindig adódik más megoldás. Az én tapasztalatom az. Osztályhierarchiák 531 A származtatott osztály a bázisosztály védett tagjai közül csak a saját osztályába tartozó objektumokat tudja elérni: class Buffer { protected: char a[128]. class Cyclic_buffer : public Buffer { // . hogy a védett tagokat – csakúgy. Ha jelentõs mennyiségû adatot úgy helyezünk el egy közös osztályban. Még rosszabb. mint a privátként bevezetettekkel. void f(Linked_buffer* p) { a[0] = 0. }. */ }.3). hogy az összes származtatott osztály használhatja azokat. .3. A mûveleteket megvalósító tagok és barátok ezen felhasználók érdekében kezelik az objektumokat.. így a védett tagok megnehezítik a program módosítását. // hiba: más típus védett tagját próbáltuk elérni } }. az osztályokra a privát az alapértelmezett hozzáférési kategória és általában ez a jobb választás. Ez megakadályozza az olyan hibákat.. De ha származtatott osztályokat használunk. Szerencsére nem kell feltétlenül védett tagokat használni.1. mert nincs jó módszer az összes használat felderítésére. Ezért a tagok védettként való megadása általában tervezési hiba. amelyek azáltal léphetnének fel. // rendben: Cyclic_buffer saját védett tagját éri el p->a[0] = 0. az adatok sérülhetnek. // .. hogy az összes származtatott osztály által közvetlenül használható.Forrás: http://www.doksi.. A privát/nyilvános modell lehetõvé teszi a megvalósítás és az általános felhasználás pontos megkülönböztetését.. class Linked_buffer : public Buffer { /* .hu 15..

Forrás: http://www.4. */ }. Ha azt is biztosítani szeretnénk.3. A privát bázisosztályok akkor a leghasznosabbak. */ }. ahol jellemzõen további öröklés történik (a §12. struct esetén nyilvános lesz: class XX : B { /* . bázisosztályát privátként kell megadnunk. */ }. class Z : private B { /* . A védett és a privát öröklés a megvalósítás módjának jelölésére használatos. A nyilvános öröklés a származtatott osztályt a bázisosztály egy altípusává (subtype) teszi. a protected minõsítõvel remekül adhatunk meg a származtatott osztályokban használható mûveleteket. */ }.. Ha ebben a példában a megvalósító osztály privát lett volna.. amikor egy osztályt a felület korlátozásával határozunk meg.2.5). A tagok elérhetõségére példákat a §C..2 pontbeli Ival_slider is példa erre. a további öröklés lehetetlenné vált volna..4. Ilyenkor az alapértelmezett hozzáférése class esetén privát...doksi..11.. ami által erõsebb garanciák adhatók. // B nyilvános bázisosztály Az olvashatóság szempontjából azonban legjobb kiírni a hozzáférési szintet megadó kulcsszót.2) való minden hozzáférés ellenõrzött legyen. */ }. Csak D barátai és tagjai konvertálhatnak egy D* mutatót B*-gá. A védett öröklés olyan osztályhierarchiákban a leghasznosabb. ez az öröklés legáltalánosabb formája. hogy ezek a kifogások nem érvényesek a védett tagfüggvényekre. így a Vec-ek nem konvertálódnak nem ellenõrzött vector-rá: template <class T > class Vec : private Vector <T> { /* . akkor nyilvános és védett tagjai csak D tagfüggvényeibõl és barátaiból érhetõk el. Bázisosztályok elérése A tagokhoz hasonlóan egy bázisosztály is lehet nyilvános. védett vagy privát: class X : public B { /* . hogy a Vec-hez (§3.1 tartalmaz. 15... . // B privát bázisosztály struct YY : B { /* . A §12.. Az osztály hozzáférési szintjét nem muszáj explicit megadni. A bázisosztály hozzáférési szintje az osztály tagjainak elérhetõsége mellett a származtatott osztályra hivatkozó mutatóknak vagy referenciáknak a bázisosztály típusára való konvertálhatóságát is jelzi.hu 532 Absztrakciós módszerek Jegyezzük meg. Vegyünk egy B bázisosztályból származó D osztályt: ♦ Ha B privát bázisosztály.2 pontbeli Ival_slider jó példa erre).7. class Y : protected B { /* .. */ }. A mutatókra vonatkozó Vector például érték-ellenõrzéssel bõvíti ki Vector<void*> bázisosztályát (§13.

3. ♦ Ha B nyilvános bázisosztály.2. mint felületének része. private D2 { /* . akkor abban az esetben lesz elérhetõ. class DD : public D1. Osztályhierarchiák 533 ♦ Ha B védett bázisosztály. attól még egyértelmûen.3). nyilvános tagjai bárhol használhatók.4. B* pb = pd. static int sm. Bármely függvény végezhet D*-ról B*-ra való konverziót. */ } .1. A bázisosztályok elérhetõségére a §C. public X2 { /* . */ }. DD* pd = new DD..hu 15. class X2 : public B { /* . mert a BBwindow inkább az Ival_slider megvalósításának. valamint a D-bõl származó osztályok tagfüggvényeibõl és barátaiból érhetõk el... A többszörös öröklõdés és az elérhetõség Ha egy nevet vagy bázisosztályt egy többszörös öröklõdési háló több útvonalán is elérhetünk.. hogy privát bázisosztállyá teszem. ha valamelyik út mentén elérhetõ: struct B { int m.. */ }.Forrás: http://www.doksi. int i1 = pd->m.2 mutat példákat. A bázisosztályok elérhetõségét ugyanazon szempontok szerint adjuk meg. A BBwindow-t például azért adtam meg az Ival_slider védett bázisosztályaként (§12..2). }. valamint a D-bõl származó osztályok tagfüggvényeibõl és barátaiból érhetõk el. akkor nyilvános és védett tagjai csak D tagfüggvényeibõl és barátaiból. */ } ... mert az Ival_slider-bõl további osztályokat akartam származtatni és azoknak el kellett érniük a megvalósítást. mint a tagokét. azaz többértelmûségi hiba nélkül hivatkozhatunk rá: class X1 : public B { /* . Ez alapvetõen azonos a tagok elérhetõségi szabályaival (§15. 15. class XX : public X1.... // rendben: elérhetõ D1-en keresztül // rendben: elérhetõ D1-en keresztül Ha egy bizonyos elemet több út mentén is elérhetünk. class D1 : public virtual B { /* .11.. . Ezenkívül védett tagjai D tagfüggvényeibõl és barátaiból. // . */ } . Csak D tagfüggvényei és barátai. A BBwindow-t azonban nem rejthettem el teljesen úgy. */ } . valamint a D-bõl származó osztályok tagfüggvényei és barátai végezhetnek konverziót D*-ról B*-ra.. class D2 : public virtual B { /* ..

csak az egyébként is hozzáférhetõ adatok kényelmesebb használatát teszik lehetõvé. de a B::a-hoz nem Lásd még a §15. public: int c. // hiba: B::a privát using B::b.3.Forrás: http://www.2 pontot.hu 534 Absztrakciós módszerek XX* pxx = new XX. int i1 = pxx->m.2. . // B::b nyilvánosan elérhetõ D-n keresztül }. }. int i2 = pxx->sm. class D : public B { public: using B::a. using B::c. // hozzáférést ad a B::b és B::c nevekhez. A using deklarációk és az elérhetõség A using deklarációk nem szolgálhatnak több információ elérésére. akkor a hozzáférési jog más felhasználók felé továbbadható: class B { private: int a.2. Ha egy using deklaráció privát vagy védett öröklõdéssel jár együtt.2. ha egy információ elérhetõ. többértelmû: XX::X1::B::m vagy XX::X2::B::m // rendben: csak egy B::sm van egy XX-ben 15. // hiba. Másrészt viszont.doksi. }. akkor a bázisosztály által rendesen felkínált szolgáltatások egy részéhez felületet adhat: class BB : private B { public: using B::b. protected: int b.

ha átadnánk azokat egy képernyõkezelõ rendszernek. Ekkor az Ival_box osztály do_something() függvényét használva meghívhatnánk programunkat: void my_event_handler(BBwindow* pw) { if (Ival_box* pb = dynamic_cast<Ival_box*>(pw)) pb->do_something(). A rendszer felületét a rendszer saját osztályai és objektumai nyelvén adják meg. Következésképpen egy objektum típusának futási idõben való lekérdezéséhez a legnyilvánvalóbb és leghasznosabb mûvelet az. Fontos észrevenni.doksi. se nem kívánatos a rendszer és a program közötti ezen párbeszédben. hogy a rendszer a my_event_handler()-t arra a BBwindow-ra hivatkozó mutatóval hívja meg. Egy felhasználói felületet kezelõ rendszer azonban nem feltétlenül tud a mi Ival_box-ainkról. valahányszor valamilyen tevékenység történt. illetve „nullpointert”. Futási idejû típusinformáció A §12. Az objektum tényleges típusának kiderítése és megemlítése se nem szükséges. hogy a dynamic_cast fordít a felhasználói felületet kezelõ rendszer sajátos nyelvérõl az alkalmazás nyelvére.hu 15. Sok felhasználói felület mûködik így. hogy információt vesztünk a rendszernek átadott és késõbb nekünk visszaadott objektumok típusáról. nem a mi alkalmazásunk osztályainak nyelvén. egy jól megtervezett felület pedig elrejti a lényegtelen részleteket.4. az objektumra hivatkozó mutatóra vagy referenciára van szükségünk. ha az objektum a várt típusú. alkalmas típusú. mondjuk a BBslider. Osztályhierarchiák 535 15.4 pontban leírt Ival_box-ok valószerû használata lenne. de azzal a kellemetlen következménnyel jár. Ez szükségszerû és rendjén is való. amely érvényes mutatót ad vissza. amellyel egy tevékenység történt. else { // hoppá! nem várt esemény } } // Vajon pw egy Ival_box-ra mutat? A folyamatot úgy is magyarázhatjuk. mondjuk Ival_slider. majd az visszaadná õket a programnak. Pontosan ezt teszi a dynamic_cast operátor. hogy mi nem nyert említést ebben a példában: az objektum tényleges típusa.Forrás: http://www. Bármilyen mûveletet akarunk is végezni az objektummal. . Az objektum az Ival_box egy bizonyos fajtája lesz. amelyet a BBwindow egy bizonyos típusa valósít meg. Az „elveszett” adatok visszanyeréséhez valahogy meg kell tudnunk kérdezni az objektumtól a típusát. Létezik felület a párbeszéd lényegének leírására. Például tegyük fel. ha nem.

Dynamic_cast A dynamic_cast (dinamikus típuskényszerítés) operátor két paramétert vár. A bázisosztályról származtatott osztályra történõ konverziót gyakran „lefelé történõ vagy származtatott irányú konverziónak” (downcast) hívják.4. míg a többi nyíl az átadott objektum különbözõ részei közötti öröklõdési viszonyokat ábrázolja. 15. A típusinformációk futási idõben való használatát hagyományosan futási idejû típusinformációnak (run-time type information) hívják és gyakran RTTI-nek rövidítik. Ehhez hasonlóan a származtatott osztályról bázisosztályra történõ konverzió neve „felfelé történõ konverzió”.hu 536 Absztrakciós módszerek Rajzban a pb = dynamic_cast<Ival_box*>(pw) utasítás hatását így ábrázolhatjuk: pw BBwindow Ival_box pb BBslider Ival_slider BB_ival_slider A pw-bõl és pb-bõl kiinduló nyilak az átadott objektumra hivatkozó mutatókat jelölik. mert az öröklési fák a hagyományos ábrázolás szerint a gyökértõl „lefelé nõnek”.Forrás: http://www. vagy „bázisirányú konverzió” (upcast). egy <> közé írt típust és egy () közé írt mutatót vagy referenciát.doksi. Vegyük elõször a mutató esetét: dynamic_cast<T *>(p) .1. A bázisosztályról testvérre – például BBwindow-ról Ival_box-ra – való konverziót „keresztbe történõ konverziónak” (crosscast) hívják.

}.. hogy a dynamic_cast nem engedi meg a védett vagy privát bázisosztályok védelmének véletlen megsértését. a dynamic_cast<T *>(p) eredménye 0 lesz. hogy a konverzió csak egyértelmûen azonosított objektumoknál mûködik. ahol T bázisosztálya D-nek.2). Ha az objektum T osztályú vagy van egy egyértelmû T típusú õse. Azt azonban jó tudni. }. más esetben 0-át. // rendben } BBslider* pbb1 = p. // nem többalakú bázisosztály (Date nem rendelkezik // virtuális függvényekkel) . // rendben: pbb2 értéke 0 lesz Ez nem túl érdekes. // hiba: BBslider védett bázisosztály BBslider* pbb2 = dynamic_cast<BBslider*>(p). Osztályhierarchiák 537 Ha a p a T * típusba.. mert a p által mutatott objektumnak több T típusú bázisosztályt képviselõ részobjektuma van (§15. Jegyezzük meg. mintha a p-t egy T * változónak adtuk volna értékül: class BB_ival_slider : public Ival_slider. akkor az eredmény ugyanaz. Ha p értéke 0. // .. akkor a dynamic_cast egy..doksi.. A dynamic_cast-nak a lefelé vagy keresztbe történõ konvertáláshoz többalakú (polimorf) mutatóra vagy hivatkozásra van szüksége: class My_slider: public Ival_slider { // többalakú bázisosztály (Ival_slider rendelkezik // virtuális függvényekkel) // . ahol a konverzió nem sikerül és 0-át ad. class My_date : public Date { }. az objektumra hivatkozó T * típusú mutatót ad vissza. amelyeknél a fordítóprogram nem tudja a konverzió helyességét megítélni: dynamic_cast<T *>(p) A fenti kód megvizsgálja a p által mutatott objektumot (ha van ilyen). A dynamic_cast célja azon esetek kezelése.Forrás: http://www. protected BBslider { // . void f(BB_ival_slider* p) { Ival_slider* pi1 = p.4. // rendben Ival_slider* pi2 = dynamic_cast<Ival_slider*>(p). Lehet olyan példákat hozni..hu 15. vagy olyan D * típusba tartozik.

vtbl: type_info: "My_slider" bázisosztályok type_info: "Ival_slider" A szaggatott nyíl az eltolást (offset) jelöli. } // rendben // hiba: Date nem többalakú Az a megkötés. hogy egy objektumokat kezelõ ki. ahol nem ismeretes a pontos típusa. hogy többalakú legyen. hogy az ilyen objektum ne kerüljön olyan környezetbe.5) helyezik. majd késõbb kicsomagoljuk belõle a konkrét típust: . A dynamic_cast céltípusa nem kell. hogy a dynamic_cast hatékonyan felhasználható. Általános megoldás.Forrás: http://www. ezért ügyelni kell. ha csak egy többalakú részobjektumra hivatkozó mutató adott.. vptr .. mert megkönnyíti az objektum típusának tárolásához szükséges információ helyének megtalálását.és bemeneti rendszeren keresztül továbbítsuk (§25. egyszerûsíti a dynamic_cast megvalósítását..1). nincs szükség dynamic_cast-ra. My_date* pd2 = dynamic_cast<My_date*>(pd). A dynamic_cast-nak többalakú típusokra való korlátozása logikai szempontból nézve is értelmes. nincs szükség hosszadalmas keresésre vagy karakterláncok összehasonlítására. csak néhány. Date* pd) { My_slider* pd1 = dynamic_cast<My_slider*>(pb).5.. a bázisosztályt leíró type_info objektumot kell összehasonlítani. hogy a típust jelzõ mutatót az objektum virtuális függvénytáblájába (§2. amelynek segítségével a teljes objektum kezdete megtalálható.hu 538 Absztrakciós módszerek void g(Ival_box* pb. akkor pontos típusának ismerete nélkül nem kezelhetõ biztonságosan. Ha egy objektumnak nincs virtuális függvénye. Világos. hogy a mutatónak többalakúnak kell lennie. Ha azonban a típusa ismert. ezáltal egy „típus-információ objektumot” fûznek az objektumhoz: My_slider: .doksi. ezért egy konkrét típust többalakúba csomagolhatunk. mondjuk.4.

mint eredményrõl van szó. Ha egy mutatóról. public Io_obj { }. // bázisosztály-objektum az I/O rendszer számára class Io_date : public Date. és ha a dynamic_cast operandusa nem a várt típusú hivatkozás. 15.1. ha mutatón vagy referencián át kezeljük.Forrás: http://www. akkor fel kell készülnünk arra a lehetõségre. hanem állítás: „a r által mutatott objektum T típusú. } // rendben // hiba: Date nem többalakú Ez azonban csak nagyon alacsony szintû függvényekkel való együttmûködés céljára hasznos.4.doksi.1.” A dynamic_cast mûvelet eredményét maga a dynamic_cast-ot megvalósító kód ellenõrzi automatikusan. A dynamic_cast mûvelet sikertelenségét 0-val jelzi. Osztályhierarchiák 539 class Io_obj { virtual Io_obj* clone() = 0. akkor bad_cast kivételt vált ki: . void f(Io_obj* pio) { Date* pd = dynamic_cast<Date*>(pio). }. A p mutatón végzett dynamic_cast<T*>(p) egy kérdésként fogható fel: „a p által mutatott objektum T típusú?” Másrészt viszont jogosan tételezhetjük fel.hu 15. hogy az eredmény 0 lesz. // . Ez referenciákra se nem kivitelezhetõ. void* pd2 = dynamic_cast<void*>(pd). Referenciák dinamikus típuskényszerítése Egy objektum többalakú (polymorph) viselkedéséhez akkor férünk hozzá. Következésképpen egy r referencia esetén a dynamic_cast<T&>(r) nem kérdés.. } Egy többalakú objektum kezdõcímét egy void*-ra való dinamikus típuskényszerítéssel határozhatjuk meg: void g(Ival_box* pb. Date* pd) { void* pd1 = dynamic_cast<void*>(pb). azaz a mutató nem mutat semmilyen objektumra. Ezért egy dynamic_cast mûvelet eredményét mindig ellenõriznünk kell. se nem kívánatos. hogy egy referencia mindig egy objektumra vonatkozik..

10 Az f() függvény elsõ meghívása sikeresen fog visszatérni. Ez egyszerû.illetve referencia-átalakítások eredményének eltérésében a mutatók.*new BBdial). de ha egy osztály többször fordul elõ a hierarchiában.*new BB_ival_slider). így alkalmasint véletlenül el is fog maradni. Ha ez aggasztja az olvasót. de gyakran túl erõs megszorítást jelent. Osztályhierarchiák bejárása Ha csak egyszeres öröklõdést használunk. akkor némi óvatossággal kell az adott osztályú objektumra vagy objektumokra hivatkoznunk. míg a második bad_cast kivételt vált ki. } f(new BBdial. Ez önmagában nem bonyolítja nagyon a helyzetet. } // a paraméterek Ival_box-ként // adódnak át // a paraméterek Ival_box-ként adódnak át // §14. akkor egy megfelelõ kivételkezelõre van szüksége: void g() { try { f(new BB_ival_slider. amit g() elkap. Ival_box& r) { if (Ival_slider* is = dynamic_cast<Ival_slider*>(p)) { // Vajon p egy Ival_slider-re mutat? // 'is' használata } else { // *p nem slider } Ival_slider& is = dynamic_cast<Ival_slider&>(r).4.Forrás: http://www.hu 540 Absztrakciós módszerek void f(Ival_box* p... // 'is' használata // az r egy Ival_slider-re hivatkozik! } A sikertelen dinamikus mutató. az osztály és bázisosztályai egyetlen bázisosztályban gyökerezõ fát alkotnak. akkor írhat egy olyan konverziós függvényt. 15. illetve a referenciák közötti alapvetõ különbség tükrözõdik. Ha egy felhasználó védekezni akar a referencia-átalakítás sikertelensége ellen. amely sikertelenség esetén a 0 érték visszaadása helyett kivételt vált ki (§15.doksi.2. . A 0 érték ellenõrzése elhagyható.8[1]). Többszörös öröklõdés használata esetén nincs egyetlen gyökér. } catch (bad_cast) { // .

*/ }.. */ }.4.1). class Radio : public Receiver. class Transmitter : public Component { /* . hamarosan szükségünk lesz annak bejárására (vagyis végignézésére). Ez az igény két esetben merülhet fel. Hogy szemléltessük az elérhetõ módszereket és a rájuk vonatkozó szabályokat. */ }.4. public Transmitter { /* . hogy alkalmas osztályt találjunk.Forrás: http://www.3 és §15. melyik Component-re gondolt a programozó: void h1(Radio& r) { Storable* ps = &r. */ }. De ha már kialakult egy bonyolultabb hierarchia.. class Receiver : public Component { /* .... Ezért a Radio-n belüli. // . ha adott egy mutató a teljes objektumra vagy annak valamely részobjektumára (§15. Osztályhierarchiák 541 Természetesen a hierarchiákat igyekszünk annyira egyszerûnek venni – de nem egyszerûbbnek –. amennyire programunk megengedi.. // pc = 0 } . Ábrával: Storable Component Component Receiver Transmitter Radio Itt a Radio objektumnak két Component osztályú részobjektuma van..hu 15. Storable-rõl Component-re való dinamikus típuskényszerítés többértelmû lesz és nullát ad. a bázisosztályt vagy annak egy származtatott osztályát megjelenítõ objektumra hivatkozó mutatóra van szükségünk... Néha kifejezetten meg akarunk nevezni egy bázisosztályt vagy annak egy tagját (például §15.4 és §15.1). Itt azt tekintjük át. Máskor egy.doksi. Component* pc = dynamic_cast<Component*>(ps). vegyünk egy többször szereplõ és virtuális bázisosztályt egyaránt tartalmazó hálót: class Component : public virtual Storable { /* .. Ilyenkor egyszerûen nem lehet tudni. amelyet felületként használhatunk.2.2. hogyan lehet típuskényszerítést (cast) használva a kívánt típusú mutatóhoz jutni.

// a Storable a Radio virtuális bázisosztálya pr = static_cast<Radio*>(ps). A static_cast (§6. amelynek a memóriában való elhelyezkedését egy másik nyelv. // .. // rendben.4.1.4.hu 542 Absztrakciós módszerek Ez a többértelmûség fordítási idõben általában nem deríthetõ fel: void h2(Storable* ps) // ps-rõl nem tudjuk.2.4. // rendben. amelynek alapján meg lehetne keresni azon objektumokat.1). amelyeknek õse (bázisa). hogy millió sornyi kód van a dynamic_cast bevezetése elõtti idõkbõl. // rendben. // hiba: virtuális bázisosztályról nem lehet átalakítani pr = dynamic_cast<Radio*>(ps). Ezzel egyenértékû többértelmûség lép fel felfelé (azaz a bázisosztály felé történõ) konverzió esetén és ezek a többértelmûségek fordítási idõben kiderülnek.2. például a Fortran vagy a C határozza meg. mert egy nem többalakú objektum nem tárol olyan információt. Miért akarna valaki static_cast-ot használni egy osztályhierarchia bejárására? A dynamic_cast némileg növeli a futási idõt (§15.doksi.4) konverzió esetén a kívánt típusú részobjektum mindig egyértelmû (ha létezik).1). de ennél jelentõsebb ok. futási idejû ellenõrzés } A dynamic_cast-nak többalakú operandusra van szüksége. 15. nincs ellenõrzés pr = dynamic_cast<Radio*>(prec). hogy Component-re mutat-e { Component* pc = dynamic_cast<Component*>(ps).7) nem vizsgálja a kiinduló objektum típusát.. // a Receiver a Radio közönséges bázisosztálya Radio* pr = static_cast<Radio*>(prec). de a dynamic_cast megvalósításához szükséges információt a futási idejû típusinformáció tartalmazza. Statikus és dinamikus konverzió A dynamic_cast mûvelet többalakú bázisosztályról származtatott osztályra vagy testvérosztályra való átalakítást tud végezni (§15. így nem képes ezekre: void g(Radio& r) { Receiver* prec = &r. Az ilyen kódok más módokon . Ezekre vonatkozóan csak statikus adatok állnak rendelkezésre. Olyan objektum is lehet például virtuális bázisosztály. §15. futási idejû ellenõrzés Storable* ps = &r. Közönséges bázisosztályok és lefelé (azaz a származtatott osztály felé történõ. } A többértelmûségnek ez a fajta felderítése csak virtuális bázisosztályoknál szükséges.Forrás: http://www.

static_cast<Receiver*>(pcr).3. Az objektum felépítése alulról felfelé. mint egyszerûen a memória egy része (§4.9. így a dynamic_cast-tal végeztetett ellenõrzés feleslegesnek tûnik. ha az objektumot eredetileg nem const-ként deklaráltuk (§10. } // Bízzunk a programozóban! Mind a dynamic_cast. dynamic_cast<set<Person>*>(pu). így.6).. A fordítóprogram nem tételezhet fel semmit egy void* mutató által mutatott memóriaterületrõl.2. Osztályhierarchiák 543 biztosítják az alkalmazott típusátalakítások helyességét. Ehhez static_cast kell: Radio* f(void* p) { Storable* ps = static_cast<Storable*>(p). dynamic_cast<Receiver*>(pcr). // .4.. const Receiver* pcr) { static_cast<set<Person>*>(pu).doksi.2.7. mind a static_cast tiszteletben tartja a const minõsítést és a hozzáférési korlátozásokat: class Users : private set<Person> { /* . használjuk a biztonságosabb dynamic_cast-ot. megsemmisítése felülrõl lefelé történik. */ }. hacsak lehet.. void f(Users* pu. és még akkor is csak úgy kapunk helyes eredményt.2. Az osztályobjektumokat konstruktoraik építik fel a „nyers memóriából” és destruktoraik lefutásával válnak újra „nyers memóriává”. és az osztályobjektum olyan mértékben létezõ .Forrás: http://www.1). // hiba: nem férhet hozzá // hiba: nem férhet hozzá // hiba: const minõsítés nem vész el // hiba: const minõsítés nem vész el // rendben } Privát bázisosztályra nem lehet konvertálni. 15. return dynamic_cast<Radio*>(ps).7). Osztályobjektumok felépítése és megsemmisítése Egy valamilyen osztályba tartozó objektum több. hogy az objektum típusa felõl érdeklõdõ dynamic_cast nem képes void*-ról konvertálni. Receiver* pr = const_cast<Receiver*>(pcr). Az ilyen – általában C stílusú típuskonverzióval (§6. Ebbõl következik. const-ot nem konstanssá konvertálni pedig csak const_cast-tal lehet (§6.hu 15..7) íródott – kódban azonban gyakran maradhatnak rejtett hibák.

4. // a p által mutatott objektum típusa typeid(p). // ál-deklaráció const type_info& typeid(expression) throw(bad_typeid).4. Ha operandusként egy típusnevet kap. Ennek fényében legjobb elkerülni a virtuális függvényeknek konstruktorból vagy destruktorból való meghívását. Ha a typeid() függvény lenne. illetve megsemmisítése megtörtént. Például tudni szeretnénk az objektum osztályának nevét vagy memóriakiosztását.7) és a virtuális függvényekre vonatkozó szabályokban. Például ha a §15. ha virtuális függvényeket. amikor az objektum még nincs készen. a <typeinfo> fejállományban definiált type_info nevû típusra való referenciát ad vissza.doksi. leginkább tévedés) } . Ez tükrözõdik a futási idejû típusazonosításra (RTTI). a kivételkezelésre (§14.4) hívunk akkor. A typeid operátor ezt az operandusa típusát jelzõ objektum visszaadásával támogatja. Néha azonban alapvetõ fontosságú tudni az objektum pontos típusát.4. Így a dynamic_cast a virtuális függvényekhez hasonlóan megõrzi a rugalmasságot és bõvíthetõséget. valahogy így adhatnánk meg: class type_info. Nem bölcs dolog az objektumfelépítés vagy -megsemmisítés sorrendjére támaszkodni. nem a Receiver. A typeid() leginkább egy referenciával vagy mutatóval jelölt objektum típusának lekérdezésére használatos: void f(Shape& r.4. futási idõben jelentkezõ információigény legnagyobb részét kielégíti. Az objektum létrehozásának ezen pontján az objektum még nem Radio. 15. // az r által hivatkozott objektum típusa typeid(*p). Shape* p) { typeid(r). vagyis Shape* (nem gyakori.4. // a mutató típusa.2 pontbeli hierarchia Component konstruktora egy virtuális függvényt hív. a kifejezés által jelölt objektumot ábrázoló type_info-ra fog hivatkozni. dynamic_cast-ot vagy typeid-t (§15.hu 544 Absztrakciós módszerek objektum. hogy biztosítja a felhasználó kód helyes mûködését a programozó által használt osztályokból származó osztályokkal is. de a sorrendet megfigyelhetjük. a typeid() az azt ábrázoló type_info-ra való referenciával tér vissza. amennyire felépítése. Typeid és kiterjesztett típusinformáció A dynamic_cast operátor az objektumok típusára vonatkozó. ha kifejezést. // ál-deklaráció Azaz a typeid() egy standard könyvtárbeli. akkor a Storable vagy Component-beli változatot fogja meghívni. csak egy részben felépített objektum. Fontos tulajdonsága. const type_info& typeid(type_name) throw().Forrás: http://www. Transmitter vagy Radio-belit.

hogy valamilyen szabványos mûveletet végezzünk az egész objektumon (és nem csak annak egy õsén). A type_info megvalósítás-független része így néz ki: class type_info { public: virtual ~type_info(). bool before(const type_info&) const. Néha tudni akarjuk egy objektum pontos típusát. // . Dinamikus csatolású könyvtárak használata esetén például valóban nehéz a több type_info objektumot elkerülõ megvalósítás elkészítése.Forrás: http://www..4. a typeid() bad_typeid kivételt vált ki. bool operator!=(const type_info&) const. Egy másik. Ezért a == mûvelettel az egyenlõséget a type_info objektumok és nem az azokra hivatkozó mutatók esetében vizsgáljuk.4. így a megoldás útja a pontos típuson keresztül vezet (§15.1). }. Nem biztos. const char* name() const. // többalakú // összehasonlítható // rendezés // a típus neve // másolás megakadályozása // másolás megakadályozása A before() függvény lehetõvé teszi a type_info objektumok rendezését. Osztályhierarchiák 545 Ha a mutató vagy referencia operandus értéke 0.. type_info& operator=(const type_info&). bool operator==(const type_info&) const.name(). így nem szükséges a pontos típus ismerete. A meghatározott rendezési sorrendnek nincs köze az öröklési viszonyokhoz. private: type_info(const type_info&). sokkal egyszerûbb használat az osztály nevének diagnosztikai kimenet céljára való lekérdezése: #include<typeinfo> void g(Component* p) { cout << typeid(*p). } .hu 15. Egyes esetekben azonban nem tételezhetõ minden egyes kezelt objektumra vonatkozó közös felület. Ideális esetben az ilyen mûveletek virtuális függvények formájában állnak rendelkezésre.doksi. hogy a rendszer minden egyes típusát egyetlen type_info objektum képviseli.

6 hash_map<const type_info*. // x használata } Valaki más egészen eltérõ információt adhat: struct TI_eq { bool operator()(const type_info* p.6. const type_info* q) { return *p==*q.4.Icon. amelyek a rendszerhez tartozó memóriarészben vannak. Layout> layout_table. void f(B* p) { Layout& x = layout_table[typeid(*p).1. Gondoljuk meg.hash_fct. hogy annak alapján a felhasználói kód megtalálhassa a memóriakiosztási információt: map<string. hogy egy program vagy programozást segítõ eszköz hogyan tudna futási idõben típusokról szóló információt adni a felhasználóknak. Tegyük fel. }. C stílusú karakterláncokkal történik. ezért ne próbáljuk meg a delete[ ] mûveletet alkalmazni rájuk. 15.hu 546 Absztrakciós módszerek Az osztályok nevének szöveges ábrázolása az adott nyelvi változattól függ.Forrás: http://www. hogy van egy eszközünk. Kiterjesztett típusinformáció Egy objektum pontos típusának meghatározása általában csak az elsõ lépés a rá vonatkozó részletesebb információk megszerzése és használata felé.TI_hash. struct TI_hash { int operator()(const type_info* p).TI_eq> icon_table. amely minden felhasznált osztályról megmondja az objektum memóriakiosztását.doksi.name()]. Ezeket a leírókat egy map-be tehetem. } }.2. // i használata } . void g(B* p) { Icon& i = icon_table[&typeid(*p)].2) // §17.4. // hasítóérték kiszámítása (§17.

. Az objektum memóriakiosztása A típus ikonos ábrázolása Ez nagyon fontos..5. A statikus (fordítási idõben történõ) ellenõrzés biztonságosabb. 15. amely egymagában minden felhasználó igényeit kielégíti. hogy egy rosszul álcázott switch utasítást írjunk: // a futási idejû típusinformáció helytelen használata void rotate(const Shape& r) { if (typeid(r) == typeid(Circle)) { // nem csinálunk semmit } else if (typeid(r) == typeid(Triangle)) { // háromszög forgatása } else if (typeid(r) == typeid(Square)) { // négyzet forgatása } // . Az RTTI helyes és helytelen használata Csak szükség esetén használjunk explicit futási idejû típusinformációt.. A futási idejû típusinformációt például arra használhatjuk.doksi.4.Forrás: http://www.. Osztályhierarchiák 547 A typeid-ekhez információ rendelésének ez a módja több programozó vagy eszköz számára teszi lehetõvé. &typeid(T) . icon_table: .. hogy a típusokhoz egymástól teljesen független információkat rendeljenek: layout_table: "T" .. hogy valaki információknak olyan halmazával tud elõállni. mert annak valószínûsége.hu 15. „olcsóbb” és – alkalmasint – jobban szerkesztett programokhoz vezet. a nullával egyenlõ. } A dynamic_cast-nak a typeid helyett való használata alig javítana ezen a kódon....

*/ }. Azok számára. amikor a típuson alapuló futási idejû megkülönböztetés szükséges. az ilyen módosítás problémákhoz vezethet.. if (Ship* q = dynamic_cast<Ship*>(p)) { return q... akik a Smalltalkon vagy a Lispen.4. Ennek a kísértésnek általában ellen kell állni. A C-hez. Object* get(). akkor az RTTI használata elkerülhetõ.5. Ha a felhasználó hajlandó és képes a könyvtári osztályok – például a BBwindow – módosítására. különben viszont szükséges. Ezt a problémát a §24.doksi. Vegyük ezt a példát: // a futási idejû típusinformáció helytelen használata class Object { /* .Forrás: http://www.1 írja le.3 némileg részletesebben tárgyalja. Container* c) { c->put(ps).és bemeneti rendszer elkészítésére szolgáló használatát a §25. Az RTTI-nek egy egyszerû.6) használjunk a legtöbb olyan eset kezelésére. §12. // . nagymértékben a dinamikus típusellenõrzésre építõ nyelveken nevelkedtek. ilyen kódot tényleg írnak.. Például szükségessé válhat a virtuális függvények ál-megvalósítása olyan osztályok esetében.. }. De ha a felhasználó hajlandó is a könyvtári osztályok módosítására. a Modula-2-höz. class Ship : public Object { /* .. hogy a programot switch utasítások halmazaként építse fel. amelyeknél azok nem szükségesek vagy nem értelmesek.. vagy az Adához hasonló nyelveken nevelkedett programozó majdnem ellenállhatatlan kísértést érez. // . objektumokat kezelõ ki.4 pontbeli Ival_box használata ennek egy példája. amikor a kódban valamilyen szolgáltatást egy bizonyos osztályhoz kapcsolunk és a felhasználó öröklõdéssel akar további szolgáltatásokat hozzáadni. } // futási idejû ellenõrzés . a Pascalhoz.5.hu 548 Absztrakciós módszerek Sajnos ez nem egy légbõl kapott példa. A §15. esetleg más.2. Futási idejû típusazonosítás helyett inkább virtuális függvényeket (§2. csábító dolog az RTTI-t túlságosan általános típusokkal együtt használni. Az RTTI helyes használata sokszor merül fel akkor. // többalakú class Container : public Object { public: void put(Object*).4.. Ship* f(Ship* ps. */ }. Object* p = c->get().

// . list<Ship*>& c) { c.. Ráadásul az ilyen kérések közvetett vagy közvetlen módon programoktól érkezhetnek. Az ilyen jellegû problémákat gyakran jobban oldja meg. 15. nagyon általános felülete. virtual void resume() = 0. Túlságosan általános.hu 15. virtual void suspend() = 0. virtual void small() = 0. Osztályhierarchiák 549 else { } } // valami mást csinálunk (általában hibakezelést végzünk) Itt az Object osztály szükségtelen és mesterkélt. a képernyõn megjelenített objektumnak tudnia kell válaszolni. Például sok „objektumorientált” felhasználói felület határoz meg egy sor kérést. amelyre minden.push_front(ps). amelyet sokféle használatra szántak. Vegyük ezen elv egy egyszerû változatát: class Std_interface { public: virtual void start() = 0. } Virtuális függvényekkel együtt használva így majdnem minden esetet megoldhatunk. ha kizárólag egy adott típusú mutatót tartalmazó tároló sablonokat használunk: Ship* f(Ship* ps.doksi. }. mert az adott alkalmazásban nem felel meg semmilyen elvonatkoztatási szintnek és a programozót egy megvalósítás-szintû fogalom használatára kényszeríti.Forrás: http://www. Tagra hivatkozó mutatók Sok osztálynak van egyszerû.pop_front(). return c. virtual void quit() = 0.. virtual ~Std_interface() {} .5. virtual void full_size() = 0.

Ideális esetben az ilyen köztes rétegeknek nem kell semmit tudniuk az egyes mûveletekrõl (resume(). a mûveletet jelölõ egész értéket. ami az Std_interface::suspend() tagra mutat. amely az osztály egy tagját azonosítja. melyik mûvelethez tartozik. p->suspend(). hogy a 2 a suspend()-et jelenti és meg kell hívnunk a suspend()-et. az adott osztályhoz tartozó objektumban levõ tag helyére. A tagra hivatkozó mutató olyan érték. } (p->*s)(). De amíg egy egész számot a számítógépek kényelmesen kezelnek. hogy közvetlenül megneveznénk. Ehelyett küldhetnénk csak egy. Valakinek azonban létre kell hoznia a karakterláncot. mint egy. ha egyáltalán tartozik valamelyikhez. amelyre alkalmazzák. Ha tudnának. valakinek pedig meg kell fejtenie. amely meghatározza. a virtuális és nem virtuális függvények stb. ráadásul még mindig meg kell írnunk a kódot. void f(Std_interface* p) { Pstd_mem s = &Std_interface::suspend. Ennek egyszerû módja az alkalmazandó mûveletet jelölõ karakterlánc küldése. Ha meg akarjuk hívni a suspend()-et valamely objektumra anélkül. Gyakran a kérést kiadó személy vagy program és a fogadó objektum között egy szoftverréteg van. az alkalmazandó mûveletre vonatkozó adatot a kérés forrásától a fogadóhoz. akkor egy olyan mutatóra lesz szükségünk. közötti különbséget. Ez gyakran túlságosan közvetettnek és fáradságosnak tûnik.hu 550 Absztrakciós módszerek Minden mûvelet pontos jelentését az az objektum definiálja.). a köztes rétegeket fel kellene újítani. Gondolhatunk rá úgy. Vegyünk egy igen egyszerû példát: typedef void (Std_interface::* Pstd_mem)(). Mondjuk a 2 jelenthetné a suspend-et. Ugyancsak szükségünk lesz a suspend() révén felfüggesztendõ objektumra hivatkozó mutatóra vagy referenciára.Forrás: http://www. de a fordító természetesen figyelembe veszi az adattagok. Következésképpen az ilyen köztes rétegek csupán továbbítanak valamely. Vegyük az Std_interface-t. az emberek számára ez meglehetõsen zavaró lehet. full_size() stb. valahányszor a mûveletek halmaza megváltozik.doksi. // közvetlen hívás // hívás tagra hivatkozó mutatón keresztül // tagra hivatkozó mutató . A C++ nyelv lehetõvé teszi az osztályok tagjainak közvetett elérését. Például a suspend() meghívása céljából a „suspend” (felfüggesztés) szöveget küldhetnénk.

*m pedig az obj objektumhoz. A közönséges függvényekre hivatkozó mutatókhoz hasonlóan a nem virtuális tagfüggvényekre hivatkozó mutatókat sem lehet másik címtérnek átadni. hogy az objektum elhelyezkedése a kettõben azonos. hogy a címképzõ & operátort egy teljes nevû („teljesen minõsített”. Osztályhierarchiák 551 Egy tagra hivatkozó mutatót (pointer to member) úgy kapunk. Ezt rajzzal így ábrázolhatjuk: vtbl: s p X::start X::suspend Mivel egy virtuális tagra hivatkozó mutató (a fenti példában s) egyfajta eltolás. a tagra hivatkozó mutatókkal való bajlódás helyett. ami egy bizonyos objektum egy bizonyos tagját azonosítja. A tagra hivatkozó mutató azonban nem egyszerûen egy mutató egy memóriaterületre. olyasvalami keletkezik. Amikor egy tagra hivatkozó mutatót a megfelelõ típusú objektumra hivatkozó mutatóval párosítjuk. Természetesen ha tudnánk. nem függ az objektum helyétõl a memóriában. az obj.hu 15. amikor a tag nevének ismerete nélkül akarunk egy függvényre hivatkozni. A C szintaxis áttekinthetõségének hiányát általában a typedef alkalmazásával ellensúlyozzák. . Az eredmény az m típusának megfelelõen használható. feltéve.Forrás: http://www.* operátorokkal fejezhetjük ki. A közönséges függvénymutatókhoz hasonlóan a tagfüggvényekre hivatkozó mutatókat akkor használjuk. közvetlenül meghívnánk azt.* mûvelet eredményét nem lehet késõbbi használatra félretenni. ezért biztonságosan átadható egy másik címtérnek (address space). az X::* forma viszont láthatóan nagyon szépen megfelel a hagyományos * deklarátornak. Például a p->*m az m-et a p által mutatott objektumhoz köti. Egy m tagra hivatkozó mutatót egy objektummal kapcsolatban használhatunk. de a ->* vagy . Az „X osztály egy tagjára hivatkozó mutató” típusú változókat az X::* formában deklarálhatjuk.doksi. A kapcsolatot a ->* és . melyik tagot akarjuk meghívni. inkább egy adatszerkezeten belüli eltolásra (offszetre) vagy egy tömbbeli indexre hasonlít. mint egy változó címe vagy egy függvénymutató. fully qualified)