You are on page 1of 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.

h. set_previous() setzt den Zei- Typs char. oben aufge. if (data != NULL) einander getrennt betrachtet.bzw. führten Listentyp einfacher zu realisieren. muss ein Zeiger auf die neu anzulegenden Zei- ten Daten abstrakt betrachtet.string). • Destruktor: Wird das Element gelöscht. void set_next (list_element_string *pointer). Da die Variablen von Zugriffen von außen geschützt tes bekannt ist. • 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. list_element_string *previous. muss der reservierte Speicher freigegeben werden. verwenden wir zwei Klassen. Zu Beginn müssen wir für die Daten eine geeignete Struktur wählen. in dem • rückwärst Durchlaufen der Liste. also auch in C++. aber bekannter Struktur list_element_string *get_previous(). Umsetzung in C++ müssen wir dies konkretisieren. bare Fehler beruhen auf „wilden“ Zeigern! . Für die jetzt folgende chenkette . (OOP). eine Zeichenkette. next und previous zeigen auf das Vor- Vorüberlegungen für C++ gänger. Dem wollen wir uns anschließen. Objekte der obigen Klassen beides in sich tragen. Zeigern) zu realisieren: Kommen wir nun zum ersten Quelltextabschnitt. 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. set_data() legt den neu überge- Um eine Listenstruktur mit diesem Datentyp realisie- benen String unter data im Element. kann nur mit Hilfe der Methoden der Klasse auf aufwendig (z. data = (char*) malloc((strlen(string)+1)).h> verstehen unter einem • get_previous() liefert einen Zeiger auf das String eine beliebig lange Folge von Zeichen des nächste Element.B. char *get_data(). list_element_string *next. durch das Zwischenspeichern von die Objektinhalte zugegriffen werden. Das ist enorm wichtig! Viele schwer auffind- wichtige Informationen zur Verfügung.unsere Daten . werden Daten und die next = NULL. 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. public: Die doppelt verkettete Liste besteht analog zu der list_element_string(char *string). thoden gewähren uns Zugriff auf die klasseninter- d. list_element_string *get_next(). ger auf das vorhergehende Element. die die Listenverwaltung über- nimmt. // Destruktor den folgenden Komponenten: void set_previous(list_element_string *pointer). dann wird der Quellstring in den Datenbereich des Die Klasse list_element_string stellt uns den nötigen Listenelements kopiert. • Zeiger auf das vorherige Element der Liste void set_data(char *string). { char *data.übergeben werden.Da jedoch immer nur der Nachfolger eines Elemen. • Konstruktor: Beim Anlegen eines neuen Ob- jekts der Klasse list_element_string mittels new Bisher haben wir die Liste und die in ihr gespeicher. • get_data() liefert einen Zeiger auf den gespei- cherten String. • Entsprechend der obigen Ausführungen über henen Klasse wollen wir uns nun näher beschäftigen. Mit dieser Struktur und deren Implementation in C++ mittels einer dafür vorgese. Diese Operationen sind beim zweiten. doppelt verkettete Listen zeigt data auf unseren String. von außen zugreifbaren (public) Me- sich für eine Beispielanwendung der Datentyp String. sind einige wichtige Operationen nur sind. Aus didaktischen Gründen eignet Die folgenden. Dies hat zu Folge. Wenn der Speichervorgang erfolgreich war.// Konstruktor einfach verketteten List aus Listenelementen mit list_element_string(). • Daten mit beliebiger. } Der Konstruktor allokiert Speicher der Länge des Die Klasse list_element_string Strings + 1 für die Abschlusskennung des Strings (das 0-Byte). aufgerufen. ren zu können. Nachfolgerelement. wir die Klasse definieren und die Methodenrümpfe • Taschen. Ein String ist in C jedoch kein nen Daten der Objekte: elementarer Datentyp. aufführen: • Einfügen und Löschen eines Elementes an class list_element_string beliebiger Stelle. die durch den Wert 0 begrenzt wird. dass strcpy(data. Die Zeiger werden mit NULL Speicherplatz und die Methoden zum Zugriff auf initialisiert. darauf angewendeten Algorithmen nicht mehr von. Aber die Funktionen der C- Laufzeitbibliothek <string. Sortieren von Elementen.

komfortabel mit der Liste umgehen Abschnitten kennen lernen. die nicht (s. die es dem An. zum nächsten Element auf den übergebenen Zeiger. dessen Daten durch den Zeiger string auf get_next() übergibt den lokalen Zeiger auf das nächs. davon abgeleitete Sonderfälle. Die Listengröße d.B. um so den gesamten verwendeten list_element_string *get_next() { Speicherplatz freizugeben. } • get_root() liefert einen Zeiger auf den Anfang der Liste zurück.h. { list_element_string *get_element_by_number if (data != NULL) (int number). list_element_string *get_root(). ohne sich um die Verwaltung zu bemühen.die Organisation der dem übergebenen String. char *get_data() Anstelle des langsamen Durchzählens kann { man daher stattdessen besser einen Zähler return(data). ob bereits Daten vor- liegen. • Die Methode add() fügt ein Element in die Liste } ein. void set_data(char *string) list_element_string *get_end(). vorige Listenelement (NULL falls nicht vorhanden). Sollte • Variablen: Jede neu angelegte Liste bedarf der dies vergessen werden. 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 Freigabe des übrigen Spei- men wie z. den Quick-Sort wird bewusst verzichtet. • get_element_by_number() liefert einen Zeiger auf das durch number referenzierte Element. set_next() verhält sich analog zu set_previous(). Diese werden dann zuvor frei gegeben. Damit wäre die Klasse list_element_string abgehan- • get_element_by_data() sucht in der Liste nach delt. Der interessantere Teil . . public: void set_next(list_element_string *pointer) { list_of_string() // Konstruktor next = pointer. data = (char *) malloc( (strlen(string)+1) ). Demonstration werden wir in den beiden folgenden wender erlauben. das mittels eines Zeigers übergeben wird. string). if (data != NULL) } strcpy(data. die Anzahl der Ele- mehr zur Laufzeit freigegeben werden können. • Der Konstruktor initalisiert die Liste und setzt get_data() übergibt den lokalen Zeiger auf den Da. 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. addieren sich im Laufe der Kenntnis des ersten und des letzten Elements Zeit immer mehr Speicherbereiche auf.folgt im nächsten Abschnitt. chers für das Objekt übernimmt C++. tenstring an die aufrufende Funktion / Methode. Mit Hilfe dieser Methoden lässt sich eine Liste von Sie organisiert die Listenelemente und stellt einen Strings komfortabel verwalten. Richten wir wieder einen Blick auf die Klassendefinition } und die Methoden. } list_size mitlaufen lassen. Zwei kurze Beispiele zur Satz von Methoden zur Verfügung. void erase_last(). int get_number_by_data(char *string). { if (data != NULL) free(data). Auf Sortieralgorith- String wieder frei. void erase_first(). get_end() entsprechend das get_previous() übergibt den lokalen Zeiger auf das Ende der Liste. dem Inhalt eines Listenelements. free(data). te Listenelement (NULL falls nicht vorhanden).~list_element_string() zu können. Die Klasse list_of_string • get_size() liefert die Anzahl der Listenelemente Die Klasse list_of_string leistet die eigentliche Arbeit. return(next). ~list_of_string() // Destruktor } void add(char *string). Ist er identisch mit Listenelemente . int get_size(). list_element_string *end. die Startwerte fest. • Der Destruktor muss alle Listenelemente lö- schen. list_element_string *get_element_by_data (char *string). • Die Methode erase() löscht das Element. eine Zeichenkette gekennzeichnet ist. fache. Aber dem engagierten Leser dürfte es nicht schwer fallen. mente der Liste. } Kommen wir zur Beschreibung der Methoden: set_data() überprüft zunächst. int erase(list_element_string *element).). ist ein häufig benötigter Wert. Die Me- list_element_string *get_previous() { thoden erase_first() und erase_last() sind ein- return(previous).o. wird die Nummer des Elements in der Liste übergeben. Die Methode set_previous() setzt den internen Zeiger list_element_string *root.

d. new list_element_string(string). // root = end => only one element left root = NULL. // next element is NULL end->set_next(new_element). { // end of list is the new element return(root). list_size--. } } Der Destruktor löscht nacheinander alle Elemente else // out of the middle part of list der Liste. schen. list_size--. nachfolgende Element noch bestimmt werden muss. • Die Liste enthält zwei oder mehr Elemente int count. Die Hilfsvariable element_to_delete spei.h. erase_last verhält sich analog zu erase_first(). delete element_to_delete. return(-1). da vor dem eigentlichen Löschvorgang das element->get_previous()-> set_next(element->get_next()). // ERROR if (element == end) // if it is the last element list_of_string() { { if (element == root) list_size = 0. hier muss in Abhängigkeit von der Position und der if (root != NULL) Anzahl der vorhandenen Listenelemente unterschied- list_size++. } } } Der lokale Zeiger root wird als Argument übergeben. Ähnlich verhält es sich bei der erase()-Methode.h. // previous element is start new_element->set_next(NULL). } new_element->set_previous(root). set_previous(element->get_previous()). { walk_through_list = root = element->get_next(). 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. root und *get_element_by_number(int number) { end sind identisch list_element_string *walk_through_list. { end = NULL. } lich verfahren werden. new list_element_string(string). NULL bzw. *element_to_delete. // next element is NULL zugegriffen und der nur lokal bekannte Zeiger root root->set_next(new_element). list_element_string *get_root() end = new_element. } } // 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. list_size--. int erase(list_element_string *element) { den Erläuterungen (spaltenbedingte Umbrüche sind if (element == NULL) zu entfernen): return(0). // ERASE OK } if(root == NULL) // first element of list { root = new list_element_string(string). void add(char *string) } { } list_element_string *new_element. Der Konstruktor initialisiert die Zeiger und Werte mit delete element. root = NULL list_element_string • Nur ein Element ist in der Liste. delete element. delete element. list_element_string *walk_through_list. Auch end = root. else { void erase_first() if (end->get_previous() == NULL) { { new_element = erase(root). aufrufen kön- list_size++. . delete element. nen (list_of_string *listobj = new list_of_string(string)) } else void erase_last() { { new_element = erase(end). walk_through_list->get_next(). } else // deleting the last element ~list_of_string() { { end = element->get_previous(). listobject->erase(listobject->get_root()). { element->get_next()-> chert den Zeiger auf das zu löschende Element zwi. übergeben. end->set_next(NULL). root = NULL. } list_element_string *get_end() Zur korrekten Zeiger-Zuweisung der Zeiger root und { return(end). list_size--. // previous element is start Hier wird auf die bereits definierte Methode erase() new_element->set_next(NULL).Zunächst der Quelltext der Methoden mit ergänzen. walk_through_list = root. list_size++. } end = NULL. } new_element->set_previous(end). • Die Liste ist leer d. 0. Alternativ kann man außerhalb der Klasse // end of list is the new element end = new_element.

string)) bearbeitet werden. i++) { cout << " " << liste-> unsigned int count. dene Listenelement führt.*/ (count < number)) } { walk_through_list = walk_through_list-> Die schnelle get_szie()-Methode liefert nur den lokalen get_next(). cout << "[ANFANG] :". wohl aber } bedeutend langsamere Methode zählt die Liste durch.h ausgelagert werden. } Sie wird hier nur als Beispiel. gegebene Wert die Anzahl und nicht ein Elementzei. << endl. while(element != NULL) ger ist. } while(walk_through_list!=NULL) { int main(void) count++. Außerdem muss die Funktion noch in class_list_of_string.string)) if (liste->get_end() != NULL) return(count). if (liste->get_root() != NULL) while (walk_through_list != NULL) cout << "Das erste Element der Liste heisst : " { << liste->get_root()->get_data() count++. Nummer number steht.count = 1. int get_size() for (i=1. Quelltext einbinden kann. so gegen zu hohe Werte von number gesichert sein. bis der Zeiger walk_through_list auf dem Element mit der Abschließend können diese Klassendefinitionen z. Alternativ hätte man eine if-Abfrage mit anschließen- der for-Schleife einsetzen können. return(NULL). count = 0. count++. auf Fehlerfreiheit mit verschiedenen Listenmanipulatio- while (walk_through_list != NULL) nen. void ZeigeGanzeListe (list_of_string *liste) int get_number_by_data(char *string) { { int i. int count. Auch in diesem C++-Quelltextbeispiel sind die unge- walk_through_list = walk_through_list-> get_next(). } #include <cstring> Die while-Schleife wird so oft durchlaufen. Hier ist der Schleifenaufbau vergleichbar mit der Methode get_number_by_data(). wollten Zeilenumbrüche – verursacht durch die be- } grenzte Spaltenbreite – zu entfernen. <<"--------------------------------------- walk_through_list = start. oder: cout << "[ANFANG] :".h" Vergleich der Zeichenketten eine Übereinstimmung zeigt. { . Die gleichwertige. wie man ganz einfach eine Liste durchlaufen kann. if (!strcmp(walk_through_list-> get_data(). element = liste->get_root(). cout << "Das letzte Element der Liste heisst : " walk_through_list = walk_through_list-> << liste->get_end()->get_data() get_next(). In dass man sie mit #include in einen beliebigen C++- diesem Fall liefert die Funktion NULL. list_element_string Beispiel I : Listendemo. list_element_string *element. } return(0). i<=liste->get_size(). using namespace std. << endl. aufgeführt. cout << " [ENDE]" << endl. Zähler list_size zurück. Die while-Schleife wird so lange durchlaufen. list_element_string *walk_through_list. return(walk_through_list). walk_through_list = walk_through_list-> walk_through_list = root. cout << "Anzeigen aller Elemente (Methode 2):" } << endl. element=element->get_next(). nur das der zurück.cpp *get_element_by_data(char *string) { list_element_string *walk_through_list. get_next_element(). cout << endl count = 0. was zur Übergabe des Zeigers auf das gefun. get_element_by_number(i)->get_data() << " :". Kern des Programm ist eine generierte Liste mit { sieben Elementen. return(walk_through_list). list_element_string *walk_through_list. << liste->get_size() << endl << endl. Das folgende Beispielprogramm ist weitestgehend selbsterklärend und testet die zuvor definierten Klassen walk_through_list = root. return(list_size). bis der #include <iostream> #include "class_list_of_string. int get_size() } { cout << " [ENDE]" << endl. } while ((walk_through_list != NULL) && return(count). die nach und nach gelöscht oder if (!strcmp(walk_through_list-> get_data(). -----------------------------------------". cout << endl << "Anzahl der Listenelemente : " walk_through_list = root.B. cout << endl } << "Anzeigen aller Elemente (Methode 1):" << endl. { cout << " " << element->get_data() << " :".

<< laenge2 list->erase(list->get_element_by_number(2)). #include "class_list_of_string. cout << endl cout << "Loesche nun das neue zweite Element." << endl. laenge2 = 0. Die Datei wird geschlossen und die Liste wird ZeigeGanzeListe(list)." << endl.i++) } { else laenge1+=strlen(document-> cout << "Element " << Suchstring get_element_by_number(i)->get_data()). ZeigeGanzeListe(list). gehen bei die- ser Methode in keinem Fall Daten verloren. wie es in vereinfachter Form z. cout << document->get_element_by_number(i)-> cout << "------------------------------------. Zeile für Zeile auf dem Bild- list->erase_first(). int i. fclose (datei). return(0).100000. set_data("umbenannt").". << " wurde nicht gefunden. list->add("E 4")." << endl. Carriage Return / Linefeed)" # ZeigeGanzeListe(list). der kann unter www. cout << "Fuege 7 Elemente hinzu." != NULL) << endl. /* Position 0 vom Ende */ list->add("E 7"). cout << "Verweis auf Element Nr. schirm ausgegeben.get(). " document->add(buffer). for (i=1." << endl.laenge2.txt im ASCII-Modus geöffnet und 'benannt'. fseek(datei." << endl. freue ich mich über eine Nachricht."r")) == NULL) cout << "Datei nicht erfolgreich cout << "Lege neue Liste an. long laenge1. list->add("E 6").de/cpp_listen. der beiden Werte. } ZeigeGanzeListe(list). Zu guter Letzt noch ein kleines Anwendungsbeispiel. 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. Fehler eingeschlichen haben oder Fragen zur Imple- char buffer[100000]. laenge2 = ftell(datei). 2 um in delete document. << "Anzahl der Zeichen laut Liste :" list->erase(list->get_root()). tragen. << " (incl. ZeigeGanzeListe(list). mentierung in verschiedenen C++-Umgebungen offen list_of_string *document. Beispiel II : Datei_in_Liste. chend ausgegeben. cout << "Loesche erstes Element." << endl.h" using namespace std. 'umbenannt'. int main() { Wer die Quelltexte nicht von Hand übertragen möchte. SEEK_SET). während in der Liste nur ein Zeichen pro Zeile gespeichert wird. -------------------------------------------". list->add("E 1"). list = new list_of_string().cpp Wenn ASCII-Texte eingelesen werden. list->get_element_by_number(2)-> return(0). << laenge1 << endl. if(list->get_element_by_data(Suchstring) cout << "Datei erfolgreich geschlossen. SEEK_END).zip die obigen Demodateien als Quelltexte und ausführba- document = new list_of_string(). { cout << "Datei erfolgreich geoeffnet. } cout << "Loesche das letze Element. ler schließt eine Zeile in einer Datei mit einer Kombina- } tion aus Linefeed und Carriage Return ab.i<=document->get_size().datei). cout << "Benenne Element Nr. cout << "Loesche letztes Element. list->get_element_by_data("umbenannt")-> sukzessive jede Zeile in jeweils ein Listenelement über- set_data("benannt"). get_data() << endl. fseek(datei. list->add("E 5").h." << endl. geblieben sein." << endl. get_element_by_data(Suchstring)-> laenge1 = 0. cout << "\nLoesche Liste.B. cout << endl cout << "Loesche das erste Element. /* Anfang */ cout << "Suche das Element mit Inhalt '" while(!feof(datei)) << Suchstring <<"' und gib es aus:" { << endl. get_data().EXE Dateien downloaden. { } cout << list-> cout << "Dokumentinhalt:" << endl << endl. << endl. aufsummierten Längen der Listenelemente verglei- ZeigeGanzeListe(list). 0L. ZeigeGanzeListe(list)." << "Anzahl der Zeichen laut Datei :" << endl. 0L. << document->get_size() << endl. cout << "Benenne Element 'umbenannt' um in Es wird die Datei text." << endl. cin. fgets(buffer. geoeffnet. if( (datei = fopen("text. << list->get_number_by_data(Suchstring) } << " : ". nun elementweise d. list_of_string *list.get(). delete list." << endl. else char Suchstring[]="E 4". list->add("E 2"). Abschließend wird die Dateilänge mit der Länge der list->erase_last(). Je nach Betriebsystem und Compi- cin. re *. ZeigeGanzeListe(list).bjoern-thoene.txt". cout << endl << "Anzahl der Zeilen :" list->erase(list->get_end()). Sollten sich in das Tutorial trotz intensiver Suche noch FILE *datei. << endl." ZeigeGanzeListe(list). Wundern Sie sich nicht über die (mögliche) Diskrepanz cout << "Bitte Eingabetaste druecken". . list->add("E 3").