【C++】正規表現の基本から応用まで8例で解説

C++での正規表現を徹底解説するイメージC++
この記事は約13分で読めます。

※本記事のコンテンツは、利用目的を問わずご活用いただけます。実務経験10000時間以上のエンジニアが監修しており、基礎知識があれば初心者にも理解していただけるように、常に解説内容のわかりやすさや記事の品質に注力しております。不具合・分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。(理解できない部分などの個別相談も無償で承っております)
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事では、C++における正規表現の使い方を初心者から上級者まで幅広く解説します。

C++は強力なプログラミング言語であり、正規表現はテキスト処理において非常に有用です。

このガイドを通じて、C++における正規表現の基本から応用までを学ぶことができます。

文章とサンプルコードを通じて、読者の皆様がC++での正規表現を活用するための知識とスキルを身につけられるように構成しました。

●C++と正規表現の基礎

C++は、高性能なアプリケーション開発に適したプログラミング言語です。

特にシステムプログラミングや組み込みシステム開発に広く使用されています。

一方、正規表現は文字列を検索、置換、解析する際に非常に強力なツールとして機能します。

C++では、標準ライブラリの一部として正規表現ライブラリが提供されており、これを使って複雑な文字列処理を簡単に行うことができます。

○C++の概要と正規表現の重要性

C++はC言語を基に開発された言語で、オブジェクト指向プログラミングをサポートしています。

C++の特徴は、直接ハードウェアにアクセスできる低レベルの機能と、クラスや継承などの高レベルの抽象化機能を兼ね備えている点にあります。

正規表現は、これらの高レベルな機能と併用することで、効率的なテキスト処理が可能になります。

例えば、ファイルから特定のパターンを持つ文字列を検索したり、ログファイルから特定の情報を抽出する際に正規表現が非常に役立ちます。

○正規表現の基本構文

正規表現は、特定のパターンにマッチする文字列を見つけるための強力な方法です。

基本的な構文には、リテラル(直接文字を指定)、メタ文字(特殊な意味を持つ文字)、キャラクタークラス(文字の集合を指定)などがあります。

例えば、[a-z]は小文字のアルファベット全てにマッチし、.(ドット)は任意の単一文字にマッチします。

C++の正規表現ライブラリでは、これらの基本的な構文を使用して、複雑なテキスト処理を行うことができます。

●正規表現の基本的な使い方

C++での正規表現の基本的な使い方を理解するには、まず基本的な正規表現のパターンと、それをどのようにC++のコードに組み込むかを学ぶ必要があります。

C++では、<regex> ライブラリを使用して正規表現を扱います。

このライブラリは、様々な正規表現操作をサポートし、文字列の検索、置換、解析などに利用できます。

○サンプルコード1:文字列の検索

文字列内で特定のパターンを探す基本的な例を見てみましょう。

例えば、ある文字列内に「abc」というパターンが存在するかどうかをチェックするには、下記のようなコードを書きます。

#include <iostream>
#include <regex>

int main() {
    std::string target = "abcdefg";
    std::regex pattern("abc");

    bool match = std::regex_search(target, pattern);
    std::cout << "Pattern found: " << match << std::endl;

    return 0;
}

このコードでは、std::regex オブジェクトを使ってパターン「abc」を定義しています。

std::regex_search 関数は、このパターンが対象の文字列target内に存在するかどうかをチェックし、結果をブール値(trueまたはfalse)で返します。

○サンプルコード2:文字列の置換

次に、文字列内の特定のパターンを別の文字列で置換する方法を見てみましょう。

たとえば、文字列内の「abc」というパターンを「xyz」に置換するには、次のようなコードを使用します。

#include <iostream>
#include <regex>

int main() {
    std::string target = "abcdefg";
    std::regex pattern("abc");
    std::string replaceWith = "xyz";

    std::string result = std::regex_replace(target, pattern, replaceWith);
    std::cout << "Replaced string: " << result << std::endl;

    return 0;
}

この例では、std::regex_replace 関数を使用しています。

この関数は、指定されたパターンにマッチする部分を別の文字列で置換し、新しい文字列を返します。

