LEKCJA35.TXT

(11 KB) Pobierz
LEKCJA 35:   O ZASTOSOWANIU DZIEDZICZENIA.  
________________________________________________________________ 
Z tej lekcji dowiesz si�, do czego w praktyce programowania  
szczeg�lnie przydaje si� dziedziczenie.  
________________________________________________________________ 
 
Dzi�ki dziedziczeniu programista mo�e w pe�ni wykorzysta� gotowe 
 
biblioteki klas, tworz�c w�asne klasy i obiekty, jako klasy  
pochodne wazgl�dem "fabrycznych" klas bazowych. Je�li bazowy  
zestw danych i funkcji nie jest adekwatny do potrzeb, mo�na np.  
przes�oni�, rozbudowa�, b�d� przebudowa� bazow� metod� dzi�ki  
elastyczno�ci C++. Zdecydowana wi�kszo�� standardowych klas  
bazowych wyposa�ana jest w konstruktory. Tworz�c klas� pochodn�  
powinni�my pami�ta� o istnieniu konstruktor�w i rozumie� sposoby 
 
przekazywania argument�w obowi�zuj�ce konstruktory w przypadku  
bardziej z�o�onej struktury klas bazowych-pochodnych. 
 
PRZEKAZANIE PARAMETR�W DO WIELU KONSTRUKTOR�W.  
 
Klasy bazowe mog� by� wyposa�one w kilka wersji konstruktora.  
Dop�ki nie przeka�emy konstruktorowi klasy bazowej �adnych  
argument�w - zostanie wywo�any (domy�lny) pusty konstruktor i  
klasa bazowa b�dzie utworzona z parametrami domy�lnymi. Nie  
zawsze jest to dla nas najwygodniejsza sytuacja. 
 
Je�eli wszystkie, b�d� cho�by niekt�re z parametr�w, kt�re  
przekazujemy konstruktorowi obiektu klasy pochodnej powinny  
zosta� przekazane tak�e konstruktorowi (konstruktorom) klas  
bazowych, powinni�my wyt�umaczy� to C++. Z tego te� powodu,  
je�li konstruktor jakiej� klasy ma jeden, b�d� wi�cej  
parametr�w, to wszystkie klasy pochodne wzgl�dem tej klasy  
bazowej musz� posiada� konstruktory. Dla przyk�adu dodajmy  
konstruktor do naszej klasy pochodnej Cpochodna:  
 
 
class CBazowa1  
{  
public:  
  CBazowa1(...);              //Konstruktor 
};  
 
class CBazowa2  
{   
public:  
  CBazowa2(...);              //Konstruktor 
};  
 
class Cpochodna : public CBazowa1, CBazowa2     //Lista klas  
{ 
public:  
  Cpochodna(...);             //Konstruktor 
};  
 
