递归模式
递归模式
对于程序员来说,递归函数
肯定都不陌生。函数体内再次调用该函数,只要是函数执行到这个点,就再次调用自己,从而进入函数执行,如果没有条件让函数返回的话,那么函数会无限次的调用自己,从而造成死循环。当然了,好的递归函数能帮助我们少写很多代码。也非常容易让别人理解。
对于正则表达式的递归模式原理其实和递归函数是相同的。它的形式为(?R)
,表示无限递归。当正则引擎走到(?R)
这个点时,就会从正则表达式的开始进行匹配。当然,在正则表达式匹配的过程中,在某些条件下正则引擎也会从正则表达式的开始部分重新进行匹配。但是和递归模式不同的是,那是在某个正则匹配失败,并且有其他的可选情况时才会去重新匹配。而使用递归模式是在正常匹配成功下也可以重新进入。
现在我们通过一个例子,说一下递归模式的遍历过程。/a(?R)?c/
可以匹配 "aaaccc"
,其中'a'
和'c'
的个数是任意的。我们就以"aaaccc"
为例来介绍。首先正则a
和第一个字符'a'
匹配成功。进行下一个正则,由于是递归模式,所以进入第一层递归,从开始的正则a
进行匹配,和第二个字符'a'
匹配成功,然后再进行一下个正则,发现还是递归,于是继续进入第二层递归,从开始的正则a
进行匹配,这次是和第三个字符'a'
进行匹配,可以匹配成功。然后继续下一个正则,还是递归,于是进入第三层递归,依然是从开始的正则a
进行匹配,此时的字符我们就以第一个'c'
来称呼,匹配失败。由于没有可选路径,正则表达式匹配失败。从第三层退出,也就是相当于(?R)
失败了,又因为?
使(?R)
是可选的,所以可以继续下一个正则c
和此时的第一个'c'
匹配,匹配成功。此时相当于第二层的正则已经匹配成功了,继续退出第二层,此时第一层的(?R)
匹配成功,继续下一个正则c
和第二个'c'
匹配,匹配成功。继续退出第一层递归,此时回到最开始的正则,(?R)
匹配成功,继续下一个正则c
和第三个'c'
匹配,很明显可以匹配成功。此时整个正则表达式已经匹配完成了。
注意,上面例子的正则表达式,(?R)
必须是一个可选的正则,也就是后面要跟着?
或者*
。这是给递归一个正确的结束条件,否则的话,递归无法正常结束。现在我们不加?
或者*
,对于正则表达式/a(?R)c/
匹配"aaaccc"
,看看会
发生什么情况。进入递归的过程我们不再赘述,现在我们直接看在第三层递归的时候,正则a
和目标字符串中的第一个'c'
匹配失败,由于没有可选项,所以第三层的正则表达式匹配失败。退出第三层回到第二层,因为第三层是失败退出,所以相当于第二层的(?R)
匹配失败了,但是我们看,这里没有?
或者*
量词,所以它不是可选的。(?R)
失败,但是也没有其他可选项,所以第二层的正则表达式也匹配失败。就会退出第二层的递归,回到第一层。同样第二层也是以失败退出,所以第一层的(?R)
也是匹配失败,没有可选项,所以继续退出第一层递归。来到最初的地方,也是一个失败。同样因为(?R)
不是可选的,所以继续失败,从而整个正则表达式匹配失败。每一层(?R)
后面的正则c
都不能拿来匹配了,你说这是不是很遗憾。
有一个比较经典的括号配对的问题,"((()))"
这是一个完整的配对的字串;((()
这是不完全配对的。如果没有递归模式,正则表达式要想匹配出其中成对的还真是不容易。然而递归模式可以很容的解决这个问题。/\((?R)?\)/
就可以将其中的成对的括号匹配出来。
除了使用(?R)
进行无限递归之外,还可以使用(?数字)
的形式指定递归几层。例如:(?1)
,(?3)
等。