1 概述
捕获组捕获到的内容,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式就是反向引用。要了解反向引用,首先要了解捕获组,关于捕获组,参考 正则基础之——捕获组(capture group)。
反向引用的作用通常是用来查找或限定重复、查找或限定指定标识配对出现等等。
对于普通捕获组和命名捕获组的引用,语法如下:
普通捕获组反向引用:\k<number>,通常简写为\number
命名捕获组反向引用:\k<name>或者\k'name'
普通捕获组反向引用中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个捕获组的,所以匹配结果为空。
<script type="text/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 <sp
分享到:
相关推荐
5. **反向引用**:在某些正则表达式引擎中,可以使用反向引用(`\g<number>`)来引用前面捕获组的匹配结果,例如`\g<1>`代表第一个捕获组的内容,这对于复杂模式的匹配非常有用。 6. **命名捕获组**:在支持高级...
正则基础之——NFA引擎匹配原理 在正则表达式中,了解引擎匹配原理是非常重要的。就像音乐家一样,一个人可以演奏出动听的乐曲,但是如果不知道如何去改变音符的组合,乐曲就不会变得更动听。同样,在使用正则...
在正则表达式中,捕获组(Capture Group)和反向引用是两个非常重要的概念,它们能帮助我们更加精确地控制匹配规则。 首先,我们来了解捕获组。捕获组是通过圆括号 "(...)" 来定义的,它允许我们将一个正则表达式的...
此外,正则表达式还支持分组和反向引用。通过"( )"将部分表达式括起来,我们可以创建一个捕获组,这个组内的匹配结果可以在后续表达式中引用。反向引用来实现复杂的匹配,例如,"(.)\1"会匹配任何连续重复的两个字符...
在JdkApi_regix.docx文档中,可能包含了更多关于Java正则表达式API的详细使用示例和深入解析,包括高级用法如反向引用、零宽度断言等。建议查阅文档以获取更全面的信息。 总结来说,Java正则表达式是强大的文本处理...
- **分组与反向引用**:使用圆括号`()`可以将部分正则表达式分组,分组后的表达式可以被引用,如`\1`表示引用第一个分组的内容。 - **选择符**:`|`用于表示或操作,如`a|b`匹配'a'或'b'。 2. **正则表达式工具的...
- `( )` 用于创建捕获组,捕获的子匹配可以使用反向引用(`\n`,n 为组号)来引用。 - `(?: )` 创建非捕获组,避免保存匹配项到缓冲区。 7. **反向引用**: - `\1`、`\2` 等表示对之前捕获的子匹配进行引用,...
8. 反向引用:对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中, 所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从1 开始, 最多可存储99 个...
同时,分组还可以利用反向引用,例如"\1"代表第一个捕获的子模式,这在复杂的匹配和替换场景中非常有用。 正则表达式还支持零宽断言,如"?"(lookahead)和"(?<!)"(lookbehind),它们可以检查某个位置后面的或前面的...
4. 功能丰富的控制台:提供捕获组、反向引用等高级功能的测试,有助于理解复杂正则表达式的工作原理。 5. 学习资源:通过实际操作,用户可以在实践中学习和掌握正则表达式语法,这对于初学者来说尤其有用。 6. ...
3. 分组和反向引用:通过圆括号()实现分组,并可以用\数字来引用前面的分组。 4. 多行模式与行边界:^和$分别匹配行的开始和结束,而m标志可以开启多行模式。 5. 不区分大小写匹配:i标志可以使匹配不考虑字符的大小...
此外,还有反向引用、预查、非捕获组等高级特性。了解并熟练掌握这些概念,能够帮助开发者写出更复杂的正则表达式,解决更复杂的问题。 总结来说,“正则表达式测试器”是一个实用的开发工具,尤其适合于对正则...
括号及反向引用... 20 神奇的转义... 22 基础知识拓展... 23 语言的差异... 23 正则表达式的目标... 23 更多的例子... 23 正则表达式术语汇总... 27 改进现状... 30 总结... 32 一家之言... 33 第2章:...
- **反向引用**:在正则表达式中使用捕获组的结果。 - **前瞻断言**:用于检查当前位置之后的文本是否满足某个条件。 - **后瞻断言**:用于检查当前位置之前的文本是否满足某个条件。 - **贪婪与懒惰匹配**:控制...
正则导向的引擎支持更高级的功能,如“惰性”量词和反向引用等。 正则导向的引擎总是返回最左边的匹配,也就是说,当存在多个匹配项时,它会选择最左侧的那个匹配项。例如,将正则表达式`regex|regexnot`应用于字符...
2. **正则导向(regex-directed)引擎**:这类引擎功能更加强大,支持更多的高级特性,如懒惰量词(lazy quantifiers)和反向引用(backreferences)。 在实际应用中,可以通过一些简单的测试来判断当前使用的引擎...
4. **反向引用**:在分组后,可以通过\数字的形式引用前面捕获的分组内容。 5. **预查**:(?:...)结构可以用来预查,确保某个模式不会被匹配,但会消耗字符。 6. **回溯**:理解正则引擎如何进行回溯,这对于优化...
- **分组与反向引用**:使用括号()可以创建分组,反向引用\数字可以引用之前分组的内容。 - **断言**:^表示行首,$表示行尾,\b表示单词边界。 - **选择符**:|表示或关系,如abc|def表示匹配abc或def。 - **转义...