Ein Telefonbuch: Die Klasse TelephoneBook

1. Aufgabe

Unter einem Nachschlagewerk (engl. Dictionary) versteht man eine Datenstruktur, die zu einem beliebigen Index eine bestimme Information abspeichert. Ein Beispiel für ein Dictionary stellt ein englisches Wörterbuch dar: Der Index wird durch ein deutsches Wort gegeben, dahinter verbirgt sich die englische Übersetzung des Wortes.

Ein weiteres Beispiel ist durch ein Telefonbuch gegeben. Als Index dient der Name einer Person, die Telefonnummer der Person verbirgt sich hinter dem Index: Implementieren Sie ein Dictionary, das die Funktionalität eines Telefonbuchs besitzt. Entwerfen Sie zu diesem Zweck eine Klasse TelephoneBook an Hand der nun folgenden Detailbeschreibungen.

1.1. Öffentliche Schnittstelle der Klasse TelephoneBook

Die öffentlichen Methoden der Klasse TelephoneBook finden Sie in Tabelle 1 beschrieben vor:

Methode

Schnittstelle und Beschreibung

Methode Count

int Count () const;

Liefert die Anzahl der Einträge eines Telefonbuchs zurück.

Methode Insert

bool Insert (char* name, int number);

Einfügen einer Person in das Telefonbuch. Zur Vereinfachung besitzt eine Person nur einen einzigen Namen (Parameter name) sowie eine Telefonnummer (Parameter number).

Methode Contains

bool Contains (char* name) const;

Liefert true zurück, wenn eine Person mit dem Namen name im Telefonbuch enthalten ist, andernfalls false.

Methode Get

bool Get (char* name, int* number) const;

Liefert die Telefonnummer einer Person mit dem Namen name zurück. Im zweiten Parameter number ist die Adresse einer int-Variablen zu übergeben, so dass nach dem erfolgreichen Aufruf von Get (Rückgabewert true) die int-Variable die Telefonnummer enthält. Ist die gesuchte Person im Telefonbuch nicht enthalten, kehrt ein Aufruf von Get mit dem Wert false zurück.

Methode Remove

bool Remove (char* name);

Entfernt die Daten der Person mit dem Namen name aus dem Telefonbuch. Mit dem Rückgabewert wird der Erfolg der Ausführung mitgeteilt.

Tabelle 1. Öffentliche Methoden der Klasse TelephoneBook.

1.2. Operatoren der Klasse TelephoneBook

Zusätzlich zu den in Tabelle 1 beschriebenen Methoden gibt es eine Reihe Operatoren, die mit Operanden des Typs TelephoneBook zusammenarbeiten. Ihre Beschreibung entnehmen Sie bitte Tabelle 2:

Operator

Schnittstelle und Beschreibung

operator<<

friend ostream& operator<< (ostream&, const TelephoneBook&);

Für die Ausgabe des Telefonbuchs auf der Konsole ist der <<-Operator in der Klasse TelephoneBook zu implementieren. Die Ausgabe eines Telefonbuchs könnte in etwa so aussehen:

Contents of Phonebook:
Name: Franz, Phone Number: 161234
Name: Peter, Phone Number: 327531
Name: Susan, Phone Number: 643675

operator==

friend bool operator== (const TelephoneBook&, const TelephoneBook&);

Vergleich zweier Telefonbücher. Beachten Sie dabei, dass es für die Gleichheit zweier Telefonbücher keine Rolle spielt, in welcher Reihenfolge die einzelnen Einträge im Telefonbuch abgelegt sind.

operator!=

friend bool operator!= (const TelephoneBook&, const TelephoneBook&);

Vergleich zweier Telefonbücher auf Ungleichheit. Weitere Hinweise siehe ==-Operator.

Tabelle 2. Operatoren der Klasse TelephoneBook.

1.3. Aufzählung (Enumeration) aller Personen des Telefonbuchs

Um eine Person nach der anderen aufzulisten, die in einem Telefonbuch eingetragen ist, stellt man in der Regel eine so genannte Aufzählungs- oder Enumerationsschnittstelle zur Verfügung. Im Falle eines Telefonbuchs bieten sich die folgenden zwei Methoden HasMoreEntries und GetNextEntry an:

bool HasMoreEntries ();
void GetNextEntry (char[] name, int* number);

Die Anwendung einer Enumerationsschnittstelle sieht so aus:

while (book.HasMoreEntries ())
{
    char szName[32];
    int number;
    book.GetNextEntry (szName, &number);
    cout << "found " << szName << ", number is " << number << endl;
}

Eine Aufzählung ist wie folgt definiert:

  • An einem aufzählbaren Objekt muss vor dem ersten Zugriff mit GetNextEntry zuerst die HasMoreEntries-Methode aufgerufen worden sein.

  • HasMoreEntries liefert den Wert true zurück, solange noch Personen in dem Telefonbuch vorhanden sind.

  • Durch abwechselnde Aufrufe der HasMoreEntries- und GetNextEntry-Methode traversiert man das aufzählbare Objekt Element für Element.

  • Unmittelbar aufeinanderfolgende Aufrufe von GetNextEntry liefern stets dasselbe Element zurück.

  • Wird HasMoreEntries nicht vor GetNextEntry aufgerufen, liefert GetNextEntry möglicherweise einen ungültigen Wert zurück.

  • Nach dem Ende einer Aufzählung kann man diese erneut einleiten. Sie beginnt wie gehabt zunächst mit einem Aufruf von HasMoreEntries.

Tipp: Eine Aufzählung wird mit Hilfe eines internen Aufzählungsindex in der Klasse realisiert.

1.4. Zusammenfassen zweier Telefonbücher

In der Praxis kommt es häufig vor, dass Sie ein Telefonbuch um die Daten eines zweiten Telefonbuchs ergänzen wollen. Entwerfen Sie eine Methode Import, die das Importieren eines Telefonbuchs durchführt.

Beispiel: Aktuelles Telefonbuch vor dem Import:

Contents of Phonebook:
Name: Franz, Phone Number: 161234
Name: Peter, Phone Number: 327531
Name: Susan, Phone Number: 643675

Zu importierendes Telefonbuch:

Contents of Phonebook:
Name: Otto, Phone Number: 654321
Name: Peter, Phone Number: 327531

Aktuelles Telefonbuch nach dem Import:

Contents of Phonebook:
Name: Franz, Phone Number: 161234
Name: Otto, Phone Number: 654321
Name: Peter, Phone Number: 327531
Name: Susan, Phone Number: 643675

2. Lösung

2.1. Entwurfsüberlegungen

Zum Studium des weiteren Vorgehens betrachten wir im Folgenden vier Ansätze, wie Objekte der Klassen TelephoneBook und Entry eine Einheit bilden können. In allen vier Fällen geht es um ein Array im Instanzvariablenbereich eines TelephoneBook-Objekts. Anders formuliert: Das Array steht in der has-a Beziehung zum TelephoneBook-Objekt.

2.1.1. Variante 1: Feste Anzahl von Entry-Objekten.

In der ersten Variante besitzt ein TelephoneBook-Objekt bereits nach seiner Erzeugung ein komplettes Array mit Entry-Objekten im Instanzdatenbereich:

const int MAX_ENTRIES = 20;

class TelephoneBook
{
private:
    Entry m_entries[MAX_ENTRIES];
    ...
};

In der grafischen Illustration dieser Variante in Abbildung 1 finden Sie sowohl belegte Entry-Objekte (mit den Telefonnummern 123, 456 und 789) wie auch unbelegte Entry-Objekte vor (Telefonnummer ist mit dem Wert -1 vorlegt):

Instanzdatenbereich eines TelephoneBook-Objekts mit einem Array fester Länge bestehend aus Entry-Objekten.

Abbildung 1. Instanzdatenbereich eines TelephoneBook-Objekts mit einem Array fester Länge bestehend aus Entry-Objekten.


Diese Variante ist vergleichsweise einfach zu implementieren, sie ist jedoch zur Laufzeit mit einigen gewichtigen Nachteilen ausgestattet:

  • Nachteil: Der Speicherplatz wird sehr unökonomisch in Anspruch genommen, da bereits bei einem leeren Telefonbuch die maximale Anzahl von Entry-Objekten im Speicher vorhanden ist.

  • Nachteil: Das Telefonbuch ist bezüglich seines Umfangs nicht veränderbar, da Arrays in C++ nur mit einer konstanten Länge definierbar sind.

Da zu jedem Zeitpunkt eine bestimmte Anzahl von Entry-Objekten im Telefonbuch vorhanden ist, muss es möglich sein, an einem Entry-Objekt zu erkennen, ob es mit einem Eintrag gefüllt ist oder ob es leer ist. Dies könnte man zum Beispiel an dem int-Wert festmachen, der die Telefonnummer enthält. Ein Wert -1 würde dann für ein unbelegtes Entry-Objekt stehen.

2.1.2. Variante 2: Variable Anzahl von Entry-Objekten.
Instanzdatenbereich eines TelephoneBook-Objekts mit einem Array variabler Länge bestehend aus Entry-Objekten.

Abbildung 2. Instanzdatenbereich eines TelephoneBook-Objekts mit einem Array variabler Länge bestehend aus Entry-Objekten.


In der zweiten Variante wurde in Abbildung 2 darauf geachtet, dass alle Entry-Objekt belegt sind. Unbelegte Entry-Objekte wären zwar prinzipiell auch denkbar, ergeben hier aber weniger Sinn, da wir es dann eigentlich mit der ersten Variante zu tun hätten. Neben einer Zeigervariablen, die auf den Anfang des dynamisch allokierten Arrays verweist, benötigen wir noch eine zweite Instanzvariable, die die Arraylänge verwaltet:

class TelephoneBook
{
private:
    Entry* m_entries;
    int m_count;
    ...
};

Die Vor- und Nachteile dieser Variante lassen sich so zusammenfassen:

  • Vorteil: Es lassen sich prinzipiell beliebig viele Einträge im Telefonbuch ablegen.

  • Nachteil: Vermeidet man, dass das Array der Entry-Objekte keine leeren Einträge enthält, muss bei jeder Einfüge- und Löschoperation das vorhandene Entry-Array umkopiert werden. Diese Operation ist sehr zeitintensiv.

Beim dynamischen Freigeben und Neuanlegen eines Arrays bestehend aus Entry-Objekten ist zu beachten, dass sehr viele Konstruktor- und Destruktor-Aufrufe der Klasse Entry zum Zuge kommen. Aus Sicht der Laufzeit ist dies sicherlich ein sehr gewichtiger Nachteil. Die nächsten Varianten versuchen dieses Manko dadurch zu umgehen, dass die Entry-Objekte prinzipiell nur noch dynamisch (mit new) allokiert werden und zu ihrer Verwaltung nun ein Array bestehend aus Zeigern auf Entry-Objekte ins Spiel kommt!

2.1.3. Variante 3: Feste Anzahl von Zeigern auf Entry-Objekte.

In der dritten Variante werden Entry-Objekte nur auf Bedarf dynamisch angelegt (Einfügeoperation) oder dynamisch freigegeben (Löschoperation). Die Zeiger (Adressen) der vorhandenen Entry-Objekte werden in einem separaten Zeigerarray fester Länge abgelegt, siehe Abbildung 3:

Instanzdatenbereich eines TelephoneBook-Objekts mit einem Array fester Länge bestehend aus Adressen dynamisch allokierter Entry-Objekte.

Abbildung 3. Instanzdatenbereich eines TelephoneBook-Objekts mit einem Array fester Länge bestehend aus Adressen dynamisch allokierter Entry-Objekte.


Der Deklarationsabschnitt für die Instanzvariable m_entries sieht so aus:

const int MAX_ENTRIES = 20;

class TelephoneBook
{
private:
    Entry* m_entries[MAX_ENTRIES];
    ...
};

Beachten Sie in Abbildung 3, dass das Feld mit den Entry-Zeigern zahlreiche NULL-Einträge besitzt. Hier lassen sich bei Bedarf noch weitere Entry-Objekte (mit ihrer Adresse) hinzufügen.

Vor- und Nachteile:

  • Vorteil: Es sind nur so viele Entry-Objekte im Telefonbuch vorhanden, wie es auch Einträge gibt.

  • Vorteil: Bei Einfüge- oder Löschoperationen werden nur Zeiger umkopiert. Es finden keine zeitaufwändigen Kopieroperationen von ganzen Entry-Objekten statt!

  • Nachteil: Das Zeigerarray mit den Adressen dynamisch angelegter Entry-Objekte besitzt eine feste Länge. Damit ist der Umfang des Telefonbuch nach oben begrenzt und kann zur Laufzeit nicht verändert werden.

2.1.4. Variante 4: Variable Anzahl von Zeigern auf Entry-Objekte.

