C++正则表达式简单用法

最近学习了一下C++11引入的正则表达式用法,对于处理字符串问题带来很大方便。下面基于几个简单应用介绍一下用法:

(1) 判断字符串是否匹配某一模式,例如,判断输入字符串是否符合“5-15位数字,并且首位不能是0”这一模式。

#include 
using namespace std;

int main()
{
    string str;
    getline(cin, str);
    //R"(字符串)" 是c++11引入的新特性,字符串中的\将不再被解释为转义字符,因此如果需要\d表示[0-9],可以直接使用'\d',降低阅读难度。
    regex pattern(R"([1-9]\d{4,14})");
    //如果不使用R"(字符串)"的用法,那么需要注意转义字符需要\\,如下
    //regex pattern("[1-9]\\d{4,14}");
    cout << (regex_match(str, pattern) ? "yes" : "no") << endl;
    return 0;
}

这里使用的是regex_match函数,它将字符串s与模式re进行完全匹配,如果完全匹配成功返回true,否则返回false。

bool regex_match(const string& s, const regex& re)

 

(2) 依照模式分割字符串,例如,将字符串按照空格分割成若干个子串。

其实C++并没有提供split函数,但是可以利用正则表达式找到符合模式的子串。因此可以将这一问题进行转化,也就是找到字符串中所有不含空格的子串,那么也就完成了分割的任务。

#include 
using namespace std;

int main()
{
    string str;
    getline(cin, str);
    regex pattern(R"([^\s]+)");
   
    for(sregex_token_iterator it(str.begin(), str.end(), pattern), e; it != e; it++) cout << *it << endl;
    return 0;
}

其中sregex_token_iterator是对string类特化的regex_token_iterator类,复杂用法还在摸索中,但是对于类似于匹配指定模式的子串这一类问题,可以记住这样使用就行。

有的时候需要需要对单词边界进行限制,例如在字符串中找到所有长度为3的单词,需要在模式中加入'\b'定位符,意为单词边界:

#include 
using namespace std;

int main()
{
    string str = "abcd efg hijk lmn";
    regex pattern(R"(\b[a-z]{3}\b)");
    for(sregex_token_iterator it(str.begin(), str.end(), pattern), e; it != e; it++) cout << *it << " ";
    return 0;
}

 

(3)将字符串中连续的相同字符只保留一个。

#include 
using namespace std;

int main()
{
    string str = "abbccc";
    regex pattern(R"((\w)\1+)");
    str =  regex_replace(str, pattern, "$1");
    cout << str << endl;
    return 0;
}

这里用到的是 regex_replace方法,它将捕获到的匹配re的字符串,替换成正则表达式匹配字符串fmt。注意它的返回值才是替换后的字符串。

string regex_replace(const string& s, const regex& re, const string& fmt)

在示例中,连续相同的字符的模式是 ”(\w)\1+“,第一个(\w)缓存了某个字符。通过(),将\w匹配到的字符进行缓存,并且记为第一组。\1是反向引用,引用第一组的结果,也就是(\w)匹配到的字符,因此\1代表这一字符与前一字符相同,最后的+表示可能有多个。而fmt是"$1",意为使用第一组,也即(\w)匹配到的结果,进行替换。因此,"bb"将会被替换为'b',”ccc"将会被替换为'c'。

关于反向引用以及组的概念,可以参考:https://www.runoob.com/regexp/regexp-syntax.html,摘录如下:

对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 \n 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

 

(4)获取分组匹配的结果。例如,对于字符串"http://www.runoob.com:80/html/html-tutorial.html",希望分别获取协议(http)、域名(www.runoob.com),端口号(80)和指定的路径(/html/html-tutorial.html)。

#include 
using namespace std;

int main()
{
    string str = "http://www.runoob.com:80/html/html-tutorial.html";
    regex pattern(R"((\w+)://([^/:]+)(:\d*)?([^#\s]*))");
    smatch result;
    if(regex_match(str, result, pattern))
    {
        for (int i = 1; i < 5; ++i)
        {
            cout << result[i] << endl;
        }
    }
    return 0;
}

这里使用regex_match函数,它将字符串s与模式re进行完全匹配,并将匹配到的结果按分组存储到smatch类型的变量m中。

bool regex_match(const string& s, smatch& m, const regex& re)

如果能够完全匹配(regex_match返回true),m[0]存储的是整个字符串,m[1]~m[n]存储的是第1~n组的匹配结果。

 

下面是几道利用正则表达式可以很快解决的问题:

(A)在一行内给出一组ip地址,以空格分隔。要求按照ip地址分类顺序进行排序,如2.2.2.2排在10.10.10.10前面,10.10.10.10排在198.0.0.1前面。

//思路

按照空格分隔提取字符串的方式上面已经介绍过了,这里的问题主要是如何保证排序的顺序。不能直接使用字符串顺序进行排序,那样会使得10.10.10.10排在2.2.2.2前面,不满足要求。之所以会出现这种情况的原因是数字的位数不统一。可以想到,如果将每个数字都前缀0使其成为3位数,那么就可以使用字符串顺序进行排序。

因此,需要:① 每个数字前面补上两个0。 ② 每个数字只保留3位。 ③按照空格分割,提取字符串。 ④ 按照字符串顺序排序。⑤输出时删去前导0。

而第①②⑤步,都需要用到替换操作和反向引用的结合。

#include 
using namespace std;

int main()
{
    string str = "192.168.1.254 102.49.23.013 10.10.10.10 2.2.2.2 8.109.90.30";
    //所有数字前面补2个0
    str = regex_replace(str, regex(R"((\d+))"), "00$1");
    //所有数字只保留3位
    str = regex_replace(str, regex(R"(0*(\d{3}))"), "$1");
    regex pattern(R"([^\s]+)");
    set st;
    for(sregex_token_iterator it(str.begin(), str.end(),pattern), e; it != e; it++) st.insert(*it);
    for(auto it : st)
    {
        //所有数字删去前导0
        cout << regex_replace(it, regex(R"(0*(\d+))"), "$1") << endl;
    }
    return 0;
}

 

(B)链接:https://www.nowcoder.com/questionTerminal/85fc7b237a254acdb5aca673a319be16
函数match检查字符串str是否匹配模板pattern,匹配则返回0,否则返回-1。模板支持普通字符(a-z0-9A-Z)及通配符?*。普通字符匹配该字符本身,?匹配任意一个字符,*匹配任意多个任意字符。

//思路

使用regex_match函数可以很快判断出str是否匹配模板,问题是如何将题目给的pattern转换为符合正则表达式模板的形式。显然,如果不是?或者*,那么就是普通的字符,可以直接加到模板中。如果是'?',按题意,匹配任意一个字符,其实对应正则表达式中的'.'号。'*'匹配任意多个字符,其实对应正则表达式中的(.*)。按照这一规则,就可以将pattern转换为符合正则表达式的形式。

#include 
using namespace std;

int main()
{
    string s1, s2;
    getline(cin, s1);
    getline(cin, s2);
    string pattern;
    for(auto c : s2)
    {
        if(c == '?') pattern += '.';
        else if(c == '*') pattern += ".*";
        else pattern += c;
    }
    cout << (regex_match(s1, regex(pattern)) ? "match" : "unmatch");
    return 0;
}

 

(C)链接:https://www.nowcoder.com/questionTerminal/54fbe83400964042a237eca44610a308


编写一个函数来验证输入的字符串是否是有效的 IPv4 或 IPv6 地址

IPv4 地址由十进制数和点来表示,每个地址包含4个十进制数,其范围为 0 - 255, 用(".")分割。比如,172.16.254.1;
同时,IPv4 地址内的数不会以 0 开头。比如,地址 172.16.254.01 是不合法的。

IPv6 地址由8组16进制的数字来表示,每组表示 16 比特。这些组数字通过 (":")分割。比如,  2001:0db8:85a3:0000:0000:8a2e:0370:7334 是一个有效的地址。而且,我们可以加入一些以 0 开头的数字,字母可以使用大写,也可以是小写。所以, 2001:db8:85a3:0:0:8A2E:0370:7334 也是一个有效的 IPv6 address地址 (即,忽略 0 开头,忽略大小写)。

然而,我们不能因为某个组的值为 0,而使用一个空的组,以至于出现 (::) 的情况。 比如, 2001:0db8:85a3::8A2E:0370:7334 是无效的 IPv6 地址。
同时,在 IPv6 地址中,多余的 0 也是不被允许的。比如, 02001:0db8:85a3:0000:0000:8a2e:0370:7334 是无效的。

说明: 你可以认为给定的字符串里没有空格或者其他特殊字符。

//思路

按照给定规则利用正则表达式匹配即可,需要注意的是由于有附加限制,例如ipv4中的数字不能大于255,ipv6中的数字不能是全零(长度大于1)等等,所以利用smatch存储分组结果,再对每个分组进行判断。

#include 
using namespace std;

bool checkIPv4(const string& str)
{
    regex pattern(R"((\d+).(\d+).(\d+).(\d+))");
    smatch m;
    if(regex_match(str, m, pattern))
    {
        for (int i = 1; i < 5; ++i)
        {
            string s = m[i];
            if(stoi(s) > 255 || (s != "0" && s[0] == '0')) return false;
        }
        return true;
    }
    return false;
}

bool allZero(const string& s)
{
    for(char c : s) if(c != '0') return false;
    return true;
}

bool checkIPv6(const string& str)
{
    regex pattern(R"(([\da-fA-F]+):([\da-fA-F]+):([\da-fA-F]+):([\da-fA-F]+):([\da-fA-F]+):([\da-fA-F]+):([\da-fA-F]+):([\da-fA-F]+))");
    smatch m;
    if(regex_match(str, m, pattern))
    {
        for (int i = 1; i < 9; ++i)
        {
            string s = m[i];
            if(s.size() > 4 || (s.size() > 1 && allZero(s))) return false;
        }
        return true;
    }
    return false;
}

int main()
{
    string str;
    getline(cin, str);
    if(checkIPv4(str)) cout << "IPv4" << endl;
    else if(checkIPv6(str)) cout << "IPv6" << endl;
    else cout << "Neither" << endl;
    return 0;
}

(D) 链接:https://www.nowcoder.com/questionTerminal/d87d5dacfd4e4d45b636132f1af85c2f
有一个列表用来描述各JSON子节点是否允许用户编辑。如下:
Y /mem/daemons/findme
N /mem/daemons
Y /mem
如果有设置用户对某个子节点的权限,则实际权限为该设定权限,否则继承其父节点的可访问性,对根节点的默认访问权限为N。

给出一些已知权限的节点列表,并给出一些待检查的节点列表,要求判断待检查节点的权限并输出。

//思路

首先根据已知节点的权限列表初始化map,map中存放的是已知节点的权限,用正则表达式‘\w+’遍历到最后一个字母组合,例如对于/mem/daemons/findme,应该存放的是 findme 的权限。

对于待检查的节点,需要从后向前检查是否在map中,如果匹配就输出权限。如果检查完还是没有匹配到map中的任何节点,那么就要看根节点'/'的访问权限。可以用栈实现从后向前检查这一功能,先利用正则将所有的有效字母组合压栈,然后再出栈遍历即可。

#include 
using namespace std;

int main()
{
    int n;
    cin >> n;
    map mp;
    vector toCheck(n);
    for (int i = 0; i < n; ++i)
    {
        cin >> toCheck[i];
    }
    int t;
    cin >> t;
    for (int i = 0; i < t; ++i)
    {
        string s1, s2;
        cin >> s1 >> s2;
        if(s2 == "/" && mp.count(s2) == 0) mp[s2] = (s1 == "Y" ? true : false);
        else
        {
            regex re(R"(\w+)");
            string last;
            for(sregex_token_iterator it(s2.begin(), s2.end(), re), e; it != e; it++)
            {
                last = *it;
            }
            if(mp.count(last) == 0) mp[last] = (s1 == "Y" ? true : false);
        }
    }
    for(string s : toCheck)
    {
        regex re(R"(\w+)");
        stack st;
        bool find = false;
        for(sregex_token_iterator it(s.begin(), s.end(), re), e; it != e; it++)
        {
            st.push(*it);
        }
        while(!st.empty())
        {
            if(mp.count(st.top()) > 0)
            {
                cout << (mp[st.top()] ? "Y" : "N") << endl;
                find = true;
                break;
            }
            else st.pop();
        }
        if(!find) cout << (mp.count("/") ? (mp["/"] ? "Y" : "N") : "N") << endl;
    }
    return 0;
}

 

你可能感兴趣的:(C++正则表达式简单用法)