Dariusz Wardowski
dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 2/12
Relacje między klasą macierzystą a potomną
Dyrektor anna(500,„Anna”, „Lis”, „Dyrektor”, 3000);
Anna.wypiszDane();
• Obiekt klasy potomnej może korzystać z metod klasy macierzystej.
• Wskaźnik do klasy macierzystej może wskazywać na obiekt klasy potomnej.
Pracownik* p1;
p1 = &anna;
p1->wypiszDane();
• Referencja do klasy macierzystej może odnosić się do obiektu klasy macierzystej.
Pracownik& p2;
p2 = anna;
p2.wypiszDane();
•Metody zdefiniowane w klasie potomnej mogę być wywołane jedynie przez referencję lub wskaźnika do obiektu klasy potomnej.
p1->setDodatekFunkcyjny(500); //błąd!!!
• Nie można przypisywać adresów i obiektów klasy macierzystej do wskaźników i referencji klasy potomnej.
Pracownik p3();
Dyrektor& d1 = p3; //błąd!!!
Dyrektor* d2 = &p3; //błąd!!!
Funkcje a referencje i wskaźniki do klasy macierzystej
void wypisz(Pracownik & p) {
cout << „Dane pracownika: ” << endl;
p.wypiszDane();
}
Funkcje, których parametrami są wskaźniki lub referencje do klasy macierzystej mogą być używane zarówno z obiektami klasy macierzystej jak i potomnej.
Powyższą funkcje możemy wywołać zarówno z argumentami typu Pracownik jak i Dyrektor:
Pracownik zenek;
Dyrektor wlodek;
wypisz(zenek);
wypisz(wlodek);
Analogicznie dla wskaźników:
void wypisz(Pracownik * p) {
cout << „Dane pracownika: ” << endl;
p->wypiszDane();
}
wypisz(&zenek);
wypisz(&wlodek);
dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 4/12
Inicjalizacja obiektu klasy macierzystej za pomocą obiektu klasy potomnej
Dyrektor dyr(300, „Jan”, „Kowalski”, „Dyrektor”, 3000);
Pracownik p(dyr);
Zgodność referencji typu klasy macierzystej z obiektami klasy potomnej daje również możliwość wykorzystywania obiektów klasy potomnej do tworzenia obiektów klasy macierzystej.
W powyższej sytuacji działa konstruktor kopiujący klasy macierzystej:
Pracownik(const Pracownik &);
Przypisanie obiektu klasy potomnej do obiektu klasy macierzystej
Dyrektor dyr(300, „Jan”, „Kowalski”, „Dyrektor”, 3000);
Pracownik p;
p = dyr;
Zgodność referencji typu klasy macierzystej z obiektami klasy potomnej daje również możliwość przypisywanie obiektów klasy potomnej do obiektów klasy macierzystej.
W powyższej sytuacji działa w sposób niejawny operator przypisania:
Pracownik & operator=(const Pracownik &) const;
Referenja do klasy macierzystej odwołuje się w tym przypadku do klasy potomnej, kopiowana jest jedynie część „macierzysta” obiektu dyr do obiektu p.
dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 6/12
Rodzaje dziedziczenia
C++ wyróżniamy trzy rodzaje dziedziczenia:
• publiczne
• chronione
• prywatne
Najczęściej występującym rodzajem dziedziczenia jest dziedziczenie publiczne.
Relacja typu „jest”, czyli dziedziczenie publiczne
Zgodnie z tą relacją obiekt klasy potomnej jest również obiektem klasy macierzystej. Wszystko co jest możliwe do wykonania z obiektem klasy macierzystej powinno się dać zrobić z obiektem klasy potomnej.
Przykłady relacji typu jest:
Każdy dyrektor jest pracownikiem.
Każdy czworokąt jest wielokątem.
Każdy pomidor jest warzywem.
W przypadku dziedziczenia publicznego, nowa klasa dziedziczy wszystkie pola i metody klasy macierzystej. Ponadto do nowej klasy (potomnej) można dodawać nowe pola i metody charakterystyczne wyłącznie dla klasy potomnej.
dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 8/12
Polimorfizm
Klasa Dyrektor dziedziczyła po klasie Pracownik, w szczególności odziedziczyła metodę getPensja() nie wnosząc do niej żadnych zmian. Bardzo często konieczna jest jednak zmiana działań odziedziczonych metod.
W takim przypadku należy uzależnić działanie metody w zależności od klasy obiektu na rzecz którego wywoływana jest owa metoda.
Mechanizm polegający na tym, że jedna metoda może występować w wielu różnych postaciach w zależności od kontekstu jej wywołania nazywamy polimorfizmem.
W celu wdrożenia polimorficznego działania dziedziczenia publicznego stosujemy:
• redefinicje metod klasy macierzystej w klasie potomnej
• metody wirtualne
metoda virtual-na
Dla klasy Dyrektor inna wersja metody getPensja() może być postaci:
double getPensja() {
return Pracownik::getPensja() + dodatekFunkcyjny;
}
Aby odpowiednia wersja metody getPensja() została wywołana należy przed deklaracją metody umieścić słowo virtual. W części deklaracyjnej słowo virtual pomijamy.
virtual double getPensja();
W przypadku poprzedzenia deklaracji słowem virtual, odpowiednia wersja metody zostanie wywołana w oparciu o typ obiektu, do którego odwołuje się referencja lub wskaźnik.
Dyrektor anna;
Pracownik janek;
Pracownik & p1 = anna;
Pracownik & p2 = janek;
p1.getPensja(); //wywołana metoda Dyrektor::getPensja();
dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 10/12
bez virtual
W przypadku, gdy metoda przedefiniowana nie jest poprzedzona w części deklaracyjnej słowem virtual, wówczas sposób działania metody opiera się na typie referencji, a nie na typie obiektu.
Dyrektor anna;
Pracownik janek;
Pracownik & p1 = anna;
Pracownik & p2 = janek;
p1.getPensja(); //wywołana metoda Pracownik::getPensja();
p2.getPensja(); //wywołana metoda Pracownik::getPensja();
Zazwyczaj dobrą praktyką jest poprzedzanie w klasie macierzystej słowem virtual deklaracje tych metod, które są przedefiniowane w klasie potomnej. Zabieg ten pozwala wybrać odpowiednie wersje metod, na podstawie obiektu, na rzecz którego są one wywoływane, a nie na postawie referencji lub wskaźnika.
wykorzystanie metod wirtualnych
Czasami mamy do czynienia z wieloma obiektami zarówno klasy macierzystej jak i pochodnej. W takiej sytuacji, aby działanie naszego kodu było uniwersalne, wykorzystuje się tablicę wskaźników do typu klasy macierzystej.
Pracownik * tabP[5];
tabP[0] = new Pracownik(„Jan”, „Kowalski”, „fizyczny”, 1500);
tabP[1] = new Pracownik(„Adam”, „Lis”, „fizyczny”, 1600);
tabP[2] = new Pracownik(„Joanna”, „Dzik”, „sekretarka”, 1800);
tabP[3] = new Pracownik(„Danuta”, „Kot”, „księgowa”, 2500);
tabP[4] = new Dyrektor(600,„Krzysztof”, „Borsuk”, „dyrektor”, 3500);
for (int i=0; i<5; i++) {
tabP[i]->wypiszDane();
cout << tabP[i]-> getPensja() << endl;
}
dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 12/12