In der vierten und letzten Variante werden – wie in der dritten Variante – Entry-Objekte nur auf Bedarf dynamisch angelegt (Einfügeoperation) oder dynamisch freigegeben (Löschoperation). Auch wird das zusätzliche Array mit den Adressen aller allokierten Entry-Objekte dynamisch angelegt (Abbildung 4):

Instanzdatenbereich eines TelephoneBook-Objekts mit einem dynamisch langen Array bestehend aus Adressen dynamisch allokierter Entry-Objekte.

Abbildung 4. Instanzdatenbereich eines TelephoneBook-Objekts mit einem dynamisch langen Array bestehend aus Adressen dynamisch allokierter Entry-Objekte.


In der letzten Variante finden wir die bestmögliche Realisierung eines Telefonbuchs vor: Es kann beliebig viele Einträge aufnehmen und im Speicher liegen keine überflüssigen Entry-Objekte. Alle notwendigen Entry-Objekte werden zur Laufzeit dynamisch mit dem new-Operator angelegt. Die Adressen dieser Entry-Objekte werden wiederum in einem Array abgelegt, das ebenfalls dynamisch mit new angelegt wird, um auf diese Weise eine beliebige Anzahl von Entry-Objekten aufnehmen zu können.

Beachten Sie in Abbildung 4: Die Länge des Felds mit den Adressen der Entry-Objekte ist exakt auf die Anzahl der vorhandenen Entry-Objekte abgestimmt.

Vor- und Nachteile:

  • Vorteil: Es sind nur so viele Entry-Objekte im Telefonbuch vorhanden, wie es auch Einträge gibt.

  • Vorteil: Es können prinzipiell beliebig viele Entry-Objekte im Telefonbuch abgelegt werden, da auch das Verwaltungsarray mit den Adressen aller Einträge flexibel lang angelegt wird.

  • Nachteil: Der Zugriff auf ein einzelnes Entry-Objekte über Zeiger dauert im Vergleich zu einem Array mit Entry-Objekten geringfügig länger, da das Objekt nur über zwei Zeiger erreichbar ist.

Wir verweilen noch etwas bei der vierten Variante, da wir es hier mit einer Besonderheit der Programmiersprache C/C++ zu tun haben: In dieser Variante haben wir es beim Datentyp des dynamisch langen Arrays mit einem „Zeiger, der auf einen Zeiger, der auf Entry-Objekte verweist“ zu tun:

class TelephoneBook
{
private:
    Entry** m_entries;
    ...
};

Zur besseren Veranschaulichung, wie sich die Datenstrukturen eines TelephoneBook-Objekts in dieser Variante zur Laufzeit verändern können, betrachten wir das folgende Codefragment:

TelephoneBook book;
book.Insert ("Franz", 123);
book.Insert ("Peter", 456);
book.Insert ("Susan", 789);
book.Remove ("Peter");

Das Anwachsen und Schrumpfen des dynamisch angelegten Entry**-Arrays sowie die Anzahl der im Speicher vorhandenen Entry-Objekte dieses Codefragments ist im zeitlichen Verlauf in Abbildung 5 dargestellt:

Arbeitsweise der Insert- und Remove-Methode im zeitlichen Ablauf.

Abbildung 5. Arbeitsweise der Insert- und Remove-Methode im zeitlichen Ablauf.

2.2. Implementierung

Für alle vier Varianten greifen wir auf eine identische Implementierung der Klasse Entry zurück, ihren Quellcode finden Sie in Listing 1 und Listing 2 vor.

01: class Entry
02: {
03: // output operator
04: friend ostream& operator<< (ostream&, const Entry&);
05: 
06: private:
07:     char* m_name;
08:     int m_number;
09: 
10: public:
11:     // c'tors / d'tor
12:     Entry ();
13:     Entry (char*, int);
14:     Entry (const Entry&);
15:     ~Entry ();
16: 
17:     // getter
18:     void GetName (char name[], int len) const;
19:     int GetNumber () const;
20: 
21:     // public interface
22:     bool IsEmpty () const;
23:     bool IsEqual (char*) const;
24:     void Clear ();
25: 
26:     // assignment operator
27:     Entry& operator= (const Entry&);
28: };

Beispiel 1. Klasse Entry: Schnittstelle.


01: #include <iostream>
02: using namespace std;
03: 
04: #include "Entry.h"
05: 
06: // c'tors / d'tor
07: Entry::Entry () : m_name ((char*) 0), m_number (-1)
08: {
09: }
10: 
11: Entry::Entry (char* name, int number)
12: {
13:     int len = strlen(name);
14:     m_name = new char[len + 1];
15:     strcpy_s (m_name, len + 1, name);
16:     m_number = number;
17: }
18: 
19: Entry::Entry (const Entry& entry) : m_name ((char*) 0), m_number (-1)
20: {
21:     if (entry.m_name != (char*) 0)
22:     {
23:         int len = strlen(entry.m_name);
24:         m_name = new char[len + 1];
25:         strcpy_s (m_name, len + 1, entry.m_name);
26:         m_number = entry.m_number;
27:     }
28: }
29: 
30: Entry::~Entry ()
31: {
32:     delete[] m_name;
33: }
34: 
35: bool Entry::IsEmpty () const
36: {
37:     return m_number == -1;
38: }
39: 
40: bool Entry::IsEqual (char* name) const
41: {
42:     return strcmp(m_name, name) == 0;
43: }
44: 
45: // getter
46: int Entry::GetNumber () const
47: {
48:     return m_number;
49: }
50: 
51: void Entry::GetName (char name[], int len) const
52: {
53:     if (m_name != (char*) 0)
54:         strcpy_s(name, len, m_name);
55: }
56: 
57: Entry& Entry::operator= (const Entry& e)
58: {
59:     if (this != &e)
60:     {
61:         delete[] m_name;
62: 
63:         if (e.m_name == (char*) 0)
64:         {
65:             m_name = (char*) 0;
66:             m_number = -1;
67:         }
68:         else
69:         {
70:             int len = strlen(e.m_name);
71:             m_name = new char[len + 1];
72:             strcpy_s (m_name, len + 1, e.m_name);
73:             m_number = e.m_number;
74:         }
75:     }
76: 
77:     return *this;
78: }
79: 
80: void Entry::Clear ()
81: {
82:     delete[] m_name;
83:     m_name = (char*) 0;
84:     m_number = -1;
85: }
86: 
87: ostream& operator<< (ostream& os, const Entry& e)
88: {
89:     os << "Name: " << e.m_name << ", Phone Number: " << e.m_number;
90:     return os;
91: }

Beispiel 2. Klasse Entry: Realisierung.


