贪婪和量词详解

量词


量词的作用就指定一个正则token处理的次数。量词有两种形式:一种是单字符的;还有一种是使用花括号{}来指定的。

花括号指定

这种量词分下面几种情况

  • {n,m} 其中 nm 都是大于等于零的整数,并且 n < m。表示至少连续重复n次,最多连续重复m次。 比如:ba{0,3} 可以匹配 bbabaabaaa
  • {n,} 这种形式就相当于 m 为正无穷,只有最少次数,不限定最多。ba{1,} 可以匹配 babaabaaabaaaa等,只要后面的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>
)

查看笔记

扫码一下
查看教程更方便