background image

 

 

Dziedziczenie

Każdy pies dziedziczy  z ssaka  wszystkie jego cechy.  Możemy powiedzieć, że 
ponieważ jest ssakiem to umie się poruszać, oddycha powietrzem itp. Jednak 
pojęcie  pies  dodaje  do  definicji  ssaka  możliwość  szczekania,  machania 
ogonem itp. Pojęcie pies jest specjalistyczne natomiast ssak ogólne.
C++  pozwala  na  reprezentowanie  takich  relacji  poprzez  definiowanie  klas 
pochodzących  od  innych  klas.  Pochodzenie  jest  metodą  wyrażania  relacji 
„jest..." 

Ssak

Gad

Kot

Pies

Terier

Myśliwski

Zwierz

ę

Można  stworzyć  klasę  Pies  jako  pochodną  klasy  Ssak.  Nie  trzeba  jawnie 
określać, że Pies potrafi się poruszać, gdyż ta cecha zostanie odziedziczona z 
klasy  Ssak.  Klasa  Pies,  poprzez  dziedziczenie  z  klasy  Ssak,  automatycznie 
posiada umiejętność "poruszania się".

background image

 

 

Klasa,  która  wprowadza  nowe  funkcje  do  już  istniejącej  klasy,  nazywana 
jest  pochodną  klasy  oryginalnej.  Klasa  oryginalna  nazywana  jest  klasą 
bazową.

Jeżeli klasa  Pies jest pochodną klasy  Ssak, to klasa Ssak jest klasą bazową 
klasy  Pies.  Klasy  pochodne  są  nadzbiorami  ich  klas  bazowych.  Tak  jak  pies 
posiada  dodatkowe  umiejętności  w  stosunku  do  statystycznego  ssaka,  tak  i 
klasa Pies dodaje nowe metody i dane do klasy Ssak.

Wyobraźmy  sobie,  że  dostaliśmy  zadanie  zaprojektowania  gry  dla  dzieci  - 
symulacji  farmy.  Trzeba  napisać  dla  każdej  klasy  metody  powodujące,  że 
każde  zwierzę  będzie  się  zachowywać  zgodnie  z  oczekiwaniami  odbiorcy 
programu. 

Zadanie

Tworzenie klasy pochodnej

Podczas deklaracji klasy trzeba zaznaczyć, że jest ona pochodną innej klasy 
poprzez napisanie dwukropka po nazwie tworzonej klasy, typu pochodzenia 
(public albo inny), a następnie nazwy klasy bazowej. 

Przykład

class Pies: public Ssak

background image

 

 

Przykład

W  świecie  rzeczywistym  ssaki  są  pochodną 
zwierząt.  W  programie  w  C++  jesteśmy  w 
stanie 

przedstawić 

jedynie 

część 

posiadanych  informacji  o  danym  obiekcie. 
Rzeczywistość  jest  zbyt  kompleksowa  i  nie 
można  uwzględnić  wszystkich  jej  aspektów. 
Każda  hierarchia  w  C++  jest  jedynie 
odzwierciedleniem  fragmentu  posiadanych 
informacji.  Sztuka  dobrego  projektowania 
polega 

na 

przedstawieniu 

ważnych 

obszarów  w  taki  sposób,  aby  całość 
maksymalnie przystawała do rzeczywistości.

W poprzednich programach, zmienne wewnętrzne klasy 
były deklarowane po słowie kluczowym private. Jednak 
zmienne deklarowane jako private nie byłyby widoczne 
w  klasie  pochodnej.  Oczywiście  można  również 
zadeklarować  zmienne  nJegoWiek  i  nJegoWaga  jako 
public,  ale  jest  to  niewskazane,  gdyż  umożliwiłoby 
bezpośredni dostęp do nich innym klasom.

background image

 

 

Private czy Protected ?

Funkcje  i  zmienne  zadeklarowane  jako  protected  są  dostępne  we 
wszystkich klasach pochodnych (i są w nich prywatne).

Zmienne 

funkcje 

zadeklarowane 

jako 

protected  są  widoczne 
dla  wszystkich  funkcji 
danej  klasy  i  jej  klas 
pochodnych.

