6. gyakorlat¶
9. gyakorlat¶
Ismétlés¶
Öröklődés¶
Az objektumorientáltság egyik alappillére az öröklődés, azaz egy adott osztály speciális esetekre bontása; a meglévő tulajdonságok, funkciók kibővítése, pontosítása. Ahhoz, hogy megtudjuk nézni, hogyan zajlik az öröklődés és milyen lehetőségeket rejt a nyelv, kell egy alap osztály. Biztosan mindenkinek ismerős az absztrakt és interface kulcsszó, azonban erre később térünk ki, most egy szokványos osztályból származtatunk.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Ahogy látszik egy Hallgato
osztályt hoztunk létre. Az neptun adattag private, így csak ez az osztály érheti el, azonban a felvet_oraszam és a nev már nem a megszokott private láthatóságú, hanem protected. Protected tagokat a leszármazott típusok is elérhetik, ahogy azt korábban is láthattuk már. A felvett_oraszam, nev a leszármazott típusokban is közvetlenül változtatható, azonban az neptun (egyetemi azonosító) kód minden példány privát tulajdonsága, kívülről nem elérhető, nem módosítható.
Egy PhD hallgató ugyan olyan hallgató mint bárki más, csupán az órák hallgatása mellet órát is tarthat, azaz bővíti az eredeti típust, annak egy speciális esete.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Ezzel létrehoztunk egy Hallgato osztályból leszármaztatott PhD_hallgato típust. Ezt úgy képzelhetjük el, mintha egy objektum gömb köré egy nagyobb burkot helyeznénk el:
A fenti öröklődés példában a fontosabb részek:
Öröklődés láthatóság¶
1 |
|
Az öröklődést a kettőspont után adjuk meg, azonban nagyon fontos, hogy milyen láthatósággal. Ahogy itt megadtuk, az öröklődés public
. Ez azt jelenti, hogy bármit is kapott az ős osztálytól a gyerek osztály azt maximum public elérhetőséggel örökli. Természetesen ez nem változtat sokat, azonban ha protectedet írnánk, már a public elemek nem ugyanúgy viselkednének. Ha private-ot írunk vagy nem írunk semmit, akkor maximum private láthatósággal örökli az elemeket (tehát ami az ősben public, protected volt, ilyenkor priváttá "válik" az örökölt osztályban).
Egy kisebb példán szemléltetve:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
|
Látható, hogy az öröklődés láthatósága nem a leszármazott osztály elől rejt el elemeket, hanem "abba bekerülve" az adattagnak a láthatóságát módosítja. Ha private is a láthatóság, az osztály eléri. Ez azonban csak az olyan adattagokra vonatkozik, melyeket az öröklődés által elérne. Vagyis az Ős eredetileg is private adattagjait nem érhetjük el a leszármazott osztályokból.
Ős osztály konstruktora¶
Ahogy a fenti ábrán is látható, a leszármazott osztály tartalmazza az ős osztályt, tehát ha a leszármazott osztályt teljes mértékben inicializálni akarjuk, akkor a benne lévő őst is inicializálni kell. Ha nincsen default inicializálás (default konstruktor), akkor meg kell adni, hogy milyen módon / melyik konstruktorral legyen az ős inicializálva. Ezt a következő módon tettük meg a PhD_hallgato esetében:
1 2 3 |
|
Látható, hogy az inicializáló lista első "inicializáló" eleme egy Hallgato konstruktor hívása. Ezzel tudjuk megadni, hogy milyen adatokat adunk át az ős résznek. Fontos, hogy mindig az ős konstruktorokat kell hívni először, azután lehet csak leszármazott saját adattagjait beállítani!
Ős osztály tagfüggvényei¶
Azt láttuk, hogyan kell az ősosztály-beli adatokat inicializálni. Láttuk, hogy a private elemeket nem érhetjük el. Azonban a konstruktorban átadott információkat - ha nem is érjük el közvetlenül - nem veszítjük el, hiszen ha létezik olyan ősosztály-beli metódus (pl.: getterek), ami a privát adatot használja, akkor azt a metódust használhatjuk leszármazott osztályban is (feltéve, hogy nem privát). Az alábbiakban bemutatjuk, hogy PhD_hallgato egyik tagfüggvényében az örökölt Hallgato-beli orakat_hallgat
metódust is meg tudjuk hívni (ez a metódus pedig a Hallgato osztály privát adatával dolgozik).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
Látható, hogy meg kell adni, melyik az a rész ahol van a számunkra fontos metódus, jelen esetben (ős osztály) Hallgato. C++ esetén, ha meg akarjuk adni, hogy honnan és mit szeretnénk elérni, akkor a scope operátort kell használnunk. Ez a ::
.
Vagyis a Hallgato-ból szeretnénk az orakat_hallgat metódust használni.
Polimorfizmus¶
Polimorfikus hívás esetén lehetőségünk van általánosan az ős osztályról beszélni, majd különféle leszármazott osztályokat átadni, hiszen azok is megfelelnek az ős osztály típusának, csupán részletesebbek, bővebbek.
A Hallgato és PhD_hallgato esetében írhatunk függvényeket, melyek Hallgatot várnak, és PhD_hallgatot kapnak. Ehhez olyan metódusra van szükségünk ami megtalálható az ősben, ha a leszármazott ezt felüldefiniálja, annak lehet más működése. Ilyen lehet például az orakat_hallgat metódus, hiszen eddig külön metódust írtunk a PhD_hallgato-nak, mely meghívta az ősosztály-beli elemet. Ezt felül tudjuk definiálni, hogy PhD_hallgato esetében másként viselkedjen.
Felüldefiniálás esetében meg kell egyezzen:
- metódus neve
- metódus paraméterezése
- metódus qualifierek
- visszatérési típus
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
|
Azt láthatjuk, hogy ha PhD_hallgato-t hoztunk létre, valóban annak az orakat_latogat metódusát meghívva a felüldefiniált hívás fut le, azonban a függvényeknek átadva nem tudtuk polimorfikusan használni. Ennek a magyarázata a típusok kezelésében rejlik. Hallgato átadása során nincsen probléma, ezért a PhD_hallgato esetét nézzük meg részletesebben!
Érték szerinti átadás¶
Ebben az esetben statikusan megmondtuk a rendszernek, hogy Hallgato típust kell várnia és amikor a PhD_hallgato-t adtuk át, a rendszer másolatot készített azonban statikusan csak a Hallgato adatait másolta le.
Pointer szerinti átadás¶
Pointer szerinti átadáskor nem másolat készül, hanem megmondjuk, hogy a kapott címre menve találunk egy adott típusú elemet, jelen esetben Hallgatot, hiszen statikusan megadtuk, hogy ott azt kapunk, akkor is, ha az egy PhD_hallgato objektum része. Így nem adtunk információt, hogy azt a dereferált elemet még több minden kiegészíti, pl. a felüldefiniált metódus, csak az alap Hallgato részt dereferálja a függvény.
Referencia szerinti átadás¶
Azt már sokszor láttuk, hogy a referencia nagyon hasznos, azonban nagyon hasonlít a pointerhez és most sem viselkedett attól eltérően. Az eredeti objektumot érte el, ami statikusan egy Hallgato.
Látható, hogy az eddig ismert módon nem tudjuk megoldani a polimorfikus hívást. A magyarázat minden esetben az, hogy statikusan (tehát nem futásidőben) megadtuk, hogy milyen típust fog használni az adott függvény. A megoldás az lenne, ha lenne egy jelzés az objektumon, hogy ha Hallgato típust is vár pl. a pointer ne csak azt dereferálja, hanem figyeljen oda arra is, hogy PhD_hallgato típust kapot, azt máshogy kell kezelje. Ez a virtualizáció. Ekkor van egy táblázat, melyben megvannak adva objektumok és az, hogy hogyan is viselkednek egyes metódusaik meghívásakor. C++ esetében nem alapértelmezett ezen tábla használata, mivel például erőforrás igényesebb, de megadható, hogy a megjelölt osztályok és azok leszármazottjai esetében használja. Ahhoz, hogy egyes metódusok virtuálisan tudjanak működni meg kell azokat jelölni a virtual kulcsszóval (ezt elegendő az ősosztályban megtenni).
Virtualizáció¶
Ahogy láttuk a virtualizáció nem alapértelmezés. Nézzük meg az átalakított példát ahol már van virtualizált metódus is!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
|
Természetesen csak a megjelölt metódusok viselkednek polimorfikusan (virtualizáció szerint), így például a nem_virtual nevű metódus nem viselkedik polimorfikusan. Az is látszódik, hogy érték szerinti átadásnál a virtuális metódus sem viselkedett polimorfikusan. Ez a fent is említett érték szerinti másolás miatt történt így.
Ha polimorfikusan akarunk típusokat kezelni, az alábbiakat kell tennünk:
- virtual kulcsszó a kívánt metódusokra
- referencia vagy pointer használata
Override¶
A virtuális felüldefiniálás feltételei miatt kisebb elírások is ahhoz vezethetnek, hogy nem a megfelelő metódust írjuk felül. Ennek kivédésére meg tudjuk jelölni a felüldefiniálandó metódust, hogy a rendszer leellenőrizze, valóban a megfelelő metódus prototípust alkalmaztuk-e. Ez az override kulcsszó. Ha olyan metódust látunk el ezzel a kulcsszóval, melynek nincsen virtuális megfelelője valamelyik ős osztályban az öröklődési láncban, akkor fordítási hibát kapunk.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
Feladatok¶
-
Hozz létre egy Jarmu osztályt, aminek egy privát string uzemanyag adattagja van publikus getterrel.
Megoldás
- Kód: 1. feladat
-
Legyen egy halad() függvénye a járműnek, ami kiírja, hogy a jármű halad és az üzemanyag típusát.
Megoldás
- Kód: 2. feladat
-
Hozz létre egy Auto osztályt, ami a Jarmu osztályból származik és van egy privát unsigned sebesseg adattagja publikus getterrel.
Megoldás
- Kód: 3. feladat
-
Az Auto definiálja felül a halad() fgv-t, az eredeti szöveg helyett írja ki, hogy az autó [sebesseg] km/h-val halad és [uzemanyag]-t fogyaszt.
Megoldás
- Kód: 4. feladat
-
Hozz létre egy Hajo osztályt, ami a Jarmu osztályból származik és van egy privát float sebesseg adattagja publikus getterrel.
Megoldás
- Kód: 5. feladat
-
A Hajo is definiálja felül a halad() fgv-t, az eredeti szöveg helyett írja ki, hogy a hajó [sebesseg] csomóval halad és [uzemanyag]-t fogyaszt.
Megoldás
- Kód: 6. feladat
-
Írj egy függvényt ami Jarmu tipusu objektumot vár és meghívja a kapott objektum halad() metódusát, majd hívd meg a létrehozott osztályok egy-egy példányával.
Megoldás
- Kód: 7. feladat
-
Gyakorló feladatsor
-
Hozzunk létre egy allat osztályt!
- Az állat egyetlen attribútuma, hogy novényevo-e (novenyevo:bool) a színe. Írj paraméteres konstruktort az osztályhoz, mely kiírja a standard outputra, hogy "allat letrehozva".
- Írj destruktort, mely kiírja: "allat torolve"
-
Hozzunk létre egy macska osztályt, ami az allat osztályból származik publikusan.
- Legyen a macskanak neve, amit létrehozáskor meg kell adni
- A novenyevo adattag legyen false-ra állítva.
- A konstruktor és a destruktor is irja ki, hogy a macska létrejött/törlődött a nevével együtt.
-
Legyen az állatban egy void taplalkozik() metódus
- Ha az állat novenyevo írja ki: legel, ha nem az írja ki: vadaszik.
- A macska osztályban ez legyen kiirva a taplalkozik metódusban: X macska egeret vadaszik (ahol X a macska neve)
-
Legyen egy függvény, amelyik allat típust vár, és azon is nézzük meg a taplalkozik metódus meghívását.
- Mi történik, ha különböző paraméterátadási módokat alkalmazunk (érték szerinti, paraméter, pointer)?
-
Példányosíts dinamikusan egy macskat, de a pointer allat legyen. Töröld! Mit tapasztalsz?
Megoldás
- Kód: Gyakorló feladatsor
- Videó: https://youtu.be/dzroI9QuYBA
-