LEKCJA36.TXT

(10 KB) Pobierz
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 <...
Zgłoś jeśli naruszono regulamin