Treffer mit Gruppen

Reguläre Ausdrücke bieten die Möglichkeit, einzelne Teile zu einer Gruppe zusammenzufassen und auf diese dann einzeln zuzugreifen. C++ bietet dafür natürlich auch eine entsprechende Schnittstelle.

Video

Quelltext

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

using namespace std;

void analyseNumber(smatch const &sm)
{
    cout << sm[0] << ": ";
    cout << ((sm[1] == "-") ? "negativ, " : "positiv, ");
    cout << "Ganzzahlanteil: " << sm[2] << ", ";
    cout << "Nachkommastellen: " << sm[3] << ".";
}

int main()
{
    regex flt { R"(([-+])?(\d+),(\d+))" };

    string text { "Ein Text mit eingebetteten Floats: 3,4 oder auch -42,23. "
                  "Natürlich auch +7,3. Allerdings nicht 7." };

    smatch sm;
    while (regex_search(text, sm, flt)) {
        analyseNumber(sm);
        text = sm.suffix();
        cout << endl;
    }
    
    cout << "Elemente: \n";
    string number { "+1234,5678" };
    regex_search(number, sm, flt);
    for (auto elem : sm) {
        cout << "  " << elem << endl;
    }
}

Erklärung

Wird in einer regular Expression ein Teilausdruck mit () eingefasst, dann ist dieser entsprechende Teil eine Gruppe. Auf diese kann dann über das match_result-Objekt einzeln zugegriffen werden. Im Beispielausdruck in Zeile 17 befinden sich drei Gruppen: der Teilausdruck "[-+]", sowie jeweils die Vor- und Nachkommastellen. Genau das ist auch Sinn der Übung: beim Parsen einer Gleitkommazahl ist es natürlich interessant, welches Vorzeichen die Zahl hat und wie Vor- und Nachkommaanteil aussehen. Völlig unwichtig ist hingegen, dass in der Mitte ein Komma ist. Das ist zwar für die Syntax wichtig, muss aber hinterher nicht als Gruppe zur Verfügung stehen. Daher sind dort auch keine () außenrum.

Auf die im match_result hinterlegten Gruppen kann man nun auf zwei Arten zugreifen: zum einen verhält sich das Objekt wie ein std::vector und bietet einen Indexzugriff. Der Index 0 ist hierbei speziell: er liefert nicht die erste Gruppe (wie man vielleicht erwarten könnte), sondern das komplette Match (inklusive aller Gruppen). Alle folgenden Indizes liefern die Teilmatches der Gruppen, so wie sie in der regular Expression vorkommen. Hierbei ist ein Detail wichtig: Gruppen können nicht wiederholt werden. Zwar ist es in der Regex syntaktisch zulässig, "(...)+" zu schreiben. Allerdings liegt im match_result-Objekt dann immer nur der letzte Treffer vor. Ein weiteres Detail betrifft optionale Gruppen (wie in der Regex oben den Vorzeichenausdruck): diese erzeugen immer einen entsprechenden Eintrag im match_result. Der kann im Beispiel oben allerdings drei Zustände haben: "+", "-" oder "". Der entsprechende Treffer wird also nicht weggelassen, sondern ist im Zweifelsfall leer.

Um eine bequeme Integration der match_results mit bspw. den Algorithmen aus der <algorithm>-Library zu ermöglichen, verhält sich das Objekt auch in einem weiteren Zusammenhang noch wie ein Container: es bietet einen Iteratorzugriff auf die Treffer. Die Methoden begin() und end() implementieren das übliche Verhalten. Wichtig hierbei: *begin() liefert das Match mit dem Index 0, also den kompletten Treffer. Will man nur die Gruppen durchlaufen, dann muss man das natürlich überspringen.