Nun folgen gemäß unseren Vorüberlegungen vier ähnliche, aber eben nicht identische Implementierungen der Klasse TelephoneBook. Die öffentliche Schnittstelle der TelephoneBook-Klasse ist dabei immer dieselbe, die privaten Instanzvariablendeklarationen unterscheiden sich natürlich von Variante zu Variante. Aus diesem Grund finden Sie beim Lösungsvorschlag zur ersten Variante die komplette TelephoneBook-Schnittstelle vor, bei den weiteren Varianten genügt es, den privaten Deklarationsteil aufzulisten. Damit geht es auch schon los:

2.2.1. Lösungsvorschlag zu Variante 1
01: const int MAX_ENTRIES = 20;
02:     
03: class TelephoneBook
04: {
05: private:
06:     Entry m_entries[MAX_ENTRIES];
07:     int m_last;
08: 
09: public:
10:     // c'tors, d'tor
11:     TelephoneBook ();
12:     TelephoneBook (const TelephoneBook&);
13:     ~TelephoneBook ();
14: 
15:     // getter
16:     int Count () const;
17: 
18:     // public interface
19:     bool Contains (char*) const;
20:     bool Insert (char*, int);
21:     bool Remove (char*);
22:     bool Get (char[], int*) const;
23:     void Import (const TelephoneBook&);
24: 
25:     // assignment operator
26:     TelephoneBook& operator= (const TelephoneBook&);
27: 
28:     // comparison operators
29:     friend bool operator== (const TelephoneBook&, const TelephoneBook&);
30:     friend bool operator!= (const TelephoneBook&, const TelephoneBook&);
31: 
32:     // enumeration interface
33:     bool HasMoreEntries ();
34:     void GetNextEntry (char name[] , int len, int* number) const;
35: 
36:     // output operator
37:     friend ostream& operator<< (ostream&, const TelephoneBook&);
38: 
39: private:
40:     // private helper method
41:     bool Insert (const Entry&);
42: };

Beispiel 3. Klasse TelephoneBook: Schnittstelle, Variante 1.


001: #include <iostream>
002: using namespace std;
003: #include "Entry.h"
004: #include "TelephoneBook.h"
005: 
006: // c'tors and d'tor
007: TelephoneBook::TelephoneBook () : m_last(-1) {}
008: TelephoneBook::~TelephoneBook () {}
009: 
010: TelephoneBook::TelephoneBook (const TelephoneBook& tb)
011: {
012:     // copy entry objects
013:     for (int i = 0; i < MAX_ENTRIES; i ++)
014:         m_entries[i] = tb.m_entries[i];
015: 
016:     // reset enumeration index
017:     m_last = -1;
018: }
019: 
020: // getter
021: int TelephoneBook::Count () const
022: {
023:     int count = 0;
024:     for (int i = 0; i < MAX_ENTRIES; i ++)
025:         if (! m_entries[i].IsEmpty())
026:             count ++;
027: 
028:     return count;
029: }
030: 
031: // public methods
032: bool TelephoneBook::Contains (char* name) const
033: {
034:     for (int i = 0; i < MAX_ENTRIES; i ++)
035:     {
036:         if (! m_entries[i].IsEmpty())
037:         {
038:             if (m_entries[i].IsEqual(name))
039:             {
040:                 return true;
041:             }
042:         }
043:     }
044: 
045:     return false;
046: }
047: 
048: bool TelephoneBook::Insert (char* name, int number)
049: {
050:     // is name already in telephone book
051:     if (Contains (name))
052:         return false;
053: 
054:     // search an empty instance in the array of entries
055:     int i = 0;
056:     while (i < MAX_ENTRIES && !m_entries[i].IsEmpty())
057:         i ++;
058:     if (i == MAX_ENTRIES)
059:         return false;
060: 
061:     m_entries[i] = Entry (name, number);
062:     return true;
063: }
064: 
065: bool TelephoneBook::Remove (char* name)
066: {
067:     for (int i = 0; i < MAX_ENTRIES; i ++)
068:     {
069:         if (! m_entries[i].IsEmpty())
070:         {
071:             if (m_entries[i].IsEqual(name))
072:             {
073:                 m_entries[i].Clear();
074:                 return true;
075:             }
076:         }
077:     }
078:     return false;
079: }
080: 
081: bool TelephoneBook::Get (char name[], int* number) const
082: {
083:     for (int i = 0; i < MAX_ENTRIES; i ++)
084:     {
085:         if (! m_entries[i].IsEmpty())
086:         {
087:             if (m_entries[i].IsEqual(name))
088:             {
089:                 *number = m_entries[i].GetNumber ();
090:                 return true;
091:             }
092:         }
093:     }
094: 
095:     return false;
096: }
097: 
098: void TelephoneBook::Import (const TelephoneBook& tb)
099: {
100:     for (int i = 0; i < MAX_ENTRIES; i ++)
101:     {
102:         if (! tb.m_entries[i].IsEmpty())
103:         {
104:             Insert (tb.m_entries[i]);
105:         }
106:     }
107: }
108: 
109: // assignment operator
110: TelephoneBook& TelephoneBook::operator= (const TelephoneBook& tb)
111: {
112:     if (this == &tb)
113:         return *this;
114: 
115:     // copy entry objects
116:     for (int i = 0; i < MAX_ENTRIES; i ++)
117:         m_entries[i] = tb.m_entries[i];
118: 
119:     // reset enumeration index
120:     m_last = -1;
121: 
122:     return *this;
123: }
124: 
125: // comparison operators
126: bool operator== (const TelephoneBook& tb1, const TelephoneBook& tb2)
127: {
128:     // compare sizes
129:     if (tb1.Count () != tb2.Count ())
130:         return false;
131: 
132:     for (int i = 0; i < MAX_ENTRIES; i ++)
133:     {
134:         if (tb1.m_entries[i].IsEmpty())
135:             continue;
136: 
137:         char name[32];
138:         tb1.m_entries[i].GetName (name, 32);
139: 
140:         if (tb2.Contains (name))
141:             continue;
142: 
143:         return false;
144:     }
145: 
146:     return true;
147: }
148: 
149: bool operator!= (const TelephoneBook& tb1, const TelephoneBook& tb2)
150: {
151:     return ! (tb1 == tb2);
152: }
153: 
154: // enumeration interface
155: bool TelephoneBook::HasMoreEntries ()
156: {
157:     // skip to next entry - if any
158:     do {
159:        m_last ++;
160:     }
161:     while (m_last < MAX_ENTRIES && m_entries[m_last].IsEmpty()); 
162: 
163:     if (m_last < MAX_ENTRIES)
164:     {
165:         return true;
166:     }
167:     else
168:     {
169:         // enumeration has finished, prepare for next enumeration
170:         m_last = -1;
171:         return false;
172:     }
173: }
174: 
175: void TelephoneBook::GetNextEntry (char name[], int len, int* number) const
176: {
177:     // copy entry to caller
178:     m_entries[m_last].GetName (name, len);
179:     *number = m_entries[m_last].GetNumber ();
180: }
181: 
182: // private helper method
183: bool TelephoneBook::Insert (const Entry& entry)
184: {
185:     char name [32];
186:     entry.GetName (name, 32);
187:     return Insert (name, entry.GetNumber());
188: }
189: 
190: // output
191: ostream& operator<< (ostream& os, const TelephoneBook& tb)
192: {
193:     os << "Contents of TelephoneBook:" << endl;
194: 
195:     for (int i = 0; i < MAX_ENTRIES; i ++)
196:     {
197:         if (! tb.m_entries[i].IsEmpty())
198:         {
199:             os << tb.m_entries[i] << endl;
200:         }
201:     }
202:     os << "[" << tb.Count() << " entries]" << endl;
203: 
204:     return os;
205: }

