正则表达式

# 正则表达式 [toc]ios

正则表达式是一种描述字符序列的方法,是一种极其强大的计算工具。本文 重点介绍如何是使用C++正则表达式库(RE库),它是C++11新标准的一部分。c++

RE库定义在头文件regex中,它包含多个组件,以下表所示:正则表达式

正则表达式库组件
regex 表示有一个正则表达式的类
regex_match 将一个字符序列与一个正则表达式匹配
regex_search 寻找第一个与正则表达式匹配的子序列
regex_replace 使用给定格式替换一个正则表达式
sregex_iterator 迭代器适配器,调用regex_search来遍历一个string中全部匹配的子串
smatch 容器类,保存在string中搜索的结果
ssub_match string中匹配的子表达式的结果

使用正则表达式库

从一个简单的例子开始——查找违反拼写规则“i除非在c以后,不然必须在e以前”的单词express

#include <iostream>
#include <regex>

using namespace std;

int main()
{
	string pattern("[^c]ei");
	pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
	regex r(pattern);	//构造一个用于查找模式的regex
	smatch results;		//定义一个对象保存搜索结果

	string test_str = "receipt freind theif receive";
	if (regex_search(test_str, results, r))
	{
		cout << results.str() << endl;
	}
	return 0;
}

正则表达式[^c]代表咱们但愿匹配任意不是'c'的字符,而[^c]ei指出咱们想要匹配这种字符后接ei的字符串。此模式描述的字符串恰好是三个字符,为了与整个单词匹配,还须要一个正则表达式与这三个字母模式以前和以后的字母匹配。数组

默认状况下,regex使用的正则表达式语言是ECMAScript。在ECMAScript中,模式[[:alpha:]]匹配任意字母,符号 + 和 * 分别表示咱们但愿“一个或多个” 或“零个或多个”匹配。函数

函数regex_search在输入序列中只要找到一个匹配子串就会中止查找,所以结果输出是工具

freind编码

指定regex对象的选项


regex r(re) regex r(re, f)spa

re表示一个正则表达式,它能够是一个string、一个表示字符范围的迭代对、一个指向空字符结尾的字符数组的指针、一个字符指针和一个计数器或是一个花括号包围的字符列表。指针

f是指出对象如何处理的标志。f经过下面列出的值来设置。若是未指定f,默认值为ECMAScript。


r1 = re

将r1中的正则表达式替换成re。

re表示一个正则表达式,它能够是另外一个regex对象、一个string、一个指向空字符结尾的字符数组的指针或是一个花括号包围的字符列表。


r1.assign(re, f)

与使用赋值运算符(=)效果相同


r.mark_count()

r中子表达式的数目


r.flags()

返回r的标志集


定义regex时指定的标志
icase 在匹配过程当中忽略大小写
nosubs 不保存匹配的子表达式
optimize 执行速度优于构造速度
ECMAScript 使用ECMA-262语法

一个正则表达式来识别扩展名的示例:

//1个或多个字母或数字字符后面接一个'.'再接"cpp"或"cxx"或"cc"
regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);
smatch results;
string filename;
while( cin >> filename)
    if( regex_search(filename, results, r))
        cout << results.str() << endl;

在正则表达式语言中,字符点(.)一般匹配任意字符。与C++同样,能够在字符以前放置一个反斜线\来去掉其特殊含义。因为反斜线\也是C++中的一个特殊字符,咱们在字符串常量中必须连续使用两个反斜线。

一个正则表达式的语法是否正确是在运行时解析的。若是咱们编写的正则表达式存在错误,则在运行时标准库会抛出一二类型为regex_error的异常。相似标准库异常类型,regex_error有一个描述发生什么错误的what操做,和一个返回错误类型对应的数值编码的code成员。

try{
    regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);
}catch (regex_error e)
{
    cout << e.what() << "\ncode:" << e.code() << endl;
}

1558963548400

  • 正则表达式类和输入序列类型

咱们使用的RE库类型必须与输入序列类型匹配。例如

regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);
smatch results;
if( regex_search("myfile.cc", results, r)) //错误:输入为char*
    cout << results.str() << endl;

这段代码编译失败,由于match参数的类型与输入序列的类型不匹配。

cmatch results;
if( regex_search("myfile.cc", results, r)) //正确
    cout << results.str() << endl;

匹配与Regex迭代器类型

在第一个查找违反单词拼写规则例子中,它只打印输入序列中第一个匹配的单词。咱们可使用sregex_iterator来获取全部匹配。regex迭代器是一种迭代器适配器,被绑定到一个输入序列和一个regex对象上。

sregex_iterator it(b, e, r); 一个sregex_iterator,遍历迭代器b和e表示的string。它调用sregex_search(b, e, r)将it定位到输入中第一个匹配的位置
sregex_iterator end; sregex_iterator的尾后迭代器

当咱们将一个sregex_iterator绑定到一个string和一个regex对象时,迭代器自动定位到给定string中第一个匹配位置。

//查找不在字符c以后的字符串ei
	string pattern("[^c]ei");

	pattern = "[[:alpha:]]" + pattern + "[[:alpha:]]*";
	regex r(pattern, regex::icase);    //构造一个用于查找模式的regex
	string test_str = "receipt freind theif receive";
	for (sregex_iterator it(test_str.begin(), test_str.end(), r), end_it;
         it != end_it; ++it)
		cout << it->str() << endl;

end_it是一个空的sregex_iterator,起到尾后迭代器的做用。

Output:

freind
thief

匹配类型有两个名为prefix和suffix的成员,分别返回表示输入序列中当前匹配以前和以后部分的ssub_match对象。一个ssub_match对象有两个名为str和length的成员,分别返回匹配的string和该string的大小

for( sregex_iterator it(file.begin(), file.end(), r), end_it;
        it != end_it; ++it)
{
    auto pos = it->prefix().length();   //前缀大小
    pos = pos > 40 ? pos - 40 : 0;		//咱们想要最多40个字符
    cout << it->prefix().str().substr(pos)	//前缀的最后一部分
            << "\n\t\t" << it->str() << " <<<\n"	//匹配的单词
            << it->suffix().str().substr(0, 40)		//后缀的第一部分
            << endl;
}

这里补充下substr()用法:

// string::substr
#include <iostream>
#include <string>

int main ()
{
  std::string str="We think in generalities, but we live in details.";
                                           // (quoting Alfred N. Whitehead)

  std::string str2 = str.substr (3,5);     // "think"

  std::size_t pos = str.find("live");      // position of "live" in str

  std::string str3 = str.substr (pos);     // get from "live" to the end

  std::cout << str2 << ' ' << str3 << '\n';

  return 0;
}

Output:

`think live in details. `

使用子表达式

正则表达式中的模式一般包含1个或多个**子表达式(subexpression)。**一个子表达式是模式的一部分,自己也具备意义。正则表达式语法一般用括号来表示子表达式。

regex r("([[:alnum:]+)\\.(cpp|cxx|cc)$", regex::icase);

这个模式保护两个括号括起来的子表达式:

  • ([[:alnum:]]+),匹配1个或多个字符的序列
  • (cpp|cxx|cc),匹配文件扩展名
if( regex_search(filename, results, r))
    cout << results.str(1) << endl;

匹配对象除了提供匹配总体的相关信息外,还提供访问模式中每一个子表达式的能力。子匹配是按位置来访问的。第一个子匹配位置为0,表示整个模式对应的匹配,随后是每一个子表达式对应的匹配。

例如若是文件名为foo.cpp,则results.str(0)将保存foo.cpp,results.str(1)将保存foo,results.str(2)将保存cpp。

验证必须匹配特定格式的数据

  • {d}表示单个数字,而{d}{n}则表示一个n个数字的序列。(如:{d}{3}匹配三个数字的序列)

  • 在方括号中的字符集合表示匹配这些字符中的任意一个。(如:[-. ]匹配一个短横线或一个点或一个空格。注意,点再括号中没有特殊含义)

  • 后接'?'的组建是可选的。(如:{d}{3}[-. ]?{d}{4}匹配序列是开始是三个数字,后接一个可选的短横线或点或空格,而后是四个数字。此模式能够匹配555-0132或555.0132或555 0132或5550132)

  • 相似C++,ECMAScript使用反斜线\表示一个字符自己而不是其特殊含义。因为咱们的模式包括括号,而括号是ECMAScript中的特殊字符,所以必须使用 和和 来表示括号是咱们模式的一部分而不是特殊字符。

    因为反斜线\是C++的特殊字符,在模式中每次出现\的地方,必须用一个额外的反斜线来告知是反斜线而不是特殊字符。即咱们用\{d}{3}来表示正则表达式{d}{3}。

string phone = 
    "(\\()?\\{d}{3}(\\))?([-. ])?(\\{d}{3})([-. ])?(\\{d}{4})";
regex r(phone);
smatch m;
string s;
 
while(getline(cin, s))
{
    for( sregex_iterator it(s.begin(), s.end(), r), end_it;
            it != end_it; ++it)
    {
        if(valid(*it))
            cout << "valid:" << it->str() << endl;
        else
            cout << "not valid:" << it->str() << endl;    
    }
}

上述代码是匹配美国电话号码的模式示例:

  • (\()? :表示区号部分可选的左括号
  • \{d}{3} :表示区号
  • (\))? :表示区号部分可选的右括号
  • ([-. ])? :表示区号部分可选的分隔符
  • (\{d}{3}) :表示号码的下三位数字
  • ([-. ])? :表示可选的分隔符
  • (\{d}{4}) :表示号码的最后四位数字

使用子匹配操做

咱们将使用下表描述的子匹配操做来编写valid函数。上述代码的pattern有7个子表达式。每一个smatch对象会包含8个ssub_match元素。若是一个子表达式是完整匹配的一部分,则其对应的ssub_match对象的matched成员是true。

1558967336038

bool valid(const smatch &m)
{
    //若是区号前有一个左括号
    if(m[1].matched)
        //则区号后面有一个右括号,且以后紧跟剩余号码或一个空格
        return m[3].matched &&
                (m[4].matched == 0 || m[4].str() == " ");
    else
        //不然,区号后面不能有由括号,且后两个组成部分间的分隔符必须匹配
        return !m[3].matched &&
                m[4].str() == m[6].str();
}

使用regex_replace

正则表达式不只用在咱们但愿查找一个给定序列的时候,还用在当咱们想将找到序列替换成另外一个序列的时候。此时,能够调用regex_replace。它接受一个输入字符序列和一个regex对象,还接受一个描述咱们想要输出形式的字符串。


m.format(dest, fmt, mft)

m.format(fmt, mft)

使用格式字符串 fmt 生成格式化输出。匹配在m中,可选的match_flag_type标志在mft中。

第一个版本写入迭代器dest指向的目的位置并接受 fmt 参数,能够是一个string,也能够是表示字符数组范围的一对指针。

第二个版本返回一个string,保存输出,并接受 fmt 参数,能够是一个string,也能够是一个指向空字符结尾的字符数组指针。

mft的默认值为format_default


regex_replace(dest, seq, r, fmt ,mft)

regex_replace( seq, r, fmt , mft)

遍历seq,用regex_search查找和regex对象r匹配的子串。使用格式字符串fmt和可选match_flag_type标志来生成输出

第一个版本写入迭代器dest指向的目的位置并接受一对迭代器seq表示范围。

第二个版本返回一个string,保存输出,且seq便可以是一个string,也能够是一个指向空字符结尾的字符数组指针。

在全部状况下, fmt便可以是一个string,也能够是一个指向空字符结尾的字符数组指针。mft的默认值为match_default


替换字符串由咱们想要的字符组合与匹配的子串对于的子表达式而组成。咱们用一个符号$后跟子表达式的索引号来表示一个特定的子表达式:

string phone = 
    "(\\()?\\{d}{3}(\\))?([-. ])?(\\{d}{3})([-. ])?(\\{d}{4})";
string fmt = "$2.$5.$7";
regex r(phone);
string number = "(908) 555-1800";
cout << regex_replace(number, r, fmt) << endl;

Output

908.555.1800

参考:《C++ Primer》 第五版

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息