Bis jetzt haben Sie mit Einfach- und Mehrfachvererbung gearbeitet, um eine ist-ein- Beziehung herzustellen. Heute lernen Sie,
Wie Sie schon in einigen der bisher besprochenen Beispielen sehen konnten, kann
man auf dem Weg über die Datenelemente einer Klasse Objekte einer anderen Klasse
einbinden. C++-Programmierer sprechen in diesem Zusammenhang davon, daß die
äußere Klasse die innere Klasse enthält (englisch: contain - enthalten). Demzufolge
kann beispielsweise eine Employee
-Klasse sowohl String
-Objekte (für den Namen der
Mitarbeiter) als auch Integer-Werte (für das Gehalt der Mitarbeiter) enthalten.
Listing 15.1 zeigt eine rudimentäre, aber nützliche String
-Klasse. Zu dem Listing gibt
es keine Ausgabe, aber den vorgestellten Code werden wir noch in nachfolgenden Listings
verwenden.
Listing 15.1: Die Klasse String
1: #include <iostream.h>
2: #include <string.h>
3:
4: class String
5: {
6: public:
7: // Konstruktoren
8: String();
9: String(const char *const);
10: String(const String &);
11: ~String();
12:
13: // ueberladene Operatoren
14: char & operator[](int offset);
15: char operator[](int offset) const;
16: String operator+(const String&);
17: void operator+=(const String&);
18: String & operator= (const String &);
19:
20: // Allgemeine Zugriffsfunktionen
21: int GetLen()const { return itsLen; }
22: const char * GetString() const { return itsString; }
23: // static int ConstructorCount;
24:
25: private:
26: String (int); // Privater Konstruktor
27: char * itsString;
28: unsigned short itsLen;
29:
30: };
31:
32: // Standardkonstruktor erzeugt String von 0 Bytes Laenge
33: String::String()
34: {
35: itsString = new char[1];
36: itsString[0] = '\0';
37: itsLen=0;
38: // cout << "\tString-Standardkonstruktor\n";
39: // ConstructorCount++;
40: }
41:
42: // Privater (Hilfs-) Konstruktor, der nur von Methoden
43: // der Klasse zum Erzeugen neuer Null-Strings der
44: // erforderlichen Groeße verwendet wird.
45: String::String(int len)
46: {
47: itsString = new char[len+1];
48: for (i = 0; i<=len; i++)
49: itsString[i] = '\0';
50: itsLen=len;
51: // cout << "\tString(int)-Konstruktor \n";
52: // ConstructorCount++;
53: }
54:
55: //Konvertiert ein Zeichen-Array in einen String
56: String::String(const char * const cString)
57: {
58: itsLen = strlen(cString);
59: itsString = new char[itsLen+1];
60: for (i = 0; i<itsLen; i++)
61: itsString[i] = cString[i];
62: itsString[itsLen]='\0';
63: // cout << "\tString(char*)-Konstruktor\n";
64: // ConstructorCount++;
65: }
66:
67: // Kopierkonstruktor
68: String::String (const String & rhs)
69: {
70: itsLen=rhs.GetLen();
71: itsString = new char[itsLen+1];
72: for (int i = 0; i<itsLen;i++)
73: itsString[i] = rhs[i];
74: itsString[itsLen] = '\0';
75: // cout << "\tString(String&)-Konstruktor\n";
76: // ConstructorCount++;
77: }
78:
79: // Destruktor, gibt zugewiesenen Speicher frei
80: String::~String ()
81: {
82: delete [] itsString;
83: itsLen = 0;
84: // cout << "\tString-Destruktor\n";
85: }
86:
87: // Zuweisungsoperator, gibt vorhandenen Speicher frei,
88: // kopiert dann String und Groeße
89: String& String::operator=(const String & rhs)
90: {
91: if (this == &rhs)
92: return *this;
93: delete [] itsString;
94: itsLen=rhs.GetLen();
95: itsString = new char[itsLen+1];
96: for (int i = 0; i<itsLen;i++)
97: itsString[i] = rhs[i];
98: itsString[itsLen] = '\0';
99: return *this;
100: // cout << "\tString-Operator=\n";
101: }
102:
103: // Nicht konstanter Offset-Operator, gibt Referenz
104: // auf Zeichen zurueck, das sich damit aendern
105: // laeßt!
106: char & String::operator[](int offset)
107: {
108: if (offset > itsLen)
109: return itsString[itsLen-1];
110: else
111: return itsString[offset];
112: }
113:
114: // Konstanter Offset-Operator fuer konstante
115: // Objekte (siehe Kopierkonstruktor!)
116: char String::operator[](int offset) const
117: {
118: if (offset > itsLen)
119: return itsString[itsLen-1];
120: else
121: return itsString[offset];
122: }
123:
124: // Erzeugt einen neuen String durch Anfuegen von
125: // rhs an den aktuellen Strings
126: String String::operator+(const String& rhs)
127: {
128: int totalLen = itsLen + rhs.GetLen();
129: String temp(totalLen);
130: int i,j;
131: for (i = 0; i<itsLen; i++)
132: temp[i] = itsString[i];
133: for (j = 0; j<rhs.GetLen(); j++, i++)
134: temp[i] = rhs[j];
135: temp[totalLen]='\0';
136: return temp;
137: }
138:
139: // Aendert aktuellen String, gibt nichts zurueck
140: void String::operator+=(const String& rhs)
141: {
142: unsigned short rhsLen = rhs.GetLen();
143: unsigned short totalLen = itsLen + rhsLen;
144: String temp(totalLen);
145: int i, j;
146: for (i = 0; i<itsLen; i++)
147: temp[i] = itsString[i];
148: for (j = 0; j<rhs.GetLen(); j++, i++)
149: temp[i] = rhs[i-itsLen];
150: temp[totalLen]='\0';
151: *this = temp;
152: }
153:
154: // int String::ConstructorCount = 0;
Listing 15.1 stellt eine String
-Klasse bereit, die stark der Klasse in Listing 12.12 aus
Kapitel 12, »Arrays und verkettete Listen«, ähnelt. Der Hauptunterschied liegt darin,
daß die Ausgabe-Anweisungen in den Konstruktoren und einigen anderen Funktionen
in Listing 15.1 auskommentiert sind. Diese Funktionen kommen in späteren Beispielen
noch zur Anwendung.
In Zeile 23 wird die statische Elementvariable ConstructorCount
deklariert, die in Zeile
154 initialisiert wird. In jedem String
-Konstruktor wird diese Variable inkrementiert.
Derzeit ist dieser Code noch auskommentiert, er wird aber in einem späteren Listing
benötigt
Listing 15.2 zeigt eine Employee
-Klasse, die drei String
-Objekte enthält.
Listing 15.2: Die Klasse Employee und das Rahmenprogramm
1: #include "String.hpp"
2:
3: class Employee
4: {
5:
6: public:
7: Employee();
8: Employee(char *, char *, char *, long);
9: ~Employee();
10: Employee(const Employee&);
11: Employee & operator= (const Employee &);
12:
13: const String & GetFirstName() const
14: { return itsFirstName; }
15: const String & GetLastName() const { return itsLastName; }
16: const String & GetAddress() const { return itsAddress; }
17: long GetSalary() const { return itsSalary; }
18:
19: void SetFirstName(const String & fName)
20: { itsFirstName = fName; }
21: void SetLastName(const String & lName)
22: { itsLastName = lName; }
23: void SetAddress(const String & address)
24: { itsAddress = address; }
25: void SetSalary(long salary) { itsSalary = salary; }
26: private:
27: String itsFirstName;
28: String itsLastName;
29: String itsAddress;
30: long itsSalary;
31: };
32:
33: Employee::Employee():
34: itsFirstName(""),
35: itsLastName(""),
36: itsAddress(""),
37: itsSalary(0)
38: {}
39:
40: Employee::Employee(char * firstName, char * lastName,
41: char * address, long salary):
42: itsFirstName(firstName),
43: itsLastName(lastName),
44: itsAddress(address),
45: itsSalary(salary)
46: {}
47:
48: Employee::Employee(const Employee & rhs):
49: itsFirstName(rhs.GetFirstName()),
50: itsLastName(rhs.GetLastName()),
51: itsAddress(rhs.GetAddress()),
52: itsSalary(rhs.GetSalary())
53: {}
54:
55: Employee::~Employee() {}
56:
57: Employee & Employee::operator= (const Employee & rhs)
58: {
59: if (this == &rhs)
60: return *this;
61:
62: itsFirstName = rhs.GetFirstName();
63: itsLastName = rhs.GetLastName();
64: itsAddress = rhs.GetAddress();
65: itsSalary = rhs.GetSalary();
66:
67: return *this;
68: }
69:
70: int main()
71: {
72: Employee Edie("Jane","Doe","1461 Shore Parkway", 20000);
73: Edie.SetSalary(50000);
74: String LastName("Levine");
75: Edie.SetLastName(LastName);
76: Edie.SetFirstName("Edythe");
77:
78: cout << "Name: ";
79: cout << Edie.GetFirstName().GetString();
80: cout << " " << Edie.GetLastName().GetString();
81: cout << ".\nAdresse: ";
82: cout << Edie.GetAddress().GetString();
83: cout << ".\nGehalt: " ;
84: cout << Edie.GetSalary();
85: return 0;
86: }
Speichern Sie den Code aus Listing 15.1 in einer Datei namens
STRING.HPP
. Wenn Sie dann dieString
-Klasse benötigen, können Sie Listing 15.1 mittels der#include
-Anweisung einbinden.
Aus praktischen Erwägungen habe ich hier Deklaration und Implementierung der
Klasse in einem Listing zusammengenommen. In einem realen Programm würden Sie
die Klassendeklaration in String.HPP
und die Implementierung in String.CPP
abspeichern.
Sie würden dann String.CPP
(mittels ADD- oder MAKE-Dateien) in ihr Programm
aufnehmen und String.HPP
mit Hilfe der #include
-Direktive in String.CPP
einbinden.
Name: Edythe Levine.
Adresse: 1461 Shore Parkway.
Gehalt: 50000
Listing 15.2 zeigt die Klasse Employee
(Angestellte), die drei String
-Objekte enthält:
itsFirstName
(Vorname), itsLastName
(Nachname) und itsAddress
(Adresse).
Zeile 72 erzeugt ein Employee
-Objekt und übergibt ihm vier Werte zur Initialisierung. In
Zeile 73 wird die Employee
-Zugriffsfunktion SetSalary()
(Gehalt festlegen) mit dem
konstanten Wert 50000
aufgerufen. Beachten Sie, daß man an dieser Stelle in einem
realen Programm entweder einen dynamischen Wert (zur Laufzeit berechnet) oder
eine Konstante übergeben würde.
Zeile 74 erzeugt einen String und initialisiert ihn mit einer C++-String-Konstanten.
Dieses String
-Objekt wird dann als Argument an SetLastName()
übergeben, um den
Nachnamen zu ändern (Zeile 75).
In Zeile 76 wird die Employee
-Funktion SetFirstName()
(Vorname festlegen) mit einer
String-Konstanten als Argument aufgerufen. Sicherlich ist Ihnen bereits ausgefallen,
daß Employee
gar nicht über eine Funktion SetFirstName()
verfügt, die eine Zeichenfolge
als Argument übernimmt - SetFirstName()
erfordert eine konstante String-Referenz.
Der Compiler kann diese Umwandlung einer konstanten Zeichenfolge in einen String selbständig vornehmen - die Anleitung dazu findet er in Zeile 9 aus Listing 15.1.
Employee
-Objekte haben keinen speziellen Zugriff auf die Elementvariablen von
String
. Wenn das Employee
-Objekt Edie
versucht, auf die Elementvariable itsLen
seiner
eigenen Elementvariablen itsFirstName
zuzugreifen, erhält man einen Compiler-
Fehler. Allerdings ist das kein großes Problem. Die Zugriffsfunktionen der String
-Klasse
bieten eine passende Schnittstelle, und die Employee
-Klasse braucht sich um die Details
der Implementierung genauso wenig zu kümmern, wie um die Art der Speicherung
von Informationen in der Integer-Variablen itsSalary
.
Die String
-Klasse stellt den Operator operator+
bereit. Der Designer der Employee
-
Klasse hat aber den Zugriff auf den Operator operator+
für Aufrufe über Employee
-Objekte
blockiert, weil er alle String
-Zugriffsfunktionen wie zum Beispiel GetFirstName()
so deklariert hat, daß diese eine konstante Referenz zurückliefern. Da operator+
keine
konstante Funktion ist (und auch nicht sein kann, da der Operator das Objekt verändert,
für das er aufgerufen wird), führen Versuche wie die folgende Anweisung zu einem
Compiler-Fehler:
String buffer = Edie.GetFirstName() + Edie.GetLastName();
GetFirstName()
gibt ein konstantes String
-Objekt zurück, und operator+
kann man
nicht für ein konstantes Objekt aufrufen.
Um dieses Problem zu lösen, überladen Sie GetFirstName()
mit einer nicht konstanten
Version:
const String & GetFirstName() const { return itsFirstName; }
String & GetFirstName() { return itsFirstName; }
Beachten Sie, daß weder der Rückgabewert noch die Elementfunktion selbst konstant
sind. Die Änderung des Rückgabewertes ist nicht ausreichend, um den Funktionsnamen
zu überladen, auch die const
-Deklaration der Funktion selbst muß aufgehoben
werden.
Es sei darauf hingewiesen, daß jedes der in Employee
-Klasse enthaltenen String
-Objekte
seinen Preis hat, und ein Programmierer, der die Employee
-Klasse verwendet, zahlt
diesen Preis, wenn ein String
-Konstruktor aufgerufen oder eine Kopie von Employee
angelegt wird.
Entfernt man die Kommentare vor den cout
-Anweisungen in Listing 15.1 (Zeilen 38,
51, 63, 75, 84 und 100), zeigt sich, wie oft diese Aufrufe stattfinden.
Zum Kompilieren dieses Listings entfernen Sie einfach die Kommentare in den Zeilen 38, 51, 63, 75, 84 und 100 in Listing 15.1.
Listing 15.3: Konstruktoraufrufe für enthaltene Klassen
1: #include "String.hpp"
2:
3: class Employee
4: {
5:
6: public:
7: Employee();
8: Employee(char *, char *, char *, long);
9: ~Employee();
10: Employee(const Employee&);
11: Employee & operator= (const Employee &);
12:
13: const String & GetFirstName() const
14: { return itsFirstName; }
15: const String & GetLastName() const { return itsLastName; }
16: const String & GetAddress() const { return itsAddress; }
17: long GetSalary() const { return itsSalary; }
18:
19: void SetFirstName(const String & fName)
20: { itsFirstName = fName; }
21: void SetLastName(const String & lName)
22: { itsLastName = lName; }
23: void SetAddress(const String & address)
24: { itsAddress = address; }
25: void SetSalary(long salary) { itsSalary = salary; }
26: private:
27: String itsFirstName;
28: String itsLastName;
29: String itsAddress;
30: long itsSalary;
31: };
32:
33: Employee::Employee():
34: itsFirstName(""),
35: itsLastName(""),
36: itsAddress(""),
37: itsSalary(0)
38: {}
39:
40: Employee::Employee(char * firstName, char * lastName,
41: char * address, long salary):
42: itsFirstName(firstName),
43: itsLastName(lastName),
44: itsAddress(address),
45: itsSalary(salary)
46: {}
47:
48: Employee::Employee(const Employee & rhs):
49: itsFirstName(rhs.GetFirstName()),
50: itsLastName(rhs.GetLastName()),
51: itsAddress(rhs.GetAddress()),
52: itsSalary(rhs.GetSalary())
53: {}
54:
55: Employee::~Employee() {}
56:
57: Employee & Employee::operator= (const Employee & rhs)
58: {
59: if (this == &rhs)
60: return *this;
61:
62: itsFirstName = rhs.GetFirstName();
63: itsLastName = rhs.GetLastName();
64: itsAddress = rhs.GetAddress();
65: itsSalary = rhs.GetSalary();
66:
67: return *this;
68: }
69:
70: int main()
71: {
72: cout << "Edie erzeugen...\n";
73: Employee Edie("Jane","Doe","1461 Shore Parkway", 20000);
74: Edie.SetSalary(20000);
75: cout << "SetFirstName mit char * aufrufen...\n";
76: Edie.SetFirstName("Edythe");
77: cout << "Temporaeren String LastName erzeugen...\n";
78: String LastName("Levine");
79: Edie.SetLastName(LastName);
80:
81: cout << "Name: ";
82: cout << Edie.GetFirstName().GetString();
83: cout << " " << Edie.GetLastName().GetString();
84: cout << "\nAdresse: ";
85: cout << Edie.GetAddress().GetString();
86: cout << "\nGehalt: " ;
87: cout << Edie.GetSalary();
88: cout << endl;
89: return 0;
90: }
1: Edie erzeugen...
2: String(char*)-Konstruktor
3: String(char*)-Konstruktor
4: String(char*)-Konstruktor
5: SetFirstName mit char * aufrufen...
6: String(char*)-Konstruktor
7: String-Destruktor
8: Temporaeren String LastName erzeugen...
9: String(char*)-Konstruktor
10: Name: Edythe Levine
11: Adresse: 1461 Shore Parkway
12: Gehalt: 20000
13: String-Destruktor
14: String-Destruktor
15: String-Destruktor
16: String-Destruktor
Listing 15.3 verwendet die gleichen Klassendeklarationen wie Listing 15.1 und 15.2.
Die Kommentarzeichen vor den cout
-Anweisungen wurden jedoch entfernt. Die Ausgabe
von Listing 15.3 wurde numeriert, um die Bezugnahme bei der Analyse zu erleichtern.
In Zeile 72 von Listing 15.3 wird die Anweisung Edie erzeugen...
ausgegeben, was
sich in der Ausgabezeile 1 widerspiegelt. Zeile 73 erzeugt ein Employee
-Objekt Edie
mit vier Parametern. Die Ausgabe zeigt, daß der Konstruktor für String
wie erwartet
drei Mal aufgerufen wird.
Zeile 75 gibt eine rein informative Meldung aus, und in Zeile 76 steht die Anweisung
Edie.SetFirstName("Edythe")
. Diese Anweisung erzeugt aus dem Zeichen-String
"Edythe"
einen temporären String, was man an den Ausgabezeilen 6 und 7 ablesen
kann. Beachten Sie, daß der temporäre String sofort nach seiner Verwendung in der
Zuweisungsanweisung wieder zerstört wird.
Zeile 78 erzeugt ein String
-Objekt. Der Programmierer macht damit explizit das, was
der Compiler implizit schon in der Anweisung davor gemacht hat. Diesmal sehen Sie
den Konstruktoraufruf (Ausgabezeile 9), jedoch keinen Destruktor. Das Objekt wird
erst zerstört, wenn es am Ende der Funktion seinen Gültigkeitsbereich verliert.
In den Zeilen 89 und 90 werden die Strings des Employee
-Objekt aufgelöst, da dieses
seinen Gültigkeitsbereich verliert. Der String LastName
, der in Zeile 78 erzeugt wurde,
wird ebenfalls zerstört, wenn er seinen Gültigkeitsbereich verliert.
Listing 15.3 zeigte, daß die Erzeugung eines Employee
-Objekts den Aufruf von fünf
String-Konstruktoren zur Folge hat. 15.4 enthält nochmals eine Neufassung des Programms,
in der die Auskommentierung des Codes für die statische Elementvariable
ConstructorCount
der String
-Klasse aufgehoben wurde.
Listing 15.1 kann man entnehmen, daß ConstructorCount
jedes Mal inkrementiert
wird, wenn ein String-Konstruktor aufgerufen wird. Das Rahmenprogramm in Listing
15.4 ruft zwei Ausgabefunktionen auf, denen es das Employee
-Objekt - zuerst als Referenz
und dann als Wert - übergibt. ConstructorCount
merkt sich, wie viele String-
Objekte bei der Übergabe der Employee
-Objekte erzeugt werden.
Um dieses Listing zu kompilieren, sollten Sie die Zeilen in Listing 15.1, von denen Sie in Listing 15.3 die Kommentarzeichen entfernt haben, so belassen und zusätzlich die Auskommentierung der Zeilen 23, 39, 52, 64, 76 und 154 aus Listing 15.1 aufheben.
Listing 15.4: Übergabe als Wert
1: #include "String.hpp"
2:
3: class Employee
4: {
5:
6: public:
7: Employee();
8: Employee(char *, char *, char *, long);
9: ~Employee();
10: Employee(const Employee&);
11: Employee & operator= (const Employee &);
12:
13: const String & GetFirstName() const
14: { return itsFirstName; }
15: const String & GetLastName() const { return itsLastName; }
16: const String & GetAddress() const { return itsAddress; }
17: long GetSalary() const { return itsSalary; }
18:
19: void SetFirstName(const String & fName)
20: { itsFirstName = fName; }
21: void SetLastName(const String & lName)
22: { itsLastName = lName; }
23: void SetAddress(const String & address)
24: { itsAddress = address; }
25: void SetSalary(long salary) { itsSalary = salary; }
26: private:
27: String itsFirstName;
28: String itsLastName;
29: String itsAddress;
30: long itsSalary;
31: };
32:
33: Employee::Employee():
34: itsFirstName(""),
35: itsLastName(""),
36: itsAddress(""),
37: itsSalary(0)
38: {}
39:
40: Employee::Employee(char * firstName, char * lastName,
41: char * address, long salary):
42: itsFirstName(firstName),
43: itsLastName(lastName),
44: itsAddress(address),
45: itsSalary(salary)
46: {}
47:
48: Employee::Employee(const Employee & rhs):
49: itsFirstName(rhs.GetFirstName()),
50: itsLastName(rhs.GetLastName()),
51: itsAddress(rhs.GetAddress()),
52: itsSalary(rhs.GetSalary())
53: {}
54:
55: Employee::~Employee() {}
56:
57: Employee & Employee::operator= (const Employee & rhs)
58: {
59: if (this == &rhs)
60: return *this;
61:
62: itsFirstName = rhs.GetFirstName();
63: itsLastName = rhs.GetLastName();
64: itsAddress = rhs.GetAddress();
65: itsSalary = rhs.GetSalary();
66:
67: return *this;
68: }
69:
70: void PrintFunc(Employee);
71: void rPrintFunc(const Employee&);
72:
73: int main()
74: {
75: Employee Edie("Jane","Doe","1461 Shore Parkway", 20000);
76: Edie.SetSalary(20000);
77: Edie.SetFirstName("Edythe");
78: String LastName("Levine");
79: Edie.SetLastName(LastName);
80:
81: cout << "Konstruktorzaehlung: " ;
82: cout << String::ConstructorCount << endl;
83: rPrintFunc(Edie);
84: cout << "Konstruktorzaehlung: ";
85: cout << String::ConstructorCount << endl;
86: PrintFunc(Edie);
87: cout << "Konstruktorzaehlung: ";
88: cout << String::ConstructorCount << endl;
89: return 0;
90: }
91: void PrintFunc (Employee Edie)
92: {
93:
94: cout << "Name: ";
95: cout << Edie.GetFirstName().GetString();
96: cout << " " << Edie.GetLastName().GetString();
97: cout << ".\nAdresse: ";
98: cout << Edie.GetAddress().GetString();
99: cout << ".\nGehalt: " ;
100: cout << Edie.GetSalary();
101: cout << endl;
102:
103: }
104:
105: void rPrintFunc (const Employee& Edie)
106: {
107: cout << "Name: ";
108: cout << Edie.GetFirstName().GetString();
109: cout << " " << Edie.GetLastName().GetString();
110: cout << "\nAdresse: ";
111: cout << Edie.GetAddress().GetString();
112: cout << "\nGehalt: " ;
113: cout << Edie.GetSalary();
114: cout << endl;
115: }
String(char*) constructor
String(char*)-Konstruktor
String(char*)-Konstruktor
String(char*)-Konstruktor
String-Destruktor
String(char*)-Konstruktor
Konstruktorzaehlung: 5
Name: Edythe Levine
Adresse: 1461 Shore Parkway
Gehalt: 20000
Konstruktorzaehlung: 5
String(String&)-Konstruktor
String(String&)-Konstruktor
String(String&)-Konstruktor
Name: Edythe Levine.
Adresse: 1461 Shore Parkway.
Gehalt: 20000
String-Destruktor
String-Destruktor
String-Destruktor
Konstruktorzaehlung: 8
String-Destruktor
String-Destruktor
String-Destruktor
String-Destruktor
Die Ausgabe zeigt, daß im Zuge der Erzeugung eines Employee
-Objekts fünf String-Objekte
erzeugt werden. Wenn ein Employee
-Objekt als Referenz an rPrintFunc()
übergeben
wird, werden keine weiteren Employee
-Objekte erzeugt und demzufolge auch keine
weiteren String-Objekte. (Diese werden auch als Referenz übergeben.)
Wenn das Employee
-Objekt als Wert an PrintFunc()
übergeben wird (Zeile 86), wird
eine Kopie von Employee
erstellt und drei weitere String-Objekte werden erzeugt
(durch Aufruf des Kopierkonstruktors).
Gelegentlich kommt es vor, daß man für die Implementierung einer Klasse gerne auf
die Attribute einer anderen Klasse zurückgreifen würde. Angenommen Sie müssen
eine Klasse PartsCatalog
erzeugen. Ihrer Spezifikation zufolge ist PartsCatalog
eine
Sammlung von Teilen (Part
s), die alle über eine eindeutige Teilenummer verfügen.
Die Klasse PartsCatalog
erlaubt den Zugriff auf einzelne Teile über die Teilenummer
und verhindert, daß Teile doppelte eingetragen werden.
Im Listing aus der Wochenrückschau zur zweiten Woche finden Sie die Klasse PartsList
. Diese Klasse ist erprobt und von Ihnen verstanden, und Sie würden für Ihre eigene
Klasse PartsCatalog
gerne auf dieser Basis aufbauen, statt bei Entwurf und Implementierung
Ihrer Klasse ganz von vorne anfangen zu müssen.
Sie könnten eine neue PartsCatalog
-Klasse erzeugen, die ein PartsList
-Objekt enthält.
PartsCatalog
könnte dann die Verwaltung der verketteten Teileliste an das in ihr
eingebettete PartsList
-Objekt delegieren.
Eine andere Alternative wäre, die Klasse PartsCatalog
von PartsList
abzuleiten und
damit deren Eigenschaften zu erben. Denken Sie jedoch daran, daß öffentliche Vererbung
eine ist-eine-Beziehung herstellt. Deshalb sollten Sie sich fragen, ob PartsCatalog
wirklich eine Art von PartsList
ist.
Eine Möglichkeit zu entscheiden, ob PartsCatalog
eine Art PartsList
ist, besteht darin
anzunehmen, daß PartsList
die Basisklasse und PartsCatalog
die abgeleitete Klasse
sei, und sich dann die folgenden Fragen zu stellen:
Gibt es irgend etwas in der Basisklasse, was nicht in die abgeleitete Klasse gehört?
Weist zum Beispiel die Basisklasse PartsList
Funktionen auf, die für die PartsCatalog
-Klasse nicht geeignet sind? Wenn ja, wollen Sie sicherlich keine öffentliche Vererbung.
Benötigt die Klasse, die Sie erzeugen wollen, mehr als ein Objekt der Basisklasse? Benötigt
zum Beispiel PartsCatalog
zwei PartsList
-Listen in jedem Objekt? Wenn ja,
werden Sie mit großer Sicherheit auf Einbettung zurückgreifen.
Müssen Sie von der Basisklasse erben, so daß Sie virtuelle Funktionen nutzen oder auf geschützte Elemente zugreifen können? Wenn ja, sollten Sie mit öffentlicher oder privater Vererbung arbeiten.
Je nachdem zu welcher Antwort Sie kommen, müssen Sie zwischen öffentlicher Vererbung (die ist-ein-Beziehung) und entweder privater Vererbung (wird weiter hinter erklärt) oder Einbettung wählen.
Warum sollte man PartsCatalog
nicht von PartsList
ableiten? PartsCatalog
ist keine
PartsList
, da es sich bei PartsList
-Objekten um geordnete Sammlungen handelt, in
denen jedes Element der Sammlung mehrfach vorliegen kann. Ein PartsCatalog
-Objekt
enthält nur eindeutige Einträge, die nicht geordnet sind. Das fünfte Element in
PartsCatalog
hat nicht automatisch die Teilenummer 5.
Sicher wäre es auch möglich gewesen, öffentlich von PartsList
zu erben und dann
Insert()
und den Offset-Operator ([]
) zu überschreiben, aber dann hätten Sie das
Wesen der Klasse PartsList
geändert. Statt dessen wollen Sie eine PartsCatalog
-
Klasse schaffen, die keinen Offset-Operator hat, doppelte Einträge verbietet und den
operator+
definiert, um zwei Sammlungen zu kombinieren.
Die erste Möglichkeit, dies zu erreichen, setzt auf Einbettung. PartsCatalog
delegiert
die Listenverwaltung an das enthaltene PartsList
-Objekt. Listing 15.5 setzt diesen
Ansatz um.
Listing 15.5: Delegierung an ein eingebettetes PartsList-Objekt
1: #include <iostream.h>
2:
3: // **************** Teile ************
4:
5: // Abstrakte Basisklasse fuer die Teile
6: class Part
7: {
8: public:
9: Part():itsPartNumber(1) {}
10: Part(int PartNumber):
11: itsPartNumber(PartNumber){}
12: virtual ~Part(){}
13: int GetPartNumber() const
14: { return itsPartNumber; }
15: virtual void Display() const =0;
16: private:
17: int itsPartNumber;
18: };
19:
20: // Implementierung einer abstrakten Funktion, damit
21: // abgeleitete Klassen die Funktion ueberschreiben
22: void Part::Display() const
23: {
24: cout << "\nTeilenummer: " << itsPartNumber << endl;
25: }
26:
27: // **************** Autoteile ************
28:
29: class CarPart : public Part
30: {
31: public:
32: CarPart():itsModelYear(94){}
33: CarPart(int year, int partNumber);
34: virtual void Display() const
35: {
36: Part::Display();
37: cout << "Baujahr: ";
38: cout << itsModelYear << endl;
39: }
40: private:
41: int itsModelYear;
42: };
43:
44: CarPart::CarPart(int year, int partNumber):
45: itsModelYear(year),
46: Part(partNumber)
47: {}
48:
49:
50: // **************** Flugzeugteile ************
51:
52: class AirPlanePart : public Part
53: {
54: public:
55: AirPlanePart():itsEngineNumber(1){};
56: AirPlanePart
57: (int EngineNumber, int PartNumber);
58: virtual void Display() const
59: {
60: Part::Display();
61: cout << "Motor-Nr.: ";
62: cout << itsEngineNumber << endl;
63: }
64: private:
65: int itsEngineNumber;
66: };
67:
68: AirPlanePart::AirPlanePart
69: (int EngineNumber, int PartNumber):
70: itsEngineNumber(EngineNumber),
71: Part(PartNumber)
72: {}
73:
74: // **************** Teile-Knoten ************
75: class PartNode
76: {
77: public:
78: PartNode (Part*);
79: ~PartNode();
80: void SetNext(PartNode * node)
81: { itsNext = node; }
82: PartNode * GetNext() const;
83: Part * GetPart() const;
84: private:
85: Part *itsPart;
86: PartNode * itsNext;
87: };
88: // PartNode Implementierungen...
89:
90: PartNode::PartNode(Part* pPart):
91: itsPart(pPart),
92: itsNext(0)
93: {}
94:
95: PartNode::~PartNode()
96: {
97: delete itsPart;
98: itsPart = 0;
99: delete itsNext;
100: itsNext = 0;
101: }
102:
103: // Liefert NULL zurueck, falls kein naechster PartNode vorhanden
104: PartNode * PartNode::GetNext() const
105: {
106: return itsNext;
107: }
108:
109: Part * PartNode::GetPart() const
110: {
111: if (itsPart)
112: return itsPart;
113: else
114: return NULL; //Fehler
115: }
116:
117:
118:
119: // **************** Teileliste ************
120: class PartsList
121: {
122: public:
123: PartsList();
124: ~PartsList();
125: // benoetigt Kopierkonstruktor und Zuweisungsoperator!
126: void Iterate(void (Part::*f)()const) const;
127: Part* Find(int & position, int PartNumber) const;
128: Part* GetFirst() const;
129: void Insert(Part *);
130: Part* operator[](int) const;
131: int GetCount() const { return itsCount; }
132: static PartsList& GetGlobalPartsList()
133: {
134: return GlobalPartsList;
135: }
136: private:
137: PartNode * pHead;
138: int itsCount;
139: static PartsList GlobalPartsList;
140: };
141:
142: PartsList PartsList::GlobalPartsList;
143:
144:
145: PartsList::PartsList():
146: pHead(0),
147: itsCount(0)
148: {}
149:
150: PartsList::~PartsList()
151: {
152: delete pHead;
153: }
154:
155: Part* PartsList::GetFirst() const
156: {
157: if (pHead)
158: return pHead->GetPart();
159: else
160: return NULL; // Fehler auffangen
161: }
162:
163: Part * PartsList::operator[](int offSet) const
164: {
165: PartNode* pNode = pHead;
166:
167: if (!pHead)
168: return NULL; // Fehler auffangen
169:
170: if (offSet > itsCount)
171: return NULL; // Fehler
172:
173: for (int i=0;i<offSet; i++)
174: pNode = pNode->GetNext();
175:
176: return pNode->GetPart();
177: }
178:
179: Part* PartsList::Find(
180: int & position,
181: int PartNumber) const
182: {
183: PartNode * pNode = 0;
184: for (pNode = pHead, position = 0;
185: pNode!=NULL;
186: pNode = pNode->GetNext(), position++)
187: {
188: if (pNode->GetPart()->GetPartNumber() == PartNumber)
189: break;
190: }
191: if (pNode == NULL)
192: return NULL;
193: else
194: return pNode->GetPart();
195: }
196:
197: void PartsList::Iterate(void (Part::*func)()const) const
198: {
199: if (!pHead)
200: return;
201: PartNode* pNode = pHead;
202: do
203: (pNode->GetPart()->*func)();
204: while (pNode = pNode->GetNext());
205: }
206:
207: void PartsList::Insert(Part* pPart)
208: {
209: PartNode * pNode = new PartNode(pPart);
210: PartNode * pCurrent = pHead;
211: PartNode * pNext = 0;
212:
213: int New = pPart->GetPartNumber();
214: int Next = 0;
215: itsCount++;
216:
217: if (!pHead)
218: {
219: pHead = pNode;
220: return;
221: }
222:
223: // Ist dieser kleiner als head
224: // dann ist dies der neue head
225: if (pHead->GetPart()->GetPartNumber() > New)
226: {
227: pNode->SetNext(pHead);
228: pHead = pNode;
229: return;
230: }
231:
232: for (;;)
233: {
234: // gibt es keinen naechsten Knoten, den neuen anhaengen
235: if (!pCurrent->GetNext())
236: {
237: pCurrent->SetNext(pNode);
238: return;
239: }
240:
241: // gehoert der Knoten zwischen diesen und den naechsten,
242: // dann hier einfuegen, ansonsten zu naechstem Knoten gehen
243: pNext = pCurrent->GetNext();
244: Next = pNext->GetPart()->GetPartNumber();
245: if (Next > New)
246: {
247: pCurrent->SetNext(pNode);
248: pNode->SetNext(pNext);
249: return;
250: }
251: pCurrent = pNext;
252: }
253: }
254:
255:
256:
257: class PartsCatalog
258: {
259: public:
260: void Insert(Part *);
261: int Exists(int PartNumber);
262: Part * Get(int PartNumber);
263: operator+(const PartsCatalog &);
264: void ShowAll() { thePartsList.Iterate(Part::Display); }
265: private:
266: PartsList thePartsList;
267: };
268:
269: void PartsCatalog::Insert(Part * newPart)
270: {
271: int partNumber = newPart->GetPartNumber();
272: int offset;
273:
274: if (!thePartsList.Find(offset, partNumber))
275:
276: thePartsList.Insert(newPart);
277: else
278: {
279: cout << partNumber << " war der ";
280: switch (offset)
281: {
282: case 0: cout << "erste "; break;
283: case 1: cout << "zweite "; break;
284: case 2: cout << "dritte "; break;
285: default: cout << offset+1 << "-te ";
286: }
287: cout << "Eintrag. Abgelehnt!\n";
288: }
289: }
290:
291: int PartsCatalog::Exists(int PartNumber)
292: {
293: int offset;
294: thePartsList.Find(offset,PartNumber);
295: return offset;
296: }
297:
298: Part * PartsCatalog::Get(int PartNumber)
299: {
300: int offset;
301: Part * thePart = thePartsList.Find(offset, PartNumber);
302: return thePart;
303: }
304:
305:
306: int main()
307: {
308: PartsCatalog pc;
309: Part * pPart = 0;
310: int PartNumber;
311: int value;
312: int choice;
313:
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 >> PartNumber;
324:
325: if (choice == 1)
326: {
327: cout << "Baujahr?: ";
328: cin >> value;
329: pPart = new CarPart(value,PartNumber);
330: }
331: else
332: {
333: cout << "Motor-Nummer?: ";
334: cin >> value;
335: pPart = new AirPlanePart(value,PartNumber);
336: }
337: pc.Insert(pPart);
338: }
339: pc.ShowAll();
340: return 0;
341: }
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 1234
Baujahr?: 94
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 4434
Baujahr?: 93
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 1234
Baujahr?: 94
1234 war der erste Eintrag. Abgelehnt!
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 2345
Baujahr?: 93
(0)Beenden (1)Auto (2)Flugzeug: 0
Neue Teilenummer: 1234
Baujahr: 94
Neue Teilenummer: 2345
Baujahr: 93
Neue Teilenummer: 4434
Baujahr: 93
Einige Compiler haben Probleme mit der Zeile 264, obwohl sie den Regeln von C++ entspricht. Macht auch Ihr Compiler Schwierigkeiten, ändern Sie die Zeile in
264: void ShowAll() { thePartsList.Iterate(&Part::Display); }(Beachten Sie das kaufmännische Und vor
Part::Display.
) Läßt sich dieser Code ausführen, rufen Sie umgehend Ihren Compiler-Händler an und beschweren Sie sich.
Listing 15.5 reproduziert die Klassen Part
, PartNode
und PartsList
aus der Rückschau
zur zweiten Woche.
Die Zeilen 257 bis 267 deklarieren eine neue Klasse namens PartsCatalog
. PartsCatalog
enthält PartsList
als Datenelement, an das es die Listenverwaltung delegiert.
Man kann auch sagen, daß PartsCatalog
mit Hilfe von PartsList
implementiert wird.
Beachten Sie, daß die Klienten von PartsCatalog
keinen direkten Zugriff auf PartsList
haben. Der Zugriff erfolgt immer über PartsCatalog
, die das Verhalten von
PartsList
auf diesem Weg dramatisch ändert. So erlaubt zum Beispiel die Methode
PartsCatalog::Insert()
keine doppelten Einträge in PartsList
.
Die Implementierung von PartsCatalog::Insert()
beginnt in Zeile 269. Für das Part
-
Objekt, das als Parameter übergeben wird, wird der Wert der Elementvariablen itsPartNumber
abgefragt. Dieser Wert wird an die Find()
-Methode von PartsList
weitergereicht,
und wenn keine Übereinstimmung gefunden wird, wird die Nummer eingefügt.
Im anderen Fall wird eine Fehlermeldung ausgegeben.
Beachten Sie, daß PartsCatalog
zum eigentlichen Einfügen die Methode Insert()
seiner
Elementvariable p1
(also seines PartsList
-Objekts) aufruft. Für das Einfügen, die
Wartung der verketteten Liste sowie Such- und Ausleseoperationen ist das in PartsCatalog
eingebettete PartsList
-Element verantwortlich. Es besteht kein Grund für
PartsCatalog
, diesen Code zu reproduzieren. Die Klasse kann vielmehr die Vorteile
einer gut definierten Schnittstelle ausschöpfen.
Das ist der Kern der Wiederverwertbarkeit in C++. PartsCatalog
kann den PartsList
-
Code wiederverwerten und der Entwickler von PartsCatalog
muß sich nicht um die
Implementierungsdetails von PartsList
kümmern. Die Schnittstelle von PartsList
(das ist die Klassendeklaration) stellt alle Informationen, die vom Entwickler der PartsCatalog
-Klasse benötigt werden, bereit.
Würde die Klasse PartsCatalog
Zugriff auf die geschützten Elemente von PartsList
(zur Zeit gibt es keine) benötigen oder müßte sie eine der PartsList
-Methoden überschreiben,
dann müßte PartsCatalog
von PartsList
abgeleitet werden.
Da PartsCatalog
kein PartsList
-Objekt ist und Sie nicht das ganze Spektrum der
Funktionalität von PartsList
den Klienten von PartsCatalog
zur Verfügung stellen
wollen, würden Sie PartsList
als private Basisklasse von PartsCatalog
deklarieren
(private Vererbung).
Zuerst sollten Sie sich darüber im klaren sein, daß bei der privaten Vererbung alle Elementvariablen
und -funktionen der Basisklasse so behandelt werden, als seien sie privat
deklariert - unabhängig von den eigentlichen in der Basisklasse festgelegten Zugriffsrechten.
Damit kann keine Funktion, die nicht Elementfunktion von
PartsCatalog
ist, auf die von PartsList
geerbten Funktionen zugreifen. Wichtig zu
merken ist: Bei der privaten Vererbung wird nicht die Schnittstelle, sondern nur die
Implementierung vererbt.
Für die Klienten der PartsCatalog
-Klasse ist die PartsList
-Klasse unsichtbar. Keine
ihrer Schnittstellen ist für die Klienten verfügbar, sie können keine ihrer Methoden
aufrufen. Sie können jedoch die Methoden von PartsCatalog
aufrufen und die wiederum
können auf die gesamte Klasse PartsList
zugreifen, da PartsCatalog
von PartsList
abgeleitet ist. Wichtig dabei ist, daß PartsCatalog
keine PartsList
ist, wie das bei
öffentlicher Vererbung der Fall wäre. PartsCatalog
ist mit Hilfe von PartsList
implementiert
- wie bei der Einbettung. Private Vererbung stellt nur eine bequeme Alternative
dar.
Listing 15.6 zeigt ein Beispiel für private Vererbung. Die Klasse PartsCatalog
wird als
private Ableitung von PartsList
deklariert.
Listing 15.6: Private Vererbung
1: //listing 15.6 Private Vererbung
2: #include <iostream.h>
3:
4: // **************** Teile ************
5:
6: // Abstrakte Basisklasse fuer die Teile
7: class Part
8: {
9: public:
10: Part():itsPartNumber(1) {}
11: Part(int PartNumber):
12: itsPartNumber(PartNumber){}
13: virtual ~Part(){}
14: int GetPartNumber() const
15: { return itsPartNumber; }
16: virtual void Display() const =0;
17: private:
18: int itsPartNumber;
19: };
20:
21: // Implementierung einer abstrakten Funktion, damit
22: // abgeleitete Klassen die Funktion ueberschreiben
23: void Part::Display() const
24: {
25: cout << "\nTeilenummer: " << itsPartNumber << endl;
26: }
27:
28: // **************** Autoteile ************
29:
30: class CarPart : public Part
31: {
32: public:
33: CarPart():itsModelYear(94){}
34: CarPart(int year, int partNumber);
35: virtual void Display() const
36: {
37: Part::Display();
38: cout << "Baujahr: ";
39: cout << itsModelYear << endl;
40: }
41: private:
42: int itsModelYear;
43: };
44:
45: CarPart::CarPart(int year, int partNumber):
46: itsModelYear(year),
47: Part(partNumber)
48: {}
49:
50:
51: // **************** Flugzeugteile ************
52:
53: class AirPlanePart : public Part
54: {
55: public:
56: AirPlanePart():itsEngineNumber(1){};
57: AirPlanePart
58: (int EngineNumber, int PartNumber);
59: virtual void Display() const
60: {
61: Part::Display();
62: cout << "Motor-Nr.: ";
63: cout << itsEngineNumber << endl;
64: }
65: private:
66: int itsEngineNumber;
67: };
68:
69: AirPlanePart::AirPlanePart
70: (int EngineNumber, int PartNumber):
71: itsEngineNumber(EngineNumber),
72: Part(PartNumber)
73: {}
74:
75: // **************** Teile-Knoten ************
76: class PartNode
77: {
78: public:
79: PartNode (Part*);
80: ~PartNode();
81: void SetNext(PartNode * node)
82: { itsNext = node; }
83: PartNode * GetNext() const;
84: Part * GetPart() const;
85: private:
86: Part *itsPart;
87: PartNode * itsNext;
88: };
89: // PartNode Implementierungen...
90:
91: PartNode::PartNode(Part* pPart):
92: itsPart(pPart),
93: itsNext(0)
94: {}
95:
96: PartNode::~PartNode()
97: {
98: delete itsPart;
99: itsPart = 0;
100: delete itsNext;
101: itsNext = 0;
102: }
103:
104: // Liefert NULL zurueck, wenn kein naechster PartNode vorhanden
105: PartNode * PartNode::GetNext() const
106: {
107: return itsNext;
108: }
109:
110: Part * PartNode::GetPart() const
111: {
112: if (itsPart)
113: return itsPart;
114: else
115: return NULL; //Fehler
116: }
117:
118:
119:
120: // **************** Teile-Liste ************
121: class PartsList
122: {
123: public:
124: PartsList();
125: ~PartsList();
126: // benoetigt Kopierkonstruktor und Zuweisungsoperator!
127: void Iterate(void (Part::*f)()const) const;
128: Part* Find(int & position, int PartNumber) const;
129: Part* GetFirst() const;
130: void Insert(Part *);
131: Part* operator[](int) const;
132: int GetCount() const { return itsCount; }
133: static PartsList& GetGlobalPartsList()
134: {
135: return GlobalPartsList;
136: }
137: private:
138: PartNode * pHead;
139: int itsCount;
140: static PartsList GlobalPartsList;
141: };
142:
143: PartsList PartsList::GlobalPartsList;
144:
145:
146: PartsList::PartsList():
147: pHead(0),
148: itsCount(0)
149: {}
150:
151: PartsList::~PartsList()
152: {
153: delete pHead;
154: }
155:
156: Part* PartsList::GetFirst() const
157: {
158: if (pHead)
159: return pHead->GetPart();
160: else
161: return NULL; // Fehler auffangen
162: }
163:
164: Part * PartsList::operator[](int offSet) const
165: {
166: PartNode* pNode = pHead;
167:
168: if (!pHead)
169: return NULL; // Fehler auffangen
170:
171: if (offSet > itsCount)
172: return NULL; // Fehler
173:
174: for (int i=0;i<offSet; i++)
175: pNode = pNode->GetNext();
176:
177: return pNode->GetPart();
178: }
179:
180: Part* PartsList::Find(
181: int & position,
182: int PartNumber) const
183: {
184: PartNode * pNode = 0;
185: for (pNode = pHead, position = 0;
186: pNode!=NULL;
187: pNode = pNode->GetNext(), position++)
188: {
189: if (pNode->GetPart()->GetPartNumber() == PartNumber)
190: break;
191: }
192: if (pNode == NULL)
193: return NULL;
194: else
195: return pNode->GetPart();
196: }
197:
198: void PartsList::Iterate(void (Part::*func)()const) const
199: {
200: if (!pHead)
201: return;
202: PartNode* pNode = pHead;
203: do
204: (pNode->GetPart()->*func)();
205: while (pNode = pNode->GetNext());
206: }
207:
208: void PartsList::Insert(Part* pPart)
209: {
210: PartNode * pNode = new PartNode(pPart);
211: PartNode * pCurrent = pHead;
212: PartNode * pNext = 0;
213:
214: int New = pPart->GetPartNumber();
215: int Next = 0;
216: itsCount++;
217:
218: if (!pHead)
219: {
220: pHead = pNode;
221: return;
222: }
223:
224: // Ist dieser kleiner als head
225: // dann ist dies der neue head
226: if (pHead->GetPart()->GetPartNumber() > New)
227: {
228: pNode->SetNext(pHead);
229: pHead = pNode;
230: return;
231: }
232:
233: for (;;)
234: {
235: // gibt es keinen naechsten, den neuen anhaengen
236: if (!pCurrent->GetNext())
237: {
238: pCurrent->SetNext(pNode);
239: return;
240: }
241:
242: // gehoert der Knoten zwischen diesen und den naechsten,
243: // dann hier einfuegen, ansonsten zu naechstem Knoten gehen
244: pNext = pCurrent->GetNext();
245: Next = pNext->GetPart()->GetPartNumber();
246: if (Next > New)
247: {
248: pCurrent->SetNext(pNode);
249: pNode->SetNext(pNext);
250: return;
251: }
252: pCurrent = pNext;
253: }
254: }
255:
256:
257:
258: class PartsCatalog : private PartsList
259: {
260: public:
261: void Insert(Part *);
262: int Exists(int PartNumber);
263: Part * Get(int PartNumber);
264: operator+(const PartsCatalog &);
265: void ShowAll() { Iterate(Part::Display); }
266: private:
267: };
268:
269: void PartsCatalog::Insert(Part * newPart)
270: {
271: int partNumber = newPart->GetPartNumber();
272: int offset;
273:
274: if (!Find(offset, partNumber))
275: PartsList::Insert(newPart);
276: else
277: {
278: cout << partNumber << " war der ";
279: switch (offset)
280: {
281: case 0: cout << "erste "; break;
282: case 1: cout << "zweite "; break;
283: case 2: cout << "dritte "; break;
284: default: cout << offset+1 << "th ";
285: }
286: cout << "Eintrag. Abgelehnt!\n";
287: }
288: }
289:
290: int PartsCatalog::Exists(int PartNumber)
291: {
292: int offset;
293: Find(offset,PartNumber);
294: return offset;
295: }
296:
297: Part * PartsCatalog::Get(int PartNumber)
298: {
299: int offset;
300: return (Find(offset, PartNumber));
301:
302: }
303:
304: int main()
305: {
306: PartsCatalog pc;
307: Part * pPart = 0;
308: int PartNumber;
309: int value;
310: int choice;
311:
312: while (1)
313: {
314: cout << "(0)Beenden (1)Auto (2)Flugzeug: ";
315: cin >> choice;
316:
317: if (!choice)
318: break;
319:
320: cout << "Neue Teilenummer?: ";
321: cin >> PartNumber;
322:
323: if (choice == 1)
324: {
325: cout << "Baujahr?: ";
326: cin >> value;
327: pPart = new CarPart(value,PartNumber);
328: }
329: else
330: {
331: cout << "Motor-Nummer?: ";
332: cin >> value;
333: pPart = new AirPlanePart(value,PartNumber);
334: }
335: pc.Insert(pPart);
336: }
337: pc.ShowAll();
338: return 0;
339: }
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 1234
Baujahr?: 94
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 4434
Baujahr?: 93
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 1234
Baujahr?: 94
1234 war der erste Eintrag. Abgelehnt!
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 2345
Baujahr?: 93
(0)Beenden (1)Auto (2)Flugzeug: 0
Teilenummer: 1234
Baujahr: 94
Teilenummer: 2345
Baujahr: 93
Teilenummer: 4434
Baujahr: 93
Listing 15.6 enthält eine geänderte Schnittstelle zu PartsCatalog
und ein umgeschriebenes
Rahmenprogramm. Die Schnittstellen zu den anderen Klassen sind unverändert
aus Listing 15.5 übernommen.
Zeile 258 deklariert PartsCatalog
als private Ableitung von PartsList
. Die Schnittstelle
zu PartsCatalog
ist die gleiche wie in Listing 15.5, auch wenn sie ab jetzt natürlich
kein Objekt vom Typ PartsList
als Datenelement benötigt.
Die PartsCatalog
-Funktion ShowAll()
ruft die PartsList
-Funktion Iterate()
mit einem
Zeiger auf die verantwortliche Elementfunktion der Klasse Part
als Argument auf.
ShowAll()
fungiert als öffentliche Schnittstelle zu Iterate()
. Sie sorgt für einen korrekten
Aufruf und verhindert, daß Klient-Klassen Iterate()
direkt aufrufen. Mit PartsList
mag es möglich sein, daß andere Funktionen an Iterate()
übergeben werden,
mit PartsCatalog
nicht.
Die Funktion Insert()
hat sich ebenfalls geändert. Beachten Sie, daß Find()
wegen
der Ableitungsbeziehung nun direkt aufgerufen werden kann (Zeile 274). Der Aufruf
von Insert()
in Zeile 275 muß vollständig qualifiziert sein, da er ansonsten endlos
wiederholt wird.
Kurz gesagt, wenn Methoden von PartsCatalog
Methoden von PartsList
aufrufen
wollen, kann dies jetzt auf direktem Wege erfolgen. Lediglich wenn PartsCatalog
die
Methode überschrieben hat und die Version von PartsList
benötigt wird, muß der
Funktionsname vollständig qualifiziert sein.
Dank der privaten Vererbung kann die PartsCatalog
-Klasse erben, was sie benötigt,
während der Zugriff auf Insert()
und andere Methoden, zu denen Klient-Klassen keinen
direkten Zugriff haben sollen, eingeschränkt und von PartsCatalog
kontrolliert
werden kann.
Manchmal erzeugt man Klassen in Paaren oder Gruppen, weil die Klassen in bestimmter
Weise zusammengehören und zusammenarbeiten. So stellen zum Beispiel
PartNode
und PartsList
ein Paar dar und es wäre äußerst praktisch gewesen, wenn
PartsList
, den Part
-Zeiger itsPart
von PartNode
direkt hätte lesen können.
Dabei liegt es gar nicht in Ihrem Interesse, itsPart
öffentlich (public
) oder geschützt
(protected
) zu machen, schließlich handelt es sich um ein Implementierungsdetail von
PartNode
, daß man weiterhin private
belassen sollte. Sie würden jedoch gerne PartsList
den direkten Zugriff gestatten.
Um private Datenelemente oder Elementfunktionen für eine andere Klasse freizugeben, muß man diese Klasse als Friend (Freund) deklarieren. Dadurch erweitert man die Schnittstelle einer Klasse um die Deklaration der Friend-Klasse.
Nachdem PartNode
die Klasse PartsList
als Friend deklariert, sind alle Datenelemente
und Elementfunktionen aus Sicht von PartsList
öffentlich.
Es ist wichtig herauszustreichen, daß Freundschaft nicht übertragbar ist. Nur weil du mein Freund bist und Joe dein Freund ist, bedeutet das nicht, daß Joe mein Freund ist. Freundschaft wird darüber hinaus auch nicht vererbt. Auch hier eine Analogie: Nur weil du mein Freund bist und ich mit dir meine Geheimnisse teile, heißt das noch nicht, daß ich meine Geheimnisse auch deinen Kindern anvertrauen möchte.
Schließlich ist Freundschaft nicht kommutativ. Die Zuweisung von KlasseEins
als Friend
von KlasseZwei
macht KlasseZwei
nicht zu einem Friend von KlasseEins
. Nur
weil du mir deine Geheimnisse mitteilen willst, bedeutet das nicht, daß ich dir auch
meine Geheimnisse erzählen will.
Listing 15.7 veranschaulicht Freundschaft anhand einer Neufassung des Beispiels aus
Listing 15.6. In diesem Beispiel wird PartsList
ein Friend von PartNode
. Beachten
Sie, daß dadurch nicht automatisch PartNode
ein Friend von PartsList
wird.
Listing 15.7: Beispiel für eine Friend-Klasse
1: #include <iostream.h>
2:
3:
4:
5:
6: // **************** Teile ************
7:
8: // Abstrakte Basisklasse fuer die Teile
9: class Part
10: {
11: public:
12: Part():itsPartNumber(1) {}
13: Part(int PartNumber):
14: itsPartNumber(PartNumber){}
15: virtual ~Part(){}
16: int GetPartNumber() const
17: { return itsPartNumber; }
18: virtual void Display() const =0;
19: private:
20: int itsPartNumber;
21: };
22:
23: // Implementierung einer abstrakten Funktion, damit
24: // abgeleitete Klassen die Funktion ueberschreiben
25: void Part::Display() const
26: {
27: cout << "\nTeilenummer: ";
28: cout << itsPartNumber << endl;
29: }
30:
31: // **************** Autoteile ************
32:
33: class CarPart : public Part
34: {
35: public:
36: CarPart():itsModelYear(94){}
37: CarPart(int year, int partNumber);
38: virtual void Display() const
39: {
40: Part::Display();
41: cout << "Baujahr: ";
42: cout << itsModelYear << endl;
43: }
44: private:
45: int itsModelYear;
46: };
47:
48: CarPart::CarPart(int year, int partNumber):
49: itsModelYear(year),
50: Part(partNumber)
51: {}
52:
53:
54: // **************** Flugzeugteile ************
55:
56: class AirPlanePart : public Part
57: {
58: public:
59: AirPlanePart():itsEngineNumber(1){};
60: AirPlanePart
61: (int EngineNumber, int PartNumber);
62: virtual void Display() const
63: {
64: Part::Display();
65: cout << "Motor-Nr.: ";
66: cout << itsEngineNumber << endl;
67: }
68: private:
69: int itsEngineNumber;
70: };
71:
72: AirPlanePart::AirPlanePart
73: (int EngineNumber, int PartNumber):
74: itsEngineNumber(EngineNumber),
75: Part(PartNumber)
76: {}
77:
78: // **************** Teile-Knoten ************
79: class PartNode
80: {
81: public:
82: friend class PartsList;
83: PartNode (Part*);
84: ~PartNode();
85: void SetNext(PartNode * node)
86: { itsNext = node; }
87: PartNode * GetNext() const;
88: Part * GetPart() const;
89: private:
90: Part *itsPart;
91: PartNode * itsNext;
92: };
93:
94:
95: PartNode::PartNode(Part* pPart):
96: itsPart(pPart),
97: itsNext(0)
98: {}
99:
100: PartNode::~PartNode()
101: {
102: delete itsPart;
103: itsPart = 0;
104: delete itsNext;
105: itsNext = 0;
106: }
107:
108: // Liefert NULL zurueck, wenn kein naechster PartNode vorhanden
109: PartNode * PartNode::GetNext() const
110: {
111: return itsNext;
112: }
113:
114: Part * PartNode::GetPart() const
115: {
116: if (itsPart)
117: return itsPart;
118: else
119: return NULL; //Fehler
120: }
121:
122:
123: // **************** Teile-Liste ************
124: class PartsList
125: {
126: public:
127: PartsList();
128: ~PartsList();
129: // benoetigt Kopierkonstruktor und Zuweisungsoperator!
130: void Iterate(void (Part::*f)()const) const;
131: Part* Find(int & position, int PartNumber) const;
132: Part* GetFirst() const;
133: void Insert(Part *);
134: Part* operator[](int) const;
135: int GetCount() const { return itsCount; }
136: static PartsList& GetGlobalPartsList()
137: {
138: return GlobalPartsList;
139: }
140: private:
141: PartNode * pHead;
142: int itsCount;
143: static PartsList GlobalPartsList;
144: };
145:
146: PartsList PartsList::GlobalPartsList;
147:
148: // Implementierungen fuer Lists...
149:
150: PartsList::PartsList():
151: pHead(0),
152: itsCount(0)
153: {}
154:
155: PartsList::~PartsList()
156: {
157: delete pHead;
158: }
159:
160: Part* PartsList::GetFirst() const
161: {
162: if (pHead)
163: return pHead->itsPart;
164: else
165: return NULL; // Fehler auffangen
166: }
167:
168: Part * PartsList::operator[](int offSet) const
169: {
170: PartNode* pNode = pHead;
171:
172: if (!pHead)
173: return NULL; // Fehler auffangen
174:
175: if (offSet > itsCount)
176: return NULL; // Fehler
177:
178: for (int i=0;i<offSet; i++)
179: pNode = pNode->itsNext;
180:
181: return pNode->itsPart;
182: }
183:
184: Part* PartsList::Find(int & position, int PartNumber) const
185: {
186: PartNode * pNode = 0;
187: for (pNode = pHead, position = 0;
188: pNode!=NULL;
189: pNode = pNode->itsNext, position++)
190: {
191: if (pNode->itsPart->GetPartNumber() == PartNumber)
192: break;
193: }
194: if (pNode == NULL)
195: return NULL;
196: else
197: return pNode->itsPart;
198: }
199:
200: void PartsList::Iterate(void (Part::*func)()const) const
201: {
202: if (!pHead)
203: return;
204: PartNode* pNode = pHead;
205: do
206: (pNode->itsPart->*func)();
207: while (pNode = pNode->itsNext);
208: }
209:
210: void PartsList::Insert(Part* pPart)
211: {
212: PartNode * pNode = new PartNode(pPart);
213: PartNode * pCurrent = pHead;
214: PartNode * pNext = 0;
215:
216: int New = pPart->GetPartNumber();
217: int Next = 0;
218: itsCount++;
219:
220: if (!pHead)
221: {
222: pHead = pNode;
223: return;
224: }
225:
226: // Ist dieser kleiner als head
227: // dann ist dies der neue head
228: if (pHead->itsPart->GetPartNumber() > New)
229: {
230: pNode->itsNext = pHead;
231: pHead = pNode;
232: return;
233: }
234:
235: for (;;)
236: {
237: // gibt es keinen next, den neuen anhaengen
238: if (!pCurrent->itsNext)
239: {
240: pCurrent->itsNext = pNode;
241: return;
242: }
243:
244: // gehoert der Knoten zwischen diesen und den naechsten,
245: // dann hier einfuegen, ansonsten zu naechstem Knoten gehen
246: pNext = pCurrent->itsNext;
247: Next = pNext->itsPart->GetPartNumber();
248: if (Next > New)
249: {
250: pCurrent->itsNext = pNode;
251: pNode->itsNext = pNext;
252: return;
253: }
254: pCurrent = pNext;
255: }
256: }
257:
258: class PartsCatalog : private PartsList
259: {
260: public:
261: void Insert(Part *);
262: int Exists(int PartNumber);
263: Part * Get(int PartNumber);
264: operator+(const PartsCatalog &);
265: void ShowAll() { Iterate(Part::Display); }
266: private:
267: };
268:
269: void PartsCatalog::Insert(Part * newPart)
270: {
271: int partNumber = newPart->GetPartNumber();
272: int offset;
273:
274: if (!Find(offset, partNumber))
275: PartsList::Insert(newPart);
276: else
277: {
278: cout << partNumber << " war der ";
279: switch (offset)
280: {
281: case 0: cout << "erste "; break;
282: case 1: cout << "zweite "; break;
283: case 2: cout << "dritte "; break;
284: default: cout << offset+1 << "th ";
285: }
286: cout << "Eintrag. Abgelehnt!\n";
287: }
288: }
289:
290: int PartsCatalog::Exists(int PartNumber)
291: {
292: int offset;
293: Find(offset,PartNumber);
294: return offset;
295: }
296:
297: Part * PartsCatalog::Get(int PartNumber)
298: {
299: int offset;
300: return (Find(offset, PartNumber));
301:
302: }
303:
304: int main()
305: {
306: PartsCatalog pc;
307: Part * pPart = 0;
308: int PartNumber;
309: int value;
310: int choice;
311:
312: while (1)
313: {
314: cout << "(0)Beenden (1)Auto (2)Flugzeug: ";
315: cin >> choice;
316:
317: if (!choice)
318: break;
319:
320: cout << "Neue Teilenummer?: ";
321: cin >> PartNumber;
322:
323: if (choice == 1)
324: {
325: cout << "Baujahr?: ";
326: cin >> value;
327: pPart = new CarPart(value,PartNumber);
328: }
329: else
330: {
331: cout << "Motor-Nummer?: ";
332: cin >> value;
333: pPart = new AirPlanePart(value,PartNumber);
334: }
335: pc.Insert(pPart);
336: }
337: pc.ShowAll();
338: return 0;
339: }
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 1234
Baujahr?: 94
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 4434
Baujahr?: 93
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 1234
Baujahr?: 94
1234 war der erste Eintrag. Abgelehnt!
(0)Beenden (1)Auto (2)Flugzeug: 1
Neue Teilenummer?: 2345
Baujahr?: 93
(0)Beenden (1)Auto (2)Flugzeug: 0
Teilenummer: 1234
Baujahr: 94
Teilenummer: 2345
Baujahr: 93
Teilenummer: 4434
Baujahr: 93
Zeile 82 deklariert die Klasse PartsList
als Friend von PartNode
.
Im Beispiel befindet sich die Friend-Deklaration in dem öffentlichen Abschnitt. Dies ist
aber nicht erforderlich, Sie können sie auch an einer beliebigen anderen Stelle in der
Klassendeklaration einfügen, ohne daß die Bedeutung der Anweisung sich dadurch
ändert. Aufgrund der Friend-Deklaration sind alle privaten Datenelemente und -funktionen
für die Elementfunktionen der Klasse PartsList
verfügbar.
In Zeile 160 spiegelt die Implementierung der Elementfunktion GetFirst()
diese Änderung
wider. Anstatt pHead->GetPart()
zurückzuliefern, kann diese Funktion jetzt die
ansonsten privaten Datenelemente mit der Anweisung pHead->itsPart
zurückgeben.
Entsprechend kann Insert()
statt pNode->SetNext(pHead)
jetzt pNode->itsNext =
pHead
schreiben.
Dies sind zugegebenermaßen unwesentliche Änderungen und es gibt auch keinen
wirklich guten Grund, um PartsList
zum Friend von PartNode
zu machen, aber sie
veranschaulichen doch ausreichend, wie man das Schlüsselwort friend
verwendet.
Deklarationen von Friend-Klassen sollte man mit äußerster Vorsicht verwenden. Wenn zwei Klassen durch und durch verflochten sind und man häufig auf Daten in der anderen zugreifen muß, gibt es gute Gründe, diese Deklaration zu verwenden. Trotzdem sollte man sparsam damit umgehen. Es ist oftmals genauso einfach, die öffentlichen Zugriffsmethoden zu nutzen. Letzterer Ansatz hat außerdem den Vorteil, daß man eine Klasse ändern kann, ohne die andere neu kompilieren zu müssen.
Oftmals beklagen sich Neueinsteiger in die C++-Programmierung darüber, daß Friend-Deklarationen die für die objektorientierte Programmierung so bedeutsame Kapselung untergraben. Offen gesagt ist das blanker Unsinn. Die Friend-Deklaration macht den deklarierten Freund zum Teil der Klassenschnittstelle und ist genausowenig ein Unterwandern der Kapselung wie eine öffentliche Ableitung.
Um eine Klasse als Friend einer anderen zu deklarieren, führen Sie die Klasse zusammen mit dem Schlüsselwort
friend
in der Klasse, die die Zugriffsrechte einräumt, auf. Ich kann sozusagen Sie als meinen Freund deklarieren, Sie aber können sich nicht selbst als mein Freund ausgeben.class PartNode{
public:
friend class PartsList; // deklariert PartsList als Freund
// von PartNode
};
Manchmal will man den freundschaftlichen Zugriff nicht für eine gesamte Klasse, sondern nur für eine oder zwei Funktionen dieser Klasse gewähren. Das läßt sich realisieren, indem man die Elementfunktionen der anderen Klasse und nicht die gesamte Klasse als Friend deklariert. In der Tat kann man jede Funktion als Friend-Funktion deklarieren, ob sie nun eine Elementfunktion einer anderen Klasse ist oder nicht.
Listing 15.1 stellte eine String
-Klasse bereit, die den Operator operator+
überschrieb.
Zusätzlich wurde ein Konstruktor deklariert, der einen konstanten char
-Zeiger übernahm,
so daß es möglich war, aus C-Strings String-Objekte zu erzeugen. Auf diese
Weise konnten Sie einen String erzeugen und ihm einen C-String anhängen.
C-Strings sind Zeichen-Arrays mit einem abschließenden Null-Zeichen, wie
char mystring[] = "Hello World".
Was jedoch nicht möglich war: einen C-String (einen Zeichenstring) zu erzeugen und ihm ein String-Objekt wie im folgenden Beispiel anzuhängen:
char cString[] = {"Hello"};
String sString(" world");
String sStringTwo = cString + sString; //Fehler
C-Strings haben keinen überladenen operator+
. Wie Sie bereits in Kapitel 10, »Funktionen
- weiterführende Themen«, gelernt haben, verbirgt sich hinter dem Aufruf
cString + sString
im Grunde genommen der Aufruf cString.operator+(sString)
.
Da Sie operator+
jedoch nicht für einen C-String aufrufen können, wird ein Compiler-
Fehler gemeldet.
Dieses Problem läßt sich lösen, indem Sie in String eine Friend-Funktion deklarieren,
die operator+
überlädt und zwei String-Objekte als Argumente übernimmt. Der C-
String wird von dem entsprechenden Konstruktor in ein String-Objekt konvertiert und
anschließend wird operator+
mit den beiden String-Objekte aufgerufen.
Listing 15.8: Der freundliche +-Operator
1: //Listing 15.8 - Friend-Operatoren
2:
3: #include <iostream.h>
4: #include <string.h>
5:
6: // Rudimentaere String-Klasse
7: class String
8: {
9: public:
10: // Konstruktoren
11: String();
12: String(const char *const);
13: String(const String &);
14: ~String();
15:
16: // Ueberladene Operatoren
17: char & operator[](int offset);
18: char operator[](int offset) const;
19: String operator+(const String&);
20: friend String operator+(const String&, const String&);
21: void operator+=(const String&);
22: String & operator= (const String &);
23:
24: // Allgemeine Zugriffsfunktionen
25: int GetLen()const { return itsLen; }
26: const char * GetString() const { return itsString; }
27:
28: private:
29: String (int); // privater Konstruktor
30: char * itsString;
31: unsigned short itsLen;
32: };
33:
34: // Standardkonstruktor erzeugt String von 0 Byte
35: String::String()
36: {
37: itsString = new char[1];
38: itsString[0] = '\0';
39: itsLen=0;
40: // cout << "\tString-Standardkonstruktor\n";
41: // ConstructorCount++;
42: }
43:
44: // Privater (Hilfs-) Konstruktor, der nur von Methoden
45: // der Klasse zum Erzeugen neuer Null-Strings der
46: // erforderlichen Groeße verwendet wird.
47: String::String(int len)
48: {
49: itsString = new char[len+1];
50: for (int i = 0; i<=len; i++)
51: itsString[i] = '\0';
52: itsLen=len;
53: // cout << "\tString(int)-Konstruktor\n";
54: // ConstructorCount++;
55: }
56:
57: //Konvertiert einen Zeichen-Array in einen String
58: String::String(const char * const cString)
59: {
60: itsLen = strlen(cString);
61: itsString = new char[itsLen+1];
62: for (int i = 0; i<itsLen; i++)
63: itsString[i] = cString[i];
64: itsString[itsLen]='\0';
65: // cout << "\tString(char*)-Konstruktor\n";
66: // ConstructorCount++;
67: }
68:
69: // Kopierkonstruktor
70: String::String (const String & rhs)
71: {
72: itsLen=rhs.GetLen();
73: itsString = new char[itsLen+1];
74: for (int i = 0; i<itsLen;i++)
75: itsString[i] = rhs[i];
76: itsString[itsLen] = '\0';
77: // cout << "\tString(String&)-Konstruktor\n";
78: // ConstructorCount++;
79: }
80:
81: // Destruktor, gibt zugewiesenen Speicher frei
82: String::~String ()
83: {
84: delete [] itsString;
85: itsLen = 0;
86: // cout << "\tString-Destruktor\n";
87: }
88:
89: // Zuweisungsoperator, gibt vorhandenen Speicher frei,
90: // kopiert dann String und Groeße
91: String& String::operator=(const String & rhs)
92: {
93: if (this == &rhs)
94: return *this;
95: delete [] itsString;
96: itsLen=rhs.GetLen();
97: itsString = new char[itsLen+1];
98: for (int i = 0; i<itsLen;i++)
99: itsString[i] = rhs[i];
100: itsString[itsLen] = '\0';
101: return *this;
102: // cout << "\tString-Operator=\n";
103: }
104:
105: // Nicht konstanter Offset-Operator, gibt Referenz
106: // auf Zeichen zurueck, das sich damit aendern
107: // laesst!
108: char & String::operator[](int offset)
109: {
110: if (offset > itsLen)
111: return itsString[itsLen-1];
112: else
113: return itsString[offset];
114: }
115:
116: // Konstanter Offset-Operator fuer konstante
117: // Objekte (siehe Kopierkonstruktor!)
118: char String::operator[](int offset) const
119: {
120: if (offset > itsLen)
121: return itsString[itsLen-1];
122: else
123: return itsString[offset];
124: }
125: // Erzeugt einen neuen String durch Anfuegen von rhs
126: // an den aktuellen String
127: String String::operator+(const String& rhs)
128: {
129: int totalLen = itsLen + rhs.GetLen();
130: String temp(totalLen);
131: int i, j;
132: for (i = 0; i<itsLen; i++)
133: temp[i] = itsString[i];
134: for (j = 0, i = itsLen; j<rhs.GetLen(); j++, i++)
135: temp[i] = rhs[j];
136: temp[totalLen]='\0';
137: return temp;
138: }
139:
140: // erzeugt einen neuen String, indem ein String
141: // an einen anderen String gehaengt wird
142: String operator+(const String& lhs, const String& rhs)
143: {
144: int totalLen = lhs.GetLen() + rhs.GetLen();
145: String temp(totalLen);
146: int i, j;
147: for (i = 0; i<lhs.GetLen(); i++)
148: temp[i] = lhs[i];
149: for (j = 0, i = lhs.GetLen();; j<rhs.GetLen(); j++, i++)
150: temp[i] = rhs[j];
151: temp[totalLen]='\0';
152: return temp;
153: }
154:
155: int main()
156: {
157: String s1("String Eins ");
158: String s2("String Zwei ");
159: char *c1 = { "C-String Eins " } ;
160: String s3;
161: String s4;
162: String s5;
163:
164: cout << "s1: " << s1.GetString() << endl;
165: cout << "s2: " << s2.GetString() << endl;
166: cout << "c1: " << c1 << endl;
167: s3 = s1 + s2;
168: cout << "s3: " << s3.GetString() << endl;
169: s4 = s1 + c1;
170: cout << "s4: " << s4.GetString() << endl;
171: s5 = c1 + s2;
172: cout << "s5: " << s5.GetString() << endl;
173: return 0;
174: }
s1: String Eins
s2: String Zwei
c1: C-String Eins
s3: String Eins String Zwei
s4: String Eins C-String Eins
s5: C-String Eins String Zwei
Bis auf operator+
wurden die Implementierungen der String-Methoden unverändert
aus Listing 15.1 übernommen. Zeile 20 überlädt einen neuen operator+,
der zwei
konstante String-Referenzen übernimmt und einen String zurückgibt. Diese Funktion
wird als Friend deklariert.
Beachten Sie, daß operator+
keine Elementfunktion dieser oder einer anderen Klasse
ist. Die Deklaration der Operatorfunktion in der String-Klasse dient lediglich dazu, sie
als Friend zu kennzeichnen. Da sie aber nun einmal deklariert wird, wird kein anderer
Funktionsprototyp benötigt.
Die Implementierung von operator+
befindet sich in den Zeilen 142 bis 153. Beachten
Sie, daß sie der Implementierung des früheren +-Operators sehr ähnlich ist. Hier
übernimmt die Funktion jedoch zwei Strings und manipuliert diese ausnahmslos über
deren öffentliche Zugriffsfunktionen.
Das Rahmenprogramm veranschaulicht den Einsatz dieser Funktion (Zeile 171). Dort
wird operator+
auf einen C-String angewendet.
Die Deklaration einer Friend-Funktion erfolgt mittels des Schlüsselwortes
friend
und der vollständigen Spezifikation der Funktion. Eine Funktion, die alsfriend
deklariert wurde, erhält keinen Zugriff auf denthis
-Zeiger der Klasse, sie hat jedoch vollen Zugriff auf alle privaten und geschützten Datenelemente und -funktionen.class PartNode
{ // ...
// Elementfunktion einer anderen Klasse als friend deklarieren
friend void PartsList::Insert(Part *);
// eine globale Funktion als friend deklarieren
friend int SomeFunction();
// ...
};
Jetzt sind Sie soweit, Ihre String
-Klasse so auszustatten, daß String
-Objekte, wie Objekte
der elementaren Datentypen mit cout
ausgegeben werden können. Bis jetzt
mußten Sie, um einen String auszugeben, folgenden Code aufsetzen:
cout << einString.GetString();
Besser wäre aber folgende Schreibweise:
cout << einString;
Um dies zu erreichen, müssen Sie den Ausgabe-Operator operator<<()
überladen. In
Kapitel 16, »Streams«, erfahren Sie Näheres zur Arbeit mit iostreams
. Hier soll Listing
15.9 zeigen, wie operator<<
mit Hilfe einer friend
-Funktion überladen werden kann.
Listing 15.9: Überladen des Ausgabe-Operators <<
1: #include <iostream.h>
2: #include <string.h>
3:
4: class String
5: {
6: public:
7: // Konstruktoren
8: String();
9: String(const char *const);
10: String(const String &);
11: ~String();
12:
13: // Ueberladene Operatoren
14: char & operator[](int offset);
15: char operator[](int offset) const;
16: String operator+(const String&);
17: void operator+=(const String&);
18: String & operator= (const String &);
19: friend ostream& operator<<
20: ( ostream& theStream,String& theString);
21: // Allgemeine Zugriffsfunktionen
22: int GetLen()const { return itsLen; }
23: const char * GetString() const { return itsString; }
24:
25: private:
26: String (int); // privater Konstruktor
27: char * itsString;
28: unsigned short itsLen;
29: };
30:
31:
32: // Standardkonstruktor erzeugt String von 0 Byte Laenge
33: String::String()
34: {
35: itsString = new char[1];
36: itsString[0] = '\0';
37: itsLen=0;
38: // cout << "\tString-Standardkonstruktor\n";
39: // ConstructorCount++;
40: }
41:
42: // Privater (Hilfs-) Konstruktor, der nur von Methoden
43: // der Klasse zum Erzeugen eines neuen Null-Strings der
44: // erforderlichen Groeße verwendet wird.
45: String::String(int len)
46: {
47: itsString = new char[len+1];
48: for (int i = 0; i<=len; i++)
49: itsString[i] = '\0';
50: itsLen=len;
51: // cout << "\tString(int)-Konstruktor\n";
52: // ConstructorCount++;
53: }
54:
55: // Konvertiert einen Zeichen-Array in einen String
56: String::String(const char * const cString)
57: {
58: itsLen = strlen(cString);
59: itsString = new char[itsLen+1];
60: for (int i = 0; i<itsLen; i++)
61: itsString[i] = cString[i];
62: itsString[itsLen]='\0';
63: // cout << "\tString(char*)-Konstruktor\n";
64: // ConstructorCount++;
65: }
66:
67: // Kopierkonstruktor
68: String::String (const String & rhs)
69: {
70: itsLen=rhs.GetLen();
71: itsString = new char[itsLen+1];
72: for (int i = 0; i<itsLen;i++)
73: itsString[i] = rhs[i];
74: itsString[itsLen] = '\0';
75: // cout << "\tString(String&)-Konstruktor\n";
76: // ConstructorCount++;
77: }
78:
79: // Destruktor, gibt zugewiesenen Speicher frei
80: String::~String ()
81: {
82: delete [] itsString;
83: itsLen = 0;
84: // cout << "\tString-Destruktor\n";
85: }
86:
87: // Zuweisungsoperator, gibt vorhandenen Speicher frei,
88: // kopiert dann String und Groeße
89: String& String::operator=(const String & rhs)
90: {
91: if (this == &rhs)
92: return *this;
93: delete [] itsString;
94: itsLen=rhs.GetLen();
95: itsString = new char[itsLen+1];
96: for (int i = 0; i<itsLen;i++)
97: itsString[i] = rhs[i];
98: itsString[itsLen] = '\0';
99: return *this;
100: // cout << "\tString-Operator=\n";
101: }
102:
103: // Nicht konstanter Offset-Operator, gibt Referenz
104: // auf Zeichen zurueck, das sich damit aendern
105: // laeßt!
106: char & String::operator[](int offset)
107: {
108: if (offset > itsLen)
109: return itsString[itsLen-1];
110: else
111: return itsString[offset];
112: }
113:
114: // Konstanter Offset-Operator fuer konstante
115: // Objekte (siehe Kopierkonstruktor!)
116: char String::operator[](int offset) const
117: {
118: if (offset > itsLen)
119: return itsString[itsLen-1];
120: else
121: return itsString[offset];
122: }
123:
124: // Erzeugt einen neuen String durch Anfuegen von rhs
125: // an den aktuellen String
126: String String::operator+(const String& rhs)
127: {
128: int totalLen = itsLen + rhs.GetLen();
129: String temp(totalLen);
130: int i, j;
131: for (i = 0; i<itsLen; i++)
132: temp[i] = itsString[i];
133: for (j = 0; j<rhs.GetLen(); j++, i++)
134: temp[i] = rhs[j];
135: temp[totalLen]='\0';
136: return temp;
137: }
138:
139: // aendert aktuellen String, liefert nichts zurueck
140: void String::operator+=(const String& rhs)
141: {
142: unsigned short rhsLen = rhs.GetLen();
143: unsigned short totalLen = itsLen + rhsLen;
144: String temp(totalLen);
145: int i, j;
146: for (i = 0; i<itsLen; i++)
147: temp[i] = itsString[i];
148: for (j = 0, i = 0; j<rhs.GetLen(); j++, i++)
149: temp[i] = rhs[i-itsLen];
150: temp[totalLen]='\0';
151: *this = temp;
152: }
153:
154: // int String::ConstructorCount =
155: ostream& operator<< ( ostream& theStream,String& theString)
156: {
157: theStream << theString.itsString;
158: return theStream;
159: }
160:
161: int main()
162: {
163: String theString("Hello world.");
164: cout << theString;
165: return 0;
166: }
Hello world.
Zeile 19 deklariert operator<<
als friend
-Funktion, die eine ostream
-Referenz und
eine String
-Referenz übernimmt und eine ostream
-Referenz zurückgibt. Beachten Sie,
daß es sich dabei nicht um eine Elementfunktion von String
handelt. Der Operator
liefert eine Referenz auf ein ostream
-Objekt zurück, damit Aufrufe von operator<<
wie
folgt verkettet werden können:
cout << "meinAlter: " << itsAge << " Jahre.";
Die Implementierung der friend
-Funktion finden Sie in den Zeilen 155 bis 159. Der
Code verbirgt lediglich die Weiterleitung des Strings an ostream
, und genau das soll ja
auch erreicht werden. In Kapitel 16 werden Sie mehr zur Überladung der Operatoren
und des Operator>> erfahren
.
Heute haben Sie gelernt, wie man funktionelle Aufgaben an eingebettete Objekte delegiert. Sie haben auch gesehen, wie man eine Klasse mit Hilfe einer anderen (durch Einbettung oder private Vererbung) implementiert. Die Einbettung ist insofern eingeschränkt, als die neue Klasse keinen Zugriff auf geschützte Elemente der eingebetteten Klasse hat und sie auch nicht die Elementfunktionen des eingebetteten Objekts überschreiben kann. Einbettung ist einfacher anzuwenden als private Vererbung und sollte möglichst den Vorzug erhalten.
Weiterhin wurde erläutert, wie man sowohl Friend-Funktionen als auch Friend-Klassen
deklariert. Am Beispiel des Ausgabe-Operators haben Sie gesehen, wie man eine Friend-Funktion
definiert und wie man Objekte selbst definierter Klassen über cout
ausgeben
kann.
Denken Sie daran, daß öffentliche Vererbung eine ist-ein-Beziehung, Einbettung eine hat-ein-Beziehung und private Vererbung implementiert mit Hilfe von ausdrückt. Die Beziehung delegiert an kann sowohl durch Einbettung als auch durch private Vererbung ausgedrückt werden. Einbettung ist jedoch gebräuchlicher.
Frage:
Warum ist es so wichtig, zwischen ist-ein, hat-ein und implementiert
mit Hilfe von zu differenzieren?
Antwort:
Das Ziel von C++ ist die Implementierung gut konzipierter objektorientierten
Programme. Durch Auseinanderhalten dieser Beziehungen können Sie leichter
sicherstellen, daß Ihr Entwurf auch mit der abzubildenden Wirklichkeit
übereinstimmt. Außerdem führt ein gut konzipierter Entwurf häufig auch zu
einem leichter verständlichen Code.
Frage:
Warum sollte man die Einbettung der privaten Vererbung vorziehen?
Antwort:
Eine der Herausforderungen der modernen Programmierung ist, der zunehmenden
Komplexität Herr zu werden. Je weniger Gedanken Sie sich bei der
Arbeit mit den Objekten Ihrer Klassen um die Details von deren Implementierung
machen müssen, um so mehr Komplexität können Sie in Ihren Programmen
bewältigen. Enthaltene Klassen verbergen ihre Details, durch private
Vererbung werden die Implementierungsdetails bloßgelegt.
Frage:
Warum macht man nicht alle Klassen zu Freunden aller Klassen, die sie
verwenden?
Antwort:
Wenn man eine Klasse zum Friend einer anderen macht, legt man die Details
der Implementierung frei und verringert die Kapselung. Im Idealfall sollte
man möglichst viele Details jeder Klasse vor allen anderen Klassen verbergen.
Frage:
Wenn eine Funktion überladen wurde, müssen Sie dann alle überladenen
Versionen der Funktion als friend deklarieren?
Antwort:
Ja. Wenn Sie eine Funktion überladen und sie als Friend einer anderen deklarieren,
müssen Sie jede andere Version, der Sie ebenfalls diesen Zugriff einräumen
wollen, ebenfalls als friend
deklarieren.
Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie die Lösungen in Anhang D lesen und zur Lektion des nächsten Tages übergehen.
friend
-Funktion?
friend
-Klasse?
Dog
ein Friend von Boy
ist, ist Boy
dann auch ein Friend von Dog
?
Dog
ein Friend von Boy
ist und Terrier
sich von Dog
ableitet, ist Terrier
dann auch ein Friend von Boy
?
Dog
ein Friend von Boy
ist und Boy
ein Friend von House
ist, ist Dog
dann auch ein Friend von House
?
friend
-Funktion deklarieren?
Animal
auf, die ein String-Objekt als Datenelement enthält.
BoundedArray
, die ein Array
darstellt.
Menge
auf der Grundlage der Klasse Array
.
>>
) fuer die String
-Klasse.
1: #include <iostream.h>
2:
3: class Animal;
4:
5: void setValue(Animal& , int);
6:
7:
8: class Animal
9: {
10: public:
11: int GetWeight()const { return itsWeight; }
12: int GetAge() const { return itsAge; }
13: private:
14: int itsWeight;
15: int itsAge;
16: };
17:
18: void setValue(Animal& theAnimal, int theWeight)
19: {
20: friend class Animal;
21: theAnimal.itsWeight = theWeight;
22: }
23:
24: int main()
25: {
26: Animal peppy;
27: setValue(peppy,5);
28: }
1: #include <iostream.h>
2:
3: class Animal;
4:
5: void setValue(Animal& , int);
6: void setValue(Animal& ,int,int);
7:
8: class Animal
9: {
10: friend void setValue(Animal& ,int);
11: private:
12: int itsWeight;
13: int itsAge;
14: };
15:
16: void setValue(Animal& theAnimal, int theWeight)
17: {
18: theAnimal.itsWeight = theWeight;
19: }
20:
21:
22: void setValue(Animal& theAnimal, int theWeight, int theAge)
23: {
24: theAnimal.itsWeight = theWeight;
25: theAnimal.itsAge = theAge;
26: }
27:
28: int main()
29: {
30: Animal peppy;
31: setValue(peppy,5);
32: setValue(peppy,7,9);
33: }
© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH