不要在 "单行" 注释 (#, //) 中放置 "一次性子模式" (?>...)。
我花了将近 1 天的时间才修复它。
使用 "C" 样式注释代替!
PHP 手册说,"单行" 注释 (# , //) 在 "?>"(PHP 结束标记)之前结束。
"单行" 注释 (# , //) 中的这些字母 "?>" 似乎被评估为 PHP 结束标记。
<?php
/* (?> */
echo '"C" 样式注释有效!<br>';
# (?>
echo '"单行" 注释';
?>
在使用最大化和最小化重复时,如果后面的内容匹配失败,通常会重新评估重复项,以查看不同的重复次数是否允许模式的其余部分匹配。有时这样做很有用,要么改变匹配的性质,要么让它比原本更早地失败,因为模式的作者知道继续下去没有意义。
例如,考虑将模式 \d+foo 应用于主题行 123456bar
在匹配所有 6 位数字后,又无法匹配 "foo",匹配器的正常操作是尝试再次匹配只有 5 位数字与 \d+ 项匹配,然后匹配 4 位数字,以此类推,最终失败。一次性子模式提供了指定一旦模式的一部分匹配,就不再以这种方式重新评估它的方法,因此匹配器在第一次无法匹配 "foo" 时会立即放弃。这种表示法是另一种特殊括号,以 (?> 开头,例如:(?>\d+)bar
这种类型的括号在匹配后会 "锁定" 它包含的模式部分,并且在模式中更深入的失败将无法回溯到它。但是,回溯到它之前的项目,仍然按正常方式工作。
另一种描述是,这种类型的子模式匹配与当前主题字符串中的位置相同的独立模式匹配的字符序列。
一次性子模式不是捕获子模式。上面示例这样的简单情况可以看作是一个必须吞噬所有它能吞噬内容的最大化重复。因此,虽然 \d+ 和 \d+? 都准备调整它们匹配的数字数量以使模式的其余部分匹配,但 (?>\d+) 只能匹配一个完整的数字序列。
当然,这种构造可以包含任意复杂的子模式,并且可以嵌套。
一次性子模式可以与后向断言结合使用,以指定主题字符串结尾处的有效匹配。考虑一个简单的模式,如 abcd$
应用于一个不匹配的长字符串。由于匹配是从左到右进行的,PCRE 将在主题中查找每个 "a",然后查看后面的内容是否与模式的其余部分匹配。如果模式被指定为 ^.*abcd$
,那么最初 .* 会先匹配整个字符串,但是当它失败(因为没有后续的 "a")时,它会回溯以匹配除最后一个字符外的所有字符,然后匹配除最后两个字符外的所有字符,以此类推。再一次,对 "a" 的搜索覆盖了整个字符串,从右到左,所以我们并没有得到改进。但是,如果模式写成 ^(?>.*)(?<=abcd)
,那么 .* 项目将无法回溯;它只能匹配整个字符串。随后的后向断言对最后四个字符进行一次测试。如果失败,匹配将立即失败。对于长字符串,这种方法对处理时间有显著影响。
当一个模式在可以无限重复的子模式内包含一个无限重复时,使用一次性子模式是避免某些失败匹配花费很长时间的唯一方法。模式 (\D+|<\d+>)*[!?]
匹配无限个子字符串,这些子字符串要么由非数字组成,要么由包含在 <> 中的数字组成,后面跟着 ! 或 ?。当它匹配时,它运行得很快。但是,如果它应用于 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
,它需要很长时间才能报告失败。这是因为字符串可以在两个重复项之间以大量的方式划分,并且所有这些方式都必须尝试。(示例使用 [!?] 而不是结尾处的单个字符,因为 PCRE 和 Perl 都有一个优化,允许在使用单个字符时快速失败。它们记住匹配所需的最后一个单个字符,如果字符串中不存在该字符,则会提前失败。)如果模式更改为 ((?>\D+)|<\d+>)*[!?]
,则不能打断非数字序列,并且会很快失败。