C++新特性——正则表达式

一、概述

正则表达式 ,又称规则表达式。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本

许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sedgrep)普及开的。正则表达式通常缩写成**"regex"** ,单数有regexp、regex,复数有regexps、regexes、regexen。


二、符号

正则表达式由一些普通字符和一些元字符(metacharacters)组成。普通字符包括大小写的字母和数字,而元字符则具有特殊的含义,我们下面会给予解释。

在最简单的情况下,一个正则表达式看上去就是一个普通的查找串。例如,正则表达式"testing"中没有包含任何元字符,它可以匹配"testing"和"testing123"等字符串,但是不能匹配"Testing"。

要想真正的用好正则表达式,正确的理解元字符是最重要的事情。下表列出了所有的元字符和对它们的一个简短的描述。

元字符 描述
\ 将下一个字符标记符、或一个向后引用、或一个八进制转义符。例如,"\\n"匹配\n。"\n"匹配换行符。序列"\\"匹配"\"而"\("则匹配"("。即相当于多种编程语言中都有的"转义字符"的概念。
^ 匹配输入字行首。如果设置了RegExp对象的Multiline属性,^也匹配"\n"或"\r"之后的位置。
$ 匹配输入行尾。如果设置了RegExp对象的Multiline属性,$也匹配"\n"或"\r"之前的位置。
* 匹配前面的子表达式任意次。例如,zo*能匹配"z",也能匹配"zo"以及"zoo"。*等价于{0,}。
+ 匹配前面的子表达式一次或多次(大于等于1次)。例如,"zo+"能匹配"zo"以及"zoo",但不能匹配"z"。+等价于{1,}。
? 匹配前面的子表达式零次或一次。例如,"do(es)?"可以匹配"do"或"does"。?等价于{0,1}。
{n } n 是一个非负整数。匹配确定的n 次。例如,"o{2}"不能匹配"Bob"中的"o",但是能匹配"food"中的两个o。
{n ,} n 是一个非负整数。至少匹配n 次。例如,"o{2,}"不能匹配"Bob"中的"o",但能匹配"foooood"中的所有o。"o{1,}"等价于"o+"。"o{0,}"则等价于"o*"。
{n ,m } mn 均为非负整数,其中n <=m 。最少匹配n 次且最多匹配m 次。例如,"o{1,3}"将匹配"fooooood"中的前三个o为一组,后三个o为一组。"o{0,1}"等价于"o?"。请注意在逗号和两个数之间不能有空格。
? 当该字符紧跟在任何一个其他限制符(*,+,?,{n },{n ,},{n ,m })后面时,匹配模式是非贪婪的。非贪婪模式尽可能少地匹配所搜索的字符串,而默认的贪婪模式则尽可能多地匹配所搜索的字符串。例如,对于字符串"oooo","o+"将尽可能多地匹配"o",得到结果"oooo",而"o+?"将尽可能少地匹配"o",得到结果 'o', 'o', 'o', 'o'
.点 匹配除"\n"和"\r"之外的任何单个字符。要匹配包括"\n"和"\r"在内的任何字符,请使用像"\\s\\S"的模式。
(pattern) 匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用0...9属性。要匹配圆括号字符,请使用"\("或"\)"。
(?:pattern) 非获取匹配,匹配pattern但不获取匹配结果,不进行存储供以后使用。这在使用或字符"(|)"来组合一个模式的各个部分时很有用。例如"industr(?:y|ies)"就是一个比"industry|industries"更简略的表达式。
(?=pattern) 非获取匹配,正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如,"Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern) 非获取匹配,正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如"Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。
(?<=pattern) 非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如,"(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。 *python的正则表达式没有完全按照正则表达式规范实现,所以一些高级特性建议使用其他语言如java、scala等。
(?<!pattern) 非获取匹配,反向否定预查,与正向否定预查类似,只是方向相反。例如"(?<!95|98|NT|2000)Windows"能匹配"3.1Windows"中的"Windows",但不能匹配"2000Windows"中的"Windows"。 *python的正则表达式没有完全按照正则表达式规范实现,所以一些高级特性建议使用其他语言如java、scala等。
x|y 匹配x或y。例如,"z|food"能匹配"z"或"food"(此处请谨慎)。"z|food"则匹配"zood"或"food"。
xyz 字符集合。匹配所包含的任意一个字符。例如,"abc"可以匹配"plain"中的"a"。
\^xyz 负值字符集合。匹配未包含的任意字符。例如,"\^abc"可以匹配"plain"中的"plin"任一字符。
a-z 字符范围。匹配指定范围内的任意字符。例如,"a-z"可以匹配"a"到"z"范围内的任意小写字母字符。 注意:只有连字符在字符组内部时,并且出现在两个字符之间时,才能表示字符的范围; 如果出字符组的开头,则只能表示连字符本身。
\^a-z 负值字符范围。匹配任何不在指定范围内的任意字符。例如,"\^a-z"可以匹配任何不在"a"到"z"范围内的任意字符。
\b 匹配一个单词的边界,也就是指单词和空格间的位置(即正则表达式的"匹配"有两种概念,一种是匹配字符,一种是匹配位置,这里的\b就是匹配位置的)。例如,"er\b"可以匹配"never"中的"er",但不能匹配"verb"中的"er";"\b1_"可以匹配"1_23"中的"1_",但不能匹配"21_3"中的"1_"。
\B 匹配非单词边界。"er\B"能匹配"verb"中的"er",但不能匹配"never"中的"er"。
\cx 匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的"c"字符。
\d 匹配一个数字字符。等价于0-9。grep 要加上-P,perl正则支持。
\D 匹配一个非数字字符。等价于\^0-9。grep要加上-P,perl正则支持。
\f 匹配一个换页符。等价于\x0c和\cL。
\n 匹配一个换行符。等价于\x0a和\cJ。
\r 匹配一个回车符。等价于\x0d和\cM。
\s 匹配任何不可见字符,包括空格、制表符、换页符等等。等价于 \\f\\n\\r\\t\\v
\S 匹配任何可见字符。等价于\^ \\f\\n\\r\\t\\v
\t 匹配一个制表符。等价于\x09和\cI。
\v 匹配一个垂直制表符。等价于\x0b和\cK。
\w 匹配包括下划线的任何单词字符。类似但不等价于"A-Za-z0-9_",这里的"单词"字符使用Unicode字符集。
\W 匹配任何非单词字符。等价于"\^A-Za-z0-9_"。
\xn 匹配n ,其中n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,"\x41"匹配"A"。"\x041"则等价于"\x04&1"。正则表达式中可以使用ASCII编码。
\num 匹配num ,其中num 是一个正整数。对所获取的匹配的引用。例如,"(.)\1"匹配两个连续的相同字符。
\n 标识一个八进制转义值或一个向后引用。如果\n 之前至少n 个获取的子表达式,则n 为向后引用。否则,如果n 为八进制数字(0-7),则n 为一个八进制转义值。
\nm 标识一个八进制转义值或一个向后引用。如果\nm 之前至少有nm 个获得子表达式,则nm 为向后引用。如果\nm 之前至少有n 个获取,则n 为一个后跟文字m 的向后引用。如果前面的条件都不满足,若nm 均为八进制数字(0-7),则\nm 将匹配八进制转义值nm
\nml 如果n 为八进制数字(0-7),且ml 均为八进制数字(0-7),则匹配八进制转义值nml
\un 匹配n ,其中n 是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。
\p{P} 小写 p 是 property 的意思,表示 Unicode 属性,用于 Unicode 正表达式的前缀。中括号内的"P"表示Unicode 字符集七个字符属性之一:标点字符。 其他六个属性: L:字母; M:标记符号(一般不会单独出现); Z:分隔符(比如空格、换行等); S:符号(比如数学符号、货币符号等); N:数字(比如阿拉伯数字、罗马数字等); C:其他字符。 *注:此语法部分语言不支持,例:javascript。
\< \> 匹配词(word)的开始(\<)和结束(\>)。例如正则表达式\<the\>能够匹配字符串"for the wise"中的"the",但是不能匹配字符串"otherwise"中的"the"。注意:这个元字符不是所有的软件都支持的。
( ) 将( 和 ) 之间的表达式定义为"组"(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),它们可以用 \1 到\9 的符号来引用。
| 将两个匹配条件进行逻辑"或"(or)运算。例如正则表达式(him|her) 匹配"it belongs to him"和"it belongs to her",但是不能匹配"it belongs to them."。注意:这个元字符不是所有的软件都支持的。

最简单的元字符是点,它能够匹配任何单个字符(注意不包括换行符)。假定有个文件test.txt包含以下几行内容:

bash 复制代码
cat
cot
cut
coat
cute

如果我们使用以下正则表达式

bash 复制代码
grep "c.t" test.txt

输出结果:

bash 复制代码
cat
cot
cut

解释:

  • c.t 表示 "以 c 开头,以 t 结尾,中间任意一个字符";

  • 因此,catcotcut 都能匹配;

  • coatcute 不符合(因为它们中间有两个字符)。


三、速记理解技巧

|-----------|---------------|------------|-----------|
| . | | ^ | $ |

四个字符是所有语言都支持的正则表达式,所以这四个是基础的正则表达式。正则难理解因为里面有一个等价的概念,这个概念大大增加了理解难度,让很多初学者看起来会懵,如果把等价都恢复成原始写法,自己书写正则就超级简单了,就像说话一样去写你的正则了:

等价:

等价是等同于的意思,表示同样的功能,用不同符号来书写。

?,*,+,\d,\w 都是等价字符

?等价于匹配长度{0,1}

*等价于匹配长度{0,}

+等价于匹配长度{1,}

\d等价于0-9

\D等价于\^0-9

\w等价于A-Za-z_0-9

\W等价于\^A-Za-z_0-9

常用运算符与表达式:

^ 开始

() 域段

\[\] 包含,默认是一个字符长度

\^ 不包含,默认是一个字符长度

{n ,m } 匹配长度

. 任何单个字符(\. 字符点)

| 或

\ 转义

$ 结尾

A-Z 26个大写字母

a-z 26个小写字母

0-9 0至9数字

A-Za-z0-9 26个大写字母、26个小写字母和0至9数字

, 分割

分割语法:

A,H,T,W 包含A或H或T或W字母

a,h,t,w 包含a或h或t或w字母

0,3,6,8 包含0或3或6或8数字

语法与释义:

基础语法 "^(\[\]{})(\[\]{})(\[\]{})$"

正则字符串 = "开始(包含内容{长度})(包含内容{长度})(包含内容{长度})结束"

?,*,+,\d,\w 这些都是简写的,完全可以用\[\]和{}代替,在(?:)(?=)(?!)(?<=)(?<!)(?i)(*?)(+?)这种特殊组合情况下除外。

初学者可以忽略?,*,+,\d,\w一些简写标示符,学会了基础使用再按表自己去等价替换

实例:

字符串;tel:086-0666-88810009999

原始正则:"^tel:0-9{1,3}-00-9{2,3}-0-9{8,11}$"

速记理解:开始 "tel:普通文本"0-9数字{1至3位}"-普通文本"0数字0-9数字{2至3位}"-普通文本"0-9数字{8至11位} 结束"

等价简写后正则写法:"^tel:\d{1,3}-0\d{2,3}-\d{8,11}$" ,简写语法不是所有语言都支持。


四、校验数字的表达式

  1. 数字:^0-9*$

  2. n位的数字:^\d{n}$

  3. 至少n位的数字:^\d{n,}$

  4. m-n位的数字:^\d{m,n}$

  5. 零和非零开头的数字:^(0|1-90-9*)$

  6. 非零开头的最多带两位小数的数字:^(1-90-9*)+(.0-9{1,2})?$

  7. 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$

  8. 正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$

  9. 有两位小数的正实数:^0-9+(.0-9{2})?$

  10. 有1~3位小数的正实数:^0-9+(.0-9{1,3})?$

  11. 非零的正整数:^1-9\d* 或 \^(\[1-9\]\[0-9\]\*){1,3} 或 ^\+?1-90-9*$

  12. 非零的负整数:^\-1-9\[\]0-9"* 或 \^-\[1-9\]\\d\*

  13. 非负整数:^\d+ 或 \^\[1-9\]\\d\*\|0

  14. 非正整数:^-1-9\d*|0 或 \^((-\\d+)\|(0+))

  15. 非负浮点数:^\d+(\.\d+)? 或 \^\[1-9\]\\d\*\\.\\d\*\|0\\.\\d\*\[1-9\]\\d\*\|0?\\.0+\|0

  16. 非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?)) 或 \^(-(\[1-9\]\\d\*\\.\\d\*\|0\\.\\d\*\[1-9\]\\d\*))\|0?\\.0+\|0

  17. 正浮点数:^1-9\d*\.\d*|0\.\d*1-9\d* 或 \^((\[0-9\]+\\.\[0-9\]\*\[1-9\]\[0-9\]\*)\|(\[0-9\]\*\[1-9\]\[0-9\]\*\\.\[0-9\]+)\|(\[0-9\]\*\[1-9\]\[0-9\]\*))

  18. 负浮点数:^-(1-9\d*\.\d*|0\.\d*1-9\d*) 或 \^(-((\[0-9\]+\\.\[0-9\]\*\[1-9\]\[0-9\]\*)\|(\[0-9\]\*\[1-9\]\[0-9\]\*\\.\[0-9\]+)\|(\[0-9\]\*\[1-9\]\[0-9\]\*)))

  19. 浮点数:^(-?\d+)(\.\d+)? 或 \^-?(\[1-9\]\\d\*\\.\\d\*\|0\\.\\d\*\[1-9\]\\d\*\|0?\\.0+\|0)


五、校验字符的表达式

  1. 汉字:^\\u4e00-\\u9fa5{0,}$

  2. 英文和数字:^A-Za-z0-9+ 或 \^\[A-Za-z0-9\]{4,40}

  3. 长度为3-20的所有字符:^.{3,20}$

  4. 由26个英文字母组成的字符串:^A-Za-z+$

  5. 由26个大写英文字母组成的字符串:^A-Z+$

  6. 由26个小写英文字母组成的字符串:^a-z+$

  7. 由数字和26个英文字母组成的字符串:^A-Za-z0-9+$

  8. 由数字、26个英文字母或者下划线组成的字符串:^\w+ 或 \^\\w{3,20}

  9. 中文、英文、数字包括下划线:^\\u4E00-\\u9FA5A-Za-z0-9_+$

  10. 中文、英文、数字但不包括下划线等符号:^\\u4E00-\\u9FA5A-Za-z0-9+ 或 \^\[\\u4E00-\\u9FA5A-Za-z0-9\]{2,20}

  11. 可以输入含有^%&',;=?\\"等字符:\[\^%\&',;=?\x22]+ 12 禁止输入含有~的字符:\^\~\\x22+

六、特殊需求表达式

  1. Email地址:^\w+(-+.\w+)*@\w+(-.\w+)*\.\w+(-.\w+)*$

  2. 域名:a-zA-Z0-9-a-zA-Z0-9{0,62}(/.a-zA-Z0-9-a-zA-Z0-9{0,62})+/.?

  3. InternetURL:a-zA-z+://\^\\s* 或 ^http://(\\w-+\.)+\\w-+(/\\w-./?%\&=*)?$

  4. 手机号码:^(130-9|145\|7|150\|1\|2\|3\|5\|6\|7\|8\|9|180\|1\|2\|3\|5\|6\|7\|8\|9)\d{8}$

  5. 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$

  6. 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}

  7. 身份证号(15位、18位数字):^\d{15}|\d{18}$

  8. 短身份证号码(数字、字母x结尾):^(0-9){7,18}(x|X)? 或 \^\\d{8,18}\|\[0-9x\]{8,18}\|\[0-9X\]{8,18}?

  9. 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^a-zA-Za-zA-Z0-9_{4,15}$

  10. 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^a-zA-Z\w{5,17}$

  11. 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*a-z)(?=.*A-Z).{8,10}$

  12. 日期格式:^\d{4}-\d{1,2}-\d{1,2}

  13. 一年的12个月(01~09和1~12):^(0?1-9|10-2)$

  14. 一个月的31天(01~09和1~31):^((0?1-9)|((1|2)0-9)|30|31)$

  15. 钱的输入格式:

  16. 1.有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^1-90-9*$

  17. 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|1-90-9*)$

  18. 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?1-90-9*)$

  19. 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^0-9+(.0-9+)?$

  20. 5.必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^0-9+(.0-9{2})?$

  21. 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^0-9+(.0-9{1,2})?$

  22. 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^0-9{1,3}(,0-9{3})*(.0-9{1,2})?$

23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^(0-9+|0-9{1,3}(,0-9{3})*)(.0-9{1,2})?$

  1. 备注:这就是最终结果了,别忘了"+"可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里

  2. xml文件:^(a-zA-Z+-?)+a-zA-Z0-9+\\.x\|Xm\|Ml\|L$

  3. 中文字符的正则表达式:\\u4e00-\\u9fa5

  4. 双字节字符:\^\\x00-\\xff (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))

  5. 空白行的正则表达式:\n\s*\r (可以用来删除空白行)

  6. HTML标记的正则表达式:<(\S*?)\^\>*>.*?</\1>|<.*? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)

  7. 首尾空白字符的正则表达式:^\s*|\s*或(\^\\s\*)\|(\\s\*) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)

  8. 腾讯QQ号:1-90-9{4,} (腾讯QQ号从10000开始)

  9. 中国邮政编码:1-9\d{5}(?!\d) (中国邮政编码为6位数字)

  10. IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)

  11. IP地址:((?:(?:250-5|20-4\\d|01?\\d?\\d)\\.){3}(?:250-5|20-4\\d|01?\\d?\\d))


七、实战示例

cpp 复制代码
#include <iostream>   
#include <string>     
#include <regex> // 正则表达式库

int main()
{
    // 定义正则表达式
	std::regex string_reg("[1-9](\\d{5,11})");
    // 定义待测试表达式
	std::string strtest("123456");
    // 用于存储匹配结果的容器
	std::smatch matchresults;

	// 正则匹配
	if (std::regex_match(strtest, matchresults, string_reg))
	{
		std::cout << "Match:" << std::endl;
		// 输出表达式结果
		for (size_t i = 0; i < matchresults.size(); i++)
		{
			std::cout << matchresults[i] << std::endl;
		}
	}
	else
	{
		std::cout << "Not Match:" << std::endl;
	}

	return 0;
}

