SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

標準C++の正規表現: <regex>


  • このエントリーをはてなブックマークに追加

 文字列の照合/検索/置換に威力を発揮する正規表現の歴史は古く、1950年代に生まれたものらしいです。PerlやRubyなどのスクリプト言語のほとんどは正規表現を最初っから(しかも言語レベルで)サポートしてくれていますが、C++に標準ライブラリとして組み入れられたのはC++11、つい最近のことです。C++標準の正規表現でどんなことができるのか、ざっくりと解説します。

  • このエントリーをはてなブックマークに追加

 正規表現のおはなしは6年前、標準入りが確実視されていたBoost版で一度書いていますが、その後C++11化に伴いスペックに多少の変更がなされたようなので、今回改めての"仕切り直し版"。

<regex>

 標準C++ライブラリ:<regex>に用意された正規表現コンポーネントは、次の4つに分類されます。

  • 正規表現
    • std::basic_regex<charT>
  • 照合/検索の結果
    • std::match_results<Iter>, std::sub_match<Iter>
  • 照合/検索/置換アルゴリズム
    • std::regex_match<>, std::regex_search<>, std::regex_replace<>
  • 照合を繰り返すイテレータ
    • std::regex_iterator<>, std::regex_token_iterator<>

正規表現:basic_regex<charT>

 照合パターンである正規表現を表すのがクラステンプレートbasic_regex<charT>であり、単バイト/ワイド文字列のそれぞれに対応したbasic_regex<>がtypedefされています。

typedef basic_regex<char>    regex;
typedef basic_regex<wchar_t> wregex;

 コンストラクタの第一引数には、照合パターンとなる正規表現を与えます。

regex  re("[aA].*z"); // 'a'または'A'で始まり'z'で終わる
wregex wre(L"あ.*ん"); // あで始まり'ん'で終わる

 第二引数は省略可能な文法オプションで、std::regex_constants::ECMAScript, basic, extended, awk, grep, egrepのいずれかが指定できます(省略時にはECMAScript)。

 また、コンストラクタに与えた照合パターンが正規表現として不正であった場合はregex_error例外がthrowされます。

list-01
#include <iostream>
#include <regex>

using namespace std;

int main() try {
  regex re("[aA.*z");
} catch ( const regex_error& err ) {
  cout << err.code() << ':' << err.what() << endl;
}

/* 実行結果(Visual C++ 12)
4:regex_error(error_brack): The expression contained mismatched [ and ].
*/

照合結果:match_results<Iter>, sub_match<Iter>

 正規表現によるパターンの照合/検索の結果はmatch_results<Iter>に格納されます。テンプレート引数Iterが文字列上の位置を表し、文字列の型に応じて4つのtypedefが用意されています。

typedef match_results<const char*>              cmatch;
typedef match_results<const wchar_t*>          wcmatch;
typedef match_results<string::const_iterator>   smatch;
typedef match_results<wstring::const_iterator> wsmatch;

 正規表現の中に置かれた'('と')'に囲まれた部分を部分マッチ(sub_match)とし、正規表現全体にマッチする部分を第0-sub_match、正規表現中最初に現れた'('と対応する')'に囲まれた部分を第1-sub_match...と呼ぶことにします。このとき第n-sub_matchの位置,長さ,部分文字列がmatch_results<Iter>のメンバ関数position(n),length(n),str(n)で得られます。

 部分マッチはクラステンプレートsub_match<iter>で表現され、sub_match<Iter>を列挙するイテレータがmatch_results<Iter>::begin()/end()で得られるのでmatch_results<Iter>はsub_match<iter>の順序集合とみなすことができます。

 また、sub_match<Iter>はpair<Iter,Iter>から導出されていて、[first,second)がマッチ範囲です。

 ……前置きはこれくらいにして、照合/検索/置換それぞれのサンプルをお見せしましょうね。

照合:regex_match

 文字列全体が正規表現にマッチしていればtrueを返します。

list-02
#include <iostream>
#include <regex>
#include <locale>

using namespace std;

int main() {
  wcout.imbue(locale("japanese"));
  const wchar_t* input = L"あじのもとのあみのさん";
  const wchar_t* pattern = L"あ.*ん"; // 'あ'で始まり'ん'で終わる
  wregex re(pattern);
  if ( regex_match(input, re) ) {
    wcout << L"マッチしました!" << endl;
  }
}

 文字列と正規表現をregex_matchに食わすだけ、簡単です。

 regex_match関数は照合対象となる文字列として:

  • 先頭/末尾(の次)を指すイテレータの組
  • const charT*
  • const basic_string<charT>&

の3種、およびmatch_results<>の有無で3×2=6通り用意されています。

 引数にmatch_results<>を与え、sub_matchを取り出してみましょう。

list-03
#include <iostream>
#include <regex>
#include <locale>

using namespace std;

int main() {
  wcout.imbue(locale("japanese"));
  const wchar_t* input = L"あじのもとのあみのさん";
  const wchar_t* pattern = L"(あ.*)の(.*ん)"; // 'あ'で始まり'の'を挟んで'ん'で終わる
  wregex re(pattern);
  wcmatch match; // match_results<const wchar_t*>
  if ( regex_match(input, match, re) ) {
    wcout << L"マッチしました!" << endl;
#if 1
    for ( size_t i = 0; i < match.size(); ++i ) {
      wcout << match.str(i)
            << L" pos= " << match.position(i)
            << L" size= " << match.length(i)
            << endl;
    }
#else /* あるいはこう書ける */
    for ( const auto& submatch : match ) { // または const wcsub_match& submatch : match
      wcout << submatch.str()
            << L" pos= " << distance(input, submatch.first)
            << L" size= " << submatch.length()
            << endl;
    }
#endif
  }
}

/* 実行結果
マッチしました!
あじのもとのあみのさん pos= 0 size= 11
あじのもとのあみ pos= 0 size= 8
さん pos= 9 size= 2
*/

 ほほぅ、途中の'の'を最後尾の'の'にマッチさせています。どうやらマッチ範囲がなるだけ長くなるような照合を行うようです。

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
検索:regex_search<>

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/7716 2014/04/28 10:40

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング