Download as pdf or txt
Download as pdf or txt
You are on page 1of 6

Opracowanie: dr inż. Łukasz Maliński i mgr inż.

Błażej Nycz, Politechnika Śląska, 2024

LABORATORIUM 7
DZIEDZICZENIE I POLIMORFIZM
Poruszane zagadnienia z dziedziny programowania:

• Składania i działanie dziedziczenia w C++ - „Opus Magnum C++11”, wyk. 7,


• Dziedziczenie wielopoziomowe - „Opus Magnum C++11”, wyk. 7,
• Klasa abstrakcyjna - „Opus Magnum C++11”, wyk. 7,
• Polimorfizm obiektowy dynamiczny - „Opus Magnum C++11”, wyk. 7,

Umiejętności do opanowania:
• stosowanie dziedziczenia do wyodrębnienia części wspólnej kilku podobnych klas lub do roz-
winięcia istniejącej już klasy o nowe funkcjonalności,
• implementacja zachowania polimorficznego w klasach pochodnych,
• tworzenie klasy abstrakcyjnej.
• stosowanie niepublicznych typów dziedziczenia do zmiany interfejsu klasy bazowej.

Oznaczenia odnośnie do samodzielności pracy:


■ – Ten problem koniecznie rozwiąż w pełni samodzielnie. Możesz posługiwać się
literaturą, wykładami i dokumentacją w celu sprawdzenia niuansów składniowych, ale
nie szukaj gotowych rozwiązań i algorytmów. Koniecznie powinieneś rozumieć prze-
znaczenie i działanie każdej linii kodu w tym rozwiązaniu. Jest to bardzo ważne z
punktu opanowania podstawowych technik programowania i oszukiwanie przyniesie tyl-
ko poważne braki w późniejszych etapach nauki!
▲ – Rozwiązując ten problem możesz wzorować się gotowych rozwiązaniach innych pro-
gramistów. Powinieneś jednak dobrze rozumieć rozwiązanie, na którym się wzorujesz,
gdyż inaczej wiele się nie nauczysz!
● – Rozwianie tego problemu polega na znalezieniu i zaadoptowaniu rozwiązania.
Wystarczy, jeśli wykażesz się użytkowym stopniem zrozumienia znalezionego rozwią-
zania, gdyż nie musi być ono trywialne.
Przykładowe zadania do rozwiązania w ramach samodzielnej nauki (nie oceniane):
Zadanie A (z rozwiązaniem):
■ Klasę Osoba z wykładu 7 rozbuduj (dziedziczenie chronione) do klasy Pracownik.
Nowa klasa powinna dawać dostęp publiczny do geterów klasy Osoba.
Wskazówki: Zwróć uwagę, że przy dziedziczeniu chronionym cały interfejs klasy oso-
ba zostanie odziedziczony do sekcji chronionej klasy Pracownik. Aby utrzymać do-
stęp publiczny do geterów, trzeba dla nich utworzyć wyjątki.
■ Nowa klasa powinna dodawać informację o wynagrodzeniu i stażu pracy (dodaj pola
i akcesory uwzgledniające ograniczenia – nieujemne wartości nowych pól).
Wskazówki: Zauważ, że to polecenie sprowadza się do prostego zdefiniowania pól
i akcesorów do nich. W klasie Pracownik nie trzeba definiować składników klasy
Osoba, gdyż zostaną po niej odziedziczone.
Przykładowe rozwiązanie:
#include "Osoba.h"
class Pracownik : protected Osoba
{
private:
double m_wynagrodzenie = 0;
double m_staz = 0;

public:
using Osoba::getWiek;
Opracowanie: dr inż. Łukasz Maliński i mgr inż. Błażej Nycz, Politechnika Śląska, 2024

double getWynagrodzenie() const


{
return m_wynagrodzenie;
}
double getStaz() const
{
return m_staz;
}
void setWynagrodzenie(int i_wynagrodzenie)
{
m_wynagrodzenie = (i_wynagrodzenie > 0) ? i_wynagrodzenie : 0;
}
void setStaz(int i_staz)
{
m_staz = (i_staz > 0) ? i_staz : 0;
}
};
Zadanie B (z rozwiązaniem):
■ Rozbuduj przykład ze slajdu nr 8 (wykład 7) następująco:
1) dodaj dodatkową klasę Gość pochodną od klasy Osoba, działającą podobnie od
podstałych klas przykładu.
Wskazówki: Jeśli w zdaniu wyraźnie nie podano sposobu dziedziczenia to stosuj
dziedziczenie publiczne. Składniki klasy (metodę i destruktor zdefiniuj analo-
gicznie do innych klas w przykładzie.
2) dodaj dodatkową klasę Absolwent, która będzie pochodną od klasy
Student i działać podobnie do innych klas przykładu.
Wskazówki: Jeśli w zdaniu wyraźnie nie podano sposobu dziedziczenia to stosuj
dziedziczenie publiczne. Składniki klasy (metodę i destruktor zdefiniuj analo-
gicznie do innych klas w przykładzie.
3) Zastąp funkcję main programem, który w jednej tablicy zgromadzi dynamicznie
tworzone, instancje: Osoby, Studenta, Absolwenta, Pracownika i Gościa, po
jednej, w dowolnej kolejności. Wszystkie instancje klas pochodnych utwórz dy-
namicznie i zadbaj, aby były poprawnie usuwane (wywołały się zarówno destruk-
tory klas bazowych i pochodnych przy operacji delete).
Wskazówki: Zauważ, że nie da się zdefiniować tablicy, która zawiera instancje
rożnych typów, ale można utworzyć tablice wskaźników do typu bazowego i do jej
elementów poprzepisywać dynamicznie tworzone instancje klas pochodnych. Ponie-
waż wskaźniki, które będą utrzymywać kontakt z instancjami są do typu Osoba,
destruktory tych klas muszą zachowywać się polimorficznie.
Przykładowe rozwiązanie:
#include<iostream>
using namespace std;

struct Liczebnosci
{
int Studenci = 0, Absolwenci = 0, Pracownicy = 0, Goscie = 0;
};

class Osoba
{
public:
virtual void wizytowka() { cout << "Osoba\n"; };
virtual ~Osoba() { cout << "Destruktor osoby\n"; }
};

class Student : public Osoba


{
public:
Opracowanie: dr inż. Łukasz Maliński i mgr inż. Błażej Nycz, Politechnika Śląska, 2024

void wizytowka() { cout << "Student\n"; }


~Student() { cout << "Destruktor studenta\n"; }
};

class Pracownik : public Osoba


{
public:
void wizytowka() { cout << "Pracownik\n"; }
~Pracownik() { cout << "Destruktor pracownika\n"; }
};

class Gosc : public Osoba


{
public:
void wizytowka() { cout << "Gosc\n"; }
~Gosc() { cout << "Destruktor goscia\n"; }
};

class Absolwent : public Student


{
public:
void wizytowka() { cout << "Absolwent\n"; }
~Absolwent() { cout << "Destruktor absolwenta\n"; }
};

void main()
{
//Tworzenie tablicy:
constexpr size_t liczebnoscGrupy = 5;
Osoba** osoby = new Osoba * [liczebnoscGrupy] {
new Osoba(), new Student(), new Pracownik(), new Gosc(), new Absolwent() };

//Zwalnianie pamięci:
Osoba* osoba;
for (size_t index = 0; index < liczebnoscGrupy; index++)
{
osoba = osoby[index];
delete osoba;
}
delete[]osoby;
}
Zadanie domowe [1,0 pkt.]
■ Napisz klasę Dodatek pochodną od klasy Gra Planszowa z pliku „klasy.h”. Zastosuj
dziedziczenie prywatne, ale udostępnij publicznie z klasy bazowej akcesory do pól:
tytuł i ocena graczy. Uwaga: jeśli w trakcie realizacji zadania okaże się koniecz-
ne zdefiniowanie w klasie bazowej brakujących geterów, to należy to zrobić.
Wskazówki: Zauważ, że na tym etapie w zasadzie tworzymy pustą klasę, w której tyl-
ko należy zdefiniować reguły i wyjątki dziedziczenia. Jeśli klasa pochodna musi
mieć getery publiczne do pewnych pól, to napisz je w klasie bazowej i odziedzicz.
■ Klasa Dodatek powinna zawierać wskazanie Gry Planszowej, do której jest rozsze-
rzeniem. Daj możliwość ustawienia gry macierzystej zarówno na etapie tworzenia
instancji jak i w trakcie jej istnienia, oraz możliwość odczytu tej informacji.
Wskazówki: W tym poleceniu trzeba dodać jedno pole, które pozwoli wskazać instan-
cję Gry Planszowej, oraz dodać do niej akcesory. Zwróć uwagę, że w poleceniu wspo-
minano o tworzeniu nowej instancji, więc należy także dodać właściwy do tego celu
składnik funkcjonalny i to zarówno w klasie pochodnej jak i bazowej.
■ Zdefiniuj konstruktory argumentowe dla klasy bazowej i pochodnej tak aby pozwa-
lały ustawić wszystkie informacje w klasach do których należą. Wyklucz konstrukto-
ry domyślne dla obu klas.
Opracowanie: dr inż. Łukasz Maliński i mgr inż. Błażej Nycz, Politechnika Śląska, 2024

