# 正则表达式

## 元字符

元字符是正则表达式规定的一个特殊代码，它不代表自己本身的字符，而是具有特殊含义。

几个例子:

- `\ba\w*\b` 匹配以字母a开头的单词——先是某个**单词开始**处(`\b`)，然后是字母a,然后是任意数量的**字母或数字**(`\w*`)，最后是**单词结束**处(`\b`)。
- `\d+` 匹配1个或更多连续的**数字**。这里的`+`是和`*`类似的元字符，不同的是`*`匹配重复**任意次**(可能是0次)，而`+`则匹配重复**1次或更多次**。
- `\b\w{6}\b` 匹配刚好6个字符的**单词**。


常用的元字符如下表所示:

| 字符 | 说明 |
|:----:|:----:|
| .   | 匹配除换行符以外的任意字符 |
| \w  | 匹配字母或数字或下划线或汉字 |
| \s  | 匹配任意的空白符 |
| \d  | 匹配数字 |
| \b  | 匹配单词的开始或结束 |
| ^   | 匹配文本的开始 |
| $   | 匹配文本的结束 |

## 反义

有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外，其它任意字符都行的情况，这时需要用到反义。

常用的反义字符如下表所示:

| 字符/语法 | 说明 |
|:----:|:----:|
| \W | 匹配任意不是字母，数字，下划线，汉字的字符 |
| \S | 匹配任意不是空白符的字符 |
| \D | 匹配任意非数字的字符 |
| \B | 匹配不是单词开头或结束的位置 |
| [\^x] | 匹配除了x以外的任意字符 |
| [\^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |

例子:

- `\S+`匹配不包含空白符的字符串。

- `<a[^>]+>`匹配用尖括号括起来的以a开头的字符串。

## 字符转义

如果你想查找元字符本身的话，比如你查找`.`,或者`*`,就出现了问题：你没办法指定它们，因为它们会被解释成别的意思。

这时你就得使用`\`(反斜杠，在回车Enter键左边或左上)来取消这些字符的特殊意义。因此，你应该使用`\.`和`\*`。当然，要查找`\`本身，你也得用`\\`。

例如: `deerchao\.cn`匹配"deerchao.cn"，`C:\\Windows`匹配"C:\Windows"。

## 重复

元字符`^`(和数字6在同一个键位上的符号)和`$`都匹配一个位置，这和`\b`有点类似。`^`匹配你要用来查找的**字符串的开头**，`$`匹配**结尾**。这两个代码在验证输入的内容时非常有用，比如一个网站如果要求你填写的QQ号必须为5位到12位数字时，可以使用: `^\d{5,12}$`。

这里的`{5,12}`和前面介绍过的`{2}`是类似的，只不过`{2}`匹配**只能不多不少重复2次**，`{5,12}`则是重复的次数**不能少于5次，不能多于12次**，否则都不匹配。

因为使用了`^`和`$`，所以输入的整个字符串都要用来和`\d{5,12}`来匹配，也就是说整个输入必须是**5到12个数字**，因此如果输入的QQ号能匹配这个正则表达式的话，那就符合要求了。 

常用的限定符如下表所示:

| 字符/语法 | 说明 |
|:----:|:----:|
| * | 重复零次或更多次 |
| + | 重复一次或更多次 |
| ? | 重复零次或一次 |
| {n} | 重复n次 |
| {n,} | 重复n次或更多次 |
| {n,m} | 重复n到m次 |

## 贪婪与懒惰

当正则表达式中包含能接受重复的限定符时，通常的行为是（在使整个表达式能得到匹配的前提下）匹配**尽可能多**的字符。以这个表达式为例：`a.*b`，它将会匹配最长的以a开始，以b结束的字符串。如果用它来搜索aabab的话，它会匹配整个字符串aabab。这被称为**贪婪**匹配。

有时，我们更需要**懒惰**匹配，也就是匹配**尽可能少**的字符。前面给出的限定符都可以被转化为懒惰匹配模式，只要在它后面加上一个问号`?`。这样.`*?`就意味着匹配任意数量的重复，但是在能使整个匹配成功的前提下使用最少的重复。

## 字符类

要想查找数字，字母或数字，空白是很简单的，因为已经有了对应这些字符集合的元字符，但是如果你想匹配没有预定义元字符的字符集合(比如元音字母a,e,i,o,u),应该怎么办？

很简单，你只需要在方括号里列出它们就行了，像`[aeiou]`就匹配任何一个英文元音字母，`[.?!]`匹配标点符号(.或?或!)。

我们也可以轻松地指定一个字符**范围**，像`[0-9]`代表的含意与`\d`就是完全一致的：一位数字；同理`[a-z0-9A-Z_]`也完全等同于`\w`（如果只考虑英文的话）。

下面是一个更复杂的表达式：`\(?0\d{2}[) -]?\d{8}`。

这个表达式可以匹配几种格式的电话号码，像(010)88886666，或022-22334455，或02912345678等。我们对它进行一些分析吧：首先是一个转义字符`\(`,它能出现0次或1次(`?`),然后是一个0，后面跟着2个数字(`\d{2}`)，然后是)或-或空格中的一个，它出现1次或不出现(`?`)，最后是8个数字(`\d{8}`)。

## 分枝条件

不幸的是，刚才那个表达式也能匹配010)12345678或(022-87654321这样的“不正确”的格式。要解决这个问题，我们需要用到分枝条件。

正则表达式里的分枝条件指的是有几种规则，如果满足其中任意一种规则都应该当成匹配，具体方法是用`|`把不同的规则分隔开。

下面是几个例子:

- `0\d{2}-\d{8}|0\d{3}-\d{7}`这个表达式能匹配两种以连字号分隔的电话号码：一种是三位区号，8位本地号(如010-12345678)，一种是4位区号，7位本地号(0376-2233445)。

- `\(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}`这个表达式匹配3位区号的电话号码，其中区号可以用小括号括起来，也可以不用，区号与本地号间可以用连字号或空格间隔，也可以没有间隔。你可以试试用分枝条件把这个表达式扩展成也支持4位区号的。

- `\d{5}-\d{4}|\d{5}`这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字，或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题：**使用分枝条件时，要注意各个条件的顺序**。如果你把它改成`\d{5}|\d{5}-\d{4}`的话，那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时，将会从左到右地测试每个条件，如果满足了某个分枝的话，就不会去再管其它的条件了。 

## 分组

我们已经提到了怎么重复单个字符（直接在字符后面加上限定符就行了）；但如果想要重复多个字符又该怎么办？

你可以用**小括号**来指定子表达式(也叫做分组)，然后你就可以指定这个子表达式的重复次数了，你也可以对子表达式进行其它一些操作。

`(\d{1,3}\.){3}\d{1,3}`是一个简单的IP地址匹配表达式。要理解这个表达式，请按下列顺序分析它：`\d{1,3}`匹配1到3位的数字，`(\d{1,3}\.){3}`匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次，最后再加上一个一到三位的数字(`\d{1,3}`)。

不幸的是，它也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话，或许能简单地解决这个问题，但是正则表达式中并不提供关于数学的任何功能，所以只能使用冗长的分组，选择，字符类来描述一个正确的IP地址：`((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)`。
