C++の基礎 - 正規表現

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動

概要

C++において、正規表現はテキストパターンのマッチング、検索、置換等を行うための強力なツールである。
C++ 11以降では、標準ライブラリとして<regex>ヘッダが導入されており、正規表現の機能が言語に組み込まれた。

これにより、外部ライブラリに頼ることなく正規表現を扱うことができる。

基本的な使用方法としては、std::regexクラスでパターンを定義して、std::regex_matchstd::regex_search等のメソッドを使用してマッチングを行う。
マッチした結果は、std::smatchクラスで取得できる。

正規表現の構文は、基本的に他の言語やツールで使用されているものと似ているが、いくつか特徴がある。
例えば、文字列リテラルで使用する場合はバックスラッシュを二重に記述する必要がある。
これは、C++の文字列エスケープシーケンスの仕様によるものである。

パターンマッチングのスタイルは複数用意されており、ECMAScriptスタイル (デフォルト)、basic、extended、awk等から選択できる。
最も一般的に使用されるのはECMAScriptスタイルであり、JavaScriptの正規表現と互換性がある。

また、正規表現操作にはいくつかのフラグを設定することができる。
大文字小文字を区別しない検索、複数行モード、より効率的なマッチングのための最適化等を指定できる。

エラーハンドリングも重要な要素である。
不正な正規表現パターンを指定した場合、std::regex_errorという例外が投げられるため、try-catchブロックでの適切な例外処理が推奨される。

C++の正規表現は他の言語と比べて実行速度が遅いことがある。
パフォーマンスが重要な場合は、Boostライブラリの正規表現機能や他の文字列処理手法の使用を検討する。


正規表現の使用例 : メールアドレス

以下の例では、ユーザ名、@記号、ドメイン部分を適切にパターンマッチングしている。

 #include <iostream>
 #include <regex>
 #include <string>
 
 void emailValidation()
 {
    try {
       std::string email = "user@example.com";
       std::regex pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
 
       if (std::regex_match(email, pattern)) {
          std::cout << "Valid email address\n";
       }
    }
    catch (const std::regex_error &e) {
       std::cerr << "Regex error: " << e.what() << "\nCode: " << e.code() << '\n';
    }
    catch (const std::exception &e) {
       std::cerr << "Error: " << e.what() << '\n';
    }
 }
 
 int main()
 {
    try {
       emailValidation();
    }
    catch (const std::exception &e) {
       std::cerr << "Main function error: " << e.what() << '\n';
       return -1;
    }
 
    return 0;
 }



正規表現の使用例 : 電話番号