public

protected

private

Jeżeli  funkcja  ma  dostęp 
do obiektu danej klasy to 
ma  również  bezpośredni 
dostęp  do  wszystkich  jej 
zmiennych 

funkcji 

zadeklarowanych 

jako 

public.

Do zmiennych i funkcji 
zadeklarowanych  jako 
private  mają  dostęp 
tylko 

funkcje 

wewnętrzne 

danej 

klasy.

background image

 

 

Przykład

background image

 

 

Konstruktory i destruktory

Obiekty  klasy  Pies  są  również  obiektami  klasy  Ssak.  Jest  to  główna  cecha 
relacji „jest...". 

Podczas  usuwania  obiektu  Chacko  z  pamięci  zachodzi  proces  odwrotny. 
Najpierw  jest  wywoływany  destruktor  klasy  Pies,  a  następnie  destruktor 
klasy  Ssak.  Każdy  destruktor  kasuje  tę  część  obiektu  Chacko,  która  należy 
do jego klasy. 

Kiedy  tworzymy  obiekt  Chacko  najpierw  jest  wywoływany  jego  konstruktor 
bazowy, którego zadaniem jest stworzenie obiektu klasy Ssak. Następnie jest 
wywoływany konstruktor klasy Pies, tworzący gotowy obiekt. Ponieważ, przy 
deklaracji  Chacko,  nie  podaliśmy  żadnych  parametrów,  to  wywoływany  jest 
domyślny  konstruktor  klasy  Pies.  Obiekt  Chacko  jest  w  pełni  stworzony 
dopiero wtedy, gdy zostaną wykonane oba konstruktory: jeden z klasy Ssak i 
drugi z klasy Pies.

background image

 

 

background image

 

 

Przekazywanie argumentów do konstruktora bazowego

Istnieje możliwość przeciążenia konstruktora klasy Ssak tak, aby pobierał on 
konkretny  wiek.  Podobnie  można  przeciążyć  konstruktor  klasy  Pies,  tak  aby 
pozwalał  on  na  proste  określenie  rasy.  Jak  odczytać  wartość  parametru 
przekazanego  do  konstruktora  w  klasie  Ssak? Co się stanie,  gdy klasa  Pies 
pozwala na inicjalizację wieku, natomiast klasa Ssak nie?

Inicjalizacja  klasy  bazowej  może  być  przeprowadzona  podczas  inicjalizacji 
klasy  poprzez  napisanie  nazwy  klasy  bazowej  i  podanie  w  nawiasach 
parametrów wymaganych przez klasę bazową. 

background image

 

 

Zauważ,  że  domyślny  konstruktor  klasy  Pies 
wywołuje  domyślny  konstruktor  klasy  Ssak
Takie  rozwiązanie  nie  jest  ściśle  wymagane, 
jest 

to 

"dokumentacja" 

wywołania 

domyślnego  konstruktora  klasy  bazowej. 
Bazowy konstruktor i tak zostanie wywołany.

Podobnie jak poprzednio, najpierw inicjalizuje on 
klasę 

bazową 

poprzez 

wywołanie 

odpowiedniego 

konstruktora 

klasy 

Ssak

Dodatkowo 

inicjalizowana 

jest 

zmienna 

wewnętrzna nJegoWaga. Zauważmy, że nie ma 
możliwości  inicjalizacji  zmiennej  klasy  bazowej 
w  części  inicjalizacyjnej  konstruktora.  Ponieważ 
klasa 

Ssak 

nie 

posiada 

konstruktora 

pobierającego 

wartość 

dla 

zmiennej 

nJegoWaga

dlatego 

inicjalizację 

trzeba 

przeprowadzić w treści konstruktora.

background image

 

 

background image

 

 

Nadpisywanie funkcji

Obiekt klasy Pies ma dostęp do wszystkich funkcji wewnętrznych klasy Ssak 
tak  jak  do  własnych.  Podobnie,  jak  dodaliśmy  metodę  MachajOgonem()
możemy  dodać  inne  funkcje.  Istnieje  również  możliwość  nadpisania  funkcji 
klasy  bazowej.  Nadpisanie  oznacza  zmianę  implementacji  funkcji  z  klasy 
bazowej. Kiedy wywołuje się metodę z klasy pochodnej kompilator wywoła tę 
właściwą - stworzoną w tej klasie.

