C++ 正規表現の完全ガイド: 基本から高度なアプリケーションまで
1. 基礎知識
正規表現は、テキストの照合、検索、編集に使用される文字列パターンです。文章のパターンや構造、特徴を柔軟に表現できる一連の文字と特殊記号で構成されています。正規表現は、C++ を含むさまざまなプログラミング言語やアプリケーションで広く使用されています。これを使用して、テキスト内の特定のパターン文字列を検索し、入力形式を確認し、情報を抽出し、テキスト置換やその他の操作を実行できます。
正規表現の基本要素には、通常の文字 (文字、数字、記号など)、特殊文字 (ワイルドカード、境界文字、数量指定子など)、およびグループのキャプチャや後方参照などの高度な機能が含まれます。これらの要素を組み合わせることで、複雑な一致ルールを構築して、強力なテキスト処理機能を実現できます。
C++ で正規表現を使用する利点:
-
強力なテキスト処理機能: 正規表現は、テキスト パターンとルールを記述する柔軟な方法を提供し、複雑なテキスト マッチング、検索、抽出機能を簡単に実装できます。
-
正規表現を使用すると、ユーザーが入力したデータ形式が要件を満たしているかどうかを迅速に検証でき、データの精度と一貫性が向上します。
-
正規表現を使用すると、テキスト内のコンテンツをバッチで置換したり、出力テキストを書式設定したりするなど、テキストの置換や書式設定の操作を簡単に実行できます。
-
正規表現を使用すると、複雑なテキスト処理関数を短いコードで実装できるため、コードの読みやすさと単純さが向上します。
-
正規表現はログ分析、データ抽出、テキスト解析などのさまざまなシナリオで使用でき、C++ に強力なテキスト処理ツールを提供します。
C++ での正規表現の使用は、<regex>
提供されたヘッダー ファイルを通じて実装されます。基本的な構文とルール:
-
通常の文字: 独自の文字を表す文字、数字、およびいくつかの記号を含みます。
-
特殊文字: 一致ルールを記述するために使用されるメタ文字 ( 、 、 、 、
^
、$
、.
など*
)を含みます。+
?
|
\
-
*
数量子: (0 回以上)、+
(1 回以上)、(0 回?
または 1 回)、{m,n}
(出現回数の範囲は m 回から n 回) など、前のパターンの出現回数を指定します。 -
エスケープ文字:
\
特殊文字を通常の文字にエスケープするために使用します。 -
文字クラス: [ ] を使用して、文字グループのいずれかを表します。たとえば、[abc] は「a」、「b」、または「c」のいずれかに一致します。
-
キャプチャ グループ:
( )
パターンを 1 つのユニットに結合して、一致する部分文字列を取得するために使用されます。
C++ <regex>
ヘッダー ファイルでは、一般的に使用されるクラスにはstd::regex
、std::smatch
、std::regex_match
などが含まれており、これらを使用して正規表現の照合、検索、抽出を実現できます。たとえば、std::regex_match
関数を使用して文字列が指定された正規表現に一致するかどうかを確認し、std::smatch
クラスを使用して一致結果を格納します。
C++ の正規表現の基本的な構文とルールは、他の言語の正規表現と基本的に同じですが、特定の実装では異なる場合があります。公式の紹介文をご覧いただけます。
2. 正規表現の基本的なマッチング
-
単一の文字に一致します:
a
文字列内の 1 文字「a」と一致する正規表現など、一致には通常の文字を使用します。- メタキャラクター
.
を使用して任意の 1 文字と一致します。たとえば、正規表現はs.
文字列内の「sa」、「sb」、「sc」などと一致します。
-
複数の文字に一致します:
- 量指定子を使用して
*
、直前の文字の 0 個以上の出現に一致します。たとえば、正規表現は、ab*
「a」、「ab」、「abb」、「abbb」などに一致します。 - 量指定子を使用して
+
、1 回以上出現する前の文字と一致します。たとえば、正規表現は、ab+
「ab」、「abb」、「abbb」などと一致します。 - 量指定子を使用して
?
、直前の文字の 0 回または 1 回の出現に一致します。たとえば、正規表現は、ab?
「a」、「ab」などに一致します。 - 中括弧を使用して
{m,n}
、m 回から n 回出現する直前の文字と一致させます。たとえば、正規表現は、a{2,4}
「aa」、「aaa」、「aaaa」と一致します。
- 量指定子を使用して
-
開始位置と終了位置を一致させる:
- アンカー文字を使用して
^
文字列の開始位置と一致します。たとえば、正規表現は^start
「start」で始まる文字列と一致します。 - アンカー文字を使用して
$
文字列の末尾と一致します。たとえば、正規表現はend$
「end」で終わる文字列と一致します。
- アンカー文字を使用して
テキストの一致に正規表現を使用する場合は、文字クラスを一致させ、文字クラスを除外する必要があります。文字クラスは文字セット内の任意の文字と一致するために使用され、除外文字クラスは指定された文字セット以外の任意の文字と一致するために使用されます。
角括弧は文字クラス[]
を表すために正規表現で使用され、角括弧には一致する文字のセットが含まれます。例えば:
[aeiou]
任意の小文字の母音と一致します。[A-Za-z]
任意の大文字または小文字と一致します。[0-9]
任意の数字と一致します。
さらに、角括弧内でハイフンを使用して、任意の小文字と一致させる-
など、範囲を示すことができます。[a-z]
^
代わりに、文字クラス内でキャレットを使用して、文字クラスを除外します。例えば:
[^aeiou]
小文字の母音を除く任意の文字と一致します。
正規表現は、異なる量指定子を使用して繰り返し一致ルールを指定することにより、繰り返しパターンの一致をサポートします。
一般的に使用される量指定子:
*
: 前のパターンと 0 回以上一致します。+
: 前のパターンと 1 回以上一致します。?
: 前のパターンと 0 回または 1 回一致します。{n}
: 前のパターンと正確に n 回一致します。{n,}
: 前のパターンと少なくとも n 回一致します。{n,m}
: 前のパターンと少なくとも n 回、最大で m 回一致します。
例えば:
a*
0 個以上の「a」と一致します。a+
1 つ以上の「a」と一致します。a?
0 個または 1 個の「a」と一致します。a{3}
3 つの「a」に正確に一致します。a{2,4}
2 ~ 4 つの「a」と一致します。
正規表現には、アンカーとグループという2 つの重要な概念があります。
アンカー ポイントは、一致する位置を指定するために使用されます。一般的に使用されるアンカー ポイントは次のとおりです。
^
: 文字列の先頭と一致します。$
:文字列の終了位置と一致します。\b
: 単語の境界を一致させます。\B
: 単語以外の境界に一致します。
例えば:
^abc
「abc」で始まる文字列と一致します。xyz$
「xyz」で終わる文字列と一致します。\bword\b
個々の「単語」の単語を照合できます。\Bword\B
単語「word」の内部を一致させることができます。
グループ化は、パターンの一致結果をグループ化し、各グループを個別に処理するために使用されます。グループ分けは括弧で示されています()
。例えば:
(ab)+
「ab」、「abab」、「ababab」などと一致します。(a|b)
「a」または「b」と一致します。
3. C++ での正規表現の使用
- C++ で正規表現を使用するには、
<regex>
ヘッダー ファイルをインクルードし、std
名前空間を使用する必要があります。 - クラスを使用して
std::regex
正規表現オブジェクトを作成します。正規表現オブジェクトを使用して、特定の正規表現パターンを保存および表現できます。 - 照合と検索には正規表現を使用します。C++ では、これらの関数を実装するために
std::regex_search
関数と関数が使用されますstd::regex_match
。
例:
#include <iostream>
#include <regex>
int main() {
std::string text = "Hello, this is a sample text with some numbers 12345.";
std::regex pattern("\\d+"); // 匹配一个或多个数字
std::smatch matches; // 用于存储匹配结果
if (std::regex_search(text, matches, pattern)) {
std::cout << "Found match: " << matches.str() << std::endl;
} else {
std::cout << "No match found." << std::endl;
}
return 0;
}
std::regex_search
関数を使用してテキストを検索し、text
正規表現パターンpattern
に一致するコンテンツの検索を試みます。std::smatch
クラスは、一致結果を保存したり、一致した内容を出力したりするためにも使用されます。
std::regex_match
関数を使用して、文字列全体が正規表現パターンに正確に一致するかどうかを確認することもできます。
if (std::regex_match(text, pattern)) {
std::cout << "Full match found" << std::endl;
} else {
std::cout << "No full match found" << std::endl;
}
さらに、C++ の正規表現ライブラリを使用して、一致する部分を抽出および置換できます。C++ 標準ライブラリのstd::regex
クラスと関数はstd::regex_replace
、これらのタスクを実行できます。
例:
#include <iostream>
#include <regex>
int main() {
std::string text = "The cat sat on the mat.";
std::regex pattern("\\b(cat)\\b"); // 匹配整个单词"cat"
std::sregex_iterator it(text.begin(), text.end(), pattern);
std::sregex_iterator end;
for (; it != end; ++it) {
std::smatch match = *it;
std::cout << "Match found: " << match.str() << " at position " << match.position() << std::endl;
}
// 替换匹配的部分
std::string replaced_text = std::regex_replace(text, pattern, "dog");
std::cout << "Replaced text: " << replaced_text << std::endl;
return 0;
}
std::sregex_iterator
一致する結果を反復処理し、std::regex_replace
関数を使用して一致する部分を置換するために使用します。ここでも関数を使用してposition()
一致位置を取得します。
4. 高度な正規表現
(1) より複雑なパターン マッチング。C++ の正規表現ライブラリは、一連の強力な正規表現構文をサポートしており、より複雑なパターン マッチングのニーズに使用でき、より高度なテキスト マッチングと抽出を実現できます。
例:
#include <iostream>
#include <regex>
int main() {
std::string text = "The cat sat on the mat. The dog sat on the rug.";
std::regex pattern("\\b(\\w+)\\s+sat\\s+on\\s+the\\s+(\\w+)\\b"); // 匹配类似"xxx sat on the xxx"的句子
std::sregex_iterator it(text.begin(), text.end(), pattern);
std::sregex_iterator end;
for (; it != end; ++it) {
std::smatch match = *it;
std::cout << "Match found: " << match.str() << std::endl;
std::cout << "First captured group: " << match[1].str() << std::endl;
std::cout << "Second captured group: " << match[2].str() << std::endl;
}
return 0;
}
(2) キャプチャ グループとトレースバックを使用します。キャプチャ グループを使用すると、正規表現の特定の部分をマークしてキャプチャできるようになり、トレースバックを使用すると、キャプチャしたコンテンツを置換テキストで参照できるようになります。
例:
#include <iostream>
#include <regex>
int main() {
std::string text = "The price is $10.99. The total is $25.50.";
std::regex pattern("\\$(\\d+\\.\\d+)"); // 匹配美元金额
std::string replaced_text = std::regex_replace(text, pattern, "¥$1"); // 使用捕获组的内容进行替换
std::cout << "Replaced text: " << replaced_text << std::endl;
return 0;
}
キャプチャ グループは量を照合するために使用され、置換テキストで$1
キャプチャされたコンテンツを参照して置換のために使用されます。ここでの表現は$1
、一致する部分を最初のキャプチャ グループの内容に置き換えることです。
(3) 遅延マッチングと貪欲マッチング。遅延マッチングと貪欲マッチングは、量指定子がどのようにマッチングされるかを説明するために使用されます。貪欲なマッチングは可能な限り多くの文字列と一致しますが、遅延マッチングは可能な限り少ない文字列と一致します。
C++ 正規表現で?
遅延マッチングを表すために使用されます。例:
#include <iostream>
#include <regex>
int main() {
std::string text = "The cat sat on the mat. The dog sat on the rug.";
std::regex greedy_pattern("s[a-z]+t"); // 贪婪匹配,尽可能多地匹配s和t之间的字母
std::regex lazy_pattern("s[a-z]+?t"); // 懒惰匹配,尽可能少地匹配s和t之间的字母
std::sregex_iterator it_greedy(text.begin(), text.end(), greedy_pattern);
std::sregex_iterator end_greedy;
for (; it_greedy != end_greedy; ++it_greedy) {
std::smatch match = *it_greedy;
std::cout << "Greedy Match found: " << match.str() << std::endl;
}
std::sregex_iterator it_lazy(text.begin(), text.end(), lazy_pattern);
std::sregex_iterator end_lazy;
for (; it_lazy != end_lazy; ++it_lazy) {
std::smatch match = *it_lazy;
std::cout << "Lazy Match found: " << match.str() << std::endl;
}
return 0;
}
貪欲なマッチングでは s と t の間のできるだけ多くの文字が一致しますが、遅延マッチングでは s と t の間のできるだけ少ない文字が一致します。
(4) 前後方向に検索します。前方検索と後方検索は、現在の一致位置に基づいた相対位置決め機能を提供し、特定の位置の前後のパターンを検索します。これは、特定のコンテキストに一致させる必要がある場合に便利です。
例:
#include <iostream>
#include <regex>
int main() {
std::string text = "The quick brown fox jumps over the lazy dog";
std::regex forward_pattern("\\b\\w+(?=\\sfox)"); // 向前查找,匹配fox之前的单词
std::regex backward_pattern("(?<=brown\\s)\\w+\\b"); // 向后查找,匹配brown之后的单词
std::smatch match;
if (std::regex_search(text, match, forward_pattern)) {
std::cout << "Forward match found: " << match.str() << std::endl;
}
if (std::regex_search(text, match, backward_pattern)) {
std::cout << "Backward match found: " << match.str() << std::endl;
}
return 0;
}
前方検索では、(?=\sfox)
fox より前の単語と一致し、後方検索では、(?<=brown\s)
brown より後の単語と一致します。
5. 実践例
(1) データの検証とフォーマット。電子メール アドレスが検証され、フォーマットされています:
#include <iostream>
#include <regex>
#include <string>
bool isValidEmail(const std::string& email) {
std::regex pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
return std::regex_match(email, pattern);
}
std::string formatPhoneNumber(const std::string& phoneNumber) {
std::regex pattern(R"(\b(\d{3})(\d{3})(\d{4})\b)");
return std::regex_replace(phoneNumber, pattern, R"($1-$2-$3)");
}
int main() {
// 邮箱验证
std::string email = "[email protected]";
if (isValidEmail(email)) {
std::cout << "Email is valid" << std::endl;
} else {
std::cout << "Invalid email" << std::endl;
}
// 电话号码格式化
std::string phoneNumber = "12345678901";
std::string formattedNumber = formatPhoneNumber(phoneNumber);
std::cout << "Formatted phone number: " << formattedNumber << std::endl;
return 0;
}
検証はstd::regex_match
実装によって行われ、フォーマットはstd::regex_replace
実装によって行われます。
(2 テキストの抽出と分析。テキスト内の数字を抽出して分析します。
#include <iostream>
#include <regex>
#include <string>
#include <vector>
std::vector<int> extractNumbers(const std::string& input) {
std::vector<int> numbers;
std::regex pattern(R"(\d+)");
std::sregex_iterator iter(input.begin(), input.end(), pattern);
std::sregex_iterator end;
for (; iter != end; ++iter) {
numbers.push_back(std::stoi(iter->str()));
}
return numbers;
}
int main() {
std::string text = "The price of the item is $250. The weight is 5.5 pounds.";
std::vector<int> numbers = extractNumbers(text);
std::cout << "Extracted numbers: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// 分析提取的数字
int sum = 0;
for (int num : numbers) {
sum += num;
}
std::cout << "Sum of the numbers: " << sum << std::endl;
return 0;
}
(3) ログファイルの解析。タイムスタンプ、IP アドレス、エラー メッセージなどのログ ファイル内の重要な情報を抽出して分析します。
#include <iostream>
#include <fstream>
#include <regex>
#include <string>
void analyzeLogFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
std::cerr << "Error opening file " << filename << std::endl;
return;
}
std::regex timePattern(R"((\d{2}:\d{2}:\d{2}))");
std::regex ipPattern(R"((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))");
std::regex errorPattern(R"(error|ERROR)");
std::string line;
while (std::getline(file, line)) {
std::smatch timeMatch;
std::smatch ipMatch;
std::smatch errorMatch;
if (std::regex_search(line, timeMatch, timePattern) && std::regex_search(line, ipMatch, ipPattern) && std::regex_search(line, errorMatch, errorPattern)) {
std::cout << "Timestamp: " << timeMatch[0] << ", IP address: " << ipMatch[0] << ", Error: " << errorMatch[0] << std::endl;
}
}
file.close();
}
int main() {
analyzeLogFile("example.log");
return 0;
}
(4) 高度なテキスト処理アプリケーション。
#include <iostream>
#include <regex>
#include <string>
void advancedTextProcessing(const std::string& text) {
// 通过正则表达式替换文本中的日期格式
std::regex datePattern(R"(\b(\d{1,2})/(\d{1,2})/(\d{4})\b)");
std::string processedText = std::regex_replace(text, datePattern, "$3-$1-$2");
std::cout << "Processed text with date format replaced: " << processedText << std::endl;
// 提取文本中的URL
std::regex urlPattern(R"((https?://\S+))");
std::sregex_iterator iter(text.begin(), text.end(), urlPattern);
std::sregex_iterator end;
std::cout << "Extracted URLs: ";
for (; iter != end; ++iter) {
std::cout << iter->str() << " ";
}
std::cout << std::endl;
// 使用正则表达式分组提取文本中的标题和内容
std::regex articlePattern(R"(<title>(.*?)</title>.*?<content>(.*?)</content>)", std::regex::dotall);
std::smatch match;
if (std::regex_search(text, match, articlePattern)) {
std::cout << "Title: " << match[1].str() << std::endl;
std::cout << "Content: " << match[2].str() << std::endl;
}
}
int main() {
std::string sampleText = "Today's date is 12/30/2023. Visit our website at https://www.baidu.com/ for more information. <title>Sample Title</title><content>This is a sample content.</content>";
advancedTextProcessing(sampleText);
return 0;
}
6. パフォーマンスの最適化
6.1. 正規表現のコンパイル
-
正規表現の事前コンパイル: 繰り返し使用される正規表現については、一致するたびに再コンパイルすることを避けるために、プログラムの初期化段階で正規表現をコンパイルすることをお勧めします。
std::regex
のコンストラクターを使用してコンパイルできます。std::regex regExpr(pattern); // 预先编译正则表达式
-
可能であれば、単純な正規表現パターンを使用し、過度に複雑で時間のかかる一致ルールを避けるようにしてください。
-
貪欲なマッチングによって引き起こされるパフォーマンスの問題を避けるために、マッチングの正規表現では非貪欲な量指定子 (
*?
、+?
、 ) を使用するようにしてください。{n, m}?
-
可能であれば、一致の複雑さを軽減するために、正規表現で汎用のワイルドカードではなく特定の文字列を使用するようにしてください。
-
プログラムで複数の正規表現を頻繁に使用する場合は、それらを事前にコンパイルして保存し、パフォーマンスを向上させることができます。
std::unordered_map<std::string, std::regex> regexCache; regexCache["datePattern"] = std::regex(R"((\d{1,2})/(\d{1,2})/(\d{4}))");
6.2. バックトラッキングの過度の使用を避ける
正規表現を作成する場合、特に長いテキストや複雑なパターンを処理する場合、バックトラッキングによって正規表現のパフォーマンスが低下する可能性があるため、バックトラッキングの過度の使用を避けることが非常に重要です。
-
照合プロセス中の不要な後戻りを避けるために、貪欲でない量指定子 (たとえば
*?
、+?
、 、 ) を使用するようにしてください。{n,m}?
-
バックトラッキングが増加する可能性があるため、正規表現でネストされた繰り返しを過度に使用しないでください。スキーマを単純化し、ネストの深さを減らすようにしてください。
-
アトミック グループを使用する: アトミック グループを使用するとバックトラッキングが回避されるため、バックトラッキングが発生する範囲を制限するために使用できます。
-
特定の固定文字列のみを一致させる必要がある場合は、正規表現を使用する代わりに文字列一致関数を直接使用することをお勧めします。
-
一部の正規表現エンジンでは、正規表現を決定論的有限オートマトン (DFA) にコンパイルでき、この方法によりバックトラッキングを回避し、マッチングのパフォーマンスを向上させることができます。
-
正規表現の事前コンパイル: 前述したように、プログラムの初期化段階で正規表現をコンパイルすると、一致するたびに再コンパイルする必要がなくなり、パフォーマンスが向上します。
6.3. マッチングアルゴリズムの最適化
-
アルゴリズムが異なれば、シナリオごとにパフォーマンスも異なります。たとえば、大きなテキストを処理する場合、有限オートマトン (DFA) に基づくアルゴリズムは、バックトラッキング アルゴリズムよりも効率的です。
-
バックトラッキングは時間のかかる操作です。正規表現で大量のバックトラッキングが必要になることは避けてください。正規表現パターンを最適化するか、貪欲でない量指定子を使用することで、バックトラッキングの回数を減らすことができます。
-
特定の固定文字列のみを照合する必要がある場合は、
strstr
正規表現を使用するよりも、文字列照合関数 ( または他の言語の同等の関数など) を直接使用する方が効率的である可能性があります。 -
同じ正規表現を頻繁に照合する必要がある場合は、照合結果をキャッシュして、照合操作の繰り返しを減らすことができます。
-
入力テキストを前処理する (たとえば、照合に必要のない部分を削除する) と、照合の複雑さを軽減し、照合のパフォーマンスを向上させることができます。
-
照合する必要がある大量のデータの場合は、マルチスレッドの並列照合を使用して照合を高速化します。
7. まとめ
正規表現の適用方向:
-
入力検証: ユーザーが入力したデータが電子メール アドレス、電話番号、日付、パスワードなどの特定の形式に準拠しているかどうかを検証するために使用されます。
-
データ抽出: Web ページからのリンクの抽出、ログ ファイルからの特定の形式のデータの抽出など、テキストから特定のパターンのデータを抽出します。
-
置換と書式設定: 日付形式の統一、不要なスペースの削除など、特定のパターンの文字列または書式設定されたテキストを置換するテキスト処理で使用されます。
-
URL ルーティング: Web 開発で URL ルーティング ルールを定義して照合し、ページ ジャンプやパラメータ抽出を実現するために使用されます。
-
構文分析: 特定の構文と構造を解析して処理するためにコンパイラーとインタープリターで使用されます。たとえば、正規表現エンジン自体は構文パーサーの実装です。
-
ログ分析: 大量のログ データ内の特定のパターンと情報を分析およびフィルターするために使用されます。
-
データ クリーニング: データの処理とクリーニングに正規表現を使用して、非標準のデータ形式を特定して処理します。
-
機密性の高い単語のフィルタリング: テキスト内の機密性の高い単語や不適切なコンテンツをフィルタリングするために使用されます。
-
文字列マッチング: 文字列に特定のパターンまたはキーワードが含まれているかどうかを検索するために使用されます。