rklds Objektumok kztti specilis kapcsolat Egy osztly a
Ö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.
• 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 • általánosít Ember név, kor az egy Diák +átlag, évfolyam az egy 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() korlátozó öröklés Square Circle +sugár, Draw()
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 x 0, int y 0, int col 0 ) { x = x 0; y = y 0; col = col 0; } void Set. Color ( int c ) { col = c; } };
Line osztály class Line : public Shape { // Line = Shape +. . . int xe, ye; public: Line( int x 1, int y 1, int x 2, int y 2, int c ) : Shape( x 1, y 1, c ) { xe = x 2, ye = y 2; } void Draw( ); void Move (int dx, int dy ) ; };
Line osztály Draw() metódusa void Line : : Draw( ) { // // } cout << "n. Line: " << x << ', ' << y; cout << " : " << xe << ', ' << ye << " ; " << col; _Set. Color (col ) ; // rajz a grafikus könyvtárral _Move. To ( x, y ); _Line. To (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 x 1, int y 1, int x 2, int y 2, Color c ) : Shape( x 1, y 1, c ) { xc = x 2, yc = y 2; } void Draw( ); void Move ( int dx, int dy ) ; };
Rect osztály Draw() metódusa void Rect : : Draw( ) { cout << "n. Rect: " << 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 x 1, int y 1, int x 2, int y 2, int c ) : Shape( x 1, y 1, c ) { xe = x 2, ye = y 2; } • 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 x 0, int y 0, int col 0 ) { x = x 0; y = y 0; col = col 0; } void Set. Color( 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 x 1, int y 1, int x 2, int y 2, int c ) : Shape( x 1, y 1, c ) { xe = x 2, ye = y 2; } void Draw( ); };
Rect osztály class Rect : public Shape { // Rect = Shape +. . . int xc, yc; public: Rect( int x 1, int y 1, int x 2, int y 2, Color c ) : Shape( x 1, y 1, c ) { xc = x 2, yc = y 2; } 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 Nem virtuális Shape: : Draw shape. Move() Shape: : Draw line. Draw() Line: : Draw line. Move() Line: : Draw Shape: : Draw sp[0] -> Draw(), Line: : Draw mutatótípus Shape * de Line objektumra mutat Shape: : Draw sp[1] -> Draw(), Rect: : Draw mutatótípus Shape * de Rect objektumra mutat Shape: : 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 x 0, int y 0, int col 0 ) { x = x 0; y = y 0; col = col 0; } void Set. Color( 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 { public: 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 1 Bí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; public: Temporary (char *nam, long sal, int time ); : Employee (nam, sal ) { emp_time = time} };
Temp_Man osztály class Temp_Man : public Manager, public Temporary { public: 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{ protected: int x; }; class B{ protected: 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, 0 L, lev), Temporary (NULL, 0 L, ti, e) { } };
Memóriában való elhelyezésük •
A konstruktor feladatai 1. A virtuális alaposztályok konstruktorainak hívása, akkor is, ha a virtuális alaposztály nem közvetlen ős. 2. A közvetlen, nem virtuális alaposztályok konstruktorainak hívása. 3. 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. 4. 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 1. A destruktor programozó által megadott részének a végrehajtása. 2. A komponensek megszüntetése a destruktoraik hívásával. 3. A közvetlen, nem-virtuális alaposztályok destruktorainak hívása. 4. A virtuális alaposztályok destruktorainak hívása.
- Slides: 50