node127






20.1 Podstawy dziedziczenia



























Dalej: 20.2 Konstruktory i destruktory klas
W górę: 20. Dziedziczenie i polimorfizm
Wstecz: 20. Dziedziczenie i polimorfizm






20.1 Podstawy dziedziczenia



Definiując klasę pochodną
definiujemy typ danych rozszerzający typ
określany przez klasę bazową,
a więc tę, z której klasa dziedziczy. Obiekty klasy pochodnej będą
zawierać te składowe, które zawierają obiekty klasy bazowej, i,
choć niekoniecznie, dodatkowe składowe, których nie było w klasie
bazowej. Klasa pochodna może też dodawać nowe metody lub zmieniać
implementację metod odziedziczonych ze swojej klasy bazowej.


Zatem klasa bazowa jest bardziej ogólna, modeluje pewien fragment
rzeczywistości na wyższym poziomie abstrakcji. Klasa pochodna jest
bardziej szczegółowa, mniej abstrakcyjna. Tak więc, na przykład,
pojęcie mebel jest bardziej abstrakcyjne, zaś krzesło bardziej
konkretne: zatem klasa opisująca krzesła dziedziczyłaby z klasy
opisującej meble:
Mebel


Krzeslo.
Mogłaby dodać, na przykład, składową opisującą ilość nóg,
której w bardziej ogólnej klasie
Mebel nie było,
bo nie każdy mebel ma nogi.


Zauważmy tu, że zaznaczając dziedziczenie za pomocą strzałek,
jak to zrobiliśmy powyżej, rysujemy te strzałki w kierunku od klasy
pochodnej do klasy bazowej.


Składnia deklaracji/definicji klas pochodnych jest następująca:
class A {
// ...
};

class B : public A {
// ...
};

class C : B {
// ...
};

Klasy bazowe dla danej klasy deklarujemy na
liście dziedziczenia
umieszczonej po nazwie klasy i dwukropku, a przed definicją (ciałem)
klasy. Klas bazowych może być kilka: ich nazwy umieszczamy wtedy na
liście oddzielając je przecinkami. Powyższy zapis oznacza, że


klasa
A jest klasą pierwotną; nie dziedziczy
z żadnej innej klasy (nie ma w C++ wspólnego
„przodka'' w rodzaju klasy
Object
z Javy);

klasa
B dziedziczy z 
A;

klasa
C dziedziczy z 
B, a więc pośrednio
również z klasy 
A.


W definicji klasy
B występuje specyfikator dostępu

public. Prócz tego specyfikatora, mogą w tym
miejscu wystąpić również specyfikatory
private
lub
protected.
Określają one górną granicę dostępności tych składowych klasy
pochodnej, które odziedziczone zostały z klasy bazowej
(patrz rozdział o dostępności składowych klasy).
Oznacza to, że składowe określone w klasie bazowej będą w klasie
pochodnej miały dostępność taką samą jak w klasie bazowej lub
węższą, jeśli ich dostępność w klasie bazowej była szersza
niż ta zadeklarowana w klasie pochodnej. Odziedziczone składowe
prywatne w ogóle nie są w klasie pochodnej bezpośrednio dostępne.
Mogą jednak być dostępne za pomocą odziedziczonych nieprywatnych
metod z klasy bazowej - takie metody znajdują się w zakresie klasy
bazowej, więc do jej składowych prywatnych oczywiście mają dostęp.






Składowe prywatne odziedziczone z klasy bazowej, nawet jeśli
nie są widoczne w klasie pochodnej, fizycznie wchodzą w skład
obiektów klasy pochodnej.





Reasumując:


Specyfikator
public oznacza, że wszystkie składowe
publiczne w klasie bazowej będą publiczne i w klasie
pochodnej, a składowe chronione (
protected) będą
chronione i w klasie dziedziczącej (składowe prywatne, jak
powiedzieliśmy, nie są widoczne);

Specyfikator
protected oznacza, że wszystkie
składowe publiczne w klasie bazowej będą chronione
(
protected) w klasie pochodnej, tak jak i składowe
chronione z klasy bazowej.

Specyfikator
private oznacza, że wszystkie składowe
publiczne i chronione z klasy bazowej stają się prywatnymi
w klasie pochodnej.


Przypomnijmy tu, że






Atrybut
protected oznacza, że dane pole
czy metoda będą dostępne (tak jakby były publiczne) w klasach
pochodnych danej klasy, a zachowują się jak prywatne dla
wszystkich innych klas i funkcji.





Brak specyfikatora na liście dziedziczenia, jak w definicji
klasy 
C z powyższego przykładu, jest równoważny z określeniem
specyfikatora
private.


Zauważmy, że w powyższym przykładzie dziedziczenie z klasy 
C
miałoby już niewielki sens, bo w obiektach ewentualnej klasy
dziedziczącej żadne składowe z klas bazowych (w tym przypadku

A,
B i 
C) nie byłyby w ogóle widoczne.


