Lab 7 12, Studia - Politechnika Opolska, Semestr 2, Informatyka


ĆWICZENIE 7/2012

1. Funkcje wirtualne - polimorfizm dynamiczny

Polimorfizm oznacza istnienie kilku wersji funkcji lub operatorów o tej samej nazwie.

Jeśli w klasie bazowej istnieje funkcja zadeklarowana ze specyfikatorem virtual, to każda funkcja tego samego typu (identyczne: nazwa, typ wyniku, liczba i typy argumentów) występująca w dowolnej klasie pochodnej w hierarchii dziedziczenia jest funkcją wirtualną czyli składowe funkcje wirtualne pozwalają na przedefiniowanie (overrriding) w każdej klasie pochodnej funkcji składowych zadeklarowanych w klasie bazowej.

Polimorfizm dynamiczny - odnosi się do przesłonięcia nazw funkcji - jest realizowany przy wykorzystaniu funkcji wirtualnych i polega na wywołaniu odpowiedniej wersji funkcji wirtualnej zgodnie z klasą obiektu, a nie wskaźnika, który na niego wskazuje.

Aby zachowanie obiektu było polimorficzne należy się do niego odnosić za pomocą wskaźnika albo referencji.

Dzięki polimorfizmowi programy stają się rozszerzalne (ang. extensibility) - modyfikacja kodu polega na dodaniu nowej klasy bez potrzeby zmian w kodzie istniejącym.

Decyzję o wyborze funkcji do wykonania podejmuje się w trakcie działania programu (jest to tak zwane późne wiązanie, w przeciwieństwie do zwykłych funkcji gdzie obowiązuje wczesne wiązanie).

W definicji metody wirtualnej poza klasą nie używa się deklaratora virtual.

Funkcja wirtualna musi być zdefiniowana dla klasy, w której po raz pierwszy została zadeklarowana.

Funkcji wirtualnej można używać nawet wtedy, gdy z jej klasy nie wyprowadzi się żadnej klasy pochodnej.

Klasa pochodna, która nie potrzebuje specjalnej wersji funkcji wirtualnej, nie musi jej dostarczać.

Funkcja w klasie pochodnej z tą samą nazwą i z tą samą listą argumentów co funkcja wirtualna w klasie podstawowej nadpisuje (ang. override) wersję funkcji wirtualnej z klasy bazowej.

Funkcja wirtualna w klasie nie może być statyczna.

Dostęp do funkcji wirtualnej może być zmieniony w klasach pochodnych (zależy to od sposobu dziedziczenia) - dostęp ten zależy więc tylko od typu wskaźnika albo referencji.

Funkcje wirtualne mogą być zaprzyjaźnione z innymi klasami.

Funkcja wirtualna może być wcześnie związana gdy będzie wywołana na rzecz konkretnego obiektu znanego z nazwy:
klasa ob;
// …
ob.funwirt();

Funkcja wirtualna będzie wcześnie związana gdy użyjemy kwalifikatora zakresu:
wsk->klasa::funwirt();
ref.klasa::funwirt();

Funkcja wirtualna będzie wcześnie związana gdy wywołamy ją w konstruktorze.

Funkcja wirtualna może być wbudowana.

Klasa z funkcjami wirtualnymi nazywa się klasą polimorficzną. Obiekty klas polimorficznych mają dodatkowe pole identyfikujące typ obiektu.

Każda klasa polimorficzna posiada swoje miejsce w tablicy metod wirtualnych. Polimorfizm jest więc kosztowny (miejsce i czas) - dlatego nie wszystkie metody są wirtualne.

W klasach polimorficznych destruktor definiuje się jako wirtualny.

Gdy funkcja wirtualna jest wywoływana w destruktorze lub w konstruktorze, to mechanizm polimorficzny nie działa.

Program 7.1 ilustruje wykorzystanie funkcji wirtualnych.

//Program 7.1.

#include <iostream>

#include <string>

using namespace std;

// --------------------------------------klasa bazowa określająca punkt na ekranie

class Punkt

{

protected:

int x, y;

public:

Punkt(int =15, int =15); //konstr. o argumentach domyślnych

virtual void wypisz();

//deklaracja funkcji wirtualnej wypisz()

};

//-------------------------------------

Punkt::Punkt(int x0, int y0)

{ cout<<"\n___konstruktor Punkt"<<endl;

x=x0;

y=y0;

}

void Punkt::wypisz()

{

cout<<"Jestem punktem!!!"<<endl;

cout<<"<x="<<x<<", y="<<y<<">"<<endl;

}

// -------------------------klasa definiująca prostokąt dziedzicząca z Punkt

class Prost : public Punkt

{

protected:

int dx, dy; //długości boków prostokąta

public:

Prost(int =10, int =10, int =100, int =100);

void wypisz();

};

//-------------------------------------

Prost::Prost(int x1, int y1, int x2, int y2) : Punkt(x1,y1), dx(x2), dy(y2)

{

cout<<"\n____konstruktor Prost"<<endl;

}

void Prost::wypisz()

{

cout<<"Jestem prostokatem!!!"<<endl;

cout<<"<x="<<x<<", y="<<y<<"> Mam boki: "<<dx<<" i "<<dy<<endl;

}

