1 Thema heute: • Vererbung und Klassenhierarchien • Abgeleitete Klassen • Vererbung von Daten und Funktionen • Virtuelle Funktionen Institut für Geometrie und Praktische Mathematik, RWTH Aachen 2 Vererbung • oft besitzen verschiedene Datentypen einen gemeinsamen Kern • Beispiel: Auto, Lastwagen Beide sind offenbar Fahrzeuge, haben Räder, Gewicht, etc. • Anderes Beispiel: Chef, Abteilungsleiter, Sekretaerin, etc. sind alle Mitarbeiter • Kern: (abstrakte) Basisklasse • Vererbung: eine abgeleitete Klasse erbt die Elemente des Basisklasse (inheritance) und verfügt über zusätzliche Eigenschaften Institut für Geometrie und Praktische Mathematik, RWTH Aachen 3 Beispiel: Die Basis–Klasse Fahrzeug (Header File) #ifndef VEHIKEL_HPP #define VEHIKEL_HPP #include <iostream> using namespace std; class Fahrzeug { protected: int _Raeder; double _Gewicht; public: Fahrzeug (int Raeder, double Gewicht); int Raeder () const {return _Raeder;} double Gewicht () const {return _Gewicht;} Institut für Geometrie und Praktische Mathematik, RWTH Aachen 4 double Radlast () const; friend ostream& operator << (ostream& s, const Fahrzeug &v); const char *Name() const { return "Fahrzeug"; } }; #endif Institut für Geometrie und Praktische Mathematik, RWTH Aachen 5 Beispiel: Die Basis-Klasse Fahrzeug (Implementierung) // fahrzeug.cpp #include "fahrzeug.hpp" // Konstruktor Fahrzeug::Fahrzeug(int Raeder, double Gewicht) : _Raeder(Raeder), _Gewicht(Gewicht) {} // Gib das Gewicht pro Rad zurueck double Fahrzeug::Radlast() const { return _Gewicht / _Raeder; } // Ausgabe ostream& operator << (ostream& s, const Fahrzeug &v) { Institut für Geometrie und Praktische Mathematik, RWTH Aachen 6 s << "Das Fahrzeug hat " << v._Raeder << ((v._Raeder>1)?" Raeder":" Rad") << " und wiegt " << v._Gewicht << " kg. "; return s << endl; } Institut für Geometrie und Praktische Mathematik, RWTH Aachen 7 Anwendung der Basis-Klasse Fahrzeug #include <iostream> #include "fahrzeug.hpp" using namespace std; int main() { Fahrzeug Auto(4,900.0), Motorrad(2,400.0), Laster(18,20500.0), Ente(4,700.0); cout << Auto; cout << "Der Laster hat ein Gewicht von " << Laster.Radlast() << " kg pro Rad." << endl; Institut für Geometrie und Praktische Mathematik, RWTH Aachen 8 cout << "Das Motorrad wiegt " << Motorrad.Gewicht() << " kg." << endl; cout << Ente; cout << "Die Ente ist ein " << Ente.Name() << "." << endl; return 0; } Institut für Geometrie und Praktische Mathematik, RWTH Aachen 9 Das Programm liefert folgende Ausgabe: Das Der Das Das Die Fahrzeug hat 4 Raeder und wiegt 900 kg. Laster hat ein Gewicht von 1138.89 kg pro Rad. Motorrad wiegt 400 kg. Fahrzeug hat 4 Raeder und wiegt 700 kg. Ente ist ein Fahrzeug. Institut für Geometrie und Praktische Mathematik, RWTH Aachen 10 Die abgeleitete Klasse Auto (Header-File) #ifndef _AUTO_HPP #define _AUTO_HPP #include "fahrzeug.hpp" class Auto : public Fahrzeug { int _Passagiere; public: Auto(int Raeder, double Gewicht, int Leute = 4) : Fahrzeug(Raeder, Gewicht), _Passagiere(Leute) {} int Passagiere () const { return _Passagiere; } const char *Name () const {return "Auto";} int Fuehrerscheinklasse () const {return 3;} }; Institut für Geometrie und Praktische Mathematik, RWTH Aachen 11 #endif Institut für Geometrie und Praktische Mathematik, RWTH Aachen 12 Die abgeleitete Klasse Laster (Header-File) #ifndef _LASTER_HPP #define _LASTER_HPP #include "fahrzeug.hpp" class Laster : public Fahrzeug { int _Besatzung; double _Ladung; public: Laster(int Raeder, double Gewicht, int Besatzung = 2, double Ladung = 10000.0) : Fahrzeug(Raeder, Gewicht), _Besatzung(Besatzung), _Ladung(Ladung) {} Institut für Geometrie und Praktische Mathematik, RWTH Aachen 13 double Effizienz () const { return _Ladung / (_Ladung + _Gewicht); } int Besatzung const char *Name int () const {return _Besatzung;} () const { return "Lastwagen"; } Fuehrerscheinklasse () const { return 2; } }; #endif Institut für Geometrie und Praktische Mathematik, RWTH Aachen 14 Anwendung der abgeleiteten Klassen #include #include #include #include <iostream> "fahrzeug.hpp" "auto.hpp" "laster.hpp" using namespace std; int main() { Fahrzeug Hochrad(1, 5.7); cout << Hochrad; cout << "Die Radlast des Hochrades betraegt " << Hochrad.Radlast() << " kg " << endl << "auf dem einzigen Rad." << endl << endl; Auto Ente(4, 700.0, 5); Institut für Geometrie und Praktische Mathematik, RWTH Aachen 15 cout << Ente; cout << "Die Ente fuehrt " << Ente.Passagiere() << " Passagiere mit sich. " << endl << "Die Radlast der Ente ist " << Ente.Radlast() << " kg pro Rad." << endl << "Die Ente ist ein " << Ente.Name() << "." << endl << endl; Laster Sattelschlepper(18, 5700.0, 1, 15300.0); cout << Sattelschlepper; cout << "Die Effizienz des Sattelschleppers ist " << 100.0 * Sattelschlepper.Effizienz() << " Prozent." << endl << "Der Sattelschlepper ist ein " << Sattelschlepper.Name() << "." << endl; return 0; } Institut für Geometrie und Praktische Mathematik, RWTH Aachen 16 Ausgabe des Programms Das Fahrzeug hat 1 Rad und wiegt 5.7 kg. Die Radlast des Hochrades betraegt 5.7 kg auf dem einzigen Rad. Das Die Die Die Fahrzeug hat 4 Raeder und wiegt 700 kg. Ente fuehrt 5 Passagiere mit sich. Radlast der Ente ist 175 kg pro Rad. Ente ist ein Auto. Das Fahrzeug hat 18 Raeder und wiegt 5700 kg. Die Effizienz des Sattelschleppers ist 72.8571 Prozent. Der Sattelschlepper ist ein Lastwagen. Institut für Geometrie und Praktische Mathematik, RWTH Aachen 17 Konstruktoren/Destruktoren bei Vererbung #include <iostream> using namespace std; class basis { public: basis() { cout << "Konstruktor fuer Klasse ’basis’" << endl; } ~basis() { cout << "Destruktor fuer Klasse ’basis’" << endl; } }; class abgeleitet : public basis { public: abgeleitet() { cout << "Konstruktor fuer Klasse ’abgeleitet’" << endl;} ~abgeleitet() { cout << "Destruktor fuer Klasse ’abgeleitet’" << endl; } }; Institut für Geometrie und Praktische Mathematik, RWTH Aachen 18 int main() { cout << "Zuerst definiere Variable vom Typ ’basis’ : " << endl; basis b; cout << "Nun definiere Variable vom Typ ’abgeleitet’ : " << endl; abgeleitet a; cout << "Compiler ruft Destruktoren automatisch auf : " << endl; return 0; } Institut für Geometrie und Praktische Mathematik, RWTH Aachen 19 Ausgabe des Programms Zuerst definiere Variable vom Typ ’basis’ : Konstruktor fuer Klasse ’basis’ Nun definiere Variable vom Typ ’abgeleitet’ : Konstruktor fuer Klasse ’basis’ Konstruktor fuer Klasse ’abgeleitet’ Compiler ruft Destruktoren automatisch auf : Destruktor fuer Klasse ’abgeleitet’ Destruktor fuer Klasse ’basis’ Destruktor fuer Klasse ’basis’ Institut für Geometrie und Praktische Mathematik, RWTH Aachen 20 Anmerkungen zur Vererbung -1- −→ Um eine Klasse B von einer Basis-klasse A abzuleiten, verwendet man die Deklaration class B : public A { ... }; −→ Die Basisklasse kann in mancherlei Hinsicht als Element der abgeleiteten Klasse aufgefasst werden. Die Basisklasse wird somit quasi in die abgeleitete Klasse eingebettet. −→ Das Schlüsselwort public bedeutet, daß alle öffentlichen Daten von A von allen Funktionen auch außerhalb von B benutzt werden können. Jede Funktion kann einen B* in einen A* konvertieren. Institut für Geometrie und Praktische Mathematik, RWTH Aachen 21 Anmerkungen zur Vererbung -2- −→ Wird stattdessen das Schlüsselwort private verwendet, so dürfen nur noch die Funktionen von B und friends von B die öffentlichen Daten von A benutzen, und nur diese dürfen einen B* in einen A* konvertieren. −→ Abgeleitete Klassen haben im allgemeinen sowohl zusätzliche Elemente als auch zusätzliche Elementfunktionen, die sie von den anderen von der gleichen BasisKlasse abgeleiteten Klassen unterscheiden. −→ Die Element-Funktionen der abgeleiteten Klasse können auf die privaten Daten der Basisklasse zugreifen, wenn in deren Deklaration das Schlüsselwort protected anstatt von private verwendet wird. Die Verwendung von protected sollte aber nicht zur Gewohnheit werden. Die sauberere Lösung ist meistens die, bei der abgeleitete Klassen nicht auf die privaten Daten der Basisklasse zugreifen. Institut für Geometrie und Praktische Mathematik, RWTH Aachen 22 Anmerkungen zur Vererbung -3- −→ Vererbung kann sich über mehrere Generationen erstrecken, d.h., abgeleitete Klassen können selbst wieder als Basisklasse anderer Klassen 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. aus den Klassen Boot und Flugzeug abgeleitet werden durch class Wasserflugzeug : public Boot, public Flugzeug { ... Institut für Geometrie und Praktische Mathematik, RWTH Aachen }; 23 Beispiel: Problem bei Vererbung #include <iostream> #include "laster.hpp" #include "auto.hpp" using namespace std; int main() { Fahrzeug* Fuhrpark[3]; Fuhrpark[0] = new Fahrzeug( 1, 5.7); Fuhrpark[1] = new Auto ( 4, 700.0); Fuhrpark[2] = new Laster (18, 5700.0); for (int i=0; i<3; i++) { cout << (Fuhrpark[i])->Name() << endl; } return 0; } Institut für Geometrie und Praktische Mathematik, RWTH Aachen 24 Ausgabe des Programms Fahrzeug Fahrzeug Fahrzeug Institut für Geometrie und Praktische Mathematik, RWTH Aachen 25 Beispiel: Das gleiche Problem bei Vererbung #include <iostream> using namespace std; class basis { public: void name() const { cout << "basis" << endl; } }; class abgeleitet : public basis { public: void name() const { cout << "abgeleitet" << endl; } }; Institut für Geometrie und Praktische Mathematik, RWTH Aachen 26 void AusgabeName(const basis &b){ b.name(); } int main() { basis b; abgeleitet a; cout << "Normale Ausgabe : " << endl; b.name(); a.name(); cout << "Ausgabe ueber Funktion ’AusgabeName’ : " << endl; AusgabeName(b); AusgabeName(a); return 0; } Institut für Geometrie und Praktische Mathematik, RWTH Aachen 27 Ausgabe des Programms Normale Ausgabe : basis abgeleitet Ausgabe ueber Funktion ’AusgabeName’ : basis basis Institut für Geometrie und Praktische Mathematik, RWTH Aachen 28 Beispiel: Ausweg durch virtuelle Funktionen #include <iostream> using namespace std; class basis { public: virtual void name() const { cout << "basis" << endl; } }; class abgeleitet : public basis { public: void name() const { cout << "abgeleitet" << endl; } }; Institut für Geometrie und Praktische Mathematik, RWTH Aachen 29 void AusgabeName(const basis &b){ b.name(); } int main() { basis b; abgeleitet a; cout << "Normale Ausgabe : " << endl; b.name(); a.name(); cout << "Ausgabe ueber Funktion ’AusgabeName’ : " << endl; AusgabeName(b); AusgabeName(a); return 0; } Institut für Geometrie und Praktische Mathematik, RWTH Aachen 30 Ausgabe des Programms Normale Ausgabe : basis abgeleitet Ausgabe ueber Funktion ’AusgabeName’ : basis abgeleitet Institut für Geometrie und Praktische Mathematik, RWTH Aachen 31 Beispiel: Das Fahrzeug Ersetzt man entsprechend in der Deklaration der Klasse Fahrzeug die Deklaration der Funktion Name durch virtual char *Name () const { return "Fahrzeug"; } so wird die Funktion in den abgeleiteten Klassen nicht nur redefiniert, Pointer oder Referenzen auf die abgeleiteten Klassen, die auf die Basisklasse ’gecastet’ werden, benutzen die Elementfunktionen der abgeleiteten Klasse. Unser Beispielprogramm der Fuhrparks liefert jetzt das gewünschte Ergebnis: Fahrzeug Auto Lastwagen Institut für Geometrie und Praktische Mathematik, RWTH Aachen 32 Beispiel: Das Fahrzeug -2Fügt man dem Programm die Zeile cout << (Fuhrpark[2])->Fahrzeug::Name() << endl; hinzu, so gibt es Fahrzeug aus. Man kann also die Benutzung der Funktion Name der Basisklasse durch Benutzung des Operators :: erzwingen. Institut für Geometrie und Praktische Mathematik, RWTH Aachen 33 Anmerkungen zu virtuellen Funktionen −→ Virtuelle Funktionen sind Funktionen der Basisklasse, die in einer abgeleiteten Klassen redefiniert werden. −→ Die Deklaration einer virtuellen Funktion in der Basisklasse ist durch das Schlüsselwort virtual gekennzeichnet. Bei der Redefinition in einer abgeleiteten Klasse braucht das Schlüsselwort virtual nicht mehr mitangegeben zu werden. −→ Soll in einer abgeleiteten Klasse ausnahmsweise einmal die virtuelle Funktion der Basisklasse anstatt der redefinierten Funktion aufgerufen werden, so kann man dazu den Bereichsauflösungsoperator :: verwenden, z.B. Fahrzeug::Name(); Institut für Geometrie und Praktische Mathematik, RWTH Aachen 34 Anmerkungen zu virtuellen Funktionen -2- −→ Sobald polymorphe Objekte verwendet werden, sollten die Destruktoren virtuell sein. virtual Institut für Geometrie und Praktische Mathematik, RWTH Aachen Fahrzeug(); 35 Laufzeittypüberprüfung #include <iostream> #include "auto.hpp" #include "laster.hpp" using namespace std; void SicherCasten(Fahrzeug *kfz) { Laster *p = dynamic_cast<Laster *>(kfz); if( p ) { cout << "Die Effizienz betraegt " << p->Effizienz() << endl; } else { cout << "Das ist kein Laster!" << endl; } } int main() Institut für Geometrie und Praktische Mathematik, RWTH Aachen 36 { // // // // Bemerkung: man darf die Variable vom Typ Auto natuerlich nicht "auto" nennen, so wie ich es in der Vorlesung versucht habe, weil auto ein C++ Schluesselwort ist! Auto autom(4, 1200.0, 5); Laster laster(6, 4000.0, 3, 12); SicherCasten(&autom); SicherCasten(&laster); return 0; } Institut für Geometrie und Praktische Mathematik, RWTH Aachen 37 Beispiel: Abstrakte Deklaration der Klasse Fahrzeug #ifndef VEHIKEL_HPP #define VEHIKEL_HPP #include <iostream> using namespace std; class Vehikel { protected: int _Raeder; double _Gewicht; public: Vehikel (int Raeder, double Gewicht); int Raeder double Gewicht double Radlast () const {return _Raeder;} () const {return _Gewicht;} () const; Institut für Geometrie und Praktische Mathematik, RWTH Aachen 38 friend ostream& operator << (ostream& s, const Vehikel &v); // Die beiden folgenden Funktionen sind rein virtuell. virtual const char *Name() const = 0; virtual int Fuehrerscheinklasse() const = 0; }; #endif Institut für Geometrie und Praktische Mathematik, RWTH Aachen 39 Beispiel: Eine abstrakte Klasse #include <iostream> #include "auto_v.hpp" #include "laster_v.hpp" using namespace std; int main() { // Vehikel Hochrad; // Verboten, da abstrakte Basisklasse Auto Ente(4, 700.0, 5); cout << "Typ : " << Ente.Name() << endl; cout << Ente; cout << << << << "Die Ente fuehrt " << Ente.Passagiere() " Passagiere mit sich." << endl "Die Radlast der Ente ist " Ente.Radlast() << " kg pro Rad." << endl; Institut für Geometrie und Praktische Mathematik, RWTH Aachen 40 cout << "Man kann ein " << Ente.Name() << " mit Fuehrerscheinklasse " << Ente.Fuehrerscheinklasse() << " fahren.\n\n"; Laster Sattelschlepper(18, 5700.0, 1, 15300.0); cout << "Typ : " << Sattelschlepper.Name() << endl; cout << Sattelschlepper; cout << "Die Effizienz des Sattelschleppers ist " << 100.0 * Sattelschlepper.Effizienz() << " Prozent.\n"; cout << "Man kann einen " << Sattelschlepper.Name() << " mit Fuehrerscheinklasse " << Sattelschlepper.Fuehrerscheinklasse() << " fahren.\n"; return 0; } Institut für Geometrie und Praktische Mathematik, RWTH Aachen 41 Institut für Geometrie und Praktische Mathematik, RWTH Aachen 42 Dieses Programm produziert folgende Ausgabe: Typ Das Die Die Man : Auto Fahrzeug hat 4 Raeder und wiegt 700 kg. Ente fuehrt 5 Passagiere mit sich. Radlast der Ente ist 175 kg pro Rad. kann ein Auto mit Fuehrerscheinklasse 3 fahren. Typ Das Die Man : Lastwagen Fahrzeug hat 18 Raeder und wiegt 5700 kg. Effizienz des Sattelschleppers ist 72.8571 Prozent. kann einen Lastwagen mit Fuehrerscheinklasse 2 fahren. Institut für Geometrie und Praktische Mathematik, RWTH Aachen 43 Abstrakte Klassen −→ Eine virtuelle Funktion, die in der Basisklasse als konstant Null vorinitialisiert ist, wird als rein virtuelle Funktion bezeichnet: virtual int rein virtuell(int, char *) = 0; Die Initialisierung ”=0” ist wie ein Schlüsselwort zu interpretieren und kann nicht auf normale Funktionen angewendet werden. −→ Eine Klasse mit mindestens einer virtuellen Funktion heißt abstrakte Klasse. Diese werden ausschließlich als Basisklassen verwendet, d.h., um andere Klassen daraus abzuleiten. −→ Objekte einer abstrakten Klasse können nicht erzeugt werden. Institut für Geometrie und Praktische Mathematik, RWTH Aachen 44 Abstrakte Klassen -2−→ Rein virtuelle Funktionen, die in einer abgeleiteten Klasse nicht redefiniert werden, bleiben rein virtuell. In diesem Fall ist auch die abgeleitete Klasse abstrakt. −→ Abstrakte Klassen werden typischerweise zur Definition von Schnittstellen eingesetzt. Institut für Geometrie und Praktische Mathematik, RWTH Aachen
© Copyright 2024 ExpyDoc