运行结果:

bash 复制代码
Match:
123456
23456

八、总结

  • 正则表达式的核心是"字符 + 限定符 + 位置";

  • 理解等价简写,能让正则变得"像自然语言一样读懂";

  • 编写时建议先用简单规则测试,通过后逐步完善。

相关推荐
小欣加油5 小时前
leetcode1926 迷宫中离入口最近的出口
数据结构·c++·算法·leetcode·职场和发展
星恒随风6 小时前
C++ 类和对象入门(五):初始化列表、explicit 和 static 成员详解
开发语言·c++·笔记·学习·状态模式
浪客灿心6 小时前
项目篇:模块设计与实现
数据库·c++
牛油果子哥q7 小时前
【C++ STL vector】C++ STL vector 终极精讲:动态数组底层原理、两倍扩容机制、迭代器失效、增删查改、性能剖析与工程避坑指南
开发语言·c++
为何创造硅基生物8 小时前
独占指针的创建std::make_unique 本身自带堆出现
c++
kyle~9 小时前
ROS 2 与 Isaac Sim 联合仿真(一)体系架构、环境选型与基础通信闭环
c++·机器人·nvidia·仿真·ros2
努力努力再努力wz9 小时前
【内存管理与高并发内存池系列】从 mmap 到 malloc:文件映射、匿名映射与 glibc 内存分配机制详解
linux·c语言·数据结构·数据库·c++·qt·链表
八解毒剂9 小时前
数据结构-平衡二叉树——对二叉搜索树的优化
数据结构·c++·算法
起床困难户57510 小时前
条款20:协助完成返回值优化
c++
啦啦啦啦啦zzzz10 小时前
算法总结(二分查找、双指针)
c++·算法