Beispiel 4. Klasse TelephoneBook: Implementierung, Variante 1.

2.2.2. Lösungsvorschlag zu Variante 2
01: class TelephoneBook
02: {
03: private:
04:     Entry* m_entries;
05:     int m_count;
06:     int m_last;
07: 
08: public:
09:     ...
10: };

Beispiel 5. Klasse TelephoneBook: Schnittstelle, Variante 2 (in Ausschnitten).


001: #include <iostream>
002: using namespace std;
003: #include "Entry.h"
004: #include "TelephoneBook.h"
005: 
006: // c'tors and d'tor
007: TelephoneBook::TelephoneBook () : m_last(-1)
008: {
009:     m_count = 0;
010:     m_entries = (Entry*) 0;
011: }
012: 
013: TelephoneBook::TelephoneBook (const TelephoneBook& tb)
014: {
015:     // copy entry objects
016:     m_count = tb.m_count;
017:     m_entries = new Entry[m_count];
018: 
019:     for (int i = 0; i < m_count; i ++)
020:     {
021:         char name [32];
022:         tb.m_entries[i].GetName (name, 32);
023:         int number = tb.m_entries[i].GetNumber();
024:         m_entries[i] = Entry (name, number);
025:     }
026: 
027:     // reset enumeration index
028:     m_last = -1;
029: }
030: 
031: TelephoneBook::~TelephoneBook ()
032: {
033:     delete[] m_entries;
034: }
035: 
036: // getter
037: int TelephoneBook::Count () const
038: {
039:     return m_count;
040: }
041: 
042: // public methods
043: bool TelephoneBook::Contains (char* name) const
044: {
045:     for (int i = 0; i < m_count; i ++)
046:     {
047:         if (m_entries[i].IsEqual(name))
048:         {
049:             return true;
050:         }
051:     }
052: 
053:     return false;
054: }
055: 
056: bool TelephoneBook::Insert (char* name, int number)
057: {
058:     // is name already in telephone book
059:     if (Contains (name))
060:         return false;
061: 
062:     // increase array
063:     Entry* entries = new Entry [m_count + 1];
064:     for (int i = 0; i < m_count; i ++)
065:         entries[i] = m_entries[i];
066: 
067:     // add new entry
068:     entries[m_count] = Entry (name, number);
069: 
070:     // switch array of entries
071:     delete[] m_entries;
072:     m_entries = entries;
073:     m_count ++;
074: 
075:     return true;
076: }
077: 
078: bool TelephoneBook::Remove (char* name)
079: {
080:     for (int i = 0; i < m_count; i ++)
081:     {
082:         if (m_entries[i].IsEqual(name))
083:         {
084:             // decrease array
085:             Entry* entries = new Entry [m_count-1];
086:             for (int k = 0; k < i; k ++)
087:                 entries[k] = m_entries[k];
088:             for (int k = i + 1; k < m_count; k ++)
089:                 entries[k - 1] = m_entries[k];
090: 
091:             // switch array of entries
092:             delete[] m_entries;
093:             m_entries = entries;
094:             m_count --;
095: 
096:             return true;
097:         }
098:     }
099: 
100:     return false;
101: }
102: 
103: bool TelephoneBook::Get (char name[], int* number) const
104: {
105:     for (int i = 0; i < m_count; i ++)
106:     {
107:         if (m_entries[i].IsEqual(name))
108:         {
109:             *number = m_entries[i].GetNumber ();
110:             return true;
111:         }
112:     }
113: 
114:     return false;
115: }
116: 
117: void TelephoneBook::Import (const TelephoneBook& tb)
118: {
119:     for (int i = 0; i < tb.m_count; i ++)
120:         Insert (tb.m_entries[i]);
121: }
122: 
123: // assignment operator
124: TelephoneBook& TelephoneBook::operator= (const TelephoneBook& tb)
125: {
126:     if (this == &tb)
127:         return *this;
128: 
129:     // delete left side
130:     delete[] m_entries;
131: 
132:     // copy entry objects
133:     m_count = tb.m_count;
134:     m_entries = new Entry[m_count];
135:     for (int i = 0; i < m_count; i ++)
136:         m_entries[i] = tb.m_entries[i];
137: 
138:     // reset enumeration index
139:     m_last = -1;
140: 
141:     return *this;
142: }
143: 
144: // comparison operators
145: bool operator== (const TelephoneBook& tb1, const TelephoneBook& tb2)
146: {
147:     // compare sizes
148:     if (tb1.Count () != tb2.Count ())
149:         return false;
150: 
151:     for (int i = 0; i < tb1.m_count; i ++)
152:     {
153:         char name[32];
154:         tb1.m_entries[i].GetName (name, 32);
155: 
156:         if (tb2.Contains (name))
157:             continue;
158: 
159:         return false;
160:     }
161: 
162:     return true;
163: }
164: 
165: bool operator!= (const TelephoneBook& tb1, const TelephoneBook& tb2)
166: {
167:     return ! (tb1 == tb2);
168: }
169: 
170: // enumeration interface
171: bool TelephoneBook::HasMoreEntries ()
172: {
173:     // skip to next entry - if any
174:     m_last ++;
175:     if (m_last < m_count)
176:     {
177:         return true;
178:     }
179:     else
180:     {
181:         // enumeration has finished, prepare for next enumeration
182:         m_last = -1;
183:         return false;
184:     }
185: }
186: 
187: void TelephoneBook::GetNextEntry (char name[], int len, int* number) const
188: {
189:     // copy entry to caller
190:     m_entries[m_last].GetName (name, len);
191:     *number = m_entries[m_last].GetNumber ();
192: }
193: 
194: // private helper method
195: bool TelephoneBook::Insert (const Entry& entry)
196: {
197:     char name [32];
198:     entry.GetName (name, 32);
199:     return Insert (name, entry.GetNumber());
200: }
201: 
202: // output
203: ostream& operator<< (ostream& os, const TelephoneBook& tb)
204: {
205:     os << "Contents of TelephoneBook:" << endl;
206: 
207:     for (int i = 0; i < tb.m_count; i ++)
208:     {
209:         os << tb.m_entries[i] << endl;
210:     }
211:     os << "[" << tb.Count() << " entries]" << endl;
212: 
213:     return os;
214: }

