[X] Stringverarbeitung in C++



  • Auf zu neuen Ufern - auch wenn mich bestimmt einige Kollegen als schreibwütig einstufen werden 😃

    ---
    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 selber um ihren Speicher. Sie wächst mit, wenn sie verlängert werden soll, und sie merkt sich auch selber, wieviel Speicherplatz sie ohne Komplikationen nutzen darf.

    Inhalt

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

    1 Basisklassen

    Header:

    #include <string
    

    Definitionen

    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 seine Methoden für die Arbeit. Weitere Informationen zu den Zeichen-"Traits" fasse ich in Kapitel 5 zusammen.

    Die Klasse basic_string und ihre Spezialisierungen string (für char's) bzw. wstring (für wchar_t's) bieten die eigentliche Funktionalität eines Strings. Sie verwenden die Methoden der Traits-Klasse (beim Defaultwert letzendlich 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 der basic_string<>-Klasse, "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+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:

    • const string& str - String
      Kopie eines anderen Strings
    • const string& str,size_t idx,size_t len - Teilstring
      Teilstring aus einem anderen String ('len' Zeichen ab Position 'idx')
    • const char cstr* - C-String
      Kopie eines C-Strings (nullterminierte Zeichenfolge)
    • const char cstr,size_t len* - char-Array
      Kopie eines char-Arrays ('len' Zeichen, \0 ist normaler Wert)
    • char c - Einzelzeichen
      ein einzelnes Zeichen
    • size_t len,char c - Zeichenblock
      'len' Kopien des Zeichens c
    • Iterator beg,Iterator end - Bereich
      Kopie aus dem Iterator-Bereich [beg,end[

    Folgende Parameterkombinationen sind dabei je nach verwendeter Methode erlaubt:

    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:

    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 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 selber darum, daß 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:

    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 Ververarbeitung durch den Präprozessor verlassen, der nebeneinanderstehende Literale zusammenfasst:

    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 Index-Version in allen Kombinationen außer Einzelzeichen und Iteratorbereich, in der Iterator-Version als Einzelzeichen, Zeichenblock oder als Iteratorbereich.

    Zum Löschen von Zeichen haben Strings die Methode erase(), die entweder einen Indexwert (löscht alle Zeichen hinter dieser Position), Indexwert und Länge (löscht entsprechend viele Zeichen ab dem Index), einen Iterator (löscht das Zeichen an der gegebenen Position) oder zwei Iteratoren (löscht den Bereich dazwischen) 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.

    Als Kombination aus 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 (Index-Version) bzw. in allen Kombinationen außer Teilstring und Einzelzeichen (Iterator-Version). Wenn sich beim Ersetzen die Stringlänge nicht ändern soll, können die Zeichen auch im Direktzugriff bzw. über Iteratoren überschrieben werden.

    Um alle Vorkommen einer Zeichenkette durch den selben Wert zu ersetzen, müssen die Methoden find() und replace() in einer Schleife zusammengefasst werden:

    void replace_all(string& text,const string& fnd,const string& rep)
    {
      size_t pos = text.find(fnd);
      while(pos!=string::npos)
      {
        text.replace(pos,pos+fnd.length(),rep);
        pos = text.find(fnd,pos);
      }
    }
    

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

    Außerdem 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:

    string str;
    str.resize(100);
    sprintf(&str[0],"Ich bin ein String: %i",4711);
    

    Anmerkung: Im normalen Gebrauch ist nicht garantiert, daß 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. bei 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"werte, 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 über 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 String.

    Die Kapazität des Strings ist die Zeichenzahl, die ohne Reallokation eingetragen werden kann. Sobald die Größe die Kapazität überschreiten würde, 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 afaik nicht garantiert, daß der String seine Kapazität verringern muss - er darf eine entsprechende Anforderung auch ignorieren. Umgekehrt ist es jedoch nicht erlaubt, daß 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":

    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 implementationsspezifische 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 entweder Strings, C-String, char-Arrays oder Einzelzeichen entgegennehmen und erhalten einen optionalen Parameter, der den Startpunkt der Suche angibt (Defaultwert ist 0=Stringanfang):

    • find(...)
      sucht die übergebene Zeichenkette als Teilstring
      (entspricht dem Algorithmus find() (für Einzelzeichen) bzw. search() (für Teilstrings))
    • rfind(...)
      sucht die Zeichenkette als Teilstring (von hinten beginnend)
      entspricht dem Algorithmus find() mit Reverse-Iteratoren (für Einzelzeichen) bzw. find_end() (für Teilstrings))
    • find_first_of(...)[/b]
      sucht das erste Zeichen des Strings, das in der Zeichenkette genannt wird
      (entspricht dem Algorithmus find_first_of())
    • find_first_not_of(...)
      sucht das erste Zeichen des Strings, das in der Zeichenkette nicht genannt wird
    • find_last_of(...)
      sucht das letzte Zeichen des Strings, das in der Zeichenkette genannt wird
      (entspricht dem Algorithmus find_first_of() mit Reverse-Iteratoren)
    • find_last_not_of(...)
      sucht das letzte Zeichen des Strings, das in der Zeichenkette nicht genannt wird

    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, aber können problemlos mit der STL zusammenarbeiten. Zu diesem Zweck bieten sie neben den stringspezifischen Methoden ein vergleichbares Interface zu Vektoren (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:

    • begin(), end(), rbegin() und rend()
      liefern normale bzw. reverse Iteratoren auf den Anfang bzw. das Ende der Zeichenfolge
    • string(beg,end)
      konstruiert einen String aus dem Bereich [beg,end[
    • assign(beg,end) und append(beg,end)
      nehmen den Bereich [beg,end[ und weisen ihn dem String zu bzw. hängen ihn an
    • insert(pos,c), insert(pos,num,c) und insert(pos,beg,end)
      fügen an der Position 'pos' ein Zeichen c, num Kopien von c bzw. den Bereich [beg,end[ ein
      Achtung: der Aufruf "s.insert(0,n,'x');" ist mehrdeutig - 0 könnte entweder die Indexposition 0 oder den NULL-Zeiger (char* als String-Iterator) darstellen. Zur Auflösung dieser Mehrdeutigkeit muß der Wert richtig gecastet werden - oder man wechselt zur Iterator-Version der Methode:
    //korrekte Typangabe:
    str.insert((string::size_type)0,n,'x');
    //Iterator verwenden:
    str.insert(str.begin(),n,'x');
    
    • erase(pos) und erase(beg,end)
      löscht das Zeichen an Position 'pos' bzw. den Bereich [beg,end[
    • replace(beg,end,str), replace(beg,end,cstr), replace(beg,end,carr,len), replace(beg,end,num,c und replace(beg,end,nbeg,nend)
      ersetzen den Bereich [beg,end[ durch den String str, den C-String cstr, das char-Array carr aus len Zeichen, num Kopien von c bzw. den Bereich [nbeg,nend[

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

    5 Charakter-Traits

    Die Charakter-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 von char's eingreifen zu müssen (die Zeichenklassen liegen als eingebaute Typen außerhalb der Reichweite des Programmierers). Die Traits-Klasse besteht aus einer Sammlung an 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).

    • char_type
      verwendeter Zeichentyp (char bzw. wchar_t)
    • int_type
      Hilfstyp für Fehlerausgaben (enthält mindestens einen Wert mehr als 'char_type', der für end-of-file steht) (int bzw. wint_t)
    • pos_type und off_type
      Hilfstypen für Positions- bzw. Offsetangaben in IO-Streams
    • state_type
      Hilfstyp für den Übersetzungsstatus in Multibyte-Streams
    • **assign(tgt,src)
      Zeichenzuweisung (tgt=src)
      **
    • [b]eq(c1,c2) und lt(c1,c2)
      Zeichenvergleich (c1==c2 bzw. c1<c2)
    • length(str)
      Stringlänge (strlen(str))
    • compare(s1,s2,n)
      Stringvergleich (memcmp(s1,s2,n))
    • copy(s1,s2,n) und move(s1,s2,n)
      Stringkopie (memcpy(s1,s2,n) bzw. memmove(s1,s2,n))
    • assign(str,n,c)
      Stringzuweisung (n mal Zeichen c) (memset(str,c,n))
    • find(str,n,c)
      Zeichensuche im String (memchr(str,c,n))
    • eof()
      der Wert für End-of-File (EOF)
    • to_int_type(c) und to_char_type(i)
      Konvertierung zwischen int_type und char_type Darstellung ("to_char_type(eof())" ist undefniert)
    • not_eof(i)
      gibt alles außer eof() zurück (char's werden 1:1 übergeben, "not_eof(eof())" ist implementationsspezifisch)
    • eq_int_type(i1,i2)
      Vergleich im int_type (i1==i2)

    Die Charakter-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:

    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,c)) return s+i;
        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 wollen.

    6 Hilfsdefinitionen

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

    [i]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:

    • value_type (charT)
      der verwaltete Zeichentyp
    • traits_type (traitT)
      der Typ der verwendeten Zeichen-Traits Klasse
    • size_type (size_t) und difference_type (ptrdiff_t)
      Zahlentypen für Indexangaben bzw. Differenzwerte
    • reference (charT&) und const_reference (const charT&)
      veränderbare bzw. konstante Referenzen auf einzelne Zeichen
    • pointer (charT*) und const_pointer (const charT*)
      veränderbare bzw. konstante Zeiger auf einzelne Zeichen
    • iterator, const_iterator, reverse_iterator und const_reverse_iterator
      Iteratoren und Reverse-Iteratoren auf String-Elemente (typischerweise als Pointer implementiert)
    • allocator_type (alloc)
      der Typ der verwendeten Allokator-Klasse

    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 Variationen zurückgegeben, wenn der Suchstring nicht gefunden wurde. Beachten Sie, daß es für die Auswertung nicht sicher ist, die Ausgaben der Suchfunktionen in einen vorzeichenbehafteten Typ zu casten.

    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 der Klasse 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 selber 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.



  • So, der erste Entwurf des Artikels steht. Würde sich den mal bitte jemand durchlesen und nach inhaltlichen Unstimmigkeiten suchen? (Danke im voraus)



  • Zu 2.6:

    Die Kapazität wird normalerweise nicht durch reserve verkleinert: Zumindest bleibt der GCC- und Borland-C++-Kompiler davon unbeeindruckt.
    Lediglich der Visual C++-Compiler reduziert die Kapazität.

    Die Kapatzität eines strings kann nur mit dem swap-Trick portabel reduziert werden.

    string & reduce_capacity(string & str)
    {
      string reducer(str.begin(),str.end());
      str.swap(reducer);
      return str;
    }
    


  • Deswegen auch die Anmerkung im nächsten Satz - der String darf die Kapazität verkleinern, muß aber nicht (Josuttis spricht von "nonbinding request to shrink").



  • Na gut, wenn keine inhaltlichen Ergänzungen mehr kommen, gebe ich den Artikel jetzt zur Endkorrektur frei (Rechtschreibung, Grammatik, evt Ausdruck).



  • Bitte keine zwei Kürzel im Titel... ich war mal so frei und hab das [F] rausgeschmissen. 🙂



  • Sorry für das, hatte wohl etwas zu eilig den Titel angepasst.

    Aber ich wäre trotzdem dankbar, wenn irgendjemand sich mal zu den grammatischen Schwächen meines Artikels äußern würde (und wenn's nur ein "konnte keine Fehler finden" ist). DANKE



  • Ja, sind unsere Rechtschreiber im Urlaub? 😞



  • Ich erbarme mich mal und checke die Prosa (aber auch nur die ;)).

    CStoll schrieb:

    ...
    Die Standard-Implementierung nutzt üblicherweise die C-Funktionen wie memcmp() und memcpy**()** für ihre Arbeit. Die meisten Funktionen des Strings nutzen seine Methoden für die Arbeit.
    ...

    CStoll schrieb:

    ...
    Die Klasse basic_string und ihre Spezialisierungen string (für char**'s**) bzw. wstring (für wchar_t**'s**) 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.
    ...

    Ich würde die 's einfach weglassen 😉

    CStoll schrieb:

    ...
    Anmerkung: Im Folgenden bezeichnet "String" eine beliebige Ausprägung der basic_string<>-Klasse, "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**+**wchar_t und jede andere Spezialisierung der String-Klasse.

    Entweder "und" oder "+".

    CStoll schrieb:

    Anmerkung: Solange es sich um String-Literale handelt, kann man sich auch auf die Ververarbeitung durch den Präprozessor verlassen, der nebeneinanderstehende Literale zusammenfasst:
    ...

    CStoll schrieb:

    ...
    Kombinationen außer Einzelzeichen und Iteratorbereich, in der Iterator-Version als Einzelzeichen, Zeichenblock oder als Iteratorbereich.
    ...

    Entweder beides zusammen, oder beides mit Bindestrich.

    CStoll schrieb:

    ...
    Anmerkung: Es ist afaik nicht garantiert, daß der String seine Kapazität verringern muss - er darf eine entsprechende Anforderung auch ignorieren. Umgekehrt ist es jedoch nicht erlaubt, daß der String aus eigenem Antrieb die Kapazität verkleinert. Der einzige Weg, garantiert die Kapazität eines Strings zu verringern, ist die Verwendung des sogenannten "swap-Tricks":
    ...

    Also "afaik" in einem Artikel? tststs 🙂

    CStoll schrieb:

    ...
    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 implementationsspezifische Konstante und kann zur Laufzeit über die Methode max_size() abgefragt werden.
    ...

    implementierungsspezifische

    CStoll schrieb:

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

    Implementierungsdetails

    CStoll schrieb:

    ...
    Die Charakter-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 von char**'s** eingreifen zu müssen (die Zeichenklassen liegen als eingebaute Typen außerhalb der Reichweite des Programmierers).
    ...

    Das 's stört mich immernoch 😉

    Ich hab es ehrlich gesagt nur grob überflogen. Man möge mir Nichtgefundenes nachsehen.



  • estartu schrieb:

    Ja, sind unsere Rechtschreiber im Urlaub?

    Sorry, aber ich schaffs bis zum Mittwoch einfach nicht mehr... 😞
    Vielleicht schaut es sich ja Mr. B noch an, ansonsten können wir den Artikel entweder erst nächstes Mal oder nur "grob-korrigiert" veröffentlichen. Da CStoll bei seinen bisherigen Artikeln nur wenige Fehler gemacht hat, wäre letzteres aber auch kein Weltuntergang ;).



  • Gut, dann versuch ich mich nochmal und wir hoffen, dass es dann eben die Menge macht. 😉

    Iterator beg,Iterator end - Bereich
    Kopie aus dem Iterator-Bereich [beg,end***]***

    Vor Kapitel 3 fehlt die Leerzeile.

    find_first_of(...)[/b]
    

    Anfangstag fehlt

    Die Aufzählung in Kapitel 5 ist auch etwas zerschossen.

    Bei 6.1 fehlt das [/i]

    Sonst hab ich nix gefunden - was aber nichts heißen will. 😉

    Allerdings hätte ich hier mind. mit mehr Zeilenumbrüchen gearbeitet, das ist doch relativ unübersichtlich:

    Zum Löschen von Zeichen haben Strings die Methode erase(), die entweder einen Indexwert (löscht alle Zeichen hinter dieser Position), Indexwert und Länge (löscht entsprechend viele Zeichen ab dem Index), einen Iterator (löscht das Zeichen an der gegebenen Position) oder zwei Iteratoren (löscht den Bereich dazwischen) 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.

    Vorschlag:

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

    • einen Indexwert (löscht alle Zeichen hinter dieser Position),
    • Indexwert und Länge (löscht entsprechend viele Zeichen ab dem Index),
    • einen Iterator (löscht das Zeichen an der gegebenen Position) oder
    • zwei Iteratoren (löscht den Bereich dazwischen)

    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.

    🙂



  • Ich komm tatsächlich ausm Urlaub 😃 😮 Ich versuch, den Artikel heut ganz durchzubekommen.



  • 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 selber um ihren Speicher. Sie wächst mit, wenn sie verlängert werden soll, und sie merkt sich auch selber, wieviel Speicherplatz sie ohne Komplikationen nutzen darf.

    Inhalt

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

    1 Basisklassen

    Header:

    #include <string
    

    Definitionen

    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 seine Methoden für die Arbeit. Weitere Informationen zu den Zeichen-"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 der basic_string<>-Klasse, "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:

    • const string& str - String
      Kopie eines anderen Strings
    • const string& str,size_t idx,size_t len - Teilstring
      Teilstring aus einem anderen String ('len' Zeichen ab Position 'idx')
    • const char cstr* - C-String
      Kopie eines C-Strings (nullterminierte Zeichenfolge)
    • const char cstr,size_t len* - char-Array
      Kopie eines char-Arrays ('len' Zeichen, \0 ist normaler Wert)
    • char c - Einzelzeichen
      ein einzelnes Zeichen
    • size_t len,char c - Zeichenblock
      'len' Kopien des Zeichens c
    • Iterator beg,Iterator end - Bereich
      Kopie aus dem Iterator-Bereich [beg,end[

    Folgende Parameterkombinationen sind dabei je nach verwendeter Methode erlaubt:

    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:

    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 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 selber darum, daß 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:

    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 nebeneinanderstehende Literale zusammenfasst:

    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 entweder

    • einen Indexwert (löscht alle Zeichen hinter dieser Position),
    • Indexwert und Länge (löscht entsprechend viele Zeichen ab dem Index),
    • einen Iterator (löscht das Zeichen an der gegebenen Position) oder
    • zwei Iteratoren (löscht den Bereich dazwischen)

    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.

    Als Kombination aus 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. in allen Kombinationen außer Teilstring und Einzelzeichen (Iteratorversion). Wenn sich beim Ersetzen die Stringlänge nicht ändern soll, können die Zeichen auch im Direktzugriff bzw. über Iteratoren überschrieben werden.

    Um alle Vorkommen einer Zeichenkette durch den selben Wert zu ersetzen, müssen die Methoden find() und replace() in einer Schleife zusammengefasst werden:

    void replace_all(string& text,const string& fnd,const string& rep)
    {
      size_t pos = text.find(fnd);
      while(pos!=string::npos)
      {
        text.replace(pos,pos+fnd.length(),rep);
        pos = text.find(fnd,pos);
      }
    }
    

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

    Außerdem 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:

    string str;
    str.resize(100);
    sprintf(&str[0],"Ich bin ein String: %i",4711);
    

    Anmerkung: Im normalen Gebrauch ist nicht garantiert, daß 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. bei 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"werte, 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 über 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 String.

    Die Kapazität des Strings ist die Zeichenzahl, die ohne Reallokation eingetragen werden kann. Sobald die Größe die Kapazität überschreiten würde, 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, daß der String seine Kapazität verringern muss - er darf eine entsprechende Anforderung auch ignorieren. Umgekehrt ist es jedoch nicht erlaubt, daß 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":

    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 entweder Strings, C-String, char-Arrays oder Einzelzeichen entgegennehmen und erhalten einen optionalen Parameter, der den Startpunkt der Suche angibt (Defaultwert ist 0=Stringanfang):

    • find(...)
      sucht die übergebene Zeichenkette als Teilstring
      (entspricht dem Algorithmus find() (für Einzelzeichen) bzw. search() (für Teilstrings))
    • rfind(...)
      sucht die Zeichenkette als Teilstring (von hinten beginnend)
      entspricht dem Algorithmus find() mit Reverse-Iteratoren (für Einzelzeichen) bzw. find_end() (für Teilstrings))
    • find_first_of(...)
      sucht das erste Zeichen des Strings, das in der Zeichenkette genannt wird
      (entspricht dem Algorithmus find_first_of())
    • find_first_not_of(...)
      sucht das erste Zeichen des Strings, das in der Zeichenkette nicht genannt wird
    • find_last_of(...)
      sucht das letzte Zeichen des Strings, das in der Zeichenkette genannt wird
      (entspricht dem Algorithmus find_first_of() mit Reverse-Iteratoren)
    • find_last_not_of(...)
      sucht das letzte Zeichen des Strings, das in der Zeichenkette nicht genannt wird

    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, aber können problemlos mit der STL zusammenarbeiten. Zu diesem Zweck bieten sie neben den stringspezifischen Methoden ein vergleichbares Interface zu Vektoren (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:

    • begin(), end(), rbegin() und rend()
      liefern normale bzw. reverse Iteratoren auf den Anfang bzw. das Ende der Zeichenfolge
    • string(beg,end)
      konstruiert einen String aus dem Bereich [beg,end[
    • assign(beg,end) und append(beg,end)
      nehmen den Bereich [beg,end[ und weisen ihn dem String zu bzw. hängen ihn an
    • insert(pos,c), insert(pos,num,c) und insert(pos,beg,end)
      fügen an der Position 'pos' ein Zeichen c, num Kopien von c bzw. den Bereich [beg,end[ ein
      Achtung: der Aufruf "s.insert(0,n,'x');" ist mehrdeutig - 0 könnte entweder die Indexposition 0 oder den NULL-Zeiger (char* als String-Iterator) darstellen. Zur Auflösung dieser Mehrdeutigkeit muß der Wert richtig gecastet werden - oder man wechselt zur Iteratorversion der Methode:
    //korrekte Typangabe:
    str.insert((string::size_type)0,n,'x');
    //Iterator verwenden:
    str.insert(str.begin(),n,'x');
    
    • erase(pos) und erase(beg,end)
      löscht das Zeichen an Position 'pos' bzw. den Bereich [beg,end[
    • replace(beg,end,str), replace(beg,end,cstr), replace(beg,end,carr,len), replace(beg,end,num,c und replace(beg,end,nbeg,nend)
      ersetzen den Bereich [beg,end[ durch den String str, den C-String cstr, das char-Array carr aus len Zeichen, num Kopien von c bzw. den Bereich [nbeg,nend[

    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 Charakter-Traits

    Die Charakter-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 an 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).

    • char_type
      verwendeter Zeichentyp (char bzw. wchar_t)
    • int_type
      Hilfstyp für Fehlerausgaben (enthält mindestens einen Wert mehr als 'char_type', der für end-of-file steht) (int bzw. wint_t)
    • pos_type und off_type
      Hilfstypen für Positions- bzw. Offsetangaben in IO-Streams
    • state_type
      Hilfstyp für den Übersetzungsstatus in Multibyte-Streams
    • assign(tgt,src)
      Zeichenzuweisung (tgt=src)
    • eq(c1,c2) und lt(c1,c2)
      Zeichenvergleich (c1==c2 bzw. c1<c2)
    • length(str)
      Stringlänge (strlen(str))
    • compare(s1,s2,n)
      Stringvergleich (memcmp(s1,s2,n))
    • copy(s1,s2,n) und move(s1,s2,n)
      Stringkopie (memcpy(s1,s2,n) bzw. memmove(s1,s2,n))
    • assign(str,n,c)
      Stringzuweisung (n mal Zeichen c) (memset(str,c,n))
    • find(str,n,c)
      Zeichensuche im String (memchr(str,c,n))
    • eof()
      der Wert für End-of-File (EOF)
    • to_int_type(c) und to_char_type(i)
      Konvertierung zwischen int_type und char_type Darstellung ("to_char_type(eof())" ist undefniert)
    • not_eof(i)
      gibt alles außer eof() zurück (char's werden 1:1 übergeben, "not_eof(eof())" ist implementationsspezifisch)
    • eq_int_type(i1,i2)
      Vergleich im int_type (i1==i2)

    Die Charakter-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:

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

    6 Hilfsdefinitionen

    Strings stellen einige zusätzliche Definitionen zur Verfügung, die im Allgemeinen nicht benötigt werden. Für 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:

    • value_type (charT)
      der verwaltete Zeichentyp
    • traits_type (traitT)
      der Typ der verwendeten Zeichen-Traits Klasse
    • size_type (size_t) und difference_type (ptrdiff_t)
      Zahlentypen für Indexangaben bzw. Differenzwerte
    • reference (charT&) und const_reference (const charT&)
      veränderbare bzw. konstante Referenzen auf einzelne Zeichen
    • pointer (charT*) und const_pointer (const charT*)
      veränderbare bzw. konstante Zeiger auf einzelne Zeichen
    • iterator, const_iterator, reverse_iterator und const_reverse_iterator
      Iteratoren und Reverse-Iteratoren auf String-Elemente (typischerweise als Pointer implementiert)
    • allocator_type (alloc)
      der Typ der verwendeten Allokator-Klasse

    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 Variationen zurückgegeben, wenn der Suchstring nicht gefunden wurde. Beachten Sie, daß es für die Auswertung nicht sicher ist, die Ausgaben der Suchfunktionen in einen vorzeichenbehafteten Typ zu casten.

    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 der Klasse 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 selber 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.

    ----

    So, bis hierhin habe ich alles eingearbeitet.

    @estartu: das [beg,end[ ist Absicht - das ist ein einseitig offener Bereich (und die Schreibweise habe ich bislang durchgängig verwendet)



  • CStoll schrieb:

    @estartu: das [beg,end[ ist Absicht - das ist ein einseitig offener Bereich (und die Schreibweise habe ich bislang durchgängig verwendet)

    Ups, okay. 🙂



  • Tut mir leid, aber ich werd in den nächsten zwei, drei Monaten nicht mehr korrigieren können, weil bei mir plötzlich vieles anfängt: Ich komm inne Oberstufe, evilissimo hat mich gebeten, sein Buch von MS n bissl zu korrigieren, und ich bastel grad an einer größeren Homepage. (Zwischenzeitlich flieg ich noch inne USA)

    Von daher: Man sieht sich vllt. mal ab und zu und spätestens in zwei, drei Monaten solltet ihr auf jeden Fall wieder etwas von mir gehört haben.

    Mr. B



  • Okay, dann viel Erfolg. 🙂



  • 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:

    #include [kor]<string>[/kor]
    

    Definitionen

    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:

    • const string& str - String
      Kopie eines anderen Strings
    • const string& str,size_t idx,size_t len - Teilstring
      Teilstring aus einem anderen String (len Zeichen ab Position idx)
    • const char cstr* - C-String
      Kopie eines C-Strings (nullterminierte Zeichenfolge)
    • const char cstr,size_t len* - char-Array
      Kopie eines char-Arrays (len Zeichen, '\0' ist normaler Wert)
    • char c - Einzelzeichen
      ein einzelnes Zeichen
    • size_t len,char c - Zeichenblock
      len Kopien des Zeichens c
    • Iterator beg,Iterator end - Bereich
      Kopie aus dem Iterator-Bereich [beg,end[

    Folgende Parameterkombinationen sind dabei je nach verwendeter Methode erlaubt:

    Methode            String    Teil-     C-String  [kor]char-[/kor]     Zeichen   Zeichen-  Iterator-
                                 string              Array               block     Bereich
    
    [kor]constructor[/kor]        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() [kor]etc.[/kor]        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:

    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:

    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:

    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

    • einen Indexwert (löscht alle Zeichen hinter dieser Position)
    • einen Indexwert und die Länge (löscht entsprechend viele Zeichen ab dem Index)
    • einen Iterator (löscht das Zeichen an der gegebenen Position)
    • zwei Iteratoren (löscht den Bereich dazwischen)

    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:

    void replace_all(string& text,const string& fnd,const string& rep)
    {
      size_t pos = text.find(fnd);
      while(pos!=string::npos)
      {
        text.replace(pos,pos+fnd.length(),rep);
        pos = text.find(fnd,pos);
      }
    }
    

    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:

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

    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):

    • find()
      sucht die übergebene Zeichenkette als Teilstring
      (entspricht dem Algorithmus find() (für Einzelzeichen) bzw. search() (für Teilstrings))
    • rfind()
      sucht die Zeichenkette als Teilstring (von hinten beginnend)
      (entspricht dem Algorithmus find() mit Reverse-Iteratoren (für Einzelzeichen) bzw. find_end() (für Teilstrings))
    • find_first_of()
      sucht das erste Zeichen des Strings, das in der Zeichenkette genannt wird
      (entspricht dem Algorithmus find_first_of())
    • find_first_not_of()
      sucht das erste Zeichen des Strings, das in der Zeichenkette nicht genannt wird
    • find_last_of()
      sucht das letzte Zeichen des Strings, das in der Zeichenkette genannt wird
      (entspricht dem Algorithmus find_first_of() mit Reverse-Iteratoren)
    • find_last_not_of()
      sucht das letzte Zeichen des Strings, das in der Zeichenkette nicht genannt wird

    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:

    • begin(), end(), rbegin() und rend()
      liefern normale bzw. reverse Iteratoren auf den Anfang bzw. das Ende der Zeichenfolge
    • string(beg,end)
      konstruiert einen String aus dem Bereich [beg,end[
    • assign(beg,end) und append(beg,end)
      nehmen den Bereich [beg,end[ und weisen ihn dem String zu bzw. hängen ihn an
    • insert(pos,c), insert(pos,num,c) und insert(pos,beg,end)
      fügen an der Position pos ein Zeichen c, num Kopien von c bzw. den Bereich [beg,end[ ein
      Achtung: der Aufruf "s.insert(0,n,'x');" ist mehrdeutig - 0 könnte entweder die Indexposition 0 oder den NULL-Zeiger (char* als String-Iterator) darstellen. Zur Auflösung dieser Mehrdeutigkeit muss der Wert richtig gecastet werden - oder man wechselt zur Iteratorversion der Methode:
    //korrekte Typangabe:
    str.insert((string::size_type)0,n,'x');
    //Iterator verwenden:
    str.insert(str.begin(),n,'x');
    
    • erase(pos) und erase(beg,end)
      löschen das Zeichen an Position pos bzw. den Bereich [beg,end[
    • replace(beg,end,str), replace(beg,end,cstr), replace(beg,end,carr,len), replace(beg,end,num,c und replace(beg,end,nbeg,nend)
      ersetzen den Bereich [beg,end[ durch den String str, den C-String cstr, das char-Array carr aus len Zeichen, num Kopien von c bzw. den Bereich [nbeg,nend[

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

    • char_type
      verwendeter Zeichentyp (char bzw. wchar_t)
    • int_type
      Hilfstyp für Fehlerausgaben (enthält mindestens einen Wert mehr als char_type, der für "end of file" steht) (int bzw. wint_t)
    • pos_type und off_type
      Hilfstypen für Positions- bzw. Offsetangaben in IO-Streams
    • state_type
      Hilfstyp für den Übersetzungsstatus in Multibyte-Streams
    • assign(tgt,src)
      Zeichenzuweisung (tgt=src)
    • eq(c1,c2) und lt(c1,c2)
      Zeichenvergleich (c1==c2 bzw. c1<c2)
    • length(str)
      Stringlänge (strlen(str))
    • compare(s1,s2,n)
      Stringvergleich (memcmp(s1,s2,n))
    • copy(s1,s2,n) und move(s1,s2,n)
      Stringkopie (memcpy(s1,s2,n) bzw. memmove(s1,s2,n))
    • assign(str,n,c)
      Stringzuweisung (n mal Zeichen c) (memset(str,c,n))
    • find(str,n,c)
      Zeichensuche im String (memchr(str,c,n))
    • eof()
      der Wert für end of file (EOF)
    • to_int_type(c) und to_char_type(i)
      Konvertierung zwischen int_type- und [ko]char_type-Darstellung[/kor] ("to_char_type(eof())" ist undefniert)
    • not_eof(i)
      gibt alles außer eof() zurück (Zeichen werden 1:1 übergeben, "not_eof(eof())" ist implementationsspezifisch)
    • eq_int_type(i1,i2)
      Vergleich im int_type (i1==i2)

    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:

    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:

    • value_type (charT)
      der verwaltete Zeichentyp
    • traits_type (traitT)
      der Typ der verwendeten Zeichen-Traits-Klasse
    • size_type (size_t) und difference_type (ptrdiff_t)
      Zahlentypen für Indexangaben bzw. Differenzwerte
    • reference (charT&) und const_reference (const charT&)
      veränderbare bzw. konstante Referenzen auf einzelne Zeichen
    • pointer (charT*) und const_pointer (const charT*)
      veränderbare bzw. konstante Zeiger auf den Zeichentyp
    • iterator, const_iterator, reverse_iterator und const_reverse_iterator
      Iteratoren und Reverse-Iteratoren auf String-Elemente (typischerweise als Pointer implementiert)
    • allocator_type (alloc)
      der Typ der verwendeten Allokator-Klasse

    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.

    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.

    ----

    Anmerkungen:

    • "Selber" ist Umgangssprache.
    • Charakter hab ich zu character gemacht, weil das deutsche Charakter nicht die Bedeutung "Zeichen" hat.
    • Man schreibt eine Zusammensetzung mit "nebeneinander" immer getrennt (musste ich selbst nachschlagen).
    • In einer Liste trennt man nicht mit Kommas (s. 2.3).
    • Die Konjunktion "dass" wird nach neuer Rechtschreibung mit Doppel-S geschrieben.
    • Bei den Suchfunktionen hab ich die drei Punkte aus den Parameterklammern genommen, weil die normalerweise für Ellipsen stehen.
    • Wenn du Funktionsparameter im Text erwähnst, hab ich konsistent die Anführungszeichen weggelassen (oder wenigstens versucht, ich hab die Änderung nachträglich gemacht, kann sein, dass ich was übersehen habe). Genauso hab ich bei Funktionen (außer bei Aufrufen) und Typen gehandelt.

    Insgesamt ist deine Rechtschreibung und Grammatik sehr gut.

    Drei Anmerkungen noch zum Inhalt: Bei deinem swap-Trick solltest du zuerst die gewünschte Kapazität bei nstr festlegen (bspw. str.size()). Außerdem ist deine Traits-Klasse für case-insensitive Strings nicht besonders gut, Begründung siehe die FAQ von Hume. Des Weiteren ist im TR1 Regex-Unterstützung.

    Ich empfehle dir, den Artikel aus meinem Post zu übernehmen, indem du ihn zitierst, weil ich nicht alle Änderungen gekennzeichnet habe.



  • CStoll? Ich würde gerne heute ab allerallerspätestens 15:00 die Artikel rausschieben. 🙂
    Deiner ist doch jetzt fertig, oder?



  • 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:

    #include <string>
    

    Definitionen

    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:

    • const string& str - String
      Kopie eines anderen Strings
    • const string& str,size_t idx,size_t len - Teilstring
      Teilstring aus einem anderen String (len Zeichen ab Position idx)
    • const char cstr* - C-String
      Kopie eines C-Strings (nullterminierte Zeichenfolge)
    • const char cstr,size_t len* - char-Array
      Kopie eines char-Arrays (len Zeichen, '\0' ist normaler Wert)
    • char c - Einzelzeichen
      ein einzelnes Zeichen
    • size_t len,char c - Zeichenblock
      len Kopien des Zeichens c
    • Iterator beg,Iterator end - Bereich
      Kopie aus dem Iterator-Bereich [beg,end[

    Folgende Parameterkombinationen sind dabei je nach verwendeter Methode erlaubt:

    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:

    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:

    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:

    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

    • einen Indexwert (löscht alle Zeichen hinter dieser Position)
    • einen Indexwert und die Länge (löscht entsprechend viele Zeichen ab dem Index)
    • einen Iterator (löscht das Zeichen an der gegebenen Position)
    • zwei Iteratoren (löscht den Bereich dazwischen)

    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:

    void replace_all(string& text,const string& fnd,const string& rep)
    {
      size_t pos = text.find(fnd);
      while(pos!=string::npos)
      {
        text.replace(pos,pos+fnd.length(),rep);
        pos = text.find(fnd,pos);
      }
    }
    

    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:

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

    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):

    • find()
      sucht die übergebene Zeichenkette als Teilstring
      (entspricht dem Algorithmus find() (für Einzelzeichen) bzw. search() (für Teilstrings))
    • rfind()
      sucht die Zeichenkette als Teilstring (von hinten beginnend)
      (entspricht dem Algorithmus find() mit Reverse-Iteratoren (für Einzelzeichen) bzw. find_end() (für Teilstrings))
    • find_first_of()
      sucht das erste Zeichen des Strings, das in der Zeichenkette genannt wird
      (entspricht dem Algorithmus find_first_of())
    • find_first_not_of()
      sucht das erste Zeichen des Strings, das in der Zeichenkette nicht genannt wird
    • find_last_of()
      sucht das letzte Zeichen des Strings, das in der Zeichenkette genannt wird
      (entspricht dem Algorithmus find_first_of() mit Reverse-Iteratoren)
    • find_last_not_of()
      sucht das letzte Zeichen des Strings, das in der Zeichenkette nicht genannt wird

    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:

    • begin(), end(), rbegin() und rend()
      liefern normale bzw. reverse Iteratoren auf den Anfang bzw. das Ende der Zeichenfolge
    • string(beg,end)
      konstruiert einen String aus dem Bereich [beg,end[
    • assign(beg,end) und append(beg,end)
      nehmen den Bereich [beg,end[ und weisen ihn dem String zu bzw. hängen ihn an
    • insert(pos,c), insert(pos,num,c) und insert(pos,beg,end)
      fügen an der Position pos ein Zeichen c, num Kopien von c bzw. den Bereich [beg,end[ ein
      Achtung: der Aufruf "s.insert(0,n,'x');" ist mehrdeutig - 0 könnte entweder die Indexposition 0 oder den NULL-Zeiger (char* als String-Iterator) darstellen. Zur Auflösung dieser Mehrdeutigkeit muss der Wert richtig gecastet werden - oder man wechselt zur Iteratorversion der Methode:
    //korrekte Typangabe:
    str.insert((string::size_type)0,n,'x');
    //Iterator verwenden:
    str.insert(str.begin(),n,'x');
    
    • erase(pos) und erase(beg,end)
      löschen das Zeichen an Position pos bzw. den Bereich [beg,end[
    • replace(beg,end,str), replace(beg,end,cstr), replace(beg,end,carr,len), replace(beg,end,num,c und replace(beg,end,nbeg,nend)
      ersetzen den Bereich [beg,end[ durch den String str, den C-String cstr, das char-Array carr aus len Zeichen, num Kopien von c bzw. den Bereich [nbeg,nend[

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

    • char_type
      verwendeter Zeichentyp (char bzw. wchar_t)
    • int_type
      Hilfstyp für Fehlerausgaben (enthält mindestens einen Wert mehr als char_type, der für "end of file" steht) (int bzw. wint_t)
    • pos_type und off_type
      Hilfstypen für Positions- bzw. Offsetangaben in IO-Streams
    • state_type
      Hilfstyp für den Übersetzungsstatus in Multibyte-Streams
    • assign(tgt,src)
      Zeichenzuweisung (tgt=src)
    • eq(c1,c2) und lt(c1,c2)
      Zeichenvergleich (c1==c2 bzw. c1<c2)
    • length(str)
      Stringlänge (strlen(str))
    • compare(s1,s2,n)
      Stringvergleich (memcmp(s1,s2,n))
    • copy(s1,s2,n) und move(s1,s2,n)
      Stringkopie (memcpy(s1,s2,n) bzw. memmove(s1,s2,n))
    • assign(str,n,c)
      Stringzuweisung (n mal Zeichen c) (memset(str,c,n))
    • find(str,n,c)
      Zeichensuche im String (memchr(str,c,n))
    • eof()
      der Wert für end of file (EOF)
    • to_int_type(c) und to_char_type(i)
      Konvertierung zwischen int_type- und char_type-Darstellung ("to_char_type(eof())" ist undefniert)
    • not_eof(i)
      gibt alles außer eof() zurück (Zeichen werden 1:1 übergeben, "not_eof(eof())" ist implementationsspezifisch)
    • eq_int_type(i1,i2)
      Vergleich im int_type (i1==i2)

    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:

    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:

    • value_type (charT)
      der verwaltete Zeichentyp
    • traits_type (traitT)
      der Typ der verwendeten Zeichen-Traits-Klasse
    • size_type (size_t) und difference_type (ptrdiff_t)
      Zahlentypen für Indexangaben bzw. Differenzwerte
    • reference (charT&) und const_reference (const charT&)
      veränderbare bzw. konstante Referenzen auf einzelne Zeichen
    • pointer (charT*) und const_pointer (const charT*)
      veränderbare bzw. konstante Zeiger auf den Zeichentyp
    • iterator, const_iterator, reverse_iterator und const_reverse_iterator
      Iteratoren und Reverse-Iteratoren auf String-Elemente (typischerweise als Pointer implementiert)
    • allocator_type (alloc)
      der Typ der verwendeten Allokator-Klasse

    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.

    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.

    ----

    Bei deinem swap-Trick solltest du zuerst die gewünschte Kapazität bei nstr festlegen (bspw. str.size()).

    Soweit ich weiß, legt der neue String seine Kapazität passend zur übergebenen Größe fest. Aber ich lasse mich gerne eines besseren belehren.

    Außerdem ist deine Traits-Klasse für case-insensitive Strings nicht besonders gut, Begründung siehe die FAQ von Hume.

    Ich weiß, er ist nicht komplett. Aber zu Demonstrationszwecken sollte es reichen (und im Gegensatz zu Hume's Beispielcode kann man Strings damit sogar nach "Größe" sortieren.

    Des Weiteren ist im TR1 Regex-Unterstützung.

    Wie ich schon früher erwähnte, habe ich die TR1 nicht 😉

    @estartu: Ja, von meiner Seite ist er fertig.


Log in to reply