Streszczenie
Dziedziczenie pozwala nam modelować relację jest pomiędzy dwoma obiektami. Obiekt, z którego dziedziczony jest, nazywany jest klasą nadrzędną, klasą bazową lub nadklasą. Obiekt dziedziczący nazywany jest klasą potomną, klasą pochodną lub podklasą.
Kiedy klasa pochodna dziedziczy z klasy bazowej, klasa pochodna nabywa wszystkie elementy klasy bazowej.
Kiedy tworzona jest klasa pochodna, najpierw tworzona jest część podstawowa klasy, a następnie część pochodna. Bardziej szczegółowo:
- Pamięć dla klasy pochodnej jest rezerwowana (wystarczająca zarówno dla części podstawowej, jak i pochodnej).
- Wywoływany jest odpowiedni konstruktor klasy pochodnej.
- Obiekt klasy bazowej jest najpierw konstruowany przy użyciu odpowiedniego konstruktora klasy bazowej. Jeśli nie określono konstruktora klasy bazowej, zostanie użyty konstruktor domyślny.
- Lista inicjalizacyjna klasy pochodnej inicjuje elementy klasy pochodnej.
- Wykonywana jest treść konstruktora klasy pochodnej.
- Kontrola zostaje zwrócona dzwoniącemu.
Zniszczenie następuje w odwrotnej kolejności, od klasy najbardziej pochodnej do klasy najbardziej podstawowej.
C++ ma 3 specyfikatory dostępu: publiczny, prywatny i chroniony. Specyfikator dostępu chronionego pozwala klasie, do której należy członek, przyjaciołom i klasom pochodnym na dostęp do chronionych członków, ale nie do publiczności.
Klasy mogą dziedziczyć z innej klasy publicznie, prywatnie lub chronicznie. Klasy prawie zawsze dziedziczą publicznie.
Oto tabela wszystkich kombinacji specyfikatorów dostępu i typów dziedziczenia:
| Specyfikator dostępu w klasie bazowej | Specyfikator dostępu w przypadku dziedziczenia publicznego | Specyfikator dostępu w przypadku dziedziczenia prywatnego | Specyfikator dostępu w przypadku dziedziczenia chronionego |
|---|---|---|---|
| Publiczny | Publiczny | Prywatny | Chroniony |
| Prywatny | Niedostępny | Niedostępny | Niedostępny |
| Chroniony | Chroniony | Prywatny | Chroniony |
Klasy pochodne mogą dodawać nowe funkcje, zmieniać sposób, w jaki funkcje istniejące w klasie bazowej działają w klasie pochodnej, zmieniać poziom dostępu dziedziczonego elementu członkowskiego lub ukrywać funkcjonalność.
Dziedziczenie wielokrotne umożliwia klasie pochodnej dziedziczenie elementów od więcej niż jednego elementu nadrzędnego. Ogólnie rzecz biorąc, należy unikać wielokrotnego dziedziczenia, chyba że alternatywy prowadzą do większej złożoności.
Czas quizu
Pytanie nr 1
Dla każdego z poniższych programów określ, co generują dane wyjściowe, a jeśli nie chcą się skompilować, wskaż dlaczego. To ćwiczenie należy wykonać w drodze inspekcji, więc nie kompiluj ich (w przeciwnym razie odpowiedzi będą trywialne).
A)
#include <iostream>
class Base
{
public:
Base()
{
std::cout << "Base()\n";
}
~Base()
{
std::cout << "~Base()\n";
}
};
class Derived: public Base
{
public:
Derived()
{
std::cout << "Derived()\n";
}
~Derived()
{
std::cout << "~Derived()\n";
}
};
int main()
{
Derived d;
return 0;
}B)
#include <iostream>
class Base
{
public:
Base()
{
std::cout << "Base()\n";
}
~Base()
{
std::cout << "~Base()\n";
}
};
class Derived: public Base
{
public:
Derived()
{
std::cout << "Derived()\n";
}
~Derived()
{
std::cout << "~Derived()\n";
}
};
int main()
{
Derived d;
Base b;
return 0;
}Wskazówka: Zmienne lokalne są niszczone w kolejności odwrotnej do definicji.
C)
#include <iostream>
class Base
{
private:
int m_x {};
public:
Base(int x): m_x{ x }
{
std::cout << "Base()\n";
}
~Base()
{
std::cout << "~Base()\n";
}
void print() const { std::cout << "Base: " << m_x << '\n'; }
};
class Derived: public Base
{
public:
Derived(int y): Base{ y }
{
std::cout << "Derived()\n";
}
~Derived()
{
std::cout << "~Derived()\n";
}
void print() const { std::cout << "Derived: " << m_x << '\n'; }
};
int main()
{
Derived d{ 5 };
d.print();
return 0;
}D)
#include <iostream>
class Base
{
protected:
int m_x {};
public:
Base(int x): m_x{ x }
{
std::cout << "Base()\n";
}
~Base()
{
std::cout << "~Base()\n";
}
void print() const { std::cout << "Base: " << m_x << '\n'; }
};
class Derived: public Base
{
public:
Derived(int y): Base{ y }
{
std::cout << "Derived()\n";
}
~Derived()
{
std::cout << "~Derived()\n";
}
void print() const { std::cout << "Derived: " << m_x << '\n'; }
};
int main()
{
Derived d{ 5 };
d.print();
return 0;
}mi)
#include <iostream>
class Base
{
protected:
int m_x {};
public:
Base(int x): m_x{ x }
{
std::cout << "Base()\n";
}
~Base()
{
std::cout << "~Base()\n";
}
void print() const { std::cout << "Base: " << m_x << '\n'; }
};
class Derived: public Base
{
public:
Derived(int y): Base{ y }
{
std::cout << "Derived()\n";
}
~Derived()
{
std::cout << "~Derived()\n";
}
void print() const { std::cout << "Derived: " << m_x << '\n'; }
};
class D2 : public Derived
{
public:
D2(int z): Derived{ z }
{
std::cout << "D2()\n";
}
~D2()
{
std::cout << "~D2()\n";
}
// note: no print() function here
};
int main()
{
D2 d{ 5 };
d.print();
return 0;
}Pytanie nr 2
a) Napisz klasę Apple i Banana, które wywodzą się ze wspólnej klasy Fruit. Owoc powinien mieć dwa człony: nazwę i kolor.
Powinien uruchomić się następujący program:
int main()
{
Apple a{ "red" };
Banana b{};
std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n";
std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n";
return 0;
}I wygeneruj wynik:
My apple is red. My banana is yellow.
b) Dodaj nową klasę do poprzedniego programu o nazwie GrannySmith, który dziedziczy po Apple.
Powinien uruchomić się następujący program:
int main()
{
Apple a{ "red" };
Banana b;
GrannySmith c;
std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n";
std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n";
std::cout << "My " << c.getName() << " is " << c.getColor() << ".\n";
return 0;
}I wygeneruj wynik:
My apple is red. My banana is yellow. My granny smith apple is green.
Pytanie nr 3
Czas na wyzwanie! Poniższe pytanie quizowe jest trudniejsze i dłuższe. Mamy zamiar napisać prostą grę, w której walczysz z potworami. Celem gry jest zebranie jak największej ilości złota, zanim umrzesz lub osiągniesz poziom 20.
Nasz program będzie składał się z 3 klas: klasy Creature, klasy Player i klasy Monster. Zarówno Gracz, jak i Potwór dziedziczą po Stworzeniu.
a) Najpierw utwórz klasę Creature. Stworzenia mają 5 atrybutów: nazwę (std::string), symbol (a char), ilość zdrowia (int), ilość obrażeń zadawanych przez atak (int) i ilość złota, które noszą (int). Zaimplementuj je jako członkowie klasy. Napisz pełny zestaw modułów pobierających (funkcja get dla każdego elementu członkowskiego). Dodaj trzy inne funkcje: void lessHealth(int) zmniejsza zdrowie stworzenia o liczbę całkowitą. bool isDead() zwraca wartość true, gdy zdrowie stworzenia wynosi 0 lub mniej. void addGold(int) dodaje złoto do stworzenia.
Powinien uruchomić się następujący program:
#include <iostream>
#include <string>
int main()
{
Creature o{ "orc", 'o', 4, 2, 10 };
o.addGold(5);
o.reduceHealth(1);
std::cout << "The " << o.getName() << " has " << o.getHealth() << " health and is carrying " << o.getGold() << " gold.\n";
return 0;
}I wygeneruj wynik:
The orc has 3 health and is carrying 15 gold.
b) Teraz utworzymy klasę Player. Klasa Gracz dziedziczy po Creature. Gracz ma jednego dodatkowego członka, poziom gracza, który zaczyna się od 1. Gracz ma własną nazwę (wprowadzoną przez użytkownika), używa symbolu „@”, ma 10 punktów zdrowia, na początku zadaje 1 obrażenia i nie ma złota. Napisz funkcję o nazwie LevelUp(), która zwiększa poziom gracza i obrażenia o 1. Napisz także moduł pobierający dla członka poziomu. Na koniec napisz funkcję hasWon(), która zwraca wartość true, jeśli gracz osiągnął poziom 20.
Napisz nową funkcję main(), która zapyta użytkownika o nazwę i wyświetli wynik w następujący sposób:
Enter your name: Alex Welcome, Alex. You have 10 health and are carrying 0 gold.
c) Następna w kolejce jest klasa Monster. Potwór dziedziczy także po Stworzeniu. Potwory nie mają niedziedziczonych zmiennych składowych.
Najpierw napisz pustą klasę Monster dziedziczącą po Creature, a następnie dodaj wyliczenie wewnątrz klasy Monster o nazwie Type, które zawiera moduły wyliczające dla 3 potworów, które będziemy mieć w tej grze: dragon, orc, I slime (będziesz także chciał max_types licznik, bo to się za chwilę przyda).
d) Każdy typ Potwora będzie miał inną nazwę, symbol, początkowe zdrowie, złoto i obrażenia. Oto tabela statystyk dla każdego typu potwora:
| Typ | Nazwa | Symbol | Zdrowie | Szkoda | Złoto |
|---|---|---|---|---|---|
| smok | smok | D | 20 | 4 | 100 |
| ork | ork | o | 4 | 2 | 25 |
| szlam | szlam | S | 1 | 1 | 10 |
Następnym krokiem jest napisanie konstruktora Monster, dzięki któremu będziemy mogli tworzyć potwory. Konstruktor potwora powinien przyjąć wyliczenie typu jako parametr, a następnie utworzyć potwora z odpowiednimi statystykami dla tego rodzaju potwora.
Istnieje wiele różnych sposobów wdrożenia tego (niektóre lepsze, inne gorsze). Jednakże w tym przypadku, ponieważ wszystkie atrybuty naszych potworów są predefiniowane (nie są losowe ani dostosowywane do każdego stworzenia), możemy skorzystać z tabeli przeglądowej. Nasza tabela przeglądowa będzie tablicą Creature w stylu C, gdzie indeksowanie tablicy typem zwróci odpowiednie Creature dla tego typu.
Ponieważ ta tabela Creature jest specyficzna dla Monstera, możemy ją zdefiniować w klasie Monster jako static inline Creature monsterData[] { }, zainicjowany naszymi elementami Creature.
Nasz konstruktor Monster jest wtedy prosty: możemy wywołać konstruktor kopiujący Creature i przekazać mu odpowiednie Creature z naszej tabeli monsterData.
Powinien się skompilować następujący program:
#include <iostream>
#include <string>
int main()
{
Monster m{ Monster::Type::orc };
std::cout << "A " << m.getName() << " (" << m.getSymbol() << ") was created.\n";
return 0;
}i wydrukuj:
A orc (o) was created.
e) Na koniec dodaj a static funkcję do potwora o nazwie getRandomMonster(). Ta funkcja powinna wybrać losową liczbę z 0 Do max_types-1 i zwróć z tym potwora (według wartości) Type (musisz static_cast int do Type aby przekazać go do Monster konstruktora).
Lekcja 8.15 -- Globalne liczby losowe (Random.h) zawiera kod, którego możesz użyć do wybrania losowej liczby.
Powinna zostać uruchomiona następująca funkcja główna:
#include <iostream>
#include <string>
int main()
{
for (int i{ 0 }; i < 10; ++i)
{
Monster m{ Monster::getRandomMonster() };
std::cout << "A " << m.getName() << " (" << m.getSymbol() << ") was created.\n";
}
return 0;
}Wyniki tego programu powinny być losowe.
f) Wreszcie jesteśmy gotowi do napisania naszej logiki gry!
Oto zasady gry:
Gracz napotyka losowo wygenerowaną liczbę potwora na raz.
Dla każdego potwora gracz ma dwie możliwości: (R)un lub (F)ight.
Jeśli gracz zdecyduje się na ucieczkę, ma 50% szans na ucieczkę.
Jeśli gracz ucieknie, przechodzi do następnego spotkania bez żadnych negatywnych skutków.
Jeśli gracz nie ucieknie, potwór otrzymuje darmowy atak, a gracz wybiera następną akcję.
Jeśli gracz zdecyduje się walczyć, atakuje jako pierwszy. Zdrowie potwora jest zmniejszone o obrażenia zadawane przez gracza.
Jeśli potwór umrze, gracz otrzymuje całe złoto, które potwór niesie. Gracz również awansuje, zwiększając swój poziom i obrażenia o 1.
Jeśli potwór nie umrze, potwór ponownie atakuje gracza. Zdrowie gracza zostaje zmniejszone w wyniku obrażeń zadawanych przez potwora.
Gra kończy się, gdy gracz umrze (przegrana) lub osiągnie poziom 20 (wygrana)
Jeśli gracz umrze, gra powinna poinformować go, jaki był poziom i ile posiadał złota.
Jeśli gracz wygra, gra powinna poinformować go, że wygrał i ile posiadał złota
Oto przykładowa sesja gry:
Wpisz swoje imię: Alex
Witaj, Alex
Napotkałeś śluz.
(R)un lub (F)ight: f
Uderzyłeś śluza zadając 1 obrażenia.
Zabiłeś śluz.
Jesteś teraz poziom 2.
Znalazłeś 10 sztuk złota.
Napotkałeś smoka (D).
(R)un lub (F)ight: r
Nie udało Ci się uciekaj.
Smok zadał ci 4 obrażenia.
(R)un lub (F)ight: r
Udało ci się uciec.
Napotkałeś orka (o).
(R)un lub (F)ight: f
Uderzyłeś orka, zadając 2 obrażenia.
Ork uderzył cię, zadając 2 obrażenia.
(R)un lub (F)ight: f
Uderzyłeś orka, zadając 2 obrażenia.
Zabiłeś orka.
Masz teraz poziom 3.
Znalazłeś 25 sztuk złota.
Napotkałeś smoka (D).
(R)un lub (F)ight: r
Nie udało Ci się uciekaj.
Smok zadał ci 4 obrażenia.
Umarłeś na poziomie 3 i mając 35 sztuk złota.
Szkoda, że nie możesz zabrać go ze sobą!
Wskazówka: Utwórz 4 funkcje:
- Funkcja main() powinna obsługiwać konfigurację gry (tworzenie Gracza) i grę główną pętla.
- fightMonster() obsługuje walkę pomiędzy Graczem a pojedynczym Potworem, w tym zadaje graczowi pytanie, co chce zrobić, obsługuje przypadki ucieczki lub walki.
- attackMonster() obsługuje gracza atakującego potwora, w tym awansuje na wyższy poziom.
- attackPlayer() obsługuje potwora atakującego gracza.
g) Extra kredyt:
Czytelnik Tom nie naostrzył miecza na tyle, aby pokonać potężnego smoka. Pomóż mu, wdrażając następujące mikstury w różnych rozmiarach:
| Typ | Efekt (mały) | Efekt (średni) | Efekt (duży) |
|---|---|---|---|
| Zdrowie | +2 Zdrowie | +2 Zdrowie | +5 Zdrowia |
| Siła | +1 Obrażenia | +1 Obrażenia | +1 Obrażenia |
| Trucizna | -1 Zdrowie | -1 Zdrowie | -1 Zdrowie |
Możesz wykazać się kreatywnością i dodać więcej mikstur lub zmienić ich działanie!
Gracz ma 30% szans na znalezienie mikstury po każdej wygranej walce i ma wybór pomiędzy wypiciem lub nie wypiciem mikstury. Jeśli gracz nie wypije mikstury, mikstura zniknie. Gracz nie wie, jaki rodzaj mikstury został znaleziony, dopóki jej nie wypije, kiedy to zostanie ujawniony rodzaj i wielkość mikstury oraz zastosowany zostanie efekt.
W poniższym przykładzie gracz znalazł trującą miksturę i zmarł po jej wypiciu (w tym przykładzie trucizna była znacznie bardziej szkodliwa)
You have encountered a slime (s). (R)un or (F)ight: f You hit the slime for 1 damage. You killed the slime. You are now level 2. You found 10 gold. You found a mythical potion! Do you want to drink it? [y/n]: y You drank a Medium potion of Poison You died at level 2 and with 10 gold. Too bad you can't take it with you!

