正则表达式中的 问号(?) 语法概览
? 是正则表达式中一个用途非常广泛的符号,能实现很多不同的功能。本文将带你深入了解它在正则表达式中的各种应用,并逐一讲解每种用法。
1. 量词的“非贪婪”匹配(取消贪婪)
在正则表达式中,量词(如 *、+、{n,m})默认是 贪婪的,即尽可能多地匹配字符。然而,在某些情况下,我们可能只希望匹配最少的字符,这时可以通过在量词后加上 ? 来将其转换为 非贪婪 模式。
1.1 贪婪匹配 vs 非贪婪匹配
贪婪匹配:
a.*b
这个表达式会尽可能多地匹配字符,直到找到最后一个 b。例如,匹配 a1234b5678b。
非贪婪匹配:
a.*?b
这个表达式会匹配从 a 到第一个 b,即只匹配最少的字符,如 a1234b。
解释:
*表示匹配 0 次或多次,贪婪会尽可能匹配更多字符。*?表示非贪婪匹配,尽量匹配最少字符。
常见的非贪婪量词:
*?:匹配 0 次或多次,尽量少匹配字符。+?:匹配 1 次或多次,尽量少匹配字符。??:匹配 0 次或 1 次,尽量少匹配字符。{n,m}?:匹配介于n到m次之间,尽量少匹配字符。
2. 环视(Lookahead 和 Lookbehind)
环视是正则表达式中的高级特性,用于指定某个模式前后必须(或不能)出现的条件,而不消耗字符。环视可以分为四种类型:
2.1 正向环视(Positive Lookahead)(?=...)
正向环视用于匹配后面必须跟随某个模式,但不会包括该模式的内容。例如:
\d(?=\D)
这表示匹配一个数字,且它后面必须是一个非数字字符。
解释:
(?=\D)表示后面必须跟着一个非数字字符。\d匹配数字。
2.2 负向环视(Negative Lookahead)(?!...)
负向环视与正向环视相似,只是要求后面不能跟随某个模式。例如:
\d(?!\d)
这表示匹配一个数字,但它后面不能是另一个数字。
解释:
(?!\d)表示后面不能跟着另一个数字。\d匹配数字。
2.3 正向回溯环视(Positive Lookbehind)(?<=...)
正向回溯环视匹配某个位置,要求在此位置之前满足某个模式。例如:
(?<=@)\w+
这表示匹配一个单词(\w+),前提是它前面有一个 @ 符号。
解释:
(?<=@)表示匹配位置前面有@符号。\w+匹配一个单词字符(字母、数字或下划线)。
2.4 负向回溯环视(Negative Lookbehind)(?<!...)
负向回溯环视与正向回溯环视类似,只是要求匹配的前面不能是某个模式。例如:
(?<!@)\w+
这表示匹配一个单词(\w+),前提是它前面没有 @ 符号。
解释:
(?<!@)表示匹配位置前面没有@符号。\w+匹配一个单词字符。
3. 命名捕获组 (?P<name>...)
命名捕获组让你可以给捕获的分组指定名称,便于后续引用和匹配。通过命名捕获组,你不仅可以使正则表达式更具可读性,还可以更方便地引用捕获的内容。对于多个捕获分组,你可以通过组名来引用,而不必记住它们的编号。
例如:
(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})
这个正则表达式会捕获日期中的年份、月份和日期,并将它们命名为 year、month 和 day,便于后续引用。
解释:
(?P<year>\d{4})捕获 4 位数字并命名为year。(?P<month>\d{2})捕获 2 位数字并命名为month。(?P<day>\d{2})捕获 2 位数字并命名为day。
通过命名捕获组,你可以使用组名(如 year)来引用捕获内容,而不需要记住组号。
3.1 使用后向引用
在正则表达式中,后向引用(如 \1、\2)用于引用之前捕获的分组。当正则表达式有多个分组时,后向引用会按顺序分配索引,例如 \1 引用第一个分组,\2 引用第二个分组,依此类推。在实际应用中,使用后向引用可以实现对重复内容的匹配。
如果你使用了命名捕获组,可以通过 \k<name> 来引用命名捕获组,避免使用数字索引,从而增强可读性。例如,\k<year> 引用名为 year 的捕获组。
举个例子:
(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})\k<year>
根据上面的正则表达式:最后的 \k<year> 表示匹配与 year 捕获组相同的内容。也就是说,这个正则要求日期的年份与后面的年份部分相同,形成一个对称的匹配。
匹配成功的文本示例:
2025-08-15-20251999-12-31-19992023-01-01-2023
匹配失败的文本示例:
2025-08-15-20241999-12-31-20002023-01-01-2024
需要注意的是,尽管命名捕获组可以通过名字引用,但它们仍会占用一个数字索引。所有捕获组都会根据它们在正则表达式中的位置分配索引,从 \1 开始。第一个捕获组(无论是命名的还是未命名的)会被分配为 \1,第二个为 \2,以此类推。因此,命名捕获组的作用主要是提升语义的清晰度,但它们仍然会按顺序分配数字索引。最后强调一点,环视不是捕获组,其本身也不存在后向引用的说法。
4. 非捕获分组 (?:...)
有时你只需要匹配某些内容,但不希望将其存储为捕获组,这时可以使用非捕获分组 (?:...)。它可以避免将匹配的内容存入内存,从而提升效率并使正则表达式更加简洁。
例如:
(?:\d{3})-(\d{2})-(\d{4})
这个正则表达式会匹配类似 123-45-6789 的号码。这里,(?:\d{3}) 是一个非捕获分组,因此不会创建捕获组,也就不会生成后向引用。而 (\d{2}) 和 (\d{4}) 分别是捕获组,匹配的内容可以通过 \1 和 \2 来引用。
概念辨析:
- 命名捕获组:使用
(?P<name>...)来为捕获的分组命名,方便后续引用。对于有多个分组的正则表达式,使用命名捕获组比记住数字组号更易于管理。- 后向引用:使用
\1、\2等来引用先前捕获的分组,命名捕获组也可以使用\k<name>进行引用。- 非捕获分组:通过
(?:...)创建不需要后续引用的分组,适用于只需要匹配但不捕获的情况。
5. 其他用途
5.1 可选匹配 ?
? 还可以用来表示前面的元素是 可选的,即它可以匹配 0 次或 1 次。例如:
colou?r
这个正则表达式会匹配 color 或 colour,u 字母是可选的。
5.2 条件表达式(Conditional Expressions)(?(id)yes-pattern|no-pattern)
条件表达式允许基于捕获组是否已匹配来决定使用哪个模式。例如:
^(?(1)\d{3}-\d{2}-\d{4}|\d{5})$
这个正则表示,如果捕获组 1 匹配了,就匹配类似 123-45-6789 的社会安全号码格式,否则匹配 5 位数字。
6. 总结
? 在正则表达式中有多种重要用途,具体包括:
- 非贪婪匹配:通过在量词后加
?,将默认的贪婪模式转为非贪婪模式,尽量匹配最少的字符。 - 环视:
?用于实现正向环视((?=...))、负向环视((?!...))、正向回溯环视((?<=...))和负向回溯环视((?<!...))。 - 命名捕获组:通过
(?P<name>...)为捕获组命名,便于引用和提高正则的可读性。 - 非捕获分组:使用
(?:...)创建非捕获分组,以避免不必要的内存占用。 - 可选匹配:
?可用于将前面的元素或分组设为可选,即匹配 0 次或 1 次。 - 条件表达式:
(?(id)yes-pattern|no-pattern)用于根据捕获组的匹配与否,决定使用不同的匹配模式。
通过灵活应用 ?,你可以编写更加精确、高效的正则表达式,满足各种复杂的匹配需求。
更多推荐




所有评论(0)