Öröklődés Objektumok közötti speciális kapcsolat. (Egy osztály a másik általánosításaként, vagy éppen specializált változataként jelenik meg.) Ember, diák, tanár.
Program készítése két módon: IS_A reláció. Program készítése két módon: független osztály kialakítása öröklődéssel történő definíció alaposztály származtatott osztály
Újrafelhasználhatóság Az öröklődéssel történő megoldás előnyei: hasonlóság kiaknázása miatt a program egyszerűbb - osztálykönyvtárak kialakítása, felhasználása (újrafelhasználhatóság).
Öröklődés leírása Ember név, kor Diák +átlag, évfolyam általánosít Ember név, kor az egy az egy Diák +átlag, évfolyam Tanár +fizetés, tárgy specializál
Geometriai alakzatok példája Shape hely, szín, Move() analitikus öröklés Rect +sarok, Draw() Line +vég, Draw() Circle +sugár, Draw() korlátozó öröklés Square
Alakzatok fajtái Általános alakzat: Shape. (szín, hely) Speciális alakzatok: Rect, Line, Circle
Általános alakzat osztálya class Shape { protected: int x, y, col; public: Shape ( int x0, int y0, int col0 ) { x = x0; y = y0; col = col0; } void SetColor ( int c ) { col = c; } };
Line osztály class Line : public Shape { // Line = Shape + ... int xe, ye; public: Line( int x1, int y1, int x2, int y2, int c ) : Shape( x1, y1, c ) { xe = x2, ye = y2; } void Draw( ); void Move (int dx, int dy ) ; };
Line osztály Draw() metódusa void Line :: Draw( ) { // cout << "\nLine: " << x << ',' << y; // cout << " : " << xe << ',' << ye << " ; " << col; _SetColor (col ) ; // rajz a grafikus könyvtárral _MoveTo ( x,y ); _LineTo (xe, ye ) ; }
Line osztály Move() metódusa void Line :: Move( int dx, int dy ) { int cl = col; //tényleges rajzolási szín elmentése col = BGD; //rajzolási szín legyen a háttér színe Draw( ); // A nonal letörlése az eredeti helyről x += dx; y += dy; // mozgatás: a pozíció változik col = cl; // a rajzolási szín a tényleges szín Draw( ); // A vonal felrajzolása az új pozícióra
Rect osztály class Rect : public Shape { // Rect = Shape + ... int xc, yc; public: Rect( int x1, int y1, int x2, int y2, Color c ) : Shape( x1, y1, c ) { xc = x2, yc = y2; } void Draw( ); void Move ( int dx, int dy ) ; };
Rect osztály Draw() metódusa void Rect :: Draw( ) { cout << "\nRect: " << x << ',' << y; cout << " : " << xc << ',' << yc << " ; " << col; }
Származtatással kapcsolatos fogalmak protected hozzáférés-módosító szó class Line :: public Shape {.....} - származtatás, szülő (ős), gyermek (származtatott) public és private jelentése. Származtatott osztályban a tagfüggvényeket újradefiniálhatjuk (ez felülbírálja az alaposztály ugyanilyen nevű tagfüggvényét.
Konstruktorok meghívása Line konstruktorának definíciója: Line( int x1, int y1, int x2, int y2, int c ) : Shape( x1, y1, c ) { xe = x2, ye = y2; } Definíció szerint az alaposztály konstruktora is meghívásra kerül.
Move függvény vizsgálata Vizsgáljuk meg az egyes osztályokban lévő Move függvényt! Ugyanaz, csak mindegyik más Draw() függvényt hív meg. Virtuális tagfüggvény!
Move() elhelyezése az ősosztályban class Shape { protected: int x, y, col; public: Shape( int x0, int y0, int col0 ) { x = x0; y = y0; col = col0; } void SetColor( int c ) { col = c; } void Move ( int dx, int dy ) ; virtual void Draw ( ) { } };
Move() megvalósítása void Shape :: Move( int dx, int dy ) { int cl = col; //tényleges rajzolási szín elmentése col = BGD; //rajzolási szín legyen a háttér színe Draw( ); // A vonal letörlése az eredeti helyről x += dx; y += dy; // mozgatás: a pozíció változik col = cl; // a rajzolási szín a tényleges szín Draw( ); // A vonal felrajzolása az új pozícióra
Line osztály class Line : public Shape { // Line = Shape + ... int xe, ye; public: Line( int x1, int y1, int x2, int y2, int c ) : Shape( x1, y1, c ) { xe = x2, ye = y2; } void Draw( ); };
Rect osztály class Rect : public Shape { // Rect = Shape + ... class Rect : public Shape { // Rect = Shape + ... int xc, yc; public: Rect( int x1, int y1, int x2, int y2, Color c ) : Shape( x1, y1, c ) { xc = x2, yc = y2; } void Draw( ); };
Üzenetek objektumoknak void main ( ) { Rect rect( 1, 10, 2, 40, RED ); Line line( 3, 6, 80, 40, BLUE ); Shape shape( 3, 4, GREEN ); shape.Move( 3, 4 ); // 2 db Draw hivás line.Draw( ); line.Move( 10, 10 ); // 2 db Draw hivás
Metódusok meghívása indirekten Shape * sp[10]; sp[0] = ▭ sp[1] = &line; // nem kell cast for( int i = 0; i < 2; i++ ) sp[i] -> Draw( ); //indirekt Draw() }
Virtuális Shape::Draw Nem virtuális shape.Move() line.Draw() Line::Draw line.Move() sp[0] -> Draw(), mutatótípus Shape * de Line objektumra mutat sp[1] -> Draw(), de Rect objektumra mutat Rect::Draw
Szimuláció C programmal struct Shape { int x, y, col }; //Shape adattagjai void Draw_Shape (struct Shape * this) {} //Shape::Draw
Move() szimulációja void Move_Shape (struct Shape * this, int dx, int dy) { //Shape::Move int cl = this ->col; //tényleges rajzolási szín elmentése this ->col = BGD; //rajzolási szín legyen a háttér színe Draw_Shape( this ); // A vonal letörlése az eredeti helyről this ->x += dx; this ->y += dy; // mozgatás: a pozíció változik this ->col = cl; // a rajzolási szín a tényleges szín Draw_Shape( this ); // A vonal felrajzolása az új pozícióra }
Tisztán virtuális tagfüggvény - absztrakt alaposztály. class Shape { protected: int x, y, col; public: Shape( int x0, int y0, int col0 ) { x = x0; y = y0; col = col0; } void SetColor( int c ) { col = c; } void Move ( int dx, int dy ) ; virtual void Draw ( ) = 0; };
Virtuális függvények Tegyük fel, hogy van egy A alaposztályunk és egy B származtatott osztályunk, amelyben az alaposztály f függvényét újradefiniáltuk. class A { public: void f ( ); // A: :f }; class B : public A { void f ( ); // B: : f
Metódus kiválasztása Az objektum orientált programozás alapelve szerint egy üzenetre lefuttatott metódust a célobjektum típusa és az üzenet neve ( valamint az átadott paraméterek típusa ) alapján kell kiválasztani. Tehát, ha definiálunk egy A típusú a objektumot és egy B típusú b objektumot, és mindkét objektumnak f üzenetet küldünk, akkor azt várnánk el, hogy az a objektum esetében az A :: f, míg a b objektumra a B:: f tagfüggvény aktivizálódik. Vannak egyértelmű esetek, amikor ezt a kívánságunkat a C++ fordító program minden további nélkül teljesíteni tudja:
Objektumok definiálása { A a; B b; a. f ( ) ; // A : : f hívás b. f ( ) ; // B : : f hívás }
Direkt üzenet küldése Ebben a példában az a.f ( ) A típusú objektumnak szól, mert az a objektumot az A a; utasítással definiáltuk. Így a fordítónak nem okoz gondot, hogy ide az A:: f hívást helyettesítse be. A C++ nyelvben azonban vannak olyan lehetőségek is, amikor a fordító program nem tudja meghatározni a célobjektum típusát. Ezek a lehetőségek a részint az indirekt üzenetküldést, részint az objektumok által saját maguknak küldött üzeneteket foglalják magukban. Nézzük először az indirekt üzenetküldést:
Indirekt üzenet küldése { A a ; B b; A * pa ; if ( getchar ( ) = = 'i ' ) pa = & a ; else pa = & b; pa - > f ( ) ; // indirekt üzenetküldés }
Célobjektum meghatározása Az indirekt üzenetküldés célobjektuma, attól függően, hogy a program felhasználója az i billentyűt nyomta-e le, lehet az A típusú a objektum vagy a B típusú b objektum. Ebben az esetben fordítási időben nyilván nem dönthető el a célobjektum típusa. Megoldásként két lehetőség kínálkozik:
Nem virtuális eset Kiindulva abból, hogy a pa mutatót A* típusúnak definiáltuk, jelentse ilyen esetben a pa -> f ( ) az A: : f tagfüggvény meghívását. Ez ugyan téves, ha a pa a b objektumot címzi meg, de ennél többre fordítási időben nincs lehetőségünk.
Virtuális eset 1Bízzuk valamilyen futási időben működő mechanizmusra annak felismerését, hogy pa ténylegesen milyen objektumra mutat, és ennek alapján futási időben válasszunk A : : f és B : : f tagfüggvények közül.
Két lehetőség a C++-ban A C++ nyelv mindkét megoldást felkínálja, melyek közül aszerint választhatunk, hogy az f tagfüggvényt az alaposztályban normál tagfüggvénynek ( 1. lehetőség ), vagy virtuálisnál ( 2. lehetőség ) deklaráltuk.
Önmagának szóló üzenet Hasonló a helyzet az "önmagukban beszélő" objektumok esetében is. Egészítsük ki az A osztályt egy g tagfüggvénnyel, amely meghívja az f tagfüggvényt. class A { public: void f ( ) ; // A : : f void g ( ) { f ( ) ; ) };
B osztályban f átdefiniálása class B : public A { public: void f ( ) ; // B : : f } ;
A::f() vagy B::f() meghívása A B típusú objektum változtatás nélkül örökli a g tagfüggvényt és újra definiálja az f-t. Ha most egy B típusú objektumnak küldenénk g üzenetet, akkor a saját magának, azaz az eredeti g üzenet célobjektumának küldene f üzenetet. Mivel az eredeti üzenet célja B típusú, az lenne természetes, ha ekkor a B::f hívódna meg. A tényleges célobjektum típusának felismerése azonban nyílván nem végezhető el fordítási időben. Tehát vagy lemondunk erről a szolgáltatásról és az f tagfüggvényt normálnak deklarálva a fordító a legkézenfekvőbb megoldást választja, miszerint a g törzsében mindig az A::f tagfüggvényt kell aktivizálni. Vagy pedig egy futási időben működő mechanizmusra bízzuk, hogy a g törzsében ismerje fel az objektum tényleges típusát és a meghívandó f-t ez alapján válassza ki.
Többszörös öröklődés (multiple inheritance) Irodai alkalmazottakat kezelő probléma: Alkalmazottak (Employee) Menedzserek (Manager) Ideiglenes alkalmazottak (Temporary) Ideiglenes menedzserek (Temporary manager)
Osztályok
Employee osztály class Employee { protected: char name[20]; long salary; public: Employee (char *nm, long sl) ( strcpy( name,nm ) ; salary = sl ; } };
Manager osztály class Manager : public Employee { int level ; public: Manager (char *nam, long sal, int lev) : Employee (nam, sal) ( level = lev; } } ;
Temporary osztály class Temporary : public Employee { int emp_time; Temporary (char *nam, long sal, int time ); : Employee (nam, sal ) { emp_time = time} };
Temp_Man osztály class Temp_Man : public Manager, public Temporary { Temp_Man (char *nam, long sal, int lev, int time) : Manager (nam, sal, lev), Temporary (nam, sal, time) {} };
Lehetséges elhelyezésük a memóriában
Nevek ütközése class A{ class B{ protected: protected: int x; int x; }; }; class C: public A, public B { int f() { x=3; x=5;} };
Többértelműség megszüntetése A többértelműség megszüntethető a scope operátor felhasználásával: int f() { { A::x=3; B::x=5;} Azonos nevű adattagok összevonása ellen szól a kompatibilitás elvesztése.
Megoldás: class Manager: virtual public Employee {…}; class Temporary: virtual public Employee {…}; class Temp_Man: virtual public Manager, public Temporary { public: Temp_Man ( char * nam, long sal, int lev, int ti,e) :Employee (nam, sal), MAneger (NULL, 0L, lev), Temporary (NULL, 0L, ti,e) { } };
Memóriában való elhelyezésük
A konstruktor feladatai A virtuális alaposztályok konstruktorainak hívása, akkor is, ha a virtuális alaposztály nem közvetlen ős. A közvetlen, nem virtuális alaposztályok konstruktorainak hívása. A saját rész konstruálása A virtuálisan származtatott osztályok objektumaiban egy mutatót kell beállítani az alaposztály adattagjainak megfelelő részre. ha az objektumosztályban van olyan virtuális függvény, amely itt új értelmet nyer (azaz az osztály a virtuális függvényt újradefiniálja), akkor az annak megfelelő mutatókat a saját megvalósításra kell állítani. A tartalmazott objektumok konstruktorainak meghívása. A konstruktornak a programozó által megadott részei csak a fenti feladatok elvégzése után kerülnek végrehajtásra.
A destruktorok feladatai A destruktor programozó által megadott részének a végrehajtása. A komponensek megszüntetése a destruktoraik hívásával. A közvetlen, nem-virtuális alaposztályok destruktorainak hívása. A virtuális alaposztályok destruktorainak hívása.