記事内にはプロモーションが含まれています

【C言語】正規表現の使い方まとめ!regex.hで文字列検索・抽出を実装

【C言語】正規表現の使い方まとめ!regex.hで文字列検索・抽出を実装 C言語

C言語で文字列操作を行う際、「特定の単語が含まれているか」だけなら strstr 関数で十分ですが、「郵便番号の形式かチェックしたい」「小数点を含む数値だけを取り出したい」といった複雑なパターンマッチングが必要な場面では、正規表現の出番となります。

PythonやRubyなどのスクリプト言語とは異なり、C言語の言語仕様自体には正規表現の構文が組み込まれていません。

しかし、標準的な環境(LinuxやmacOSなどのPOSIX準拠システム)であれば、<regex.h> ライブラリを使用することで強力な正規表現機能を利用できます。

この記事では、C言語における正規表現の基本的な使い方から、一致した部分の文字列抽出、浮動小数点数の判定、そしてC言語特有のロケール設定やメモリ管理の注意点まで、サンプルコード付きで徹底解説します。

【本記事の信頼性】
プロフィール
執筆者:マヒロ
  • 執筆者は元エンジニア
  • SES⇒大手の社内SE⇒独立
  • 現在はこじんまりとしたプログラミングスクールを運営
  • モットーは「利他の精神」

C言語で正規表現を扱うための基礎知識

C言語で正規表現を扱うには、主に POSIX正規表現ライブラリ である <regex.h> を使用します。
このライブラリを使うための基本的な流れは、以下の3ステップです。

  1. コンパイル (regcomp): 文字列のパターンを、コンピュータが処理できる形式に変換する。
  2. 実行 (regexec): コンパイルしたパターンを使って、対象の文字列を検索・判定する。
  3. 解放 (regfree): 使い終わったメモリを解放する。

この「コンパイル」と「解放」の手順が必要な点が、他の軽量言語と大きく異なるポイントです。

【重要】ロケール(言語設定)について

C言語の正規表現関数は、実行環境のロケール設定に依存します。

特に日本語などのマルチバイト文字を扱う場合、ロケールを適切に設定しないと、バイト単位での誤判定や文字化けが発生するリスクがあります。

プログラムの冒頭で必ず <locale.h> をインクルードし、setlocale(LC_ALL, ""); を実行して、システムのデフォルトロケールを適用するようにしましょう。

主要な関数と定数一覧

まずは、プログラミングで頻繁に使用する関数とフラグ(定数)を整理しておきましょう。

関数名 役割
regcomp 正規表現パターンをコンパイルする
regexec コンパイルされたパターンで文字列検索を実行する
regfree 使用した正規表現オブジェクトのメモリを解放する
regerror エラーが発生した際にエラーメッセージを取得する

コンパイル時によく使うフラグ

  • REG_EXTENDED: 拡張正規表現を使用する(+? などを使う場合に必須)。
  • REG_ICASE: 大文字と小文字を区別しない。
  • REG_NOSUB: マッチした位置情報を報告しない(判定だけしたい場合に高速)。

よく使う正規表現パターン一覧

C言語に限った話ではありませんが、記述時によく使うメタ文字も確認しておきましょう。

