Regular expressions

Mit C++11 sind reguläre Ausdrücke in den Standard eingezogen. std::regex und Co. bieten die Möglichkeit, nach bestimmten Mustern in Strings zu suchen.

Video

Quelltext

#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
    regex rex(R"(\d+)");
    regex rex2(R"(\D*\d+\D*)");
    string text { "In diesem Text versteckt sich eine lange Zahl: "
                  "mittendrin steht 1234567 mit noch was dahinter." };
    
    cout << "rex matcht den Text" << (regex_match(text, rex) ? "\n" : " nicht\n");
    cout << "rex2 matcht den Text" << (regex_match(text, rex2) ? "\n" : " nicht\n");
    
    smatch sm;
    if (regex_search(text, sm, rex)) {
        cout << "rex: \"" << sm.str() << "\"" << endl;
    }
    smatch sm2;
    if (regex_search(text, sm2, rex2)) {
        cout << "rex2: \"" << sm2.str() << "\"" << endl;
    }
}

Erklärung

Das zentrale Objekt, um das sich die gesamte Regular Expression Library in C++11 dreht, ist die Klasse std::regex. Objekte dieser Klasse repräsentieren eine bestimmte regular expression, die als Muster zum Matchen oder Suchen in Strings dienen kann. Das eigentliche Muster wird über eine eigene Sprache definiert, die in vielen Programmiersprachen so oder so ähnlich verfügbar ist. C++ setzt standardmäßig auf die aus JavaScript bekannte ECMAScript-Syntax. 

Hilfreich für die Eingabe von Stringliteralten für regex sind raw strings. Das Problem: typischerweise sind in Regex verhältnismäßig viele \ enthalten. Diese leiten allerdings im normalen Stringliteral als Escape-Character spezielle Sequenzen ein (bspw. \n für den Zeilenumbruch). Soll nun tatsächlich ein \ im String vorkommen, so wäre das immer als \\ zu schreiben. Um dem Aufwand aus dem Weg zu gehen, kann man mit R"(...)" ein raw string literal definieren, in dem \ keine besondere Bedeutung hat und man daher ohne die Doppelung auskommt.

Im Beispiel oben sind zwei Regex zu finden: rex, welche als \d+ eine oder mehrere Ziffern nacheinander matcht und rex2 mit \D*\d+\D*, was auf das Muster <irgendwas><zahl><irgendwas> passt. \d steht für eine einzelne Ziffer. Das + zeigt an, dass das vorhergehende Symbol einmal oder mehr gematcht werden soll. 9 würde also genauso passen, wie 1234657. \D hingegen steht für das Gegenteil: alles, außer Ziffern. Auch hier geht es wieder um ein einzelnes Zeichen. Die Wiederholung des Matches kommt durch * zustande: der matcht das Vorgängersymbol 0 mal oder öfter. Hier passt also jeder beliebig lange String, solange er keine Zahlen enthält (auch der leere String würde hier passen).

Der Unterschied macht sich im Aufruf der Funktion std::regex_match bemerkbar: diese prüft, ob der übergebene String auf das Muster passt. Für rex ist das natürlich nicht der Fall: passen würde das nur, wenn der String nur aus Zahlen bestünde. rex2 ist eine andere Sache: der String folgt eindeutig dem vorgebenen Muster: erst kommen nur Buchstaben, Sonderzeichen etc., dann eine Zahl und dann wieder nur Buchstaben etc. Daher wird das Programm am Ende ausgeben, dass rex nicht gematcht hat, rex2 aber schon.

Etwas anders liegt die Sache beim Suchen mit regex. Aufgabe ist hier, nach Teilstrings zu suchen, die einem bestimmten Muster genügen, ohne dass genau bekannt ist, wie die aussehen. Das eigentliche Suchen erledigt std::regex_search. Diese Methode liefert die Information, ob überhaupt ein Treffer erzeugt wurde, über ihren Rückgabewert. Ist der true, dann hat das regex-Muster auf irgendwas gepasst. Den eigentlichen Treffer schreibt die Funktion dann in das übergebene smatch-Objekt. Dort kann man sich bspw. den Text mit der Methode smatch::str() abholen. Im Beispiel wird rex beim Suchen genau auf die Zahl 1234567 passen und demzufolge auch nur diese als Ergebnis liefern, während rex2 auf den kompletten String passt (deswegen ja auch der Erfolg bei regex_match), also diesen auch als Ergebnis bei der Suche liefert.

regex_match und regex_search dienen leicht unterschiedlichen Aufgaben: match soll prüfen, ob ein kompletter String einer bestimmten Syntax genügt (vorgegeben durch die regex), während regex_search die Teile ausschneiden soll, die der Syntax genügen, und alles andere ignoriert.