18
Dziedziczenie klas. Hierarchia klas.
PUNKT_2D
protected: int x, y;
void Przesuń(int, int);
OKRG PROSTOKT
int promień; int bok , bokB;
void Skaluj(float); int Pole(void);
float Pole(void); int Obwód(void);
float Obwód(void);
Rysunek przedstawia przykład prostej hierarchii klas z
dziedziczeniem, narysowanej z wykorzystaniem notacji UML.
" Dane składowe x i y zadeklarowano w klasie bazowej
PUNKT_2D jako chronione protected: dzięki czemu będą się
one dziedziczyć do klas pochodnych i będą tam dostępne.
" Klasa pochodna OKRG wykorzystuje odziedziczone z klasy
bazowej PUNKT_2D składowe x i y do zapamiętania
współrzędnych środka okręgu, oraz metodę Przesuń( ) do
przesuwania okręgu. Deklaruje też trzy własne metody.
" Klasa pochodna PROSTOKT wykorzystuje odziedziczone z
klasy bazowej PUNKT_2D składowe x i y do zapamiętania
położenia jednego z wierzchołków prostokąta, oraz
odziedziczoną metodę Przesuń( ) do przesuwania całego
19
prostokąta. by w pełni zdefiniować tę figurę, oprócz
położenia wybranego wierzchołka, potrzebne są jeszcze
długości jego boków, zadeklarowano więc dwie dane
składowe bokA i bokB. W klasie PROSTOKT zgłoszono też
dwie metody do obliczenia pola i obwodu prostokąta, które
nie mają jednak żadnych związków z metodami o takiej samej
nazwie, zadeklarowanymi w klasie OKRG.
Poniżej zamieszczono fragment kodu, implementującego
zamieszczoną na rysunku hierarchię klas:
class PUNKT_2D
{
protected:
Dziedziczenie
int x,y;
publiczne
public:
void Przesun(int, int);
};
class OKR G: public PUNKT_2D
{
int promien;
public:
void Skaluj(float k);
float Obwod( ); float Pole( );
};
class PROSTOK T: public PUNKT
{
protected:
int bok , bokB;
public:
int Obwod( ); int Pole( );
};
20
" W klasie PROSTOKT zdecydowano dodatkowo, że jej dane
składowe będą chronione w celu umożliwienia ich
dziedziczenia do klas pochodnych względem klasy
PROSTOKT.
" Zastosowano dziedziczenie publiczne.
Rodzaje dziedziczeń
o Klasa pochodna może dziedziczyć z klasy bazowej nie
tylko w sposób publiczny. Występuje jeszcze
dziedziczenie prywatne i chronione.
Poniższa tabela przedstawia dostępność składowych klas
bazowych w klasach pochodnych w zależności od wybranego
rodzaju dziedziczenia:
Rodzaj Składowe klasy w klasach
dziedziczenia bazowej ... pochodnych są ...
private niedostępne
public
protected protected
public public
protected private niedostępne
protected protected
public protected
private private niedostępne
protected prywatnymi klasy
pochodnej
public prywatnymi klasy
pochodnej
o Jak wynika z powyższej tabeli, dziedziczenie chronione
ogranicza składowe publiczne do chronionych, natomiast
dziedziczenie prywatne praktycznie uniemożliwia dalsze
stosowania dziedziczenia.
21
Jeśli żadne słowo kluczowe po nazwie klasy bazowej nie
wystąpiło przyjmuje się:
o domyślnie private dla klas zdefiniowanych z użyciem
słowa class,
o domyślnie public dla klas zdefiniowanych z użyciem
słów struct lub union.
Mówiąc o rodzajach dziedziczeń wyróżniamy też:
dziedziczenie wielopoziomowe (jak w hierarchii klas
omawianej powyżej),
dziedziczenie wielobazowe, gdzie klasa pochodna
może dziedziczyć z więcej niż jednej klasy bazowej.
W niektórych językach programowania, wspiera-
jących paradygmat obiektowości, dziedziczenie
wielobazowe jest zabronione.
Dziedziczenie wielopoziomowe z udziałem dziedziczenia
wielobazowego (krata)
Omówimy teraz bardziej złożony przykład. Celem tego
przykładu jest zaprezentowanie dziedziczenia i przesłanianie
składowych w warunkach dziedziczenia bardziej złożonego.
Baza
Klasa1 Klasa2
Klasa3
22
Przykładową implementację tej hierarchii przedstawiono
poniżej:
class Baza
{
void h( );
protected:
void g( );
public:
void f( ) { & h( ); & }
// metoda publiczpa f( ) korzysta z metody prywatpej h( )
};
class Klasa1: public Baza
{
public:
void f( ) { & g( ); h( ); & }
// przesłopięcie odziedziczopej metody Baza::f( ),
// oraz - wewpątrz defipicji metody f( ), odwołapie do
// odziedziczopej metody g( ) i właspej metody h( )
void h( );
// deklaracja właspej metody h( ) bez przesłapiapia
// metody Base::h( ), która pie jest dziedziczopa
};
class Klasa2: public Baza
{
public:
void f( )
{ g( ); // wywołapie metody odziedziczopej
h( ); // wystąpi błąd kompilacji Base::h( ) piedostęppa
};
};
23
class Klasa3: public klasa1, public Klasa2
// dziedziczepie wielobazowe
{ public:
void f( )
{ g( ); // wywołapie metody odziedziczopej z klasy Baza
Klasa1:: h( ); Baza:: f( );
// poprawpe wywołapie dwóch metod klas bazowych z
// wykorzystapiem kwalifikatora dostępu
}
};
Poniżej przykład testowania powyższej hierarchii klas w
funkcji maip( )
int main( )
{ Baza obj;
Klasa1 obj1;
Klasa2 obj2;
Klasa3 obj3;
obj.f( ); // wywołapie poprawpe
obj.g( ); // błąd Baza::g( ) dostęppa tylko w klasach
// pochodpych
obj.h( ); // błąd Baza::h( ) metoda prywatpa
obj1.f( ); // wywołapie poprawpe
obj1.g( ); // błąd odziedziczopa metoda Baza::g( )
// piedostęppa pa zewpątrz defipicji klasy
obj1.h( ); // wywołapie poprawpe
obj2.f( ); // wywołapie poprawpe
obj2.h( ); // błąd Base::h( ) piedostęppa
obj3.f( ); // wywołapie poprawpe
obj3.g( ); // błąd odziedziczopa metoda Base::g( )
// piedostęppa pa zewpątrz defipicji klasy
obj3.h( ); // poprawpe h( ) odziedziczopa z klasy Klasa1
returp 0; }
24
Obiekty klasy pochodnej w roli obiektów klasy bazowej
Przykład:
class PR COWNIK
{
protected:
string Nazwisko;
string Dział;
int Uposażenie;
. . . . . . . . . . . . . . . . . . . . .
};
class KIEROWNIK: public PR COWNIK
{
int Uprawnienia[ 8 ];
. . . . . . . . . . . . . . . . . . . . .
};
void main( )
{
KIEROWNIK kk;
PR COWNIK * p = & kk;
// każdy kierownik jest jednocześnie pracownikiem,
PR COWNIK pp;
KIEROWNIK * k = & pp;
// . . . ale nie każdy pracownik kierownikiem
}
o Obiekty klasy pochodnej są traktowane jak obiekty klasy
bazowej, jeśli sięgnie się do nich za pomocą wskaznika.
Odwrotnie NIE !
25
Polimorfizm składowych. Wiązanie statyczne i dynamiczne
class Class1 {
public:
virtual void f( ) { & }
void g( );
};
class Class2 {
public:
virtual void f( ) { & }
void g( ); };
int main( )
{
Class1 object1;
Class1 *p=&object1;
// wskazpik p pokazuje pa obiekt object1
Class2 object2;
p=(Class1 *) &object2;
// kopwersja jawpa adresu obiektu object2
// do typu Class1 *
p->g( ); // nastąpi wywołanie funkcji Class1::g( )
// wiązanie statyczne
p->f( ); // nastąpi wywołanie funkcji Class2::f( )
// - wiązanie dynamiczne
return 0;
}
o Wiązanie statyczne wskaznika z obiektem ma miejsce na
etapie kompilacji. Pózniejsze wiązanie wskaznika z
obiektem innej klasy nie ma już znaczenia.
26
o W przypadku wiązania dynamicznego (wymuszonego
tutaj związaniem obu funkcji składowych f( ) słowem
virtual) decyzja o wiązaniu funkcji z obiektem jest
odkładana do czasu wykonania programu. Słowo virtual
wiąże wszystkie funkcje poprzedzone słowem virtual o
tej samej nazwie w różnych klasach.
Zjawisko póznego wiązania funkcji składowej z obiektem
(dopiero na etapie wykonania) nazywamy polimorfizmem.
Polimorfizm jest potężnym narzędziem programowania
obiektowego. Jeśli wyślemy dowolny komunikat do obiektów
wielu różnych klas, zawierających wirtualne funkcje o tej
samej nazwie (i na ogół różnych treściach), wykona się tylko
funkcja z klasy, z której obiektem jest w danej chwili
związany wskaznik.
Dziedziczenie i polimorfizm. Abstrakcyjne typy danych.
W poniższym przykładzie zaprezentowane zostanie użycie:
klas abstrakcyjnych, funkcji wirtualnych i czysto wirtualnych
(abstrakcyjnych) w warunkach dziedziczenia, oraz omówiony
mechanizm polimorfizmu.
Klasa abstrakcyjna
FIGURE
RECT NGLE CIRCLE
SQU RE
27
Klasa FIGURE istnieje tylko po to, aby dostarczać interfejsu
dla wyprowadzanych z niej klas jest abstrakcyjnym typem
danych (abstract data type ADT). DT jest zwykle typem
bazowym dla hierarchii klas konkretnych, nie tworzy
obiektów i często nie posiada danych składowych.
class FIGURE
{
public:
virtual ~FIGURE( ) { } // wirtualny destruktor
// popiżej deklaracje funkcji abstrakcyjnych,
// to jest czysto wirtualnych
virtual float Get rea( )=0;
virtual void Draw( )=0;
};
class CIRCLE: public FIGURE
{
float itsRadius;
public:
CIRCLE(float radius): itsRadius(radius) {}
virtual ~CIRCLE( ) { }
virtual float Get rea( ) { return 3.14*itsRadius*itsRadius;}
virtual void Draw( ); // wymagana definicja funkcji
};
class RECT NGLE: public FIGURE
{
float itsWidth;
float itsLength;
public:
RECT NGLE (float width, float len):
itsWidth(width), itsLength(len) {}
virtual ~ RECT NGLE ( ) { }
28
virtual float Get rea( ) { return itsWidth*itsLength;}
virtual float GetLength( ) { return itsLength;}
virtual float GetWidth( ) { return itsWidth;}
virtual void Draw( ); // wymagana definicja funkcji
};
class SQU RE: public RECT NGLE
{
public:
SQU RE (float len);
virtual ~ SQU RE ( ) { }
// wszystkie jawne metody są dostępne jako
// odziedziczone z klasy RECT NGLE
};
SQU RE::SQU RE( float len): RECT NGLE(len, len) { }
// definicja konstruktora klasy SQU RE -
// faktycznie tworzony jest obiekt klasy RECT NGLE
int main( ) // fupkcja testująca
{
enum figure {circle, rectangle, square };
figure fig;
float len, area;
FIGURE *sp; // wskazpik pa klasę bazową hierarchii
& & & & & & & & & & & & & & & & & ..
switch(fig)
{
case circle : sp=new CIRCLE(5); break;
case rectangle: sp=new RECT NGLE(4,6); break;
case square : sp=new SQU RE(5); break;
default : break;
}
29
sp->Draw( ); // wystąpi zjawisko polimorfizmu !!!
area=sp->GetArea( ); // wystąpi zjawisko polimorfizmu !!!
if((fig==rectangle) or (fig==square)) len=sp->GetLength( );
delete sp; // polimorficzne wywołapie destruktora !!!
return 0;
}
o Wywołanie metod Draw( ) i GetArea( ), dla obiektu
wskazywanego przez sp, powoduje wywołanie
odpowiedniej funkcji klasy pochodnej, odpowiedniej do
klasy obiektu, na który aktualnie wskazuje sp, chociaż sp
jest z deklaracji wskazaniem klasy bazowej FIGURE.
o Podobnie delete sp jest równoważne z wywołaniem
destruktora klasy pochodnej, odpowiedniego dla obiektu
klasy, na który wskazuje w danej chwili sp. Destruktor w
klasie FIGURE jest czysto wirtualny a jego definicje
znajdują się w klasach pochodnych.
Zasady używania funkcji wirtualnych i czysto wirtualnych
w warunkach dziedziczenia:
" Można tworzyć obiekty klas zawierających funkcje
wirtualne, ale jeśli chociaż jedną z tych funkcji uczynimy
abstrakcyjną, cała klasa staje się abstrakcyjną nie
tworzącą obiektów.
" Funkcje wirtualne dziedziczą się do klas pochodnych tak
jak zwykłe funkcje.
" Typ funkcji wirtualnej jest deklarowany w klasie bazowej
i nie może ulec zmianie w klasach pochodnych.
" Funkcja wirtualna musi być zdefiniowana w pierwszej
klasie konkretnej hierarchii klas.
" Klasy pochodne nie muszą korzystać z funkcji
wirtualnych klas bazowych.
30
" Jeśli wywołuje się funkcję wirtualną z kwalifikatorem
zakresu, np. SQUARE:: Draw( ), mechanizm wirtualny
nie działa.
Jeszcze o funkcjach wirtualnych i klasach abstrakcyjnych
class X {
public:
virtual void f( )=0; // fupkcja czysto wirtualpa
virtual void g( )=0; // ippa fupkcja czysto wirtualpa
};
X x; // błąd - deklaracja obiektu klasy abstrakcyjpej X
class : public X {
public:
void f( ); // unieważnienie X::f( )
};
a; // błąd - deklaracja obiektu klasy A, która staje się
// abstrakcyjną z powodu dziedziczenia funkcji czysto
// abstrakcyjnej X::g( )
class B: public {
public:
void g( ); // unieważnienie X::g( )
};
B b; // w porządku
class K: public X {
public:
virtual void f( ); // wymagana definicja K::f( )
void g( ); // unieważnienie X::g( )
};
31
K k; // w porządku
o Funkcja czysto wirtualna z klasy abstrakcyjnej, która nie
ma swojej definicji w klasie pochodnej, dziedziczy się do
klasy pochodnej jako czysto wirtualna, czyniąc klasę
pochodną również klasą abstrakcyjną.
Dane i funkcje składowe statyczne
o Kwalifikator static w odniesieniu do danych składowych
oznacza dane wspólne do wszystkich obiektów danej
klasy .
o Dane te po zdefiniowaniu istnieją, nawet jeśli nie istnieją
obiekty tej klasy.
o Dane te podlegają paradygmatowi hermetyzacji - jeśli są
zadeklarowane w sekcji prywatnej, mogą być widziane
tylko przez funkcje składowe klasy.
Przykład:
#include
class INTEGER
{ static int cnt; // deklaracja dapej statyczpej
int val;
public:
INTEGER (void): val(0) { cnt++; }
INTEGER ( int i ): val(i) { cnt++; }
static int IleObiektow(void) { return cnt; }
// powyżej defipicja fupkcji statyczpej
};
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
int INTEGER:: cnt = 0;
// ipicjalizacja danej składowej statycznej poza klasą
32
int main( )
{ cout<< \n < INTEGER i(10), j, k;
cout<< < return 0;
};
Wyniki: 0 3
o Dane składowe statyczne powinny być inicjalizowane
poza klasą, tak jak to pokazano w przykładzie powyżej.
o Powinny być modyfikowane wyłącznie za pomocą
funkcji składowych statycznych, gdyż te mogą działać
nawet jeżeli nie powołano do życia jeszcze żadnego
obiektu klasy, zawierającego dane składowe statyczne.
Modyfikacja ta natychmiast uwidoczni się we wszystkich
egzemplarzach klasy.
o Dane te podlegają paradygmatowi hermetyzacji - jeśli są
zadeklarowane w sekcji prywatnej, mogą być widziane
tylko przez funkcje składowe klasy.
Funkcje składowe statyczne:
" mogą być wywoływane, nawet jeżeli nie utworzono jeszcze
żadnego obiektu klasy, w której zostały zadeklarowane, lub
zdefiniowane patrz /* 1 */,
" nie generują wskaznika this,
" w związku z powyższym:
- umożliwiają tylko odwołania do danych statycznych
klasy,
- poza klasą mogą być wywoływane jedynie z kwalifika-
torem zakresu, gdyż nie mogą być wywoływane na
rzecz jakiegokolwiek obiektu ,
" zmiany wprowadzone przez funkcje statyczna natych-
miast obowiązują we wszystkich obiektach klasy.
33
Obiekty statyczne
void f( . . . )
{ . . . . .
static PUNKT p;
// powyżej obiekt statyczpy w roli obiektu automatyczpego
. . . . .
};
o Wszystkie obiekty statyczne (nawet te zadeklarowane w
funkcjach) są tworzone po załadowaniu do pamięci
modułu programu jako obiekty globalne i dostępne do
końca modułu. Obiekt p klasy PUNKT istnieje więc
między wywołaniami funkcji f( ... ).
o Zasady obsługi obiektów statycznych, globalnych,
dynamicznych i automatycznych są więc takie same, jak
zasady obsługi zwykłych zmiennych zgodnie z
paradygmatem proceduralnym.
Dostęp do własnych składowych przez zmienną this
class PUNKT {
int x, y;
public:
void UstawWsp( int i, int j) { x=i; y=j; }
};
W każdej funkcji składowej dowolnej klasy jest niejawnie
deklarowany stały wskaznik this i inicjowany po wywołaniu
ustalonym wskazaniem obiektu, na rzecz którego funkcja została
wywoływana. W naszym przykładzie ta niejawna deklaracja ma
postać:
PUNKT * const this;
34
Dzięki temu możliwe są wywołania tej samej funkcji dla
różnych obiektów. W rzeczywistości bowiem, w naszym
przykładzie, funkcja o nagłówku UstawWsp(int, int) ma
definicję
void UstawWsp( int i, int j) { this y=j; }
x=i; this
Jawne używanie this nie jest konieczne, ale czasem może się
bardzo przydać, W dalszej części wykładu pokazane będą
ciekawe użycia wskaznika this.
Dane składowe stałe i ulotne
class X
{ const int Cval; // składowa stała
volatile int Vval; // składowa ulotpa
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
};
o Wartość składowej stałej Cval nie może ulec zmianie.
Klasy zawierające składowe stałe muszą posiadać jawne
konstruktory, inicjujące te składowe. Ich pózniejsza
zmiana nie będzie możliwa. Kompilator nie dopuszcza
więc konstruktorów domyślnych.
o Jakikolwiek dostęp do składowej ulotnej Vval wiąże się
zawsze z jej przepisaniem z rejestru procesora do pamięci
operacyjnej, i powtórnym załadowaniem z pamięci do
rejestru procesora. Tego typu dane są niezbędne w
systemach, w których mają miejsce tzw.
niesynchroniczne przerwania. Taka obsługa danych
zabezpiecza przed ich zmianą przez inne procesy,
przebiegające w trakcie przerwania.
Koniec części II
Wyszukiwarka
Podobne podstrony:
Jezyki i paradygmaty cz III
Jezyki i paradygmaty cz IV
Jezyki i paradygmaty cz V
Języki i paradygmaty zaocz cz I
2009 SP Kat prawo cywilne cz II
413 (B2007) Kapitał własny wycena i prezentacja w bilansie cz II
Fotografia ślubna zdjęcia w plenerze, cz II
Choroby obturacyjne górnych dróg oddechowych u koni cz II(1)
4 połączenia śrubowe cz II
Aparat czy kamera Każdemu wg potrzeb, cz II – kamery zaawansowane
9 cz II
test Chemia materiałów cz II
Maraton życia, cz II
więcej podobnych podstron