c正则表达式提取字符串(详解c正则表达式应用)

【CPP开发者导读】:在讨论正则表达式之前,先介绍另外一话题:字符串处理能力是所有程序员的基本功,例如在自然语言处理领域,就经常会遇到字符串处理的问题,当数据在输入到机器学习模型之前和之后,要涉及到大量的预处理和后处理工作,比如要在预处理阶段过滤掉一部分字符,或者把数据进行规范化,在后处理阶段可能还要纠正模型的一些错误和不足。尤其是在工业界,这些工作占的比重还会更大。这时正则表达式的作用显得极其重要。

而在过去写C 程序的时候,当需要用到正则表达式的时候,由于C 本身的标准库不支持,要么使用第三方库,要么自己编写程序实现,不但非常的麻烦而且容易出错难以维护。

因此从C 11开始,引入对正则表达式的支持,使得C 具备了更多的现代语言特性。

本文适合急需提高字符串处理能力的C 程序员阅读,利用从本文所学到的知识大大提高开发效率,达到事半功倍的效果。

以下是正文

首先你得分析一下其对应规则,依次列出:

长度大于5,小于等于11;

首位不能为0;

是否为纯数字?

规则既列,接着就该尝试实现了,那么用什么来表示字符串呢?在C 中,最容易想到的就是string了,其中提供了许多成员函数可以处理字符串,所以有了如下实现:

1std::stringqq; 2std::cin>>qq; 3 4//1.判断位数是否合法 5if(qq.length()>=5&&qq.length()<=11) 6{ 7//2.判断是否非’0’开头 8if(qq[0]!=’0′) 9{10//3.判断是否为纯数字11autopos=std::find_if(qq.begin(),qq.end(),[](constchar&ch){12returnch<‘0’||ch>’9’;13});14if(pos==qq.end())15std::cout<<“valid.\n”;16}17}

1std::regexqq_reg(“[1-9]\\d{4,11}”);2boolret=std::regex_match(qq,qq_reg);3std::cout<<(ret?”valid”:”invalid”)<<std::endl;

是不是超级方便呢?那么接下来便来看看如何使用「正则表达式」。

正则程序库(regex)

「正则表达式」就是一套表示规则的式子,专门用来处理各种复杂的操作。

std::regex是C 用来表示「正则表达式」(regular expression)的库,于C 11加入,它是class std::basic_regex<>针对char类型的一个特化,还有一个针对wchar_t类型的特化为std::wregex。

正则文法(regex syntaxes)

std::regex默认使用是ECMAScript文法,这种文法比较好用,且威力强大,常用符号的意义如下:

符号意义^匹配行的开头$匹配行的结尾.匹配任意单个字符[…]匹配[]中的任意一个字符(…)设定分组\转义字符\d匹配数字[0-9]\D\d 取反\w匹配字母[a-z],数字,下划线\W\w 取反\s匹配空格\S\s 取反 前面的元素重复1次或多次*前面的元素重复任意次?前面的元素重复0次或1次{n}前面的元素重复n次{n,}前面的元素重复至少n次{n,m}前面的元素重复至少n次,至多m次|逻辑或

上面列出的这些都是非常常用的符号,靠这些便足以解决绝大多数问题了。

匹配(Match)

字符串处理常用的一个操作是「匹配」,即字符串和规则恰好对应,而用于匹配的函数为std::regex_match(),它是个函数模板,我们直接来看例子:

1std::regexreg(“<.*>.*</.*>”); 2boolret=std::regex_match(“<html>value</html>”,reg); 3assert(ret); 4 5ret=std::regex_match(“<xml>value<xml>”,reg); 6assert(!ret); 7 8std::regexreg1(“<(.*)>.*</\\1>”); 9ret=std::regex_match(“<xml>value</xml>”,reg1);10assert(ret);1112ret=std::regex_match(“<header>value</header>”,std::regex(“<(.*)>value</\\1>”));13assert(ret);1415//使用basic文法16std::regexreg2(“<\\(.*\\)>.*</\\1>”,std::regex_constants::basic);17ret=std::regex_match(“<title>value</title>”,reg2);18assert(ret);

这个小例子使用regex_match()来匹配xml格式(或是html格式)的字符串,匹配成功则会返回true,意思非常简单,若是不懂其中意思,可参照前面的文法部分。

对于语句中出现\\,是因为\需要转义,C 11以后支持原生字符,所以也可以这样使用:

1std::regexreg1(R”(<(.*)>.*</\1>)”);2autoret=std::regex_match(“<xml>value</xml>”,reg1);3assert(ret);

但C 03之前并不支持,所以使用时要需要留意。

若是想得到匹配的结果,可以使用regex_match()的另一个重载形式:

1std::cmatchm; 2autoret=std::regex_match(“<xml>value</xml>”,m,std::regex(“<(.*)>(.*)</(\\1)>”)); 3if(ret) 4{ 5std::cout<<m.str()<<std::endl; 6std::cout<<m.length()<<std::endl; 7std::cout<<m.position()<<std::endl; 8} 910std::cout<<“—————-“<<std::endl;1112//遍历匹配内容13for(autoi=0;i<m.size(); i)14{15//两种方式都可以16std::cout<<m[i].str()<<“”<<m.str(i)<<std::endl;17}1819std::cout<<“—————-“<<std::endl;2021//使用迭代器遍历22for(autopos=m.begin();pos!=m.end(); pos)23{24std::cout<<*pos<<std::endl;25}

输出结果为:

1<xml>value</xml> 216 30 4—————- 5<xml>value</xml><xml>value</xml> 6xmlxml 7valuevalue 8xmlxml 9—————-10<xml>value</xml>11xml12value13xml

cmatch是class template std::match_result<>针对C字符的一个特化版本,若是string,便得用针对string的特化版本smatch。同时还支持其相应的宽字符版本wcmatch和wsmatch。

在regex_match()的第二个参数传入match_result便可获取匹配的结果,在例子中便将结果储存到了cmatch中,而cmatch又提供了许多函数可以对这些结果进行操作,大多方法都和string的方法类似,所以使用起来比较容易。

m[0]保存着匹配结果的所有字符,若想在匹配结果中保存有子串,则得在「正则表达式」中用()标出子串,所以这里多加了几个括号:

1std::regex(“<(.*)>(.*)</(\\1)>”)

这样这些子串就会依次保存在m[0]的后面,即可通过m[1],m[2],…依次访问到各个子串。

搜索(Search)

「搜索」与「匹配」非常相像,其对应的函数为std::regex_search,也是个函数模板,用法和regex_match一样,不同之处在于「搜索」只要字符串中有目标出现就会返回,而非完全「匹配」。

还是以例子来看:

1std::regexreg(“<(.*)>(.*)</(\\1)>”); 2std::cmatchm; 3autoret=std::regex_search(“123<xml>value</xml>456”,m,reg); 4if(ret) 5{ 6for(auto&elem:m) 7std::cout<<elem<<std::endl; 8} 910std::cout<<“prefix:”<<m.prefix()<<std::endl;11std::cout<<“suffix:”<<m.suffix()<<std::endl;

输出为:

1<xml>value</xml>2xml3value4xml5prefix:1236suffix:456

这儿若换成regex_match匹配就会失败,因为regex_match是完全匹配的,而此处字符串前后却多加了几个字符。

对于「搜索」,在匹配结果中可以分别通过prefix和suffix来获取前缀和后缀,前缀即是匹配内容前面的内容,后缀则是匹配内容后面的内容。

那么若有多组符合条件的内容又如何得到其全部信息呢?这里依旧通过一个小例子来看:

1std::regexreg(“<(.*)>(.*)</(\\1)>”); 2std::stringcontent(“123<xml>value</xml>456<widget>center</widget>hahaha<vertical>window</vertical>theend”); 3std::smatchm; 4autopos=content.cbegin(); 5autoend=content.cend(); 6for(;std::regex_search(pos,end,m,reg);pos=m.suffix().first) 7{ 8std::cout<<“—————-“<<std::endl; 9std::cout<<m.str()<<std::endl;10std::cout<<m.str(1)<<std::endl;11std::cout<<m.str(2)<<std::endl;12std::cout<<m.str(3)<<std::endl;13}

输出结果为:

1—————- 2<xml>value</xml> 3xml 4value 5xml 6—————- 7<widget>center</widget> 8widget 9center10widget11—————-12<vertical>window</vertical>13vertical14window15vertical

此处使用了regex_search函数的另一个重载形式(regex_match函数亦有同样的重载形式),实际上所有的子串对象都是从std::pair<>派生的,其first(即此处的prefix)即为第一个字符的位置,second(即此处的suffix)则为最末字符的下一个位置。

一组查找完成后,便可从suffix处接着查找,这样就能获取到所有符合内容的信息了。

分词(Tokenize)

而在C 的正则中,把这种操作称为Tokenize,用模板类regex_token_iterator<>提供分词迭代器,依旧通过例子来看:

1std::stringmail(“123@qq.vip.com,456@gmail.com,789@163.com,abcd@my.com”);2std::regexreg(“,”);3std::sregex_token_iteratorpos(mail.begin(),mail.end(),reg,-1);4decltype(pos)end;5for(;pos!=end; pos)6{7std::cout<<pos->str()<<std::endl;8}

1123@qq.vip.com2456@gmail.com3789@163.com4abcd@my.com

sregex_token_iterator是针对string类型的特化,需要注意的是最后一个参数,这个参数可以指定一系列整数值,用来表示你感兴趣的内容,此处的-1表示对于匹配的正则表达式之前的子序列感兴趣;而若指定0,则表示对于匹配的正则表达式感兴趣,这里就会得到“,”;还可对正则表达式进行分组,之后便能输入任意数字对应指定的分组,大家可以动手试试。

替换(Replace)

最后一种操作称为「替换」,即将正则表达式内容替换为指定内容,regex库用模板函数std::regex_replace提供「替换」操作。

现在,给定一个数据为”he…ll..o, worl..d!”, 思考一下,如何去掉其中误敲的“.”?

有思路了吗?来看看正则的解法:

1chardata[]=”he…ll..o,worl..d!”;2std::regexreg(“\\.”);3//output:hello,world!4std::cout<<std::regex_replace(data,reg,””);

我们还可以使用分组功能:

1chardata[]=”001-Neo,002-Lucia”;2std::regexreg(“(\\d )-(\\w )”);3//output:001name=Neo,002name=Lucia4std::cout<<std::regex_replace(data,reg,”$1name=$2″);

当使用分组功能后,可以通过N来得到分组内容,这个功能挺有用的。

这个需求在注册登录时常有用到,用于检测用户输入的合法性。

若是对匹配精确度要求不高,那么可以这么写:

1std::stringdata=”123@qq.vip.com,456@gmail.com,789@163.com,abcd@my.com”;2std::regexreg(“\\w @\\w (\\.\\w ) “);34std::sregex_iteratorpos(data.cbegin(),data.cend(),reg);5decltype(pos)end;6for(;pos!=end; pos)7{8std::cout<<pos->str()<<std::endl;9}

这里使用了另外一种遍历正则查找的方法,这种方法使用regex iterator来迭代,效率要比使用match高。这里的正则是一个弱匹配,但对于一般用户的输入来说没有什么问题,关键是简单,输出为:

1123@qq.vip.com2456@gmail.com3789@163.com4abcd@my.com

1std::stringdata=”123@qq.vip.com,\ 2456@gmail.com,\ 3789@163.com.cn.mail,\ 4abcd@my.com,\ 5Abc0_@aAa1.123.456.789\ 6haha@163.com.cn.com.cn”; 7std::regexreg(“[a-zA-z0-9_] @[a-zA-z0-9] (\\.[a-zA-z] ){1,3}”); 8 9std::sregex_iteratorpos(data.cbegin(),data.cend(),reg);10decltype(pos)end;11for(;pos!=end; pos)12{13std::cout<<pos->str()<<std::endl;14}

输出为:

1123@qq.vip.com2456@gmail.com3789@163.com.cn.mail4abcd@my.com5haha@163.com.cn.com2. 匹配IP

有点晚了,便不详细解释了,这里直接给出答案,可供大家参考:

1std::stringip(“192.68.1.254102.49.23.01310.10.10.102.2.2.28.109.90.30”); 2 3std::cout<<“原内容为:\n”<<ip<<std::endl; 4 5//1.位数对齐 6ip=std::regex_replace(ip,std::regex(“(\\d )”),”00$1″); 7 8std::cout<<“位数对齐后为:\n”<<ip<<std::endl; 910//2.有0的去掉11ip=std::regex_replace(ip,std::regex(“0*(\\d{3})”),”$1″);1213std::cout<<“去掉0后为:\n”<<ip<<std::endl;1415//3.取出IP16std::regexreg(“\\s”);17std::sregex_token_iteratorpos(ip.begin(),ip.end(),reg,-1);18decltype(pos)end;1920std::set<std::string>ip_set;21for(;pos!=end; pos)22{23ip_set.insert(pos->str());24}2526std::cout<<“——\n最终结果:\n”;2728//4.输出排序后的数组29for(autoelem:ip_set)30{31//5.去掉多余的032std::cout<<std::regex_replace(elem,33std::regex(“0*(\\d )”),”$1″)<<std::endl;34}

输出结果为:

1原内容为: 2192.68.1.254102.49.23.01310.10.10.102.2.2.28.109.90.30 3位数对齐后为: 400192.0068.001.0025400102.0049.0023.000130010.0010.0010.0010002.002.002.002008.00109.0090.0030 5去掉0后为: 6192.068.001.254102.049.023.013010.010.010.010002.002.002.002008.109.090.030 7—— 8最终结果: 92.2.2.2108.109.90.301110.10.10.1012102.49.23.1313192.68.1.254

看精选C 技术文章 .加C 开发者专属圈子

↓↓↓

发表评论

登录后才能评论