Dieser und noch weitere Artikel wurde von CStoll erstellt.


Folgende Themen werden von diesem Artikel berührt:


Druckversion des Artikels


Vorbemerkungen

In C gibt es keinen eigenständigen Datentyp für Strings. Stattdessen werden char-Pointer als Beginn einer Zeichenkette interpretiert, die von der angegebenen Adresse bis zum nächsten Null-Element reichen. Dieses Vorgehen ist fehleranfällig, da die Speicherverwaltung komplett in den Händen des Anwenders liegt und von Seiten der Bibliothek nicht kontrolliert werden kann.

Im Gegensatz dazu kümmert sich die String-Klasse der C++-Bibliothek selbst um ihren Speicher. Sie wächst mit, wenn die Zeichenkette verlängert werden soll, und sie merkt sich auch selbst, wieviel Speicherplatz sie ohne Komplikationen nutzen darf.

Inhalt
  1. Basisklassen
  2. Grundfunktionen
  3. Suchfunktionen
  4. STL-Unterstützung
  5. Character Traits
  6. Hilfsdefinitionen
  7. Erweiterungen


1 Basisklassen

Header:
C++:
#include <string>

Definitionen
C++:
template<typename charT>
class char_traits;
 
template<typename charT, typename traitT = char_traits<charT>, class alloc = allocator<charT> >
class basic_string;
typedef basic_string<char>    string;
typedef basic_string<wchar_t> wstring;
Die Klasse char_traits definiert die nötigen Grundfunktionen zum Vergleichen, Zuweisen und Verarbeiten von Zeichen und Zeichenketten. Die Standard-Implementierung nutzt üblicherweise die C-Funktionen wie memcmp() und memcpy() für ihre Arbeit. Die meisten Funktionen des Strings nutzen ihre Methoden für die Arbeit. Weitere Informationen zu den Character Traits fasse ich in Kapitel 5 zusammen.

Die Klasse basic_string und ihre Spezialisierungen string (für char) bzw. wstring (für wchar_t) bieten die eigentliche Funktionalität eines Strings. Sie verwenden die Methoden der Traits-Klasse (beim Defaultwert letztendlich die Stringfunktionen der C-Bibliothek) für die Zeichenverarbeitung und die Methoden der Allocator-Klasse (die Defaultklasse basiert auf new[] und delete[]) für die Speicherverwaltung.

Anmerkung: Im Folgenden bezeichnet "String" eine beliebige Ausprägung des basic_string-Klassen-Templates, "Zeichen" den dazugehörigen Zeichentyp und "C-String" einen String in C-Semantik (nullterminierte Zeichenfolge).
Außerdem gelten alle Aussagen für std::string und char analog auch für wstring und wchar_t und jede andere Spezialisierung der String-Klasse.

2 Grundfunktionen

Alle wichtigen Grundfunktionen für den Umgang mit Zeichenketten wurden in die String-Klasse aufgenommen. An Stellen, die String-Eingaben benötigen, sind mehrere Parameterkombinationen möglich:
Folgende Parameterkombinationen sind dabei je nach verwendeter Methode erlaubt:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Methode            String    Teil-     C-String  char-     Zeichen   Zeichen-  Iterator-
                             string              Array               block     Bereich
 
constructor        ja        ja        ja        ja         -        ja        ja
 
operator =         ja         -        ja         -        ja         -         -
assign()           ja        ja        ja        ja         -        ja        ja
 
operator +=        ja         -        ja         -        ja         -         -
append()           ja        ja        ja        ja         -        ja        ja
push_back()         -         -         -         -        ja         -         -
 
insert() (Index)   ja        ja        ja        ja         -        ja*        -
insert() (Iter.)    -         -         -         -        ja        ja*       ja
 
replace() (Index)  ja        ja        ja        ja        ja        ja         -
replace() (Iter.)  ja         -        ja        ja         -        ja        ja
 
find() etc.        ja         -        ja        ja        ja         -         -
 
operator +         ja         -        ja         -        ja         -         -
 
Vergleiche         ja         -        ja         -         -         -         -
compare()          ja        ja        ja        ja         -         -         -
 
(*) eventuell mehrdeutig, siehe Kapitel 4


Anmerkung: Außer bei Übergabe eines C-Strings zählt das '\0'-Zeichen als ganz normaler Wert, kann also auch mitten in einem String vorkommen.

2.1 Konstruktion und Zuweisung von Strings

Die Konstruktoren für Strings unterstützen alle Kombinationen außer einem einzelnen Zeichen - dafür kann alternativ der (size_t,char)-Konstruktor verwendet werden, indem als Anzahl eine 1 übergeben wird:
C++:
string s('x');  //FEHLER
string s(1,'x');//OK, legt eine Kopie von 'x' an


Eine weitere Möglichkeit, einen String anzulegen, bietet die Methode substr(idx=0,len=MAX). Diese kopiert maximal len Zeichen (wenn der zweite Parameter fehlt: alle Zeichen bis zum Stringende) ab der Position idx in einen neuen String.

Zuweisungen zu einem bestehenden String können entweder mit dem Operator = (für Strings, C-Strings und Zeichen) oder der Methode assign() (für alle Kombinationen außer Einzelzeichen) durchgeführt werden.

2.2 Stringverkettungen

An einen vorhandenen String können mit dem Operator += (für Strings, C-Strings und Zeichen), der Methode append() (für alle Kombinationen außer Einzelzeichen) oder der Methode push_back() (für Zeichen) weitere Zeichen angehängt werden. Alle Funktionen kümmern sich selbst darum, dass der reservierte Speicherplatz des Strings mitwächst.

Außerdem können mit dem Operator + zwei bestehende Strings bzw. ein String mit einem C-String oder Einzelzeichen zusammengefügt werden.

Achtung: Aufgrund der Art, wie C++ seine Operatoren auswertet, ist es nicht möglich, zwei C-Strings auf diese Weise zusammenzufassen (C-Strings sind weiterhin char-Pointer und lassen sich nicht addieren). Um das Problem zu umgehen, reicht es aus, einen der beteiligten Strings in einen std::string umzuwandeln:
C++:
string str = "Hallo" + "Welt"; // Fehler
string str = string("Hallo")+"Welt"; //klappt
Anmerkung: Solange es sich um String-Literale handelt, kann man sich auch auf die Vorverarbeitung durch den Präprozessor verlassen, der nebeneinander stehende Literale zusammenfasst:
C++:
string str = "Hallo" "Welt";//klappt - aber nur mit Literalen


2.3 Einfügen, Löschen und Ersetzen von Zeichen

Strings bieten die Methode insert(), um in der Mitte ihrer Daten neue Elemente einzufügen. Die Zielposition kann dabei entweder als Index oder als Iterator angegeben werden, die einzufügenden Daten in der Indexversion in allen Kombinationen außer Einzelzeichen und Iteratorbereich, in der Iteratorversion als Einzelzeichen, Zeichenblock oder als Iteratorbereich.

Zum Löschen von Zeichen haben Strings die Methode erase(), die

als Parameter erhalten kann und die Methode clear(), die den kompletten String löscht.

Eine weitere Methode, Zeichen zu löschen, bietet resize() an - wenn die Stringlänge verkürzt wird, werden entsprechend viele Zeichen am Ende des Strings gelöscht.

Aus der Kombination von Löschen und Einfügen ergibt sich das Ersetzen von Teilstrings. Die Methode replace() bekommt die Position der zu ersetzenden Zeichen entweder als Index und Länge oder als Iteratorbereich und die einzufügenden Daten in allen Kombinationen außer Iteratorbereich (Indexversion) bzw. Teilstring und Einzelzeichen (Iteratorversion). Wenn sich beim Ersetzen die Stringlänge nicht ändern soll, können die Zeichen auch per Direktzugriff bzw. über Iteratoren überschrieben werden.

Um alle Vorkommen einer Zeichenkette durch denselben Wert zu ersetzen, müssen die Methoden find() und replace() in einer Schleife zusammengefasst werden:
C++:
1
2
3
4
5
6
7
8
9
void replace_all(string& text,const string& fnd,const string& rep)
{
  size_t pos = text.find(fnd);
  while(pos!=string::npos)
  {
    text.replace(pos,fnd.length(),rep);
    pos = text.find(fnd,pos+rep.length());
  }
}
Einzelne Zeichen können auch mit dem Algorithmus replace() oder replace_if() ersetzt werden.

2.4 Stringvergleich

Strings können miteinander verglichen werden und sortieren sich dabei nach lexikografischer Reihenfolge. Dazu werden sämtliche Vergleichsoperatoren überladen, um entweder zwei Strings oder einen String mit einem C-String vergleichen zu können.

Des Weiteren kann die Methode compare() (für Strings, Stringteile, C-Strings oder char-Arrays) verwendet werden, um einen kompletten String oder einen Teil des Strings (gegeben durch Startindex und Länge) mit anderen Werten zu vergleichen. Diese Methode gibt je nach Vergleichsergebnis einen negativen Wert (*this ist kleiner als der String), 0 (beide Strings sind gleich) oder einen positiven Wert (*this ist größer als der String) zurück.

2.5 Zeichenzugriff und C-String-Anbindung

Strings bieten mit dem Index-Operator [] und der Methode at() vollen Zugriff auf die einzelnen Zeichen, die sie verwalten. Der Unterschied zwischen beiden Versionen ist wie bei vector<> und deque<> die Fehlerkontrolle - operator[] gibt Datenmüll zurück, wenn der angegebene Index größer oder gleich der aktuellen Stringlänge ist, at() wirft eine out_of_range-Exception.

Anmerkung: Bei konstanten Strings ist length() ein legaler Parameter für operator[] - und gibt den Wert \0 zurück. Bei nichtkonstanten Strings und für die Methode at() liegt dieser Index außerhalb des zulässigen Bereiches und führt zu undefiniertem Verhalten bzw. einer Exception.

Um die Daten eines Strings als char-Array nutzen zu können, gibt es die Methoden data() und c_str() (beide liefern einen const char* auf die internen Daten, c_str() garantiert außerdem, daß der Bereich nullterminiert ist) sowie copy(buf,len,idx=0) (kopiert maximal len Zeichen ab Position idx in ein extern verwaltetes char-Array - ohne Nullterminator).

Um den Inhalt eines Strings als veränderbares char-Array verwenden zu können, sind etwas mehr Verrenkungen notwendig. Versuchen Sie deshalb nach Möglichkeit, sie zu vermeiden:
C++:
string str;
str.resize(100);
sprintf(&str[0],"Ich bin ein String: %i",4711);


Anmerkung: Im normalen Gebrauch ist nicht garantiert, dass die Zeichendaten des Strings mit '\0' abgeschlossen werden. Der String verwaltet seine Länge üblicherweise seperat und hängt das Schlußzeichen für C-Strings nur an, wenn es notwendig ist - z.B. beim Aufruf der Methode c_str().

2.6 Größe und Kapazität

Genau wie Vektoren verwalten Strings ihre Daten in einem zusammenhängenden Speicherbereich und können vorsorglich mehr Platz anfordern, als sie tatsächlich benötigen. Dadurch ergeben sich drei Größen, die für einen String gesetzt und abgefragt werden können.

Die physikalische Größe des Strings ist die Anzahl an gespeicherten Zeichen ohne eventuell vorhandenen Null-Terminator (Strings müssen ihre Daten nicht mit '\0' abschließen, außer zur Ausgabe durch c_str()). Die Größe kann über die Methoden size() und length() abgefragt (beide Methoden sind gleichwertig) und über resize(len,c) gesetzt werden - letzteres schneidet Zeichen am Ende ab bzw. füllt den String mit Kopien von c (Defaultwert ist '\0') auf, um die nötige Größe zu erreichen. Außerdem beeinflussen viele Stringmethoden, z.B. replace(), append() oder erase(), indirekt auch die Größe des Strings.

Die Kapazität des Strings ist die Zeichenzahl, die ohne Reallokation eingetragen werden kann. Sobald die Größe die Kapazität überschreitet, wird der gesamte Stringinhalt in einen neuen, größeren Block umkopiert und der alte Speicherblock freigegeben. Die Kapazität kann über die Methode capacity() abgefragt und über die Methode reserve(size) gesetzt werden. Im Gegensatz zu Vektoren, deren Kapazität NIE verkleinert wird, darf ein String auch in einen kleineren Speicherblock umziehen (allerdings nur auf Anforderung) - insbesondere bewirkt ein reserve() ohne Parameter eine Verkleinerung der Kapazität auf die aktuelle Größe.

Anmerkung: Es ist im Standard nicht garantiert, dass der String seine Kapazität verringern muss - er darf eine entsprechende Anforderung auch ignorieren. Umgekehrt ist es jedoch nicht erlaubt, dass der String aus eigenem Antrieb die Kapazität verkleinert. Der einzige Weg, garantiert die Kapazität eines Strings zu veringern, ist die Verwendung des sogenannten "swap-Tricks":
C++:
void shrink(string& str)
{
  string nstr(str.begin(),str.end());
  str.swap(nstr);
}


Die Maximalgröße eines Strings ist die maximale Anzahl an Zeichen, die theoretisch in einem String untergebracht werden können. Jeder Versuch, dieses Limit zu überschreiten, verursacht eine length_error-Exception. Die Maximalgröße ist eine implementierungsspezifische Konstante und kann zur Laufzeit über die Methode max_size() abgefragt werden. Die einzige Möglichkeit, diesen Wert zu beeinflussen, ist der Einsatz eines anderen Allokators. Aber normalerweise ist der Grenzwert groß genug, um ihn im laufenden Betrieb nicht zu überschreiten.

3 Suchfunktionen

Suchfunktionen dienen dazu, bestimmte Zeichenfolgen oder -kombinationen in einem String zu finden. Alle Methoden, die ein String dazu anbietet, können als Suchwert Strings, C-Strings, char-Arrays oder Einzelzeichen entgegennehmen und erhalten einen optionalen Parameter, der den Startpunkt der Suche angibt (Defaultwert ist 0=Stringanfang):
Alle diese Funktionen geben den Index des gefundenen Zeichens zurück, bzw. die Konstante string::npos (siehe Kapitel 6.2), wenn sie nichts gefunden haben.

4 STL-Unterstützung

Die Stringklassen sind zwar unabhängig von der Standard Template Library entwickelt worden, können aber problemlos mit der STL zusammenarbeiten. Zu diesem Zweck bieten sie neben den stringspezifischen Methoden ein zu dem des Vektors vergleichbaren Interface an (das ist auch der Grund, warum die Methoden size() und length() nebeneinander existieren) - inklusive der Methoden insert() und push_back(), die den Einsatz von Insert-Iteratoren mit Strings ermöglichen.

Außerdem hat die String-Klasse etliche Methoden, die mit Iteratoren arbeiten können:

String-Iteratoren bieten Random Access auf die verwalteten Zeichendaten. Auch wenn der C++-Standard sich nicht um Implementierungsdetails kümmert, dürften meistens char-Pointer dafür verwendet werden.

5 Character Traits

Die Character Traits einer String-Klasse bestimmen, wie diese mit ihren Zeichenwerten umgeht. Auf diese Weise lässt sich das Verhalten eines Strings anpassen, ohne in die Implementation des Zeichentyps eingreifen zu müssen (die Zeichenklassen liegen als eingebaute Typen außerhalb der Reichweite des Programmierers). Die Traits-Klasse besteht aus einer Sammlung von Typdefinitionen und statischen Methoden und operiert auf blanken char-Feldern. Die meisten ihrer Methoden entsprechen einer Funktion der C-Stringverarbeitung (bzw. greifen in der Standardversion sogar auf diese zurück).
Die Character Traits werden auch von den IO-Streams verwendet, deshalb enthalten sie auch Elemente, die für Strings eigentlich überflüssig sind (z.B. pos_type oder eof()).

Indem eine eigene Traits-Klasse bereitgestellt wird, kann das Verhalten von Strings angepasst werden. Z.B. ist es möglich, den Vergleich von Zeichen(folgen) unabhängig von Groß- und Kleinschreibung zu implementieren:
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct nocase_traits : public std::char_traits<char>
{
  static bool eq(const char& c1,const char& c2)
  { return toupper(c1)==toupper(c2); }
  static bool lt(const char& c1,const char& c2)
  { return toupper(c1)<toupper(c2); }
 
  static int compare(const char* s1, const char* s2, size_t n)
  {
    for(size_t p=0;p<n;++p)
      if(!eq(s1[p],s2[p])) return lt(s1[p],s2[p])?-1:1;
    return 0;
  }
 
  static const char* find(const char* s, size_t n, const char& c)
  {
    for(size_t p=0;p<nn;++p)
      if(eq(s[p],c)) return s+p;
    return 0;
  }
};
 
typedef std::basic_string<char,nocase_traits> ncstring;
 
//Ein-/Ausgabe von ncstring's
inline ostream& operator<< (ostream& strm,const ncstring& s)
{ return strm << string(s.data(),s.length()); }
 