main()  
{  
  Cpochodna Obiekt(...);          //Wywolanie konstruktora 
 ... 
 
W momencie wywo�ania kostruktora obiektu klasy pochodnej  
Cpochodna() przekazujemy kostruktorowi argumenty. Mo�emy (je�li  
chcemy, nie koniecznie) przekaza� te argumenty konstruktorom  
"wcze�niejszym" - konstruktorom klas bazowych. Ta mo�liwo��  
okazuje si� bardzo przydatna (niezb�dna) w �rodowisku obiektowym 
 
- np. OWL i TVL. Oto prosty przyk�ad definiowania konstruktora w 
 
przypadku dziedziczenia. Rola konstruktor�w b�dzie polega� na  
trywialnej operacji przekazania pojedynczego znaku. 
 
class CBazowa1  
{ 
public:  
  CBazowa1(char znak) { cout << znak; }  
};  
 
class CBazowa2  
{ 
public:  
  CBazowa2(char znak) { cout << znak; }  
};  
 
class Cpochodna : public CBazowa1, CBazowa2  
{ 
public:  
  Cpochodna(char c1, char c2, char c3);   
};  
 
Cpochodna::Cpochodna(char c1,char c2,char c3) : CBazowa1(c2),  
 CBazowa2(c3)  
{  
     cout << c1;  
}  
 
Konstruktor klasy pochodnej pobiera trzy argumenty i dwa z nich: 
 
c2 --> przekazuje do konstruktora klasy CBazowa1  
c3 --> przekazuje do konstruktora klasy CBazowa2  
Spos�b zapisu w C++ wygl�da tak:  
 
Cpochodna::Cpochodna(char c1,char c2,char c3) : CBazowa1(c2),  
 CBazowa2(c3)  
 
Mo�emy zatem przekaza� parametry "w ty�" do konstruktor�w klas  
bazowych w taki spos�b:  
 
kl_pochodna::kl_pochodna(lista):baza1(lista), baza2(lista), ...  
 
gdzie:  
lista - oznacza list� parametr�w odpowiedniego konstruktora.  
 
W takiej sytuacji na li�cie argument�w konstruktor�w klas  
bazowych mog� znajdowa� si� tak�e wyra�enia, przy za�o�eniu, �e  
elementy tych wyra�e� s� widoczne i dost�pne (np. globalne  
sta�e, globalne zmienne, dynamicznie inicjowane zmienne globalne 
 
itp.). Konstruktory b�d� wykonywane w kolejno�ci:  
 
CBazowa1 --> CBazowa2 --> Cpochodna 
 
Dzi�ki tym mechanizmom mo�emy �atwo przekazywa� argumenty  
"wstecz" od konstruktor�w klas pochodnych do konstruktor�w klas  
bazowych.  
 
FUNKCJE WIRTUALNE. 
 
Dzia�anie funkcji wirtualnych przypomina rozbudow� funkcji  
dzi�ki mechanizmowi overloadingu. Je�li, zdefiniowali�my w  
klasie bazowej funkcj� wirtualn�, to w klasie pochodnej mo�emy  
definicj� tej funkcji zast�pi� now� definicj�. Przekonajmy si� o 
 
tym na przyk�adzie. Zacznijmy od zadeklarowania funkcji  
wirtualnej (przy pomocy s�owa kluczowego virtual) w klasie  
bazowej. Zadeklarujemy jako funkcj� wirtualn� funkcj� oddychaj() 
 
w klasie CZwierzak:  
 
class CZwierzak  
{  
public:  
  void Jedz();  
  virtual void Oddychaj();  
};  
 
Wyobra�my sobie, �e chcemy zdefiniowa� klas� pochodn� CRybka  
Rybki nie oddychaj� w taki sam spos�b, jak inne obiekty klasy  
CZwierzak. Funkcj� Oddychaj() trzeba zatem b�dzie napisa� w dwu  
r�nych wariantach. Obiekt Ciapek mo�e t� funkcj� odziedziczy�  
bez zmian i sapa� spokojnie, z Sardynk� gorzej: 
 
class CZwierzak  
{  
public:  
  void Jedz();  
  virtual void Oddychaj() { cout << "Sapie..."; } 
};  
 
class CPiesek : public CZwierzak  
{  
  char imie[30];  
} Ciapek; 
 
class CRybka 
  char imie[30];  
public:  
  void Oddychaj() { cout << "Nie moge sapac..."; }  
} Sardynka;  
 
 
Zwr�� uwag�, �e w klasie pochodnej w deklaracji funkcji s�owo  
kluczowe virtual ju� nie wyst�puje. W klasie pochodnej funkcja  
CRybka::Oddychaj() robi wi�cej ni� w przypadku "zwyk�ego"  
overloadingu funkcji. Funkcja CZwierzak::Oddychaj() zostaje  
"przes�oni�ta" (ang. overwrite), mimo, �e ilo�� i typ  
argument�w. pozostaje bez zmian. Taki proces - bardziej  
drastyczny, ni� overloading nazywany jest przes�anianiem lub  
nadpisywaniem funkcji (ang. function overriding). W programie  
przyk�adowym Ciapek b�dzie oddycha� a Sardynka nie.  
 
[P127.CPP] 
 
# include <iostream.h>  
 
class CZwierzak  
{  
public:  
  void Jedz();  
  virtual void Oddychaj() {cout << "\nSapie...";} 
};  
 
class CPiesek : public CZwierzak  
{  
  char imie[30];  
} Ciapek; 
 
class CRybka 
  char imie[30];  
public:  
  void Oddychaj() {cout << "\nSardynka: A ja nie oddycham.";} 
} Sardynka;  
 
void main()  
{  
  Ciapek.Oddychaj();  
  Sardynka.Oddychaj(); 
}  
 
Funkcja CZwierzak::Oddychaj() zosta�a w obiekcie Sardynka  
przes�oni�ta przez funkcj� CRybka::Oddychaj() - nowsz� wersj�  
funkcji-metody pochodz�c� z klasy pochodnej.   
 
