r12.pdf

(334 KB) Pobierz
Szablon dla tlumaczy
Rozdział 12.
Dziedziczenie
W poprzednim rozdziale poznałeś wiele relacji związanych z projektowaniem obiektowym, m.in.
relację specjalizacji/generalizacji. Język C++ implementuje ją poprzez dziedziczenie.
Z tego rozdziału dowiesz się:
czym jest dziedziczenie,
w jaki sposób wyprowadzać klasę z innej klasy,
czym jest dostęp chroniony i jak z niego korzystać,
czym są funkcje wirtualne.
Czym jest dziedziczenie?
Czym jest pies? Co widzisz, gdy patrzysz na swoje zwierzę? Ja widzę cztery łapy i pysk. Biolodzy
widzą sieć interesujących organów, fizycy — atomy i różnorodne siły, zaś taksonom widzi
przedstawiciela gatunku canine domesticus .
Skupmy się na tym ostatnim przypadku. Pies jest przedstawicielem psowatych, psowate są
przedstawicielami ssaków, i tak dalej. Taksonomowie dzielą świat żywych stworzeń na królestwa,
typy, klasy, rzędy, rodziny, rodzaje i gatunki.
Hierarchia specjalizacji/generalizacji ustanawia relację typu jest-czymś . Homo sapiens jest
przedstawicielem naczelnych. Taką relację widzimy wszędzie: wóz kempingowy jest rodzajem
samochodu, który z kolei jest rodzajem pojazdu. Budyń jest rodzajem deseru, który jest rodzajem
pożywienia.
Gdy mówimy, że coś jest rodzajem czegoś innego, zakładamym że stanowi to specjalizację tej
rzeczy. Samochód jest zatemspecjalnym rodzajem pojazdu.
Dziedziczenie i wyprowadzanie
Pies dziedziczy — to jest, automatycznie otrzymuje — wszystkie cechy ssaka. Ponieważ jest
ssakiem, porusza się i oddycha powietrzem. Wszystkie ssaki, z definicji, poruszają się i oddychają.
Pies wzbogaca te elementy o cechy takie jak, szczekanie, machanie ogonem, zjadanie dopiero co
ukończonego rozdziału mojej książki, warczenie, gdy próbuję zasnąć... Przepraszam. Gdzie
skończyłem? A, wiem:
Psy możemy podzielić na psy przeznaczone do pracy, psy do sportów i teriery, zaś psy do sportów
możemy podzielić na psy myśliwskie, spaniele i tak dalej. Można także dokonywać dalszego
podziału, na przykład psy myśliwskie można podzielić na labradory czy goldeny.
Golden jest rodzajem psa myśliwskiego, który jest psem do sportów, należącego do rodzaju psów,
czyli będącego ssakiem, a więc zwierzęciem, czyli rzeczą żywą. Tę hierarchię przedstawia rysunek
12.1.
Rys. 12.1. Hierarchia zwierząt
C++ próbuje reprezentować te relacje, umożliwiając nam definiowanie klas, które są
wyprowadzane z innych klas. Wyprowadzanie jest sposobem wyrażenia relacji typu jest-czymś .
Nową klasę, Dog (pies), można wyprowadzić z klasy Mammal (ssak). Nie musimy wtedy wyraźnie
mówić, że pies porusza się, gdyż dziedziczy tę cechę od ssaków.
Klasa dodająca nowe właściwości do istniejącej już klasy jest wyprowadzona z klasy pierwotnej.
Ta pierwotna klasa jest nazywana klasą bazową .
2971137.001.png 2971137.002.png
Jeśli klasa Dog jest wyprowadzona z klasy Mammal , oznacza to, że klasa Mammal jest klasą bazową
(nadrzędną) klasy Dog . Klasy wyprowadzone (pochodne) stanową nadzbiór swoich klas
bazowych. Pies przejmuje swoje cechy od ssaków, tak klasa Dog przejmie pewne metody lub dane
klasy Mammal .
Zwykle klasa bazowa posiada więcej niż jedną klasę pochodną. Ponieważ zarówno psy, jak i koty
oraz konie są ssakami, więc ich zostały były wyprowadzone z klasy Mammal .
Królestwo zwierząt
Aby ułatwić przedstawienie procesu dziedziczenia i wyprowadzania, w tym rozdziale skupimy się
na związkach pomiędzy różnymi klasami reprezentującymi zwierzęta. Możesz sobie wyobrazić, że
bawimy się w dziecięcą grę — symulację farmy.
Opracujemy cały zestaw zwierząt, obejmujący konie, krowy, psy, koty, owce, itd. Stworzymy
metody dla tych klas, aby zwierzęta mogły funkcjonować tak, jak oczekiwałoby tego dziecko, ale
na razie każdą z tych metod zastąpimy zwykłą instrukcją wydruku.
Minimalizowanie funkcji (czyli pozostawienie tylko jej szkieletu) oznacza, że napiszemy tylko
tyle kodu, ile wystarczy do pokazania, że funkcja została wywołana. Szczegóły pozostawimy na
później, gdy będziemy mieć więcej czasu. Jeśli tylko masz ochotę, możesz wzbogacić minimalny
kod zaprezentowany w tym rozdziale i sprawić, by zwierzęta zachowywały się bardziej
realistycznie.
Składnia wyprowadzania
Gdy deklarujesz klasę, możesz wskazać klasę, od której pochodzi, zapisując po nazwie tworzonej
klasy dwukropek, rodzaj wyprowadzania (publiczny lub inny) oraz klasę bazową. Oto przykład:
class Dog : public Mammal
Rodzaj wyprowadzania opiszemy w dalszej części rozdziału. Na razie będziemy używać
wyprowadzania publicznego (oznaczonego słowem kluczowym public ). Klasa bazowa musi być
zdefiniowana wcześniej, gdyż w przeciwnym razie kompilator zgłosi błąd. Listing 12.1 ilustruje
sposób deklarowania klasy Dog , wyprowadzonej z klasy Mammal .
Listing 12.1. Proste dziedziczenie
0: //Listing 12.1 Proste dziedziczenie
1:
2: #include <iostream>
3: using namespace std;
4:
5: enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };
6:
7: class Mammal
8: {
9: public:
10: // konstruktory
11: Mammal();
12: ~Mammal();
13:
14: //akcesory
15: int GetAge() const;
16: void SetAge(int);
17: int GetWeight() const;
18: void SetWeight();
19:
20: //inne metody
21: void Speak() const;
22: void Sleep() const;
23:
24:
25: protected:
26: int itsAge;
27: int itsWeight;
28: };
29:
30: class Dog : public Mammal
31: {
32: public:
33:
34: // konstruktory
35: Dog();
36: ~Dog();
37:
38: // akcesory
39: BREED GetBreed() const;
40: void SetBreed(BREED);
41:
42: // inne metody
43: WagTail();
44: BegForFood();
45:
46: protected:
47: BREED itsBreed;
48: };
Ten kod nie wyświetla wyników, gdyż zawiera jedynie zestaw deklaracji klas (bez ich
implementacji). Mimo to można w nim wiele zobaczyć.
Analiza
W liniach od 7. do 28. deklarowana jest klasa Mammal (ssak). Zwróć uwagę, że w tym przykładzie
klasa Mammal nie jest wyprowadzana z żadnej innej klasy. W realnym świecie ssaki pochodzą od
(to znaczy, że są rodzajem) zwierząt. W programie C++ możesz zaprezentować jedynie część
informacji, które posiadasz na temat danego obiektu. Rzeczywistość jest zdecydowanie zbyt
skomplikowana, aby uchwycić ją w całości, więc każda hierarchia w C++ jest umowną
reprezentacją dostępnych danych. Sztuka dobrego projektowania polega na reprezentowaniu
interesujących nas obszarów tak, aby reprezentowały rzeczywistość w możliwie najlepszy sposób.
Hierarchia musi się gdzieś zaczynać; w tym programie rozpoczyna się od klasy Mammal . Z
powodu tego, pewne zmienne składowe, które mogły należeć do wyższej klasy, nie są tu
reprezentowane. Wszystkie zwierzęta z pewnością posiadają na przykład wagę i wiek, więc jeśli
klasa Mammal byłaby wyprowadzona z klasy Animal (zwierzę), moglibyśmy oczekiwać, że
dziedziczy te atrybuty. Jednak w naszym przykładzie atrybuty te występują w klasie Mammal .
Aby zachować niewielką i spójną postać programu, w klasie Mammal zostało umieszczonych
jedynie sześć metod — cztery akcesory oraz metody Speak() (mówienie) i Sleep() (spanie).
Klasa Dog (pies) dziedziczy po klasie Mammal , co wskazuje linia 30. Każdy obiekt typu Dog
będzie posiadał trzy zmienne składowe: itsAge (wiek), itsWeight (waga) oraz itsBread
(rasa). Zwróć uwagę, że deklaracja klasy Dog nie obejmuje zmiennych składowych itsAge oraz
itsWeight . Obiekty klasy Dog dziedziczą te zmienne od klasy Mammal , razem z metodami tej
klasy (z wyjątkiem operatora kopiującego oraz destruktora i konstruktora).
Prywatne kontra chronione
Być może w liniach 25. i 46. listingu 12.1 zauważyłeś nowe słowo kluczowe dostępu, protected
(chronione). Wcześniej dane klasy były deklarowane jako prywatne. Jednak prywatne składowe
nie są dostępne dla klas pochodnych. Moglibyśmy uczynić zmienne itsAge i itsWeight
składowymi publicznymi, ale nie byłoby to pożądane. Nie chcemy, by inne klasy mogły
bezpośrednio odwoływać się do tych danych składowych.
UWAGA Istnieje powód, by wszystkie dane składowe klasy oznaczać jako prywatne, a nie jako
chronione. Powód ten przedstawił Stroustrup (twórca języka C++) w swojej książce The Design
and Evolution of C++ , ISBN 0-201-543330-3, Addison Wesley, 1994. Metody chronione nie są
jednak uważane za kłopotliwe i mogą być bardzo użyteczne.
Potrzebujemy teraz oznaczenia, które mówi: „Uczyń te zmienne widocznymi dla tej klasy i dla
klas z niej wyprowadzonych”. Takim oznaczeniem jest właśnie słowo kluczowe protected
chroniony. Chronione funkcje i dane składowe są w pełni widoczne dla klas pochodnych i nie są
dostępne dla innych klas.
Istnieją trzy specyfikatory dostępu: publiczny ( public ), chroniony ( protected ) oraz private
(prywatny). Jeśli funkcja posiada obiekt twojej klasy, może odwoływać się do wszystkich jej
publicznych funkcji i danych składowych. Z kolei funkcje składowe klasy mogą odwoływać się do
wszystkich prywatnych funkcji i danych składowych swojej własnej klasy, oraz do wszystkich
chronionych funkcji i danych składowych wszystkich klas, z których ich klasa jest wyprowadzona.
Tak więc, funkcja Dog::WagTail() (macha ogonem) może odwoływać się do prywatnej danej
itsBreed oraz do prywatnych danych klasy Mammal .
Nawet gdyby pomiędzy klasą Mammal , a klasą Dog występowały inne klasy (na przykład
DomesticAnimals — zwierzęta domowe), klasa Dog w dalszym ciągu mogłaby się odwoływać
do prywatnych składowych klasy Mammal , zakładając że wszystkie inne klasy używałyby
dziedziczenia publicznego. Dziedziczenie prywatne zostanie omówione w rozdziale 16.,
„Dziedziczenie zaawansowane”.
Listing 12.2 przedstawia sposób tworzenia obiektów typu Dog oraz dostępu do danych i funkcji
zawartych w tym typie.
Listing 12.2. Użycie klasy wyprowadzonej
0: //Listing 12.2 Użycie klasy wyprowadzonej
1:
2: #include <iostream>
Zgłoś jeśli naruszono regulamin