C++ Kurs

String Objekte II

Die Themen:

In diesem Kapitel werden wir uns die string Klasse der Standard-Bibliothek einmal detaillierter ansehen. Im Folgenden wird davon ausgegangen, dass bekannt ist, wie string Objekte definiert, eingelesen und ausgegeben werden und dass die string Operatoren =, + sowie die Vergleichsoperationen bekannt sind.

Bisher haben wir der Einfachheit halber immer nur Objekte der Klasse string verwendet. Diese Klasse verwendet zur Speicherung der Zeichen intern den Datentyp char. Um auch Zeichen mit dem Datentyp wchar_t ablegen zu können, gibt es zusätzlich noch eine weitere Klasse, die Klasse wstring.

PgmHeader#include <string>
std::string charString;
std::wstring wcharString;

Da string und wstring die gleichen Eigenschaften und Schnittstellen besitzen, wird im Folgenden nicht mehr zwischen diesen beiden string-Typen unterschieden.

Speicherverwaltung

Der von einem string Objekt letztendlich belegte Speicher wurde bei den bisherigen Ausführungen nicht betrachtet. Da in manchen Fällen aber der Speicherplatz eine nicht zu vernachlässigende Rolle spielt, wollen wir dies an dieser Stelle jetzt nachholen.

Zur Ermittlung des von einem String belegten Speichers stehen die Memberfunktionen size(), length() und capacity() zur Verfügung.

size() und length() sind identischen Memberfunktionen, sie liefern die Anzahl der in einem String abgelegten Zeichen. capacity() hingegen liefert die Anzahl der Zeichen zurück, die in einem String abgelegt werden können ohne dass erneut Speicher angefordert werden muss. Und diese Anzahl muss nicht zwingend mit der Anzahl der Zeichen im String übereinstimmen. Sehen wir uns dazu einmal die nachfolgenden Anweisungen sowie die dazugehörigen Ausgaben an. Wie der Ausgabe entnommen werden kann, verändert sich nach der zweiten Zuweisung nur die Länge des Strings, nicht aber die Anzahl der maximalen Zeichen im String ohne erneute Speicheranforderung.

PgmHeaderstring myString = "123456789";
cout << myString.length() << ',' << myString.capacity() << endl;
myString = "12";
cout << myString.length() << ',' << myString.capacity() << endl;
Programmausgabe9,15
2,15

Je nach Compiler können hier andere Werte stehen, aber die grundsätzliche Aussage bleibt die gleiche: der von einem String einmal belegte Speicherplatz wird in der Regel aus Gründen der Ausführungsgeschwindigkeit nicht mehr verkleinert. Dies bedeutet in der Praxis, dass ohne weiteres Zutun der von einem String belegte Speicherplatz immer nur anwächst.

Um die zeitintensive Speicheranforderung für eine Stringerweiterung zu minimieren, stellt die string Klasse eine Memberfunktion reserve(...) zur Verfügung. reserve(...) erhält als Parameter die Anzahl der Zeichen, für die mindestens Speicher zu reservieren ist. Wenn also die maximale Länge eines String im Voraus bekannt ist, so kann mit Hilfe dieser Memberfunktion gleich von Anfang an genügend Speicher reserviert werden und somit die zeitintensiven Speicheranforderungen vermieden werden. Wird reserve() ohne Parameter aufgerufen, so wird der für den String belegte Speicherplatz so bemessen, dass zumindest der aktuelle Stringinhalt darin Platz findet. Je nach Implementierung der string Klasse kann damit eine Reduzierung des Speicherplatzverbrauchs erreicht werden.

HinweisWie viel Speicher nach dem Aufruf von reserve() tatsächlich belegt wird ist jedoch implementierungsabhängig.

string Iteratoren

Bei einigen der nachfolgenden string-Operationen werden sogenannte Iteratoren eingesetzt. Iteratoren im Allgemeinen werden erst später ausführlich behandelt. Für den Augenblick können Sie sich unter einem Iterator eine Art Zeiger vorstellen, der auf eine beliebige Position innerhalb des Strings verweist. Der String-Iterator ist quasi das C++ Gegenstück zum char-Zeiger in C-Strings.

