在正则表达式中,反向引用(backreference)、零宽断言(zero-width assertion)、负向零宽断言(negative zero-width assertion)是高级的匹配技术,用于更精细地指定匹配规则,而不是简单地匹配字符或字符集。

1. 反向引用(Backreference)

反向引用允许在正则表达式中引用前面捕获的子组(或子模式)。它允许在同一个正则表达式中重复使用先前匹配的文本。通常用 \n 表示,其中 n 是一个数字,表示对第 n 个捕获组的引用。

语法和示例:

  • 使用括号 () 捕获子组。
  • 使用 \n 在正则表达式中引用捕获的子组,其中 \n 是对应的子组编号。

例如,正则表达式 (a+)\1 匹配连续出现的字母 a,如 aaaaaaaaa 等。在这里,\1 表示引用第一个捕获组 (a+) 所匹配的内容。

2. 零宽断言(Zero-Width Assertions)

零宽断言是一种特殊的匹配条件,用于在不消耗字符的情况下(零宽度),仅仅判断当前位置是否满足某种条件。

常见的零宽断言有:

  • 正向先行断言(Positive Lookahead)(?=…)
    • 正向先行断言匹配在某些内容之前的位置,而不匹配这些内容本身。它在当前位置向前检查是否能匹配括号中的表达式。

    示例: 匹配包含数字和字母的字符串,但只匹配数字前的部分。

    \d(?=\w)
    

    这个正则表达式 \d(?=\w) 匹配一个数字后面跟着一个字母或数字。

  • 负向先行断言(Negative Lookahead)(?!…)
    • 负向先行断言匹配在某些内容之前的位置,但这些内容不能匹配括号中的表达式。

    示例: 匹配不包含特定字符序列的单词。

    \b(?!bad\b)\w+\b
    

    这个正则表达式 \b(?!bad\b)\w+\b 匹配不是 bad 的单词。

前瞻示例

  • 正前瞻\w+(?=ing\b),匹配以“ing”结尾的单词中的前面部分(不包含“ing”)。
  • 负前瞻\b(?!bad\b)\w+\b,匹配不等于“bad”的单词。

后顾示例

  • 正后顾(?<=\$)\d+,匹配以 $ 符号开头的数字(不包含 $)。
  • 负后顾(?<!\$)\d+,匹配前面没有 $ 符号的数字。

3. 负向零宽断言(Negative Zero-Width Assertions)

负向零宽断言是一种特殊的零宽断言,它用来指定当前位置不应该匹配某些内容。

常见的负向零宽断言有:

  • 负向后行断言(Negative Lookbehind)(?<!…)
    • 负向后行断言匹配当前位置之前的内容,但这些内容不应该匹配括号中的表达式。

    示例: 匹配不在引号内的单词。

    (?<!")\b\w+\b(?!")
    

    这个正则表达式 (?<!")\b\w+\b(?!") 匹配不在双引号内的单词。


平衡组以及递归匹配

<[^<>]*(((?'Open'<)[^<>]*)+((?'-Open'>)[^<>]*)+)*(?(Open)(?!))>

这段正则表达式是用来匹配最外层的 <...> 形式的标签,并确保标签的嵌套结构是正确闭合的。让我解释一下它的各个部分:

  1. <[^<>]*: 匹配最外层的左尖括号 < 开始的部分,然后跟着零个或多个非尖括号字符 [^<>]*,表示匹配标签的开始部分。

  2. ( 开始的部分:
    • (?'Open'<): 定义一个命名捕获组 Open,它匹配一个左尖括号 <,并将其命名为 Open
    • [^<>]*: 匹配左尖括号后面的任意非尖括号字符。
  3. )+ 表示这个组合模式可以重复一次或多次,以处理标签内部的可能的嵌套。

  4. ( 开始的部分:
    • (?'-Open'>): 定义一个命名捕获组 Open,它匹配一个右尖括号 >,并尝试从堆栈中弹出一个名为 Open 的左尖括号。
    • [^<>]*: 匹配右尖括号后面的任意非尖括号字符。
  5. )+ 表示这个组合模式可以重复一次或多次,以确保所有打开的左尖括号都有相应的右尖括号来闭合。

  6. *: 匹配零个或多个这样的嵌套结构,以处理可能存在多个标签的情况。

  7. (?(Open)(?!)): 这是一个条件表达式,用来检查是否还有未匹配的左尖括号。如果堆栈中还有未匹配的 Open,则匹配失败。

  8. >: 最外层的右尖括号 >,用来匹配标签的结束部分。

这段正则表达式的目的是确保在处理 HTML 标签或类似的结构时,所有的左尖括号 < 都有相应的右尖括号 > 来正确闭合,而且不会有嵌套的标签结构错位。

(?(Open)(?!)) 是正则表达式中的条件表达式,用来检查是否堆栈中还有未匹配的捕获组 Open

解释:

  • (?(Open)(?!)):这个结构分为两部分:
    • ?(Open):这是条件部分,它检查是否存在名为 Open 的捕获组。在正则表达式中,捕获组的堆栈类似于一个栈,用于跟踪捕获组的开启和关闭。
    • (?!):这是断言部分,表示否定前向查找。它会尝试匹配当前位置,但只有在紧接着的位置不匹配条件时才成功。换句话说,它要求当前位置的后续内容不匹配 Open 的条件。

工作原理:

  1. 条件部分 (?(Open)
    • 如果存在名为 Open 的捕获组,说明在之前的正则表达式中有使用 (?'Open'<) 来定义一个名为 Open 的捕获组,并且在后续的模式中可能会有对应的 (?'-Open'>) 来关闭这个捕获组。
  2. 断言部分 (?!)
    • 如果条件部分 (?(Open) 成立(即存在未关闭的 Open 捕获组),则断言部分 (?!) 将尝试在当前位置匹配失败。这意味着如果在当前位置后面的内容中继续有 < 字符(即未关闭的标签),则整个正则表达式将不匹配。

实际应用:

在处理嵌套结构(如 HTML 标签)时,这种技术可以确保所有打开的标签都有对应的闭合标签。如果条件部分 (?(Open) 检测到还有未匹配的打开标签 Open,那么断言部分 (?!) 将强制匹配失败,因为当前位置后面的内容与期望的闭合标签不匹配,从而使整个正则表达式不匹配。

这种技术在正则表达式中的应用,使得能够更加精确和有效地处理复杂的嵌套结构,如处理文本中的嵌套括号、HTML 标签等情况。


前瞻后顾一些误区

(?<=\$)\d+(?=\$)\d+ 表达的意思是不同的,具体区别如下:

1. (?<=\$)\d+(正后顾)

  • 解释:匹配在美元符号 $ 后面的数字,但不包括 $ 本身。
  • 实例:匹配字符串 "Total is $100" 时,(?<=\$)\d+ 会匹配 100,因为它检查的是 $ 后面的数字。

2. (?=\$)\d+(正前瞻,错误)

  • 解释:这个正则表达式的意思是匹配前面是 $ 的数字,但不包含 $ 本身。
  • 实例:匹配字符串 "100$" 时,(?=\$)\d+ 会匹配 100,但它检查的是数字前面是否有 $,而不是后面。

3. 两者的区别

  • (?<=\$)后顾,它是从当前位置向看,确认前面是 $
  • (?=\$)前瞻,它是从当前位置向看,确认后面是 $

总结

  • (?<=\$)\d+:匹配在 $ 符号后面的数字。
  • (?=\$)\d+:匹配后面是 $ 符号的数字。

因此,(?=\$)\d+ 是有效的,但它的含义和 (?<=\$)\d+ 不同。

是的,你的理解是正确的!如果你想匹配在 $ 符号前面的数字,应该写成 \d+(?=\$)

解释:

  • \d+:匹配一个或多个数字。
  • (?=\$):这是正前瞻,它检查在数字后面是否跟着一个 $ 符号,但不包含 $

示例:

  • 对于字符串 "100$"(\d+(?=\$)) 会匹配 100,因为它确认数字后面紧跟着一个 $ 符号。

综上:

  • (?<=\$)\d+:匹配 $ 后面的数字。
  • \d+(?=\$):匹配数字并确保后面紧跟着 $ 符号。

这两个正则表达式是针对不同的需求的。如果你的目标是匹配数字前面没有 $,就使用 (?=\$);如果要确保匹配的数字后面是 $,就使用 (?<=\$)