Transcript polimorfizm
KURS JĘZYKA C++
– WYKŁAD 6 (7.04.2014)
Polimorfizm
SPIS TREŚCI
Metody wirtualne
Implementacja polimorfizmu
Wczesne i późne wiązanie metod wirtualnych
Klasy abstrakcyjne
Klasa pair<>
Klasa vector<>
SKŁADOWE FUNKCJE WIRTUALNE
Składowe funkcje wirtualne pozwalają na przedefiniowanie w
każdej klasie pochodnej funkcji składowych zadeklarowanych
w klasie bazowej.
Poprzez funkcje wirtualne w programie zapewnione jest
wywołanie metody najlepiej odpowiadającej obiektowi.
Składowe funkcje wirtualne należy opatrzyć deklaratorem
virtual (wewnątrz klasy).
W definicji metody wirtualnej poza klasą nie używa się
deklaratora virtual.
SKŁADOWE FUNKCJE WIRTUALNE
Przykład deklaracji klas z metodami wirtualnymi i zwykłymi:
class bazowa
{
public:
void opis_zwykly ();
virtual void opis_wirtualny ();
};
class pochodna: public bazowa
{
public:
void opis_zwykly ();
virtual void opis_wirtualny ();
};
SKŁADOWE FUNKCJE WIRTUALNE
Przykład definicji metod wirtualnych i zwykłych:
void bazowa::opis_zwykly()
{ cout << "bazowa::opis_zwykly()" << endl; }
void bazowa::opis_wirtualny()
{ cout << "bazowa::opis_wirtualny()" << endl; }
void pochodna::opis_zwykly()
{ cout << "pochodna::opis_zwykly()" << endl; }
void pochodna::opis_wirtualny()
{ cout << "pochodna::opis_wirtualny()" << endl; }
SKŁADOWE FUNKCJE WIRTUALNE
Przykład użycia metod wirtualnych i zwykłych:
bazowa *a = new bazowa();
a->opis_zwykly();
a->opis_wirtualny();
// bazowa::opis_zwykly()
// bazowa::opis_wirtualny()
bazowa *b = new pochodna();
b->opis_zwykly();
b->opis_wirtualny();
// bazowa::opis_zwykly()
// pochodna::opis_wirtualny()
SKŁADOWE FUNKCJE WIRTUALNE
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.
POLIMORFIZM
Uzyskanie
zachowania się funkcji adekwatnego do typu
obiektu nazywa się polimorfizmem (ang. polymorphism).
Klasa z funkcjami wirtualnymi nazywa się klasą
polimorficzną.
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.
IMPLEMENTACJA ZACHOWAŃ POLIMORFICZNYCH
Obiekty
klas polimorficznych mają dodatkowe pole
identyfikujące typ obiektu.
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).
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.
REZULTAT FUNKCJI WIRTUALNEJ
Przy nadpisywaniu funkcji wirtualnej trzeba zachować
odpowiedni typ rezultatu:
albo rezultat musi być identyczny,
albo rezultat musi być kowariantny (referencja lub wskaźnik
do obiektu tej samej klasy lub do klasy, dla której jest ona
jednoznaczną i dostępną klasą podstawową).
Przykład:
owoc * bazowa::fun () {/*…*/}
pomelo * pochodna::fun () {/*…*/}
INNE CECHY FUNKCJI WIRTUALNYCH
Funkcja wirtualna w klasie nie może być statyczna.
Dostęp do funkcji wirtualnej może być zmieniony w
klasach pochodnych (co zależy od sposobu
dziedziczenia) – dostęp ten zależy więc tylko od typu
wskaźnika albo referencji.
Funkcje wirtualne mogą być przyjacielami w innych
klasach.
FUNKCJA WIRTUALNA
WCZEŚNIE ZWIĄZANA
Funkcja
wirtualna będzie 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.
KLASY ABSTRAKCYJNE
Klasy abstrakcyjne służą do definiowania interfejsów (pojęć
abstrakcyjnych).
Klasa abstrakcyjna zawiera co najmniej jedną abstrakcyjną metodą
wirtualną (funkcja czysto wirtualna).
Deklaracja metody czysto wirtualnej wygląda następująco:
virtual typ funkcja (lista-argumentów) = 0;
Nie trzeba (ale można) podawać definicji metody czysto wirtualnej.
W klasach potomnych, które nie mają być klasami abstrakcyjnymi,
należy zdefiniować wszystkie odziedziczone metody abstrakcyjne.
KLASY 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.
WIRTUALNY DESTRUKTOR
W klasach polimorficznych (zawierających metody
wirtualne) destruktor definiujemy jako wirtualny.
KONSTRUKTOR NIE MOŻE BYĆ WIRTUALNY ALE…
Czasami istnieje potrzeba wyprodukowania nowego
obiektu tej samej klasy – w takiej sytuacji można
zdefiniować funkcję wirtualną, która będzie
przygotowywać taki obiekt (zastąpi konstruktor
domyślny albo kopiujący).
KLASA PAIR<>
W pliku nagłówkowym <utility> zdefiniowano szablon
klasy pair<F,S>, który służy do przechowywania pary
wartości.
Para std::pair<> to struktura z dwoma polami first i
second dowolnych typów podawanych jako parametry
wzorca.
Przykład:
pair<string,double> pi =
new pair<string,double>(”pi”,3.141593);
W szablonie klasy std::pair<> zdefiniowano oprócz
konstruktora z dwiema wartościami inicjalizującymi pola
first i second także konstruktor kopiujący i domyślny.
KLASA PAIR<>
Typy występujące w parze mogą być wydedukowane, gdy para
jest tworzona za pomocą funkcji szablonowej make_pair().
Przykład:
pair<string,double> e =
make_pair(string(”e”),2.718282);
Dla szablonu klasy std::pair<> zdefiniowano nieskładowe
operatory relacji porównujących == i < (porównywane są
pierwsze pola, a gdy te są równe porównywane są drugie
pola).
Funkcja składowa oraz zewnętrzna funkcja szablonowa
swap<>() zamienia zawartości dwóch par.
KLASA VECTOR<>
W pliku nagłówkowym <vector> zdefiniowano szablon klasy
vector<T>, który służy do przechowywania dowolnej liczby
wartości określonego typu.
Kolekcja std::vector<> przechowuje swoje elementy w tablicy
dynamicznej – dodawanie (metoda push_back()) i usuwanie
(metoda pop_back()) elementów na końcu tablicy działa bardzo
szybko.
Przykład:
vector<int> coll;
for (int i=1; i<=10; ++i)
coll.push_back(i*i);
for (int i=0; i<coll.size(); ++i)
cout << coll[i] << ’ ’;
cout << endl;
KLASA VECTOR<>
W kolekcji std::vector<> istnieją metody do wstawiania
elementu na początku wektora push_front() i usuwania
elementu z początku pop_front().
Dostęp do elementów w wektorze jest realizowany za pomocą
operatora indeksowania [] albo za pomocą metody at();
odczytanie wartości pierwszego elementu w wektorze można zrobić
za pomocą metody front() a ostatniego za pomocą back().
Metoda empty() mówi czy wektor jest pusty a metoda size()
zwraca liczbę wszystkich elementów w wektorze.
Metoda clear() usuwa wszystkie elementy z wektora.