Wykład 7
Dziedziczenie (3)
Funkcje wirtualne, klasy wirtualne

Funkcje wirtualne

#include 

class A
{
public:
  void Wypisz(){printf("Wypisz A\n");};
  void virtual WypiszV(){printf("Wypisz virtual A\n");};
};

class B1: public A
{
public:
  void Wypisz(){printf("Wypisz B1\n");};
  void WypiszV(){printf("Wypisz virtual B1\n");};
};

class B2: public A
{
public:
  void Wypisz(){printf("Wypisz B2\n");};
  void WypiszV(){printf("Wypisz virtual B2\n");};
};

void aktywuj(A & w)
{
  w.Wypisz();
  w.WypiszV();
}

int main()
{
  A a;
  B1 b1;
  B2 b2;
  A * wsk;
  
  wsk =& a;
  wsk->Wypisz();
  wsk->WypiszV();
  printf("--------\n");
  
  wsk =& b1;
  wsk->Wypisz();
  wsk->WypiszV();
  printf("--------\n");
  
  wsk =& b2;
  wsk->Wypisz();
  wsk->WypiszV();
  printf("--------\n");
  
  aktywuj(a);
  aktywuj(b1);
  aktywuj(b2);
  
  return 0;
}
Listing 7.1
Wypisz A
Wypisz virtual A
--------
Wypisz A
Wypisz virtual B1
--------
Wypisz A
Wypisz virtual B2
--------
Wypisz A
Wypisz virtual A
Wypisz A
Wypisz virtual B1
Wypisz A
Wypisz virtual B2
Output 7.1 Efekt działania programu z listingu 7.1
Jak widać, gdy nie ma słówka virtual przy deklaracji funkcji, wówczas zawsze wywoływana jest funkcja z klasy podstawowej. Gdy virtual wystapi, mowic bedziemy o poliformizmie, to znaczy o wielopostaciowości kodu (polimorfizm wywołania). W zależności od tego na co wskazuje wskaźnik wywoływana jest funkcja z odpowiedniej klasy. Kod pozostaje bez zmian. Funkcję składową nazwiemy wirtualną, gdy w definicji klasy przy jej deklaracji stoi słowo virtual, lub gdy w jednej z klas podstawowych identyczna funkcja zadeklarowana jest jako virtual. Słowo virtual pojawia się tylko raz przy deklaracji funkcji wewnątrz ciała klasy. Przy ewentualnej definicji funkcji poza ciałem klasy jest ono opuszczane.

Klasy abstrakcyjne

Klasy abstrakcyjne to klasy reprezentujące nieistniejące obiekty. Bezsensu! Jeśli coś nieistnieje to przecież nie ma czego reprezentować! No nie do końca... Powróćmy znowu do przykładu z pojazdem. Co to jest pojazd? Czy ktoś "to" widział. Jak wygląda pojazd? Jeśli ktoś powie, że pojazd wygląda jak rower to nie będzie miał racji bo rower wygląda jak... rower. Ma on cechy pojazdu, ale mimo wszystko to rower. Podobnie z Syrenką. To też pojazd, ale przecież każdy pojazd to nie syrenka. Zatem pojazd reprezentuje pewnien nieistniejacy obiekt. Skupia niejako w sobie wspólne cechy wszystkich pojazdów: prędkość, położenie, masę itp. nie jest jednka żadnym konkretnym. Klasy abstrakcyjne wprowadzamy po to aby zebrać w jakąś całość zespół cech i funkcji wspólnych dla pewnych obiektów. Sami nie możemy jednak stworzyć takiego obiektu bo on nie istnieje. Możemy za to tworzyć obiekty potomne, które będą posiadały pewną ilość wspólnych cech. Spójrzmy zresztą na poniższy program

#include 

class A
{
public:
  int x;
  void virtual Wypisz() = 0;
};

class B1: public A
{
public:
  void Wypisz(){printf("Wypisz B1\n");};
};

class B2: public A
{
public:
  void Wypisz(){printf("Wypisz B2\n");};
};

int main()
{
  // jesli ponizsza linia nie ma komentarza
  //A a;
  // to pojawia sie nastepujacy komunikat przy
  // kompilacji
  //cannot declare variable `a' to be of type `A'
  //because the following virtual functions are abstract:

  B1 b1;
  B2 b2;
  A * wsk;
  
  wsk=&b1;
  wsk->Wypisz();
  printf("%d\n",wsk->x);
  wsk->x=5;
  printf("%d\n",wsk->x);
  
  wsk=&b2;
  wsk->Wypisz();
  printf("%d\n",wsk->x);
  wsk->x=7;
  printf("%d\n",wsk->x);
  
  return 0;
}
Listing 7.2
Wypisz B1
2013313654
5
Wypisz B2
-2120598408
7
Output 7.2 Efekt działania programu z listingu 7.2
Jak widać klasy B1 i B2 mają cechy wspólne (zmienna x) odziedziczone po klasie podstawowej A, ale nie udaje się zadeklarować zmiennej typu A. Osiągneliśmy to przez deklarowanie funkcji Wypisz() jako funkcji czysto wirtualnej czyli jako funkcji, która nigdy ma się nie wykonać. Funkcję tego typu zapisujemy w następujący sposób
zwracanyWynik virtual nazwaFunkcji(argumenty) = 0;
Listing 7.3