Sie sind auf Seite 1von 6

Tutorial C++ Programmierung

Realisierung einer doppelt-verketteten Liste


von Björn Philippe Thöne

Liebe Joycer, • die hundertprozentige Kontrolle (und damit


Verantwortung) über das Geschehen;
vor kurzem entstand im Rahmen meiner Tutorentä- • das Interesse am Programmieren und an kom-
tigkeit (Universität Duisburg-Essen) ein Aufsatz über plexeren Datenstrukturen der Informatik.
die Programmierung der sogenannten doppelt ver-
ketteten Liste, einer wichtigen und klassischen,
dynamischen Datenstruktur in der Informatik. Die doppelt-verkettete Liste
n diesem Artikel werde ich für alle, die diese Struktur
noch nicht kennen, genauer erläutern, was sich da- Die doppelt-verkettete Liste ist eine wichtige Standard-
hinter verbirgt. Datenstruktur der Informatik und gehört natürlich zu der
Für die Umsetzung wurde C++ gewählt, da es in der Gruppe der Listen.
heutigen Softwareentwicklung als die am häufigsten Listen sind dynamische Datenstrukturen, deren Größe
verwendete Programmiersprache angesehen werden zu Beginn nicht feststeht und die sich zur Laufzeit än-
kann. dern kann. Eine Liste besteht, wie der Name bereits
Auch wenn nahezu alle Microsoft-Produkte darin erkennen lässt, aus einer Zahl von Daten-Elementen,
entwickelt wurden (aber auch große Teile von Unix / die (ähnlicher einer Liste von Zeilen auf dem Papier
Linux) ;-), ist diese Sprache in Punkto Flexibilität, von oben nach unten angeordnet) linear miteinander
Portierbarkeit und Geschwindigkeit unschlagbar und verknüpft sind.
besitzt zudem durch ihre zum Teil asketisch kurzen Bei der Implementierung einer Listenstruktur in eine
und manchmal etwas kryptisch anmutenden Quell- eigene Anwendung verhält sich die Liste zumeist wie
texte eine gewisse Anziehungskraft. eine undurchschaubare Black Box.
Ich hoffe, dass dieser Beitrag für den Leser ausrei- Nachdem die Liste einmalig angelegt wurde, können
chend Licht ins Dunkel der C++ Programmierung als durch zur Verfügung gestellte Funktionen (in C++ Me-
auch in die Struktur der doppelt verketteten Liste thoden) Elemente hinzugefügt, gelöscht, verschiede-
bringt. Einzige Voraussetzung für den Genuss der nen Kriterien abgerufen, vertauscht und noch weitere
folgenden Zeilen sind Grundkenntnisse in der C- und Operationen durchgeführt werden. Häufig bieten Listen
C++-Programmierung und der OOP (Objekt- die Möglichkeit der Sortierung.
orientierten Programmier)-Konzepte.
Für Anregungen und Nachfragen stehe ich gerne per Die Kenntnis der internen Organisation der Daten
e-Mail unter bjoern.thoene@t-online.de zur Verfü- ist für die Nutzung der Liste im Prinzip nicht nötig.
gung. Damit stürzen wir uns jetzt in das Vergnügen.
Wenn wir nicht auf fertige Lösungen zurückgreifen
wollen, müssen wir uns damit jedoch auseinanderset-
Prolog zen. Insbesondere die Art der Verknüpfung der Listen-
elemente entscheidet über die Fähigkeiten der Liste.
Wozu sollte man eine DLL (double-linked list) selbst Hierbei kann man zwei Arten unterscheiden:
programmieren? Die Frage, weshalb man Klassen
zur Verwaltung doppelt verkettete Listen einmal • Einfach-verkettete Listen
selbst entwickeln sollte, ist zunächst auf dem folgen- • Doppelt-verkettete Listen
den Hintergrund gut nachvollziehbar: Der Ansi-C++
Standard enthält bereits die von HP entwickelte STL Die einfach verkettete Liste besteht aus Listenele-
(Standard Template Library) Bibliothek, die u.a. eine menten, die die folgenden Komponenten aufweisen:
komfortable, umfassende und stabile Listenverwal-
tung bietet und mit einer großen Zahl von Methoden • Daten mit beliebiger, aber bekannter Struktur;
ausgestattet ist. • Verweis (Zeiger) auf das nächste Element der
Dennoch sprechen viele Gründe dafür, zumindest Liste.
einmal im Leben eines Programmierers eine solche
Struktur selbst in die Hand zu nehmen: Es existiert für jede einfach-verkettete Liste zudem
immer ein Verweis (im Folgenden in Hinblick auf die
• das tiefere Verständnis, wie Listen gespei- Implementation in C nur noch Zeiger genannt) auf das
chert und Daten darin organisiert werden; erste Element (root) der Liste.
• die Möglichkeit, die Datenstruktur zu modifi- Zu Beginn ist die Liste leer, d.h. root zeigt definitions-
zieren und neue Algorithmen zur Verarbei- gemäß auf NULL (nicht vorhanden bzw. definiert).
tung der Daten zu implementieren; Dies ermöglicht uns folgende Operationen auf einfache
• der effizientere Umgang mit Listen und das Weise durchzuführen:
Wissen, für welche Einsatzgebiete sich eine • Durchlaufen der Liste von Anfang zum Ende;
DLL lohnt; • Anhängen und Löschen eines Elementes.
Da jedoch immer nur der Nachfolger eines Elemen- Da die Variablen von Zugriffen von außen geschützt
tes bekannt ist, sind einige wichtige Operationen nur sind, kann nur mit Hilfe der Methoden der Klasse auf
aufwendig (z.B. durch das Zwischenspeichern von die Objektinhalte zugegriffen werden.
Zeigern) zu realisieren:
Kommen wir nun zum ersten Quelltextabschnitt, in dem
• rückwärst Durchlaufen der Liste; wir die Klasse definieren und die Methodenrümpfe
• Taschen, Sortieren von Elementen; aufführen:
• Einfügen und Löschen eines Elementes an
class list_element_string
beliebiger Stelle. {
char *data;
list_element_string *previous;
Diese Operationen sind beim zweiten, oben aufge- list_element_string *next;
führten Listentyp einfacher zu realisieren.
public:
Die doppelt verkettete Liste besteht analog zu der list_element_string(char *string);// Konstruktor
einfach verketteten List aus Listenelementen mit list_element_string(); // Destruktor
den folgenden Komponenten: void set_previous(list_element_string *pointer);
void set_next (list_element_string *pointer);
list_element_string *get_next();
• Daten mit beliebiger, aber bekannter Struktur list_element_string *get_previous();
• Zeiger auf das vorherige Element der Liste void set_data(char *string);
char *get_data();
• Zeiger auf das nächste Element der Liste }

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. }

