1、介绍
正则表达式是一种描述字符序列的方法,下面介绍C++11正则表达式库(RE)库的使用。
其包含的组件如下:
regex //表示一个正则表达式的类
regex_match //将一个字符与一个正则表达式匹配
regex_search //寻找第一个与正则表达式匹配的子序列
regex_replace //使用给定格式替换一个正则表达式
sregex_iterator //迭代器适配器,调用regex_search来遍历一个string中所有匹配的字串
smatch //容器类,保存在string中搜索的结果
ssub_match //string中匹配的子表达式的结果
2、正则表达式的使用
从一个例子开始,查找违反拼写规则:i除非在c之后,否则必须在e之前的单词。
string pattern("[^c]ei");
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern);
smatch ret;
string reg_test = "receipt freind theif receive";
if (regex_search(reg_test, ret, r))
cout << "find the str: " <<ret.str() << endl;
首先声明我们需要使用的pattern,即正则表达式;然后定义regex 和smatch ,调用regex_search函数查找对应的字符串中满足pattern的字串。然后调用smatch.str()输出。C++11中的正则表达式默认使用的正则表达式语法是ECMAScript。
下面列出regex常用的一些选项:
regex r(re) //re可以是一个正则表达式,一个string,一个表示字符范围的迭代器对,一个指向空字符结尾的字符数组的指针,一个字符指针和一个计数器或者是一个花括号包围的字符列表
regex r(re, f) //f是指出对象如何处理的标志
r = re //将r中正则表达式替换为re
r.assign(re, f); //与运算符 = 效果相同
r.mark_count(); //r中子表达式的数目
r.flags(); //返回r的标志集合
下面是标志集合:
icase 忽略大小写
nosubs 不保存匹配的子表达式
optimize 执行速度优先于构造速度
ECMAScript 使用ECMA-262语法
basic 使用POSIX基本的正则表达式语法
extended 使用POSIX扩张的正则表达式语法
awk 使用POSIX版本的awk语言的语法
grep 使用POSIX版本的grep语言的语法
grep 使用POSIX版本的egrep语言的语法
接下来再看一个例子,查找后缀为.cpp .cc. cxx的文件:
array<string, 5> as5{"1.cpp", "sdja.txt", "qwei91.cxx", "sdauhd.c", "asdjh.cc"};
regex r1("([[:alnum:]]+)\\.(cpp|cxx|cc)$", regex::icase); //在第一个表达式中加了()就变成了子表达式
smatch ret1;
for (auto e : as5)
{
if(regex_search(e, ret1, r1))
cout << "find the cpp\\cxx\\cc file: " << ret1.str() << endl;
}
在编写正则表达式的时候如果写错了,比如缺少括号这样的错误,在编译阶段是不会报错的,因为正则表达式本身相当于一个简单语言设计的“程序”。所以如果正则表达式写错了,只会在运行阶段,抛出异常。C++11中定义的正则表达式异常有13个,用到的时候再去查吧。
3、匹配与Regex迭代器类型
在使用正则表达式匹配字符串的时候,我们可以使用sregex_iterator来获得所有的匹配。
下面是sregex_iterator的操作:
sregex_iterator it(b,e,r); //遍历迭代器b和e表示的string,它调用sregex_search(b,e,r)将it定位到输入中第一个匹配的位置
sregex_iterator end; //尾后迭代器
*it //返回一个smatch对象的引用
it-> //指向smatch对象的指针
++it //自加
it++
it1 == it2; //如果两个sregex_iterator都是尾后迭代器,则它们相等。两个非尾后迭代器是从相同的输入序列和regex对象构造,则它们相等
it1 != it2;
当我们将一个sregex_iterator绑定到一个string和一个regex对象时,迭代器自动定位到给定string中第一个匹配位置。
下面是一个使用sregex_iterator的例子:
//使用regex_iterator,输出所有的匹配结果,并且输出上下文
string pattern1("[^c]ei");
pattern1 = "[[:alpha:]]*" + pattern1 + "[[:alpha:]]*";
regex r2(pattern1, regex::icase);
string reg_test = "receipt freind theif receive";
for (sregex_iterator it(reg_test.begin(), reg_test.end(), r2), end_it; it != end_it; ++it)
{
auto pos = it->prefix().length();
pos = pos > 40 ? pos - 40 : 0;
cout << it->prefix().str().substr(pos)
<< "\n\t\t>>>" << it->str() << "<<<\n"
<< it->suffix().str().substr(0, 40) << endl;
}
上面的例子中使用sregex_iterator输出所有匹配的结果,并且使用prefix()和suffix()输出匹配位置的上下文。
下面是smatch的常用操作:
m.ready(); //如果已经通过调用regex_serach或者regex_match设置了m,则返回true;否则返回false。如果ready返回false,则对m的操作是未定义的
m.size(); //如果匹配失败,则返回0;否则返回最近一次匹配的正则表达式中子表达式的数目
m.empty(); //若m.size()为0,则返回true
m.prefix(); //一个ssub_match对象,表示当前匹配之前的序列
m.suffix(); //一个ssub_match对象,表示当前匹配之后的序列
m.format(...);
m.length(n); //第n个匹配的子表达式的大小,0表示整个匹配
m.position(n); //第n个子表达式距离序列开始的距离
m.str(n); //第n个子表达式匹配的string
m[n]; //第n个匹配的子表达式的ssub_match对象
m.begin(), m.end(); //表示匹配范围的迭代器
m.cbegin(), m.cend(); //表示匹配范围的常迭代器
4、使用匹配的子表达式
字符串使用正则表达式匹配之后有许多的子表达式,常用于进行数据验证或者格式转换。
看下面的例子:
bool phone_valid(const smatch& m)
{
//如果区号前有一个左括号
if (m[1].matched)
//return m[3].matched && (m[4].matched == 0 || m[4].str() == " ");
return m[3].matched;
else
//return !m[3].matched && m[4].str() == m[6].str();
return !m[3].matched;
}
//使用正则表达式检查电话号码是否合法
string reg_phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
regex r3(reg_phone);
//smatch m;
string reg_s = "0201234567, 020.1234567, 020-1234567, 020-123456789, (020)-1234567, (020).1234567, (020) 1234567, (020)-12345678910, (0201234567, 020)1234567, 12345678910, 020s1234567, 020 s123456, 020/1234657, +86 1234567, +86.1234567, +86-1234567";
for (sregex_iterator it(reg_s.begin(), reg_s.end(), r3), end_it; it != end_it; ++it)
{
if(phone_valid(*it))
cout << "valid: " << it->str() << endl;
else
cout << "not valid: " << it->str() << endl;
}
使用regex_replace,这个对象可以用于替换字符串中的特定字符。
m.format(dest, fmt, mft)
m.format(fmt, mft);
regex_replace(dest, seq, r, fmt, mft);
regex_replace(seq, r, fmt, mft);
上面的几个操作是regex_replace的常用操作,光看命名就很好理解。
下面用一个例子直接展示regex_replace的使用:
string reg_phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
string reg_replace_test = "(654) 123-7856";
regex reg_replace(reg_phone);
string regex_replace_fmt = "$2-$5-$7";
cout << regex_replace(reg_replace_test, reg_replace, regex_replace_fmt) << endl;
上述例子将电话的格式转换成用 - 连接的形式。
标准库中定义了标志来直到如何处理正则表达式,在替换过程中也是一样的,定义了标志来控制匹配或格式的标志。这些标志可以传递给函数regex_search或regex_match或是类smatch的fomat成员。这些值定义在命名空间std::regex_constants中。
match_default 等价于format_default
match_not_bol 不将首字符作为行首处理
match_not_eol 不将尾字符作为行首处理
match_not_bow 不将首字符作为单词首处理
match_not_eow 不将尾字符作为单词尾处理
match_any 如果存在多个匹配,则返回任意一个匹配
match_not_null 不匹配任何空序列
match_continuous 匹配必须从输入的首字符开始
match_prev_avail 输入序列包含第一个匹配之前的内容
format_default 用ECMAScript规则替换字符串
format_sed 用POSIX sed规则替换字符串
format_no_copy 不输出输入序列中未匹配的部分
format_first_only 只替换子表达式的第一次出现的
如修改上面的替换例子:
string reg_phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
string reg_replace_test = "(654) 123-7856";
regex reg_replace(reg_phone);
string regex_replace_fmt = "$2-$5-$7";
cout << regex_replace(reg_replace_test, reg_replace, regex_replace_fmt, format_no_copy ) << endl;
这样只会输出regex_replace拷贝的文本,而不会输出整个输入序列。
正则表达式的使用建议
(1)避免创建不必要的正则表达式,正则表达式的编译是一个非常慢的操作,运行时开销也比较大。
(2)正则表达式的的类型要与输入的类型匹配,如输入类型是string,使用正则表达式的类为regex, smatch等;如输入类型为const char,使用正则表达式的类为:regex,cmatch等;如输入类型为wstring,使用正则表达式的类为:wregex,wsmatch等;如输入类型为const wchar_t,使用正则表达式的类为:wregex,wcmatch等;
总结
C++11新标准引入了正则表达式库,在C++代码里面可以直接使用标准库的正则表达式工具,对于字符串的处理非常方便。但是因为正则表达式的开销比较大,并且在编译阶段是不会报错的,所以应该避免不必要的使用以及做好异常处理。