Zudem sind der Anfang (root) und das Ende (end) Betrachten wir die Klassendefinition schrittweise:
der Liste stets bekannt. Mit dieser Struktur und deren
Implementation in C++ mittels einer dafür vorgese- • Entsprechend der obigen Ausführungen über
henen Klasse wollen wir uns nun näher beschäftigen. doppelt verkettete Listen zeigt data auf unseren
String, next und previous zeigen auf das Vor-
Vorüberlegungen für C++ gänger- bzw. Nachfolgerelement.
• Konstruktor: Beim Anlegen eines neuen Ob-
jekts der Klasse list_element_string mittels new
Bisher haben wir die Liste und die in ihr gespeicher- muss ein Zeiger auf die neu anzulegenden Zei-
ten Daten abstrakt betrachtet. Für die jetzt folgende chenkette - unsere Daten - übergeben werden.
Umsetzung in C++ müssen wir dies konkretisieren.
• Destruktor: Wird das Element gelöscht, muss
der reservierte Speicher freigegeben werden.
Zu Beginn müssen wir für die Daten eine geeignete
Struktur wählen. Aus didaktischen Gründen eignet
Die folgenden, von außen zugreifbaren (public) Me-
sich für eine Beispielanwendung der Datentyp String,
thoden gewähren uns Zugriff auf die klasseninter-
d.h. eine Zeichenkette. Ein String ist in C jedoch kein
nen Daten der Objekte:
elementarer Datentyp. Aber die Funktionen der C-
Laufzeitbibliothek <string.h> verstehen unter einem
• get_previous() liefert einen Zeiger auf das
String eine beliebig lange Folge von Zeichen des
nächste Element, set_previous() setzt den Zei-
Typs char, die durch den Wert 0 begrenzt wird.
ger auf das vorhergehende Element.
Dem wollen wir uns anschließen.
• get_data() liefert einen Zeiger auf den gespei-
cherten String, set_data() legt den neu überge-
Um eine Listenstruktur mit diesem Datentyp realisie-
benen String unter data im Element.
ren zu können, verwenden wir zwei Klassen, die uns
den nötigen Speicher und die Funktionen (Methoden)
Alle Methoden werden später von unserer zweiten
dynamisch zur Verfügung stellen:
Klasse list_of_string, die die Listenverwaltung über-
nimmt, aufgerufen. Der vollständige Quelltext der Me-
• Die Listenelementklasse (list_element_string)
thoden sieht wie folgt aus (mit kurzen Erläuterungen):
• Die Listenklasse (list_of_string)
list_element_string(char *string)
{
In der klassischen objektorientierten Programmierung previous = NULL;
(OOP), also auch in C++, werden Daten und die next = NULL;
darauf angewendeten Algorithmen nicht mehr von- data = (char*) malloc((strlen(string)+1));
if (data != NULL)
einander getrennt betrachtet. Dies hat zu Folge, dass strcpy(data,string);
Objekte der obigen Klassen beides in sich tragen. }
public:
void set_next(list_element_string *pointer)
{ list_of_string() // Konstruktor
next = pointer; ~list_of_string() // Destruktor
}
void add(char *string);
int erase(list_element_string *element);
set_next() verhält sich analog zu set_previous(). void erase_first();
void erase_last();
list_element_string *get_root();
void set_data(char *string) list_element_string *get_end();
{ list_element_string *get_element_by_number
if (data != NULL) (int number);
free(data); list_element_string *get_element_by_data
(char *string);
data = (char *) malloc( (strlen(string)+1) ); int get_number_by_data(char *string);
int get_size();
if (data != NULL) }
strcpy(data, string);
}
Kommen wir zur Beschreibung der Methoden:
set_data() überprüft zunächst, ob bereits Daten vor-
liegen. Diese werden dann zuvor frei gegeben. Sollte • Variablen: Jede neu angelegte Liste bedarf der
dies vergessen werden, addieren sich im Laufe der Kenntnis des ersten und des letzten Elements
Zeit immer mehr Speicherbereiche auf, die nicht (s.o.). Die Listengröße d.h. die Anzahl der Ele-
mehr zur Laufzeit freigegeben werden können. mente der Liste, ist ein häufig benötigter Wert.
char *get_data() Anstelle des langsamen Durchzählens kann
{ man daher stattdessen besser einen Zähler
return(data);
} list_size mitlaufen lassen.
• Der Konstruktor initalisiert die Liste und setzt
get_data() übergibt den lokalen Zeiger auf den Da- die Startwerte fest.
tenstring an die aufrufende Funktion / Methode. • Der Destruktor muss alle Listenelemente lö-
schen, um so den gesamten verwendeten
list_element_string *get_next()
{ Speicherplatz freizugeben.
return(next); • Die Methode add() fügt ein Element in die Liste
}
ein, dessen Daten durch den Zeiger string auf
get_next() übergibt den lokalen Zeiger auf das nächs- eine Zeichenkette gekennzeichnet ist.
te Listenelement (NULL falls nicht vorhanden). • Die Methode erase() löscht das Element, das
mittels eines Zeigers übergeben wird. Die Me-
list_element_string *get_previous()
{ thoden erase_first() und erase_last() sind ein-
return(previous); fache, davon abgeleitete Sonderfälle.
}
• get_root() liefert einen Zeiger auf den Anfang
der Liste zurück, get_end() entsprechend das
get_previous() übergibt den lokalen Zeiger auf das
Ende der Liste.
vorige Listenelement (NULL falls nicht vorhanden).
• get_element_by_number() liefert einen Zeiger
auf das durch number referenzierte Element.
Damit wäre die Klasse list_element_string abgehan-
• get_element_by_data() sucht in der Liste nach
delt. Der interessantere Teil - die Organisation der
dem übergebenen String. Ist er identisch mit
Listenelemente - folgt im nächsten Abschnitt.
dem Inhalt eines Listenelements, wird die
Nummer des Elements in der Liste übergeben.
Die Klasse list_of_string • get_size() liefert die Anzahl der Listenelemente
Die Klasse list_of_string leistet die eigentliche Arbeit. Mit Hilfe dieser Methoden lässt sich eine Liste von
Sie organisiert die Listenelemente und stellt einen Strings komfortabel verwalten. Zwei kurze Beispiele zur
Satz von Methoden zur Verfügung, die es dem An- Demonstration werden wir in den beiden folgenden
wender erlauben, komfortabel mit der Liste umgehen Abschnitten kennen lernen.
Zunächst der Quelltext der Methoden mit ergänzen- int erase(list_element_string *element)
{
den Erläuterungen (spaltenbedingte Umbrüche sind if (element == NULL)
zu entfernen): return(0); // ERROR
if (element == end) // if it is the last element
list_of_string() {
{ if (element == root)
list_size = 0; // root = end => only one element left
root = NULL; {
end = NULL; root = NULL;
} end = NULL;
Der Konstruktor initialisiert die Zeiger und Werte mit delete element;
list_size--;
NULL bzw. 0. }
else // deleting the last element
~list_of_string() {
{ end = element->get_previous();
list_element_string *walk_through_list, end->set_next(NULL);
*element_to_delete; delete element;
list_size--;
walk_through_list = root; }
}
// Deleting all elements of the list else // not the last element
{
while(walk_through_list!=NULL) if (element == root)
{ // if it is the first element
element_to_delete = walk_through_list; {
walk_through_list = root = element->get_next();
walk_through_list->get_next(); delete element;
delete element_to_delete; list_size--;
} }
Der Destruktor löscht nacheinander alle Elemente else // out of the middle part of list
der Liste. Die Hilfsvariable element_to_delete spei- {
element->get_next()->
chert den Zeiger auf das zu löschende Element zwi- set_previous(element->get_previous());
schen, da vor dem eigentlichen Löschvorgang das element->get_previous()->
set_next(element->get_next());
nachfolgende Element noch bestimmt werden muss. delete element;
list_size--;
void add(char *string) }
{ }
list_element_string *new_element; return(-1); // ERASE OK
}
if(root == NULL) // first element of list
{
root = new list_element_string(string); Ähnlich verhält es sich bei der erase()-Methode. Auch
end = root; hier muss in Abhängigkeit von der Position und der
if (root != NULL) Anzahl der vorhandenen Listenelemente unterschied-
list_size++;
} lich verfahren werden.
else
{ void erase_first()
if (end->get_previous() == NULL)
{ {
new_element = erase(root);
new list_element_string(string); }
new_element->set_previous(root);
// previous element is start Hier wird auf die bereits definierte Methode erase()
new_element->set_next(NULL);
// next element is NULL zugegriffen und der nur lokal bekannte Zeiger root
root->set_next(new_element); übergeben. Alternativ kann man außerhalb der Klasse
// end of list is the new element
end = new_element; listobject->erase(listobject->get_root()); aufrufen kön-
list_size++; nen (list_of_string *listobj = new list_of_string(string))
}
else void erase_last()
{ {
new_element = erase(end);
new list_element_string(string); }
new_element->set_previous(end);
// previous element is start
new_element->set_next(NULL); erase_last verhält sich analog zu erase_first();
// next element is NULL
end->set_next(new_element); list_element_string *get_root()
end = new_element; {
// end of list is the new element return(root);
list_size++; }
}
} Der lokale Zeiger root wird als Argument übergeben.
}
list_element_string *get_end()
Zur korrekten Zeiger-Zuweisung der Zeiger root und {
return(end);
end muss die obige Methode zum Hinzufügen von }
Elementen Fallunterscheidungen für die folgenden
Situationen aufweisen: Der lokale Zeiger end wird als Argument übergeben.
• Die Liste ist leer d.h. root = NULL list_element_string
• Nur ein Element ist in der Liste, d.h. root und *get_element_by_number(int number)
{
end sind identisch list_element_string *walk_through_list;
• Die Liste enthält zwei oder mehr Elemente int count;
count = 1; walk_through_list = walk_through_list->
walk_through_list = root; get_next_element();
}
while ((walk_through_list != NULL) && return(count);*/
(count < number)) }
{
walk_through_list = walk_through_list-> Die schnelle get_szie()-Methode liefert nur den lokalen
get_next();
count++; Zähler list_size zurück. Die gleichwertige, wohl aber
} bedeutend langsamere Methode zählt die Liste durch.
return(walk_through_list);
} Sie wird hier nur als Beispiel, wie man ganz einfach
eine Liste durchlaufen kann, aufgeführt.
Die while-Schleife wird so lange durchlaufen, bis der
Zeiger walk_through_list auf dem Element mit der Abschließend können diese Klassendefinitionen z.B.
Nummer number steht. Außerdem muss die Funktion noch in class_list_of_string.h ausgelagert werden, so
gegen zu hohe Werte von number gesichert sein. In dass man sie mit #include in einen beliebigen C++-
diesem Fall liefert die Funktion NULL. Quelltext einbinden kann.
Alternativ hätte man eine if-Abfrage mit anschließen-
der for-Schleife einsetzen können.
list_element_string
Beispiel I : Listendemo.cpp
*get_element_by_data(char *string)
{
list_element_string *walk_through_list; Das folgende Beispielprogramm ist weitestgehend
selbsterklärend und testet die zuvor definierten Klassen
walk_through_list = root;
auf Fehlerfreiheit mit verschiedenen Listenmanipulatio-
while (walk_through_list != NULL) nen. Kern des Programm ist eine generierte Liste mit
{ sieben Elementen, die nach und nach gelöscht oder
if (!strcmp(walk_through_list->
get_data(),string)) bearbeitet werden.
return(walk_through_list); Auch in diesem C++-Quelltextbeispiel sind die unge-
walk_through_list = walk_through_list->
get_next(); wollten Zeilenumbrüche – verursacht durch die be-
} grenzte Spaltenbreite – zu entfernen.
return(NULL);
} #include <cstring>
Die while-Schleife wird so oft durchlaufen, bis der #include <iostream>
#include "class_list_of_string.h"
Vergleich der Zeichenketten eine Übereinstimmung
zeigt, was zur Übergabe des Zeigers auf das gefun- using namespace std;
dene Listenelement führt. void ZeigeGanzeListe (list_of_string *liste)
int get_number_by_data(char *string) {
{ int i;
list_element_string *walk_through_list; list_element_string *element;
int count;
cout << endl << "Anzahl der Listenelemente : "
walk_through_list = root; << liste->get_size() << endl << endl;
count = 0;
if (liste->get_root() != NULL)
while (walk_through_list != NULL) cout << "Das erste Element der Liste heisst : "
{ << liste->get_root()->get_data()
count++; << endl;
if (!strcmp(walk_through_list->
get_data(),string)) if (liste->get_end() != NULL)
return(count); cout << "Das letzte Element der Liste heisst : "
walk_through_list = walk_through_list-> << liste->get_end()->get_data()
get_next(); << endl;
}
return(0); cout << endl
} << "Anzeigen aller Elemente (Methode 1):"
<< endl;
cout << "[ANFANG] :";
Hier ist der Schleifenaufbau vergleichbar mit der
Methode get_number_by_data(), nur das der zurück- element = liste->get_root();
gegebene Wert die Anzahl und nicht ein Elementzei- while(element != NULL)
ger ist. {
cout << " " << element->get_data() << " :";
element=element->get_next();
int get_size() }
{ cout << " [ENDE]" << endl;
return(list_size);
cout << "Anzeigen aller Elemente (Methode 2):"
} << endl;
oder: cout << "[ANFANG] :";