Der Konstruktor allokiert Speicher der Länge des


Die Klasse list_element_string Strings + 1 für die Abschlusskennung des Strings (das
0-Byte). Wenn der Speichervorgang erfolgreich war,
dann wird der Quellstring in den Datenbereich des
Die Klasse list_element_string stellt uns den nötigen
Listenelements kopiert. Die Zeiger werden mit NULL
Speicherplatz und die Methoden zum Zugriff auf
initialisiert. Das ist enorm wichtig! Viele schwer auffind-
wichtige Informationen zur Verfügung.
bare Fehler beruhen auf „wilden“ Zeigern!
~list_element_string() zu können, ohne sich um die Verwaltung zu bemühen.
{
if (data != NULL)
free(data); Richten wir wieder einen Blick auf die Klassendefinition
}
und die Methoden. Es werden im Hinblick auf die Kom-
plexität des Beispiels nur die wichtigsten Operationen
Der Destruktor gibt den allokierten Speicher für den
einer Listenverwaltung integriert. Auf Sortieralgorith-
String wieder frei. Die Freigabe des übrigen Spei-
men wie z.B. den Quick-Sort wird bewusst verzichtet.
chers für das Objekt übernimmt C++.
Aber dem engagierten Leser dürfte es nicht schwer
fallen, basierend auf den folgenden Methoden fehlende
void set_previous(list_element_string *pointer)
{ Operationen zu ergänzen:
previous = pointer;
} class list_of_string
{
int list_size;
Die Methode set_previous() setzt den internen Zeiger list_element_string *root;
zum nächsten Element auf den übergebenen Zeiger. list_element_string *end;

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] :";

int get_size() for (i=1; i<=liste->get_size(); i++)


