background image

 

 

Funkcje wirtualne i polimorfizm

Wykład 7

background image

 

 

Mechanizm omawiany w ramach tematu funkcje 
wirtualne decyduje o jednej z przewag programowania 
obiektowego nad strukturalnym. Rozważmy dziedziczenie 
w ramach klas (Grębosz ale wczesniej Eckel w Thinking 
in Java):

Instrument: trąbka, bęben, fortepian

Przykład: plik nagłówkowy zawierający klasę z 

wirtualną funkcją składową.

 
#include <iostream.h>

class instrument {

public:

void 

virtual

 wydaj_dzwiek()

{
 cout<<”nieokreslony pisk!\n”;
}
};
//

background image

 

 

class trabka: public instrument {

public:

void wydaj_dzwiek()

{

cout<<”tra-ta-ta-ta\n”;

}

};

class beben: public instrument {

public:

void wydaj_dzwiek()

{

cout<<”bum-bum-bum\n”;

}

};

class fortepian: public instrument {

public:

void wydaj_dzwiek()

{

cout<<”plim-plim-plim\n”;

}

};

void muzyk(instrument &instrument);

background image

 

 

main()
{
instrument jakis_instrument;
trabka zlota_trabka;
fortepian steinway;
beben beben_dobosza;
 

cout<<“wywolanie funkcji skladowych na rzecz 

obiektow\n“;
 
}
jakis_instrument.wydaj_dzwiek();
zlota_trabka.wydaj_dzwiek();
steinway.wydaj_dzwiek();
beben_dobosza.wydaj_dzwiek();
 ///// powinien zadzialac mechanizm przeslaniania

cout<<”wywolanie funkcji na rzecz obiektu\n 

pokazanego wskaznikiem instrumentu\n”;
instrument *wskinstr; 

//deklaracja wskaznika

 

background image

 

 

//ustawianie wskaznika
 
wskinstr=&jakis_instrument;

//przypisanie pod wskaźnik 

referencji na obiekt 
wskinstr-> wydaj_dzwiek(); // wywołanie funkcji na rzecz zawartosci 
wyłuskiwanej spod wskaźnika
 
cout<<”okazuje sie ze możemy pokazac także na obiekty klasy 
pochodnej”;
 
wskinstr=& zlota_trabka;
wskinstr-> wydaj_dzwiek();
 
wskinstr=& steinway;
wskinstr-> wydaj_dzwiek();
 
wskinstr=& beben_dobosza;
wskinstr-> wydaj_dzwiek();
 

background image

 

 

cout<<”albo na referencje do funkcji”;
 
 
muzyk(jakis_instrument);

//obiekt jest tu abstarkcyjna zmienna

muzyk(zlota_trabka);
muzyk(steinway);
muzyk(beben_dobosza);
}
/////
void muzyk(instrument &pysk);
{
pysk.wydaj_dźwięk();
}

background image

 

 

Po uruchomieniu programu trzymamy na ekranie:
 
wywolanie funkcji skladowych na rzecz obiektow
 
nieokreslony pisk!
tra-ta-ta-ta
bum-bum-bum
plim-plim-plim
 
wywolanie funkcji na rzecz obiektu
pokazanego wskaznikiem instrumentu
 
nieokreslony pisk!
 

background image

 

 

okazuje sie ze możemy pokazac także na obiekty klasy 
pochodnej
 
tra-ta-ta-ta
bum-bum-bum
plim-plim-plim
 
albo na referencje do funkcji
nieokreslony pisk!
tra-ta-ta-ta
bum-bum-bum
plim-plim-plim
 

background image

 

 

Gdyby  jednak  usunąć  słowo  virtual  przy  funkcji 
wydaj_dzwiek w klasie podstawowej, to na ekranie pojawi 
się następujący wynik:
 
wywolanie funkcji skladowych na rzecz obiektow
 
nieokreslony pisk!
tra-ta-ta-ta
bum-bum-bum
plim-plim-plim
 
wywolanie funkcji na rzecz obiektu
pokazanego wskaznikiem instrumentu
 
nieokreslony pisk!
 

background image

 

 

okazuje sie ze możemy pokazac także na obiekty klasy 
pochodnej
 
nieokreslony pisk!
nieokreslony pisk!
nieokreslony pisk!
 
albo na referencje do funkcji
nieokreslony pisk!
nieokreslony pisk!
nieokreslony pisk!
nieokreslony pisk!

background image

 

 

Czyli  po  wywołaniu  funkcji  wydaj_dźwięk  na  rzecz 
obiektów  z  poszczególnych  klas  wykonała  się  po  prostu 
funkcja z każdej z tych klas zgodnie z zaleceniem:
 

obiekt.wydaj_dźwięk();

 Tu działał ukryty wskaźnik this oraz mechanizm 
przesłaniania.
Dalej  wprowadziliśmy  definiowany  wskaźnik,  który 
pokazywał  na  obiekty  klasy  instrument.  Przy  tym 
pokazuje  na  jakis_instrument  czyli  dowolny  obiekt  klasy 
instrument. 
Następnie  kierujemy  wskaźnik  na  funkcję,  co  powoduje, 
ze  jest  ona  wykonana  na  rzecz  wskazanego  wcześniej 
obiektu:
 

