美文网首页
Regular Expression

Regular Expression

作者: MrDecoder | 来源:发表于2023-10-30 20:06 被阅读0次

[TOC]

#1. 什么是正则表达式

正则表达式(Regular Expression,简称regex)是一些用来匹配和处理文本的字符串。正则表达式是用正则表达式语言创建的。与其他程序设计语言一样,正则表达式语言也有其特殊的语法和指令。

正则表达式的两种基本用途:搜索和替换。给定一个正则表达式,它要么匹配一些文本(进行一次搜索),要么匹配并替换一些文本(进行一次替换)。

正则表达式是文本处理方面功能最强大的工具之一。正则表达式语言用来构造正则表达式(最终构造出来的字符串就称为正则表达式),正则表达式用来完成搜索和替换操作。

#2. 匹配单个字符

2.1 匹配纯文本

正则表达式可以包含纯文本(甚至可以只包含纯文件)。例如,Ben。

#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "Hello, my name is Ben. Please visit my websit at http://wwww.forta.com/.";
    regex expression("Ben");
    smatch matches;
    if (regex_search(text, matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
    }
    return 0;
}

// Results:
// matches for 'Hello, my name is Ben. Please visit my websit at http://wwww.forta.com/.'
// Prefix: 'Hello, my name is '
// 0: Ben
// Suffix: '. Please visit my websit at http://wwww.forta.com/.'
字母的大小写问题

正则表达式是区分大小写的,所以Ben不匹配ben。不过,绝大多数正则表达式的实现也支持不区分字母大小写的匹配操作。

2.2 匹配任意字符

在正则表达式里,特殊字符(或字符集合)用来给出要搜索的东西。.字符(英文句号)可以匹配任何一个单个字符。

2.3 匹配特殊字符

.字符在正则表达式里有着特殊的含义。如果模式里需要一个.,就需要对它进行转义。\是一个元字符(metacharacter,表示“这个字符有特殊含义,而不是字符本身含义”)。

#3. 匹配一组字符

在正则表达式里可以使用元字符[]来定义一个字符集合。在使用[]定义的字符集合里,这两个元字符之间的所有字符都是该集合的组成部分,字符集合的匹配结果是能够与该集合里的任意一个成员相匹配的文本。

元字符[]用来定义一个字符集合,其含义是必须匹配该集合里的字符之一。定义一个字符集合的具体做法有两种:一是把所有的字符都列举出来;二是用元字符-以字符区间的方式给出。字符集合可以用元字符^来求非;这将把给定的字符集合强行排除在匹配操作以外,除了该字符集合里的字符,其他字符都可以被匹配。

3.1 匹配多个字符中的某一个

.字符可以匹配任意单个字符。例如.a可以匹配na和sa。如果需要匹配ca1.xls的字符串,.匹配就无法满足。在正则表达式里,我们可以使用元字符[]来定义一个字符集合。在使用[]定义的字符集合里,这两个元字符之间的所有字符都是该集合的组成部分,字符集合的匹配结果是能够与该集合里的任意一个成员相匹配的文本。

#include <Windows.h>
#include <boost/regex.hpp>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "sales1.xls orders3.xls sales2.xls na1.xls sa1.xls";
    regex expression("[ns]a.\.xls");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }

    return 0;
}

// matches for 'sales1.xls orders3.xls sales2.xls na1.xls sa1.xls'
// Prefix: 'sales1.xls orders3.xls sales2.xls '
// 0: na1.xls
// Suffix: ' sa1.xls'

// matches for 'sales1.xls orders3.xls sales2.xls na1.xls sa1.xls'
// Prefix: ' '
// 0: sa1.xls
// Suffix: ''

这里使用的正则表达式以[ns]开头;这个集合将匹配字符n或s(但不匹配字符c或其他字符)。[和]不匹配任何字符,它们只负责定义一个字符集合。接下来,正则表达式里的字符a将匹配一个a字符,.将匹配一个任意字符,\.将匹配.字符本身,xls将匹配字符串xls。

3.2 利用字符集合区间

在使用正则表达式的时候,会频繁地用到一些字符区间(0~9、A~Z等)。为了简化字符区间的定义,正则表达式提供了一个特殊的元字符,字符区间可以用-(连字符)来定义。

#include <Windows.h>
#include <boost/regex.hpp>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "na1.xls sa1.xls";
    regex expression("[ns]a[0-9]\.xls");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }

    return 0;
}

