反向引用
反向引用
所谓反向引用,就是将子组所捕获的内容作为正则表达式中的一项去重复匹配。它的用法就是在反斜线'\'
后面跟着一个大于0的正整数。
我们举个例子 <([\w]+)>.*?<\/\1>
是将html标签的开始的标签名称捕获然后在标签结束位置引用。在这个例子中只使用了一对捕获和引用。
在子组和捕获一节已经介绍过,每个捕获子组的下标计数方法,相对哪个子组进行引用就可以使用反斜线'\'
后面紧跟着相对应的下标。那么问题来了,如果捕获子组的数量小于引用的数,那会出现什么情况呢? 这个问题其实就涉及到了PHP对反斜线'\'
后面紧跟非0数字的处理方式。本来这个问题应该放在转义字符那一节去讲,但是由于反斜线后面跟数字的情况主要还是用在正则的反向引用里,至于那些表示特殊字符的情况(比如\011
表示制表符)用到的情况比较少,所以我们借着反向引用所引出来的问题,把它放在这里一块儿结合说明。
这里我们有个前提,就是捕获子组的数量小于引用计数。下面我们来分情况进行说明。
首先就是引用计数是一个小于10,也就是反斜线后面跟着的数字是小于10的,比如\9
。 如果捕获子组的数量少于9个那系统会报错。
对于引用计数是大于9的情况,当捕获子组的数量少于引用计数的时候。正则引擎是这样处理引用计数的:首先,从反斜线开始向后读取至多三个有效的八进制数字,然后取这个数的最低8bit位,生成一个字符(可以对照asc码表);对于再后面的数字就是其本身的字符含义。看到这你可能还是有点迷糊,没关系,下面我们通过一个例子来看一下这个逻辑是怎么样的。 有这样一个引用计数\1013
(实际不会出现这么大的引用,在PHP中最大只能到99,这里只是为了说明这个处理过程,这个数比较容易说明问题),首先从反斜线向后至多取三个有效的八进制数,就是101
。这个数是一个八进制的,转换成二进制为001000001
,按照其要求,取最低的8位,也就是01000001
,对应的十进制数是65
,对照asc码表
其对应的字符是A
;那剩下的后面的数字3
就代表其本身的字符了,也就是字符’3‘
。所以\1013
最终的转换结果为A3
。我们通过将\1013
放到正则中/<([\w]+)>.*?<\/\\1013>/
,它可以正常匹配 '<A3>hello world!</A3>'
。 千万注意,这个例子中其实就不是用到的反向引用了,实质是PHP对反斜线后面跟数字这种情况的处理。因为如果使用非捕获子组/(?:[\w]+)>.*?<\/\\1013>/
也能正常匹配'<A3>hello world!</A3>'
,但是如果将使用非捕获子组的情况下的\1013
换成\1
,/(?:[\w]+)>.*?<\/\\1>/
,那就报错了。所以这里再强调一下就是非捕获子组没有对应的下标,不能被反向引用。
再看一个例子\5382
,从反斜线向后至多读取三个有效八进制数(注意是至多三个),因为8不是有效的八进制数,所以只能读取两个53
(八进制数53,而不是十进制53),然后取二进制的最低8位为00101011
,对应的十进制数为43
,对照asc码表,对应的字符为+
。而剩下的两位就分别代表其本身的字符'8'
和'2'
。所以\5382
是字符串"+82"
。
注意:
\377
对应的十进制数是255,是一个全为1的字节。所以在我们的表达式中尽量不要出现从反斜线后读取的八进制数大于377
的情况。如果出现,程序是会给出警告的。但是,程序会继续进行解析。比如\5013
,程序就会给出警告,Warning: Octal escape sequence overflow \501 is greater than \377
。但是它的结果和上面的\1013
相同,最后也是字符串A3
。 这个是需要多留心的。
上面说了这么多,都是在捕获子组的数量小于引用计数的情况下发生的。对于正常情况下,捕获子组的数量多于最大引用计数,那当然就没什么问题了。剩下的就是按照正常逻辑写正则表达式了。
正向引用
除了反向引用
,像PHP语言还提供了正向引用的功能。它允许我们将一个引用计数放在要被引用的子组的前面。根据正则引擎的遍历过程,正向引用
很明显只有在重复子组里面才有用。
/(\2two|(one))+/
可以匹配字符串"oneonetwo"
。 首先是token\2
匹配失败,因为是可选择的路径,所以子组(one)
继续匹配字符串的开头的one
。因为是嵌套的捕获子组,外层有一个子组。所以存储的下标是2
。正则引擎继续下一个token,+
量词,这个使我们很放心,继续前面的子组匹配。此时\2
就是one
了,而现在的字符串是从第二个"one"
的开头,所以\2
可以匹配成功字符串"one"
。正则引擎继续下一个挨着的two
,可以匹配出字符串"two"
。
按照这个过程,如果说正则中没有量词+
,/(\2two|(one))/
,那在一开始\2
就匹配失败了,即使后面匹配到了(one)
并且正则引擎将其存储下来,但是正则匹配完(one)
之后,整个正则表达式就已经完成了,所以只能匹配到字符串"one"
。因此,我们上面说正向引用
只有在重复子组里面才有用。其实,我们仔细想想这个逻辑,还是一种反向引用
,虽然在正则表达式中是写在了要引用的子组的前面,但是在实际正则引擎的执行中,也是先将子组匹配到然后存储,再通过量词重复,让引用去进行后续的匹配。
嵌套引用
嵌套引用(Nested References
) 是将一个反向引用放在它要引用的子组里。和正向引用一样,根据正则引擎的遍历过程,嵌套引用也只有在重复子组里面才有用。
/(\1two|(one))+/
可以匹配字符串'oneonetwo'
。看这形式不就是正向引用
吗。其实它们是有区别的。正向引用是引用计数放在了要引用的子组的前面,\2
是引用的后面的子组(one)
,不明原因的可以看一下前面介绍的子组下标的计数规则。而在嵌套引用
中,引用计数是放在它要引用的子组的里面。\1
是要引用最外层的子组,自己本身就属于最外层子组要捕获的一部分。