C++
Wyrażenia regularne
Szukaj…
Wprowadzenie
Wyrażenia regularne (czasem nazywane wyrażeniami regularnymi lub wyrażeniami regularnymi) to składnia tekstowa, która reprezentuje wzorce, które można dopasować w obsługiwanych ciągach.
Wyrażenia regularne, wprowadzone w c ++ 11 , mogą opcjonalnie obsługiwać tablicę zwrotną dopasowanych ciągów lub inną składnię tekstową określającą sposób zastępowania dopasowanych wzorców w operowanych ciągach.
Składnia
- regex_match // Zwraca, czy regex dopasował całą sekwencję znaków, opcjonalnie przechwytując w obiekcie dopasowania
- regex_search // Zwraca czy część sekwencji znaków była dopasowana przez regex, opcjonalnie przechwytując w obiekcie dopasowania
- regex_replace // Zwraca wejściową sekwencję znaków zmodyfikowaną przez regex za pomocą łańcucha formatu zastępczego
- regex_token_iterator // Zainicjowany sekwencją znaków zdefiniowaną przez iteratory, listą indeksów przechwytywania do iteracji i wyrażeniem regularnym. Dereferencing zwraca aktualnie zindeksowane dopasowanie wyrażenia regularnego. Inkrementacja przesuwa się do następnego indeksu przechwytywania lub, jeśli aktualnie znajduje się na ostatnim indeksie, resetuje indeks i utrudnia następne wystąpienie dopasowania wyrażenia regularnego w sekwencji znaków
- regex_iterator // Zainicjowany sekwencją znaków zdefiniowaną przez iteratory i wyrażenie regularne. Dereferencing zwraca część sekwencji znaków, którą aktualnie dopasowuje cały regex. Zwiększanie znajduje kolejne wystąpienie dopasowania wyrażenia regularnego w sekwencji znaków
Parametry
Podpis | Opis |
---|---|
bool regex_match(BidirectionalIterator first, BidirectionalIterator last, smatch& sm, const regex& re, regex_constraints::match_flag_type flags) | BidirectionalIterator to dowolny iterator znaków, który zapewnia operatorom inkrementacji i dekrementacji smatch może być cmatch lub dowolnym innym wariantem match_results który akceptuje typ BidirectionalIterator argument smatch może zostać pominięty, jeśli wyniki wyrażenia regularnego nie są potrzebne Zwraca, czy re pasuje do całego znaku sekwencja zdefiniowana przez first i last |
bool regex_match(const string& str, smatch& sm, const regex re&, regex_constraints::match_flag_type flags) | string może być albo const char* lub L Wartość string funkcje przyjmującą wartość R string wyraźnie usunięte smatch może cmatch lub dowolny inny drugi wariant match_results przyjmująca typ str smatch argumentem może być pominięta, jeżeli wyniki wyrażenia regularnego nie są potrzebne Zwraca, czy re pasuje do całej sekwencji znaków zdefiniowanej przez str |
Podstawowe przykłady wyrażeń regularnych i wyszukiwania wyrażeń regularnych
const auto input = "Some people, when confronted with a problem, think \"I know, I'll use regular expressions.\""s;
smatch sm;
cout << input << endl;
// If input ends in a quotation that contains a word that begins with "reg" and another word begining with "ex" then capture the preceeding portion of input
if (regex_match(input, sm, regex("(.*)\".*\\breg.*\\bex.*\"\\s*$"))) {
const auto capture = sm[1].str();
cout << '\t' << capture << endl; // Outputs: "\tSome people, when confronted with a problem, think\n"
// Search our capture for "a problem" or "# problems"
if(regex_search(capture, sm, regex("(a|d+)\\s+problems?"))) {
const auto count = sm[1] == "a"s ? 1 : stoi(sm[1]);
cout << '\t' << count << (count > 1 ? " problems\n" : " problem\n"); // Outputs: "\t1 problem\n"
cout << "Now they have " << count + 1 << " problems.\n"; // Ouputs: "Now they have 2 problems\n"
}
}
regex_replace Przykład
Ten kod przyjmuje różne style nawiasów klamrowych i przekształca je w One True Brace Style :
const auto input = "if (KnR)\n\tfoo();\nif (spaces) {\n foo();\n}\nif (allman)\n{\n\tfoo();\n}\nif (horstmann)\n{\tfoo();\n}\nif (pico)\n{\tfoo(); }\nif (whitesmiths)\n\t{\n\tfoo();\n\t}\n"s;
cout << input << regex_replace(input, regex("(.+?)\\s*\\{?\\s*(.+?;)\\s*\\}?\\s*"), "$1 {\n\t$2\n}\n") << endl;
regex_token_iterator Przykład
std::regex_token_iterator
zapewnia ogromne narzędzie do wyodrębniania elementów pliku wartości oddzielonych przecinkami . Oprócz zalet iteracji, iterator może także przechwytywać przecinki, w których występują inne metody:
const auto input = "please split,this,csv, ,line,\\,\n"s;
const regex re{ "((?:[^\\\\,]|\\\\.)+)(?:,|$)" };
const vector<string> m_vecFields{ sregex_token_iterator(cbegin(input), cend(input), re, 1), sregex_token_iterator() };
cout << input << endl;
copy(cbegin(m_vecFields), cend(m_vecFields), ostream_iterator<string>(cout, "\n"));
Godnym uwagi problemem z iteratorami wyrażeń regularnych jest to, że argument regex
musi być wartością L. Wartość R nie będzie działać .
regex_iterator Przykład
Gdy przetwarzanie przechwyceń musi być wykonywane iteracyjnie, regex_iterator
jest dobrym wyborem. Dereferencja regex_iterator
zwraca wynik match_result
. Jest to świetne w przypadku przechwytywania warunkowego lub przechwytywania, które są współzależne. Powiedzmy, że chcemy tokenizować część kodu C ++. Dany:
enum TOKENS {
NUMBER,
ADDITION,
SUBTRACTION,
MULTIPLICATION,
DIVISION,
EQUALITY,
OPEN_PARENTHESIS,
CLOSE_PARENTHESIS
};
Możemy tokenizować ten ciąg: const auto input = "42/2 + -8\t=\n(2 + 2) * 2 * 2 -3"s
za pomocą regex_iterator
takiego jak to:
vector<TOKENS> tokens;
const regex re{ "\\s*(\\(?)\\s*(-?\\s*\\d+)\\s*(\\)?)\\s*(?:(\\+)|(-)|(\\*)|(/)|(=))" };
for_each(sregex_iterator(cbegin(input), cend(input), re), sregex_iterator(), [&](const auto& i) {
if(i[1].length() > 0) {
tokens.push_back(OPEN_PARENTHESIS);
}
tokens.push_back(i[2].str().front() == '-' ? NEGATIVE_NUMBER : NON_NEGATIVE_NUMBER);
if(i[3].length() > 0) {
tokens.push_back(CLOSE_PARENTHESIS);
}
auto it = next(cbegin(i), 4);
for(int result = ADDITION; it != cend(i); ++result, ++it) {
if (it->length() > 0U) {
tokens.push_back(static_cast<TOKENS>(result));
break;
}
}
});
match_results<string::const_reverse_iterator> sm;
if(regex_search(crbegin(input), crend(input), sm, regex{ tokens.back() == SUBTRACTION ? "^\\s*\\d+\\s*-\\s*(-?)" : "^\\s*\\d+\\s*(-?)" })) {
tokens.push_back(sm[1].length() == 0 ? NON_NEGATIVE_NUMBER : NEGATIVE_NUMBER);
}
Godnym uwagi problemem z iteratorami wyrażeń regularnych jest to, że argument regex
musi być wartością L, wartość R nie będzie działać: Visual Studio regex_iterator Błąd?
Dzielenie sznurka
std::vector<std::string> split(const std::string &str, std::string regex)
{
std::regex r{ regex };
std::sregex_token_iterator start{ str.begin(), str.end(), r, -1 }, end;
return std::vector<std::string>(start, end);
}
split("Some string\t with whitespace ", "\\s+"); // "Some", "string", "with", "whitespace"
Kwantyfikatory
Powiedzmy, że otrzymaliśmy const string input
jako numer telefonu do sprawdzenia. Możemy zacząć od wprowadzenia danych liczbowych z zerowym lub większym kwantyfikatorem : regex_match(input, regex("\\d*"))
lub jednym lub więcej kwantyfikatorem : regex_match(input, regex("\\d+"))
Ale oba z nich naprawdę nie są wystarczające, jeśli input
zawierają nieprawidłowy ciąg liczbowy, taki jak: „123” Użyjmy n lub więcej kwantyfikatora, aby upewnić się, że otrzymujemy co najmniej 7 cyfr:
regex_match(input, regex("\\d{7,}"))
To zagwarantuje, że otrzymamy co najmniej numer telefonu, ale input
mogą również zawierać ciąg liczbowy, który jest zbyt długi, na przykład: „123456789012”. Przejdźmy więc do kwantyfikatora między n i m, aby dane input
co najmniej 7 cyfr, ale nie więcej niż 11:
regex_match(input, regex("\\d{7,11}"));
To nas zbliża, ale niedozwolone ciągi liczbowe z zakresu [7, 11] są nadal akceptowane, na przykład: „123456789” Ustawmy opcjonalny kod kraju za pomocą leniwego kwantyfikatora :
regex_match(input, regex("\\d?\\d{7,10}"))
Ważne jest, aby pamiętać, że leniwy kwantyfikator dopasowuje jak najmniej znaków , więc jedynym sposobem dopasowania tego znaku jest, jeśli istnieje już 10 znaków, które zostały dopasowane przez \d{7,10}
. (Aby zachłannie dopasować pierwszy znak, musielibyśmy zrobić: \d{0,1}
.) Leniwy kwantyfikator można dołączyć do dowolnego innego kwantyfikatora.
Jak sprawić, by numer kierunkowy był opcjonalny i akceptować kod kraju tylko wtedy, gdy numer kierunkowy był obecny?
regex_match(input, regex("(?:\\d{3,4})?\\d{7}"))
W tym ostatnim wyrażeniu \d{7}
wymaga 7 cyfr. Te 7 cyfr jest opcjonalnie poprzedzone 3 lub 4 cyframi.
Zauważ, że nie dodaliśmy leniwego kwantyfikatora : , \d{3,4}?\d{7}
\d{3,4}?
dopasowałby 3 lub 4 znaki, preferując 3. Zamiast tego dopasowujemy grupę nie przechwytującą co najwyżej raz, woląc nie pasować. Powoduje niezgodność, jeśli input
nie zawierają kodu obszaru, takiego jak: „1234567”.
Podsumowując temat kwantyfikatora, chciałbym wspomnieć o innym dołączanym kwantyfikatorze, którego można użyć, kwantyfikat dzierżawczy . Zarówno leniwy kwantyfikator, jak i kwantyfikator dzierżawczy można dołączyć do dowolnego kwantyfikatora. Jedyną funkcją kwantyfikatora dzierżawczego jest wspomaganie silnika wyrażeń regularnych poprzez mówienie, łapczywie zabierać te znaki i nigdy ich nie poddawać, nawet jeśli powoduje to, że wyrażenie regularne kończy się niepowodzeniem . To na przykład nie ma większego sensu: regex_match(input, regex("\\d{3,4}+\\d{7}))
Ponieważ input
takie jak:„ 1234567890 ”nie byłyby dopasowane jako \d{3,4}+
zawsze będzie pasować do 4 znaków, nawet jeśli dopasowanie 3 pozwoliłoby na wyrażenie regularne.
Kwantyfikator dzierżawczy najlepiej stosować, gdy skwantyfikowany token ogranicza liczbę pasujących znaków . Na przykład:
regex_match(input, regex("(?:.*\\d{3,4}+){3}"))
Można użyć do dopasowania, jeśli input
zawierały jedno z poniższych:
123 456 7890
123–456–7890
(123)456-7890
(123) 456–7890
Ale kiedy ten regex naprawdę świeci, to gdy input
zawierają nieprawidłowe dane wejściowe:
12345–67890
Bez kwantyfikatora dzierżawczego silnik regex musi cofnąć się i przetestować każdą kombinację .*
Oraz 3 lub 4 znaki, aby sprawdzić, czy może znaleźć pasującą kombinację. Z zaborczej kwantyfikatorem rozpoczyna regex gdzie 2 nd zaborczy kwantyfikator przerwano, znak „0”, i próbuje silnik regex aby dostosować .*
, Aby umożliwić \d{3,4}
, aby dopasować; gdy nie może regex po prostu zawodzi, nie jest wykonywane śledzenie wsteczne, aby sprawdzić, czy wcześniej .*
dostosowanie mogło pozwolić na dopasowanie.
Kotwice
C ++ zapewnia tylko 4 kotwice:
-
^
który zapewnia początek łańcucha -
$
który zapewnia koniec łańcucha -
\b
który zapewnia znak\W
lub początek lub koniec ciągu -
\B
który zapewnia znak\w
Powiedzmy na przykład, że chcemy uchwycić liczbę ze znakiem:
auto input = "+1--12*123/+1234"s;
smatch sm;
if(regex_search(input, sm, regex{ "(?:^|\\b\\W)([+-]?\\d+)" })) {
do {
cout << sm[1] << endl;
input = sm.suffix().str();
} while(regex_search(input, sm, regex{ "(?:^\\W|\\b\\W)([+-]?\\d+)" }));
}
Ważną uwagą jest to, że kotwica nie zużywa żadnych znaków.