网站首页的模块布局,深圳有哪些网站开发公司,微信公众号推广创意语,芜湖哪家公司做网站不错From: http://blog.csdn.net/lxcnn/article/details/4476746 1 概述
捕获组捕获到的内容#xff0c;不仅可以在正则表达式外部通过程序进行引用#xff0c;也可以在正则表达式内部进行引用#xff0c;这种引用方式就是反向引用。要了解反向引用#xff0c;首先要了…From: http://blog.csdn.net/lxcnn/article/details/4476746 1 概述
捕获组捕获到的内容不仅可以在正则表达式外部通过程序进行引用也可以在正则表达式内部进行引用这种引用方式就是反向引用。要了解反向引用首先要了解捕获组关于捕获组参考 正则基础之——捕获组capture group。
反向引用的作用通常是用来查找或限定重复、查找或限定指定标识配对出现等等。
对于普通捕获组和命名捕获组的引用语法如下
普通捕获组反向引用\knumber通常简写为\number
命名捕获组反向引用\kname或者\kname
普通捕获组反向引用中number是十进制的数字即捕获组的编号命名捕获组反向引用中的name为命名捕获组的组名。
2 反向引用匹配原理
捕获组(Expression)在匹配成功时会将子表达式匹配到的内容保存到内存中一个以数字编号的组里可以简单的认为是对一个局部变量进行了赋值这时就可以通过反向引用方式引用这个局部变量的值。一个捕获组(Expression)在匹配成功之前它的内容可以是不确定的一旦匹配成功它的内容就确定了反向引用的内容也就是确定的了。
反向引用必然要与捕获组一同使用的如果没有捕获组而使用了反向引用的语法不同语言的处理方式不一致有的语言会抛异常有的语言会当作普通的转义处理。
2.1 从一个简单例子说起
源字符串abcdebbcde
正则表达式([ab])\1
对于正则表达式“([ab])\1”捕获组中的子表达式“[ab]”虽然可以匹配“a”或者“b”但是捕获组一旦匹配成功反向引用的内容也就确定了。如果捕获组匹配到“a”那么反向引用也就只能匹配“a”同理如果捕获组匹配到的是“b”那么反向引用也就只能匹配“b”。由于后面反向引用“\1”的限制要求必须是两个相同的字符在这里也就是“aa”或者“bb”才能匹配成功。
考察一下这个正则表达式的匹配过程在位置0处由“([ab])”匹配“a”成功将捕获的内容保存在编号为1的组中然后把控制权交给“\1”由于此时捕获组已记录了捕获内容为“a”“\1”也就确定只有匹配到“a”才能匹配成功这里显然不满足“\1”匹配失败由于没有可供回溯的状态整个表达式在位置0处匹配失败。
正则引擎向前传动在位置5之前“([ab])”一直匹配失败。传动到位置5处时“([ab])”匹配到“b”匹配成功将捕获的内容保存在编号为1的组中然后把控制权交给“\1”由于此时捕获组已记录了捕获内容为“b”“\1”也就确定只有匹配到“b”才能匹配成功满足条件“\1”匹配成功整个表达式匹配成功匹配结果为“bb”匹配开始位置为5结束位置为7。
扩展一下正则表达式“([a-z])\1{2}”也就表达连续三个相同的小写字母。
2.2 一个复杂例子的分析
详细的分析讨论参考正则表达式正向预搜索的问题。
源字符串aaa bbbb ffffff 999999999
正则表达式(\w)((?\1\1\1)(\1))
测试代码
string test aaa bbbb ffffff 999999999;
Regex reg new Regex((\w)((?\1\1\1)(\1)));
MatchCollection mc reg.Matches(test);
foreach (Match m in mc)
{ richTextBox2.Text 匹配结果 m.Value.PadRight(12, ) 匹配开始位置 m.Index \n;
}
//输出
匹配结果bb 匹配开始位置4
匹配结果ffff 匹配开始位置9
匹配结果9999999 匹配开始位置16
匹配结果分析
正则表达式(\w)((?\1\1\1)(\1))从匹配结果上分析其实就等价于 (\w)(\1)*(?\1\1\1)(\1) 这个会相对好理解一些下面讨论下分析过程。
因为“”等价于“{1,}”表示至少匹配1次下面把子表达式“((?\1\1\1)(\1))”展开来看下规律下表中的“次数”表示子表达式“((?\1\1\1)(\1))”匹配成功的次数 。 次数 等价表达式 1 (\w)((?\1\1\1)(\1)) 2 (\w)((?\1\1\1)(\1))((?\1\1\1)(\1)) 3 (\w)((?\1\1\1)(\1))((?\1\1\1)(\1))((?\1\1\1)(\1)) … …
如果最后一个“((?\1\1\1)(\1))”匹配成功那么中间的“((?\1\1\1)(\1))”一定可以匹配成功所以中间的限制条件(?\1\1\1)就没有意义了这时就可以简写为“(\1)”也就是 次数 等价表达式 1 (\w)((?\1\1\1)(\1)) 2 (\w)(\1)((?\1\1\1)(\1)) 3 (\w)(\1)(\1)((?\1\1\1)(\1)) … …
可以归纳为等价于
(\w)(\1)*((?\1\1\1)(\1))
因为“((?\1\1\1)(\1))”开始和结尾的()原来是用作量词修饰范围的这里已经没有什么意义了所以表达式最后可以归纳为等价于
(\w)(\1)*(?\1\1\1)(\1)
分析这个表达式就容易多了。“(\w)”匹配一个字符占一位“\1”是对“\w”匹配内容的引用“(\1)*”可以匹配0到无穷多个“(\w)”匹配到的字符“(?\1\1\1)(\1)”只占一位但是“(?\1\1\1)”要求所在位置右侧有三个连续相同的“(\w)”匹配到的字符所以在“(?\1\1\1)”这个位置右侧应该有三个字符不过只有这个位置右侧的一个字符计入最后的匹配结果最后两个只作为限制条件不计入最后的匹配结果 。
以“999999999”为例第一个“9”由“(\w)”匹配第二到第六个“9”由“(\1)*”来匹配第七个“9”由“(?\1\1\1)(\1)”中最后的“(\1)”来匹配而第七、八、九这三个“9”是用来保证满足“(?\1\1\1)”这个条件的。
2.3 反向引用的编号
对于普通捕获组的反向引用是通过捕获组的编号来实现的那么对于一些可能存在歧义的语法又是如何解析的呢对于正则表达式
([ab])\10
这里的“\10”会被解析成第10个捕获组的反向引用还是第1个捕获组的反向引用加一个普通字符“0”呢?不同语言的处理方式是不一样的。
string test ab0cdebb0cde;
richTextBox2.Text Regex.Match(test, ([ab])\10).Value;
在.NET中以上测试代码输出为空说明这里的“\10”被解析成第10个捕获组的反向引用而这个表达式中是不存在第10个捕获组的所以匹配结果为空。
scripttypetext/javascript
var str ab0cdebb0cde;
var reg /([ab])\10/;
var arr str.match(reg);
if(arr ! null)
{ document.write(arr[0]);
}
/script
/*--------输出--------
bb0
*/
而在JavaScript中由于浏览器解析引擎的不同得到的结果也不一样以上为IE下是可以得到匹配结果“bb0”说明在IE的浏览器引擎中“\10”被解析成第1个捕获组的反向引用加一个普通字符“0”。而在Firefox、Opera等浏览器中得到的结果为空说明“\10”被解析成第10个捕获组的反向引用而这个表达式中是不存在第10个捕获组的。
string test ab0cdebb0cde;
richTextBox2.Text Regex.Match(test, ([ab])\10, RegexOptions.ECMAScript).Value;
/*--------输出--------
bb0
*/
而在.NET中如果正则表达式加了RegexOptions.ECMAScript参数则这里的“\10”被解析成第1个捕获组的反向引用加一个普通字符“0”。
至于正则表达式中确实有10个以上的捕获组时“\10”的具体意义留给有兴趣的读者去测试了因为在实际应用当中如果你的正则表达式中用到了10个以上捕获组而同时又用到了第10个以上捕获组的反向引用时就要注意分析一下你的正则是否需要进行优化甚至于这里是否适合使用正则表达式了。
出于对现实应用场景的分析第10个以上捕获组的反向引用几乎不存在对它的研究通常仅存在于理论上。而对于10个以内捕获组反向引用后面还有数字容易造成混淆的情况可以通过非捕获组来解决。
([ab])\1(?:0)
这样就可以明确是对第1个捕获组的反向引用后面跟一个普通字符“0”。也就不会产生混淆了。
string test ab0cdebb0cde;
richTextBox2.Text Regex.Match(test, ([ab])\1(?:0)).Value;
/*--------输出--------
bb0
*/
而事实上即使是这样用的场景也非常少至今为止只在日期正则表达式中用到过。
^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\1(?:29|30)|(?:0?[13578]|1[02])\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\2(?:29))$
这一节讨论的内容了解一下就可以了在实际应用当中如果遇到注意一下不要出现混淆而导致匹配结果错误就可以了。
3 反向引用应用场景分析
反向引用的作用通常是用来查找或限定重复、查找或限定指定标识配对出现等等。以下以实例进行场景分析及应用讲解。
3.1 查找重复
查找重复通常的应用场景是查找或验证源字符串中是否有重复单词、重复项等等。
3.1.1 验证数字元素重复项
需求描述
验证源字符串中以“,”分隔的数字是否有重复项。
代码实现
string[] test new string[] { 1,2,3,123,32,13, 12,56,89,123,56,98, 8,2,9,10,38,29,2,9, 8,3,9,238,93,23 };
Regex reg new Regex(\b(\d)\b.*?\b\1\b);
foreach (string s in test)
{ richTextBox2.Text 源字符串 s.PadRight(20, ) 验证结果 reg.IsMatch(s) \n;
}
/*--------输出--------
源字符串 1,2,3,123,32,13 验证结果 False
源字符串 12,56,89,123,56,98 验证结果 True
源字符串 8,2,9,10,38,29,2,9 验证结果 True
源字符串 8,3,9,238,93,23 验证结果 False
*/
源字符串的规则比较明确就是用“,”分隔的数字类似于这种查找是否有重复的需求最简单的就是用反向引用来解决了。
由于要验证的是用“,”分隔的元素的整体是否有重复所以“(\d)”两侧的“\b”就是必须的用来保证取到的数字子串是一个元素整体而不是“123”中的“1”当然这里前后两个“\b”分别换成“(?!\d)”和“(?!\d)”是一个效果可能意义上更明确。后面的两个“\b”也是一样的作用。
3.1.2 验证连续数字是否有重复
参考 问两个正则表达式。
需求描述
数据:
1985aaa1985bb
bcae1958fiefadf1955fef
atijc1944cvkd
df2564isdjfef2564d
实现1:匹配第一次出现的四个数字.然后后面也存在这四个数字的
如:
1985aaa1985bb
第一次出现的四个数字是1985.然后后面也存在这四个数字,所以这个匹配
bcae1958fiefadf1955fef
第一次出现的四个数字是1958.然后后面不存在这四个数字.所以不匹配
-----
所以实现1.应该匹配
1985aaa1985bb
df2564isdjfef2564d
代码实现
//如果是验证第一个出现的连续4个数字是否有重复
string[] test new string[] { 1985aaa1985bb, bcae1958fiefadf1955fef, atijc1944cvkd, df2564isdjfef2564d, abc1234def5678ghi5678jkl };
Regex reg new Regex(^(?:(?!\d{4}).)*(\d{4})(?:(?!\1).)*\1);
foreach (string s in test)
{ richTextBox2.Text 源字符串 s.PadRight(25, ) 验证结果 reg.IsMatch(s) \n;
}
/*--------输出--------
源字符串 1985aaa1985bb 验证结果 True
源字符串 bcae1958fiefadf1955fef 验证结果 False
源字符串 atijc1944cvkd 验证结果 False
源字符串 df2564isdjfef2564d 验证结果 True
源字符串 abc1234def5678ghi5678jkl 验证结果 False
*/
由于需求要求验证第一次出现的四个数字是否有重复所以这里需要用“^(?:(?!\d{4}).)*(\d{4})”来保证捕获组取得的是第一次出现的四个数字。
这样写可能有些复杂可读性较差但这里需要用这种顺序环视结合贪婪模式来达到匹配第一次出现的四个数字的目的而不能使用非贪婪模式.
对于使用非贪婪模式的正则“^.*?(\d{4})(?:(?!\1).)*\1”可以看一下它匹配的结果。
string[] test new string[] { 1985aaa1985bb, bcae1958fiefadf1955fef, atijc1944cvkd, df2564isdjfef2564d, abc1234def5678ghi5678jkl };
Regex reg new Regex(^.*?(\d{4})(?:(?!\1).)*\1);
foreach (string s in test)
{ richTextBox2.Text 源字符串 s.PadRight(25, ) 验证结果 reg.IsMatch(s) \n;
}
/*--------输出--------
源字符串 1985aaa1985bb 验证结果 True
源字符串 bcae1958fiefadf1955fef 验证结果 False
源字符串 atijc1944cvkd 验证结果 False
源字符串 df2564isdjfef2564d 验证结果 True
源字符串 abc1234def5678ghi5678jkl 验证结果 True
*/
是的最后一项的验证结果也是“True”为什么会这样当捕获组“(\d{4})”匹配到“1234”时由于“1234”没有重复所以后面的子表达式匹配失败此时“.*?”会进行回溯放弃当前状态继续向前匹配直到它匹配到“5678”前的“f”由捕获组“(\d{4})”匹配到“5678”后面的子表达式可以匹配成功报告整个表达式匹配成功。
NFA引擎在有可供回溯的状态时会一直尝试直到所有可能都尝试失败后才报告失败。上例中非贪婪模式在继续尝试时是可以找到匹配成功的位置的而采用贪婪模式的正则“^(?:(?!\d{4}).)*(\d{4})”由于“^(?:(?!\d{4}).)*”匹配到的内容不可能是连续的四个数字所以无论怎么回溯接下来的“(\d{4})”都不可能匹配成功一直回溯到起始位置“^”报告整个表达式匹配失败。
而后面的顺序环视贪婪模式子表达式“(?:(?!\1).)*”则不存在以上问题所以在源字符串比较简单时可以写作“.*?”不会影响匹配结果。
而对于验证任意位置是否存在四个重复数字则不需要加起始位置的限定。
//如果是验证任意位置出现的连续4个数字是否有重复可以用我38楼的正则
string[] test new string[] { 1985aaa1985bb, bcae1958fiefadf1955fef, atijc1944cvkd, df2564isdjfef2564d, abc1234def5678ghi5678jkl };
Regex reg new Regex((\d{4})(?:(?!\1).)*\1);
foreach (string s in test)
{ richTextBox2.Text 源字符串 s.PadRight(25, ) 验证结果 reg.IsMatch(s) \n;
}
/*--------输出--------
源字符串 1985aaa1985bb 验证结果 True
源字符串 bcae1958fiefadf1955fef 验证结果 False
源字符串 atijc1944cvkd 验证结果 False
源字符串 df2564isdjfef2564d 验证结果 True
源字符串 abc1234def5678ghi5678jkl 验证结果 True
*/
3.2 限定指定标识配对
相对于查找重复来说查找或指定标识配对出现这种应用场景要更多一些。尤其是对于HTML的处理中这种应用更普遍。
3.2.1 限定标点配对
由于HTML语言的不规范性导致以下三种写法可以被解析。
1. a hrefwww.csdn.netCSDN/a
2. a hrefwww.csdn.netCSDN/a
3. a hrefwww.csdn.netCSDN/a
而这对于一些需要进行字符串解析的应用造成很大的麻烦。在提取链接时虽然两侧都用“[‘”]?”通常也可以得到正确结果却不如用反向引用来得严谨、方便。
Regex reg new Regex((?is)a(?:(?!href).)*href([]?)(?url[^\s]*)\1[^]*(?text(?:(?!/a).)*)/a);
MatchCollection mc reg.Matches(yourStr);
foreach (Match m in mc)
{ richTextBox2.Text m.Groups[url].Value \n; richTextBox2.Text m.Groups[text].Value \n;
}
/*--------输出--------
www.csdn.net
CSDN
www.csdn.net
CSDN
www.csdn.net
CSDN
*/
以下可以正确解析出三种形式的HTML代码中的链接和文本下面把正则改一下
Regex reg new Regex((?is)a(?:(?!href).)*href([])?(?url[^\s]*)\1[^]*(?text(?:(?!/a).)*)/a);
看到区别了吗只是把“([‘””]?)”改成了“([‘””])?”结果会怎么样呢
Regex reg new Regex((?is)a(?:(?!href).)*href([])?(?url[^\s]*)\1[^]*(?text(?:(?!/a).)*)/a);
MatchCollection mc reg.Matches(yourStr);
foreach (Match m in mc)
{ richTextBox2.Text m.Groups[url].Value \n; richTextBox2.Text m.Groups[text].Value \n;
}
/*--------输出--------
www.csdn.net
CSDN
www.csdn.net
CSDN
*/
结果只取到了两组数据。这是因为对于情况1的HTML字符串在“([‘””]?)”这种情况下捕获组虽然匹配到的只是一个位置但毕竟是匹配成功了所以可以用“\1”进行反向引用而改成“([‘””])?”捕获组根本就没有进行匹配所以也就无法进行反向引用。
当然对于HTML来说还有一些比较复杂的情况如
a hrefjavascript:alert(1 2)/
这种复杂情况涉及到的场景比较少通常应用可以不予以考虑否则考虑的场景太复杂会影响匹配效率。写正则的一个一般原则就是适用就好。这种场景如果遇到需求根据具体情况是否需要提取等进行分析根据分析结果不同写出的正则也是不一样的。
3.2.2 限定标签配对
这种应用一般是在取某几个特定标签或是动态生成正则表达式时用到。
需求描述
删除script……/script与style……/style标签及其中间的内容。
代码实现
Regex reg new Regex((?is)(script|style)\b[^]*(?(?!\1\b).)*/\1);
string result reg.Replace(yourStr, );
因为这里要删除的标签不止一个所以事先无法确定是哪个标签需要用到反向引用来限定标签的配对。
当然对于标签有嵌套的情况就要用到平衡组了。可以参考 .NET正则基础之——平衡组。
3.2.3 取配对标签中的内容
需求描述
[id]5554323[id!][destid]10657302023180404[destid!][srcterminalid]13518841197[srcterminalid!][msgcontent]好的[msgcontent!][receivetime]20090409165217[receivetime!]
源字符串中标签成对出现无嵌套分别提取标签和对应的内容。
代码实现
string test [id]5554323[id!][destid]10657302023180404[destid!][srcterminalid]13518841197[srcterminalid!][msgcontent]好的[msgcontent!][receivetime]20090409165217[receivetime!];
Regex reg new Regex((?s)\[([^\]])\]((?:(?!\[\1).)*)\[\1!\]);
MatchCollection mc reg.Matches(test);
foreach (Match m in mc)
{ richTextBox2.Text Tag: m.Groups[1].Value.PadRight(20, ) Content: m.Groups[2].Value \n;
}
/*--------输出--------
Tag: id Content: 5554323
Tag: destid Content: 10657302023180404
Tag: srcterminalid Content: 13518841197
Tag: msgcontent Content: 好的
Tag: receivetime Content: 20090409165217
*/
这种需求通常是由捕获组匹配到一个标签然后向后匹配直到与之配对的标签外为止根据源字符串的特点中间可以使用非贪婪模式也可以使用顺序否定环视贪婪模式。
3.3 反向引用的综合应用
3.3.1 12位数字其中不能出现6位连续相同数字
需求描述
只允许12位数字并且其中不能出现6位连续相同数字。
例如123456789012是允许的而123333334567是不允许的。
正则表达式^(?:([0-9])(?!\1{5})){12}$
类似这种需要判定是否有连续相同元素的需求其实也是验证重复也要用到反向引用。
说下分析过程需求分解一下
1、 一个数字
2、 它后面不能连续出现5个与它相同的数字
3、 满足以上两条的字符一共12个
那么根据需求分解写出相应的正则
1、([0-9])
2、(?!\1{5})
3、.{12}
将以上三个分解后得出的正则按需求逻辑关系组合一下
(([0-9])(?!\1{5})){12}
由于是验证整个字符串的规则所以开始和结束标识“^”和“$”是少不了的不需要用捕获组的地方用非捕获代替也就成了最后满足需求的正则
^(?:([0-9])(?!\1{5})){12}$
其实这个例子的分析过程也是一些正则问题解析的通用过程先把复杂的需求由整到零的分解再各个实现然后把实现的正则由零到整考虑一下相互间的逻辑关系基本上就可以得出正确的正则表达式了。
3.3.2 A-Z以内不重复的10个字母
需求描述A-Z以内不重复的10个字母
正则表达式1^(?:([A-Z])(?!.*?\1)){10}$
正则表达式2^(?:([A-Z])(?((?!\1).)*$)){10}$
这个需求与上一个需求类似分析过程也差不多。其实这个问题如果用正则来实现思路是非常清晰的 。
首先因为是验证规则所以“^”和“$”是必不可少的分别匹配开始和结束的位置 。
然后是10个字母那么([A-Z]){10}合起来就是^([A-Z]){10}$ 。
最后就是加一个规则字母不能重复 。
如何保证不能重复必然是用到反向引用 (一个字母)后面任意一个字母不能与这个字母重复这样实现起来就有两种方式当然实质都是一样的
实现方式一^(?:([A-Z])(?!.*?\1)){10}$
实现方式二^(?:([A-Z])(?(?:(?!\1).)*$)){10}$
在这个需求当中由于可能出现的源字符串不会太长也不会太复杂所以这两个正则表达式在匹配效率上不会有明显的差异。
解释一下正则的含义先解释一下方式一的正则
^(?:([A-Z])(?!.*?\1)){10}$
“^”和“$”分别匹配开始和结束位置“{10}”为量词表示修饰的子串重复10次。
“(?:Expression)”是非捕获组目的是不将“()”内的“Expression”匹配的内容保存到内存中之所以要这样用是因为后面的反向引用使用的是“\1”如果不用非捕获组那么“([A-Z])”就是编号为2的捕获组后面的“\1”就要换成“\2”来引用第二个捕获组替换后对匹配结果当然不会有什么影响但由于由“(([A-Z])(?!.*?\1))”捕获的内容我们并不关心所以还是用非捕获组可以提升匹配效率。
“([A-Z])”就是匹配A到Z之间的任意一个字母并保存匹配结果到捕获组中。
“(?!.*?\1)”顺序环视它是零宽度的虽然进行匹配但不保存匹配结果可以理解为它就是在所在位置的右侧附加了一个条件用在这里表示它所在位置的右侧不管间隔多少个字符都不能出现之前匹配到的那个字符也就是不能有重复的字母出现。
“(?:([A-Z])(?!.*?\1)){10}”就是匹配到这样一个字符
1、它首先是一个字母
2、然后这个字母的右侧间隔任意多个字符不能再出现同样的字母
3、最后符合以上两条规则的字符一共有10个。
加上首尾限定字符“^”和“$”就是满足需求的正则。
接下来讨论一下方式二的正则
^(?:([A-Z])(?(?:(?!\1).)*$)){10}$
思路和以及其余部分子表达式与方式一完全一样 只有“(?(?:(?!\1).)*$)”这里不同这个子表达式表示它所在位置右侧一直到结尾都不能是之前匹配到的那个字符。方式一是非贪婪模式的实现而这个就是贪婪模式的实现。
这里需要用到顺序肯定环视“(?Expression)”而不能用非捕获组“(?:(?:(?!\1).)*$)”是因为这里的表达式不能占有字符只能作为条件存在由量词“{10}”修饰的子表达式最终只能匹配一个字符否则就无法限定长度了。
3.3.3 提取指定单元长度字符串
需求描述 参考 求一正则表达式c#
例如有字符串 string str w1w2w3w2w3w1w3w2w4w5w4w5w4w4w5w4w2w4w3w4w3w2w6w5w6w5w6w4w7,找出有且仅有两个单元w数字作为一个单元例如w1,w2组成的长度大于等于4个单元的字串必须包括这两个单元这个例子应输出w2w3w2w3,w4w5w4w5w4w4w5w4,w4w3w4w3,w6w5w6w5w6 。
如果找出有且仅有三个单元长度大于等于6个单元的字串该如何写正则表达式
代码实现
//第一个需求两单元的
string str w7w7w7w5w7w1w2w3w2w3w1w3w2w4w5w4w5w4w4w5w4w2w4w3w4w3w2w6w5w6w5w6w4w7w7w7w5w7;
MatchCollection mc Regex.Matches(str, (?i)(?(w\d)\1*(w\d))(?:\1|\2){4,});
foreach (Match m in mc)
{ richTextBox2.Text m.Value \n;
}
/*--------输出--------
bb0w7w7w7w5w7
w2w3w2w3
w4w5w4w5w4w4w5w4
w4w3w4w3
w6w5w6w5w6
w4w7w7w7
*/
//第二个需求三单元的
string str w7w7w7w5w7w1w2w3w2w3w1w3w2w4w5w4w5w4w4w5w4w2w4w3w4w3w2w6w5w6w5w6w4w7w7w7w5w7;
MatchCollection mc Regex.Matches(str, (?i)(?(w\d)\1*(w\d)(?:\1|\2)*(w\d))(?:\1|\2|\3){6,});
foreach (Match m in mc)
{ richTextBox2.Text m.Value \n;
}
/*--------输出--------
bb0w7w7w7w5w7w1
w2w3w2w3w1w3w2
w4w5w4w5w4w4w5w4w2w4
w2w6w5w6w5w6
w4w7w7w7w5w7
*/
这个实例可以认为是环视和反向引用综合运用的一个经典实例。主要是用到了环视零宽度不占有字符的特性先由环视来取得规定单元的捕获组的内容再通过反向引用来进行实际的匹配。