vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Das folgende Programm vereint viele der fortschrittlicheren Techniken, die Sie in den letzten drei Wochen in harter Arbeit gelernt haben. Es implementiert auf der Basis von Templates eine verkettete Liste und die dazugehörige Fehlerbehandlung. Gehen Sie das Programm sorgfältig durch. Wenn Sie alles genau verstehen, sind Sie bereits zu einem C++-Programmierer gereift.

Wenn Ihr Compiler Templates oder try und catch nicht unterstützt, können Sie dieses Listing nicht kompilieren und ausführen.

Listing R3.1 Rückblick auf die dritte Woche

1:   // ********************************************
2: //
3: // Title: Rückblick auf die dritte Woche
4: //
5: // File: Week3
6: //
7: // Beschreibung: Programm, das anhand einer verketteten Liste den
8: // Einsatz von Templates und Exceptions demonstriert
9:
10: // Klassen: PART - enthält die Teilenummern und eventuell andere
11: // Informationen über die Teile. Hilfsklasse zur
11: // Demonstration der Liste
12: //
13: // Beachten Sie die Verwendung des <<-Operators
14: // zur Ausgabe von Informationen über ein
15: // Laufzeitobjekt
16: //
17: // Node - fungiert als Knoten in List
18: //
19: // List - Template-basierte Liste, die die Mechanismen
20: // für eine verkettete Liste bereitstellt
21: //
22: //
23: // Autor: Jesse Liberty (jl)
24: //
25: // Entwickelt: Pentium 200 Pro. 128MB RAM MVC 5.0
26: //
27: // Ziel: Plattformunabhängig
28: //
29: // Rev History: 9/94 - First release (jl)
30: // 4/97 - Updated (jl)
31: // **************************************************
32:
33: #include <iostream.h>
34:
35: // Ausnahmeklassen
36: class Exception {};
37: class OutOfMemory : public Exception{};
38: class NullNode : public Exception{};
39: class EmptyList : public Exception {};
40: class BoundsError : public Exception {};
41:
42:
43: // **************** Teile ************
44: // // Abstrakte Basisklasse für die Teile
45: class Part
46: {
47: public:
48: Part():itsObjectNumber(1) {}
49: Part(int ObjectNumber):itsObjectNumber(ObjectNumber){}
50: virtual ~Part(){};
51: int GetObjectNumber() const { return itsObjectNumber; }
52: virtual void Display() const =0; // muss überschrieben werden
53:
54: private:
55: int itsObjectNumber;
56: };
57:
58: // Implementierung einer abstrakten Funktion, damit
59: // abgeleitete Klassen die Funktion überschreiben
60: void Part::Display() const
61: {
62: cout << "\nTeilenummer: " << itsObjectNumber << endl;
63: }
64:
65: // Dieser eine <<-Operator wird für alle Part-Objekte aufgerufen.
66: // Er braucht kein Friend zu sein, da er nicht auf private Daten
67: // zugreift. Er ruft die polymorphe Funktion Display() auf. Es wäre
68: // schön, wenn man den Operator überschreiben und für den Laufzeittyp
69: // der Objekte aufrufen lassen könnte, aber in C++ geht das nicht
70: ostream& operator<<( ostream& theStream,Part& thePart)
71: {
72: thePart.Display(); // virtuelle Kontravarianz!
73: return theStream;
74: }
75:
76: // **************** Autoteile ************
77: class CarPart : public Part
78: {
79: public:
80: CarPart():itsModelYear(94){}
81: CarPart(int year, int partNumber);
82: int GetModelYear() const { return itsModelYear; }
83: virtual void Display() const;
84: private:
85: int itsModelYear;
86: };
87:
88: CarPart::CarPart(int year, int partNumber):
89: itsModelYear(year),
90: Part(partNumber)
91: {}
92:
93: void CarPart::Display() const
94: {
95: Part::Display();
96: cout << "Baujahr: " << itsModelYear << endl;
97: }
98:
99: // **************** Flugzeugteile ************
100: class AirPlanePart : public Part
101: {
102: public:
103: AirPlanePart():itsEngineNumber(1){};
104: AirPlanePart(int EngineNumber, int PartNumber);
105: virtual void Display() const;
106: int GetEngineNumber()const { return itsEngineNumber; }
107: private:
108: int itsEngineNumber;
109: };
110:
111: AirPlanePart::AirPlanePart(int EngineNumber, int PartNumber):
112: itsEngineNumber(EngineNumber),
113: Part(PartNumber)
114: {}
115:
116: void AirPlanePart::Display() const
117: {
118: Part::Display();
119: cout << "Motor-Nr.: " << itsEngineNumber << endl;
120: }
121:
122: // Vorwärtsdeklaration der Klasse List
123: template <class T>
124: class List;
125:
126: // **************** Knoten ************
127: // Allgemeiner Knoten, der in eine Liste eingefügt werden kann
128: // ************************************
129:
130: template <class T>
131: class Node
132: {
133: public:
134: friend class List<T>;
135: Node (T*);
136: ~Node();
137: void SetNext(Node * node) { itsNext = node; }
138: Node * GetNext() const;
139: T * GetObject() const;
140: private:
141: T* itsObject;
142: Node * itsNext;
143: };
144:
145: // Implementierungen für Node...
146:
147: template <class T>
148: Node<T>::Node(T* pOjbect):
149: itsObject(pOjbect),
150: itsNext(0)
151: {}
152:
153: template <class T>
154: Node<T>::~Node()
155: {
156: delete itsObject;
157: itsObject = 0;
158: delete itsNext;
159: itsNext = 0;
160: }
161:
162: // Liefert NULL zurück, wenn kein nächster PartNode vorhanden
163: template <class T>
164: Node<T> * Node<T>::GetNext() const
165: {
166: return itsNext;
167: }
168:
169: template <class T>
170: T * Node<T>::GetObject() const
171: {
172: if (itsObject)
173: return itsObject;
174: else
175: throw NullNode();
176: }
177:
178: // **************** List ************
179: // Generisches Listen-Template
180: // für beliebige indizierte Objekte
181: // ***********************************
182: template <class T>
183: class List
184: {
185: public:
186: List();
187: ~List();
188:
189: T* Find(int & position, int ObjectNumber) const;
190: T* GetFirst() const;
191: void Insert(T *);
192: T* operator[](int) const;
193: int GetCount() const { return itsCount; }
194: private:
195: Node<T> * pHead;
196: int itsCount;
197: };
198:
199: // Implementierungen für List...
200: template <class T>
201: List<T>::List():
202: pHead(0),
203: itsCount(0)
204: {}
205:
206: template <class T>
207: List<T>::~List()
208: {
209: delete pHead;
210: }
211:
212: template <class T>
213: T* List<T>::GetFirst() const
214: {
215: if (pHead)
216: return pHead->itsObject;
217: else
218: throw EmptyList();
219: }
220:
221: template <class T>
222: T * List<T>::operator[](int offSet) const
223: {
224: Node<T>* pNode = pHead;
225:
226: if (!pHead)
227: throw EmptyList();
228:
229: if (offSet > itsCount)
230: throw BoundsError();
231:
232: for (int i=0;i<offSet; i++)
233: pNode = pNode->itsNext;
234:
235: return pNode->itsObject;
236: }
237:
238: // Finde Objekt in der Liste anhand seiner eindeutigen Nummer (ID)
239: template <class T>
240: T* List<T>::Find(int & position, int ObjectNumber) const
241: {
242: Node<T> * pNode = 0;
243: for (pNode = pHead, position = 0;
244: pNode!=NULL;
245: pNode = pNode->itsNext, position++)
246: {
247: if (pNode->itsObject->GetObjectNumber() == ObjectNumber)
248: break;
249: }
250: if (pNode == NULL)
251: return NULL;
252: else
253: return pNode->itsObject;
254: }
255:
256: // Einfügen, wenn die Nummer des Objekts eindeutig ist
257: template <class T>
258: void List<T>::Insert(T* pObject)
259: {
260: Node<T> * pNode = new Node<T>(pObject);
261: Node<T> * pCurrent = pHead;
262: Node<T> * pNext = 0;
263:
264: int New = pObject->GetObjectNumber();
265: int Next = 0;
266: itsCount++;
267:
268: if (!pHead)
269: {
270: pHead = pNode;
271: return;
272: }
273:
274: // ist dieser kleiner als head
275: // dann ist dies der neue head
276: if (pHead->itsObject->GetObjectNumber() > New)
277: {
278: pNode->itsNext = pHead;
279: pHead = pNode;
280: return;
281: }
282:
283: for (;;)
284: {
285: // gibt es keinen nächsten Knoten, den neuen anhängen
286: if (!pCurrent->itsNext)
287: {
288: pCurrent->itsNext = pNode;
289: return;
290: }
291:
292: // gehört der Knoten zwischen diesen und den nächsten,
293: // dann hier einfügen, ansonsten zu nächstem Knoten
294: pNext = pCurrent->itsNext;
295: Next = pNext->itsObject->GetObjectNumber();
296: if (Next > New)
297: {
298: pCurrent->itsNext = pNode;
299: pNode->itsNext = pNext;
300: return;
301: }
302: pCurrent = pNext;
303: }
304: }
305:
306:
307: int main()
308: {
309: List<Part> theList;
310: int choice;
311: int ObjectNumber;
312: int value;
313: Part * pPart;
314: while (1)
315: {
316: cout << "(0)Beenden (1)Auto (2)Flugzeug: ";
317: cin >> choice;
318:
319: if (!choice)
320: break;
321:
322: cout << "Neue Teilenummer?: ";
323: cin >> ObjectNumber;
324:
325: if (choice == 1)
326: {
327: cout << "Baujahr?: ";
328: cin >> value;
329: try
330: {
331: pPart = new CarPart(value,ObjectNumber);
332: }
333: catch (OutOfMemory)
334: {
335: cout << "Nicht genuegend Speicher; Aergerlich..." << endl;
336: return 1;
337: }
338: }
339: else
340: {
341: cout << "Motor-Nummer?: ";
342: cin >> value;
343: try
344: {
345: pPart = new AirPlanePart(value,ObjectNumber);
346: }
347: catch (OutOfMemory)
348: {
349: cout << "Nicht genuegend Speicher; Aergerlich..." << endl;
350: return 1;
351: }
352: }
353: try
354: {
355: theList.Insert(pPart);
356: }
357: catch (NullNode)
358: {
359: cout << "Die Liste ist defekt, der Knoten ist Null!" << endl;
360: return 1;
361: }
362: catch (EmptyList)
363: {
364: cout << "Die Liste ist leer!" << endl;
365: return 1;
366: }
367: }
368: try
369: {
370: for (int i = 0; i < theList.GetCount(); i++ )
371: cout << *(theList[i]);
372: }
373: catch (NullNode)
374: {
375: cout << "Die Liste ist defekt, der Knoten ist Null!" << endl;
376: return 1;
377: }
378: catch (EmptyList)
379: {
380: cout << "Die Liste ist leer!" << endl;
381: return 1;
382: }
383: catch (BoundsError)
384: {
385: cout << "Zugriff hinter das Ende der Liste!" << endl;
386: return 1;
387: }
388: return 0;
389: }
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 2837
Baujahr? 90