Fangen wir mit der Definition des Iterators an. Nachfolgend sehen Sie die Definition des String-Iterators iter.

std::string::iterator iter;

Und was für normale Zeiger gilt, gilt auch für Iteratoren: sie müssen nach ihrer Definition unbedingt initialisiert werden. Für die Initialisierung der Iteratoren stehen die string-Memberfunktionen begin() und end() zur Verfügung. begin() liefert einen Iterator auf den Anfang des Strings und end() einen Iterator auf die nächste Position hinter dem String, d.h. begin() und end() definieren einen offenen Bereich [...).

PgmHeaderstring myString("12345");
// Iterator auf Stringanfang setzen
// std::string::iterator iter = myString.begin();
// oder einfacher

auto iter = myString.begin();
// Iterator auf Stringende+1 setzen
iter = myString.end();

Um auf das Zeichen zuzugreifen, auf das der Iterator verweist, wird der Iterator, genauso so wie ein Zeiger, einfach dereferenziert. Und es besteht noch eine weitere Gemeinsamkeit zwischen Zeigern und String-Iteratoren: auch auf String-Iteratoren sind nur die Operationen Addition und Subtraktion sowie Vergleichsoperationen erlaubt.

PgmHeader// String zeichenweise ausgeben
for (auto iter=myString.begin(); iter!=myString.end(); ++iter)
   cout << *iter << ',';
// oder einfacher, aber ohne Iterator
for (auto ch: myString)
   cout << ch << ',';

Das obige Beispiel gibt den Inhalt des String-Objekts myString zeichenweise aus. Beachten Sie in der for-Schleife das Abbruch-Kriterium! end() liefert einen Iterator der hinter das letzte Zeichen im String verweist.

Außer diesem Standard String-Iterator gibt es noch einen Reverse-Iterator. Er erlaubt es, einen String rückwärts zu durchlaufen. Dabei ist zu beachten, dass nun anstelle von begin() und end() die Memberfunktionen rbegin() und rend() verwendet werden. Um den Iterator auf das vorhergehende Zeichen zu positionieren, ist dieser Iterator zu inkrementieren!

PgmHeader// Reverse-Iterator definieren
string::reverse_iterator riter;
// String rückwärts ausgeben
for (riter=myString.rbegin(); riter!=myString.rend(); ++riter)
   cout << *riter << ',';
HinweisSollten Sie aus Versehen anstelle von rbegin() bzw. rend() einmal begin() bzw. end() geschrieben haben, so erhalten Sie vom Compiler eine Fehlermeldung.
HinweisVerwenden Sie bei Iteratoren nach Möglichkeit immer die Präfix-Operatoren ++iter bzw. --iter und nicht die Suffix-Operatoren iter++ bzw. iter--. Die Suffix-Operatoren benötigen intern ein temporäres Iterator-Objekt, was sich negativ auf die Laufzeit auswirkt.
AchtungIteratoren können nach der Durchführung von Operationen, die eine erneute Speicheranforderung verursachen, ungültig werden! Zu diesen Operationen gehören zum Beispiel alle Operationen, die die Länge eines Strings erhöhen (wie z.B. der Operator +) oder auch die Memberfunktion reserve(...).

Vergleichen von strings

Den üblichen Vergleich von Strings mit Hilfe von Vergleichsoperatoren haben wir schon des Öfteren in den vorangegangen Kapiteln eingesetzt. Das Ergebnis des Vergleichs ist entweder true oder false.

PgmHeader// Strings definieren
string s1("abc"), s2("xyz");
// Vergleich mit Vergleichsoperatoren
bool ret = s1<s2;

Zusätzlich bietet die string Klasse für Vergleiche auch noch die Memberfunktion compare(...) an. compare(...) erhält im einfachsten Fall als Parameter das zu vergleichende string Objekt oder einen const char Zeiger auf einen C-String. Als Returnwert liefert compare(...) jetzt aber einen int-Wert der '0' ist, wenn beide Strings den gleichen Inhalt haben, <0 wenn der übergebene (zu vergleichende) String größer ist und >0 wenn er kleiner ist.