Beispiel 6. Klasse TelephoneBook: Implementierung, Variante 2.

2.2.3. Lösungsvorschlag zu Variante 3
01: const int MAX_ENTRIES = 20;
02:     
03: class TelephoneBook
04: {
05: private:
06:     Entry* m_entries[MAX_ENTRIES];
07:     int m_last;
08: 
09: public:
10:     ...
11: };

Beispiel 7. Klasse TelephoneBook: Schnittstelle, Variante 3 (in Ausschnitten).


001: #include <iostream>
002: using namespace std;
003: #include "Entry.h"
004: #include "TelephoneBook.h"
005: 
006: // c'tors and d'tor
007: TelephoneBook::TelephoneBook () : m_last(-1)
008: {
009:     for (int i = 0; i < MAX_ENTRIES; i ++)
010:         m_entries[i] = (Entry*) 0;
011: }
012: 
013: TelephoneBook::TelephoneBook (const TelephoneBook& tb)
014: {
015:     // copy entry objects
016:     for (int i = 0; i < MAX_ENTRIES; i ++)
017:     {
018:         m_entries[i] = (Entry*) 0;
019:         if (tb.m_entries[i] != (Entry*) 0)
020:             m_entries[i] = new Entry (*tb.m_entries[i]);
021:     }
022: 
023:     // reset enumeration index
024:     m_last = -1;
025: }
026: 
027: TelephoneBook::~TelephoneBook ()
028: {
029:     for (int i = 0; i < MAX_ENTRIES; i ++)
030:         delete m_entries[i];
031: }
032: 
033: // getter
034: int TelephoneBook::Count () const
035: {
036:     int count = 0;
037:     for (int i = 0; i < MAX_ENTRIES; i ++)
038:         if (m_entries[i] != (Entry*) 0)
039:             count ++;
040: 
041:     return count;
042: }
043: 
044: // public methods
045: bool TelephoneBook::Contains (char* name) const
046: {
047:     for (int i = 0; i < MAX_ENTRIES; i ++)
048:     {
049:         if (m_entries[i] != (Entry*) 0)
050:         {
051:             if (m_entries[i] -> IsEqual(name))
052:             {
053:                 return true;
054:             }
055:         }
056:     }
057: 
058:     return false;
059: }
060: 
061: bool TelephoneBook::Insert (char* name, int number)
062: {
063:     // is name already in telephone book
064:     if (Contains (name))
065:         return false;
066: 
067:     // search an empty instance in the array of entries
068:     int i = 0;
069:     while (i < MAX_ENTRIES && m_entries[i] != (Entry*) 0)
070:         i ++;
071:     if (i == MAX_ENTRIES)
072:         return false;
073: 
074:     m_entries[i] = new Entry (name, number);
075: 
076:     return true;
077: }
078: 
079: bool TelephoneBook::Remove (char* name)
080: {
081:     for (int i = 0; i < MAX_ENTRIES; i ++)
082:     {
083:         if (m_entries[i] != (Entry*) 0)
084:         {
085:             if (m_entries[i] -> IsEqual(name))
086:             {
087:                 delete m_entries[i];
088:                 m_entries[i] = (Entry*) 0;
089:                 return true;
090:             }
091:         }
092:     }
093: 
094:     return false;
095: }
096: 
097: bool TelephoneBook::Get (char name[], int* number) const
098: {
099:     for (int i = 0; i < MAX_ENTRIES; i ++)
100:     {
101:         if (m_entries[i] != (Entry*) 0)
102:         {
103:             if (m_entries[i] -> IsEqual(name))
104:             {
105:                 *number = m_entries[i] -> GetNumber ();
106:                 return true;
107:             }
108:         }
109:     }
110: 
111:     return false;
112: }
113: 
114: void TelephoneBook::Import (const TelephoneBook& tb)
115: {
116:     for (int i = 0; i < MAX_ENTRIES; i ++)
117:     {
118:         if (tb.m_entries[i] != (Entry*) 0)
119:         {
120:             Insert (*tb.m_entries[i]);
121:         }
122:     }
123: }
124: 
125: // assignment operator
126: TelephoneBook& TelephoneBook::operator= (const TelephoneBook& tb)
127: {
128:     if (this == &tb)
129:         return *this;
130: 
131:     // delete left side
132:     for (int i = 0; i < MAX_ENTRIES; i ++)
133:         delete m_entries[i];
134: 
135:     // copy entry objects
136:     for (int i = 0; i < MAX_ENTRIES; i ++)
137:     {
138:         m_entries[i] = (Entry*) 0;
139:         if (tb.m_entries[i] != (Entry*) 0)
140:             m_entries[i] = new Entry (*tb.m_entries[i]);
141:     }
142: 
143:     // reset enumeration index
144:     m_last = -1;
145: 
146:     return *this;
147: }
148: 
149: // comparison operators
150: bool operator== (const TelephoneBook& tb1, const TelephoneBook& tb2)
151: {
152:     // compare sizes
153:     if (tb1.Count () != tb2.Count ())
154:         return false;
155: 
156:     for (int i = 0; i < MAX_ENTRIES; i ++)
157:     {
158:         if (tb1.m_entries[i] != (Entry*) 0)
159:         {
160:             char name[32];
161:             tb1.m_entries[i] -> GetName (name, 32);
162: 
163:             if (tb2.Contains (name))
164:                 continue;
165: 
166:             return false;
167:         }
168:     }
169: 
170:     return true;
171: }
172: 
173: bool operator!= (const TelephoneBook& tb1, const TelephoneBook& tb2)
174: {
175:     return ! (tb1 == tb2);
176: }
177: 
178: // enumeration interface
179: bool TelephoneBook::HasMoreEntries ()
180: {
181:     // skip to next entry - if any
182:     do {
183:        m_last ++;
184:     }
185:     while (m_last < MAX_ENTRIES && m_entries[m_last] == (Entry*) 0); 
186: 
187:     if (m_last < MAX_ENTRIES)
188:     {
189:         return true;
190:     }
191:     else
192:     {
193:         // enumeration has finished, prepare for next enumeration
194:         m_last = -1;
195:         return false;
196:     }
197: }
198: 
199: void TelephoneBook::GetNextEntry (char name[], int len, int* number) const
200: {
201:     // copy entry to caller
202:     m_entries[m_last] -> GetName (name, len);
203:     *number = m_entries[m_last] -> GetNumber ();
204: }
205: 
206: // private helper method
207: bool TelephoneBook::Insert (const Entry& entry)
208: {
209:     char name [32];
210:     entry.GetName (name, 32);
211:     return Insert (name, entry.GetNumber());
212: }
213: 
214: // output
215: ostream& operator<< (ostream& os, const TelephoneBook& tb)
216: {
217:     os << "Contents of TelephoneBook:" << endl;
218: 
219:     for (int i = 0; i < MAX_ENTRIES; i ++)
220:     {
221:         if (tb.m_entries[i] != (Entry*) 0)
222:         {
223:             os << *tb.m_entries[i] << endl;
224:         }
225:     }
226:     os << "[" << tb.Count() << " entries]" << endl;
227: 
228:     return os;
229: }