(0)Beenden (1)Auto (2)Flugzeug: 2
Neue Teilenummer?: 378
Motor-Nummer?: 4938

(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 4499
Baujahr? 94

(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 3000
Baujahr? 93

(0)Beenden (1)Auto (2)Flugzeug: 0
Teilenummer: 378
Motor-Nr. 4938

Teilenummer: 2837
Baujahr: 90

Teilenummer: 3000
Baujahr: 93

Teilenummer 4499
Baujahr: 94

Dieses Programm ist ein Neufassung des Programms aus dem Rückblick auf die zweite Woche. Hinzugekommen sind Templates, ostream-Verarbeitung und Exception-Behandlung. Die Ausgabe ist jedoch die gleiche geblieben.

Die Zeilen 36 bis 40 deklarieren eine Reihe von Exception-Klassen. In der etwas einfach gehaltenen Exception-Behandlung dieses Programms benötigen die Exceptions keine Daten oder Methoden. Sie dienen als Flags für die catch-Anweisungen, die eine einfache Warnung ausgeben und dann das Programm beenden. Ein etwas robusteres Programm würde diese Exceptions als Referenz übergeben und dann Kontextinformationen oder Daten aus den Exception-Objekten extrahieren, um zu versuchen, das Problem zu beheben.

Zeile 45 deklariert, wie in Woche 2, die abstrakte Basisklase Part. Die einzige interessante Änderung hier ist das Nicht-Klassenelement operator<<(), das in den Zeilen 70 bis 74 deklariert wird. Beachten Sie, daß es sich dabei weder um ein Element von Part noch um einen friend von Part handelt, es übernimmt lediglich eine Referenz auf Part als eines seiner Argumente.

Vielleicht kommen Sie auf die Idee, das es schön wäre, wenn man operator<< für Autoteile (CarPart) und Flugzeugteile (AirPlanePart) überladen könnte, in der Hoffnung, daß dann der korrekte operator<< aufgerufen wird, je nachdem ob ein Autoteil oder ein Flugzeugteil übergeben wird. Da das Programm jedoch einen Zeiger auf ein Part- Objekt übergibt, und nicht einen Zeiger auf ein Autoteil oder ein Flugzeugteil, müßte C++ die entsprechende Funktion auf der Basis des realen Typs eines Arguments der Funktion aufrufen. Man bezeichnet dies als Kontravarianz - ein Konzept, das von C++ nicht unterstützt wird.

Es gibt nur zwei Wege, in C++ Polymorphie zu erreichen: Funktionspolymorphie und virtuelle Funktionen. Funktionspolymorphie geht hier nicht, da sie in beiden Fällen die gleiche Signatur haben: eine Referenz auf Part.

Virtuelle Funktionen funktionieren hier aber auch nicht, da operator<< keine Elementfunktion von Part ist. Sie können operator<< nicht zu einer Elementfunktion von Part machen, da Sie

cout << thePart

aufrufen wollen, und das bedeutet, daß der eigentliche Aufruf an cout.operator<<(Part&) erfolgen würde, und cout hat keine Version von operator<<, die eine Part-Referenz übernimmt!

Um diese Beschränkung zu umgehen, verwendet das Programm der dritten Woche nur einen operator<<, der eine Referenz auf Part übernimmt. Dieser ruft dann die virtuelle Elementfunktion Display() auf, und so wird die korrekte Ausgabe erzeugt.

Die Zeilen 130 bis 143 definieren Node als Template-Klasse. Das Template hat die gleiche Funktion wie Node in dem Programm des Rückblicks auf die Woche 2. Diese Version von Node ist jedoch nicht an ein Part-Objekt gebunden. Sie kann vielmehr als Knoten für beliebigen Objekte fungieren.

Wenn Sie versuchen, auf das Objekt eines Node-Knotens zuzugreifen, für den es kein Objekt gibt, wird dies als Exception behandelt (Zeile 175).

Die Zeilen 182 bis 183 definieren eine generisches Klassentemplate List. Diese List- Klasse kann Knoten jedweder Objekte aufnehmen, die über eine eindeutige Identifikationsnummern verfügen, und sie in aufsteigender Reihenfolge ablegen. Jede der List- Funktionen prüft auf ungewöhnliche Umstände und löst die entsprechenden Exceptions aus.

Das Hauptprogramm erzeugt eine Liste für zwei Arten von Part-Objekten und gibt dann mit Hilfe des Standardstreammechanismus die Werte der Objekte in der Liste aus.

Bei der Besprechung des <<-Operators wurde erwähnt, daß C++ keine Kontravarianz unterstützt. Doch was ist Kontravarianz?

Antwort: Kontravarianz ist die Fähigkeit, einem Zeiger auf eine abgeleitete Klasse einen Zeiger auf eine Basisklasse zuzuweisen

Würde C++ Kontravarianz unterstützen, könnten wir die Funktion für die realen Typen überschreiben, die die Objekte zur Laufzeit haben. Listing R3.2 läßt sich in C++ nicht kompilieren. Würde C++ jedoch Kontravarianz unterstützen, wäre der Code kompilierbar.

Dieses Listing läßt sich nicht kompilieren!

Listing R3.2 Ein Beispiel für Kontravarianz

#include<iostream.h>
class Animal
{
public:
virtual void Speak() { cout << "Animal spricht\n"; }
};

class Dog : public Animal
{
public:
void Speak() { cout << "Dog spricht\n"; }
};


class Cat : public Animal
{
public:
void Speak() { cout << "Cat spricht\n"; }
};

void DoIt(Cat*);
void DoIt(Dog*);

int main()
{

Animal * pA = new Dog;
DoIt(pA);
return 0;
}

void DoIt(Cat * c)
{
cout << "Cat-Objekt uebergeben!\n" << endl;
c->Speak();
}

void DoIt(Dog * d)
{
cout << "Dog-Objekt uebergeben!\n" << endl;
d->Speak();
}

Sie können das Problem jedoch teilweise lösen, indem Sie eine virtuelle Funktion verwenden.

#include<iostream.h>

class Animal
{
public:
virtual void Speak() { cout << "Animal spricht\n"; }
};

class Dog : public Animal
{
public:
void Speak() { cout << "Dog spricht\n"; }
};


class Cat : public Animal
{
public:
void Speak() { cout << "Cat spricht\n"; }
};

void DoIt(Animal*);

int main()
{

Animal * pA = new Dog;
DoIt(pA);
return 0;
}

void DoIt(Animal * c)
{
cout << "Eine Art von Animal wurde uebergeben\n" << endl;
c->Speak();
}



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH