• Keine Ergebnisse gefunden

Anmerkung: In diesem Skript wird der Begriff mehrfaches Erben (oder mehrfaches Beerben, mit Dank an Christoph Knabe) anstelle der verbreiteten Bezeichnung mehrfache Vererbung

Im Dokument C++ für Java-Programmierer (Seite 102-107)

verwendet. Die verbreitete Bezeichnung wird nicht in ihrer wörtlichen Bedeutung verwendet (in allen OO-Programmiersprachen darf eine Klasse ihre Elemente mehrfach an andere Klassen vererben, aber das ist mit mehrfacher Vererbung nicht gemeint), sondern in einer eher

künstlichen Bedeutung ("ein Klasse K darf mehrere andere Klassen zum vererben ihrer Elemente an K auffordern"). Das englische Verb to inherit bedeutet erben und nicht vererben (vererben heisst im Englischen to leave, to bequeath) und somit sollte man den englischen Fachausdruck multiple inheritance auch nicht mit mehrfacher Vererbung übersetzen. Ein letztes Argument: In OO-Programmiersprachen (Smalltalk, Eiffel, C++, Java) wird immer nur das Erben konkret notiert, an welche Klassen vererbt wird kann höchstens im Kommentar angegeben werden.

1. Mehrfaches Erben, ein einfaches (?) Beispiel

1 // Datei Mehrfach01.cpp

2 // ---3

// Demonstriert einen einfachen Fall von Mehrfachbeerbung. Die Klasse Tief 4

// beerbt die beiden Klassen Links und Rechts. Die Klassennamen Tief, Links 5

// und Rechts sollen die Positionen der Klassen in einem Beerbungsgraphen 6

// deutlich machen.

7

// ---8 #include <iostream>

9

using namespace std;

10

// ---11

class Links { // Eine Basisklasse von Tief ("vererbt an Tief") 12

// Jedes Objekt dieser Klasse stellt eine (rechteckige) Flaeche dar.

13

public:

14

// Ein Standard- und allgemeiner Konstruktor:

15 Links(int laenge=0, int breite=0) { 16

setLaenge(laenge);

17

setBreite(breite);

18

} // Konstruktor Links 19

20

// Methoden:

21 int getFlaeche() const {return laenge * breite;}

22

void setLaenge(int laenge) {

23 // Statt einer negativen Laenge wird 0 genommen:

24

this->laenge = laenge > 0 ? laenge : 0;

25

} // setLaenge 26

27

void setBreite(int breite) {

28 // Statt einer negativen Breite wird 0 genommen:

29 this->breite = (breite > 0) ? breite : 0;

30 } // setBreite 31

32

protected:

33

int laenge;

34 int breite;

class Rechts { // Noch eine Basisklasse von Tief 38

// Jedes Objekt dieser Klasse stellt ein Gewicht dar.

39 public:

40

// Ein (Standard-) Konstruktor:

41 Rechts(double gewicht=0) { 42 setGewicht(gewicht);

43 } // Konstruktor Rechts 44

45 // Methoden:

46 double getGewicht() const {return gewicht;}

47 ---57 class Tief: public Links, public Rechts {

58

// Jedes Objekt dieser Klasse stellt eine Flaeche dar, die mit einem 59

// bestimmten Gewicht belastet ist.

60

public:

61 // Ein Standard- und allgemeiner Konstruktor:

62 Tief(int laenge=0, int breite=0, double gewicht=0) : 63 Links(laenge, breite), Rechts(gewicht) {

cout << "Programm Mehrfach01: Jetzt geht es los!" << endl << endl;

76 Tief otto(12, 5, 150.0);

cout << "otto.getGewicht(): " << otto.getGewicht() << endl;

81

cout << "otto.getFlaeche(): " << otto.getFlaeche() << endl;

82 cout << "otto.druck (): " << otto.druck () << endl << endl;

83

84 cout << "emil.getGewicht(): " << emil.getGewicht() << endl;

85

cout << "emil.getFlaeche(): " << emil.getFlaeche() << endl;

86

cout << "emil.druck (): " << emil.druck () << endl << endl;

87 88

cout << "karl.getGewicht(): " << karl.getGewicht() << endl;

89

cout << "karl.getFlaeche(): " << karl.getFlaeche() << endl;

90

cout << "karl.druck (): " << karl.druck () << endl << endl;

91

92 cout << "Programm Mehrfach01: Das war's erstmal!" << endl;

93

} // main 94

/* ---95 Ausgabe des Programms Mehrfach01:

96 97

Programm Mehrfach01: Jetzt geht es los!

98 99

otto.getGewicht(): 150 100 otto.getFlaeche(): 60 101 otto.druck (): 2.5

102

111 Programm Mehrfach01: Das war's erstmal!

112 --- */

2. Mehrfaches Erben, virtuelle, überladene und überschriebene Methoden

1 // Datei Mehrfach02.cpp 2

// ---3 // Demonstriert mehrfache Beerbung (die Klasse Tief erbt von Links und 4 // Rechts), das Ueberladen und Ueberschreiben von Funktionsnamen (virginia, 5 // nicoline und nikolaus) und eindeutige/mehrdeutige Funktionsaufrufe.

6 // Die Funktion virginia ist virtuell, nicoline und nikolaus sind *nicht*

7 // virtuell, nicoline und nikolaus gibt es jeweils *ohne* Parameter bzw.

8

// mit einem int-Parameter.

9 // Innerhalb einer Klasse genuegt es, wenn gleichnamige Funktionen sich 10 // durch ihre Parameter unterscheiden (siehe nicoline in Klasse Tief). Bei 11 // Funktionen aus verschiedenen Klassen genuegt das nicht (siehe nikolaus 12 // aus Klasse Links und nikolaus aus Klasse Rechts).

13

// ---14

#include <iostream>

15

#include <string>

16 ---32 class Rechts { // Noch eine Basisklasse von Tief

33 public:

class Tief: public Links, public Rechts { 47

52 string nicoline() {

cout << "zt->Links ::virginia(): " << zt->Links ::virginia() << endl;

73

cout << "zt->Rechts::virginia(): " << zt->Rechts::virginia() << endl;

74

81 cout << "zt->Links ::nicoline(): " << zt->Links ::nicoline() << endl;

82 cout << "zt->Rechts::nicoline(): " << zt->Rechts::nicoline() << endl;

83

cout << "zt->Links ::nikolaus(): " << zt->Links ::nikolaus() << endl;

90

cout << "zt->Rechts::nikolaus(1): " << zt->Rechts::nikolaus(5) << endl;

91

92 cout << endl << "nicoline ist ueberladen, aber eindeutig:" << endl;

93 cout << "zt-> nicoline(): " << zt-> nicoline() << endl;

Fehlermeldung des Gnu-Compilers, wenn Zeile 88 kein Kommentar ist:

99 100

Mehrfach02.cpp: In function `int main(...)':

101

Mehrfach02.cpp:88: request for method `nikolaus' is ambiguous 102

---103

Ausgabe des Programms Mehrfach02:

104

105 Mehrfach02: Jetzt geht es los!

106 virginia ist virtuell:

107 zt-> virginia(): virginia aus Klasse Tief!

108

zt->Links ::virginia(): virginia aus Klasse Links!

109

zt->Rechts::virginia(): virginia aus Klasse Rechts!

110

zl-> virginia(): virginia aus Klasse Tief!

111

zr-> virginia(): virginia aus Klasse Tief!

112

113 nicoline ist nicht virtuell:

114 zt-> nicoline(): nicoline aus Klasse Tief!

115

zt->Links ::nicoline(): nicoline aus Klasse Links!

116 zt->Rechts::nicoline(): nicoline aus Klasse Rechts!

117 zl-> nicoline(): nicoline aus Klasse Links!

118 zr-> nicoline(): nicoline aus Klasse Rechts!

119

120 nikolaus ist mehrdeutig:

121 zt->Links ::nikolaus(): nikolaus aus Klasse Links!

122 zt->Rechts::nikolaus(1): nikolaus aus Klasse Rechts!

123

124 nicoline ist ueberladen, aber eindeutig:

125

zt-> nicoline(): nicoline aus Klasse Tief!

126

zt-> nicoline(5): nicoline mit int-Param!

127

--- */

Rein virtuelle Methoden (pure virtual methods) haben eigentlich wenig mit virtuellen Methoden zu tun. Sie entsprechen genau den abstrakten Methoden in Java. Für eine rein virtuelle Methode braucht und darf man keinen Rumpf angeben (stattdessen "= 0"). Eine Klasse, die mindestens eine rein virtuelle Methode enhält, wird dadurch automatisch zu einer abstrakten Klasse. Von einer solchen Klasse kann man keine Objekte vereinbaren, sie kann nur als Oberklasse für weitere Klassendefinitionen dienen. Die erbenden Klassen müssen die geerbten rein virtuellen Methoden mit richtigen Methoden überschreiben, um konkrete Klassen zu sein. Hier ein kleines Beispielpro-gramm:

1 // Datei Virtuell02.cpp

2 /* ---3 Demonstriert eine Klasse Hoch die 2 rein virtuelle Methoden (pure virtual 4 methods) enthaelt. Die Klasse wird dadurch automatisch abstrakt.

5 --- */

6 #include <iostream>

7 using namespace std;

8

// ---9

// Die Klasse Hoch ist abstrakt:

10

class Hoch { 11

protected:

12

int zahl;

13

public:

14

virtual int getZahl() const = 0; // Eine rein virtuelle Methode 15

virtual void setZahl(int zahl) = 0; // Eine rein virtuelle Methode 16

}; // class Hoch 17

// ---18

// Die Klasse Tief erbt von Hoch, implementiert die rein virtuellen 19

// Methoden und ist somit eine konkrete Klasse:

20

class Tief: public Hoch { 21 public:

22

int getZahl() const {return zahl;}

23 void setZahl(int zahl) {this->zahl = zahl;}

24 }; // class Tief

25 // ---26 void main() {

27

cout << "Virtuell02: Jetzt geht es los!" << endl;

28

// Hoch h1;

29

Tief t1;

30

t1.setZahl(17);

31

cout << "t1.getZahl(): " << t1.getZahl() << endl;

32 cout << "Virtuell02: Das war's erstmal!" << endl;

33 } // main

34 /* ---35 Fehlermeldung des Gnu-Compilers, wenn Zeile 28 kein Kommentar ist:

36

37 Virtuell02.cpp:28: cannot declare variable `h1' to be of type `Hoch' 38 Virtuell02.cpp:28: since the following virtual functions are abstract:

39 Virtuell02.cpp:15: void Hoch::setZahl(int) 40 Virtuell02.cpp:14: int Hoch::getZahl() const

41 ---42 Ausgabe des Programms Virtuell02:

43 44

Virtuell02: Jetzt geht es los!

45 t1.getZahl(): 17

46 Virtuell02: Das war's erstmal!

47

--- */

3. Mehrfaches Erben, die Nappo-Frage

Ein Nappo ist eine Raute (aus klebrigem Nougat, mit Schokolade überzogen). Wenn ein Ver-erbungs-Graph Rauten enthält, sollte der Programmierer sich unbedingt die Nappo-Frage stellen (und möglichst korrekt beantworten). Hier ein Beispiel für einen Vererbungs-Graphen mit einem mehrfachen Nappo darin.

Die Knoten eines Vererbungs-Graphen sind (mit) Klassen (beschriftet). Ein Pfeil von A nach B bedeutet: Die Klasse A erbt von der Klasse B.

Im Beispiel bilden schon die äußeren 4 Klassen (Hoch, Links, Rechts und Tief) allein einen

Im Dokument C++ für Java-Programmierer (Seite 102-107)