Beispiel 8. Klasse TelephoneBook: Implementierung, Variante 3.

2.2.4. Lösungsvorschlag zu Variante 4
01: class TelephoneBook
02: {
03: private:
04:     Entry** m_entries;
05:     int m_count;
06:     int m_last;
07: 
08: public:
09:     ...
10: };

Beispiel 9. Klasse TelephoneBook: Schnittstelle, Variante 4 (in Ausschnitten).


001: #include <iostream>
002: using namespace std;
003: #include "Entry.h"
004: #include "TelephoneBook.h"
005: 
006: // c'tors and d'tor
007: TelephoneBook::TelephoneBook () : m_last(-1)
008: {
009:     m_count = 0;
010:     m_entries = (Entry**) 0;
011: }
012: 
013: TelephoneBook::TelephoneBook (const TelephoneBook& tb)
014: {
015:     // copy entry objects
016:     m_count = tb.m_count;
017:     m_entries = new Entry* [m_count];
018:     for (int i = 0; i < tb.m_count; i ++)
019:         m_entries[i] = new Entry (*tb.m_entries[i]);
020: 
021:     // reset enumeration index
022:     m_last = -1;
023: }
024: 
025: TelephoneBook::~TelephoneBook ()
026: {
027:     for (int i = 0; i < m_count; i ++)
028:         delete m_entries[i];
029:     delete[] m_entries;
030: }
031: 
032: // getter
033: int TelephoneBook::Count () const
034: {
035:     return m_count;
036: }
037: 
038: // public methods
039: bool TelephoneBook::Contains (char* name) const
040: {
041:     for (int i = 0; i < m_count; i ++)
042:     {
043:         if (m_entries[i] -> IsEqual(name))
044:         {
045:             return true;
046:         }
047:     }
048: 
049:     return false;
050: }
051: 
052: bool TelephoneBook::Insert (char* name, int number)
053: {
054:     // is name already in telephone book
055:     if (Contains (name))
056:         return false;
057: 
058:     // increase array
059:     Entry** entries = new Entry* [m_count+1];
060:     for (int i = 0; i < m_count; i ++)
061:         entries[i] = m_entries[i];
062: 
063:     // add new entry
064:     entries[m_count] = new Entry (name, number);
065: 
066:     // switch buffer
067:     delete[] m_entries;
068:     m_entries = entries;
069:     m_count ++;
070: 
071:     return true;
072: }
073: 
074: bool TelephoneBook::Remove (char* name)
075: {
076:     for (int i = 0; i < m_count; i ++)
077:     {
078:         if (m_entries[i] -> IsEqual(name))
079:         {
080:             // delete entry
081:             delete m_entries[i];
082: 
083:             // decrease array
084:             Entry** entries = new Entry* [m_count-1];
085:             for (int k = 0; k < i; k ++)
086:                 entries[k] = m_entries[k];
087:             for (int k = i + 1; k < m_count; k ++)
088:                 entries[k - 1] = m_entries[k];
089: 
090:             // switch buffer
091:             delete[] m_entries;
092:             m_entries = entries;
093:             m_count --;
094: 
095:             return true;
096:         }
097:     }
098: 
099:     return false;
100: }
101: 
102: bool TelephoneBook::Get (char name[], int* number) const
103: {
104:     for (int i = 0; i < m_count; i ++)
105:     {
106:         if (m_entries[i] -> IsEqual(name))
107:         {
108:             *number = m_entries[i] -> GetNumber ();
109:             return true;
110:         }
111:     }
112: 
113:     return false;
114: }
115: 
116: void TelephoneBook::Import (const TelephoneBook& tb)
117: {
118:     for (int i = 0; i < tb.m_count; i ++)
119:         Insert (*tb.m_entries[i]);
120: 
121: }
122: 
123: // assignment operator
124: TelephoneBook& TelephoneBook::operator= (const TelephoneBook& tb)
125: {
126:     if (this == &tb)
127:         return *this;
128: 
129:     // delete left side
130:     for (int i = 0; i < m_count; i ++)
131:         delete m_entries[i];
132:     delete[] m_entries;
133: 
134:     // copy entry objects
135:     m_count = tb.m_count;
136:     m_entries = new Entry* [m_count];
137:     for (int i = 0; i < m_count; i ++)
138:         m_entries[i] = new Entry (*tb.m_entries[i]);
139: 
140:     // reset enumeration index
141:     m_last = -1;
142: 
143:     return *this;
144: }
145: 
146: // comparison operators
147: bool operator== (const TelephoneBook& tb1, const TelephoneBook& tb2)
148: {
149:     // compare sizes
150:     if (tb1.Count () != tb2.Count ())
151:         return false;
152: 
153:     for (int i = 0; i < tb1.m_count; i ++)
154:     {
155:         char name[32];
156:         tb1.m_entries[i] -> GetName (name, 32);
157: 
158:         if (tb2.Contains (name))
159:             continue;
160: 
161:         return false;
162:     }
163: 
164:     return true;
165: }
166: 
167: bool operator!= (const TelephoneBook& tb1, const TelephoneBook& tb2)
168: {
169:     return ! (tb1 == tb2);
170: }
171: 
172: // enumeration interface
173: bool TelephoneBook::HasMoreEntries ()
174: {
175:     // skip to next entry - if any
176:     m_last ++;
177:     if (m_last < m_count)
178:     {
179:         return true;
180:     }
181:     else
182:     {
183:         // enumeration has finished, prepare for next enumeration
184:         m_last = -1;
185:         return false;
186:     }
187: }
188: 
189: void TelephoneBook::GetNextEntry (char name[], int len, int* number) const
190: {
191:     // copy entry to caller
192:     m_entries[m_last] -> GetName (name, len);
193:     *number = m_entries[m_last] -> GetNumber ();
194: }
195: 
196: // private helper method
197: bool TelephoneBook::Insert (const Entry& entry)
198: {
199:     char name [32];
200:     entry.GetName (name, 32);
201:     return Insert (name, entry.GetNumber());
202: }
203: 
204: // output
205: ostream& operator<< (ostream& os, const TelephoneBook& tb)
206: {
207:     os << "Contents of TelephoneBook:" << endl;
208: 
209:     for (int i = 0; i < tb.m_count; i ++)
210:     {
211:         os << *tb.m_entries[i] << endl;
212:     }
213:     os << "[" << tb.Count() << " entries]" << endl;
214: 
215:     return os;
216: }