Wskazówki: Pamiętaj o wywołaniu właściwego konstruktora klasy bazowej w konstruk-


torze klasy pochodnej.
■ Dodaj do obu klas funkcjonalność polimorficzną „opis”, która zwróci do kontekstu
zewnętrznego (nie na ekran) łańcuch znakowy zawierający tekst opisujący zawartość
instancji według formatu:
dla Gry Planszowej: Tytuł, typ gry (liczba graczy) [czas trwania]: ocena
dla Dodatku: Tytył dodatku, tytył gry macierzystej: ocena
Koniecznie zadbaj o to, aby generowanie opisu działało polimorficznie.
Wskazówki: Dodane metody nie powinny wyświetlać nic na ekranie tylko zwracać tekst
przez return. Przypomnij sobie z poprzedniego bloku co musi być spełnione, aby
treść tego tekstu nie przestała być aktualna tuż po zakończeniu metody – tablica
znakowa musi istnieć w pamięci nawet poza metodą. Pamiętaj, że w klasie bazowej
należy dodać odpowiedni specyfikator, aby metoda ta działała polimorficznie.
■ Napisz funkcję wyświetlającą na ekranie opis Gry Planszowej/Dodatku, w zależno-
ści jak jakiego typu instancję jej przekażemy. Zademonstruj działanie tej funkcji
w programie głównym na przykładzie jednej gry planszowej i dodatku do niej.
Wskazówki: Zastanów się co należy przekazać do funkcji, aby zadziałał polimorfizm.
Zadania do samodzielnego rozwiązania w ramach nauki:
Zadanie 1:
■ Dla klas SamochodOsobowy i SamochodCiezarowy z pliku „klasy.h” wydziel klasę
bazową Pojazd, która zawierać będzie wszystkie wspólne informacje dla obu klas.
W tym celu napisz nową klasę i skopiuj do niej pola wspólne wraz z geterami i se-
terami. W klasach SamochodOsobowy i SamochodCiezarowy dodaj dziedziczenie po kla-
sie Pojazd i usuń z nich skopiowane składniki – są one teraz dziedziczone.
Bardzo ważne jest, aby nie zmieniać nazw tych składników (szczególnie składników
publicznych) aby zachować zgodność interfejsu tych klas. Trzymaj się zasady, że
jeśli nie podano, jak należy dziedziczyć, to zawsze domyślnie dziedziczymy pu-
blicznie.
■ W obu klasach pochodnych utwórz taki wyjątek od dziedziczenia, aby ustawianie
prędkości nie było dostępne w interfejsach tych klas. Klasy te powinny dawać moż-
liwość zadania prędkości przy tworzeniu instancji, ale dla klasy SamochodCiezarowy
ograniczenie prędkości maksymalnej powinno wynieść 90 (dla klasy SamochodOsobowy
ograniczenie powinno być identyczne jak w klasie bazowej.
Wskazówki: Umieść instrukcję tworzenia wyjątku w sekcji innej niż publiczna.
W klasie Samochod Osobowy, wystarczy użyć oddziedziczonego setera. W drugiej kla-
sie trzeba w konstruktorze uwzględnić dodatkowe ograniczenie.
■ Dla całej hierarchii klas: Pojazd i klas pochodnych, utwórz funkcjonalność poli-
morficzną „opłata rejestracyjna”, której zadaniem będzie wyliczenie kosztu reje-
stracji danego typu pojazdu według następujących wytycznych:
- Samochód Osobowy: koszt = 500 + 50 * liczba pasażerów + 5 * nadmiarowa pręd-
kość (nadmiarowa prędkość to każdy km/h prędkości maksymalnej powyżej 140).
- Samochód Ciężarowy: koszt = 1000 + pojemność + taryfa typu towaru (sypki –
200, ciekły – 400, paczkowany – 100).
Nie ma zasad wyliczania kosztu dla samego Pojazdu, więc zdefiniuj wersję metody
która po prostu zwraca 0.
Wskazówki: Pamiętaj, że aby metoda działała polimorficznie to w klasie bazowej
musi mieć odpowiedni specyfikator. Przy redefinicjach metod w klasach pochodnych
koniecznie stosuj specyfikator override aby upewnić się, że metody te na pewno
przesłaniają oryginalną metodę z klasy bazowej.
Opracowanie: dr inż. Łukasz Maliński i mgr inż. Błażej Nycz, Politechnika Śląska, 2024

■ W programie głównym utwórz tablicę 4 wskaźników na Pojazd i przypisz do jej ele-


mentów po 2 Samochody Osobowe i Samochody Ciężarowe. Następnie napisz krótki kod
wyświetlający koszty rejestracji wszystkich pojazdów zebranych w tej tablicy (każ-
dy koszt osobno).
Wskazówki: Przypomnij sobie w jaki sposób musimy odwołać się do obiektu, aby za-
działał polimorfizm – to odpowie na pytanie jak należy utworzyć tablicę. Wylicza-
nie kosztów dla wszystkich pojazdów to wywołanie metody polimorficznej dla każdego
z tych pojazdów.
Zadanie 2:
■ Zdefiniuj prostą klasę abstrakcyjną Figura, która zawiać będzie:
- informacje o położeniu na ekranie (dwie współrzędne typu double).
- setery i getery położenia.
- czysto wirtualne metody do wyznaczania pola powierzchni i obwodu figury.
Wskazówki: Pamiętaj, że jeśli choć jedna metoda klasy jest czysto wirtualna to nie
ma ona implementacji. Taka klasa z automatu stanie się klasą abstrakcyjną i nie
będzie zatem można tworzyć samodzielnej instancji tej klasy w programie. Dla metod
czysto wirtualnych nie definiujemy ciała i musimy ich prototyp oznaczyć w specy-
ficzny sposób. Położenie możesz przechowywać w strukturze.
■ Zdefiniuj klasy: Koło, Prostokąt i Trójkąt jako klasy pochodne od klasy figura.
Dodaj do nich minimum potrzebnej informacji koniecznej do wyliczenia dla nich pól
powierzchni i obwodów.
Wskazówki: Zastanów się co jest potrzebne, aby dało się policzyć wskazane cechy
geometryczne tych figur. Staraj się stosować opis najprostszy jak się da, ale pa-
miętaj, aby wszystkie dane były typów rzeczywistych.
■ Dla każdej klasy pochodnej zdefiniuj interfejs (setery, getery) oraz konstrukto-
ry argumentowe (jawnie wyklucz domyślne).
Wskazówki: Zwróć uwagę, aby w konstruktorach klas pochodnych (na liście inicjali-
zacyjnej) zawsze wskazywać który konstruktor klasy bazowej ma być użyty.
■ Dodaj finalną klasę Kwadrat jako pochodną od Prostokąta. Korzystając z wyjątków
od typu dziedziczenia i/lub dziedziczenia niepublicznego, tak odziedzicz interfejs
klasy bazowej, aby w klasie pochodnej nie było zbędnych metod.
Wskazówki: Zauważ, że kwadrat jest szczególnym typem prostokąta, ale do jego opisu
potrzeba mniej informacji. Jeśli zatem do ustawienia parametrów geometrycznych
prostokąta potrzebne były setery i etery obu boków, tak w kwadracie wystarczą tyl-
ko te jednego boku. To jest ważne, aby nie dało się dla kwadratu ustawiać danych,
których on nie używa, a które dostaliśmy mimowolnie z klasy bazowej.
■ Dla każdej z klas pochodnych zdefiniuj właściwe wersje metod obliczających pola
powierzchni i obwody.
Wskazówki: Koniecznie pamiętaj o specyfikatorze override, aby upewnić się, że zde-
finiowane metody na pewno przeciążaj metodę wirtualną.
■ Dla całej hierarchii klas dodaj funkcjonalność zliczania i odczytywania, ile
jest wszystkich figur w programie. Zadbaj, aby żadna figura nie była liczona wię-
cej niż jeden raz.
Wskazówki: To jest w zasadzie powtórzenie materiału z poprzedniego tematu. Ważne
jest by się zastanowić, w których konstruktorach i destruktorach należy umieścić
inkrementacje i dekrementacje licznika. Tak, potrzebny jest tylko jeden licznik,
gdyż interesuje nas liczba wszystkich figur bez podziału na ich typy szczegółowe.
Pamiętaj, że każda klasa pochodna od klasy Figura, jest figurą.
■ Napisz krótki program, w którym zdefiniuj taką tablicę 10 elementów, aby dało
się w niej przechować dowolny typ pochodny od Figury. Poprzepisuj do niej instan-
Opracowanie: dr inż. Łukasz Maliński i mgr inż. Błażej Nycz, Politechnika Śląska, 2024

cje różnych figur geometrycznych o rożnych parametrach. Na koniec policz sumarycz-


ne pole wszystkich figur zebranych w tej tablicy.
Zadanie 3:
Pracuj na projekcie Uczelnia z pliku Uczelnia.cpp.
■ W gałęzi pracowniczej hierarchii klas dodaj funkcjonalność polimorficzną związa-
ną z wyliczaniem pensji dla pracownika. Podstawa pensji dla dowolnego pracownika
powinna być obliczana jako:
100 * staż pracy + stawka za stopień naukowy PLN
Stawki za stopnie naukowe:

mgr Dr dr hab. prof.


2000 3000 4000 5000

Pracownik naukowy pewien dostawać 50 PLN dodatku za każdą publikację, a pracownik


dydaktyczny 100 PLN dodatku za każdy przedmiot, który prowadzi.
Wskazówki: Aby działał polimorfizm, trzeba dodać metody wirtualne. Zauważ, że jak
napiszesz już metodę w klasie Pracownik, to możesz ją wykorzystać definiując jej
odpowiedniki w klasach pochodnych (operator zakresu) - nie trzeba powielać jej
kodu w każdej klasie pochodnej.
■ W funkcji main napisz kod tworzący dynamicznie 2 Kierunki Studiów, 2 Katedry, 10
Studentów i po 5 Pracowników Naukowych i Dydaktycznych. Po przypisuj Studentów do
Kierunków Studiów a Pracowników do Katedr tak, aby na każdym kierunku było minimum
2 Studentów, a w każdej Katedrze było nie mniej niż 2 Pracowników. Wszystkie in-
stancje powinny zostać utworzone dynamicznie (to ważne). Napisz fragment kodu,
który zwolni wszystkie utworzone wcześniej instancje w jednej pętli.
Wskazówki: Zastanów się nad kolejnością tworzenia instancji - przyjrzyj się kon-
struktorom klas i informację jakie trzeba im przekazać. Przy zwalnianiu pamięci
posługuj się kolejnymi wskaźnikami z tablicy.
■ Napisz funkcję globalną, która obliczy koszty działania uczelni jako sumę pensji
i stypendiów wszystkich Pracowników, powiększaną o koszty dydaktyczne w kwocie 100
PLN za każdego Studenta. Funkcja ta powinna przyjmować tablicę wszystkich osób na
uczelni (utworzoną w drugim poleceniu).
Wskazówki: Wykorzystaj polimorfizm. Zauważ, że Student i zwykła Osoba nie dostają
pensji (przynajmniej na uczelni). Nie możemy zatem im takiej funkcjonalności do-
dać. Trzeba to rozwiązać inaczej, na przykład dodając inną funkcjonalność polimor-
ficzną do całej hierarchii.
■ Dla klas Osoba, Student i Pracownik dodaj funkcjonalność zliczania instancji
(dla każdej klasy oddzielnie). Następnie sprawdź czy po utworzeniu tablicy (z dru-
giego polecenia) poprawnie zostały zliczone instancje. Na koniec sprawdź czy po
usunięciu instancji wszystkie liczniki się prawidłowo wyzerowały. Jeśli nie to
popraw kod programu tak aby zadziałał polimorfizm destruktorów.
Wskazówki: Zadbaj o poprawne zaliczanie instancji. Nowy Pracownik (bez względu na
swój typ) jest zaliczany jako Pracownik i jako Osoba (zwiększamy dwa rożne liczni-
ki). Pamiętaj jednak, aby Pracownik nie powodował podwójnego zwiększenia samego
licznika Osób.

You might also like