// matches for 'na1.xls sa1.xls'
// Prefix: ''
// 0: na1.xls
// Suffix: ' sa1.xls'

// matches for 'na1.xls sa1.xls'
// Prefix: ' '
// 0: sa1.xls
// Suffix: ''

模式[0-9]的功能与[0123456789]完全等价。

字符区间并不仅限于数字,以下这些都是合法的字符区间。

  • A-Z,匹配所有A到Z的所有大写字母。
  • a-z,匹配所有a到z的所有小写字母。
  • A-F,匹配所有A到F的所有大写字母。
  • A-z,匹配从ASCII字符A到ASCII字符z的所有字母。这个模式一般不常用,因为它还包含着[^等在ASCII字符表里排列在Za之间的字符。

3.3 取非匹配

字符集合通常用来指定一组必须匹配其中之一的字符。但在某些场合,我们需要反过来做:除了字符集合里的字符,其他字符都可以匹配。用元字符^来表明你想对一个字符集合进行取非匹配。

#4. 使用元字符

4.1 对特殊字符进行转义

元字符是一些在正则表达式里有着特殊含义的字符。英文句号.是一个元字符,它可以用来匹配任何一个单个字符。类似地,左方括号[也是一个元字符,它标志着一个字符集合的开始。

因为元字符在正则表达式里有着特殊的含义,所以这些字符就无法用来代表它们本身。对元字符进行转义需要用到\字符。这意味着\字符也是一个元字符,它的特殊含义是对其他元字符进行转义。

4.2 匹配空白字符

在进行正则表达式搜索的时候,我们经常会遇到需要对原始文本里的非打印空白字符进行匹配的情况。比如说,我们可能需要把所有的制表符找出来,或者我们需要把换行符找出来,这类字符很难直接被输入到一个正则表达式里,下表列出了这些特殊的元字符来输入它们。

元字符 说明
[\b] 回退(并删除)一个字符(Backspace键)
\f 换页符
\n 换行符
\r 回车符
\t 制表符
\v 垂直制表符

4.3 匹配特定的字符类别

正则表达式可以通过纯文本匹配特定的字符、通过.匹配任意单个字符、通过[]字符集合匹配多个字符中的某一个以及通过^取非匹配。

1. 匹配数字(与非数字)

[0-9][0123456789]的简写形式,它可以用来匹配任何一个数字。如果想匹配除数字以外的其他东西,那么把这个集合“反”过来写成[^0-9]就行了。

元字符 说明
\d 任何一个数字字符(等价于[0-9])
\D 任何一个非数字字符(等价于[^0-9])
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "myArray0";
    regex expression("myArray[\\d]");
    if (regex_match(text, expression))
    {
        cout << "Digital Matched." << endl;
    }
    return 0;
}
2. 匹配字母和数字(与非字母和数字)

字母和数字,A到Z(不分大小写)、数字0到9,再加上下划线字符(_),是另一种比较常用的字符集合。下表列出了用来匹配字母数字和非字母数字的类元字符。

元字符 说明
\w 任何一个字母数字字符(大小写均可)或下划线字符(等价于[a-zA-Z0-9_])
\W 任何一个非字母数字或非下划线字符(等价于[^a-zA-Z0-9_])
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "Hello World";
    regex expression("([\\w]+)[ ]([\\w]+)");
    if (regex_match(text, expression))
    {
        cout << "Word Matched." << endl;
    }
}
3. 匹配空白字符(与非空白字符)

另一种常见的字符类别是空白字符。下表列出了匹配所有空白字符的类元字符。

元字符 说明
\s 任何一个空白字符(等价于[\f\n\r\t\v])
\S 任何一个非空白字符(等价于[^\f\n\r\t\v])

#5. 重复匹配

5.1 有多少个匹配

1. 匹配一个或多个字符

要想匹配同一个字符(或字符集合)的多次重复,只要简单地给这个字符(或字符集合)加上一个+字符作为后缀就行了。+匹配一个或多个字符(至少一个;不匹配零个字符的情况)。

#include <regex>
#include <iostream>
#include <string>

int main() 
{
    using namespace std;

    string text = "henry.hu@nextlabs.com";
    regex expression("[\\w.]+@[\\w]+\.[\\w]+");
    if (regex_match(text, expression))
    {
        cout << "Email Matched." << endl;
    }
    return 0;
}

一般来说,当在字符集合里使用的时候,像.+这样的元字符将被解释为普通字符,不需要被转义,但转义了也没有坏处。

2. 匹配零个或多个字符

+匹配一个或多个字符,但不匹配零个字符,+最少也要匹配一个字符。如果想要匹配一个可有可无的字符可以用*元字符完成。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main() 
{
    using namespace std;

    string text = "Hello .henry.hu@nextlabs.com is my email address.";
    regex expression("\\w+[\\w.]*@[\\w]+\.\\w+");

    smatch matches;
    if (regex_search(text, matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
    }
    return 0;
}
// matches for 'Hello .henry.hu@nextlabs.com is my email address.'
// Prefix: 'Hello .'
// 0: henry.hu@nextlabs.com
// Suffix: ' is my email address.'
3. 匹配零个或一个字符

另一个非常有用的元字符是??只能匹配一个字符(或字符集合)的零次或一次出现,最多不超过一次。如果需要再一段文本里匹配某个特定的字符(或字符集合)而该字符可能出现、也可能不出现,?无疑是最佳的选择。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "The URL is http://www.forta.com/, to connect securely use https://www.forta.com/ instead.";
    regex expression("https?://[\\w./]+");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }
    return 0;
}

// matches for 'The URL is http://www.forta.com/, to connect securely use https://www.forta.com/ instead.'
// Prefix: 'The URL is '
// 0: http://www.forta.com/
// Suffix: ', to connect securely use https://www.forta.com/ instead.'

// matches for 'The URL is http://www.forta.com/, to connect securely use https://www.forta.com/ instead.'
// Prefix: ', to connect securely use '
// 0: https://www.forta.com/
// Suffix: ' instead.'

5.2 匹配的重复次数

正则表达式里的+、*和?解决了许多问题,但有些问题光靠它们还不够。

  • +*匹配的字符个数没有上限。我们无法为它们将匹配的字符个数设定一个最大值。
  • +*至少匹配零个或一个字符。我们无法为它们将匹配的字符个数另行设定一个最小值。
  • 如果只使用+*,我们无法把它们将匹配的字符个数设定为一个精确的数字。

为了解决这些问题并让程序员对重复性匹配有更多的控制,正则表达式语言提供了一个用来设定重复次数(interval)的语法。重复次数要用{}字符来给出,把数值写在它们之间。

1. 为重复匹配次数设定一个精确的值

如果你想为重复匹配的次数设定一个精确的值,把那个数字写在{}之间即可。比如说,{3}意味着模式里的前一个字符(或字符集合)必须在原始文本里连续重复出现3次才算是一个匹配;如果只重复了两次,则不算是一个匹配。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "#336633 TEXT=#FFFFFF";
    regex expression("#[a-fA-F0-9]{6}");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }
    return 0;
}

// matches for '#336633 TEXT=#FFFFFF'
// Prefix: ''
// 0: #336633
// Suffix: ' TEXT=#FFFFFF'

// matches for '#336633 TEXT=#FFFFFF'
// Prefix: ' TEXT='
// 0: #FFFFFF
// Suffix: ''
2. 为重复匹配设定一个区间

{}语法还可以用来为重复匹配次数设定一个区间,也就是为重复匹配次数设定一个最小值和一个最大值。这种区间必须以{2,4}这样的形式给出,{2,4}的含义是最少重复2次、最多重复4次。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "10/30/2023 10-30-2023 2/2/2 01-01-01";
    regex expression("\\d{1,2}[-\/]\\d{1,2}[-\/]\\d{2,4}");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }
    return 0;
}

// matches for '10/30/2023 10-30-2023 2/2/2 01-01-01'
// Prefix: ''
// 0: 10/30/2023
// Suffix: ' 10-30-2023 2/2/2 01-01-01'

// matches for '10/30/2023 10-30-2023 2/2/2 01-01-01'
// Prefix: ' '
// 0: 10-30-2023
// Suffix: ' 2/2/2 01-01-01'

// matches for '10/30/2023 10-30-2023 2/2/2 01-01-01'
// Prefix: ' 2/2/2 '
// 0: 01-01-01
// Suffix: ''

日期正则表达式的设置是为了检查它们的格式是否正确。\d{1,2}将匹配一个或两个数字字符(用来匹配日子和月份);\d{2,4}用来匹配年份;[-/]用来匹配日期值里的分隔符-或/。2/2/2不满足的原因是因为年份太短了。

3. 匹配“至少重复多少次”

{}语法的最后一种用法是给出一个最小的重复次数(但不必给出一个最大值)。{}的这种用法与我们用来重复匹配次数设定一个区间的{}语法很相似,只是省略了最大值部分而已。比如说,{3, }表示至少重复3次。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "1001: $896.80, 1002: $1290.69, 1003: $26.43";
    regex expression("\\d+: [$]\\d{3,}\.\\d{2}");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }

    return 0;
}

// matches for '1001: $896.80, 1002: $1290.69, 1003: $26.43'
// Prefix: ''
// 0: 1001: $896.80
// Suffix: ', 1002: $1290.69, 1003: $26.43'

// matches for '1001: $896.80, 1002: $1290.69, 1003: $26.43'
// Prefix: ', '
// 0: 1002: $1290.69
// Suffix: ', 1003: $26.43'

例子中的正则表达式用来匹配交易金额内容,它的格式由订单号和订单金额组成。我们构造的正则表达式首先使用了一个\d+:来匹配订单号。模式[]\d{3,}\\.\d{2}用来匹配金额部分:[]匹配、\d{3,}匹配至少三位数字(也就是所有大于或等于100美元的金额)、\.匹配.、\d{2}匹配小数点后面的两位数字。

5.3 防止过度匹配

?只能匹配零个或一个字符,{n}{m,n}也有一个重复次数的上限;下面的例子演示了过度匹配的结果,文本包含了两个HTML<B>标签;而我们的任务是用一个正则表达式把那两个<B>标签里的文本匹配出来(为了对这些文本进行替换或排版等)。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;
    
    string text = "This offer is not available to customers living in <B>AK</B> and <B>HI</B>.";
    regex expression("<[Bb]>.*</[Bb]>");
    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }

    return 0;
}

// matches for 'This offer is not available to customers living in <B>AK</B> and <B>HI</B>.'
// Prefix: 'This offer is not available to customers living in '
// 0: <B>AK</B> and <B>HI</B>
// Suffix: '.'

<[Bb]>匹配<B>标签(大小写均可),</[Bb]>匹配</B>标签(大小写均可)。这个模式只找到了一个匹配而不是预期中的两个:实际匹配的结果<B>AK</B> and <B>HI</B。出现这种匹配结果的原因是*和+都是所谓的“贪婪型”元字符,它们在进行匹配时的行为模式是多多益善而不是适可而止。它们会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头匹配到碰到第一个匹配时为止。

在不需要这种“贪婪行为”的时候需要使用“懒惰型”的元字符版本(“懒惰”在这里的含义是匹配尽可能少的字符,与“贪婪型”元字符的行为模式刚好相反)。懒惰型元字符的写法很简单,只要给贪婪型元字符加上一个?后缀即可。

贪婪型元字符 懒惰型元字符
* *?
+ +?
{n, } {n, }?

*?是*的懒惰型版本;下面是使用*?重新匹配的结果。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;
    
    string text = "This offer is not available to customers living in <B>AK</B> and <B>HI</B>.";
    regex expression("<[Bb]>.*?</[Bb]>");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }

    return 0;
}

// matches for 'This offer is not available to customers living in <B>AK</B> and <B>HI</B>.'
// Prefix: 'This offer is not available to customers living in '
// 0: <B>AK</B>
// Suffix: ' and <B>HI</B>.'

// matches for 'This offer is not available to customers living in <B>AK</B> and <B>HI</B>.'
// Prefix: ' and '
// 0: <B>HI</B>
// Suffix: '.'

#6. 位置匹配

位置匹配是针对某段文本的特定位置进行匹配。

6.1 边界

位置匹配用来解决在什么地方进行字符串匹配操作的问题。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "This cat scattered his food all over the room.";
    regex expression("cat");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }
    return 0;
}

模式cat把原始文本里所有cat都找了出来,但这一结果并不是我们所预期的,单词scattered里面的cat不应该包含其中(这里的cat代表的动物而不是词组)。能够解决这个问题的办法只有一个:使用边界限定符,也就是在正则表达式里用一些特殊的元字符来表明我们想让匹配操作在什么位置(或边界)发生。

6.2 单词边界

第一种边界(也就是最常用的边界)是由限定符\b指定的单词边界。顾名思义,\b用来匹配一个单词的开始或结尾。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "This cat scattered his food all over the room.";
    regex expression("\\bcat\\b");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }

    return 0;
}

// matches for 'This cat scattered his food all over the room.'
// Prefix: 'This '
// 0: cat
// Suffix: ' scattered his food all over the room.'

\b匹配的是一个这样的位置,这个位置位于一个能够用来构成单词的字符(字母、数字和下划线,也就是与\w相匹配的字符)和一个不能用来构成单词的字符(也就是与\w相匹配的字符)之间。

6.3 字符串边界

单词边界可以用来进行与单词有关位置匹配(单词的开头、单词的结束、整个单词等)。字符串边界有着类似的用途,只不过是用来进行与字符串有关的位置匹配而已(字符串的开头、字符串的结束、整个字符串等)。用来定义字符串边界的元字符有两个:一个是用来定义字符串开头的^,另一个是用来定义字符串结尾的$

#7. 子表达式

7.1 什么是子表达式

子表达式是一个更大的表达式的一部分;把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立元素来使用。子表达式必须用()括起来。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "Pinging hog.forta.com[12.159.46.200]";
    regex expression("(\\d{1,3}\.){3}\\d{1,3}");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    if (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
    }

    return 0;
}

// matches for 'Pinging hog.forta.com[12.159.46.200]'
// Prefix: 'Pinging hog.forta.com['
// 0: 12.159.46.200
// 1: 46.
// Suffix: ']'

上述例子通过正则表达式来查找IP地址。IP地址的格式是以英文句号分隔的四组数字,例如12.159.46.200。因为每组数字由1个、2个或3个数字字符构成,所以这4组数字可以统一使用模式\d{1,3}来匹配。(\d{1,3}.){3}表明(\d{1,3}.)这部分出现了三次(它们对应着IP地址里的前3组数字),最后面的\d{1,3}用来匹配IP地址里的最后一组数字。

#8. 回溯引用:前后一致匹配

子表达式的基本用途之一:把一组字符编组为一个字符集合。这样的字符集合主要用于精确设定需要重复匹配的文本及其重复次数。子表达式还有另一个重要用途——定义回溯引用(backreference)。

8.1 回溯引用匹配

假如有一段文本,如果想把这段文本里所有连续重复出现的单词(打字错误,其中有一个单词输了两遍)找出来。显然,在搜索某个单词的第二次出现时,这个单词必须是已知的。回溯引用允许正则表达式引用前面的匹配结果(具体到以下的例子就是前面匹配到的单词)。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "This is a block of of text, several words here are are repeated, and and they should not be.";
    regex expression("[ ]+(\\w+)[ ]+\\1");

    smatch matches;
    string::const_iterator searchStart(text.cbegin());
    while (regex_search(searchStart, text.cend(), matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
        searchStart = matches.suffix().first;
    }
    return 0;
}

// matches for 'This is a block of of text, several words here are are repeated, and and they should not be.'
// Prefix: 'This is a block'
// 0:  of of
// 1: of
// Suffix: ' text, several words here are are repeated, and and they should not be.'

// matches for 'This is a block of of text, several words here are are repeated, and and they should not be.'
// Prefix: ' text, several words here'
// 0:  are are
// 1: are
// Suffix: ' repeated, and and they should not be.'

// matches for 'This is a block of of text, several words here are are repeated, and and they should not be.'
// Prefix: ' repeated,'
// 0:  and and
// 1: and
// Suffix: ' they should not be.'

这个模式找到了我们想要的东西,[ ]+匹配一个或多个空格,\w+匹配一个或多个字母数字字符,[ ]+匹配随后的空格。主要,\w+是括在括号里的,它是一个子表达式。这个子表达式不是用来进行重复匹配的。这个子表达式只是把整个模式的一部分单独划出来以便在后面引用。这个模式的最后一部分是\1;这是一个回溯引用,而它引用的正是前面划分出来的那个子表达式:当(\w+)匹配到单词of的时候,\1也匹配单词of;当(\w+)匹配到单词and的时候,\1也匹配单词and。\1代表着模式里的第一个子表达式,\2代表着第2个子表达式、\3代表着第3个;以此类推。

#9. 前后查找

之前介绍的正则表达式都是用来匹配文本的,但有时我们还需要用正则表达式标记要匹配的文本的位置(而不仅仅是文本本身)。这就引出了前后查找(lookaround, 对某一位置的前、后内容进行查找)的概念。

9.1 向前查找

向前查找指定了一个必须匹配但不在结果中返回的模式。向前查找实际就是一个子表达式,而且从格式上看也确实如此。从语法上看,一个向前查找模式其实就是一个以?=开头的子表达式,需要匹配的文本跟在=的后面。

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;
    
    string text = "http://www.forta.com/";
    regex expression(".+(?=:)");

    smatch matches;
    if (regex_search(text, matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
    }
}

// matches for 'http://www.forta.com/'
// Prefix: ''
// 0: http
// Suffix: '://www.forta.com/'

在上面列出的URL地址里,协议名与主机名之间以一个:分隔。模式.+匹配任意文本(第一个匹配是http),子表达式(?=:)匹配:。注意,被匹配到的:并没有出现在最终的匹配结果里;我们用?=向正则表达式引擎表明:只要找到:就行了,不要把它包括在最终的匹配结果里,用术语来说,就是“不消费”它。

9.2 向后查找

?=被称为向前查找操作符。除了向前查找,许多正则表达式实现还支持向后查找,也就是查找出现在被匹配文本之前的字符(但不消费它),向后查找操作符是?<=?<=?=的具体使用方法大同小异;它必须用在一个子表达式里,而且后跟要匹配的文本。

下面的例子中,需要提取出产品的价格:

#include <Windows.h>
#include <regex>
#include <iostream>
#include <string>

int main()
{
    using namespace std;

    string text = "ABC01: $23.45";
    regex expression("[\$][0-9.]+");

    smatch matches;
    if (regex_search(text, matches, expression))
    {
        cout << "matches for '" << text << "'\n";
        cout << "Prefix: '" << matches.prefix() << "'\n";
        for (size_t i = 0; i < matches.size(); ++i)
        {
            cout << i << ": " << matches[i] << '\n';
        }
        cout << "Suffix: '" << matches.suffix() << "\'\n\n";
    }
    return 0;
}

9.3 对前后查找取非

向前查找和向后查找通常用来匹配文本,其目的是为了确定将被返回为匹配结果的文本的位置(通过指定匹配结果的前后必须是哪些文本)。这种用法被称为正向前查找(positive lookahead)和正向后查找(positive lookbehind)。

前后查找还有一种不太常见的用法叫作负前后查找(negative lookaround)。负向前查找(negative lookahead)将向前查找不与给定模式相匹配的文本,负向后查找(negative lookbehind)将向后查找不与给定模式相匹配的文本。

操作符 说明
(?=) 正向前查找
(?!) 负向前查找
(?<=) 正向后查找
(?<!) 负向后查找

#10 嵌入条件

正则表达式里的条件用?来定义。

  • ?匹配前一个字符或表达式,如果它存在的话。
  • ?=和?<=匹配前面或后面的文本,如果它存在的话。

嵌入条件语法也使用了?,嵌入条件不外乎以下两种情况:

  • 根据一个回溯引用来进行条件处理。
  • 根据一个前后查找来进行条件处理。

相关文章

  • 正则表达式 re包 2018-10-02

    参考官网:Regular expression operations re: regular expression...

  • 10. Regular Expression Matching

    最怕regular Expression的题了。出现regular expression立刻想到几点:notes:...

  • AX 使用.Net的Regular Expression

    AX本身不支持regular expression,但可以使用.Net 的Regular Expression。 ...

  • Regular Expression

    校验数字的表达式 数字:^[0-9]*$ n位的数字:^\d{n}$ 至少n位的数字:^\d{n,}$ m-n位的...

  • Regular Expression

    I spend 3 hours to learn about regular expression. The le...

  • Regular Expression

    \d,\w,\s,[a-zA-Z0-9],\b,.,*,+,?,x{3},^,$分别是什么?\d:匹配数字\w :...

  • Regular expression

    定义 来自维基百科正则表达式。在理论计算机科学和形式语言理论中,正则表达式是定义搜索模式的一串字符。这种模式通常用...

  • Regular Expression

    1 正则常规用法 ①几个常用方法 正则调用: test()<用于检测字符是否匹配某个模式,有则返回true,否则返...

  • Regular Expression

    一、在 iOS 中使用正则表达式 二、Online Tool LINK 三、匹配字符 Special Symbol...

  • Regular Expression

    The regular expression written by me in github. Regular e...

网友评论

      本文标题:Regular Expression

      本文链接:https://www.haomeiwen.com/subject/btzbidtx.html