DZIEDZICZENIE
Na dzisiejszych zajęciach powiemy sobie:
- Co to jest dziedziczenie?
- Jak tworzyć klasę pochodną na podstawie już istniejącej klasy.
- Co to jest dostęp chroniony i jak go wykorzystywać.
CO TO JEST DZIEDZICZENIE?
Na początku proste pytanie. Co to jest pies? Kiedy patrzycie na swojego puchatego ulubieńca to co widzicie? Biolog widzi system współdziałających organów wewnętrznych. Fizyk widzi atomy i działające siły. Specjalista od taksonomii zobaczy przedstawiciela gatunku canis familiarus, natomiast moja żona widzi tylko psią sierść i ślinę. W tej chwili skupimy się na stanowisku reprezentowanym przez specjalistę od taksonomii. Pies jest rodzajem drapieżnika który z kolei jest rodzajem ssaka, ssak jest zwierzęciem itd. Taksonomia dzieli świat żywych organizmów na królestwa, grupy, klasy, rodziny itd. Taksonomia wprowadza hierarchię „jest…” albo „należy do…”. Pies jest drapieżnikiem. Wszędzie możemy dostrzec tego typu relacje. Toyota jest samochodem, który jest z kolei rodzajem pojazdu. Lody są deserem, który z kolei jest rodzajem pożywienia.
Co właściwie mamy na myśli, że coś jest „rodzajem czegoś”? Otóż najczęściej chodzi nam o to, że jedna rzecz jest szczególnym przypadkiem innej rzeczy (bardziej ogólnej). Samochód jest pojazdem. Samochody i autobusy to pojazdy, każdy z nich ma swoje cechy charakterystyczne, świadczące o ich „samochodowości” lub „autobusowości”. Jednak po uogólnieniu, że są pojazdami stają się one identyczne i dominująca staje się ich „pojazdowość”.
DZIEDZICZENIE I POCHODZENIE.
Każdy pies dziedziczy od ssaka wszystkie jego cechy. Możemy powiedzieć, że ponieważ jest ssakiem to umie się poruszać, oddychać powietrzem itd. Jednak pojęcie pies dodaje do definicji ssaka możliwość szczekania, machania ogonem itp. Czyli widzimy, że pojęcie pies jest specjalistyczne, natomiast ssak - ogólne. Możemy posunąć się dalej i podzielić psy na myśliwskie i pasterskie. Pasterskie możemy podzielić na owczarki niemieckie, owczarek colcie itd. Owczarek colcie jest rodzajem psa pasterskiego czyli owczarka, jest również psem, ssakiem, zwierzęciem i w końcu żywym stworzeniem. Poniższy rysunek pokazuje taką hierarchię.
C++ pozwala na reprezentowanie takich relacji poprzez definiowanie klas pochodzących od innych klas. Pochodzenie jest metodą wyrażania relacji „jest…”. Można stworzyć klasę Pies jako pochodną klasy Ssak. Nie musimy jawnie określać, że Pies potrafi poruszać się, gdyż ta cecha zostanie odziedziczona z klasy Ssak. Klasa Pies, poprzez dziedziczenie z klasy Ssak, automatycznie posiada umiejętność „poruszania się”. Klasa która wprowadza nowe funkcje do już istniejącej klasy, nazywana jest pochodną klasy oryginalnej. Klasa oryginalna nazywana jest klasą bazową. Jeśli pies jest pochodną klasy Ssak, to klasa Ssak jest 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. Zazwyczaj każda klasa bazowa ma więcej niż jedną klasę pochodną. Tak jak psy, koty i konie są ssakami, tak samo ich klasy będą pochodnymi klasy Ssak.
KRÓLESTWO ZWIERZĄT.
Żeby ułatwić dyskusję o pochodnych i dziedziczeniu na dzisiejszych zajęciach skupimy się na relacjach między klasami reprezentującymi zwierzęta.
TWORZENIE KLASY POCHODNEJ.
Podczas deklaracji klasy możemy zaznaczyć, że jest ona pochodną innej klasy poprzez napisanie dwukropka po nazwie tworzonej klasy, typu pochodzenia (public albo inny), a następnie nazwa klasy bazowej. Oto przykład:
Lass Pies : public Ssak
Rodzaj pochodzenia (derivation type) będziemy omawiali w dalszej części rozdziału. Na razie będziemy wykorzystywać wyłącznie public. Klasa bazowa musi być zadeklarowana wcześniej ( w przeciwnym przypadku kompilator zgłosi błąd). Napiszemy program demonstrujący tworzenie klasy pochodnej. Pies z klasy Ssak.
#include <iostream.h>
enum Rasa {Wilczur, Colcie, Jamnik, Chart, Spaniel, Kaukaz};
class Ssak
{
public:
Ssak ();
~Ssak ();
int pobierzWiek () const;
void UstawWiek (int);
int PobierzWage () const;
void ustawWage (int);
void mow ();
void spij ();
protected:
int jegoWiek;
int jegoWaga;
};
class Pies : public Ssak
{
public:
Pies ();
~Pies ();
Rasa pobierzRasa () const;
void ustawRasa (Rasa);
void machajOgonem ();
void prosOJedzenie ();
protected:
Rasa jegoRasa;
};
Ten program nic nie wypisuje na ekranie, jest jedynie zbiorem deklaracji klas z pominięciem definicji. Na samym początku pojawia się instrukcja:
enum Rasa {Wilczur, Colcie, Jamnik, Chart, Spaniel, Kaukaz};
Jest to metoda nazwania stałych całkowitych (podobnie jak const). Definiuje ona stałe całkowite, zwane stałymi wyliczeniowymi. Wyliczenie może mieć nazwę (w tym przypadku - Rasa) np.
enum Rasa {…}
Nazwa wyliczenia staje się odrębnym typem równoważnym np. z int. Następnie deklarujemy klasę Ssak. W tym przykładzie klasa Ssak nie jest pochodną żadnej klasy. W świecie rzeczywistym ssaki są pochodną zwierząt. W programie w C++ jesteśmy w stanie przedstawić jedynie część posiadanych informacji o danym obiekcie. 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. Hierarchia musi mieć swój początek. początek naszym programie zaczęliśmy jej tworzenie od klasy Ssak. Powoduje to, że niektóre zmienne wewnętrzne, które teoretycznie powinny znaleźć się w jakiejś „wyższej” klasie są reprezentowane tutaj. Np. Każde zwierzę ma swój wiek i wagę dlatego jeśli klasa Ssak byłaby pochodną klasy Zwierze, to dziedziczyłaby z niej te atrybuty. Aktualnie znajdują się one wewnątrz klasy Ssak. Dla uproszczenia programu, w klasie Ssak zadeklarowaliśmy 6 metod: 4 funkcje dostępu; Mow () i Spij (). Klasa Pies dziedziczy z klasy Ssak. Każdy obiekt klasy Pies będzie posiadał trzy zmienne wewnętrzne: jegoWiek, jegoWaga i jegoRasa. Zauważcie, że deklaracja klasy Pies nie zawiera deklaracji zmiennych jegoWiek i jegoWaga. Zmienne te są dziedziczone z klasy Ssak podobnie jak metody tej klasy z wyjątkiem konstruktorów destruktora.
PRIVATE CZY PROTECTED?
Zapewne zauważyliście, że wykorzystaliśmy nowe słowa kluczowe w deklaracji klasy: protected. W poprzednich programach zmienne wewnętrzne klasy były deklarowane po słowie kluczowym private. Jednak zmienne deklarowane przez private nie byłyby widoczne w klasie pochodnej. Oczywiście można również zadeklarować zmienne jegoWiek i jegoWaga jako public, ale jest to niewskazane, gdyż uniemożliwiałoby to bezpośredni dostęp do nich innym klasom. To co jest nam w tej chwili potrzebne to udostępnienie tych zmiennych nie tylko klasie aktualnej, ale i wszystkim klasom pochodnym. Gwarantuje to słowo kluczowe protected. Funkcje i zmienne zadeklarowane jako protected są dostępne we wszystkich klasach pochodnych (i są w nich prywatne). Generalnie istnieją trzy typy postępowości elementu klasy: public, protected, i private. Jeżeli funkcja ma dostęp do obiektu danej klasy to ma również bezpośredni dostęp do wszystkich jej zmiennych i funkcji zadeklarowanych jako public. Do zmiennych i funkcji zadeklarowanych jako private mają dostęp tylko funkcje wewnętrzne danej klasy. Zmienne i funkcje zadeklarowane jako protected są widoczne dla wszystkich funkcji danej klasy i jej klas pochodnych. Dlatego funkcja Pies::MachajOgonem () ma dostęp do zmiennej jegoRasa i do wszystkich zmiennych klasy Ssak zadeklarowanych jako protected. Nawet jeśli w hierarchii umieścilibyśmy inne klasy pomiędzy Ssak:pies (np. klasa ZwierzeDomowe) tak klasa Pies i tak miałaby dostęp do wszystkich elementów klasy Ssak zadeklarowanych jako protected (oczywiście przy założeniu, że te dodatkowe klasy wykorzystują dziedziczenie publiczne). Czyli należy pamiętać, że jeżeli stworzymy obiekt klasy Pies to oprócz atrybutów klasy pies , dziedziczy on również wszystkie atrybuty klasy Ssak.
KONSTRUKTORY I DESTRUKTORY.
Obiekty klasy Pies są również obiektami klasy Ssak. Jest to główna cecha relacji „jest…”. Kiedy tworzymy obiekt np. Pluto 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 główny obiekt. Ponieważ, przy deklaracji Pluto, nie podajemy żadnych argumentów, to wywoływany jest domyślny konstruktor klasy Pies. Obiekt Pluto jest w pełni stworzony dopiero wtedy, gdy zostaną wykonane oba konstruktory jeden z klasy Ssak i drugi z klasy Pies. Podczas usuwania obiektu Pluto 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 Pluto, która należy do jego klasy. Należy pamiętać, aby posprzątać po Pies-ku! Napiszemy taki program:
#include <iostream.h>
enum Rasa {Wilczur, Colcie, Jamnik, Chart, Spaniel, Kaukaz};
class Ssak
{
public:
Ssak ();
~Ssak ();
int GetWiek () const {return jegoWiek;};
void SetWiek (int wiek) {jegoWiek=wiek;};
int GetWaga () const {return jegoWaga};
void UstawWage (int waga) {jegoWaga=waga;};
void Mow () const {cout<<”Odgłos Ssaka!\n”;};
void Spij () {cout<<”Cicho! Ja teraz śpie.\n”;};
protected:
int jegoWiek;
int jegoWaga;
};
class Pies : public Ssak
{
public:
Pies ();
~Pies ();
Rasa PobierzRasa () const {return jegoRasa;};
void UstawRasa (Rasa rasa) {jegoRasa=rasa;};
void MachajOgonem () {cout<<”Machanie ogonem.\n;};
void ProsOJedzenie () {cout<<”Prośba o jedzenie.\n;};
private:
Rasa jegoRasa;
};
Ssak :: Ssak();
jegoWiek (2);
jegoWaga (5);
{
cout<<”Konstruktor Ssaka.\n”;
}
Ssak :: ~Ssak()
{
cout<<”Destruktor Ssaka.\n”;
}
Pies :: Pies ()
jegoRasa (Colcie)
{
cout<<”Konstruktor Psa.\n”;
}
Pies :: ~Pies()
{
cout<<”Destruktor Psa.\n”;
}
int main()
{
Pies Pluto;
Pluto.Mow();
Pluto.MachajOgonem();
cout<<”Pluto ma”<<Pluto.GetWiek()<<”lata.\n”;
return 0;
}
Efekt działania:
Konstruktor Ssaka.
Konstruktor Psa.
Odgłos Ssaka.
Machanie ogonem.
Pluto ma 2 lata.
Destruktor Psa.
Destruktor Ssaka.
Wszystkie funkcje są zadeklarowane i zdefiniowane jako inline. Deklarujemy klasę Pies jako pochodną klasy Ssak. Dzięki takiej deklaracji każdy pies, oprócz własnej rasy, ma również określoną wagę i wiek. W programie deklarujemy obiekt klasy Pies o nazwie (imieniu) Pluto. Pluto oprócz atrybutów klasy Pies dziedziczy również wszystkie atrybuty klasy Ssak. Dla tego Pluto posiada metodę MachajOgonem(), Mow() i Spij(). Konstruktory i destruktory wypisują informację pozwalającą określić kiedy zostaną wywołane. Najpierw jest wywoływany konstruktor klasy Ssak, a następnie konstruktor klasy Pies. Dopiero w tym momencie obiekt Pies jest całkowicie stworzony i można odwoływać się do jego metod. Kiedy Pluto jest usuwany z pamięci, najpierw jest wywoływany destruktor klasy Pies, a następnie destruktor klasy Ssak.
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 przekazywanego 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ą. Spójrzmy na przykład:
#include <iostream.h>
enum Rasa {Wilczur, Colcie, Jamnik, Chart, Spaniel, Kaukaz};
class Ssak
{
public:
Ssak ();
Ssak (int wiek);
~Ssak ();
int PobierzWiek () const {return jegoWiek;};
void UstawWiek (int wiek) {jegoWiek=wiek;};
int PobierzWaga () const {return jegoWaga};
void UstawWage (int waga) {jegoWaga=waga;};
void Mow () const {cout<<”Odgłos Ssaka!\n”;};
void Spij () {cout<<”Cicho! Ja teraz śpie.\n”;};
protected:
int jegoWiek;
int jegoWaga;
};
class Pies : public Ssak
{
public:
Pies ();
Pies (int wiek);
Pies (int wiek, int waga);
Pies (int wiek, Rasa rasa);
Pies (int wiek, int waga, Rasa rasa);
~Pies ();
Rasa PobierzRasa () const {return jegoRasa;};
void UstawRasa (Rasa rasa) {jegoRasa=rasa;};
void MachajOgonem () {cout<<”Machanie ogonem.\n;};
void ProsOJedzenie () {cout<<”Prośba o jedzenie.\n;};
private:
Rasa jegoRasa;
};
Ssak :: Ssak();
jegoWiek (1);
jegoWaga (5);
{}
Ssak :: Ssak (int wiek)
jegoWiek (wiek);
jegoWaga (5)
{}
.
.
.
Pies :: Pies (int wiek, int waga, Rasa rasa)
Ssak (wiek);
jegoRasa (rasa);
{
jegoWaga=waga;
}
Klasa Ssak zawiera deklarację przeciążonego konstruktora klasy Ssak. Pobiera on wartość całkowitą będącą wiekiem tworzonego Ssaka. Zawarta w programie implementacja inicjalizuje zmienną jegoWiek wartością podaną jako argument konstruktora. Klasa Pies zawiera pięć przeciążonych konstruktorów. Pierwszy z nich to konstruktor domyślny. Drugi pobiera wiek będący następnie argumentem konstruktora klasy Ssak. Trzeci konstruktor pobiera wiek i wagę tworzonego obiektu - Psa. Czwarty pobiera wiek i rasę. Piąty ma najwięcej argumentów - wiek, waga i rasa. Zauważcie, że konstruktor klasy Pies wywołuje konstruktor klasy Ssak. W części inicjalizującej Pies inicjalizuje swoją klasę bazową, przekazując argument inicjalizuje zmienną mówiącą o rasie Psa. Dodatkowo inicjalizowana jest zmienna wewnętrzna jegoWaga. Zauważcie, ż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 jegoWaga, dlatego inicjalizację trzeba przeprowadzić w treści konstruktora.
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 także możliwość nadpisywania funkcji bazowej. Nadpisywanie oznacza zmianę implementacji funkcji z klasy bazowej. Kiedy wywołujemy 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ą argumentów co jakaś funkcja w klasie bazowej i definiuje jej nową implementację to mówimy o nadpisywaniu tej funkcji (metody). Kiedy chcemy napisać funkcję, musimy zapewnić zgodność typu wartości zwracanej, nazwy i listy argumentów z funkcją klasy bazowej. Napiszemy program ilustrujący, co się stanie, gdy klasa Pies nadpisze metodę Mow() klasy Ssak. Dla skrócenia programu pominiemy funkcje dostępu.
#include <iostream.h>
enum Rasa {Wilczur, Colcie, Jamnik, Chart, Spaniel, Kaukaz};
class Ssak
{
public:
Ssak () {cout<<”Konstruktor Ssaka.\n”};
~Ssak () {cout<<”Destruktor Ssaka.\n”};
void Mow () const {cout<<”Odgłos Ssaka!\n”;};
void Sleep () {cout<<”Cicho! Ja teraz śpie.\n”;};
protected:
int jegoWiek;
int jegoWaga;
};
class Pies : public Ssak
{
public:
Pies ();
~Pies ();
void MachajOgonem () {cout<<”Machanie ogonem.\n;};
void Mow() const {cout<<”Hau!\n”;}
void ProsOJedzenie () {cout<<”Prośba o jedzenie.\n;};
private:
Rasa jegoRasa;
};
int main()
{
Ssak duzeZwierze;
Pies Pluto;
duzeZwierze.Mow();
Pluto.Mow();
return 0;
}
Efekt działania:
Konstruktor Ssaka.
Konstruktor Ssaka.
Konstruktor Psa.
Odgłos Ssaka.
Hau!
Destruktor Psa.
Destruktor Ssaka.
Destruktor Ssaka.
W programie tym w klasie Pies napisaliśmy metodę Mów() tak, każdy obiekt klasy Pies szczekał (wypisywał na ekranie Hau!) w momencie wywołania metody Mow(). W programie głównym tworzymy obiekt klasy Ssak o nazwie duzeZwierze. Następnie obiekt klasy Pies. Dlatego wywołane są dwa konstruktory Ssaka. Jeden dla „duzeZwierze”, drugi dla „Pies”. W kolejnej linii ssak wywołuje swoją metodę Mow(), natomiast Pies - swoją metodę Mow().
PRZECIĄŻENIE CZY NADPISANIE.
Obie metody dają podobne efekty. Kiedy przeciążamy metodę to tworzymy kilka różnych metod o tej samej nazwie, o różnej wartości zwracanej i liczbie argumentów (sygnaturze). Kiedy nadpisujemy metodę to tworzymy w klasie pochodnej metodę, która zastępuje metodę z klasy bazowej.
UKRYWANIE METOD KLASY BAZOWEJ.
W ostatnim programie, metoda klasy Pies o nazwie Mow() ukryła metodę klasy bazowej. Oto 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śli zaś Ssak miałaby trzy metody przeciążające funkcję Ruch() jedną nie pobierającą argumentów, drugą pobierającą wartość całkowitą i trzecią dwuargumentową i jeśli klasa Pies napisałaby metodę Ruch() funkcją nie pobierającą argumentów to dostęp do pozostałych dwóch metod z klasy Ssak byłby bardzo utrudniony. Przeanalizujmy program:
#include <iostream.h>
enum Rasa {Wilczur, Colcie, Jamnik, Chart, Spaniel, Kaukaz};
class Ssak
{
public:
void Ruch () const {cout<<”Ssak przeszedł jeden krok.\n”;};
void Ruch (int distance) {cout<<”Ssak przeszedł <<distance<< kroki.\n”;};
protected:
int jegoWiek;
int jegoWaga;
};
class Pies : public Ssak
{
public:
void Ruch () const {cout<<”Pies przeszedł 5 kroków.\n;};
// Może pojawi się ostrzeżenie o ukrywaniu metody.
int main ()
{
Ssak duzeZwierze;
Pies Pluto;
duzeZwierze.Ruch();
duzeZwierze.Ruch(2);
Pluto.Mow();
//Pluto.Ruch (10);
return 0;
}
Efekt działania:
Ssak przeszedł jeden krok.
Ssak przeszedł 2 kroki.
Ssak przeszedł 5 kroków.
W programie usunęliśmy wszystkie chwilowo niepotrzebne metody. W deklaracji klasy Ssak jest przeciążona metodą Ruch (bez argumentu i z jednym argumentem). W deklaracji klasy Pies nadpisana jest metoda Ruch() bez argumentów. W programie głównym wywołane są odpowiednie metody Ruch(). Gdyby została wywołana linia Pluto.Ruch (10) wystąpiłby błąd kompilacji. Jeśli klasa pies nie napisałaby metody Ruch() to mogłaby wywoływać metodę Ruch (int). Teraz jeśli chcielibyśmy wykorzystać metodę z argumentem musielibyśmy również ją nadpisać. Zwróćcie uwagę na analogię z konstruktorami. Jeżeli stworzysz jakikolwiek konstruktor to kompilator nie będzie już zapewniał konstruktora domyślnego. 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śli napisaliście 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:
Ssak :: Ruch ()
Możliwa jest również pewna ciekawa modyfikacja linii Pluto.Ruch (10) z ostatniego programu:
Pluto.Ssak :: Ruch (10);
Tak wygląda bezpośrednie wywołanie metody klasy Ssak. Spójrzmy na przykład:
#include <iostream.h>
enum Rasa {Wilczur, Colcie, Jamnik, Chart, Spaniel, Kaukaz};
class Ssak
{
public:
void Ruch () const {cout<<”Ssak przeszedł jeden krok.\n”;};
void Ruch (int distance) {cout<<”Ssak przeszedł <<distance<< kroki.\n”;};
protected:
int jegoWiek;
int jegoWaga;
};
class Pies : public Ssak
{
public:
void Ruch () const;
};
void Pies :: Ruch() const
{
cout<<”Ruch psa.\n”;
Ssak :: Ruch (3);
}
int main ()
{
Ssak duzeZwierze;
Pies Pluto;
duzeZwierze.Ruch (2);
duzeZwierze.Ruch (4);
return 0;
}
Efekt działania:
Ssak przeszedł 2 kroki.
Ssak przeszedł 4 kroki.
W programie głównym tworzony jest obiekt Ssak o nazwie duzeZwierze i Pluto z klasy Pies. Wywoływana jest również metoda Ruch(). Z klasy Ssak, pobierająca wartość całkowitą. Programista chce wywołać metodę Ruch() z obiektu Pluto 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) tak, jak jest to wykonane w programie głównym czyli:
Pluto.Ssak :: Ruch(4);
Zwierze
Ssak
Gad
Pies
Koń
Myśliwski
Pasterski
Owczarek Collie
Owczarek Niemiecki