正则表达式中方括号和圆括号有什么区别?

149

这是我创建的一个在JavaScript中使用的正则表达式:

var reg_num = /^(7|8|9)\d{9}$/

这是我的团队成员建议的另一个方案。

var reg_num = /^[7|8|9][\d]{9}$/

验证电话号码的规则如下:

  • 电话号码应该只包含十个数字。
  • 第一个数字应该是7、8或9中的任意一个。

如果您从重复的地方来到这里,请注意这里的一些细节是特定于JavaScript的,但是您在这里得到的大部分建议适用于任何正则表达式实现。一些正则表达式方言(如POSIX grep)需要像 \(7\|8\|9\) 这样的反斜杠,和/或不支持 \d 简写来匹配数字。另请参见 Stack Overflow 的 regex 标签信息页面,其中涵盖了许多其他常见的初学者问题。 - tripleee
3个回答

167
这些正则表达式在匹配方面是等效的:
  • /^(7|8|9)\d{9}$/
  • /^[789]\d{9}$/
  • /^[7-9]\d{9}$/
解释如下:
  • (a|b|c) 是一个正则表达式的"或",表示 "a 或 b 或 c",虽然括号必须存在以实现或操作,但也会捕获该数字。如果要完全等效,可以使用 (?:7|8|9) 来创建一个捕获组。

  • [abc] 是一个"字符类",表示"来自 a、b、c 中的任何字符"(字符类也可以使用范围,例如[a-d] = [abcd]

这些正则表达式相似的原因是字符类可用作“或”的简写形式(但仅适用于单个字符)。在选择操作中,您还可以做像(abc|def) 这样的事情,这种操作不能转换为字符类。

38
"(7|8|9)"和"[789]"不等价,因为前者是捕获组,而后者不是。另一方面,"(?:7|8|9)"将是等价的(我猜你当然知道这个...)。 - hochl
我看到了这个正则表达式:[<<|>>|\]\]|\[\[]。由于上下文,我知道该正则表达式是尝试匹配 <<>>[[]]。但根据你所说,它应该匹配 <>[]。如果在 [] 之间使用 |,那么括号的行为会有所不同吗? - Daniel Kaplan
1
@DanielKaplan,除非你想匹配管道字符本身,否则不要在字符类[...]中使用|。此外,在字符类中重复字符没有任何效果——字符类是一个字符列表,只会匹配其中的一个。我猜你想要一个,它使用普通圆括号:(<<|>>|\]\]|\[\[) - Bohemian

77

你团队的建议几乎是正确的,除了那个犯的错误。一旦你找出原因,就永远不会忘记它。看一下这个错误。

/^(7|8|9)\d{9}$/

这是它的作用:

  • ^$ 表示锚定匹配,断言在这些锚点之间的子模式是整个匹配。字符串只有在子模式匹配整个字符串时才会匹配成功,而不仅仅是一部分。
  • () 表示一个捕获组
  • 7|8|9 表示匹配789中的任意一个。它使用交替来实现,即使用管道运算符|来交替匹配。这会在交替之间回溯:如果第一个交替没有匹配成功,引擎必须在指针位置移动到交替匹配期间之前返回,以继续匹配下一个交替;而字符类可以按顺序前进。请参见禁用优化的正则表达式引擎上的此匹配:
Pattern: (r|f)at
Match string: carat

alternations

Pattern: [rf]at
Match string: carat

class

  • \d{9} 匹配九个数字。\d 是一个简写元字符,可以匹配任何数字。
/^[7|8|9][\d]{9}$/

看看它的作用:

  • ^$ 也表示锚定匹配。
  • [7|8|9] 是一个字符类。可以匹配列表中的任何字符7|8|9,因此错误地添加了|。这种匹配不需要回溯。
  • [\d] 是一个字符类,包含元字符\d。顺便说一句,使用字符类和单个元字符的组合是一个坏主意,因为抽象层可以减慢匹配速度,但这只是实现细节,并且仅适用于少数正则表达式实现。JavaScript 不是其中之一,但它确实使子模式稍微变长了一些。
  • {9} 表示前面的单个结构总共重复九次。
优化的正则表达式为/^[789]\d{9}$/,因为/^(7|8|9)\d{9}$/会不必要地捕获内容,这会导致大多数正则表达式实现的性能下降(可能就是其中之一,因为问题中使用了var关键字,这很可能是JavaScript)。使用运行在PCRE上的可以优化掉缺少回溯的问题,但我们并不在PHP环境中,所以使用类[]而不是选择器|会给匹配带来性能提升,因为匹配不会回溯,因此匹配和失败都比使用先前的正则表达式更快。

24
请问这个屏幕截图是哪个程序的? - Mr Mystery Guest
2
@MrMysteryGuest 我猜你已经得到了答案,但是对于任何想知道的人,这来自于Unihedron在另一个线程中的评论(https://dev59.com/s2Eh5IYBdhLWcg3wtFJE#zKygEYcBWogLw_1bbEh_),这是来自 https://regex101.com 的调试器。我认为调试器自那时以来已经改变了,因为我没有静态步骤列表,但我可以逐步前进。 - Ricola

16

如果你要用其他东西替换前两个示例,它们的行为会有很大不同。如果你匹配这个:

str = str.replace(/^(7|8|9)/ig,''); 

你需要用空字符串替换7、8或9。

如果你匹配到这个

str = str.replace(/^[7|8|9]/ig,''); 

你需要将789或者竖线字符替换为空字符串。

我是通过吃亏才发现这一点的。


7
欢迎来到SO!替换或匹配,都是错误的。很多人会犯这个错误,并且通常能够逃脱惩罚——有时甚至持续数年——因为他们输入的字符串从未包含管道符(“|”)。 - Alan Moore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接