Beispiel 10. Klasse TelephoneBook: Implementierung, Variante 4.

2.2.5. Einheitlicher Testrahmen für alle Realisierungsvarianten

Beispiel:

#include <iostream>
using namespace std;
#include "Entry.h"
#include "TelephoneBook.h"

void
TestingInsertRemove ()
{
    TelephoneBook book;

    book.Insert ("Franz", 123);
    book.Insert ("Peter", 456);
    book.Insert ("Susan", 789);
    cout << book << endl;

    book.Remove ("Peter");
    cout << book << endl << endl;
}

void
TestingGet ()
{
    TelephoneBook book;
    book.Insert ("Franz", 123);
    book.Insert ("Peter", 456);
    book.Insert ("Susan", 789);

    int number;
    bool bFound = book.Get ("Susan", &number);
    if (bFound)
        cout << "Number of Susan: " << number << endl;

    bFound = book.Get ("Alex", &number);
    if (! bFound)
        cout << "Alex: Not found!" << endl;
    cout << endl;
}

void
TestingAssignmentOperatorCopyCtor ()
{
    TelephoneBook book;
    book.Insert ("Franz", 123);
    book.Insert ("Peter", 456);
    book.Insert ("Susan", 789);
    cout << book << endl;

    TelephoneBook book1 = book;
    cout << book1 << endl;

    TelephoneBook book2;
    book2 = book;
    cout << book2 << endl;
    cout << endl;
}

void
TestingEnumeration ()
{
    TelephoneBook book;
    book.Insert ("Franz", 123);
    book.Insert ("Peter", 456);
    book.Insert ("Susan", 789);

    // testing enumeration
    cout << "Enumeration:" << endl;
    while (book.HasMoreEntries ())
    {
        char szName[32];
        int number;
        book.GetNextEntry (szName, 32, &number);
        cout << "  Found " << szName << ", number is " << number << endl;
    }
    cout << endl;
}

void
TestingComparison ()
{
    TelephoneBook book1;
    book1.Insert ("Franz", 123);
    book1.Insert ("Peter", 456);
    book1.Insert ("Susan", 789);

    TelephoneBook book2 = book1;
    cout << "Book1 == Book2: " << (book1 == book2) << endl;
    book2.Remove ("Peter");
    cout << "Book1 == Book2: " << (book1 == book2) << endl;
    book2.Insert ("Peter", 456);
    cout << "Book1 == Book2: " << (book1 == book2) << endl;
    cout << endl;
}

void
TestingImport ()
{
    // testing 'import'
    TelephoneBook book1;
    book1.Insert ("Helmut", 100);
    book1.Insert ("Manfred", 101);
    book1.Insert ("Peter", 102);
    book1.Insert ("Franz", 103);
    cout << "Book1: " << endl << book1 << endl;

    TelephoneBook book2;
    book2.Insert ("Susan", 110);
    book2.Insert ("Manfred", 111);
    cout << "Book2: " << endl << book2 << endl;

    book1.Import (book2);
    cout << "Phonebook after import: " << endl << book1 << endl;
    cout << endl;
}

void
main ()
{
    TestingInsertRemove ();
    TestingGet ();
    TestingAssignmentOperatorCopyCtor ();
    TestingEnumeration ();
    TestingComparison ();
    TestingImport ();
}

Ausgabe:

Contents of TelephoneBook:
Name: Franz, Phone Number: 123
Name: Peter, Phone Number: 456
Name: Susan, Phone Number: 789
[3 entries]

Contents of TelephoneBook:
Name: Franz, Phone Number: 123
Name: Susan, Phone Number: 789
[2 entries]


Number of Susan: 789
Alex: Not found!

Contents of TelephoneBook:
Name: Franz, Phone Number: 123
Name: Peter, Phone Number: 456
Name: Susan, Phone Number: 789
[3 entries]

Contents of TelephoneBook:
Name: Franz, Phone Number: 123
Name: Peter, Phone Number: 456
Name: Susan, Phone Number: 789
[3 entries]

Contents of TelephoneBook:
Name: Franz, Phone Number: 123
Name: Peter, Phone Number: 456
Name: Susan, Phone Number: 789
[3 entries]


Enumeration:
  Found Franz, number is 123
  Found Peter, number is 456
  Found Susan, number is 789

Book1 == Book2: 1
Book1 == Book2: 0
Book1 == Book2: 1

Book1:
Contents of TelephoneBook:
Name: Helmut, Phone Number: 100
Name: Manfred, Phone Number: 101
Name: Peter, Phone Number: 102
Name: Franz, Phone Number: 103
[4 entries]

Book2:
Contents of TelephoneBook:
Name: Susan, Phone Number: 110
Name: Manfred, Phone Number: 111
[2 entries]

Phonebook after import:
Contents of TelephoneBook:
Name: Helmut, Phone Number: 100
Name: Manfred, Phone Number: 101
Name: Peter, Phone Number: 102
Name: Franz, Phone Number: 103
Name: Susan, Phone Number: 110
[5 entries]