基础语法与元字符

正则表达式之所以"看上去像乱码",根源就是那十几个特殊符号。这一章我们先把核心元字符的作用一次性列清楚,再讲清楚如何用反斜杠让它们匹配字面字符。读完本章,你看到任何正则都能粗略拆解出"哪些是符号、哪些是字面"。

正则的核心元字符

正则里大部分字符就是匹配它们自己——字母、数字、汉字、空格都是"字面字符"。但有十几个字符对引擎有特殊含义,称为元字符(metacharacter)。下面这张表建议背下来,后面所有章节都在讲它们的具体用法。

// 正则表达式里一共有 14 个元字符(metacharacter)
// 它们对引擎有特殊含义,要匹配字面字符需要用 \ 反斜杠转义

\    反斜杠      转义字符,或构成预定义类(\d \w 等)
.    点          任意一个字符(默认除换行)
^    脱字符      字符串开头;在方括号开头表示"取反"
$    美元        字符串结尾
*    星号        前面的元素出现 0 次或多次
+    加号        前面的元素出现 1 次或多次
?    问号        前面的元素出现 0 次或 1 次
{    左花括号    量词的起始,如 {n,m}
[    左方括号    字符类的起始,如 [abc]
(    左圆括号    分组的起始,如 (...)
|    竖线        选择(或运算)

// 不同流派(flavor)略有差异,但这 14 个是 PCRE/JS/Python 通用的核心集
// 其余三个是 } ] ) —— 它们是上面三个的右半边,通常不需单独转义

注意每个元字符往往身兼多职。比如脱字符在方括号外是"开头锚点",在方括号开头是"取反";问号既是量词(0 或 1 次),放在量词后又变成"懒惰"修饰。这些会在后续章节逐一展开。

字面匹配 vs 元字符

先看最朴素的情况:当正则里没有任何元字符时,它就是在做"普通的字符串查找"——和你用 Ctrl+F 没区别。一旦出现元字符,引擎就开始按"模式"而非"字面"匹配。

// 1. 字面字符:大多数字符就是匹配它们自己
/cat/.test("a cat here");     // true
/12345/.test("order 12345");  // true
/中文/.test("一段中文");       // true

// 2. 元字符如果要匹配"它自己",前面加 \ 反斜杠
/3.14/.test("pi is 3.14159");  // true  (此处的点是元字符"任意字符")
/3\.14/.test("3.14 only");    // true  (转义后才精确匹配小数点)
/3\.14/.test("3x14");         // false (转义后只匹配真正的点)

// 3. 价格里的美元符号也要转义
/$100/.test("$100");          // false ($ 是字符串结尾元字符)
/\$100/.test("$100");         // true  (\$ 才是字面的美元符号)

关键对比:3.14 里的点是元字符(任意字符),所以也能匹配 "3x14";写成转义形式才能精确匹配小数点。同理,价格里的美元符号、文件名里的星号,统统要转义。

转义规则

"转义"就是让元字符失去特殊含义、回归字面值。规则极其简单:在前面加一个反斜杠。但要小心一个细节——反斜杠本身就是元字符,要匹配一个字面反斜杠,得写两个反斜杠(在正则源码里)。

// 转义规则:在元字符前加 \ 反斜杠
// 要匹配这些字符的字面值,前面都加 \:

\.   \*   \+   \?   \^   \$   \(   \)   \[   \]   \{   \}   \|   \\

// 例 1:匹配算式 "a*(b+c)"
/a\*\(b\+c\)/.test("result: a*(b+c) = ?");   // true

// 例 2:匹配 Windows 路径里的反斜杠
// 正则源码里 \\ 表示两个反斜杠字符,匹配一个字面 \
/C:\\Windows/.test("C:\\Windows\\System32");   // true

// 例 3:匹配 "1 + 1 = 2" 这种算式(允许空白)
/\d+\s*\+\s*\d+\s*=\s*\d+/.test("1 + 1 = 2");   // true

// 例 4:匹配 "yes/no" 里的斜杠(字面量语法需转义)
/yes\/no/.test("answer: yes/no");   // true

提示:在 Python 里推荐用 r"模式" 原生字符串,在 Go 里推荐用反引号原生字符串,这样反斜杠所见即所得,不用写两层。JavaScript 没有原生字符串,但用字面量 /.../ 写法也避免了字符串层的二次转义,只有用 new RegExp 时才需要双倍。

三个常见陷阱

新手写正则 90% 的 bug 来自转义,下面这三个坑几乎人人都踩过:

// 常见陷阱 1:忘记转义点号
// 想匹配网址 "example.com",结果连 "examplexcom" 也匹配
/example.com/.test("examplexcom");   // true  (. 是任意字符)
/example\.com/.test("examplexcom");  // false (只匹配真正的点)

// 常见陷阱 2:字符串写法 vs 字面量写法
// JS 字面量 /\d+/   → 引擎看到的就是 \d,匹配数字
// JS 字符串 "\d+"   → JS 先把 \d 当转义,变成 "d",引擎看到的是字面 d
new RegExp("\d+").test("abcd");     // false! 实际匹配字面字母 d
new RegExp("\\d+").test("a123");    // true  正确:字符串里 \\ 给引擎一个 \

// 常见陷阱 3:字面量里的斜杠必须转义
/https:\/\/example\.com/.test("https://example.com");   // true
// 或者用字符串构造,避免斜杠转义的视觉混乱
new RegExp("https://example\\.com").test("https://example.com");

一个实用速记

不确定某字符要不要转义时,有个偷懒办法:有疑问就转义。给非元字符加反斜杠在大多数字符串里不会有副作用(比如给字母 k 前加反斜杠,在 JS 里就等同字母 k 本身),而漏转义却会出 bug。不过有几个例外——下一章你会看到,反斜杠加字母构成了预定义字符类(数字、单词、空白),加了反斜杠反而有了新含义,这才是真正需要记的部分。

← 上一篇 正则表达式简介

下一篇 字符类

✈️💬