Kompaktkurs C++ Vererbung (inheritance) 1 Oft besitzen verschiedene Datentypen einen gemeinsamen Kern: Kreis und Viereck sind geometrische Figuren; sie haben eine Kantenzahl, eine Fläche, einen Umfang etc. Den gemeinsamen Kern kann man in einer Basisklasse Figur abbilden. Die abgeleiteten Klassen Kreis und Viereck erben die Elemente des Basisklasse und verfügen über zusätzliche Eigenschaften (Seitenlänge, Radius etc.).
Kompaktkurs C++ Beispiel: Basisklasse 2 #include <iostream> #include <cmath> class Figur protected: int Kanten_; double Flaeche_, Umfang_; public: Figur(int k, double fl, double u) : Kanten_(k), Flaeche_(fl), Umfang_(u) } }; int Kanten() const return Kanten_; } double Flaeche() const return Flaeche_; } double Umfang() const return Umfang_; } const char* Name() const return "Figur"; } std::ostream& operator << (std::ostream &os, const Figur &f) return os << "Die Figur hat " << f.kanten() << " Kanten, eine Flaeche von " << f.flaeche() << ", einen Umfang von " << f.umfang() << "\n"; }
Kompaktkurs C++ Beispiel: abgeleitete Klassen 3 class Kreis : public Figur protected: double Radius_; public: Kreis (double r) : Figur(0, M_PI*r*r, 2*M_PI*r), Radius_(r) } }; double Radius() const return Radius_; } const char* Name() const return "Kreis"; } class Viereck : public Figur protected: double Seite_[2]; public: Viereck (double s0, double s1) : Figur(4, s0*s1, 2*(s0+s1)) Seite_[0]=s0; Seite_[1]=s1; } }; double Seite(int i) const return Seite_[i]; } const char* Name() const return "Viereck"; }
Kompaktkurs C++ Beispiel: Hauptprogramm 4 int main() Figur mykreis(0, M_PI, 2*M_PI); Figur myquadrat(4,1,4); Kreis kreis(1); Viereck quadrat(1,1); std::cout << mykreis << "und ist ein(e) " << mykreis.name() << ".\n" << myquadrat << "und ist ein(e) " << myquadrat.name() << ".\n" << kreis << "und ist ein(e) " << kreis.name() << ".\n" << quadrat << "und ist ein(e) " << quadrat.name() << "." << std::endl; } return 0; Die Figur hat 0 Kanten, eine Flaeche von 3.14159, einen Umfang von 6.28319 und ist ein(e) Figur. Die Figur hat 4 Kanten, eine Flaeche von 1, einen Umfang von 4 und ist ein(e) Figur. Die Figur hat 0 Kanten, eine Flaeche von 3.14159, einen Umfang von 6.28319 und ist ein(e) Kreis. Die Figur hat 4 Kanten, eine Flaeche von 1, einen Umfang von 4 und ist ein(e) Viereck.
Kompaktkurs C++ Vererbung 5 Um eine Klasse B von einer Basisklasse A abzuleiten, verwendet man die Definition class B : public A... }; Die Basisklasse A kann in mancherlei Hinsicht als Element der abgeleiteten Klasse B aufgefasst werden. Im Konstruktor wird sie beispielsweise wie ein Element angesprochen. Die Basisklasse wird somit quasi in die abgeleitete Klasse eingebettet. Dennoch besteht ein großer Unterschied zu class C A a;... }; Klasse B kann in eine Klasse A konvertiert werden, bei C funktionert das nicht (B ist ein A, C hat ein A). Von A abgeleitete Klassen dürfen auf die protected Elemente von A zugreifen, nicht aber auf die private Elemente. Das Schlüsselwort public bei der Ableitung bewirkt, dass die Basisklasse A öffentlich ist. Damit sind alle öffentlichen Daten von A auch in B öffentlich, und jede Funktion kann B*/B& in A*/A& konvertieren. Wird stattdessen private verwendet, dann dürfen nur noch die Elementfunktionen und Friends von B die öffentlichen Daten von A benutzen, und nur diese dürfen B*/B& in A*/A& konvertieren.
Kompaktkurs C++ Vererbung 6 Vererbung kann sich über mehrere Generationen erstrecken, d. h., abgeleitete Klassen können selbst wieder als Basisklassen anderer Klassen dienen. Die Klasse Viereck, abgeleitet von Figur, könnte z. B. als Basisklasse einer Klasse Quadrat dienen. Mehrfachvererbung ist ebenfalls möglich, d. h., Klassen können von mehr als einer Basisklasse abgeleitet werden. Eine Klasse Wasserflugzeug könnte z. B. von den Klassen Boot und Flugzeug abgeleitet werden durch class Wasserflugzeug : public Boot, public Flugzeug... };
Kompaktkurs C++ Typkonvertierung zur Basisklasse 7 Leider liefert folgendes Programm nicht die erhoffte Ausgabe: void print_name (const Figur &f) std::cout << "Die Figur ist ein(e) " << f.name() << ".\n"; } int main() Figur mykreis(0, M_PI, 2*M_PI); Kreis kreis(1); Viereck quadrat(1,1); print_name(mykreis); print_name(kreis); print_name(quadrat); } return 0; Die Figur ist ein(e) Figur. Die Figur ist ein(e) Figur. Die Figur ist ein(e) Figur.
Kompaktkurs C++ Virtuelle Funktionen 8 Dass bei der abgeleiteten Klasse Kreis die eigene Funktion Name und nicht die der Basisklasse Figur aufgerufen wird, selbst wenn Kreis über const Figur& angesprochen wird, kann man durch virtuelle Funktionen erreichen. Dazu muss die Funktion Name in Figur deklariert werden als: virtual const char* Name() const; Der Übersicht wegen kann man in Kreis auch virtual hinzufügen. Die Ausgabe des Programms ist nun Die Figur ist ein(e) Figur. Die Figur ist ein(e) Kreis. Die Figur ist ein(e) Viereck. Hat eine Klasse virtuelle Funktionen, so sollte man den Destruktur ebenfalls als virtual deklarieren.
Kompaktkurs C++ Vtable 9 Dass die richtigen Funktionen aufgerufen werden, wird erreicht, indem eine Tabelle (vtable) mit den Adressen der anzusprechenden Funktionen mitgeführt wird. Da dies Speicherplatz und Rechenzeit kostet, ist dieses Verhalten nicht der Default, sondern muss durch virtual aktiviert werden. Durch explizite Angabe der Klasse kann man erzwingen, dass der Vtable übergangen wird: Figur::Name() ruft die Funktion der Basisklasse Figur auf.
Kompaktkurs C++ Abstrakte Funktionen und Klassen 10 Anstatt die Daten Flaeche_ und Umfang_ zu speichern, könnten wir sie in Flaeche() und Umfang() berechnen. Da diese Berechnung nur mit den Daten aus den abgeleiteten Klassen möglich ist, müsste man dazu Flaeche() und Umfang() zu virtuellen Funktionen machen. Für die Basisklasse alleine könnte man aber keine sinnvollen Definitionen der beiden Funktionen angeben. Durch Anhängen von =0 deklariert man daher die Funktion als abstrakt. Eine Klasse, die eine abstrakte Funktion enthält, ist selbst abstrakt. Man kann keine Instanzen einer abstrakten Klasse direkt erzeugen, da ihr Vtable nicht vollständig ist. Eine solche Klasse dient nur als Basisklasse. Eine von ihr abgeleitete Klasse ist nicht mehr abstrakt, wenn sie die fehlenden Funktionen definiert.
Kompaktkurs C++ Beispiel: Basisklasse 11 #include <iostream> #include <cmath> class Figur protected: int Kanten_; public: Figur(int k) : Kanten_(k) } virtual ~Figur() } }; int Kanten() const return Kanten_; } virtual double Flaeche() const = 0; virtual double Umfang() const = 0; virtual const char* Name() const return "Figur"; } std::ostream& operator << (std::ostream &os, const Figur &f) return os << "Die " << f.figur::name() << " " << f.name() << " hat " << f.kanten() << " Kanten, eine Flaeche von " << f.flaeche() << ", einen Umfang von " << f.umfang() << "\n"; }
Kompaktkurs C++ Beispiel: abgeleitete Klassen 12 class Kreis : public Figur protected: double Radius_; public: Kreis (double r) : Figur(0), Radius_(r) } double Radius() const return Radius_; } virtual double Flaeche() const return M_PI*Radius_*Radius_; } virtual double Umfang() const return 2*M_PI*Radius_; } virtual const char* Name() const return "Kreis"; } }; class Viereck : public Figur protected: double Seite_[2]; public: Viereck (double s0, double s1) : Figur(4) Seite_[0]=s0; Seite_[1]=s1; } double Seite(int i) const return Seite_[i]; } virtual double Flaeche() const return Seite_[0]*Seite_[1]; } virtual double Umfang() const return 2*(Seite_[0]+Seite_[1]); } virtual const char* Name() const return "Viereck"; } };
Kompaktkurs C++ Beispiel: Hauptprogramm 13 int main() Kreis kreis(1); Viereck quadrat(1,1); std::cout << kreis << quadrat; } return 0; Die Figur Kreis hat 0 Kanten, eine Flaeche von 3.14159, einen Umfang von 6.28319 Die Figur Viereck hat 4 Kanten, eine Flaeche von 1, einen Umfang von 4
Kompaktkurs C++ Konstruktoren 14 Konstruktoren können nicht virtuell sein. Im Normalfall ist das sinnvoll, da man ja erst ein Objekt haben muss, bevor man es über eine Referenz auf eine Basisklasse ansprechen kann. Beim Kopier-Konstruktor ist die Situation allerdings eine andere: Man hat bereits ein Objekt und will eine genaue Kopie davon anlegen. Spricht man das Objekt über eine Referenz auf eine Basisklasse an, dann wird leider nur der Basisteil kopiert. Hier hilft ein Trick: In der abstrakten Basisklasse deklarieren wir virtual Figur* Copy() const = 0; In den anderen Klassen (hier für Kreis) definieren wir Kreis* Copy() const return new Kreis(*this); } Die virtuelle Funktion Copy liefert jetzt einen Zeiger auf eine exakte Kopie zurück. Leider muss man diese Kopie wieder mit delete zerstören.