Bevezetés a programozásba 2 5. előadás: Öröklődés
lé ke zt et ő
em ●
●
●
Tagfüggvény
struct koord { double x,y,r; void set(double ux, double uy) { x=ux; y=uy; r=sqrt(x*x+y*y); } };
Használat: koord k; k.set(4,5); Egységbezárás
lé ke zt et ő
em ●
●
Dinamikus memóriakezelés
T *m; m = new T; *m ... delete m; A new operátorral kérünk memóriát, T típus méretével megegyező mennyiségben, az OS az m mutatónkba adja a címét
●
Ha már nem kell, delete operátorral szólunk
●
A delete nem nullázza m-et!
lé ke zt et ő
em ●
Láthatóság
struct S { // ez látszik kívülről private: // ez nem látszik // amíg más módosító nem érkezik // mezők, tagfüggvények... public: // innentől megint látszik // mezők, tagfüggvények... };
lé ke zt et ő
em
Interface és implementáció
struct Vektor { Vektor(int M); ~Vektor(); int osszeg(); private: int *v; int m; }; Vektor::Vektor(int M) { v=new int[M]; m=M; for (int i=0;i<m;i++) v[i]=i+1; } Vektor::~Vektor() {delete v;} int Vektor::osszeg() { int sum=0; for (int i=0;i<m;i++) sum+=v[i]; return sum; } int main() { int M=10; Vektor v(M); cout << v.osszeg(); }
Interface (felület)
Implementáció (megvalósítás)
lé ke zt et ő
em
Implementáció elrejtése
vektor.hpp #ifndef VEKTOR_HPP #define VEKTOR_HPP struct Vektor { Vektor(int M); ~Vektor(); int osszeg(); private: int *v; int m; }; #endif
main.cpp #include “vektor.hpp” int main() { int M=10; Vektor v(M); cout << v.osszeg(); }
vektor.cpp #include “vektor.hpp” Vektor::Vektor(int M) { v=new int[M]; m=M; for (int i=0;i<m;i++) v[i]=i+1; } Vektor::~Vektor() { delete v; } int Vektor::osszeg() { int sum=0; for (int i=0;i<m;i++) sum+=v[i]; return sum; }
lé ke zt et ő
em
Implementáció elrejtése
●
Három szintet láttunk: –
Láthatóság módosítása ●
–
Több fájlba bontott program, fejlécfájlok ●
–
A fordítóprogram segítsége a különböző szerepű komponensek független fejlesztésében Fordítási egységek, személyekre bontható illetékesség, a kód ismerete nem szükséges
Könyvtárak ●
Újrafelhasználható típusok gyűjteménye, előre lefordítva, a kód megismerése sem lehetséges
Redundancia ●
Alapvető elv: a programban minden csak egyszer szerepeljen. –
A redundáns kód változtatása az ismétlődések és összefüggések minden pontján változtatást jelent
●
Következmény: amit lehet, ki kell emelni
●
Eddig –
Ciklust használtunk ismétlődő utasítások helyett
–
Konstansokat használtunk számok helyett
–
Függvényeket használtunk gyakori kódrészletek helyett
Öröklődés, bevezető ●
● ●
●
Az öröklődés lényege, hogy egy meglevő osztályból kiindulva hozunk létre új osztályt, és csak a változásokat írjuk meg Ősosztály: az az osztály, amiből kiindultunk Leszármazott: az az osztály, amiben felhasználtuk az ősosztályt Minden class és struct lehet ősosztály
Öröklődés class A { protected: int x; public: A() {x=0;} void kiir() {cout<<x;} }; class B : public A { public: B() {x=1;} }; int main() { B b; b.kiir(); }
Ősosztály
Leszármazott
Öröklődés ●
●
Lehetséges változások, amiket a leszármazott tartalmazhat: –
Új metódusok, amik az ősosztályban nem léteztek
–
Új mezők
–
Felüldefiniálás: egy már meglevő metódus mást csinál
A felüldefiniált metódus haszna, hogy más-más leszármazottak más-más viselkedésre képesek
Öröklődés class A { protected: int x; public: A() {x=0;} void kiir() {cout<<x;} }; class B : public A { public: B() {x=1;} void ujfuggveny(..) {..} T ujmezo; void kiir() { //mást csinálunk } };
új tagfüggvény új mező felüldefiniált tagfüggvény
Példa öröklődés használatára ●
●
●
●
Szeretnénk két gombot csinálni, amik mást csinálnak, ha megnyomják őket, de mindkettő jelezze a színével, ha felette van az egér
Megoldás: olyan ősosztályt csinálni, amiben a szín kezelése meg van oldva majd a két leszármazottban a két funkciót megírjuk Így minden kód csak egyszer van a programban
Példa öröklődés használatára ●
●
Következmény: létrehozunk egy “Gomb” osztályt, ami konkrét funkció nélkül, minden gomb közös vonásait viseli –
megjelenés
–
eseménykezelés
–
felület a program többi része felé
–
...
Amit később úgy használunk, hogy “öröklődünk belőle”, és a konkrét funkciót az örökösbe tesszük
Polimorfizmus ●
Többalakúság –
●
●
●
Ilyet már láttunk: istream és ifstream
is-a reláció: “az ifstream az egy istream”. A “kockarajzológomb az egy gomb”. A [leszármazott] az egy [ősosztály] Dinamikus memóriakezelésnél ez látványos: ha B leszármazottja A-nak, akkor A * m=new B(..) A típusfogalmunk finomodott: m-nek kétféle típusa van
Polimorfizmus ●
● ●
●
ha B leszármazottja A-nak, akkor A * m=new B(..) m statikus típusa: A*, hisz így lett deklarálva m dinamikus típusa: B*, hisz így lett példányosítva Ennek a tulajdonságnak komoly előnye, hogy –
futásidőben dönthetjük el a (dinamikus) típust
–
lehet pl. vektorunk különböző (dinamikus) típusú elemekkel ●
mint a második beadandóban...
Polimorfizmus ●
● ●
●
A metódushívásnál a típus művelete hívódik meg. De most melyik típus szerinti művelet? Ezt eldönthetjük: Alapértelmezésben a statikus típus szerinti tagfüggvény hívódik meg Ha szeretnénk, hogy egy tagfüggvény a dinamikus típus szerint hívódjék meg, használjuk a “virtual” módosítót
Polimorfizmus class A { public: void simafv() { cout virtual void vfv() { }; class B : public A { public: void simafv() { cout virtual void vfv() { };
<< "A1 ";} cout <<"A2 ";}
<< "B1 ";} cout <<"B2 ";}
int main(){ A *aa = new A; A *ab = new B; B *bb = new B; aa->simafv(); aa->vfv(); ab->simafv(); ab->vfv(); bb->simafv(); bb->vfv(); return 0; }
Az eredmény: A1 A2 A1 B2 B1 B2
Polimorfizmus class A { public: void simafv() { cout virtual void vfv() { }; class B : public A { public: void simafv() { cout virtual void vfv() { };
<< "A1 ";} cout <<"A2 ";}
<< "B1 ";} cout <<"B2 ";}
int main(){ A *aa = new A; A *ab = new B; B *bb = new B; aa->simafv(); aa->vfv(); ab->simafv(); ab->vfv(); bb->simafv(); bb->vfv(); return 0; }
Az eredmény: A1 A2 A1 B2 B1 B2
Dinamikus kötés
Öröklődés class A { protected: int x; public: A() {x=0;} virtual void kiir() {cout<<x;} }; class B : public A { public: B() {x=1;} void ujfuggveny(..) {..} T ujmezo; virtual void kiir() { //mást csinálunk, például //másképp írjuk ki } };
Általában tehát ennek lesz haszna: virtuális tagfüggvényekkel biztosítjuk a dinamikus kötést, ezzel nyitva hagyva a lehetőséget az általános ősosztályból a konkrét leszármazott felé
Néhány példa ●
Sakkprogram
●
Ősosztály: “Bábu”
●
Leszármazottak: “Vezér” “Bástya”, stb
●
Indoklás: –
a lépés lehetőségének kiszámítása más-más kódrészleteket kíván, de egységes felületre van szükség, tehát a virtuális műveletek: kirajzolás, lépéslehetőségvizsgálat
–
az ősosztálynak már lehet akár közös “lépés(ide)” metódusa, ami ellenőrzi a lépés érvényességét a virtuális “lépéslehetőség” metódussal.
Néhány példa ●
Ablakozó (ilyet kell majd csinálni)
●
Ősosztály: widget (“bigyó”)
●
Leszármazottak: “gomb” “számbeállító”, stb
●
Indoklás: –
Így megoldható egy olyan közös kezelőfelület, amelyik a fókuszt, megjelenítést és az események továbbítását végzi, függetlenül a tartalomtól. Az egyes widgetek megjelenítő, kiválasztó, eseménykezelő tagfüggvényei virtuálisak, ezért közös felületet alkotnak
Néhány példa ●
Játékprogram: Márió
●
Ősosztály: Sprite
●
Leszármazottak: Márió, Szörny, Háttér, Platform
●
Indoklás: –
A rajzolás műveletei ezzel kiemelhetőek, a “Szörny” osztály továbbörökíthető az egyes jellemző viselkedésű leszármazottakra. Közös rajzolómotor hozható létre. A “viselkedés” virtuális tagfüggvény a Márió-ban a felhasználót, a Szörny-ben a gépi irányítást tartalmazza.
Öröklődés ●
●
Nagyon sok lehetőség van az öröklődésben, ebből nekünk ezek fontosak egyelőre: –
Statikus és dinamikus típus
–
Virtuális tagfüggvények
Tehát egyelőre a következő irányelveket tartsuk be, bár néha indokolt lehet az ellenkezője: –
minden felüldefiniált metódus legyen virtuális
–
private helyett protected láthatóság
–
mindig :public öröklés
Öröklődés ●
●
Témák, amikkel még foglalkozunk később: –
protected és private öröklés
–
absztrakt metódus, absztrakt osztály
–
esetleg többszörös öröklés
Sok következménye van az öröklődés lehetőségének, ezekkel a lehetőségekkel szerencsére nem kell mind foglalkozni ahhoz, hogy a már megismert lehetőségeket használjuk
Objektum elvű programozás ●
“Objektum orientált programozás” (OOP)
●
Új hozzáállásra van szükség –
Eleve úgy tervezzük a típusainkat, hogy kihasználjuk a lehetséges öröklődésből származó előnyöket
–
A meglevő (akár szabványos) típusokat is felhasználjuk ősosztálynak, ha hasznos
–
A tagfüggvények szerepét az eddigieknél is alaposabban kell megválasztani, a funkciók szétválasztásának elmulasztása követhetetlenné teszi a szerkezetet
Összefoglalás ●
●
●
Öröklés: egy osztály újrafelhasználása, hogy csak a változásokat kelljen megírni Dinamikus típus, dinamikus kötés, polimorfizmus: a konkrét tagfüggvény kiválasztás híváskor futásidőben dől el Új tervezési mód: az azonos szerepű tagfüggvényekkel rendelkező típusokat közös ős köré szervezzük