上記のコードでは、target内の「abc」が「xyz」に置換され、結果がresultに格納されます。

●正規表現の応用例

C++における正規表現の応用例は多岐にわたります。

複雑な文字列パターンの識別から、特定のデータフォーマットの検証まで、様々な場面で有効に使用できます。

ここでは、そのような応用例のいくつかをサンプルコードとともに紹介します。

○サンプルコード3:複雑なパターンのマッチング

複雑なパターンのマッチングは、ログファイルの解析やデータのバリデーションに役立ちます。

例えば、日付のフォーマットにマッチする正規表現を用いて、文字列内の日付を識別することができます。

#include <iostream>
#include <regex>

int main() {
    std::string target = "今日は2024年2月23日です。";
    std::regex pattern(R"(\d{4}年\d{1,2}月\d{1,2}日)");

    std::smatch matches;
    if (std::regex_search(target, matches, pattern)) {
        std::cout << "Found date: " << matches[0] << std::endl;
    } else {
        std::cout << "No date found" << std::endl;
    }

    return 0;
}

このコードは、指定されたフォーマットに合致する日付を文字列から検索します。

\d{4}年\d{1,2}月\d{1,2}日は、正確に年月日の形式を指定した正規表現パターンです。

○サンプルコード4:メールアドレスの検証

メールアドレスのフォーマットを検証することも、正規表現の重要な応用例の一つです。

下記のコードは、メールアドレスが正しいフォーマットであるかをチェックします。

#include <iostream>
#include <regex>

int main() {
    std::string email = "example@example.com";
    std::regex pattern(R"(^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$)");

    if (std::regex_match(email, pattern)) {
        std::cout << "Valid email address" << std::endl;
    } else {
        std::cout << "Invalid email address" << std::endl;
    }

    return 0;
}

このコードでは、メールアドレスの標準的なフォーマットに適合するかどうかを検証しています。

このような検証は、ユーザー入力のバリデーションに非常に役立ちます。

○サンプルコード5:ログファイルの解析

ログファイルの解析は、正規表現を使う典型的な応用例です。

特定のパターンやキーワードに基づいてログファイルから重要な情報を抽出することが可能になります。

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

int main() {
    std::string log = "Error: File not found. File: example.txt";
    std::regex pattern(R"(Error: (.+))");

    std::smatch matches;
    if (std::regex_search(log, matches, pattern)) {
        std::cout << "Error message: " << matches[1] << std::endl;
    } else {
        std::cout << "No error found" << std::endl;
    }

    return 0;
}

このコードは、ログメッセージからエラー情報を抽出します。

Error: (.+)の正規表現は、”Error:”に続く任意の文字列にマッチします。

●正規表現の高度な応用例

C++における正規表現の応用は多岐にわたります。

複雑なテキスト処理からデータの整形、ウェブスクレイピングなど、さまざまな場面でその力を発揮します。

ここでは、いくつかの高度な応用例とそれに伴うサンプルコードを紹介します。

○サンプルコード6:ウェブスクレイピング

ウェブページから特定のデータを抽出するために正規表現を使用する例です。

たとえば、HTMLから特定のタグ内のテキストを取得することができます。

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

int main() {
    std::string html = "<div>Hello World!</div>";
    std::regex pattern("<div>(.*?)</div>");
    std::smatch matches;

    if (std::regex_search(html, matches, pattern)) {
        std::cout << "Matched text: " << matches[1] << std::endl;
    }

    return 0;
}

このコードでは、<div>タグで囲まれたテキストを抽出しています。

std::smatchを使用してマッチした部分を取得し、出力しています。

○サンプルコード7:ファイルシステムの操作

ファイル名やディレクトリ名に特定のパターンが含まれているかどうかをチェックし、それに基づいて処理を行う例です。

例えば、特定の拡張子を持つファイルだけを処理する場合に役立ちます。

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

int main() {
    std::string fileName = "example.txt";
    std::regex pattern(".+\\.txt$");

    bool match = std::regex_match(fileName, pattern);
    std::cout << "Is a text file: " << match << std::endl;

    return 0;
}

このコードでは、ファイル名が.txtで終わるかどうかをチェックしています。

std::regex_match関数を使用して、全体がパターンと一致するかを確認しています。

○サンプルコード8:データの整形と加工

テキストデータを特定のフォーマットに合わせて整形したり、不要な部分を取り除いたりする場合に正規表現が有効です。

下記のコードは、日付のフォーマットを変更する一例です。

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

int main() {
    std::string date = "2023/02/23";
    std::regex pattern("(\\d{4})/(\\d{2})/(\\d{2})");
    std::string format = "$2-$3-$1";

    std::string newDate = std::regex_replace(date, pattern, format);
    std::cout << "Formatted date: " << newDate << std::endl;

    return 0;
}

このコードでは、日付をYYYY/MM/DDからMM-DD-YYYYの形式に変更しています。

std::regex_replaceを使用して、マッチした部分を新しいフォーマットに置き換えています。

●注意点と対処法

C++における正規表現の利用は多くのメリットがありますが、いくつかの注意点が存在します。

これらの点を理解し、適切に対処することで、正規表現を安全かつ効率的に使用することができます。

○正規表現のパフォーマンスに関する注意点

正規表現を使用する際のパフォーマンスの低下は、しばしば問題となります。

これは特に、大量のデータや複雑なパターンを処理する場合に顕著です。

パフォーマンスを最適化するためには、正規表現の複雑さを最小限に抑え、不要なグルーピングや貪欲なマッチングを避けることが重要です。

また、特定のケースでは正規表現よりも単純な文字列操作の方が適している場合もあります。

したがって、使用する正規表現の複雑性を常に意識し、必要に応じてよりシンプルな方法を検討することが推奨されます。

○正規表現のセキュリティ上のリスク

正規表現は強力なツールですが、セキュリティ上のリスクも伴います。

特に「正規表現拒否サービス攻撃(ReDoS)」のリスクがあります。

これは、悪意のある入力によってプログラムが予期せず長時間動作することを引き起こす攻撃です。

このリスクを回避するためには、ユーザーからの入力に対する正規表現の使用を慎重に行い、可能な限り安全なパターンを使用することが重要です。

また、正規表現の処理にタイムアウトを設定することで、リスクを軽減することが可能です。

安全なプログラミングを心がけ、正規表現を使用する際にはこれらのセキュリティ上の考慮を行うことが不可欠です。

●カスタマイズ方法

C++での正規表現の利用において、特定の要件や状況に応じたカスタマイズが求められることがあります。

カスタマイズには、標準ライブラリの機能拡張や、独自の正規表現機能の実装が含まれます。

こうしたカスタマイズを行うことで、アプリケーションの特定のニーズに合わせたより柔軟な正規表現処理が可能になります。

○正規表現ライブラリのカスタマイズ

C++の標準ライブラリである<regex>は、多くの正規表現のニーズに対応していますが、特定のケースではこれを拡張する必要があります。

例えば、特定のパターンマッチングのルールを変更したり、処理効率を向上させるために内部ロジックを変更するなどのカスタマイズが考えられます。

こうしたカスタマイズは、特定のアプリケーションに特化した正規表現の処理を実現するために役立ちます。

○独自の正規表現機能の実装

場合によっては、完全に独自の正規表現エンジンを開発することが望ましいかもしれません。

これには、特定の文法やパフォーマンス要件を満たすために、基本的な文字列処理機能から正規表現パーサーやマッチャーをゼロから構築することが含まれます。

独自の正規表現機能の実装は、既存のライブラリでは対応できない高度な要件を持つアプリケーションにおいて特に有効です。

ただし、このアプローチは時間とリソースが大幅に必要となるため、実装にあたってはそのコストと利益を慎重に検討する必要があります。

まとめ

この記事を通じて、C++における正規表現の基本から応用までの幅広い知識を紹介しました。

初心者から上級者までがC++での正規表現の使い方、パフォーマンスの最適化、セキュリティリスクの管理、さらにはライブラリのカスタマイズや独自機能の実装方法について学ぶことができます。

正規表現は強力なツールであり、適切に使用すればC++プログラミングの多くの面で大きな助けとなるでしょう。