贪婪和量词详解
量词
量词的作用就指定一个正则token处理的次数。量词有两种形式:一种是单字符的;还有一种是使用花括号{}
来指定的。
花括号指定
这种量词分下面几种情况
{n,m}
其中n
和m
都是大于等于零的整数,并且n < m
。表示至少连续重复n次,最多连续重复m次。 比如:ba{0,3}
可以匹配b
,ba
,baa
和baaa
;{n,}
这种形式就相当于 m 为正无穷,只有最少次数,不限定最多。ba{1,}
可以匹配ba
,baa
,baaa
,baaaa
等,只要后面的a
是连续的就都可以匹配;{n}
这种形式就相当于固定长度了。ba{3}
就只能匹配baaa
;
基本上就上面三种形式,{,m}
这种量词是不允许的,正则引擎会报错。
单字符量词
单字符量词包含三个字符 *
、+
和 ?
。
*
重复次数大于等于0次。相当于{0,}
;+
重复次数大于等于1次,也就是至少匹配一次。相当于{1,}
;?
重复次数0次或者1次。 相当于{0,1}
。
其中?
除了作为量词的作用之外,还有另一个功能:将贪婪模式转换为非贪婪(懒惰)模式。在接下来我们介绍贪婪模式的时候会看到?
的第二个功能。
贪婪模式
所谓贪婪,就是尽可能多的去匹配。比如说将正则表达式/<.+>/
应用于字符串This is a <span>greedy</span> example
。由于量词+
默认是贪婪的,所以点号.
会尽可能多的去匹配。最后匹配到的结果是<span>greedy</span>
。其实我们期望中的结果是只匹配<span>
。这就是贪婪模式造成的。为什么会产生贪婪呢?贪婪都是正则中的量词造成的。像上面我们说到的*
、+
、?
还有花括号定义的量词{n,}
和{n,m}
默认都是贪婪的。像*
、+
和{n,}
是贪婪的,这比较容易理解。而{n,m}
和?
也是贪婪的,只是就是小范围内的贪婪,像{n,m}
是能匹配m
个绝不仅匹配n
个;?
也是一样,尽可能去匹配一个。
/ba?/
是能匹配b
,也能匹配ba
。但是如果我们给定字符串ba
,匹配出来的结果肯定就是ba
,而不会是b
。给定字符串ba
,要想只匹配出b
,那就要将贪婪模式变为非贪婪(懒惰)模式。这时就要用到?
的第二个功能了,在量词后面加上?
,就能转换贪婪模式为非贪婪的。/ba??/
就只能匹配b
。
回归到上面第一个例子,/<.+?>/
就只是匹配字符串This is a <span>greedy</span> example
中的 <span>
。?
将+
的贪婪转换成了非贪婪的。 下面我们就用这个例子,看一下贪婪模式和非贪婪模式下,正则引擎的匹配过程。
首先看贪婪模式/<.+>/
。正则表达式中的第一个token < 匹配到字符串中的第一个字符<
。然后是token 点号.
,由于其任意性,所以可以匹配到字符s
,又因为+
的贪婪性,所以点号.
继续向后匹配,直到字符串的最后一个字符e
都可以匹配成功。也就是经过前两个token <.+
的匹配,匹配出了字符串'<span>greedy</span> example'
。接着是下一个token >。因为字符串当前的位置是末尾,所以 > 不能匹配下一个字符,因为下一个字符没有了。由于正则引擎知道+
是贪婪的,所以正则引擎会回溯,将.+
的次数减去一,此时匹配出来的字符串为'<span>greedy</span> exampl'
,同样,> 不能和下一个字符e
匹配。正则引擎继续回溯,.+
次数继续减去一,此时匹配出来直到字符串为'<span>greedy</span> examp'
,同样,> 不能和下一个字符l
匹配。正则引擎继续回溯,直到匹配出来的字符串为'<span>greedy</span'
,此时token > 和下一个字符>
匹配成功。整个正则完成匹配,所以匹配出来的结果就是'<span>greedy</span>'
。其实,贪婪模式的匹配过程就是先尽可能多的向后面匹配字符,然后当下一个token不能和当前匹配出来的字符串的最后位置的字符匹配的时候,再往前回溯,再去匹配,直到该token首次匹配成功。
看了上面的匹配过程,你应该能了解为什么称其为贪婪模式了。为了加深理解,下面我们介绍非贪婪模式的匹配过程,同样还是以上面的字符串'This is a <span>greedy</span> example'
为例。
非贪婪模式/<.+?>/
。正则表达式中的第一个token < 匹配到字符串中的第一个字符<
。然后是token 点号.
,可以匹配到字符s
,虽然+
是贪婪的,但是由于?
的非贪婪性,所以匹配到字符s
之后,就不再向后匹配。然后正则引擎将下一个token > 和 下一个字符p
去匹配,不能匹配成功。这时正则引擎不是将+
的次数减一了,而是加一,所以<.+?
匹配出来的结果是<sp
。同样,token >
和下一个字符a
也不能匹配。那正则引擎继续将+
次数加一,直到<.+?
匹配出来字符串<span
。此时token > 和下一个字符 >
匹配成功。然后整个正则表达式匹配成功,匹配的结果为'<span>'
。
这时我们可能会说了,字符串</span>
也是符合正则表达式的,那为什么匹配的结果是'<span>'
而不是'<span>'
呢? 这就要说到正则表达式的另一个概念了,最左原则(left most)
。这时什么意思呢,其实就是说,字符串是从左向右遍历的。当整个正则表达式匹配成功,正则中的最后一个token匹配成功的字符串中的第一个位置是结束的位置,即使后面再有能使整个正则表达式匹配成功的字符串也没用,正则引擎到这里就不再继续了。因为一般的正则引擎都是reg-directed
模式,是以正则表达式匹配完成为准。所以匹配出来的是'<span>'
而不是'</span>'
。正则引擎匹配到'<span>'
发现整个表达式成功了,所以按照left most原则,就不再向右边进行匹配了。
那另一个问题来了,贪婪模式下/<.+>/
为什么就匹配到了</span>
,按照上面说的<span>
也符合正则表达式,为什么它能匹配出</span>
来呢,这岂不是和left most
原则相矛盾了吗? 其实并不矛盾。+
之所以是贪婪的,是因为它是一个变量,它不知道应该向右面匹配多少个字符才算终止,所以就尽可能多的向右面匹配符合前面给定的条件的字符,直到不能匹配为止。像>
、<
和/
都是能被点号.
匹配到的。等到不能匹配的时候,再去匹配token >。这时匹配token的过程其实就相当于字符串是反向匹配的,也是在第一次匹配成功token > 之后正则引擎就停止了。所以这一点和left most
原则并不违背。
对于这个问题,如果还是不能确认的话,我们可以看这样的一个例子:正则表达式/<[^m]+>/
应用于字符串'<span>first</span>m<span>second</span>'
。我们以m
作为分割,像'<span>first</span>'
和'<span>second</span>'
都是符合条件的,正则表达式中[^m]+
也是贪婪的。按照上面说的left most
原则,只会匹配出字符串'<span>first</span>'
,而不会匹配'<span>second</span>'
。
<?php
$str = "<span>first</span>m<span>second</span>";
$pattern = "/<[^m]+>/";
$res = preg_match($pattern,$str,$matches);
print_r($matches);
执行结果
Array
(
[0] => <span>first</span>
)