HinweisGrößer bzw. kleiner bezieht sich hier nicht auf die Länge des Strings, sondern es wird lexikalisch verglichen, d.h. "A" ist größer als "aaa".

Aber das ist noch nicht alles, was die Memberfunktion compare(...) kann. compare(...) kann auch dazu verwendet werden, Teile eines Strings zu vergleichen. Dazu stehen folgenden Memberfunktionen zur Verfügung:

int compare (size_type pos, size_type len, const string& s2) const;

Vergleich den Teilstring mit der Länge len ab der Position pos mit den zweiten String s2.

int compare (size_type pos, size_type len, const string& s2, size_type pos2, size_type len2) const;

Vergleich den Teilstring mit der Länge len ab der Position pos mit dem zweiten Teilstring s2 mit der Länge len2 ab der Position pos2.

int compare (size_type pos, size_type len, const char* pstr) const;

Vergleich den Teilstring mit der Länge len ab der Position pos mit dem C-String auf den pstr verweist.

int compare (size_type pos, size_type len, const char* pstr, size_type len2) const;

Vergleich den Teilstring mit der Länge len ab der Position pos mit len2 Zeichen aus dem C-String auf den pstr verweist.

Hinweissize_type ist ein Synonym (typedef) innerhalb der Klasse string für size_t. Wollen Sie Variablen von diesem Datentyp definieren, so müssen Sie vor size_type die Klasse string angegeben, also string::size_type.

Das nachfolgende Beispiel zeigt einen möglichen Einsatz der compare(...) Memberfunktion auf. Innerhalb eines in einem String abgelegten Textes wird auf das Vorhandsein eines bestimmten Textes ab einer definierten Stelle geprüft. Beachten Sie, dass der Text an einer bestimmten Stelle stehen muss, compare(...) durchsucht nicht den komplette String. Hierzu werden andere Memberfunktionen verwendet die gleich noch vorgestellt werden.

PgmHeader// Zu prüfender Text
string test1("Sehr geehrte Frau Ypsilon");
// Prüfen ob Empfänger männlich/weiblich
if (test1.compare(12,5," Frau") == 0)
   cout << "Empfänger ist weiblich" << endl;
else
   cout << "Empfänger ist männlich" << endl;

Suchen in strings

Zum Suchen von Zeichen und Strings in string-Objekten stehen über 40 verschiedene Memberfunktionen zur Verfügung. Aber keine Angst, wir werden uns hier nicht alle Memberfunktionen ansehen, sondern nur deren 'Grundformen'. So erlauben die meisten Memberfunktionen die Angabe des Suchkriteriums entweder als string oder als C-String.

Alle Memberfunktionen zum Suchen haben als Rückgabetyp den Datentyp string::size_type.

string::npos Wert

Bei einer Suche kann es vorkommen, dass ein gesuchtes Zeichen bzw. ein gesuchter String im zu untersuchenden String nicht vorhanden ist. In diesem Fall liefern die Such-Memberfunktionen den Wert string::npos zurück (siehe auch nachfolgendes Beispiel).

Suchen von Zeichen

Zum Suchen von Zeichen stehen die Memberfunktionen find(...) und rfind(...) zur Verfügung. Beide Memberfunktionen erhalten das zu suchende Zeichen als Parameter übergeben. find(...) sucht nach dem ersten Auftreten des Zeichens und rfind(...) nach dem letztem. Als Returnwert liefern die Memberfunktionen die Position des gesuchten Zeichens im String. Die Positionsangabe ist 0-basierend. Ist das Zeichen nicht im String vorhanden, so wird, wie bereits erwähnt, string::npos zurückgeliefert.

PgmHeader// Zu durchsuchender String
string path("c:/corba/doc/readme.doc");
// Suche nach erstem Slash
// string::size_type first = path.find('/');
// oder wieder einfacher

auto first = path.find('/');
// Suche nach letztem Slash
auto last = path.rfind('/');
// Suche nach nicht vorhandenem Zeichen
if (path.find('X') == string::npos)
   cout << "Zeichen X nicht gefunden";

Suchen von Teilstrings