// -----------------------------klasa definiująca ramkę dziedzicząca z Prost

class Ramka: public Prost

{

protected:

string kol; // kolor obramowania

int g; //grubość ramki

public:

Ramka(int, int, int, int, int, string );

void wypisz();

};

Ramka::Ramka(int x1, int y1, int x2, int y2, int g1, string c) : Prost(x1,y1,x2,y2), g(g1), kol(c)

{

cout<<"\n___konstr. Ramka"<<endl;

}

void Ramka::wypisz()

{cout<<"Jestem ramka!!!"<<endl;

cout<<"<x="<<x<<", y="<<y<<"> Mam boki: "<<dx<<" i "<<dy<<endl;

cout<<" oraz kolor "<< kol<<" i grubosc: "<< g<<endl;

}

int main()

{

Punkt P(30,30), P1;

P.wypisz();

P1.wypisz();

Prost K(12,12,20,5), K1;

K.wypisz();

K1.wypisz();

Ramka R(10,15,5,4,1, "zielony");

R.wypisz();

cout<<"\nWskaznik z klasy Punkt"<<endl;

Punkt *wskP; // deklaracja wskaźnika z klasy Punkt

cout<<"\nPodstawienie punktu"<<endl;

wskP=&P; //przypisanie wskaźnikowi adresu obiektu

wskP ->wypisz(); //aktywowanie funkcji na rzecz wskaźnika

cout<<"\nPodstawienie prostokata"<<endl;;

wskP =&K;

wskP ->wypisz();

cout<<"\nPodstawienie ramki"<<endl;;

wskP =&R;

wskP ->wypisz();

system("PAUSE");

return 0;

}

Zadanie do samodzielnego wykonania

1. - NA ZALICZENIE -Napisać program na temat Budowla wykorzystujący funkcje wirtualne.

2. Klasy abstrakcyjne, czyste funkcje wirtualne

W hierarchii dziedziczenia jako klasę bazową można umieścić klasę abstrakcyjną, tzn. taką której obiektów nie będzie się tworzyć. Służy ona do zdefiniowania wspólnych cech obiektów klas potomnych oraz służą do definiowania interfejsów.

Klasa abstrakcyjna musi zawierać przynajmniej jedną czystą funkcje wirtualną (pure virtual).

Czysta funkcja wirtualna ma postać: virtual typ_funkcji nazwa_funkcji(argumenty) = 0; czyli nie posiada żadnej treści.

Czyste funkcje wirtualne odziedziczone z klasy abstrakcyjnej muszą być w klasach pochodnych zdefiniowane lub ponownie zadeklarowane jako czyste.

W klasach potomnych, które nie mają być klasami abstrakcyjnymi, należy zdefiniować wszystkie odziedziczone metody abstrakcyjne.

Nie wszystkie metody w klasie abstrakcyjnej muszą być abstrakcyjne.

Żaden konstruktor ani destruktor nie może być abstrakcyjny.

Nie można utworzyć obiektu klasy abstrakcyjnej.

Nie wolno zdefiniować funkcji, która odbierałaby argument takiej klasy przez wartość.

Nie wolno zdefiniować funkcji, która zwracałaby wynik takiej klasy przez wartość.

Klasa abstrakcyjna nie może być typem w jawnej konwersji.

//Program 7.2

#include <iostream>

using namespace std;

class A

{

public:

int x;

void virtual wypisz() = 0;

};

class B1: public A

{

public:

void wypisz()

{ cout<<"Jestem z B1\n";}

};

class B2: public B1

{

public:

void wypisz()

{cout<<"Jestem z B2\n";}

};

int main()

{

//A a; //niemożliwe

B1 b1;

B2 b2;

A * wsk;

wsk=&b1;

wsk->wypisz();

cout<<"wartość x: "<<wsk->x<<endl;

wsk->x=5;

cout<<"wartość x: "<<wsk->x<<endl;

wsk=&b2;

wsk->wypisz();

cout<<"wartość x: "<<wsk->x<<endl;

wsk->x=7;

cout<<"wartość x: "<<wsk->x<<endl;

system("PAUSE");

return 0;

}

Zadanie do samodzielnego wykonania - NA ZALICZENIE

1. Zdefiniować abstrakcyjną klasę Figura zawierającą czystą funkcje wirtualną pole_powierzchni().

2. Z klasy Figura wyprowadzić sekwencyjnie dziedziczące klasy potomne Prostokat i Ostroslup i zawierające odpowiednie definicje funkcji pole_powierzchni() zwracające pole powierzchni odpowiednio prostokąta i ostrosłupa

3. W programie utworzyć obiekty w/w klas i użyć dla nich funkcje obliczające ich pola.

4. W programie zdefiniować wskaźnik do klasy Figura, podstawiając do niego adresy utworzonych obiektów obu klas potomnych, zilustrować działanie funkcji pole_powierzchni().

INFORMATYKA II - laboratorium

I rok INFORMATYKA

STUDIA STACJONARNE I STOPNIA

Rok akademicki 2011/2012 semestr letni

3



Wyszukiwarka