Pierwsze spojrzenie
Omawiając cechy programowania obiektowego na wykładzie pierwszym, powiedzieliśmy, że pozwala ono w łatwy sposób w oparciu o już istniejące obiekty tworzyć nowe, które będą rozszerzeniem już istniejących. Powróćmy do przedstawionego tam przykładu z pojazdem...
#include <stdio.h>
class pojazd
{
public:
float predkosc;
float polozenie;
// dla uproszczenia zakladamy, ze poruszamy sie
// w przestrzeni jednowymiarowej
pojazd();
~pojazd();
void Stoj();
void PoruszajSie();
void Wypisz();
};
class pojazdCzterokolowy : public pojazd
{
public:
int iloscDobrychKol;
pojazdCzterokolowy();
~pojazdCzterokolowy();
void SkrecWLewo();
void SkrecWPrawo();
void Wypisz();
};
pojazd::pojazd()
{
printf("Konstruktor pojazdu\n");
predkosc=2.0;
polozenie=7.0;
}
pojazd::~pojazd()
{
printf("Destruktor pojazdu\n");
}
void pojazd::Stoj()
{
printf("Zatrzymuje pojazd...\n");
}
void pojazd::PoruszajSie()
{
printf("Ruszam pojazdem...\n");
}
void pojazd::Wypisz()
{
printf("Wypisz (pojazd): predkosc %f, polozenie %f\n",predkosc,polozenie);
}
pojazdCzterokolowy::pojazdCzterokolowy()
{
printf("Konstruktor pojazduCzterokolowego\n");
iloscDobrychKol=4;
}
pojazdCzterokolowy::~pojazdCzterokolowy()
{
printf("Destruktor pojazduCzterokolowego\n");
}
void pojazdCzterokolowy::SkrecWLewo()
{
printf("Skrecam w lewo...\n");
}
void pojazdCzterokolowy::SkrecWPrawo()
{
printf("Skrecam w prawo...\n");
}
void pojazdCzterokolowy::Wypisz()
{
printf("Wypisz (pojazdCzterokolowy): predkosc %f\n",predkosc);
printf(" polozenie %f\n",polozenie);
printf(" iloscDobrychKol %d\n",iloscDobrychKol);
}
int main()
{
pojazd p;
pojazdCzterokolowy p4;
p.Wypisz();
p4.Wypisz();
p4.polozenie=-3.5;
p4.Wypisz();
p4.pojazd::Wypisz();
return 0;
}
|
|
Listing 5.1
|
Konstruktor pojazdu
Konstruktor pojazdu
Konstruktor pojazduCzterokolowego
Wypisz (pojazd): predkosc 2.000000, polozenie 7.000000
Wypisz (pojazdCzterokolowy): predkosc 2.000000
polozenie 7.000000
iloscDobrychKol 4
Wypisz (pojazdCzterokolowy): predkosc 2.000000
polozenie -3.500000
iloscDobrychKol 4
Wypisz (pojazd): predkosc 2.000000, polozenie -3.500000
Destruktor pojazduCzterokolowego
Destruktor pojazdu
Destruktor pojazdu
|
|
Output 5.1
Efekt działania programu z listingu 5.1
|
Jak zatem widać w klasie pochodnej (
pojazdCzterokolowy) od klasy podstawowej
pojazd możemy
- zdefiniować dodatkowe pola (iloscDobrychKol);
- zdefiniować dodatkowe metody (SkrecWLewo, SkrecWPrawo);
- zdefiniować pole lub metode, która w klasie podstawowej już istnieje (Wypisz).
Kolejność wywołania konstruktorów i destruktorów
Ponieważ obiekt potomny zawiera niejako w sobie obiekt klasy podstawowej dlatego chyba naturalna jest kolejność wywołania konstruktorów jaką mogliśmy zaobserwować w ostatnim przykładzie. Weżmy jednak dodatkowo pod uwagę, że pewna klasa może zawierać składniki będące obiektami innej klasy. Wówczas kolejność wywołania konstruktorów będzie nastęująca
- Konstruktor obiektu będącego składnikiem klasy podstawowej.
- Konstruktor klasy podstawowej.
- Konstruktor obiektu będącego składnikiem klasy pochodnej.
- Konstruktor klasy pochodnej.
Przypatrzmy się poniższemu kodowi
#include <stdio.h>
class pomocnicza1
{
public:
pomocnicza1()
{
printf("Konstruktor klasy pomocnicza1\n");
}
~pomocnicza1()
{
printf("Destruktor klasy pomocnicza1\n");
}
};
class pomocnicza2
{
public:
pomocnicza2()
{
printf("Konstruktor klasy pomocnicza2\n");
}
~pomocnicza2()
{
printf("Destruktor klasy pomocnicza2\n");
}
};
class podstawowa
{
public:
pomocnicza1 p;
podstawowa()
{
printf("Konstruktor klasy podstawowa\n");
}
~podstawowa()
{
printf("Destruktor klasy podstawowa\n");
}
};
class pochodna : public podstawowa
{
public:
pomocnicza2 p;
pochodna()
{
printf("Konstruktor klasy pochodna\n");
}
~pochodna()
{
printf("Destruktor klasy pochodna\n");
}
};
int main()
{
pochodna p;
return 0;
}
|
|
Listing 5.2
|
i efektowi jego wywołania
Konstruktor klasy pomocnicza1
Konstruktor klasy podstawowa
Konstruktor klasy pomocnicza2
Konstruktor klasy pochodna
Destruktor klasy pochodna
Destruktor klasy pomocnicza2
Destruktor klasy podstawowa
Destruktor klasy pomocnicza1
|
|
Output 5.2
Efekt działania programu z listingu 5.2
|
Jak widać konstruktor klasy pochodnej jest zwykłym konstruktorem. Dodatkowo jednak na jego liście inicjalizacyjnej można umieścić wywołanie konstruktora klasy podstawowej. Jeśli na liście inicjalizacyjnej nie umieścimy żadnego wywołania konstruktora wówczas wywołany będzie automatycznie konstruktor domniemany. Wywołanie konstruktora na liście inicjalizacyjej dla klasy podstawowej można więc pominąc jeśli
- klasa podstawowa nie ma żadnego konstruktora;
- wśród zdefiniowanych konstruktorów jest konstruktor domyślny.
Dostęp do składników
Klasa podstawowa przez odpowiednie użycie etykiet sterujących dostępem może decydować, które składniki będą dostępne w klasie pochodnej i na jakich zasadach.
- Składniki public dostępne są bezpośrednio w zakresie klasy pochodnej.
- Składniki private nie są dostępne w zakresie klasy pochodnej.
- Składniki protected dla bezpośrednich użytkowników zachowują się jak private natomiast w zakresie klasy pochodnej jak public.
#include <stdio.h>
class podstawowa
{
public:
int x;
private:
int y;
protected:
int z;
public:
podstawowa(){x=2;y=4;z=6;}
int GetX(){return x;};
int GetY(){return y;};
int GetZ(){return z;};
};
class pochodna : public podstawowa
{
public:
int a;
pochodna(){a=3;};
void Wypisz1();
void Wypisz2();
};
void pochodna::Wypisz1()
{
printf("Metoda Wypisz1 wypisze wszystko do czego ma swobodny dostep\n");
printf("z wnetrza klasy pochodna...\n");
printf("x=%d y=? z=%d a=%d\n",x,z,a);
}
void pochodna::Wypisz2()
{
printf("Metoda Wypisz2 wypisze wszystko do czego ma dostep\n");
printf("swobodny i/lub w oparciu o metody z klasy podstawowej\n");
printf("x=%d y=%d z=%d a=%d\n",GetX(),GetY(),GetZ(),a);
}
int main()
{
pochodna p;
printf("Zawartosc klasy pochodna\n");
printf("Najpierw brutalna sila...(czyli tam gdzie mozna bez metod)\n");
printf("Niestety mozna tylko...\n");
printf("x=%d y=? z=? a=%d\n",p.x,p.a);
printf("Teraz skorzystamy z metod...\n");
p.Wypisz1();
p.Wypisz2();
return 0;
}
|
|
Listing 5.3
|
Zawartosc klasy pochodna
Najpierw brutalna sila...(czyli tam gdzie mozna bez metod)
Niestety mozna tylko...
x=2 y=? z=? a=3
Teraz skorzystamy z metod...
Metoda Wypisz1 wypisze wszystko do czego ma swobodny dostep
z wnetrza klasy pochodna...
x=2 y=? z=6 a=3
Metoda Wypisz2 wypisze wszystko do czego ma dostep
swobodny i/lub w oparciu o metody z klasy podstawowej
x=2 y=4 z=6 a=3
|
|
Output 5.3
Efekt działania programu z listingu 5.3
|
O sposobie dziedziczenia może decydować także klasa pochodna; dotyczy to tylko składników nieprywatnych. Dokonujemy tego pisząc
class nazwaKlasyPochodnej : sposób nazwaKlasyPodstawowej
{
// ...
};
gdzie sposób może przyjąć wartość: public, private, protected
- Dziedziczenie "public" - składniki public i protected dostępne są w klasie pochodnej jako odpowiednio public i protected.
- Dziedziczenie "private" - składniki public i protected uznawane są za private w klasie pochodnej.
- Dziedziczenie "protected" - składniki public i protected uznawane są za protected w klasie pochodnej.
Reguły rządzące dziedziczeniem są raczej jasne i logiczne, ale na wszelki wypadek w poniższej tabelce zebrano je wszystkie razem
Atrybut dostępu w klasie podstawowej |
Dostępność na zewnątrz klasy |
Sposób dziedziczenia |
Dostępność w klasie dziedziczącej |
Dostępność na zewnątrz klasy dzidziczącej |
public | tak | public | tak | tak |
| | private | tak | nie |
| | protected | tak | nie |
private | nie | public | nie | nie |
| | private | nie | nie |
| | protected | nie | nie |
protected | nie | public | tak | nie |
| | private | tak | nie |
| | protected | tak | nie |
Istnieje również sposób na udostępnienie tylko niektórych składowych klasy podstawowej. Należy mianowicie odziedziczyć klasę podstawową prywatnie natomiast przed nazwami składników które mimo to mają być
public lub
protected umieścić odpowiednią etykiete:
class podstawowa
{
public:
int x1;
int x2
protected:
int y1;
int y2
};
class pochodna : private podstawowa
{
public:
podstawowa::x1;
protected:
podstawowa::y1;
}
|
|
Listing 5.4
|
Należy mieć na uwadze, że taka deklaracja dostępu może jedynie powtórzyć dostęp jaki nazwa miała w klasie podstawowej. Deklaracja dostępu nie może dostępu ani zaostrzyć ani rozluźnić.
Nie wszystko się dziedziczy
Dziedziczeniu nie podlegają:
- konstruktory,
- operator przypisania,
- destruktor.
Przyczyny dla których tak się dzieje są chyba oczywiste, więc je pominę :))