inline istream& operator>> (istream& strm,ncstring& s)
{
  string s2;strm>>s2;
  if(strm) s.assign(s2.data(),s2.length());
  return strm;
}
inline istream& getline(istream& strm,ncstring& s,char delim='\n')
{
  string s2;getline(strm,s2,delim);
  if(strm) s.assign(s2.data(),s2.length());
  return strm;
}
Anmerkung: Ein- und Ausgabeoperatoren sind nur definiert, wenn der Zeichentyp und Traits-Typ von String und IO-Stream übereinstimmt. Deshalb müssen sie gesondert überladen werden, wenn Spezialstrings mit den "normalen" Streamklassen zusammenarbeiten sollen.

6 Hilfsdefinitionen

Strings stellen einige zusätzliche Definitionen zur Verfügung, die im Allgemeinen nicht benötigt werden. Für den professionellen Einsatz kann es jedoch ganz nützlich sein, diese Definitionen zu kennen.

6.1 Hilfstypen

Wie die STL-Container definieren auch Strings ein ganzes Sortiment an Hilfstypen, die besonders für die Arbeit mit generischen Stringtypen (z.B. in Template-Funktionen) verwendet werden können:

Anmerkung: Viele der Typdefinitionen werden aus der Allokator-Klasse übernommen.

6.2 npos

"string::npos" ist ein spezieller Wert vom Typ size_type. Dabei handelt es sich üblicherweise um (size_type)-1. Er wird von den String-Methoden für zwei Verwendungszwecke verwendet:

Erstens dient er als Defaultwert für die Längenangabe bei Teilstring-Funktionen (z.B. erase(), replace() oder substr()) und definiert, daß der betrachtete Abschnitt bis zum Ende des Strings reichen soll.

Zweitens wird npos von find() und seinen Varianten zurückgegeben, wenn der Suchstring nicht gefunden wurde. Beachten Sie, dass es für die Auswertung nicht sicher ist, die Ausgaben der Suchfunktionen in einen vorzeichenbehafteten Typ zu casten.
C++:
1
2
3
4
5
6
7
8
9
10
11
string s;
 
int p = s.find("sub");
if(p == string::npos) //unsicher wg. Typanpassungen
  ...
if(p == -1) //unsicher, klappt aber normalerweise
  ...
 
string::size_type p = s.find("sub")
if(p == string::npos) //einzige portable Version
  ...


6.3 Allokator-Unterstützung

Genau wie die Containerklassen der STL können Strings für verschiedene Methoden zur Speicherverwaltung eingerichtet werden. Dazu wird als dritter Template-Parameter dem Klassentemplate basic_string die verwendete Allokator-Klasse übergeben. Diese wird genutzt, um Speicher anfordern, initialisieren und freigeben zu können.

Außerdem bieten Strings die Typdefinition allocator_type, die die verwendete Allokator-Klasse enthält, die Memberfunktion get_allocator(), die den Allokator des Strings zurückgibt, und einen optionalen Parameter für die meisten String-Konstruktoren (nur der Copy Constructor übernimmt den Allocator vom kopierten String).

7 Erweiterungen

Auch wenn Strings ein weites Gebiet der Zeichenverarbeitung abdecken, können sie noch lange nicht alles. Zum Beispiel bietet die Standard-Bibliothek keine direkte Unterstützung für reguläre Ausdrücke oder Textverarbeitung.

Allerdings lassen sich die fehlenden Funktionen oft mit einfachen Mitteln selbst programmieren. Aufgaben der Textverarbeitung (z.B. einen String in Großbuchstaben zu übersetzen oder alle Vorkommen eines Wertes zu ersetzen) lassen sich mit Hilfe der STL-Algorithmen oder mit einer Schleifenstruktur wie in Kapitel 2.3 durchführen.

Etwas aufwendiger ist es, den Einsatz von regulären Ausdrücken zu implementieren. Aber dafür gibt es eine ganze Reihe an Spezialbibliotheken, zum Beispiel Boost::Regex oder - als letzte Option - Boost::Spirit. Auch in die TR1 wurde die Verarbeitung von regulären Ausdrücken aufgenommen.

Sie können Kommentare zu diesem Artikel im Forum schreiben. (Eine Registrierung ist nicht notwendig.)

Logo-Design: MastaMind Webdesign