4.1 Was bedeutet Vererbung von Klassen?br />
Stellen wir uns einmal vor, wir hätten von einem kürzlich verstorbenen Verwandten ein nicht ganz so sprotliches Auto vererbt bekommen. Nachdem wir aber über die nötigen Kenntnisse verfügen, nehmen wir ein paar kleinere Eingriffe am Motor und am Fahrwerk vor. Am Ende fährt unser Wagen gut 230 km/h, schwebt nur noch 3 cm über dem Boden und hat keine Rückbank mehr. Diese haben wir als alte Sound-Fetischisten durch einen stattliche Subwoofer ersetzt. Der Kofferraum ist komplett durch den zugehörigen CD-Wechsler mit zugehörigen Verstärker samt Wasserkühlung ausgefüllt.
Unsere Klasse \"Auto\" hat sich also ein wenig verändert. Wir fahren jetzt zwar schneller (natürlich nur da, wo´s erlaubt ist) und können uns während der Fahrt voll auf unsere CD-Sammlung konzentrieren. Leider müssen wir uns von zwei unserer drei Freundinnen trennen, weil die Rückbank weg ist. Und das Überqueren von Bahnübergängen macht uns nur zu klar, das sich die Fahrfunktion wesentlich geöndert hat. Eine Funktion, die wir gänzlich verloren haben, ist \"mit den Kindern in den Urlaub fahren\". Vielleicht war uns das aber auch nicht so wichtig.
Bei der Programmierung von Klassen haben wir ähnliche Möglichkeiten. Stellen wir uns vor, wir haben bereits eine Klasse CFahrzeug definiert, weil wir irgendwann mal ein Objekt benötigt haben, das Kennzeichen, Fahrzeuglänge und Anzahl der Personen speichert, die von dem Fahrzeug befördert werden können. Jetzt benötigen wir für ein neues Projekt weitere Klassen CSportwagen, CBus und CLkw, welche zusätzlich zum Beispiel Beschleunigung (CSportwagen), Fahrzeuglänge (CBus) und zulässiges Beladegewicht (CLkw) speichert. Anstatt jetzt drei komplett neue Klassen zu programmieren, die sich alle nur in einem Merkmal voneinander unterscheiden, leiten wir die drei neuen Klassen aus CFahrzeug ab und fügen jeder Klasse nur eine zusätzliche Variable ein. Die abgeleiteten Klassen erben damit die Eigenschaften und Funktionen der Basisklasse.
4.2 Wie leitet man eine Klasse ab?
Wenn wir eine Klasse B von einer Klasse A ableiten wollen, so trennen wir in der Deklarationszeile der abgeleiteten Klasse die Zeile durch einen Doppelpunkt ab. Dahinter steht die Basisklasse und die Art der Ableitung (public, private, protected). Auf der linken Seite des Doppelpunktes steht wie gewohnt die Deklaration der Klasse, hier der abgeleiteten Klasse. Sehen wir uns ein Beispiel an:
class A
{ // Hier steht die Klassenbeschreibung wie gewohnt };
class B : public A
{ // Hier steht das, was B von A unterscheidet };
Wir müssen uns nun noch gedanken über die Zugriffsrechte auf die Member der abgeleiteten Klassen machen. In welcher Form kann auf verschieden deklarierte Member einer Klasse zugegriffen werden - jeweils abhängig von der Art der Ableitung. Folgende Tabelle gibt Aufschluss:
Ableitung Zugriffsschutz der
Basisklasse Zugriffsmöglichkeit der
Abgeleiteten Klassen
public public
protected
private public
protected
kein Zugriff
private public
protected
private private
private
kein Zugriff
protected public
protected
private protected
protected
kein Zugriff
Die Tabelle ist wie folgt zu lesen: Leite ich (wie im obigen Beispiel der Klasse B) public ab, so gilt die erste Zeile der Tabelle. Dort kann ich sehen, dass public-Elemente in der abgeleiteten Klasse ebenfalls public sind, also auch von außerhalb der Klasse verwendet werden. Auf Elemente, die in der Basisklasse private sind, kann auf keinen Fall zugegriffen werden.
4.3 Konstruktoren und Destruktoren ableiten
Falls vom Programmierer nicht anders angegeben, werden beim Erzeugen von Objekten und deren Zerstörung nach Ende ihrer Lebensdauer die Konstruktoren und Destruktoren automatisch aufgerufen. Bei Konstruktoren muss dazu jedoch ein geeigneter (im Normalfall parameterloser) Standart- (Default-)konstruktor in der Klasse vorhanden sein.
In den meisten Fällen möchten wir jedoch, dass die abgeleitete Klasse Werte an einen parametrisierten Konstruktor der Basisklasse übergibt, um die Elemente der Basisklasse zu füllen. Dies ist besonders dann wichtig, wenn die Basisklasse private Elemente enthält (auf die wir ja bei Ableitungen laut der obigen Tabelle auf keinen Fall erreicht werden können). Nun ist es jedoch nicht möglich, einfach vom Konstruktor der abgeleiteten Klasse den der Basisklasse aufzurufen. Aufgrund der Reihenfolge der Speicherreservierung muss erst der Basisklassenteil inititalisiert werden, bevor die abgeleitete Klasse angelegt wird.
Dazu rufen wir den Konstruktor der Basisklasse auserhalb der Konstruktordefinition innerhalb der Initialisierungsliste des abgeleiteten Konstruktors auf. Dies funktioniert so:
class A
{
A( int, double );
int a;
double b;
}
class B : public A
{
B( int, double, char );
char c;
}
A::A(int i, double ii)
{ a = i; b = ii; }
B::B(int i, double ii, char z) : A(i, ii)
{ c = z; }
Wir sehen hier die Art des Aufrufs des Basisklassenkonstruktors. Es wird deutlich, dass Parameter, die der Basisklasse mitgegeben werden sollen, bereits Parameter des abgeleiteten Konstruktors sein müssen. Dies wird bei der implementierung gerne vergessen!
4.4 Funktionen neu definieren
Wenn wir in einer abgeleiteten Klasse eine Funktion definieren, die bereits in der Basisklasse vorkommt, so haben wir die Funktion nicht überschrieben, sondern quasi überladen. Rufen wir die Funktion auf der abgeleiteten Klasse auf, so kommt die Basisversion nicht zum Einsatz (entgegen den Regeln bei Konstruktoren). Wir können jedoch unter Angabe des Klassennamens auf die Basisfunktion zurückgreifen. Das sieht dann so aus:
class A
{ void f(); }
class B : public A
{ void f(); }
void A::f()
{ // Programmcode hier }
void B::f()
{
A::f();
// Hier zusätzlichen Programmcode
}
|