LEKCJA 36: FUNKCJE WIRTUALNE i KLASY ABSTRAKCYJNE. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, co mawia �ona programisty, gdy nie chce by� obiektem klasy abstrakcyjnej. ________________________________________________________________ FUNKCJE W PE�NI WIRTUALNE (PURE VIRTUAL). W skrajnych przypadkach wolno nam umie�ci� funkcj� wirtualn� w klasie bazowej nie definiuj�c jej wcale. W klasie bazowej umieszczamy wtedy tylko deklaracj�-prototyp funkcji. W nast�pnych pokoleniach klas pochodnych mamy wtedy pe�n� swobod� i mo�emy zdefiniowa� funkcj� wirtualn� w dowolny spos�b - adekwatny dla potrzeb danej klasy pochodnej. Mo�emy np. do klasy bazowej (ang. generic class) doda� prototyp funkcji wirtualnej funkcja_eksperymentalna() nie definiuj�c jej w (ani wobec) klasie bazowej. Sens umieszczenia takiej funkcji w klasie bazowej polege na uzyskaniu pewno�ci, i� wszystkie klasy pochodne odziedzicz� funkcj� funkcja_eksperymentalna(), ale ka�da z klas pochodnych wyposa�y t� funkcj� we w�asn� definicj�. Takie post�powanie mo�e okaza� si� szczeg�lnie uzasadnione przy tworzeniu biblioteki klas (class library) przeznaczonej dla innych u�ytkownik�w. C++ w wersji instalacyjnej posiada ju� kilka gotowych bibliotek klas. Funkcje wirtuale, kt�re nie zostaj� zdefiniowane - nie posiadaj� zatem cia�a funkcji - nazywane s� funkcjami w pe�ni wirtualnymi (ang. pure virtual function). O KLASACH ABSTRAKCYJNYCH. Je�li zadeklarujemy funkcj� CZwierzak::Oddychaj() jako funkcj� w pe�ni wirtualn�, opr�cz s�owa kluczowego virtual, trzeba t� informacj� w jaki� spos�b przekaza� kompilatorowi C++. Aby C++ wiedzia�, �e nasz� intencj� jest funkcja w pe�ni wirtalna, nie mo�emy zadeklarowa� jej tak: class CZwierzak { ... public: virtual void Oddychaj(); ... }; a nast�pnie pomin�� definicj� (cia�o) funkcji. Takie post�powanie C++ uzna�by za b��d, a funkcj� - za zwyk�� funkcj� wirtualn�, tyle, �e "niedorobion�" przez programist�. Nasz� intencj� musimy zaznaczy� ju� w definicji klasy w taki spos�b: class CZwierzak { ... public: virtual void Oddychaj() = 0; ... }; Informacj� dla kompilatora, �e chodzi nam o funkcj� w pe�ni wirtualn�, jest dodanie po prototypie funkcji "= 0". Definiuj�c klas� pochodn� mo�emy rozbudowa� funkcj� wirtualn� np.: class CZwierzak { ... public: virtual void Oddychaj() = 0; ... }; class CPiesek : public CZwierzak { ... public: void Oddychaj() { cout << "Oddycham..."; } ... }; Przyk�adem takiej funkcji jest funkcja M�w() z przedstawionego poni�ej programu. Zostawiamy j� w pe�ni wirtualn�, poniewa� r�ne obiekty klasy CZLOWIEK i klas pochodnych class CZLOWIEK { public: void Jedz(void); virtual void Mow(void) = 0; //funkcja WIRTUALNA }; class NIEMOWLE : public CZLOWIEK { public: void Mow(void); // Tym razem BEZ slowa virtual }; /* Tu definiujemy metod� wirtualn�: -------------------- */ void NIEMOWLE::Mow(void) { cout << "Nie Umiem Mowic! \n"; }; mog� m�wi� na r�ne sposoby... Obiekt Niemowle, dla przyk�adu, nie chce m�wi� wcale, ale z innymi obiektami mo�e by� inaczej. Wyobra� sobie np. obiekt klasy �ona (�ona to przecie� te� cz�owiek !). class Zona : public CZLOWIEK { public: void Mow(void); } W tym pokoleniu definicja wirtualnej metody Mow() mog�aby wygl�da� np. tak: void Zona::Mow(void) { cout << "JA NIE MAM CO NA SIEBIE WLOZYC !!! "; cout << "DLACZEGO KOWALSKI ZARABIA ZAWSZE WIECEJ NIZ TY ?!!!"; //... itd., itd., itd... } [P128.CPP] #include "iostream.h" class CZLOWIEK { public: void Jedz(void); virtual void Mow(void) = 0; }; void CZLOWIEK::Jedz(void) { cout << "MNIAM, MNIAM..."; }; class Zona : public CZLOWIEK { public: void Mow(void); //Zona mowi swoje }; //bez wzgledu na argumenty (typ void) void Zona::Mow(void) { cout << "JA NIE MAM CO NA SIEBIE WLOZYC !!!"; cout << "DLACZEGO KOWALSKI ZARABIA ZAWSZE WIECEJ NIZ TY ?!!!"; } class NIEMOWLE : public CZLOWIEK { public: void Mow(void); }; void NIEMOWLE::Mow(void) { cout << "Nie Umiem Mowic! \n"; }; main() { NIEMOWLE Dziecko; Zona Moja_Zona; Dziecko.Jedz(); Dziecko.Mow(); Moja_Zona.Mow() return 0; } Przyk�adowa klasa CZ�OWIEK jest klas� ABSTRAKCYJN�. Je�li spr�bujesz doda� do powy�szego programu np.: CZLOWIEK Facet; Facet.Jedz(); uzyskasz komunikat o b��dzie: Cannot create a variable for abstract class "CZLOWIEK" (Nie mog� utworzy� zmiennych dla klasy abstrakcyjnej "CZLOWIEK" [???] KLASY ABSTRAKCYJNE. ________________________________________________________________ * Po klasach abstrakcyjnych MO�NA dziedziczy�! * Obiekt�w klas abstrakcyjnych NIE MO�NA stosowa� bezpo�rednio! ________________________________________________________________ Poniewa� wyja�nili�my, dlaczego klasy s� nowymi typami danych, wi�c logika (i sens) innej rozpowszechnionej nazwy klas abstrakcyjnych - ADT - Abstract Data Type (Abstrakcyjne Typy Danych) jest chyba zrozumia�a i oczywista. ZAGNIE�D�ANIE KLAS I OBIEKT�W. Mo�e si� np. zdarzy�, �e klasa stanie si� wewn�trznym elementem (ang. member) innej klasy i odpowiednio - obiekt - elementem innego obiektu. Nazywa si� to fachowo "zagnie�d�aniem" (ang. nesting). Je�li, dla przyk�adu klasa CB b�dzie zawiera� obiekt klasy CA: class CA { int liczba; public: CA() { liczba = 0; } //Konstruktor domyslny CA(int x) { liczba = x; } void operator=(int n) { liczba = n } }; class CB { CA obiekt; public: CB() { obiekt = 1; } }; Nasze klasy wyposa�yli�my w konstruktory i od razu poddali�my overloadingowi operator przypisania = . Aby prze�ledzi� kolejno�� wywo�ywania funkcji i spos�b przekazywania parametr�w pomi�dzy tak powi�zanymi obiektami rozbudujemy ka�d� funkcj� o zg�oszenie na ekranie. class CA { int liczba; public: CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; } CA(int x) { liczba = x; cout << "->CA(int) "; } void operator=(int n) { liczba = n; cout << "->operator "; } }; class CB { CA obiekt; public: CB() { obiekt = 1; cout << "->Konstruktor CB() "; } }; Mo�emy teraz sprawdzi�, co stanie si� w programie po zadeklarowaniu obiektu klasy CB: [P129.CPP] # include "iostream.h" class CA { int liczba; public: CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; } CA(int x) { liczba = x; cout << "->CA(int) "; } void operator=(int n) { liczba = n; cout << "->operator "; } }; class CB { CA obiekt; public: CB() { obiekt = 1; cout << "->Konstruktor CB() "; } }; main() { CB Obiekt; return 0; } Po uruchomieniu programu mo�esz przekona� si�, �e kolejno�� dzia�a� b�dzie nast�puj�ca: C:\>program -> CA(), CA_O::liczba = 0 ->operator ->Konstruktor CB() Skoro opr�cz zainicjowania obiektu klasy pochodnej nie robimy w programie dok�adnie nic, nie dziwmy si� ostrze�eniu Warning: Obiekt is never used... Jest to sytuacja troch� podobna do komunikacji pomi�dzy konstruktorami klas bazowych i pochodnych. Je�li zaprojektujemy prost� struktur� klas: class CBazowa { private: int liczba; public: CBazowa() { liczba = 0} CBazowa(int n) { liczba = n; } }; class CPochodna : public CBazowa { public: CPochodna() { liczba = 0; } CPochodna(int x) { liczba = x; } }; problem przekazywania parametr�w mi�dzy konstruktorami klas mo�emy w C++ rozstrzygn�� i tak: class CPochodna : public CBazowa { public: CPochodna() : CBazowa(0) { liczba = 0; } CPochodna(int x) { liczba = x; } }; B�dzie to w praktyce oznacza� wywo�anie konstruktora klasy bazowej z przekazanym mu argumentem 0. Podobnie mo�emy post�pi� w stosunku do klas zagnie�d�onych: [P130.CPP] #include "iostream.h" class CA { int liczba; public: CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; } CA(int x) { liczba = x; cout << "->CA(int) "; } void operator=(int n) { liczba = n; cout << "->operator "; } }; class CB { CA obiekt; public: CB() : CA(1) {} }; main() { CB Obiekt; return 0; } Eksperymentuj�c z dwoma powy�szymi programami mo�esz przekona� si�, jak przebiega przekazywanie parametr�w pomi�dzy konstruktorami i obiektami klas bazowych i pochodnych. JESZCZE RAZ O WSKA�NIKU *this. Szczeg�lnie wa�nym wska�nikiem przy tworzeniu klas pochodnych i funkcji operatorowych mo�e okaza� si� pointer *this. Oto przyk�ad listy. [P131.CPP] # include "string.h" # include "iostream.h" class CLista { private: char *poz_listy; CLista *poprzednia; public: CLista(char*); CLista* Poprzednia() { return (poprzednia); }; void Pokazuj() { cout << '\n' << poz_listy; } void Dodaj(CLista&); ~CLista() { delete poz_listy; } }; CLista::CLista(char *s) { poz_listy = new char[strlen(s)+1]; strcpy(poz_listy, s); poprzednia = NULL; } void CLista::Dodaj(CLista& obiekt) { obiekt.poprzednia = this; } main() { CLista *ostatni = NULL; cout <...
mikomil