Kiedy  klasa  pochodna  tworzy  funkcję  z  tym  samym  typem  wartości 
zwracanej,  z  tą  samą  nazwą  i  listą  parametrów  (sygnaturą)  co  jakaś 
funkcja w klasie bazowej i definiuje jej nową implementację to mówimy o 
nadpisaniu tej funkcji (metody).

UWAGA: Kiedy chce się napisać funkcję, trzeba zapewnić zgodność typu 
wartości zwracanej, nazwy i listy parametrów z funkcją klasy bazowej.

Przeciążanie czy nadpisywanie ? 

Obie metody dają podobne efekty. Kiedy przeciąża się metodę to tworzy się 
kilka  różnych  metod  o  tej  samej  nazwie  i  o  różnej  sygnaturze  (wartość 
zwracana,  lista  parametrów).  Kiedy  nadpisuje  się  metodę  to  tworzy  się  w 
klasie pochodnej metodę, która zastępuje metodę z klasy bazowej.

background image

 

 

klasa 

Pies 

nadpisuję 

metodę Mow() tak, aby każdy 
obiekt  klasy  Pies  szczekał 
(wypisywał na ekranie Hau!) w 
momencie  wywołania  metody 
Mow() 

background image

 

 

Ukrywanie metod klasy bazowej

W  ostatnim  programie,  metoda  klasy  Pies  o  nazwie  Mow()  ukryła  metodę 
klasy bazowej. O to nam chodziło, ale istnieje możliwość zaistnienia pewnego 
efektu  ubocznego.  Jeżeli  klasa  Ssak  miałaby  przeciążoną  metodę  Ruch()
którą  byśmy  nadpisali  w  klasie  Pies,  to  zostałyby  ukryte  wszystkie 
przeciążone metody Ruch() w klasie Ssak.
Jeżeli klasa  Ssak miałaby trzy metody przeciążające funkcję  Ruch() - jedną 
nie  pobierającą  parametrów,  druga  pobierającą  wartość  całkowitą  i  trzecią 
dwuargumentową i jeżeli klasa Pies nadpisałaby metodę Ruch() funkcją nie 
pobierającą parametrów to dostęp do pozostałych dwóch metod z klasy Ssak 
byłby bardzo utrudniony.

Linia  została  zakomentowana  ponieważ 
powoduje błąd kompilacji. Jeżeli klasa Pies 
nie  nadpisałyby  metody  Ruch()  to 
mogłaby  wywołać  metodę  Ruch(int)
Teraz,  jeżeli  chcielibyśmy  wykorzystać 
metodę  z  parametrem,  musielibyśmy 
również ją nadpisać. 

background image

 

 

Bardzo częstym błędem jest nieświadome ukrycie metod klasy bazowej przy 
próbie nadpisania ich. Przyczyną jest pominięcie słowa kluczowego const. 
const
 jest częścią sygnatury funkcji i pominięcie go zmienia sygnaturę 
powodując ukrycie funkcji zamiast jej nadpisania.

Wywołanie metody bazowej

Jeżeli nadpisało się już metodę bazową to nadal istnieje możliwość wywołania 
nadpisanej  funkcji.  Należy  w  tym  celu  podać  pełną  nazwę  metody  łącznie  z 
nazwą klasy bazowej. Oto przykład:

Możliwa  jest  również  pewna  ciekawa  modyfikacja  wykomentowanej  linii  z 
ostatniego programu:

Ssak: :Ruch()

 oChcacko.Ssak::Ruch(10) ;

background image

 

 

Programista chce wywołać metodę Ruch(int) z 
obiektu oChacko klasy Pies. Jest jednak pewien 
problem, gdyż klasa Pies nadpisała metodę 
Ruch(), ale nie przeciążyła jej czyli funkcja 
Ruch(int) nie jest bezpośrednio dostępna. 
Rozwiązaniem jest bezpośrednie odwołanie się do 
klasy Ssak i wywołanie metody Ruch (int).


Document Outline