{ cout << " " << liste->
unsigned int count; get_element_by_number(i)->get_data() << " :";
list_element_string *walk_through_list; cout << " [ENDE]" << endl;
cout << endl
count = 0; <<"---------------------------------------
walk_through_list = start; -----------------------------------------";
}
while(walk_through_list!=NULL)
{ int main(void)
count++; {
list_of_string *list; if( (datei = fopen("text.txt","r")) == NULL)
cout << "Datei nicht erfolgreich
cout << "Lege neue Liste an." << endl; geoeffnet." << endl;
list = new list_of_string(); else
char Suchstring[]="E 4"; {
cout << "Datei erfolgreich geoeffnet."
ZeigeGanzeListe(list); << endl;
cout << "Fuege 7 Elemente hinzu." << endl;
list->add("E 1"); list->add("E 2"); laenge2 = 0;
list->add("E 3"); list->add("E 4"); fseek(datei, 0L, SEEK_END);
list->add("E 5"); list->add("E 6"); /* Position 0 vom Ende */
list->add("E 7"); laenge2 = ftell(datei);
ZeigeGanzeListe(list);
fseek(datei, 0L, SEEK_SET); /* Anfang */
cout << "Suche das Element mit Inhalt '" while(!feof(datei))
<< Suchstring <<"' und gib es aus:" {
<< endl; fgets(buffer,100000,datei);
cout << "Verweis auf Element Nr. " document->add(buffer);
<< list->get_number_by_data(Suchstring) }
<< " : "; fclose (datei);
if(list->get_element_by_data(Suchstring) cout << "Datei erfolgreich geschlossen."
!= NULL) << endl;
{ }
cout << list-> cout << "Dokumentinhalt:" << endl << endl;
get_element_by_data(Suchstring)-> laenge1 = 0;
get_data() << endl; for (i=1;i<=document->get_size();i++)
} {
else laenge1+=strlen(document->
cout << "Element " << Suchstring get_element_by_number(i)->get_data());
<< " wurde nicht gefunden."; cout << document->get_element_by_number(i)->
cout << "------------------------------------- get_data();
-------------------------------------------"; }
cout << "Loesche das letze Element." << endl; cout << endl << "Anzahl der Zeilen :"
list->erase(list->get_end()); << document->get_size() << endl;
ZeigeGanzeListe(list); cout << endl
cout << "Loesche das erste Element." << endl; << "Anzahl der Zeichen laut Liste :"
list->erase(list->get_root()); << laenge1 << endl;
ZeigeGanzeListe(list); cout << endl
cout << "Loesche nun das neue zweite Element." << "Anzahl der Zeichen laut Datei :"
<< endl; << laenge2
list->erase(list->get_element_by_number(2)); << " (incl. Carriage Return / Linefeed)" #
ZeigeGanzeListe(list); << endl;
cout << "Benenne Element Nr. 2 um in delete document;
'umbenannt'." << endl; cin.get();
list->get_element_by_number(2)-> return(0);
set_data("umbenannt"); }
ZeigeGanzeListe(list);
cout << "Benenne Element 'umbenannt' um in Es wird die Datei text.txt im ASCII-Modus geöffnet und
'benannt'." << endl;
list->get_element_by_data("umbenannt")-> sukzessive jede Zeile in jeweils ein Listenelement über-
set_data("benannt"); tragen. Die Datei wird geschlossen und die Liste wird
ZeigeGanzeListe(list);
cout << "Loesche erstes Element." << endl; nun elementweise d.h. Zeile für Zeile auf dem Bild-
list->erase_first(); schirm ausgegeben.
ZeigeGanzeListe(list);
cout << "Loesche letztes Element." << endl; Abschließend wird die Dateilänge mit der Länge der
list->erase_last(); aufsummierten Längen der Listenelemente verglei-
ZeigeGanzeListe(list); chend ausgegeben.
cout << "\nLoesche Liste." << endl;
delete list; Wundern Sie sich nicht über die (mögliche) Diskrepanz
cout << "Bitte Eingabetaste druecken"; der beiden Werte. Je nach Betriebsystem und Compi-
cin.get();
return(0); ler schließt eine Zeile in einer Datei mit einer Kombina-
} tion aus Linefeed und Carriage Return ab, während in
der Liste nur ein Zeichen pro Zeile gespeichert wird.
Beispiel II : Datei_in_Liste.cpp Wenn ASCII-Texte eingelesen werden, gehen bei die-
ser Methode in keinem Fall Daten verloren.

Zu guter Letzt noch ein kleines Anwendungsbeispiel,


wie es in vereinfachter Form z.B. in einem Editor
vorkommen könnte:
Schlusswort
#include <cstdio> Ich hoffe die oben aufgeführten Beispiele bringen das
#include <iostream> Thema in verständlicher Form nahe.
#include "class_list_of_string.h"
using namespace std; Sollten sich in das Tutorial trotz intensiver Suche noch
FILE *datei; Fehler eingeschlichen haben oder Fragen zur Imple-
char buffer[100000]; mentierung in verschiedenen C++-Umgebungen offen
list_of_string *document;
geblieben sein, freue ich mich über eine Nachricht.
int main()
{ Wer die Quelltexte nicht von Hand übertragen möchte,
int i;
long laenge1,laenge2; der kann unter www.bjoern-thoene.de/cpp_listen.zip
die obigen Demodateien als Quelltexte und ausführba-
document = new list_of_string();
re *.EXE Dateien downloaden.