Jeśli w klasie pochodnej zawęziliśmy dostępność
pól/metod specyfikatorem
protected lub
private
w definicji klasy (po dwukropku, a przed ciałem klasy), to można tę
dostępność przywrócić indywidualnie dla
wybranych pól/metod, podając ich identyfikatory w postaci
kwalifikowanych nazw w odpowiednich sekcjach. Podkreślmy jeszcze raz:
nazwy, a nie pełne deklaracje. Kwalifikowane, czyli wraz
z nazwą klasy bazowej.


W poniższym przykładzie
w klasie
A składowe
x,
y i 
z są
publiczne, a składowe (tutaj będące metodami)
fff,

ggg i 
hhh są chronione. Składowa
k jest
prywatna i nie będzie bezpośrednnio widoczna w klasie pochodnej.
class A {
double k;

public:
int x, y, z

protected:
double fff(int);
double ggg(int);
double hhh(int);
// ...
};

Niech teraz klasa
B będzie zdefiniowana tak:
1. class B : private A {
2. public:
3. A::x;
4. A::y;
5.
6. protected:
7. A::fff;
8. // ...
9. };

W klasie
B dostępność dziedziczonych składowych klasy

A zawężona jest do poziomu
private (linia 1; ten sam
efekt można było zapewnić nie podając specyfikatora dostępności
w ogóle, gdyż dostępność
private jest domyślna).
Następnie jednak przywracana jest dostępność publiczna
dla składowych
x i 
y (linie 2-4).
Zauważmy, że nie przywracamy takiej dostępności składowej

z, tak więc w klasie
B stanie się ona prywatna.
Zauważmy też, że w liniach 3 i 4 wymienieliśmy tylko kwalifikowane
nazwy, a nie deklaracje - nie podaliśmy na przykład typu pól.


W liniach 6-7 przywróciliśmy dostępność chronioną dla
składowej
fff. Tu również podaliśmy tylko kwalifikowaną
nazwę, a nie deklarację funkcji - nie ma tu listy parametrów
czy określenia typu wartości zwracanej. Metody
ggg

hhh, którym dostępności chronionej nie przywróciliśmy,
stają się w klasie
B prywatne.


Prócz odziedziczonych składowych w klasie pochodnej można
definiować własne pola i metody. Tak więc obiekt klasy pochodnej
nigdy nie będzie mniejszy niż obiekt klasy bazowej: jak
wspomnieliśmy, zawiera bowiem zawsze kompletny podobiekt klasy bazowej
i często dodatkowe składowe, których w klasie bazowej
nie było.


W klasie pochodnej można definiować składowe o tych samych nazwach
co składowe klasy bazowej. Mówimy wtedy o
przesłanianiu pól i metod
(przedefiniowywaniu, nadpisywaniu, przekrywaniu, przykrywaniu ...;
ang. overriding). Przesłaniania pól lepiej nie stosować,
bo prowadzi to do chaosu trudnego do opanowania. Natomiast
przesłanianie metod jest fundamentalnym narzędziem
programowania obiektowego.


Metody/pola z klasy bazowej (na przykład klasy 
A) mają zakres
tejże klasy bazowej; na przykład mają dostęp do składowych
prywatnych tej klasy. Natomiast zakres klasy pochodnej jest
zawarty w zakresie klasy podstawowej. Znaczy to, że
jeśli te pola i metody nie są prywatne i nie zostały
w klasie pochodnej przedefiniowane (przesłonięte),
to w klasie pochodnej (na przykład
B) są również widoczne
bezpośrednio (a więc nie trzeba stosować dla nich nazw
kwalifikowanych). Jeśli natomiast w klasie pochodnej zostały
przesłonięte, to zakresem takich pól/metod będzie klasa pochodna

B: w tej klasie dostępne są bezpośrednio „wersje''
przedefiniowane. Oczywiście składowe prywatne z klasy bazowej nie są
dostępne w zakresie klasy
B. Natomiast do składowych
nieprywatnych z klasy
A, przesłoniętych w klasie pochodnej

B, można się wciąż odwołać z zakresu klasy
B
poprzez jawną kwalifikację nazwy za pomocą operatora zakresu
klasowego, a więc, w naszym przypadku, poprzedzając nazwę
wskazaniem zakresu '
A::'.


Rozpatrzmy przykład:



Wyszukiwarka

Podobne podstrony:
node12
node126
node121
node126
node129
node126 EAT2FBA72AHUMICANGCYU4DWVTTCBUBOOFOV5DI
node128
node124
node127
node128
node126
node124
node12 5RJIDD32YMXJNTH7EYXB4KIBTOGGXXKPFHRU6VA
node120
node128

więcej podobnych podstron