Zum Suchen nach einem Teilstring werden ebenfalls die Memberfunktionen find(...) und rfind(...) verwendet. Beide Memberfunktionen erhalten jetzt den zu suchenden String entweder als string oder C-String (const char*) übergeben. find(...) sucht wieder nach dem ersten Auftreten der Zeichenfolge und rfind(...) nach dem letzten. Als Returnwert liefern die Memberfunktionen ebenfalls die Anfangsposition des gesuchten Strings im String.

PgmHeader// Zu durchsuchender String
string path("c:/corba/doc/readme.doc");
// Suche nach erstem Teilstring
auto first = path.find("doc");
// Suche nach Laufwerkstrenner
string toTest(":/");
auto last = path.rfind(toTest);

Suchen nach dem ersten (nicht) Auftreten eines Zeichens aus einer Zeichenmenge

Ein String kann auch auf das Vorkommen eines Zeichens aus einer definierten Zeichenmenge durchsucht werden. Hierzu dienen die Memberfunktionen find_first_of(...) und find_first_not_of(...). Beide Memberfunktionen erhalten als Parameter einen String übergeben, der die zu suchende Zeichenmenge enthält. find_first_of(...) liefert die Position zurück, an der ein beliebiges Zeichen aus der Zeichenmenge im String das erste Mal auftritt. find_first_not_of(...) hingegen liefert als Returnwert die Position, an der das erste nicht in der Zeichenmenge vorhandene Zeichen auftritt. Im Beispiel unten liefert find_first_of(..) die Position des ersten Kleinbuchstabens (hier 1) und find_first_not_of(...) die Position des ersten Zeichens, das kein Buchstabe und kein Leerzeichen ist (hier 11, Ziffer 2 in der Zahl 23).

