Programming lesson
C++ Vererbung und Polymorphie: Drachen zähmen mit Vererbungshierarchien – Ein Tutorial zu COWCOP3504C Lab 07
In diesem Tutorial lernst du, wie du in C++ mithilfe von Vererbung und Polymorphie eine Drachenklasse erstellst – inspiriert von CowLab 07. Wir bauen auf einer Cow-Klasse auf, leiten Dragon und IceDragon ab und zeigen, wie dynamische Typabfragen und virtuelle Methoden funktionieren.
Einführung: Vererbung in C++ – Mehr als nur Kühe
Stell dir vor, du entwickelst ein Spiel wie Clash of Clans oder eine KI-gesteuerte Kreaturenwelt: Jede Kreatur hat Grundfähigkeiten, aber Drachen können Feuer spucken, Eisdrachen nicht. Genau hier kommt die Vererbung ins Spiel. In diesem Tutorial lernst du, wie du mit C++ Vererbung und Polymorphie eine Hierarchie aus Cow, Dragon und IceDragon aufbaust – ganz wie im Lab 07 der COWCOP3504C. Keine Sorge, wir lösen nicht die ganze Aufgabe, sondern zeigen dir die entscheidenden Konzepte.
1. Die Basis: Cow-Klasse
Bevor wir zu feuerspeienden Drachen kommen, brauchen wir eine Basisklasse. Die Cow-Klasse speichert einen Namen und ein Bild (ASCII-Art). In C++ sieht eine einfache Cow-Klasse so aus:
// Cow.h
#ifndef COW_H
#define COW_H
#include <string>
class Cow {
public:
Cow(const std::string& name);
std::string getName() const;
std::string getImage() const;
virtual void setImage(const std::string& image);
private:
std::string name_;
std::string image_;
};
#endifBeachte: setImage ist virtual. Das ist der Schlüssel zur Polymorphie – abgeleitete Klassen können diese Methode überschreiben. Warum virtual? Damit später, wenn wir einen Cow-Zeiger auf ein Dragon-Objekt haben, die richtige Methode aufgerufen wird.
Die Implementierung ist simpel:
// Cow.cpp
#include "Cow.h"
Cow::Cow(const std::string& name) : name_(name) {}
std::string Cow::getName() const { return name_; }
std::string Cow::getImage() const { return image_; }
void Cow::setImage(const std::string& image) { image_ = image; }2. Dragon-Klasse: Von der Kuh zum Drachen
Ein Drache ist eine spezielle Kuh – er erbt alle Eigenschaften, kann aber zusätzlich Feuer spucken. In C++ leiten wir Dragon von Cow ab:
// Dragon.h
#ifndef DRAGON_H
#define DRAGON_H
#include "Cow.h"
class Dragon : public Cow {
public:
Dragon(const std::string& name, const std::string& image);
bool canBreatheFire() const;
};
#endifDer Konstruktor ruft den Cow-Konstruktor auf und setzt das Bild. Die Methode canBreatheFire() gibt für einen normalen Drachen immer true zurück. In der Implementierung:
// Dragon.cpp
#include "Dragon.h"
Dragon::Dragon(const std::string& name, const std::string& image)
: Cow(name) {
setImage(image);
}
bool Dragon::canBreatheFire() const {
return true;
}Spannend wird es, wenn wir später durch Polymorphie herausfinden, ob ein Cow-Zeiger auf einen Drachen zeigt. Dafür nutzen wir dynamic_cast – wie die HeiferGenerator-Klasse es tut.
3. IceDragon: Ein eisiger Untertyp
IceDragon erbt von Dragon und überschreibt canBreatheFire(), um false zurückzugeben. Kein Feuer, aber dafür Eis – cool, oder?
// IceDragon.h
#ifndef ICEDRAGON_H
#define ICEDRAGON_H
#include "Dragon.h"
class IceDragon : public Dragon {
public:
IceDragon(const std::string& name, const std::string& image);
bool canBreatheFire() const;
};
#endifImplementierung:
// IceDragon.cpp
#include "IceDragon.h"
IceDragon::IceDragon(const std::string& name, const std::string& image)
: Dragon(name, image) {}
bool IceDragon::canBreatheFire() const {
return false;
}Beachte: Der IceDragon-Konstruktor ruft den Dragon-Konstruktor auf, der wiederum den Cow-Konstruktor aufruft. Die Vererbungskette ist: Cow → Dragon → IceDragon.
4. Polymorphie in Aktion: Der HeiferGenerator
Die bereitgestellte HeiferGenerator-Klasse erzeugt eine Liste von Cow-Zeigern, die auf verschiedene Objekte zeigen: Cow, Dragon, IceDragon. Mit dynamic_cast prüft sie, ob ein Cow-Zeiger tatsächlich auf einen Dragon zeigt:
Dragon* HeiferGenerator::getDragonPointer(Cow* candidate) {
Dragon* dragon = dynamic_cast<Dragon*>(candidate);
return dragon; // nullptr, wenn es kein Dragon ist
}Das ist mächtig: Du kannst eine Liste von „Kühen“ durchgehen und nur bei Drachen eine Extra-Meldung ausgeben. Genau das passiert im cowsay-Programm: Wenn der Nutzer -n dragon wählt, wird der Cow-Zeiger per dynamic_cast in einen Dragon-Zeiger umgewandelt und canBreatheFire() aufgerufen.
5. CMakeLists.txt: C++17 einstellen
Damit alles funktioniert, musst du in deiner CMakeLists.txt die Sprache auf C++17 setzen:
cmake_minimum_required(VERSION 3.10)
project(cowsay)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(cowsay cowsay.cpp Cow.cpp Dragon.cpp IceDragon.cpp HeiferGenerator.cpp)Warum C++17? Weil dynamic_cast und andere Features ab C++11 stabil sind, aber C++17 bringt nützliche Erweiterungen wie std::string_view (optional). Dein Professor verlangt es – also halten wir uns dran.
6. Typische Fehler vermeiden
- Vergiss virtual nicht: Wenn
setImagenicht virtual ist, wird bei einem Cow-Zeiger auf Dragon die Cow-Version aufgerufen – das Bild wird nicht gesetzt. - Konstruktorverkettung: IceDragon muss den Dragon-Konstruktor aufrufen, nicht direkt Cow. Sonst fehlt die Initialisierung der Dragon-Ebene.
- dynamic_cast nur bei Polymorphie: Du brauchst mindestens eine virtuelle Methode (hier
setImage), damit der Cast funktioniert. Ohne virtual ist dynamic_cast undefiniert.
7. Ausblick: Vererbung im echten Leben
Vererbung begegnet dir überall: In Spiele-Engines wie Unity (MonoBehaviour → PlayerController), in GUI-Frameworks (QWidget → QPushButton) oder in KI-Modellen (BaseModel → GPT4). Sogar in der Finanzwelt: Ein Basisklasse „Asset“ und abgeleitete „Stock“, „Bond“ etc. Mit diesem Tutorial hast du die Grundlagen verstanden – und kannst jetzt deine eigenen Drachen (oder Kühe) programmieren.
Zusammenfassung
- Erstelle eine Basisklasse Cow mit virtueller Methode.
- Leite Dragon ab und füge
canBreatheFire()hinzu. - Leite IceDragon von Dragon ab und überschreibe die Methode.
- Nutze
dynamic_castfür Typabfragen. - Setze C++17 in CMakeLists.txt.
Jetzt bist du bereit, das Lab zu meistern. Viel Erfolg – und lass die Drachen fliegen!