vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


In dem Programm für den Rückblick auf die zweiten Woche sind viele der Techniken vereint, die Sie im Laufe der letzten 14 Tage kennengelernt haben - entsprechend leistungsstark ist das Programm.

Das Programm demonstriert die Implementierung verketteter Listen und verwendet dazu virtuelle Funktionen, abstrakte Funktionen, überschriebene Funktionen, Polymorphie, öffentliche Vererbung, überladene Funktionen, Endlosschleifen, Zeiger, Referenzen und vieles mehr. Beachten Sie, daß diese verkettete Liste sich von der vorher vorgestellten unterscheidet. In C++ führen viele Wege nach Rom.

Das Ziel dieses Programms ist die Erstellung einer verketteten Liste. Die Knoten der Liste sind so konzipiert, daß man in ihnen Teile (Parts), wie sie in einer Fabrik vorkommen könnten, speichern kann. Obwohl dieses Programm noch keine endgültige Fassung darstellt, veranschaulicht es die anspruchsvolle Datenstruktur bereits recht gut. Der Code hat einen Umfang von 311 Zeilen. Versuchen Sie, den Code erst einmal selbst durchzugehen, bevor sie die Analyse im Anschluß an die Ausgabe lesen.

Listing R2.1 Rückblick auf die zweite Woche

