最近项目里需要抓取别的网站的数据,于是重新回顾了一下正则,特别是零宽断言,下面是个人理解和总结。
基本概念说的很难理解,先上个例子:
<?php
preg_match_all("/(\w*)ing/", "aing bing", $mtches1);
preg_match_all("/(\w*)(?=ing)/", "aing bing", $mtches2);
print_r($matches1);
print_r($matches2);
刚开始我以为这两的结果是一样的,但其实不然,结果如下:
Array
(
[0] => Array
(
[0] => aing
[1] => bing
)
[1] => Array
(
[0] => a
[1] => b
)
)
Array
(
[0] => Array
(
[0] => a
[1] =>
[2] => b
[3] =>
)
[1] => Array
(
[0] => a
[1] =>
[2] => b
[3] =>
)
)
还是太天真。
于是从匹配过程上分析一波。
0a1b2i3n4g5 6b7i8n9g : 数字代表位置
第一个:先从位置0开始匹配,后的字符a匹配到\w,继续后面ing虽然能匹配到\w,但是它们是确定字符,所以匹配到第一个aing,于是继续从位置5开始,后面是空格,匹配不到\w,到位置6,b匹配到\w,继续向后,匹配到第二个bing,结束。于是分组1捕获的就是a和b。
第二个:同样从位置0开始,后的字符a匹配到\w,跳到位置1,此时控制权会交给后面的(?=ing),它断言位置1后面是ing字符,一看还真是,于是匹配到a,其实这里分不分组都一样,因为(?=exp)是零宽度,就是只占一个位置,继续从位置1开始往后匹配(因为(?=exp)是零宽),为什么空值成了第二个匹配到的值,我的理解是:因为*可以匹配空,也是零宽度,并且可以匹配到ing,类似于\0这种表示,也是就是说aing可能表示成a\0b\0i\0g\0这种。在往后就跟前面一样了,所以最后结果是a \0 b \0。
如果上面的\w*改成\w+,那么\0就不会被匹配到了。
看着很ok,但其实以上分析是不对的,如果把aing bing改成ainging bing,如果按照以上步骤,那么第二种情况应该是a \0 aing \0 b \0,但运行结果没有a和\0,????
其实先行断言的执行步骤是这样的先从要匹配的字符串中的最右端找到第一个ing(也就是先行断言中的表达式)然后 再匹配其前面的表达式,若无法匹配则继续查找第二个ing 再匹配第二个 ing前面的字符串,若能匹配 则匹配 ,意思是匹配是从最右边开始的,找到ing后,匹配b,找到第一个,再往前走,\0,再往前走aing,最后是\0。
负向零宽断言跟正向的两种情况是相反的。
果然正则是不适合地球人看的。