Overloading funkcji zasadza� si� na "typologicznym pedanty�mie"  
C++ i na dodatkowych informacjach, kt�re C++ do��cza przy  
kompilacji do funkcji, a kt�re dotycz� licznby i typ�w  
argument�w danej wersji funkcji. W przypadku funkcji wirtualnych 
 
jest inaczej. Aby wykona� przes�anianie kolejnych wersji funkcji 
 
wirtualnej w taki spos�b, funkcja we wszystkich "pokoleniach"  
musi mie� taki sam prototyp, tj. pobiera� tak� sam� liczb�  
parametr�w tych samych typ�w oraz zwraca� warto�� tego samego  
typu. Je�li tak si� nie stanie, C++ potraktuje r�ne prototypy  
tej samej funkcji w kolejnych pokoleniach zgodnie z zasadami  
overloadingu funkcji. Zwr��my tu uwag�, �e w przypadku funkcji  
wirtualnych o wyborze wersji funkcji decyduje to, wobec kt�rego  
obiektu (kt�rej klasy) funkcja zosta�a wywo�ana. Je�li wywo�amy  
funkcj� dla obiektu Ciapek, C++ wybierze wersj�  
CZwierzak::Oddychaj(), natomiast wobec obiektu Sardynka zostanie 
 
zastosowana wersja CRybka::Oddychaj().  
 
W C++ wska�nik do klasy bazowej mo�e tak�e wskazywa� na klasy  
pochodne, wi�c zastosowanie funkcji wirtualnych mo�e da� pewne  
ciekawe efekty "uboczne". Je�li zadeklarujemy wska�nik *p do  
obiekt�w klasy bazowej CZwierzak *p; a nast�pnie zastosujemy ten 
 
sam wska�nik do wskazania na obiekt klasy pochodnej: 
 
p = &Ciapek;     p->Oddychaj();  
 ...  
p = &Sardynka;   p->Oddychaj(); 
 
zarz�damy w taki spos�b od C++ rozpoznania w�a�ciwej wersji  
wirtualnej metody Oddychaj() i jej wywo�ania we w�a�ciwym  
momencie. C++ mo�e rozpozna�, kt�r� wersj� funkcji nale�a�oby  
zastosowa� tylko na podstawie typu obiektu, wobec kt�rego  
funkcja zosta�a wywo�ana. I tu pojawia si� pewien problem.  
Kompilator wykonuj�c kompilcj� programu nie wie, co b�dzie  
wskazywa� pointer. Ustawienie pointera na konkretny adres  
nast�pi dopiero w czasie wykonania programu (run-time).  
Kompilator "wie" zatem tylko tyle:  
 
p->Oddychaj()();    //kt�ra wersja Oddychaj() ???  
 
Aby mie� pewno��, co w tym momencie b�dzie wskazywa� pointer,  
kompilator musia�by wiedzie� w jaki spos�b b�dzie przebiega�  
wykonanie programu. Takie wyra�enie mo�e zosta� wykonane "w  
ruchu programu" dwojako: raz, gdy pointer b�dzie wskazywa�  
Ciapka (inaczej), a drugi raz - Sardynk� (inaczej):  
 
  CZwierzak *p;  
 ... 
  for(p = &Ciapek, int i = 0; i < 2; i++)  
     {  
       p->Oddychaj();  
       p = &Sardynka;  
     }  
 
lub inaczej:  
 
  if(p == &Ciapek) CZwierzak::Oddychaj();  
  else CRybka::Oddychaj(); 
 
Taki efekt nazywa si� polimorfizmem uruchomieniowym (ang.  
run-time polymorphism).  
 
Overloading funkcji i operator�w daje efekt tzw. polimorfizmu  
kompilacji (ang. compile-time), to funkcje wirtualne daj� efekt  
polimorfizmu uruchomieniowego (run-time). Poniewa� wszystkie  
wersje funkcji wirtualnej maj� taki sam prototyp, nie ma innej  
metody stwierdzenia, kt�r� wersj� funkcji nale�y zastosowa�.  
Wyb�r w�a�ciwej wersji funkcji mo�e by� dokonany tylko na  
podstawie typu obiektu, do kt�rego nale�y wersja funkcji-metody. 
 
R�nica pomi�dzy polimorfizmem przejawiaj�cym si� na etapie  
kompilacji i poliformizmem przejawiaj�cym si� na etapie  
uruchomienia programu jest nazywana r�wnie� wszesnym albo p�nym 
 
polimorfizmem (ang. early/late binding). W przy...
Zgłoś jeśli naruszono regulamin