写程序的人迟早都要面对一个问题:你拿到了一段文本,想查出里面到底有没有手机号、邮箱或者 IP;你想把一坨日志里所有 404 的请求行挑出来;你想把用户输入里的多余空格统一干掉。如果全靠手工循环和字符串截取,不仅写起来累,改起来更要命。正则表达式就是为解决这类“按模式处理文本”而生的工具。虽然教科书通常把它放到靠后的章节,比如第 16 章,但实际项目里,它出现的频率一点不比 if/else 低。
这篇内容我不打算给你照搬手册,而是把它当成一个“文本处理经验帖”来写。你会看到正则表达式语法里真正值得背的内容、在 C# 里判断 IP 地址并计算单网段最大可用主机数的方法、在 grep 命令行里安全使用正则的避坑姿势,以及 Delphi 这种老干部框架下怎么低门槛地把正则用起来。通篇都是我在实际代码里踩过、调过、优化过的东西,尽量用大白话讲透,并且保证你照着能直接抄作业。
1. 正则表达式到底是什么,为什么要单独开一章
我习惯把正则表达式理解成一种“在文本上做模式匹配的小型编程语言”。绝大多数编程语言里字符串函数已经很强了,但它只能做点对点的精确匹配:contains 判断有没有,replace 替换指定串,split 按固定分隔符切开。可现实中文本没有这么规矩,电话号码有区号也可能没区号,日志行的前缀有时候是 INFO 有时候是 WARNING,IP 地址第四段可能是 1 也可能是 255。这种情况要继续用精确函数去写,代码就会越来越长、判断越来越碎,最后自己都看不懂分支到底覆盖了什么。正则表达式的价值,就是把那些“符合某类形状”的文本统一抽象成一个表达式,让代码只关心“这种形状是否存在”,而不是一个字符一个字符地判断。
当然这里有个容易被怀疑的问题:为什么很多教编程的书都把正则放到偏后面的第 16 章、第 17 章?我个人的理解是,它和函数、数组这类基础语法不太一样——正则的调试思维偏向“声明式”。你告诉它“开头必须是字母,后面是 6 到 8 位数字”,剩下的是引擎自己去跑状态机。新人如果没写过几次文本处理,很容易背了一堆规则却不知道用在哪。所以它像“内力”而不是“招式”,前期铺垫够了再用,效果要比一上来硬啃好得多。
1.1 我眼里的正则表达式解决的核心问题
核心问题我一直觉得只有三个:校验、提取、替换。校验是判断一段输入是否符合格式;提取是从一段混合文本里捞出子串;替换是把符合规则的内容改成另一种形态。这三个动作覆盖了绝大多数文本处理需求。比如注册页验证手机号是校验,从一段乱糟糟的备注里找出所有订单编号是提取,把用户输入的多个空格折成一个空格是替换。无论你用 Python、Java、C# 还是命令行 grep,本质都在这三件事上打转。
只要把这三点想清楚,后面写表达式时会更有方向。因为你不会急着去看语法大全,而是先问自己:我到底要匹配哪类字符,这个模式可能出现多少次,我关心的部分需不需要在后续代码里引用。带着这三个问题去写,比从第一个元字符背到最后一个元字符要实用得多。
1.2 会用正则的人,和不会用正则的人,差别在哪
区别不在“背了多少语法”,而在“边界感”。同样校验一个 IPv4 地址,新手的答案可能是 \d+\.\d+\.\d+\.\d+,这个写法确实能把“192.168.1.1”匹配出来,但也会高高兴兴地把“999.1.1.1”放过去。懂正则的人会想:IPv4 每一段的范围是 0 到 255,那匹配规则不能只写“数字出现 1 到 3 次”,而是要分 1-199、200-249、250-255 几种情况来组合。
这种“边界感”,本质上是对数据格式本身的理解。正则表达式只是把这种理解翻译成引擎能读懂的东西。所以我说正则不是背出来的,是“想清楚边界以后写出来的”。后面所有实战案例,也都是围绕这个思路展开的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 正则表达式语法大全:先记得住,再说熟练
网上搜索“正则表达式语法大全”,出来的表都特别长,看起来和查字典一样。真要这么学,容易劝退。我建议把它压成三块来记:字符怎么表示,次数怎么控制,分组和位置怎么处理。这三块能覆盖 90% 的日常需求。至于反向预查、命名分组这些高级能力,等你把一个基础模式写熟了再回头翻不迟。
2.1 字符匹配三兄弟:字符原义、字符集、预定义转义
正则表达式的第一层含义是“匹配哪个字符”。最简单的情况,字面量 abc 就匹配字符串里的 abc。但这显然不够用,所以有了字符集和转义符。
| 写法 | 含义 | 典型例子 |
|---|---|---|
[abc] |
匹配 a/b/c 中任意一个 | b[ae]t 可匹配 bat、bet |
[a-z] |
匹配小写字母 | [A-Za-z0-9] 匹配字母或数字 |
[^0-9] |
排除数字,注意是排除不是取反后的匹配 | [^,] 匹配非逗号字符 |
\d |
一个数字,等价于 [0-9] |
\d{4} 匹配四位数字 |
\w |
一个单词字符,字母/数字/下划线 | 常用于匹配变量名 |
\s |
一个空白字符,含空格、Tab、换行 | 处理带缩进的文本 |
. |
匹配任意字符,默认不匹配换行 | 在日志中很常用 |
用字符集是个好习惯。有人会把“一个数字”直接写成 [0-9],也有人直接写 \d,都行。但如果你匹配的是十六进制数,就必须写成 [0-9A-Fa-f],这时候没有现成的 \h 可以偷懒。另一点容易忽略的,是 . 在正则里的特殊含义。很多新手看到 a.b 以为只匹配 a.b,实际它还能匹配 a1b、a-b。如果确实要匹配一个点号,得写成 \.,或者在字符集里写 [.]。IP 地址每段中间的“点”,就必须转义,这也是后面 IP 校验很容易翻车的原因。
2.2 量词决定数量,贪婪与懒惰决定脾气
光知道匹配什么字符还不够,因为大多数输入都不会只有一位。量词解决的就是“匹配多少次”的问题。
| 量词 | 含义 |
|---|---|
* |
重复 0 次或多次,越多越好 |
+ |
重复 1 次或多次 |
? |
重复 0 次或 1 次,也可用于关闭贪婪 |
| `{n |
