Lernziele
| Die Klasse string |
| Anlegen, Kopieren, Abändern und Verketten von Strings |
| Zugriff auf Substrings |
„Am Anfang war das Wort und das Wort bei Gott und Gott war das Wort.“. Es ist schon eine biblische Weisheit, dass Worte etwas ungeheuer Wichtiges sind. Beginnen wir daher mit dem Datentyp von LEDA, der zum Speichern und Verarbeiten von Worten und Sätzen nützlich ist, dem Typ string, einem der einfachsten Datentypen von LEDA.
Ein String ist eine Zeichenkette, d. h. eine Folge von mehreren (oder auch gar keinem) Zeichen. Der LEDA-Typ string ist somit eng mit dem C++-Typ char[] verwandt, besitzt gegenüber diesem jedoch eine Reihe von Vorteilen, die wir gleich kennen lernen werden.
Wir wollen diesen Typ zunächst dazu benutzen, ein bisschen mit Worten zu spielen. Viele weitere Beispiele dieses Tutoriums sind Spiele mit Wörtern und Sätzen. Mit ihnen können komplexe Sachverhalte oftmals auf anschauliche und belustigende Weise dargestellt werden.
RETSINAKANISTER. Wie bitte? Der Autor ist von der Komplexität der Sprache C++ frustriert und möchte sich betrinken?[4] Nein, er hat ein Wort gebildet, das vorwärts wie rückwärts gelesen gleich ist! Ein solches Wort heißt Palindrom. Vernachlässigen wir dabei Leerzeichen, Satzzeichen und Groß-/Kleinschreibung, so können wir die Definition auf ganze Sätze ausdehnen. Einige berühmte Satzpalindrome sind „Ein Neger mit Gazette zagt im Regen nie“, „Nie grub Ramses Marburg ein“ oder der englische Satz „A man, a plan, a canal - Panama“.
Schreiben wir zunächst ein LEDA-Programm, das einen String von der Standardeingabe liest und testet, ob dieser ein Palindrom ist. Dazu erzeugt das Programm aus dem Eingabestring den umgedrehten String (d. h. den String mit umgekehrter Reihenfolge der einzelnen Zeichen) und vergleicht ihn mit dem ursprünglichen. Um den String umzudrehen, hängt es nacheinander alle Zeichen von hinten nach vorne an einen zweiten String an. Dieses Programm offenbart schon recht viele Eigenschaften von LEDA im Allgemeinen und LEDA-Strings im Besonderen.
#include <LEDA/string.h>
#include <iostream>
using leda::string;
using std::cout;
int main() {
string s; // default initialization
s.read_line();
string r;
for(int i = s.length() - 1 ; i >= 0 ; i--)
r += s[i]; // inefficient, see below!
if(s == r) // fast comparison of handle-like type
cout << s << " is a palindrome\n";
else
cout << s << " is not a palindrome\n";
}
Hier wird zunächst der String s mit Default-Initialisierung definiert, d. h., bei der Definition wird die Variable auf den Default-Wert des Datentyps[5] gesetzt. Das ist typischerweise der „einfachste“ Wert des Typs; bei Strings ist das der Leerstring, d. h. der String, der aus keinem Zeichen besteht.
Strings bieten eine große Anzahl von Operationen an.[6] Über die Methode
s.read_line();
kann ein String s von der Standardeingabe gelesen werden. Dabei dient das Zeilentrennzeichen \n als Endmarkierung einer Zeile.
Das obige Programm hängt durch Verwenden des Operators += das jeweils nächste Zeichen des Strings s an den String r an. Allgemein kann durch
r += s;
ein String s an den String r angehängt werden. Der Operator += liefert dabei eine Referenz auf r zurück.
Auf die einzelnen Zeichen eines Strings wird dabei durch den Subskript-Operator [] zugegriffen:
char& c = s[i];
Er liefert eine Referenz auf das i-te Zeichen zurück, so dass über ihn dieses Zeichen auch abgeändert werden kann.
Der Vergleichsoperator
s == r;
vergleicht zwei Strings s und r miteinander.
Im Vergleich zum Typ char[] von C++ fällt auf, dass wir uns hier nicht um Allokation von Speicherplatz kümmern müssen. Der String r wird durch den Operator += auf magische Weise länger und länger.
Schauen wir uns einige weitere Grundoperationen der Klasse string an. Wir erzeugen einen String, greifen auf Teile davon zu und ersetzen sie durch andere Strings oder löschen sie ganz. Zum Schluss hängen wir noch ein paar Strings aneinander.
#include <LEDA/string.h>
#include <iostream>
using leda::string;
using std::cout;
using std::endl;
int main()
{
string s = "The LEDA Tutorial"; // LEDA rule 1
cout << s.head(3) << endl;
cout << s(4,8) << endl; // substring
cout << s.tail(8) << endl;
string t("LEDA"); // equivalent to t = "LEDA";
cout << "LEDA is a substring starting on position " << s.pos(t) << endl;
s = s.insert("wonderful ",4);
cout << s << endl;
s = s.replace("LEDA Tutorial","soupstone");
cout << s << endl;
s = s.replace_all("soupstone","soupstone by Dr. Hook");
cout << s << endl;
s = s.del("soupstone by ");
cout << s << endl;
string u;
u = s; // let u and s reference the same memory
u = u + " sings '" + t + " " + t + " " + t +"'" ;
cout << u << endl;
}
Die Ausgabe des Programms ist
The LEDA Tutorial LEDA is a substring starting on position 4 The wonderful LEDA Tutorial The wonderful soupstone The wonderful soupstone by Dr. Hook The wonderful Dr. Hook The wonderful Dr. Hook sings 'LEDA LEDA LEDA'
Die Methode
s.pos(t);
sucht in einem String s nach dem Teilstring t und liefert die Position zurück, an der t zum ersten Mal in s auftritt, bzw. -1, falls der Teilstring t nicht vorkommt. Die Zählung beginnt dabei bei 0, d. h. das erste Zeichen eines Strings s ist das Zeichen s[0].
Die Methoden
s.head(i); s.tail(i);
liefern die ersten bzw. letzten i Zeichen des Strings s zurück (in einem neuen Objekt vom Typ string).
Mit dem überladenen Funktionsoperator
s(n,m);
kann der Substring vom Zeichen mit der Nummer n (einschließlich) bis zum Zeichen mit der Nummer m (ausschließlich) extrahiert werden.
Die Methode
s.insert(i, t);
fügt einen String t an Position i in s ein.
Die Methode
s.replace(t, u);
ersetzt das erste Vorkommen des Teilstrings t durch den String u; replace_all() ersetzt alle Vorkommen von t durch u.
Mit
s.del(t);
wird das erste Vorkommen von t in s gelöscht, d. h. durch den Leerstring ersetzt.
Der Operator
s + t;
verkettet zwei Strings, d. h., er liefert einen String, der aus allen Zeichen von s gefolgt von allen Zeichen von t besteht.
Der Zuweisungsoperator
s = t;
schließlich weist einem String s eine Kopie des Strings t zu.
Dabei verändert keine dieser Methoden und Operatoren den ursprünglichen String; vielmehr wird immer ein veränderter String zurückgeliefert.
[4] Ja, C++ ist manchmal frustrierend. Der Autor hat in diesem Tutorium versucht, nur Basiskonzepte dieser mächtigen Programmiersprache zu verwenden. Der Schwerpunkt liegt auf der Beschreibung der Typen und Algorithmen von LEDA, und daher bestehen die meisten Programme aus einem einzigen main().
[5] Falls es einen solchen gibt. Die eingebauten C++-Typen wie char, int oder double haben keinen Default-Wert und auch einige Typen von LEDA nicht.
[6] Wir müssen hier allerdings zugeben, dass LEDA-Strings nicht so mächtig sind wie die entsprechenden Datentypen auf Stringbehandlung spezialisierter Sprachen wie Perl und AWK. Einem Vergleich mit der gleichnamigen Klasse string aus der C++-Standardbibliothek halten sie aber stand; die beiden Klassen besitzen nahezu dieselbe Funktionalität.