PgmHeader// Zeichenmengen definieren
string  lowerChar("abcdefghijklmnopqrstuvwxyz");
string  upperChar("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
string  anyChar(lowerChar+upperChar+' ');
// Zu durchsuchender String
string street("Lerchenweg 23");
// Suchen nach dem ersten Kleinbuchstaben
auto pos = street.find_first_of(lowerChar);
// Suche nach dem ersten Nicht-Buchstaben
pos = street.find_first_not_of(anyChar);

Suchen nach dem letzten (nicht) Auftreten eines Zeichens aus einer Zeichenmenge

Während die find_first_xx(...) Memberfunktionen nach dem ersten (nicht-) Auftreten eines Zeichens aus einer Zeichenmenge suchen, so suchen die Memberfunktionen find_last_of(...) bzw. find_last_not_of(...) nach dem letzten (nicht-) Auftreten eines Zeichens aus der Zeichenmenge. Im Beispiel liefert find_last_of(...) die Position 9 zurück, da 'g' der letzte Kleinbuchstabe im String ist. find_last_not_of(...) liefert die Position 12 zurück, da die Ziffer 3 (aus der Zahl 23) der letzte 'Nicht-Buchstabe' im String ist.

PgmHeader// Zeichenmengen definieren
string  lowerChar("abcdefghijklmnopqrstuvwxyz");
string  upperChar("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
string  anyChar(lowerChar+upperChar+' ');
// Zu durchsuchender String
string street("Lerchenweg 23");
// Suchen nach dem letzten Kleinbuchstaben
auto pos = street.find_last_of(lowerChar);
// Suche nach dem letzten Nicht-Buchstaben
pos = street.find_last_not_of(anyChar);

Damit wollen wir es an dieser Stelle mit den Suchfunktionen bewenden lassen. Alle hier vorgestellten Memberfunktionen haben, wie bereits erwähnt, noch weitere Formen, die es erlauben, zum Beispiel auch einen C-String als Suchkriterium bzw. als Zeichenmenge anzugeben.

Kopieren von strings und Teil-strings

Das Kopieren eines Strings in einen anderen String erfolgt wie üblich mit dem Zuweisungsoperator. Was wir uns an dieser Stelle aber näher ansehen werden ist, wie der Inhalt eines Strings in ein char-Feld umkopiert werden kann.

HinweisBenötigen Sie nur einen const char-Zeiger auf den Inhalt eines string-Objekts, so können Sie dafür die Memberfunktion c_str() verwenden. Sie ist für diesen Fall wesentlich effektiver als die gleich vorgestellte Memberfunktion, da hierbei kein Kopiervorgang stattfindet. Eine Veränderung des C-Strings ist dann aber nicht möglich.

Um den Inhalt eines Strings in ein char-Feld zu kopieren, ist die Memberfunktion copy(...) aufzurufen. copy(...) erhält als ersten Parameter einen Zeiger auf den Speicherbereich, in dem der String-Inhalt abgelegt werden soll und als zweiten Parameter die max. Anzahl der zu kopierenden Zeichen aus dem String. Als Returnwert liefert die Memberfunktion die tatsächliche Anzahl der kopierten Zeichen.

PgmHeader// String definieren
string street("Lerchenweg 23");
// char-Feld definieren
char buffer[80];
// String in char-Feld umkopieren
auto len = street.copy(buffer,sizeof(buffer));
// C-String mit 0 abschliessen!
buffer[len] = 0;
AchtungDie kopierte Zeichenfolge wird nicht mit einer '0' abgeschlossen. Wollen Sie den Inhalt des Feldes als C-String weiterverwenden, so müssen Sie explizit die '0' hinzufügen (siehe auch Beispiel oben). Außerdem müssen Sie selbst darauf achten, dass das char-Feld auch groß genug ist, um die Zeichen aus dem string-Objekt aufnehmen zu können.

Zusätzlich steht noch eine zweite copy(...) Memberfunktion zur Verfügung, die es gestattet, nur Teile eines Strings umzukopieren. Dazu erhält die Memberfunktion als dritten Parameter die Anfangsposition innerhalb des Strings, ab der kopiert werden soll.

Um aus einem String einen Teilstring zu extrahieren, wird die Memberfunktion substr(...) eingesetzt. Im Gegensatz zu copy(...) wird der extrahierte Teilstring aber nicht in einem char-Feld abgelegt sondern in einem weiteren string-Objekt, welches als Returnwert von substr(...) zurückgeliefert wird.

Wird substr() ohne Parameter aufgerufen, so wird der komplette String in einen neuen String umkopiert. Um nun einen Teilstring zu extrahieren, erhält substr(...) als ersten Parameter die Anfangsposition, ab der die Zeichen kopiert werden sollen, und als zweiten Parameter die Anzahl der zu kopierenden Zeichen (1. Aufruf von substr(...) im nachfolgenden Beispiel). Alternativ kann auch der zweite Parameter entfallen. In diesem Fall wird ab der Anfangsposition bis zum Ende alles extrahiert (2. Aufruf von substr(...)). Das Beispiel extrahiert aus dem String street zum Beispiel den Straßennamen in den String part1 und in den String part2 die Hausnummer. Leerzeichen zwischen dem Straßennamen und der Hausnummer werden dabei explizit übersprungen.

PgmHeader// Strings definieren
string street("Lerchenweg 23");
string lowerChar("abcdefghijklmnopqrstuvwxyz");
string upperChar("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
string anyChar(lowerChar+upperChar);
// Suche nach erstem 'Nicht-Buchstaben'
auto pos = street.find_first_not_of(anyChar);
// Strassennamen extrahieren
auto part1 = street.substr(0,pos);
// Hausnummer extrahieren
auto part2 = street.substr(pos+1);

Anhängen und Ersetzen in strings

Anhängen von Zeichen

Zum Anhängen von Zeichen an einen String stehen ebenfalls diverse Memberfunktionen zur Verfügung, von denen wir uns an dieser Stelle nur die beiden wichtigsten ansehen werden. Mehr dazu entnehmen Sie bitte der Online-Hilfe.

Soll ein einzelnes Zeichen an einen String angefügt werden, so kann dies mit Hilfe des Operators += erfolgen.

Um mehrere gleiche Zeichen an einen String anzuhängen, wird die Memberfunktion append(...) verwendet. Der erste Parameter gibt die Anzahl der anzufügenden Zeichen an und der zweite Parameter das Zeichen.

PgmHeaderstring myString("bla bla bla");
// Ein Ausrufezeichen anhängen
myString += '!';
// 5 Fragezeichen anhängen
myString.append(5,'?');

Anhängen von Strings

Zum Zusammenfügen von Strings kann u.a. der Operator += oder auch die Memberfunktion append(...) verwendet werden. Zusätzlich erlaubt append(...) auch einen Teilstring aus einem anderen String oder C-String anzuhängen. Dazu erhält append(...) dann im ersten Parameter den String, aus dem die Zeichen angefügt werden sollen, im zweiten Parameter die Startposition innerhalb dieses Strings und im dritten Parameter die Anzahl der anzuhängenden Zeichen.

PgmHeaderstring string1("eins zwei ");
string string2("drei vier fuenf");
// Komplette strings2 anfügen
string1 += string2;
// C-String anfügen
string1.append("...");
// Teilstring aus string2 anfügen
string1.append(string2,5,4);

Ersetzen von Zeichen

Um ein Zeichen oder eine Zeichenfolgen innerhalb eines Strings zu ersetzen, wird die Memberfunktion replace(...) eingesetzt. replace(...) kann ein oder mehrere Zeichen ab einer bestimmten Position durch ein oder mehrere andere Zeichen ersetzen. Die ersetzende Zeichenfolge kann dabei entweder als String oder als C-String angegeben werden. Im Beispiel hat der String gruss am Schluss den Inhalt 'Guten Morgen Frau Maier'.

PgmHeaderstring gruss("Hallo #");
string anrede("Frau Maier");
const char* morgen = "Guten Morgen";
// Nach Zeichen # suchen
auto pos = gruss.find('#');
// und durch den String anrede ersetzen
gruss.replace(pos,anrede.length(),anrede);
// 'Hallo' durch den C-String morgen ersetzen
gruss.replace(0,5,morgen,strlen(morgen));

Einfügen und Entfernen in strings

Einfügen

Zum Einfügen von Zeichenfolgen dient die Memberfunktion insert(...). Auch hier stehen wieder  verschiedene Formen dieser Memberfunktion zur Verfügung. Alle Memberfunktionen erhalten als ersten Parameter den Index, ab der das einzufügende Zeichen bzw. die einzufügende Zeichenfolge einzufügen ist. Im Beispiel wird das Wort drei zwischen den Wörtern zwei und vier eingefügt.

PgmHeader// String definieren
string s1("eins zwei vier");
// Position von 'vier' suchen
string::size_type pos = s1.find("vier");
// und davor 'drei ' einfügen
s1.insert(pos,"drei ");

Sehen wir uns zum Abschluss noch an, wie Zeichen bzw. Zeichenfolgen aus einem String entfernt werden können.

Entfernen

Um den kompletten Inhalt eines Strings zu löschen, wird die Memberfunktion clear() verwendet. Ob dabei auch der bisher durch den String belegten Speicher freigegeben wird ist implementierungsabhängig. Definiert ist einzig und allein, dass der String danach leer ist.

PgmHeaderstring s1("Guten Tag");
....
// String-Inhalt löschen
s1.clear();

Sollen nur Teile eines Strings entfernt werden, so ist hierfür die Memberfunktion erase(...) einzusetzen. Auch hiervon gibt es mehrere Formen. Allen gemeinsam ist, dass sie im ersten Parameter die Startposition erhalten, ab der Zeichen gelöscht werden sollen. Wird ein zweiter Parameter angegeben, so legt er die Anzahl der zu löschenden Zeichen fest. Im Beispiel wird das Wort wunderschoenen aus dem String s1 entfernt.

PgmHeaderstring s1("Einen wunderschoenen guten Morgen");
string toErase("wunderschoenen");
// Position des zu löschenden Strings suchen
auto pos1 = s1.find(toErase);
// String nun entfernen
s1.erase(pos1,toErase.length()+1);

Beenden wir damit die Übersicht über die Klasse string. Sie sollten sich aber auf jeden Fall auch einmal die Online-Hilfe zu dieser Klasse ansehen. Dort finden Sie die Beschreibungen der Memberfunktionen unter der Klasse basic_string. string selbst ist nur eine Spezialisierung von basic_string zur Verarbeitung von char Elementen.

Beispiel und ÜbungUnd hier geht's zum Beispiel und zur Übung.