第 3 课:控制重复次数的量词
本课目标:
- 用
*、+、?重复前面的条目。- 用
{n}和{n,m}固定精确或有界的次数。- 说出量词附着在什么上(它前面紧挨着的单个条目)。
一个字符类匹配一个字符,可数据是成串来的
上一课里,每个字符类都恰好匹配一个字符。真实数据不会一次只来一个字符:邮政编码是五位数字,用户名是一串单词字符,电话号码是一堆数字加上可能出现的连字符。如果一个字符类只匹配一个位置,怎么匹配“一整串”?这正是量词的全部工作:这族元字符说明它们前面那个东西重复多少次。要是搞错量词抓住的是“前面的哪个东西”,你的计数就会落在错误的位置上。
讲解
量词(quantifier)紧跟在一个条目后面,控制该条目重复多少次。有三个你会不停用到1:
*:前面的条目,重复 0 次或更多。/bo*/匹配 “b”(零个 “o”)也匹配 “boooo”。+:前面的条目,重复 1 次或更多。/a+/匹配 “candy” 里的那一个 “a”,也匹配 “caaandy” 里的全部 “a”1。?:前面的条目,出现 0 次或 1 次(可选)。/colou?r/同时匹配 “color” 和 “colour”,因为 “u” 是可选的。
需要精确或有界的数量时,用花括号1:
{n}:恰好 n 次。/a{2}/匹配 “aa”。{n,m}:至少 n 次、至多 m 次。/a{1,3}/匹配一到三个 “a”。{n,}:n 次或更多,没有上限。
下面是让人栽跟头的细节:量词附着在它前面紧挨着的单个条目上,而不是整个模式上。在 /ab+/ 里,+ 只作用于 “b”,所以它匹配 “ab”、“abb”、“abbb”:一个 “a” 后面跟一个或多个 “b”。“a” 不会被重复。这个“前面的条目”可以是字面量、字符类,或(后面课里的)分组,但永远只是左边的那一个 token。
一句下一课才会兑现的预警:* 和 + 是贪婪(greedy)的:它们能抓多少重复就抓多少。/a+/ 在 “caaandy” 上拿走全部三个 “a”,不是一个。我们会在第 6 课把这个行为(以及如何反转它)当作全部重点。
完整示例(跟着做)
你想匹配 “09:30” 这样的时间:两位数字、一个冒号、两位数字。用字符类加计数拼出来:
一步步看:\d{2} 的意思是恰好两位数字;: 是一个字面量冒号;第二个 \d{2} 又是两位数字。“09:30” 严丝合缝地符合这个形状,所以 test 是 true。“9:30” 失败,因为 {2} 要求冒号前有两位数字,这里只有一位。每个量词都附着在它前面紧挨着的 \d 上:不附着在冒号上,也不附着在整个模式上。
你来试试(填空示例)
你想匹配一个话题标签:一个 # 后面跟一个或多个单词字符(所以 #a 和 #regex101 都匹配,孤零零的 # 不匹配)。填上量词:
答案:空格处填 +,得到 /#\w+/。# 是字面量,\w+ 的意思是“一个或多个单词字符”。因为 + 至少要求一个,光秃秃的 “#” 会失败(它后面有零个单词字符),而 “#regex101” 匹配。如果你填的是 *,孤零零的 “#” 也会匹配,因为 * 允许零个:这正是你现在可以主动做出的“0 个起步还是 1 个起步”的选择。
小结 + 下节课预告
量词说明它们前面那个条目重复多少次:*(0 次起)、+(1 次起)、?(0 或 1 次)、{n}(恰好 n 次)、{n,m}(n 到 m 次)。每个量词都附着在左边的单个 token 上,*/+ 默认贪婪。你现在可以匹配长度可控的字符串段了。但 /\d{5}/ 这样的模式仍然匹配长字符串里任何位置的五位数字:它还说不出“并且周围没有别的东西”。下一课我们加上锚点,把匹配钉在开头、结尾或词边界上。
Footnotes
Exercises
为 “90210-1234” 这样的美式 ZIP+4 写一个模式:五位数字、一个连字符、然后四位数字。用 "90210-1234"(应当通过)和 "90210"(应当失败)测试它。然后解释为什么 /\d+-\d+/ 是个更差的写法。
第 2 级(进阶)My note
Jot down thoughts, sticking points, things you didn't get. Written to this course's appendix only — the lesson file is never touched.