Vererbung und Klassenhierarchien • Abgeleitete Klassen

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