元字符:(不说明,自己对照表自己捣鼓就行)
. |
匹配除换行符以外的任意字符 |
\w |
匹配字母或数字或下划线或汉字 |
\s |
匹配任意的空白符 |
\d |
匹配数字 |
\b |
匹配单词的开始或结束 |
^ |
匹配字符串的开始 |
$ |
匹配字符串的结束 |
字符转义:与正常语言一致,均使用'\'转义。(不解释,也没必要解释)
重复方式:(不说明,自己对照表自己捣鼓就行)
代码/语法 |
说明 |
* |
重复零次或更多次 |
+ |
重复一次或更多次 |
? |
重复零次或一次 |
{n} |
重复n次 |
{n,} |
重复n次或更多次 |
{n,m} |
重复n到m次 |
字符类:
[expression0expression1…] 表示或的关系,表达式中expressions只要有一个expression成立既可以。如[a-z0-9A-Z_]匹配英文字母或者阿拉伯数字
分支条件:用于解决表达式可能接受的不正确格式,也就是说进一步滤波。从而保证接收内容的正确性。分支条件重要的是编写时的顺序安排。分支运算符号"|"的运算法则为左结合短路运算法则当发现满足某个分支的时候则不会再去管其他条件,所以分支条件的编写必须是细节到细节或者细节到整体。关于分支条件里面使用的运算符为"|",这一点所有学过布尔代数的人都很熟悉。据个例子:
\d{5}-\d{4} | \d{5} 表示的是带四位数字后缀的五位数字。后缀符号为-,或者五位数字。
\d{5} | \d{5}-\d{4} 表示的是五位数字。后面的部分 将不会使用到。
这一点机制其实可以通过;理解正则表达式自己悟出来。正则表达式其实就是一个布尔运算,不过每一个布尔因子都经过包装而已(为正则表达式因子与实际输入内容的匹配与否,bool)。只要布尔表达式最终为true,则结果符合规定,所以按照计算机的算法优化,那么短路是必定的。
分组:分组的目的是改变操作颗粒度。如果没有分组的概念,我们只能操作于单个匹配的字符。如重复,\d{5}表示单个数字重复五次。这样灵活性虽然好,但如果我操作的内容 很多,则明显带来事务繁多而且性能难以优化,且最重要的就是没事就会不小心捣鼓错。这个也是为什么现在大家在非必要的情况下,绝不会轻易自己重绘微软控件,也不会没事自己去重写类库中的方法。颗粒度大带来的是效率性能,缺点只是灵活性而已,这东西,自己找一个平衡点就行。分组的方法其实就是将你想当做颗粒的表达式直接用()括起来,那么就行了,你可以将其视作一个子表达式,可以对其直接进行操作。(\d{5}\w{2}){3},意思我就不解释了,很明了。
反义:这个需求很明了,我们自己做软件的时候,checkboxlist也必须要提供全选反选的按钮,不为什么别的,只是为了用户用起来方便。这里也一样,我们就是正则表达式的用户,那么编写者也要我们用着舒服吧,毕竟总不能我使用到非逻辑的时候,我要枚举所有其他类型吧,暂且不说其他类型是不是可以罗列清楚的。世界上很多东西非之后简直就是宇宙,说都说不清。看一下反义的表格吧。
代码/语法 |
说明 |
\W |
匹配任意不是字母,数字,下划线,汉字的字符 |
\S |
匹配任意不是空白符的字符 |
\D |
匹配任意非数字的字符 |
\B |
匹配不是单词开头或结束的位置 |
[^x] |
匹配除了x以外的任意字符 |
其实有点脑袋的都应该看出来了,如果是通配的,那么只要将其大写就行了,如果用的是字符表达式,那么也很简单,只是在前面加上^就行了。当然了,必须用[]括起来,至于括起来的原因自己去捣鼓,可以通过正则表达式的机制理解的。要注意的是这里面[^]这个东东的思想,原来[expressions]我们用的是"|"的思想,那么[^expressions]是怎么样的呢?布尔代数里面,这个应该是最基本的了,手边没有公式编辑器,不然我倒准备写几个数字电路里面常用的几个公式,其实就是或的非是每一个非的与。这个如果不懂,自己回去学布尔代数吧,基本知识。
后向引用:
还是先说为什么会有这个东东吧。写代码的时候大家有没有想过为什么需要名字,其实就是复用,这个绝对不是面向对象专有的。而且你用面向结构的思想写一个application再用面向对象的思想写一个同样的application,其实说真的,代码复用,绝对是面向结构的大于面向对象的。但面向结构淘汰掉的主要原因就是开发效率开发成本以及最最重要的维护。同样一个application面向结构的整体把握比面向对象难得很多,而且对于团队要求高,至于维护,那简直就是悲剧了,对于接手项目维护的人来说约等于自杀。
现在来说用法吧,其实就是使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。后向引用用于重复搜索前面某个分组匹配的文本,如\1代表分组1匹配的文本。\b(\w+)\b\s+\1\b可以用来匹配重复的单词
当然也可以自己指定组名(枚举还可以自己指定索引值呢)。(?<Word>\w+)(或者把尖括号换成'也行:(?'Word'\w+))。
分类 |
代码/语法 |
说明 |
捕获 |
(exp) |
匹配exp,并捕获文本到自动命名的组里 |
(?<name>exp) |
匹配exp,并捕获文本到名称为name的组里,也可以写成(?'name'exp) | |
(?:exp) |
匹配exp,不捕获匹配的文本,也不给此分组分配组号 | |
零宽断言 |
(?=exp) |
匹配exp前面的位置 |
(?<=exp) |
匹配exp后面的位置 | |
(?!exp) |
匹配后面跟的不是exp的位置 | |
(?<!exp) |
匹配前面不是exp的位置 | |
注释 |
(?#comment) |
这种类型的分组不对正则表达式的处理产生任何影响,用于提供注释让人阅读 |
解释下吧,这一段也贼晦涩了,真的很不好懂,尤其原文中那个为什么的提问,绝大多数人会傻眼。我上网搜索了一下,就顺别盗用别人的研究成果了,权当一回贼了。
(?=exp) 匹配exp前面的位置如:\b\w+(?=ing\b)在查找I'm singing while you're dancing的时候会匹配sing和danc。原因,自己看一看就知道了。需要记住的是这里面匹配的只是一个位置,不是任何字符。
(?<=exp) 匹配exp后面的位置。同样是上面的那个例子,如果正则表达式变为\b\w+(?<=ing\b),匹配结果就变成了singing和dancing,因为(?<=ing)匹配的是ing后面的位置。
(?!exp)匹配后面跟的不是exp的位置。\b\w+n(?!g)查询I'm singing while you're dancing,那么结果就是dan。
(?<!exp)匹配前面不是exp的位置。(这个我没捣鼓出来,正郁闷着呢,我就纳闷了,怎么出来的不是我想要的呢,难道我理解错了?) (捣鼓出来了原来匹配要写全了,如此而已,例如\b\w+g(?<!n))查询的话,并不能避开singing ,但如果写成\b\w+g(?<!n\w+))则是可以避开singing的,也就是说查询匹配的时候,他运用的思想是贪婪法,一步走下来就不会回头,不会半路以某一个字为起点到终点进行匹配,看来这个原因也是他之所以会贪婪的一个原因)
贪婪与懒惰:
当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符。以这个表达式为例:a.*b,它将会匹配最长的以a开始,以b结束的字符串。如果用它来搜索aabab的话,它会匹配整个字符串aabab。这被称为贪婪匹配。
有时,我们更需要懒惰匹配,也就是匹配尽可能少的字符。前面给出的限定符都可以被转化为懒惰匹配模式,只要在它后面加上一个问号?。这样.*?就意味着匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复。现在看看懒惰版的例子吧:
代码/语法 |
说明 |
*? |
重复任意次,但尽可能少重复 |
+? |
重复1次或更多次,但尽可能少重复 |
?? |
重复0次或1次,但尽可能少重复 |
{n,m}? |
重复n到m次,但尽可能少重复 |
{n,}? |
重复n次以上,但尽可能少重复 |
处理选项:
名称 |
说明 |
IgnoreCase(忽略大小写) |
匹配时不区分大小写。 |
Multiline(多行模式) |
更改^和$的含义,使它们分别在任意一行的行首和行尾匹配,而不仅仅在整个字符串的开头和结尾匹配。(在此模式下,$的精确含意是:匹配\n之前的位置以及字符串结束前的位置.) |
Singleline(单行模式) |
更改.的含义,使它与每一个字符匹配(包括换行符\n)。 |
IgnorePatternWhitespace(忽略空白) |
忽略表达式中的非转义空白并启用由#标记的注释。 |
ExplicitCapture(显式捕获) |
仅捕获已被显式命名的组。 |
平衡组(递归匹配):
- (?'group') 把捕获的内容命名为group,并压入堆栈(Stack)
- (?'-group') 从堆栈上弹出最后压入堆栈的名为group的捕获内容,如果堆栈本来为空,则本分组的匹配失败
- (?(group)yes|no) 如果堆栈上存在以名为group的捕获内容的话,继续匹配yes部分的表达式,否则继续匹配no部分
- (?!) 零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败
xx <aa <bbb> <bbb> aa> yy这样的字符串里,最长的配对的尖括号内的内容捕获出来:
我们需要做的是每碰到了左括号,就在压入一个"Open",每碰到一个右括号,就弹出一个,到了最后就看看堆栈是否为空--如果不为空那就证明左括号比右括号多,那匹配就应该失败。正则表达式引擎会进行回溯(放弃最前面或最后面的一些字符),尽量使整个表达式得到匹配。
< #最外层的左括号
[^<>]* #最外层的左括号后面的不是括号的内容
(
(
(?'Open'<) #碰到了左括号,在黑板上写一个"Open"
[^<>]* #匹配左括号后面的不是括号的内容
)+
(
(?'-Open'>) #碰到了右括号,擦掉一个"Open"
[^<>]* #匹配右括号后面不是括号的内容
)+
)*
(?(Open)(?!)) #在遇到最外层的右括号前面,判断黑板上还有没有没擦掉的"Open";如果还有,则匹配失败
> #最外层的右括号
这个其实就是算法的问题了。至于为什么选择堆栈,自己去想吧,很明了的。
代码/语法 |
说明 |
\a |
报警字符(打印它的效果是电脑嘀一声) |
\b |
通常是单词分界位置,但如果在字符类里使用代表退格 |
\t |
制表符,Tab |
\r |
回车 |
\v |
竖向制表符 |
\f |
换页符 |
\n |
换行符 |
\e |
Escape |
\0nn |
ASCII代码中八进制代码为nn的字符 |
\xnn |
ASCII代码中十六进制代码为nn的字符 |
\unnnn |
Unicode代码中十六进制代码为nnnn的字符 |
\cN |
ASCII控制字符。比如\cC代表Ctrl+C |
\A |
字符串开头(类似^,但不受处理多行选项的影响) |
\Z |
字符串结尾或行尾(不受处理多行选项的影响) |
\z |
字符串结尾(类似$,但不受处理多行选项的影响) |
\G |
当前搜索的开头 |
\p{name} |
Unicode中命名为name的字符类,例如\p{IsGreek} |
(?>exp) |
贪婪子表达式 |
(?<x>-<y>exp) |
平衡组 |
(?im-nsx:exp) |
在子表达式exp中改变处理选项 |
(?im-nsx) |
为表达式后面的部分改变处理选项 |
(?(exp)yes|no) |
把exp当作零宽正向先行断言,如果在这个位置能匹配,使用yes作为此组的表达式;否则使用no |
(?(exp)yes) |
同上,只是使用空表达式作为no |
(?(name)yes|no) |
如果命名为name的组捕获到了内容,使用yes作为表达式;否则使用no |
(?(name)yes) |
同上,只是使用空表达式作为no |