C++: Dziedziczenie i polimorfizm
Dziedziczenie umożliwia tworzenie nowych klas bazując na istniejących klasach, co ułatwia ponowne wykorzystanie kodu i organizację złożonych programów. Z kolei polimorfizm pozwala na wykorzystanie jednego interfejsu do różnych form funkcjonalności, co zwiększa elastyczność i skalowalność kodu.
W kontekście C++, dziedziczenie i polimorfizm są ze sobą ściśle powiązane. Dziedziczenie pozwala na tworzenie hierarchii klas, gdzie klasy pochodne dziedziczą atrybuty i zachowania po klasach bazowych, natomiast polimorfizm umożliwia wykorzystanie tych klas w sposób, który nie jest z góry określony przez ich hierarchię.
Przyjrzyjmy się bliżej tym dwóm koncepcjom, ich znaczeniu w projektowaniu oprogramowania i sposobom, w jakie współpracują one ze sobą, aby zwiększyć możliwości i efektywność kodu napisanego w języku C++.
Rodzaje dziedziczenia
W C++ istnieją różne rodzaje dziedziczenia, które definiują, jak klasy pochodne mogą korzystać z członków klasy bazowej:
- Publiczne: Gdy klasa pochodna dziedziczy publicznie, wszystkie publiczne i chronione składowe klasy bazowej zachowują swój dostęp w klasie pochodnej.
- Chronione (Protected): W tym przypadku, publiczne i chronione składowe klasy bazowej stają się chronione w klasie pochodnej.
- Prywatne: Tutaj, publiczne i chronione składowe klasy bazowej stają się prywatnymi składowymi klasy pochodnej.
Wielokrotne dziedziczenie
C++ oferuje również możliwość wielokrotnego dziedziczenia, co oznacza, że klasa może dziedziczyć funkcjonalność z więcej niż jednej klasy bazowej. Jest to potężne narzędzie, ale również może prowadzić do skomplikowanych zależności i problemów, takich jak diament problemu dziedziczenia, gdzie ta sama klasa bazowa jest dziedziczona przez różne ścieżki.
Konstruktory i destruktory w dziedziczeniu
Konstruktory i destruktory są wywoływane w określonej kolejności, zgodnie z hierarchią dziedziczenia. Konstruktory klas bazowych są zwykle wywoływane przed konstruktorami klas pochodnych, podczas gdy destruktory działają w odwrotnej kolejności, co zapewnia prawidłową inicjalizację i deinicjalizację obiektów.
Polimorfizm Statyczny i Dynamiczny
W C++ istnieją dwa główne rodzaje polimorfizmu: statyczny i dynamiczny. Polimorfizm statyczny jest osiągany głównie przez przeciążanie funkcji i szablonów. Pozwala to na używanie tej samej nazwy funkcji dla różnych kontekstów, w zależności od argumentów przekazanych do funkcji. Z kolei polimorfizm dynamiczny korzysta z wirtualnych funkcji i jest związany z dziedziczeniem. Umożliwia różne implementacje tej samej metody w klasach pochodnych, co jest decydowane w czasie wykonania programu, a nie jego kompilacji.
Wirtualne Funkcje i Klasy Abstrakcyjne
Kluczowym elementem polimorfizmu dynamicznego są wirtualne funkcje. Definiują one interfejs, który klasy pochodne mogą nadpisać, dostarczając własne implementacje. Klasy zawierające przynajmniej jedną wirtualną funkcję nazywane są klasami abstrakcyjnymi i służą jako baza dla polimorfizmu. Klasy abstrakcyjne często zawierają co najmniej jedną czysto wirtualną funkcję, która musi być zaimplementowana przez klasy pochodne.
Dynamiczne Rzutowanie Typów
Dynamiczne rzutowanie typów, czyli dynamic_cast
, jest wykorzystywane do bezpiecznego rzutowania obiektów do ich właściwego typu w hierarchii dziedziczenia. Jest to szczególnie przydatne w kontekście polimorfizmu, gdy musimy zarządzać obiektami różnych klas pochodnych poprzez wskaźniki lub referencje do klasy bazowej. dynamic_cast
sprawdza w czasie wykonania, czy rzutowanie jest legalne, co zapewnia bezpieczeństwo typów i pomaga uniknąć błędów wynikających z niewłaściwego rzutowania.
Przykład dziedziczenia w C++
#include <iostream>
using namespace std;
// Klasa bazowa
class Pojazd {
public:
Pojazd() { cout << "Tworzenie Pojazdu" << endl; }
virtual ~Pojazd() { cout << "Usuwanie Pojazdu" << endl; }
virtual void uruchom() { cout << "Pojazd uruchomiony" << endl; }
};
// Klasa pochodna
class Samochod : public Pojazd {
public:
Samochod() { cout << "Tworzenie Samochodu" << endl; }
~Samochod() { cout << "Usuwanie Samochodu" << endl; }
void uruchom() override { cout << "Samochod uruchomiony" << endl; }
};
int main() {
Pojazd *pojazd = new Samochod();
pojazd->uruchom();
delete pojazd;
return 0;
}
Analiza Kodu
#include <iostream>
using namespace std;
// Klasa bazowa
class Pojazd {
public:
Pojazd() { cout << "Tworzenie Pojazdu" << endl; }
virtual ~Pojazd() { cout << "Usuwanie Pojazdu" << endl; }
virtual void uruchom() { cout << "Pojazd uruchomiony" << endl; }
};
Klasa Bazowa Pojazd
:
- Posiada konstruktor, który wyświetla komunikat przy tworzeniu obiektu.
- Zawiera wirtualny destruktor, co jest kluczowe w hierarchii dziedziczenia, zwłaszcza przy pracy z wskaźnikami do obiektów klas bazowych. Zapewnia to poprawne wywołanie destruktorów klas pochodnych.
- Metoda
uruchom()
jest oznaczona jako wirtualna, co pozwala na jej nadpisanie w klasach pochodnych.
// Klasa pochodna
class Samochod : public Pojazd {
public:
Samochod() { cout << "Tworzenie Samochodu" << endl; }
~Samochod() { cout << "Usuwanie Samochodu" << endl; }
void uruchom() override { cout << "Samochod uruchomiony" << endl; }
};
Klasa Pochodna Samochod
:
- Dziedziczy po klasie
Pojazd
. - Konstruktor i destruktor wypisują komunikaty przy tworzeniu i usuwaniu obiektów klasy
Samochod
. - Nadpisuje metodę
uruchom()
, co jest sygnalizowane przez słowo kluczoweoverride
.
int main() {
Pojazd *pojazd = new Samochod();
pojazd->uruchom();
delete pojazd;
return 0;
}
Funkcja main
:
- Inicjuje wskaźnik typu
Pojazd
, przypisując do niego nowo utworzony obiekt klasySamochod
. - Jest to przykład polimorfizmu. Obiekt klasy
Samochod
jest traktowany jakoPojazd
, ale zachowuje swoje specyficzne cechy (w tym nadpisaną metodęuruchom()
). - Wywoływana jest metoda
uruchom()
klasySamochod
, mimo że wskaźnik jest typuPojazd
. Jest to możliwe dzięki wirtualnym metodom. - Używane jest słowo kluczowe
delete
do zwolnienia pamięci. Dzięki wirtualnemu destruktorowi w klasiePojazd
, destruktorSamochod
jest również poprawnie wywoływany.
Przykład polimorfizmu w C++
#include <iostream>
using namespace std;
// Klasa bazowa
class Figura {
public:
virtual void rysuj() = 0; // Czysta funkcja wirtualna
};
// Klasa pochodna
class Kwadrat : public Figura {
public:
void rysuj() override { cout << "Rysowanie Kwadratu." << endl; }
};
class Kolo : public Figura {
public:
void rysuj() override { cout << "Rysowanie Koła." << endl; }
};
int main() {
Figura* f1 = new Kwadrat();
Figura* f2 = new Kolo();
f1->rysuj();
f2->rysuj();
delete f1;
delete f2;
return 0;
}
Analiza Kodu
#include <iostream>
using namespace std;
// Klasa bazowa
class Figura {
public:
virtual void rysuj() = 0; // Czysta funkcja wirtualna
};
Klasa Bazowa Figura
:
- Jest to klasa abstrakcyjna, ponieważ zawiera czystą funkcję wirtualną
rysuj()
. Oznacza to, że nie można utworzyć bezpośrednio instancji tej klasy. - Słowo kluczowe
virtual
wskazuje, że funkcjarysuj()
może być nadpisana w klasie pochodnej.
// Klasa pochodna
class Kwadrat : public Figura {
public:
void rysuj() override { cout << "Rysowanie Kwadratu." << endl; }
};
class Kolo : public Figura {
public:
void rysuj() override { cout << "Rysowanie Koła." << endl; }
};
Klasy Pochodne Kwadrat
i Kolo
:
- Obydwie klasy dziedziczą po klasie
Figura
. - Implementują one metodę
rysuj()
, co jest wymagane, ponieważFigura
jest klasą abstrakcyjną. - Słowo kluczowe
override
zapewnia, że metoda jest prawidłowo nadpisana z klasy bazowej.
int main() {
Figura* f1 = new Kwadrat();
Figura* f2 = new Kolo();
f1->rysuj();
f2->rysuj();
delete f1;
delete f2;
return 0;
}
Funkcja main
:
- Tworzone są dwa wskaźniki typu
Figura
,f1
if2
, ale inicjalizowane są jakoKwadrat
iKolo
. - Jest to przykład polimorfizmu. Mimo że
f1
if2
są wskaźnikami doFigura
, wywołują odpowiednie implementacje metodyrysuj()
w zależności od rzeczywistego typu obiektu, na który wskazują. - Metoda
rysuj()
jest wywoływana dla każdego obiektu, pokazując polimorficzne zachowanie. - Następuje zwolnienie zasobów za pomocą
delete
, co jest ważne przy zarządzaniu pamięcią dynamiczną w C++.
Testy przypięte do lekcji | |
---|---|
Aby uzyskać dostęp do testów i ćwiczeń interaktywnych - Zaloguj się |