C言語の文字列内で書く場合、バックスラッシュ \ はエスケープが必要(\\ と書く)な点に注意してください。

  • ^: 行頭
  • $: 行末
  • .: 任意の1文字
  • *: 直前の文字が0回以上繰り返し
  • +: 直前の文字が1回以上繰り返し(要 REG_EXTENDED
  • ?: 直前の文字が0回または1回(要 REG_EXTENDED
  • [0-9]: 数字
  • [a-z]: 英小文字

実践!正規表現で文字列を検索・判定する

それでは、実際にコードを書いてみましょう。

まずは最も基本的な「文字列がパターンに一致するかどうか」を判定するプログラムです。

ここでは、入力された文字列が「数字のみで構成されているか」をチェックします。

基本的なマッチングのサンプルコード

#include <stdio.h>
#include <regex.h>
#include <locale.h> // ロケール設定に必要

int main(void) {
    // ロケールをシステムのデフォルトに設定(日本語環境などで重要)
    setlocale(LC_ALL, "");

    const char *target = "12345";     // 判定したい文字列
    const char *pattern = "^[0-9]+$"; // 正規表現パターン(数字のみ)
    
    regex_t reg;
    int ret;
    char msgbuf[100];

    // 1. 正規表現のコンパイル
    // REG_EXTENDED: 拡張正規表現を有効化(+を使うため)
    // REG_NOSUB: 位置情報は不要(判定のみ)
    ret = regcomp(&reg, pattern, REG_EXTENDED | REG_NOSUB);
    if (ret != 0) {
        fprintf(stderr, "コンパイルエラー\n");
        return 1;
    }

    // 2. マッチングの実行
    ret = regexec(&reg, target, 0, NULL, 0);
    if (!ret) {
        printf("マッチしました: '%s' は数字のみです。\n", target);
    } else if (ret == REG_NOMATCH) {
        printf("マッチしませんでした。\n");
    } else {
        // エラー処理
        regerror(ret, &reg, msgbuf, sizeof(msgbuf));
        fprintf(stderr, "実行エラー: %s\n", msgbuf);
    }

    // 3. メモリの解放
    regfree(&reg);

    return 0;
}

実行結果

マッチしました: '12345' は数字のみです。

ソースコードの内容を詳しく解説します。

まず、setlocale でロケールを設定します。

次に、regcomp 関数を使って正規表現パターン "^[0-9]+$" をコンパイルしています。
この際、REG_EXTENDED を指定することで +(1回以上の繰り返し)が使えるようになります。

次に、regexec 関数で実際の検索を行います。
戻り値が 0 であればマッチ成功、REG_NOMATCH であればマッチ失敗です。

最後に、regfree を呼び出して、regcomp で確保されたメモリ領域を必ず解放します。
これを忘れるとメモリリークの原因になるため、C言語では必須の作法です。

マッチした文字列を抽出するテクニック

単なる判定だけでなく、「文字列の中から日付部分を取り出したい」あるいは「浮動小数点数だけを抽出したい」というケースも多いでしょう。

C言語で抽出を行うには、regmatch_t 構造体を使用します。

regmatch_t構造体で位置を特定する

regexec の引数に regmatch_t 配列を渡すことで、マッチした部分の「開始位置」と「終了位置」を取得できます。

これを利用して、元の文字列から該当部分を切り出します。

浮動小数点数を抽出するサンプルコード

文章の中から、小数(例: -3.14 や .5)を抽出して表示するプログラムです。

ここでは、負の数や整数部が省略された小数にも対応するパターンを使用し、無限ループ対策も実装しています。

#include <stdio.h>
#include <regex.h>
#include <locale.h>

int main(void) {
    setlocale(LC_ALL, "");

    const char *target = "Value is -3.14159, and ratio is .5.";
    
    // 浮動小数点数のパターン: 
    // [-+]? : 符号(省略可)
    // (...|...) : グループ化
    // [0-9]+\\.[0-9]* : 数字 + ドット + 数字(省略可、例: 3.)
    // \\.[0-9]+ : ドット + 数字(例: .5)
    const char *pattern = "[-+]?([0-9]+\\.[0-9]*|\\.[0-9]+)"; 
    
    regex_t reg;
    regmatch_t pmatch[1]; // マッチ結果を格納する配列(全体マッチ用)
    int ret;

    // コンパイル(位置情報が必要なので REG_NOSUB は指定しない)
    if (regcomp(&reg, pattern, REG_EXTENDED) != 0) {
        fprintf(stderr, "コンパイルエラー\n");
        return 1;
    }

    // 検索実行
    const char *p = target;
    while (1) {
        // ※ループ2回目以降、行頭指定(^)を無効にする REG_NOTBOL を検討する場合もありますが
        // 今回のパターンには ^ が含まれていないため省略します。
        ret = regexec(&reg, p, 1, pmatch, 0);
        if (ret == REG_NOMATCH) break; // マッチしなくなったら終了

        int start = pmatch[0].rm_so;
        int end = pmatch[0].rm_eo;
        int len = end - start;
        
        // 開始位置と長さを使って表示
        // %.*s は、長さを指定して文字列を表示するテクニック
        printf("抽出された数値: %.*s\n", len, p + start);

        // 次の検索のためにポインタを進める
        // 【重要】無限ループ対策
        // マッチ長さが0の場合(空文字マッチなど)は強制的に1文字進める
        if (len == 0) {
            p++;
            if (*p == '\0') break; // 文字列終端なら終了
        } else {
            p += end;
        }
    }

    regfree(&reg);
    return 0;
}

実行結果

抽出された数値: -3.14159
抽出された数値: .5

ポイントは、regexec の第3引数と第4引数です。

ここに「取得したいマッチの数」と「結果を格納する構造体配列(pmatch)」を渡します。
マッチが成功すると、pmatch[0].rm_so に開始オフセット、pmatch[0].rm_eo に終了オフセットが格納されます。

printf("%.*s", length, string) という書式指定を使うことで、わざわざバッファにコピーしなくても、指定した長さ分だけ文字列を表示することができます。

また、ループ内でポインタを進める際、マッチした長さが0の場合の対策を行っています。

正規表現によっては空文字にマッチすることがあり、その場合にポインタが進まず無限ループに陥るのを防ぐためです。

C言語での「置換」処理について

Pythonなどの他言語には subreplace といった置換関数が用意されていますが、残念ながら C言語の <regex.h> には置換機能がありません

そのため、正規表現で置換を行いたい場合は、以下の手順で自作する必要があります。

  1. regexec でマッチした位置(開始・終了)を取得する。
  2. マッチした箇所の「前」までの文字列を新しいバッファにコピーする。
  3. 置換したい文字列をバッファに追加する。
  4. マッチした箇所の「後」の文字列をバッファに追加する。

非常に手間がかかるため、単純な固定文字列の置換であれば、正規表現を使わずに自前のループ処理や strstr を組み合わせて実装する方が、パフォーマンス的にもコードの量的にも有利な場合があります。

C言語のスキルを活かして年収を上げる方法

以上、C言語の正規表現の使い方について解説してきました。

なお、C言語のスキルがある場合には、「転職して年収をアップさせる」「副業で稼ぐ」といった方法を検討するのがおすすめです。

C言語を扱えるエンジニアは比較的希少価値が高く、転職によって数十万円の年収アップはザラで、100万円以上年収が上がることも珍しくありません。

なお、転職によって年収を上げたい場合は、エンジニア専門の転職エージェントサービスを利用するのが最適です。

今すぐ転職する気がなくとも、とりあえず転職エージェントに無料登録しておくだけで、スカウトが届いたり、思わぬ好待遇の求人情報が送られてきたりするというメリットがあります。

併せて、副業案件を獲得できるエージェントにも登録しておくと、空いている時間を活かして稼げるようなC言語の案件を探しやすくなります。

転職エージェントも副業エージェントも、登録・利用は完全無料なので、どんな求人や副業案件があるのか気になる方は、気軽に利用してみるとよいでしょう。