以下の例では、国内の一般的な電話番号形式に対応するパターンを使用している。

 #include <iostream>
 #include <regex>
 #include <string>
 
 void phoneNumberFormat()
 {
    try {
       std::string phone = "090-1234-5678";
       std::regex pattern(R"(\d{2,4}-\d{2,4}-\d{4})");
       std::smatch matches;
 
       if (std::regex_match(phone, matches, pattern)) {
          std::cout << "Valid phone number" << std::endl ;
          for (const auto &match : matches) {
             std::cout << match << std::endl;
          }
       }
    }
    catch (const std::regex_error &e) {
       std::cerr << "Regex error: " << e.what() << std::endl;
       std::cerr << "Code: " << e.code() << std::endl;
 
       switch (e.code()) {
          case std::regex_constants::error_collate:
             std::cerr << "Invalid collation element request" << std::endl;
             break;
          case std::regex_constants::error_ctype:
             std::cerr << "Invalid character class" << std::endl;
             break;
          case std::regex_constants::error_escape:
             std::cerr << "Invalid escape character or trailing escape" << std::endl;
             break;
          case std::regex_constants::error_backref:
             std::cerr << "Invalid back reference" << std::endl;
             break;
          case std::regex_constants::error_brack:
             std::cerr << "Mismatched brackets" << std::endl;
             break;
          case std::regex_constants::error_paren:
             std::cerr << "Mismatched parentheses" << std::endl;
             break;
          case std::regex_constants::error_brace:
             std::cerr << "Mismatched braces" << std::endl;
             break;
          case std::regex_constants::error_badbrace:
             std::cerr << "Invalid range inside braces" << std::endl;
             break;
          case std::regex_constants::error_range:
             std::cerr << "Invalid character range" << std::endl;
             break;
          case std::regex_constants::error_space:
             std::cerr << "Insufficient memory" << std::endl;
             break;
          case std::regex_constants::error_badrepeat:
             std::cerr << "Invalid repeat expression" << std::endl;
             break;
          case std::regex_constants::error_complexity:
             std::cerr << "Operation too complex" << std::endl;
             break;
          case std::regex_constants::error_stack:
             std::cerr << "Stack space overflow" << std::endl;
             break;
          default:
             std::cerr << "Unknown regex error" << std::endl;
             break;
       }
    }
    catch (const std::exception &e) {
       std::cerr << "Error: " << e.what() << std::endl;
    }
 }
 
 int main()
 {
    try {
       phoneNumberFormat();
    }
    catch (const std::exception &e) {
       std::cerr << "Main function error: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }



正規表現の使用例 : HTMLタグ

以下の例では、開始タグと終了タグの対応を確認しながら、HTMLタグの内容を抽出している。

 #include <iostream>
 #include <regex>
 #include <string>
 
 void htmlTagExtraction()
 {
    try {
       std::string html = "<div>Content</div><p>Paragraph</p>";
       std::regex pattern("<([a-zA-Z0-9]+)>([^<]+)</\\1>");
       std::smatch matches;
       std::string::const_iterator searchStart(html.cbegin());
 
       while (std::regex_search(searchStart, html.cend(), matches, pattern)) {
          std::cout << "Tag: " << matches[1] << ", Content: " << matches[2] << std::endl;
          searchStart = matches.suffix().first;
       }
    }
    catch (const std::regex_error &e) {
       std::cerr << "Regex error: " << e.what() << std::endl;
       std::cerr << "Code: " << e.code() << std::endl;
    }
    catch (const std::out_of_range &e) {
       std::cerr << "Out of range error: " << e.what() << std::endl;
    }
    catch (const std::exception &e) {
       std::cerr << "Error: " << e.what() << std::endl;
    }
 }
 
 int main()
 {
    try {
       htmlTagExtraction();
    }
    catch (const std::exception &e) {
       std::cerr << "Main function error: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }



正規表現の使用例 : URLの解析

以下の例では、プロトコル、ドメイン、ポート番号、パス等の各要素を個別に抽出している。

 #include <iostream>
 #include <regex>
 #include <string>
 
 void urlParsing()
 {
    try {
       std::string url = "https://www.example.com:8080/path?param=value";
       std::regex pattern(R"(^(https?):\/\/([^:\/\s]+)(?::(\d+))?(\/[^\s]*)?$)");
       std::smatch matches;
 
       if (std::regex_match(url, matches, pattern)) {
          std::cout << "Protocol: " << matches[1] << std::endl;
          std::cout << "Domain: " << matches[2] << std::endl;
          std::cout << "Port: " << (matches[3].matched ? matches[3] : "default") << std::endl;
          std::cout << "Path: " << (matches[4].matched ? matches[4] : "/") << std::endl;
       }
       else {
          std::cerr << "Invalid URL format\n";
       }
    }
    catch (const std::regex_error &e) {
       std::cerr << "Regex error in URL parsing: " << e.what() << std::endl;
       std::cerr << "Code: " << e.code() << std::endl;
    }
    catch (const std::out_of_range &e) {
       std::cerr << "Out of range error in URL parsing: " << e.what() << std::endl;
    }
    catch (const std::exception &e) {
       std::cerr << "Error in URL parsing: " << e.what() << std::endl;
    }
 }
 
 int main()
 {
    try {
       urlParsing();
    }
    catch (const std::exception &e) {
       std::cerr << "Main function error: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }



正規表現の使用例 : パスワードの検証

以下の例では、大文字、小文字、数字、特殊文字を含む一般的なパスワードポリシーを実装している。

 #include <iostream>
 #include <regex>
 #include <string>
 
 void passwordValidation()
 {
    try {
       std::string password = "Password123!";
       std::regex pattern(R"(^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,})$)");
 
       if (std::regex_match(password, pattern)) {
          std::cout << "Valid password" << std::endl;
       }
       else {
          std::cout << "Invalid password. Password must contain:\n"
                    << "- At least one uppercase letter\n"
                    << "- At least one lowercase letter\n"
                    << "- At least one digit\n"
                    << "- At least one special character (!@#$%^&*)\n"
                    << "- Minimum length of 8 characters\n";
       }
    }
    catch (const std::regex_error &e) {
       std::cerr << "Regex error in password validation: " << e.what() << "\nCode: " << e.code() << std::endl;
 
       // パターンが複雑な場合の特別なハンドリング
       if (e.code() == std::regex_constants::error_complexity) {
          std::cerr << "Password pattern is too complex. Simplifying validation..." << std::endl;
 
          // 必要に応じてフォールバックの検証ロジックを実装
       }
    }
    catch (const std::exception &e) {
       std::cerr << "Error in password validation: " << e.what() << std::endl;
    }
 }
 
 int main()
 {
    try {
       passwordValidation();
    }
    catch (const std::exception &e) {
       std::cerr << "Main function error: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }



正規表現の使用例 : IPアドレスの検証

以下の例では、各オクテットの範囲チェックを含む厳密な検証を行っている。

 #include <iostream>
 #include <regex>
 #include <string>
 
 void ipAddressValidation()
 {
    try {
       std::string ip = "192.168.1.1";
       std::regex pattern(R"(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)");
 
       if (std::regex_match(ip, pattern)) {
          std::cout << "Valid IP address" << std::endl;
 
          // オプション: IPアドレスの種類をチェック
          std::smatch matches;
          std::regex privateIpPattern(R"(^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.).*)");
          if (std::regex_match(ip, matches, privateIpPattern)) {
             std::cout << "Note: This is a private IP address" << std::endl;
          }
       }
       else {
          std::cerr << "Invalid IP address format" << std::endl;
       }
    }
    catch (const std::regex_error &e) {
       std::cerr << "Regex error in IP validation: " << e.what() << std::endl;
       std::cerr << "Code: " << e.code() << std::endl;
    }
    catch (const std::exception &e) {
       std::cerr << "Error in IP validation: " << e.what() << std::endl;
    }
 }
 
 int main()
 {
    try {
       ipAddressValidation();
    }
    catch (const std::exception &e) {
       std::cerr << "Main function error: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }



正規表現の使用例 : ログファイルの解析

以下の例では、タイムスタンプ、ログレベル、メッセージを分離して抽出している。

 #include <iostream>
 #include <regex>
 #include <string>
 
 void logFileAnalysis()
 {
    try {
       std::string logLine = "[2024-01-01 12:34:56] ERROR: Database connection failed";
       std::regex pattern(R"(\[(.*?)\] (\w+): (.*))");
       std::smatch matches;
 
       if (std::regex_match(logLine, matches, pattern)) {
          if (matches.size() < 4) {
             throw std::runtime_error("Invalid log format: insufficient capture groups");
          }
          std::cout << "Timestamp: " << matches[1] << std::endl;
          std::cout << "Level: " << matches[2] << std::endl;
          std::cout << "Message: " << matches[3] << std::endl;
 
          // ログレベルの検証
          std::string level = matches[2];
          if (level != "INFO" && level != "WARNING" && level != "ERROR" && level != "DEBUG") {
             std::cerr << "Warning: Unknown log level " << level << std::endl;
          }
       }
       else {
          std::cerr << "Invalid log format" << std::endl;
       }
    }
    catch (const std::regex_error &e) {
       std::cerr << "Regex error in log analysis: " << e.what() << std::endl;
       std::cerr << "Code: " << e.code() << std::endl;
    }
    catch (const std::runtime_error &e) {
       std::cerr << "Runtime error in log analysis: " << e.what() << std::endl;
    }
    catch (const std::exception &e) {
       std::cerr << "Error in log analysis: " << e.what() << std::endl;
    }
 }
 
 int main()
 {
    try {
       logFileAnalysis();
    }
    catch (const std::exception &e) {
       std::cerr << "Main function error: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }



正規表現の使用例 : 日付形式の変換

以下の例では、regex_replaceメソッドを使用して日付形式を変更している。

 #include <iostream>
 #include <regex>
 #include <string>
 
 void textReplacement()
 {
    try {
       std::string text = "The date is 2023/12/25 and 2024/01/01";
       std::regex pattern(R"((\d{4})/(\d{2})/(\d{2}))");
 
       std::string result = std::regex_replace(text, pattern, "$3-$2-$1");
       std::cout << "Transformed: " << result << std::endl;
 
       // 置換が行われたか確認
       if (result == text) {
          std::cerr << "Warning: No dates were transformed" << std::endl;
       }
    }
    catch (const std::regex_error &e) {
       std::cerr << "Regex error in date transformation: " << e.what() << std::endl;
       std::cerr << "Code: " << e.code() << std::endl;
    }
    catch (const std::exception &e) {
       std::cerr << "Error in date transformation: " << e.what() << std::endl;
    }
 }
 
 int main()
 {
    try {
       textReplacement();
    }
    catch (const std::exception &e) {
       std::cerr << "Main function error: " << e.what() << std::endl;
       return -1;
    }
 
    return 0;
 }