1:     // ******************************************
2: //
3: // Titel: Rueckblick auf die zweite Woche
4: //
5: // Datei: Week2
6: //
7: // Beschreibung: Demonstrationsprogramm zu verketteten Listen
8: //
9: // Klassen: PART - enthaelt die Teilenummern und eventuell andere
10: // Informationen ueber die Teile
11: //
12: // PartNode - fungiert als Knoten in einer PartsList
13: //
14: // PartsList - stellt die Mechanismen fuer eine
15: // verkettete Liste von Teilen bereit
16: //
17: // **************************************************
18:
19: #include <iostream.h>
20:
21:
22:
23: // **************** Teile ************
24:
25: // Abstrakte Basisklasse fuer die Teile
26: class Part
27: {
28: public:
29: Part():itsPartNumber(1) {}
30: Part(int PartNumber):itsPartNumber(PartNumber){}
31: virtual ~Part(){};
32: int GetPartNumber() const { return itsPartNumber; }
33: virtual void Display() const =0; // muss ueberschrieben werden
34: private:
35: int itsPartNumber;
36: };
37:
38: // Implementierung einer abstrakten Funktion, damit
39: // abgeleitete Klassen die Funktion ueberschreiben
40: void Part::Display() const
41: {
42: cout << "\nTeilenummer: " << itsPartNumber << endl;
43: }
44:
45: // **************** Autoteile ************
46:
47: class CarPart : public Part
48: {
49: public:
50: CarPart():itsModelYear(94){}
51: CarPart(int year, int partNumber);
52: virtual void Display() const
53: {
54: Part::Display(); cout << "Baujahr: ";
55: cout << itsModelYear << endl;
56: }
57: private:
58: int itsModelYear;
59: };
60:
61: CarPart::CarPart(int year, int partNumber):
62: itsModelYear(year),
63: Part(partNumber)
64: {}
65:
66:
67: // **************** Flugzeugteile ************
68:
69: class AirPlanePart : public Part
70: {
71: public:
72: AirPlanePart():itsEngineNumber(1){};
73: AirPlanePart(int EngineNumber, int PartNumber);
74: virtual void Display() const
75: {
76: Part::Display(); cout << "Motor-Nr.: ";
77: cout << itsEngineNumber << endl;
78: }
79: private:
80: int itsEngineNumber;
81: };
82:
83: AirPlanePart::AirPlanePart(int EngineNumber, int PartNumber):
84: itsEngineNumber(EngineNumber),
85: Part(PartNumber)
86: {}
87:
88: // **************** Teile-Knoten ************
89: class PartNode
90: {
91: public:
92: PartNode (Part*);
93: ~PartNode();
94: void SetNext(PartNode * node) { itsNext = node; }
95: PartNode * GetNext() const;
96: Part * GetPart() const;
97: private:
98: Part *itsPart;
99: PartNode * itsNext;
100: };
101:
102: // Implementierungen von PartNode ...
103:
104: PartNode::PartNode(Part* pPart):
105: itsPart(pPart),
106: itsNext(0)
107: {}
108:
109: PartNode::~PartNode()
110: {
111: delete itsPart;
112: itsPart = 0;
113: delete itsNext;
114: itsNext = 0;
115: }
116:
117: // Liefert NULL zurueck, wenn kein naechster PartNode vorhanden
118: PartNode * PartNode::GetNext() const
119: {
120: return itsNext;
121: }
122:
123: Part * PartNode::GetPart() const
124: {
125: if (itsPart)
126: return itsPart;
127: else
128: return NULL; // Fehler
129: }
130:
131: // **************** Teileliste ************
132: class PartsList
133: {
134: public:
135: PartsList();
136: ~PartsList();
137: // benötigt Kopierkonstruktor und Zuweisungsoperator!
138: Part* Find(int & position, int PartNumber) const;
139: int GetCount() const { return itsCount; }
140: Part* GetFirst() const;
141: static PartsList& GetGlobalPartsList()
142: {
143: return GlobalPartsList;
144: }
145: void Insert(Part *);
146: void Iterate(void (Part::*f)()const) const;
147: Part* operator[](int) const;
148: private:
149: PartNode * pHead;
150: int itsCount;
151: static PartsList GlobalPartsList;
152: };
153:
154: PartsList PartsList::GlobalPartsList;
155:
156: // Implementierungen fuer Liste...
157:
158: PartsList::PartsList():
159: pHead(0),
160: itsCount(0)
161: {}
162:
163: PartsList::~PartsList()
164: {
165: delete pHead;
166: }
167:
168: Part* PartsList::GetFirst() const
169: {
170: if (pHead)
171: return pHead->GetPart();
172: else
173: return NULL; // Fehler auffangen
174: }
175:
176: Part * PartsList::operator[](int offSet) const
177: {
178: PartNode* pNode = pHead;
179:
180: if (!pHead)
181: return NULL; // Fehler auffangen
182:
183: if (offSet > itsCount)
184: return NULL; // error
185:
186: for (int i=0;i<offSet; i++)
187: pNode = pNode->GetNext();
188:
189: return pNode->GetPart();
190: }
191:
192: Part* PartsList::Find(int & position, int PartNumber) const
193: {
194: PartNode * pNode = 0;
195: for (pNode = pHead, position = 0;
196: pNode!=NULL;
197: pNode = pNode->GetNext(), position++)
198: {
199: if (pNode->GetPart()->GetPartNumber() == PartNumber)
200: break;
201: }
202: if (pNode == NULL)
203: return NULL;
204: else
205: return pNode->GetPart();
206: }
207:
208: void PartsList::Iterate(void (Part::*func)()const) const
209: {
210: if (!pHead)
211: return;
212: PartNode* pNode = pHead;
213: do
214: (pNode->GetPart()->*func)();
215: while (pNode = pNode->GetNext());
216: }
217:
218: void PartsList::Insert(Part* pPart)
219: {
220: PartNode * pNode = new PartNode(pPart);
221: PartNode * pCurrent = pHead;
222: PartNode * pNext = 0;
223:
224: int New = pPart->GetPartNumber();
225: int Next = 0;
226: itsCount++;
227:
228: if (!pHead)
229: {
230: pHead = pNode;
231: return;
232: }
233:
234: // ist dieser kleiner als head
235: // dann ist dies der neue head
236: if (pHead->GetPart()->GetPartNumber() > New)
237: {
238: pNode->SetNext(pHead);
239: pHead = pNode;
240: return;
241: }
242:
243: for (;;)
244: {
245: // gibt es keinen naechsten Knoten, den neuen anhaengen
246: if (!pCurrent->GetNext())
247: {
248: pCurrent->SetNext(pNode);
249: return;
250: }
251:
252: // gehört der Knoten zwischen diesen und den naechsten,
253: // dann hier einfuegen, ansonsten zu naechstem Knoten
254: pNext = pCurrent->GetNext();
255: Next = pNext->GetPart()->GetPartNumber();
256: if (Next > New)
257: {
258: pCurrent->SetNext(pNode);
259: pNode->SetNext(pNext);
260: return;
261: }
262: pCurrent = pNext;
263: }
264: }
265:
266: int main()
267: {
268: PartsList&pl = PartsList::GetGlobalPartsList();
269: Part * pPart = 0;
270: int PartNumber;
271: int value;
272: int choice;
273:
274: while (1)
275: {
276: cout << "(0)Beenden (1)Auto (2)Flugzeug: ";
277: cin >> choice;
278:
279: if (!choice)
280: break;
281:
282: cout << "Neue Teilenummer?: ";
283: cin >> PartNumber;
284:
285: if (choice == 1)
286: {
287: cout << "Baujahr?: ";
288: cin >> value;
289: pPart = new CarPart(value,PartNumber);
290: }
291: else
292: {
293: cout << "Motor-Nummer?: ";
294: cin >> value;
295: pPart = new AirPlanePart(value,PartNumber);
296: }
297:
298: pl.Insert(pPart);
299: }
300: void (Part::*pFunc)()const = Part::Display;
301: pl.Iterate(pFunc);
302: return 0;
303: }
(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

Das Listing aus dem Rückblick auf die zweite Woche zeigt die Implementierung einer verketteten Liste für Part-Objekte. Eine verkettete Liste ist eine dynamische Datenstruktur. Sie ähnelt einem Array, kann jedoch in der Größe angepaßt werden, wenn Objekte hinzugefügt oder gelöscht werden.

Die verkettete Liste aus obigem Programm soll Objekte der Klasse Part aufnehmen, wobei Part ein abstrakter Datentyp ist, der als Basisklasse für alle Objekte mit einer Teilenummer dient. In diesem Beispiel gibt es zu Part die Unterklassen CarPart und AirPlanePart.

Die Klasse Part wird in den Zeilen 26 bis 36 deklariert und besteht aus einer Teilenummer und einigen Zugriffsfunktionen. Man könnte diese Klasse weiter ausbauen, um andere wichtige Informationen über die Teile aufzunehmen, wie zum Beispiel, in welchen Komponenten die Teile eingebaut werden, wie viele davon auf Lager sind, und so weiter. Part ist ein abstrakter Datentyp, denn er enthält die abstrakte Funktion Display().

Beachten Sie, daß es für Display() in den Zeilen 40 bis 43 eine Implementierung gibt. Der Entwickler der Klasse erzwingt damit, daß für abgeleitete Klassen eigene Display()-Methode aufgesetzt werden müssen, und erlaubt gleichzeitig, dabei auf den Code der bereits implementierten Funktion zurückzugreifen.

Die Zeilen 47 bis 59 und 69 bis 87 deklarieren zwei einfache abgeleitete Klassen, CarPart und AirPlanePart. Jede verfügt über eine überschriebene Display()-Methode, die in der Tat die Display()-Methode der Basisklasse verwenden.

Die Klasse PartNode dient als Schnittstelle zwischen der Klasse Part und der Klasse PartList. Sie enthält einen Zeiger auf ein Part-Objekt und einen Zeiger auf den nächsten Knoten in der Liste. Ihre Methoden haben lediglich die Aufgabe, den nächsten Knoten in der Liste einzulesen und zu setzen und das Part-Objekt zurückzuliefern, auf das der Knoten zeigt.

Die eigentliche Listenfunktionalität findet sich in der Klasse PartsList, deren Deklaration in den Zeilen 132 bis 152 erfolgt. PartsList enthält einen Zeiger auf das erste Element in der Liste (pHead) und nutzt dieses Element, um die Liste durchzugehen und dabei auf alle anderen Knoten zuzugreifen. Das Durchgehen der Liste sieht so aus, daß jeder Knoten in der Liste nach dem nächsten Knoten gefragt wird, bis ein Knoten erreicht wird, dessen nächster Zeiger NULL ist.

Sie sehen hier nur eine partielle Implementierung. Eine vollständig implementierte Liste würde entweder weitreichendere Zugriffsmöglichkeiten auf ihren ersten und letzten Knoten bieten oder ein Iterator-Objekt bereitstellen, mit dem Klienten leicht die Liste durchgehen können.

PartsList verfügt nichtsdestotrotz über eine Reihe interessanter Methoden, die in alphabetischer Reihenfolge aufgeführt sind. Dies ist durchaus empfehlenswert, da man so die Funktionen leichter wiederfinden kann.

Find() übernimmt eine PartNumber und einen int. Wurde das Teil (Part-Objekt), das mit PartNumber übereinstimmt, gefunden, wird ein Zeiger auf Part zurückgeliefert und dem int-Parameter wird die Position des Teils in der Liste zugewiesen. Wurde PartNumber nicht gefunden, wird NULL zurückgeliefert und die Position ist ohne Bedeutung.

GetCount() liefert die Anzahl der Elemente in der Liste. PartsList speichert diese Zahl als Elementvariable itsCount, auch wenn es diese Zahl jederzeit wieder durch Abarbeiten der Liste ermitteln könnte.

GetFirst() liefert einen Zeiger auf das erste Part-Objekt in der Liste zurück, oder NULL, wenn die Liste leer ist.

GetGlobalPartsList() liefert eine Referenz auf die statische Elementvariable GlobalPartsList . Dabei handelt es sich um die statische Instanz dieser Klasse. Jedes Programm mit einer PartsList weist auch eine GlobalPartsList-Liste auf. Darüber hinaus steht es dem Programm jedoch frei, weitere PartsList-Listen anzulegen. Eine vollständige Implementierung dieser Idee würde den Konstruktor von Part modifizieren, um sicherzustellen, daß jedes Teil auch in die GlobalPartsList aufgenommen würde.

Insert() übernimmt einen Zeiger auf Part, erzeugt einen PartNode und fügt das Part- Objekt gemäß seiner PartNumber in die Liste ein.

Iterate() übernimmt einen Zeiger auf eine Elementfunktion von Part, die keine Parameter übernimmt, void zurückliefert und const ist. Iterate() ruft diese Funktion für jedes Part-Objekt in der Liste auf. In dem Beispielprogramm wird die Funktion Display() übergeben. Dabei handelt es sich um eine virtuelle Funktion, so daß die Display() -Methode auf der Basis des Laufzeittyps des aktuellen Part-Objekts aufgerufen wird.

Operator[] erlaubt den direkten Zugriff auf das Part-Objekt am gegebenen Offset. Es wird eine rudimentäre Bereichsprüfung durchgeführt. Wenn die Liste NULL ist oder der angeforderte Offset größer als die Liste, wird NULL als Fehlerwert zurückgeliefert.

Beachten Sie, daß in einem richtigen Programm diese Kommentare zu der Funktion in die Klassendeklaration geschrieben worden wären.

Das Hauptprogramm befindet sich in den Zeilen 266 bis 303. Zeile 268 deklariert eine Referenz auf PartsList und initialisiert sie mit GlobalPartsList. Beachten Sie, daß GlobalPartsList bereits in Zeile 154 initialisiert wurde. Dies ist notwendig, da die Deklaration einer statischen Elementvariablen keine Definition beinhaltet. Die Definition muß außerhalb der Deklaration einer Klasse erfolgen.

In den Zeilen 274 bis 299 wird der Anwender wiederholt aufgefordert, anzugeben, ob ein Autoteil oder ein Flugzeugteil in die Liste aufgenommen werden soll. In Abhängigkeit von der Wahl des Anwenders wird ein entsprechender Wert angefordert und das entsprechende Teil erzeugt. Nachdem das Teil erzeugt wurde, wird es in Zeile 298 in die Liste eingefügt.

Die Implementierung der Insert()-Methode von PartsList befindet sich in den Zeilen 218 bis 264. Nachdem die erste Teilenummer, 2837, eingegeben wurde, wird ein CarPart-Objekt mit dieser Teilenummer und dem Baujahr 90 erzeugt und LinkedList::Insert() übergeben.

Zeile 220 erzeugt einen neuen PartNode für das Teil, und die Variable New wird mit der Teilenummer initialisiert. Zeile 226 inkrementiert die Elementvariable itsCount von PartsList.

In Zeile 228 wird der Test, ob pHead NULL ergibt, als true ausgewertet. Da es sich um den ersten Knoten handelt, stimmt es, daß der pHead-Zeiger von PartsList gleich Null ist. Entsprechend wird in Zeile 230 pHead so gesetzt, daß er auf den neuen Knoten zeigt, und die Funktion kehrt zurück.

Der Anwender wird zur Eingabe eines zweiten Teils aufgefordert und diesmal wird ein AirPlane-Teil mit der Teilenummer 37 und der Motor-Nummer 4938 eingegeben. Es wird erneut PartsList::Insert() aufgerufen und pNode wird mit dem neuen Knoten initialisiert. Die statische Elementvariable itsCount wird auf 2 inkrementiert und pHead getestet. Da pHead das letzte Mal zugewiesen wurde, ist es nicht länger Null und der Test schlägt fehl.

Zeile 236 vergleicht die Teilenummer von pHead, 2837, mit der aktuellen Teilenummer 378. Da die neue Teilenummer kleiner ist als die von pHead, wird der neue Knotenzeiger zum neuen Kopfzeiger und der Test in Zeile 236 ergibt true.

In Zeile 238 wird der neue Knoten so gesetzt, daß er auf den Knoten zeigt, auf den gegenwärtig pHead weist. Beachten Sie, daß damit der neue Knoten nicht auf pHead weist, sondern auf den Knoten, auf den pHead zeigte! In Zeile 239 wird pHead so gesetzt, daß er auf den neuen Knoten zeigt.

Beim dritten Durchlauf durch die Schleife gibt der Anwender die Teilenummer 4499 für ein Auto (Car) des Baujahrs 94 ein. Der Zähler wird inkrementiert, und die Zahl ist diesmal nicht kleiner als die Zahl, auf die pHead zeigte. Deshalb wird in die for-Schleife, die in Zeile 243 beginnt, eingetreten.

Der Wert, auf den pHead zeigte, lautet 378. Der Wert, auf den der zweite Knoten zeigte, beträgt 2837. Der aktuelle Wert liegt bei 4499. Der Zeiger pCurrent zeigt auf den gleichen Knoten wie pHead und hat deshalb einen Folgeknoten next. pCurrent zeigt auf den zweiten Knoten und aus diesem Grund schlägt der Test in Zeile 246 fehl.

Der Zeiger pCurrent wird auf den nächsten Knoten gesetzt und die Schleife wird noch einmal durchlaufen. Diesmal ist der Test in Zeile 246 erfolgreich. Es gibt kein weiteres Element. Deshalb wird der aktuellen Knoten angewiesen, auf den neuen Knoten zu zeigen (Zeile 248) und die Einfüge-Operation ist beendet.

Beim vierten Durchlauf wird die Teilenummer 3000 eingegeben. Dabei wird wie bei der vorigen Iteration vorgegangen. Wenn der aktuelle Knoten auf 2837 und der nächste Knoten auf 4499 zeigt, liefert der Test in Zeile 256 true zurück und der neue Knoten wird an seiner Position eingefügt.

Wenn der Anwender zum Schluß die 0 drückt, ergibt der Test in Zeile 279 true und die while(1)-Schleife endet. Zeile 300 weist dem Elementfunktionszeiger pFunc die Elementfunktion Display()zu. In einem realen Programm würde diese Zuweisung auf der Grundlage der Benutzerauswahl dynamisch erfolgen.

Der Zeiger auf die Elementfunktion wird der Methode Iterate() von PartsList übergeben. In Zeile 208 stellt Iterate() sicher, daß die Liste nicht leer ist. Danach wird in den Zeilen 213 bis 215 für jedes Teil der Liste die Funktion aufgerufen, auf die der Funktionszeiger weist - letztendlich also an die Display()-Methode des jeweiligen Teil, wie in der Ausgabe zu sehen.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


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