多字符匹配的一般形式
为什么需要量词?因为使用量词可以方便的匹配多个字符。以匹配邮政编码为例,其是由6位数字构成的字符串,比如201203。根据之前学习的知识,匹配这样的字符串需要使用正则表达式\d\d\d\d\d\d。而使用量词进行匹配则只需要写成\d{6}。
量词可以表达不确定的长度,其通用形式是{m,n},其中m和n是两个数字,m是下限,n是上限(均是闭区间),m和n共同限定了之前的元素能够出现的次数。
\d{m,n}表示所匹配的数字字符串长度,最短是m个字符,最长是n个字符。
如果不确定长度的上限,可以省略n值,只给出m值,例如\d{m,},表示数字字符串的长度必须在m个字符之上。
量词限定的出现次数一般都有明确的下限,如果没有,则默认为0。
注:量词中的逗号之后绝不能有空格。
量词 |
说明 |
{n} |
之前的元素必须出现n次 |
{m,n} |
之前的元素最少出现m次,最多出现n次 |
{m,} |
之前的元素最少出现m次,出现次数无上限 |
{0,n} |
之前的元素可以不出现,也可以出现,最多出现n次(在某些语言中可以写为{,n}) |
常用量词
{m,n}是量词表达的通用形式,在正则表达式中还存在三个作为“量词简记法”的常用量词,如下表: 常用量词 |
{m,n}等价形式 |
说明 |
* |
{0,} |
可能出现,也可能不出现,出现次数没有上限 |
+ |
{1,} |
至少出现1次,出现次数没有上限 |
? |
{0,1} |
至多出现1次,也可能不出现 |
一些使用常用量词的例子:
- 针对美式英语和英式英语单词拼写的使用,如travell?er。
- 针对http和https两种协议的匹配,如https?。
- 匹配HTML中的所有tag,如<[^>]+>(该正则表达有一点缺陷,以前一篇文章和本文中的知识无法解决)。
匹配所有tag的表达式 |
tag分类 |
匹配分类tag的表达式 |
<[^>]+> |
Open tag |
<[^/>][^>]*> |
|
Close tag |
</[^>]+> |
|
Self-closing tag |
<[^>/]+/> |
- 匹配双引号字符串,如”[^”]*”。
上面给出用于匹配open tag的正则表达式,也能够匹配self-closing tag。以目前已学知识无法解决。
特殊元字符:点号
一般文档都说,点号可以匹配“任意字符”,但事实是,点号可以匹配除换行符\n之外的任意字符。如果非要匹配“任意字符”,有两种办法:在正则匹配时指定使用单行模式(目前不解释细节),在这种模式下,点号可以匹配换行符;或者使用之前说过的通配字符组[\s\S](也可以是[\d\D]或[\w\W])。点号的使用容易出现滥用,比如随意使用.*或.+。
例如,之前我们使用”[^”]*”匹配双引号字符串,而“图省事”的做法是”.*”。这种用法会出现意外,因为用”.*”匹配双引号字符串,不但可以匹配正常的双引号字符串”quoted string”,还可以匹配格式错误的字符串”quoted string” and another”。另外”.*”无法匹配有换行符的情况。
这个问题简答的讲,是因为所使用量词的类型导致。之前介绍过的量词都属于匹配优先量词(greedy quantifier,也称作贪婪量词)。这类量词的特点是,在拿不准是否要匹配的时候,优先尝试匹配,并记下这个状态,以备将来进行回溯(backtracking)。例如下图所示过程
匹配优先量词使用的常见场景:
与匹配优先量词对应的,正则表达式中还提供了忽略优先量词(lazy quantifier或reluctant quantifier,也称作懒惰量词)。这类量词的特点是,如果不确定是否要匹配,忽略优先量词会选择“不匹配”的状态,再尝试匹配表达式之后的元素,如果尝试失败,再回溯,重新使用忽略优先量词进行“匹配”。
忽略优先量词使用的常见场景:
- 匹配多段javascript代码(<script type=”text/javasript”>...</script>);
- 匹配类似C语言那样的多行注释(行尾注释//...,和多行注释/*...*/);
- 提取HTML代码中的超链接(<a href=”http://somehost/somepath”>text</a>);
目前已知的匹配优先量词和其对应的忽略优先量词如下表所示
匹配优先量词 |
忽略优先量词 |
限定次数 |
* |
*? |
可能不出现,也可能出现,出现次数没有上限 |
+ |
+? |
至少出现1次,出现次数没有上限 |
? |
?? |
至多出现1次,也可能不出现 |
{m,n} |
{m,n}? |
出现次数最少为m次,最多为n次 |
{m,} |
{m,}? |
出现次数最少为m次,没有上限 |
{,n} |
{,n}? |
可能不出现,也可能出现,最多出现n次 |
匹配优先量词和忽略优先量词逐一对应,只是在对应的匹配优先量词之后添加?,两者限定的元素能出现的次数也一样,遇到不能匹配的情况同样需要回溯;唯一的区别在于,忽略优先量词会优先选择“忽略”,而匹配优先量词会优先选择“匹配”。另外,匹配优先量词只需要考虑自己限定的元素能够匹配即可,而忽略优先量词必须兼顾它所限定的元素和之后的元素,效率自然大大降低,当处理字符串很长时,尤为明显。
问题:C语言的两种注释方式,一种是在行末,以//开头;另一种可以跨多行,以/*开头,以*/结束。要匹配这两种注释,如何写正则表达式?
忽略优先量词在HTML页面解析中的应用:
类型 |
正则表达式 |
匹配table |
<table[\s>][\s\S]+?</table> |
匹配tr |
<tr[\s>][\s\S]+?</tr> |
匹配td |
<td[\s>][\s\S]+?</td> |
注:因为tag是不区分大小写的,所以如果还希望匹配大小写的情况,则必须使用字符组,table写成[tT][aA][bB][lL][eE]。
在实际的HTML代码中,table、tr、td这三个元素经常是嵌套的,它们之间存在着包含关系。但是仅仅使用正则表达式匹配,并不能得到这种包含关系信息。换句话说,正则表达式只能进行纯粹的文本处理,单纯依靠它不能整理出层次结构;如果希望解析文本的同时构建层次结构信息,则必须将正则表达式配合程序代码一起使用。
转义
之前介绍过元字符的转义,这里要介绍的是量词的转义。对于常用量词所使用的字符+、*、?来说,如果希望表示这三个字符本身,直接添加反斜线,变为\+、\*、\?即可。但是在一般形式的量词{m,n}中,虽然具有特殊含义的字符不止一个,转义时却只需要给第一个{添加反斜线即可,也就是说,如果希望匹配字符串{m,n},则正则表达式必须写成\{m,n}。
需要注意的是针对忽略优先量词的转义,因为其需要对两个量词全部转义。例如,如果要匹配字符串*?,正则表达式必须写作\*\?,而不是\*?。
下表为各种量词的转义
量词 |
转义形式 |
{n} |
\{n} |
{m,n} |
\{m,n} |
{m,} |
\{m,} |
{,n} |
\{,n} |
* |
\* |
+ |
\+ |
? |
\? |
*? |
\*\? |
+? |
\+\? |
?? |
\?\? |