wskaźnik->wydaj_dźwięk();

 
Potem ustawiliśmy wskaźnik na obiekty klas pochodnych. 
Mogliśmy  to  zrobić  bo  przy  dziedziczeniu  następuje 
konwersja  typów  obiektu
  i  wskaźnikiem  do  obiektu 
klasy  podstawowej  możemy  pokazać  na  obiekt  klasy 
pochodnej.

background image

 

 

Wprawdzie typ wskaźnika jest przy dziedziczeniu ogólnie 
różny  od  typu  obiektu  ale  konwersja  działa  w  ramach 
mechanizmu  dziedziczenia.  Dlaczego  jednak  kompilator 
wybiera  właściwą  obiektowi  funkcję  mimo  takiej  samej 
nazwy funkcji? Sprawcą takiego zachowania kompilatora 
jest  słowo  virtual  przy  funkcji  składowej  klasy 
podstawowej. 

To 

ono 

sprawia, 

że 

konwersja 

przekierowuje  kompilator  inteligentnie  także  do  funkcji 
dla obiektu pokazanego wskaźnikiem.
Gdy  słowo  virtual  zostało  usunięte  to  mechanizm 
prawidłowego  wykonania  funkcji  przypisanej  obiektowi 
nie  zadziałał  i  wykonywała  się  funkcja  tylko  z  klasy 
podstawowej.

background image

 

 

Kompilatory języków niezorientowanych 
obiektowo używają tzw. wczesnego wiązania 
funkcji. Kompilator generuje wywołanie funkcji a 
linker zamienia to wywołanie na bezwzględny 
adres kodu, który ma być wykonany.
Kompilatory w językach obiektowych stosują tzw. 
późne wiązanie. Kod przy takim wiązaniu jest 
wywoływany dopiero podczas wykonywania. 
Kompilator tylko sprawdza poprawność i 
obecność poszczególnych składników  w 
wiązaniu. W języku C++ takie wywołanie 
powoduje słowo kluczowe virtual.

background image

 

 

Polimorfizm

 

Dzięki terminowi virtual fragment kodu funkcji muzyk 
podany w formie
 

&wydaj_dźwięk();

 

wykonuje się w formie stosownej do zakresu klasy, z 
której wskazujemy adresem obiekt:
 
&instrument::wydaj_dźwięk()
&trabka::wydaj_dźwięk()
&fortepian::wydaj_dźwięk()
 
zależnie od sytuacji. Czyli funkcja muzyk wykonała się 
różnie mimo tej samej formy. To się nazywa 
polimorfizmem, co oznacza wielość form. Zastosowanie 
funkcji wirtualnej pozwoliło na uzyskanie wielości form. 

background image

 

 

Dodatkową cechą klasy zawierającej składową funkcję 

wirtualną  jest  to,  że 

zadziała  uniwersalnie  dla  każdej 

klasy pochodnej

 wywołującej funkcje wydaj_dźwięk(): 

#include”instrum.h” // nasze defincje do klasy 

instrument zawrzemy w pliku head

/////
class sluchacz:public instrument{
public:

void wydaj_dzwiek();

{

cout<<”jazz-jazz”;

}
////
main()
{
sluchacz bzzzzz;
muzyk(bzzzzz);
}
 

background image

 

 

to na ekranie otrzymamy:
 

jazz-jazz

Dlaczego?  Dlatego,  że  instrukcja  z  funkcji  muzyk  ma 
teraz formę:
 

instrument.sluchacz::wydaj_dźwięk();

 

Nietrudno zauważyć, że daje to zupełnie nowe możliwości 
modyfikacji działania programu w ramach polimorfizmu.
Dlaczego w takim razie nie uznać wszystkich funkcji jako 
wirtualnych  w  trybie  domyślnym?  Głównie  dlatego,  że 
funkcje  wirtualne  zabierają  znacznie  więcej  miejsca  w 
pamięci  niż  zwykłe  funkcje  składowe  i  ich  uruchamianie 
trwa znacząco dłużej.

background image

 

 

Należy pamiętać, że:

•wirtualna  może  być  tylko  funkcja  składowa,  a  nie 
funkcja globalna;

•słowo  virtual  występuje  tylko  przy  deklaracji  funkcji  w 
klasie, a ciało funkcji już nie musi go zawierać;

•jeśli klasa pochodna nie zdefiniuje swojej wersji funkcji 
wirtualnej,  to  będzie  ona  wywoływana  z  klasy 
podstawowej w jej zakresie ważności;

•funkcja  wirtualna  nie  może  być  funkcją  typu  static  bo 
wtedy  nie  może  być  stosowana  wirtualnie  na  wielu 
obiektach a tylko na tym, na którym jest przypisana jako 
static;

•funkcja  wirtualna  może  być  funkcją  zaprzyjaźnioną  ale 
straci wówczas możliwość polimorficznego działania czyli 
możemy ja zaprzyjaźnić ale za ceną utraty polimorfizmu


Document Outline