PCRE零宽断言
(一) 前后左右作为计算机术语名词的含义
计算机里面的前后、左右、上下等等的方位名词,往往与我们中文环境中的意思是不太一样,所以理解起来很别扭。
如 “向前兼容”、“向上兼容” 是指老版本的数据或者API可以与新版本通用。
而 “向后兼容”、“向下兼容” 是指新版本的数据或者API可以与老版本通用。
在【正则】中,前 = 右,后 = 左,那么前瞻(向前)就是向右,后顾(回顾)就是向左
0 a 1 b 2 c 3
例如,对于上面 “0a1b2c3” 这个字符串而言, 0 是在 a 的后面,1 是在 a 的前面。
需要特别注意的是,前瞻或者后顾是可以配合 “肯定” 或者 “否定” 条件一起执行的。
这就是所谓的 【正前瞻】【负前瞻】【正回顾】【负回顾】。
其中的 【正】表示“肯定”,也相当于等于或者全等于的意思,【负】就是取反,也就是不等于的意思。
搞清了这些基本概念就不会被一大堆的正则术语名词搞懵了。
(二)正则表达式是如何执行的
执行原理
正则表达式的执行顺序是从左到右依次【分段】执行,这里的段是指一个一个的小规则单元。
字符串的匹配过程是逐个字符进行的,每完成一个字符的匹配,【指针】向前移动一个位置。
需要注意的是,指针所在的位置与字符的位置并不是一一对应的,而是前后的关系,这对于理解【零宽】至关重要。

什么叫指针
"abc" // 字符串
"0 a 1 b 2 c 3" // 数字为指针可以移动到的位置
如上面的代码 "abc" 字符串为例,指针可以在 0,1,2,3 四个地方来回移动。
也就是说每个字符前后的位置就是指针可以移到的位置。
可以简单的理解为当前匹配 “走” 到哪儿了,我们就在哪个字符前面或者后面的位置加个标记。
表示下一个匹配就从这个标记的位置处开始,这个标记就是【指针】。
指针的作用
指针的作用是标记字符串匹配到了哪个位置,它决定了接下来的匹配从哪个地方开始。
(三)什么是非零宽?
【非零宽】是个【抢劫犯】,以抢占字符为荣。
凡是我【非零宽】规则走过的地方、扫描过的字符,
我不管你是不是跟我匹配,我都宣布你已经阵亡了,你的地盘都是我的了,这就是【非零宽】。
它会把它经手过的字符统统占有了,我抢到就是我的,不让后面的规则再来扫描它们了,我管事管的宽,所以我就叫【非零宽】。
'abc'
/ab/
如上面的示例,'abc' 是一个字符串, /ab/ 是一个正则。
结合上面第二条说的执行原理,那么位置数据就是 "0a1b2c3",一开始指针位于起始位置 0。
这个时候从规则中取出第一个小单元 /a/,因为默认非特殊类型的匹配都是 【前瞻】类型的,也就是说都是向右的方向。

那么 0 的位置下面第一个字符是 a, 这恰好与 /a/ 相匹配,完成小单元匹配,正则引擎将【指针】往前移动到位置 1。

接下来的 /b/ 就直接从位置 1 开始匹配下一个字符,顺利匹配到 b,然后【指针】顺利移动到位置 2。

这个时候 'abc' 中的 'ab' 都被标记为已完成匹配,接下来的匹配不能再让 'ab' 两个字符参与了,得从 'c' 开始了。
所以,/ab/ 这种扫描到哪里就把【指针】搬到哪里的类型,就是非零宽。
匹配步骤详见: https://regex101.com/r/K3iDO9/1/debugger
(四)什么是零宽?
【零宽】是个【偷窥狂】,以悄悄偷窥前后字符为荣。
我【零宽】可不像上面那个抢劫犯,占有欲那么强。
对于经手过的字符我从来不私自占有,美的东西是要大家一起欣赏的嘛。
万花丛中过,片叶不沾身,这就是【非零宽】。
它每次都会从【指针】所在的位置伸出脑袋去看前后有没有字符是符合它的审美标准的。
一旦发现之后就会大声嚷嚷,快看啊我前面(后面)有几个绝色字符,非常漂亮。
'abc'
/(?=bc)/
(?= pattern) 是一个【正前瞻(零宽正向先行断言)】。
如上的例子,同样的位置数据 "0a1b2c3"。

一上来,当指针在 0 位置的时候, (?=bc) 就往右(前瞻)伸出脑袋来看:我的右边有 bc 么?
结果这个时候看到了字符 a, 看了两眼,不是它喜欢的类型,就又把脑袋缩回到 0 的位置了。

这个时候【正则引擎】把【指针】移动到 1 的位置,这家伙马上又伸出脑袋去看,这下子发现右边有 bc 好开心。

但是,我不能占有她们的地盘,我也不能捕获她们,我只是看看就好,就又把脑袋缩回到 1 的位置。

引擎继续移动指针,它继续这么浪下去,到头来它什么都没捞着。。。
所以,这就是【零宽】,即使它把脑袋直接伸长(扫描)到字符串末尾,它也不会对【指针】有半分的移动。
匹配步骤详见:https://regex101.com/r/9mISYE/1/debugger
(五)总结
零与非零:
1.【非零宽】就是我扫描到哪里,我就把【指针】插到哪里,凡是我经过的【都是我的】。
2.【零宽】就是【指针】插到哪里,我就从哪里开始左右偷窥,然后乖乖的回到【指针】这里,凡是我经过的【都不是我的】。
| 零宽特点 | 描述解释 |
|---|---|
| 不消耗字符 | 扫描完了指针还是在原地,不会把扫描过的字符“占有” |
| 不捕获字符 | 扫描完了什么都不会获取 |
零宽断言:
主要有【正前瞻】【负前瞻】【正回顾】【负回顾】四种类型的零宽断言。不同的人叫法不一样,我只取简单易记的这种。
【正向】=【是】表示肯定类型的匹配,相当于 ==
【负向】=【非】表示否定类型的匹配,相当于 !=
【先行】=【右】表示向前方找,向右方找
【后发】=【左】表示向后方找,向左方找
| 表达式 | 术语名词 | 功能描述 |
|---|---|---|
| (?=pattern) | 【正前瞻】(零宽正向先行断言) | 从当前指针处,往【右】边找,找【是】 pattern 的 |
| (?!pattern) | 【负前瞻】(零宽负向先行断言) | 从当前指针处,往【右】边找,找【非】pattern 的 |
| (?<=pattern) | 【正回顾】(零宽正向后发断言) | 从当前指针处,往【左】边找,找【是】pattern 的 |
| (?<!pattern) | 【负回顾】(零宽负向后发断言) | 从当前指针处,往【左】边找,找【非】pattern 的 |