Dědičnost V objektově orientovaném programování je dědičnost způsob, jak vytvořit novou třídu použitím již existujících definic jiných tříd. Takto vytvořené třídy přebírají vlastnosti a metody svého předka a doplní k nim své vlastní. Co se nedědí: - konstruktor - lze jej vyvolat v konstruktoru odděděné třídy. - destruktor - je volán automaticky v destruktoru odvozené třídy - přetížené operátory =, new, delete Chování konstruktorů a destruktorů při dědění Pokud rodičovská třída nemá konstruktor (překladač tedy vytváří implicitní konstruktor) nebo má rodičovská třída prázdný konstruktor, není nutné jej volat v konstruktroru v odděděné třídě. Při vytváření instance třídy se tak nejprve vyvolá bezparametrický konstruktor nejvyšší nadtřídy, poté jejího potomka, atd., až se vyvolá požadovaný konstruktor pro instanci. class A A()cout << "Konstruktor tridy A"; class B: public A int y; B() cout << "Konstruktor tridy B"; Pokud je třeba vyvolat konstruktor předka se vstupními inicializačními parametry, volá se v konstruktoru potomka za dvojtečkou na začátku seznamu inicializátorů. class A A(int a) class B : public A B(int a, int b) : A(a) //spravne B(int a, int b) A(a) //spatne B(int a, int b) //spatne Pořadí volání destruktorů se děje v opačném pořadí než-li u konstruktorů. Nejprve se volá destruktor potomka a poté destruktor předka.
class Base Base() cout << "Konstruktor predka\n"; ~Base() cout << "Destruktor predka\n"; class Derived : public Base Derived() cout << "Konstruktor potomka\n"; ~Derived() cout << "Destruktor potomka\n"; int main ( void ) Derived d; return 0; Výpis na obrazovce: Konstruktor predka Konstruktor potomka Destruktor potomka Destruktor predka Překrývání metod Někdy ve třídě potomka potřebujeme, aby se implementace odděděných metod mírně lišily od metod třídy předka. V takovém případě můžeme tuto metodu překrýt. Ve třídě potomka napíšeme hlavičku metody stejnou jako ve třídě předka, ale dáme jí nové tělo. class Base void display() cout << "Třída Base\n"; class Derived : public Base void display() cout << "Třída Derived\n"; Derived d; d.display(); Base b; b.display(); Při překrývání metod můžeme narazit na jedno úskalí. Pokud vytvoříme např. třídu Zivocich, která bude mít vlastnost vek, konstruktor a přetíženou metodu OzviSe(string s="zivocich"). Od třídy Zivocich oddědíme třídu Pes, která překryje pouze jednu metodu OzviSe(). Druhá metoda OzviSe(string s) se tak pro třídu Pes stává metodou skrytou (nelze pro Psa použít). enum RasaPitbul, Jezevcik, Labrador, Kokrspanel
class Zivocich int vek; Zivocich(int v = 1)vek = v; cout << "zivocich "; string OzviSe()return "Jsem zivocich"; string OzviSe(string s)return s; class Pes: public Zivocich string name; Rasa rasa; Pes(int v, string n, Rasa r); string OzviSe()return "Haf"; Pes::Pes(int v=1, string n="hafik", Rasa r = Jezevcik ):Zivocich(v),name(n),rasa(r) Pes p(5); cout << p.ozvise(); cout << p.ozvise("vrr, haf, haf"); //nezna zavolat cin >> x; Virtuální metody Pozdní vazba (late binding; virtual call) - Je-li metoda nějaké třídy virtuální či čistě virtuální, pak všechny metody se stejným jménem, počtem a typy parametrů deklarované v potomcích třídy jsou považovány za různé implementace téže metody. - Která implementace se vybere, tedy které tělo bude zavoláno, se rozhoduje až za běhu programu podle skutečného typu celého objektu. - Použije se tělo z posledního potomka, který definuje tuto funkci a je součástí celého objektu. - Pozdní vazba má smysl pouze u vyvolání na objektu určeném odkazem Obyčejná (nevirtuální) metoda class A int f() return 1; class B : public A virtual int f() return 1;
A oa; A * paa = & oa; B ob; B * pbb = & ob; A * pab = pbb; paa->f(); // zavolá se metoda ze trídy A - A::f pbb->f(); // zavolá se metoda ze trídy B - B::f pab->f(); // zavolá se metoda ze trídy A - A::f Virtuální metoda class A virtual int f() return 1; class B : public A virtual int f() return 2; A oa; A * paa = & oa; B ob; B * pbb = & ob; A * pab = pbb; paa->f(); // zavolá se metoda ze trídy A - A::f pbb->f(); // zavolá se metoda ze trídy B - B::f pab->f(); // zavolá se metoda ze trídy B - B::f Čistě virtuální metoda - Deklarována bez definování těla - Tělo bude doplněno později u potomka Abstraktní třída - Třída obsahující nějaké čistě virtuální metody (přímo či v předcích), jejichž tělo ještě nebylo definováno... - Nelze do ní vytvořit instanci - Lze používat ukazatele na tuto třídu a vyvolávat metody této třídy - Typicky neobsahuje žádné datové položky class AbstractGraphicObject virtual void paint() = 0; //ciste virtualni metoda AbstractGraphicObject * next; Mnohonásobná dědičnost V jazyce C++ lze kromě jednoduché dědičnosti využít i mnohonásobnou dědičnost. Napsat kód pro vícenásobnou dědičnost je poměrně snadné viz. příklad: class A int a; class B
int b; class AB : public A, public B int ab; Problémy mohou nastat ve chvíli, kdy mají rodičovské třídy vlastnosti nebo metody se stejnými názvy. Potom k nim musíme ve třídě potomka přistupovat přes jméno rodičovské třídy a operátor :: nebo je můžeme v případě metod obě přetížit a získat tím metodu pouze jednu. class A class B class AB : public A, public B AB ab; ab.a::x = 5; // pouzije x z A ab.b::x = 6; // pouzije x z B class Hello void g() cout << "Hello World\n"; class Goodbye void g() cout << "Goodbye World\n"; class HelloGoodbye : public Hello, public Goodbye void g() //pretizi obe Hello::g() i Goodbye::g() Hello::g(); Goodbye::g(); HelloGoodbye hg; hg.g();
Mnohonásobná dědičnost společný prapředek Pokud dědíme od dvou (či více) tříd, které mají společného předka a nechceme mít vlastnosti a metody, které pocházejí od společného prapředka ve třídě zdvojené, stejně jako v předchozích příkladech, musíme využít virtuální dědičnost. class Animal int vek; Animal(int v = 1) vek = v; cout << "Animal " << vek << endl; virtual void eat() int getvek() return vek; class Mammal : public virtual Animal Mammal(int v = 1):Animal(v)cout << "Mammal " << vek << endl; virtual void walk() class WingedAnimal : public virtual Animal WingedAnimal(int v):animal(v)cout << "WingedAnimal " << vek << endl; virtual void flap() class Bat : public Mammal, public WingedAnimal Bat(int v):animal(v), Mammal(v), WingedAnimal(v)cout << "Bat " << vek << endl; //pokud nepotrebuji, aby se pro prapredka volal konstruktor s parametrem není nutne pouzit volani Animal(v). Bude automaticky zavolan implicitni konstruktor při volani konstruktoru Mammal. int main() Bat* bat = new Bat(5); return 0; Příklad: Vytvořte třídy hráč a zbraň. Zbraň bude mít jako vlastnost body, které zbraň bude přidávat hráči při útoku (tato třída bude představovat sečnou zbraň). Dále od třídy Zbran odděďte třídu PalnaZbran, která bude mít navíc jako vlastnosti velikost zásobníku a počet nábojů. Třídě PalnaZbran připište metody pro přidání nábojů do zásobníku (pozor nemůžete přidat více nábojů, než se do zásobníku vejde) a metodu, která vrací informaci o tom, jestli jsou ve zbrani ještě náboje. Vytvořte třídu hráč, která bude obsahovat jako vlastnosti hráčův útok, obranu, body na zdraví a zbraň, kterou hráč má u sebe (může to být palná nebo sečná zbraň). Všem třídám zapouzdřete vlastnosti, vytvořte konstruktory a metody set, get, které budete potřebovat. Dále ve třídě hráč vytvořte instanční metodu souboj (hráč, kterému bude tato metoda příslušet, bude vždy útočník). Výsledek souboje bude rozdíl mezi celkový útokem útočníka (útok hráče + útok od zbraně pozor u střelné zbraně kontrolujte, jestli v ní má hráč náboje) a obranou obránce. O tento rozdíl snižte zdraví obránce. V metodě main vytvořte dva hráče a nechte je spolu navzájem dvakrát bojovat.