Math、Group 和 Capture
2007年07月26日 原创
.NET 平台中的正则表达式包含在 System.Text.RegularExpressions 命名空间中。该命名空间中的类主要有:
- Regex
- MatchCollection
- Match
- GroupCollection
- Group
- CaptureCollection
- Capture
其中,Regex 类是核心。
Regex.Match() 方法返回一个包含(或不包含)匹配项信息的 Match 对象;
Regex.Matches() 方法则返回包含所有匹配项的 MatchCollection (Match 对象的集合)对象;
Match.Groups 属性中包含着 GroupCollection (Group 对象的集合);
Group.Captures 属性中包含着 CaptureCollection(Capture 对象的集合)。
而 MatchCollection、GroupCollection 和 CaptureCollection 中包含的 Match、Group 和 Capture 对象可以通过 For Each 语句来枚举。
下面的示例代码涉及到了以上所有的类、方法和属性的使用:
Imports System.Text.RegularExpressions
Module Module1
Sub Main()
Dim myRegex = New Regex("([A-Z])+(d)+")
Console.WriteLine("Enter a string on the following line:")
Dim inputString = Console.ReadLine()
Dim myMatchCollection = myRegex.Matches(inputString)
Console.WriteLine()
Console.WriteLine("There are {0} matches.", myMatchCollection.Count)
Console.WriteLine()
Dim myMatch As Match
Dim myGroupCollection As GroupCollection
Dim myGroup As Group
For Each myMatch In myMatchCollection
Console.WriteLine("At position {0}, the match '{1}' was found",
myMatch.Index, myMatch.ToString)
Console.WriteLine("This match has {0} groups.",
myMatch.Groups.Count)
myGroupCollection = myMatch.Groups
For Each myGroup In myGroupCollection
Dim myCaptureCollection As CaptureCollection = myGroup.Captures
Dim myCapture As Capture
Console.WriteLine("Group containing '{0}' found at position
'{1}'.", myGroup.Value, myGroup.Index)
For Each myCapture In myCaptureCollection
Console.WriteLine("Capture: '{0}' at position '{1}'.",
myCapture.Value, myCapture.Index)
Next
Next
Console.WriteLine()
Next
Console.WriteLine()
Console.WriteLine("Press Return to close this application.")
Console.ReadLine()
End Sub
End Module
在命令行中执行以上代码,并根据提示输入测试字符串:ABC1 A123(注意中间的空格),按回车后观察如下图所示的执行结果:

更直观的示意图如下所示:
能够反映动态匹配、组、和捕获过程的示意图如下:
原理剖析:
本例中使用的正则表达式模式是:([A-Z])+(\d)+。这是一个精心设计的模式,为什么不把 + 限定放在分组的圆括号中呢?目的就是为了验证对多个匹配的字符创建多个分组和多个捕获的效果。如果把模式修改为 ([A-Z]+)(\d+) 那结果就完全不一样了--对我们理解组和捕获之间的关系就没有帮助了(若不理解,请继续看下面的分析)。
.NET 中对正则表达式匹配项进行了细分,即分为匹配(由 Match 对象表示)、组(由 Group 对象表示)和捕获(由 Capture 对象来表示)三个层次。这三个层次是“父子”关系,或者说具有包含关系,即一个匹配中包含所有组,一个组中包含所有捕获。当然,捕获是最小单位,可以最小化到捕获一个字符--比如本例模式中的 ([A-Z]) 这个组每次就能捕获到一个大写字母(如果有)。这种关系通过上面第二幅插图能够看得非常清楚。
在匹配项是 ABC1 时,匹配(Match)中包含着 ABC1 这个字符序列,而与该匹配对应的第0组(Group[0])中包含同样的字符序列,与第0组对应的第0个捕获(Capture[0])中也是如此。而与第一个分组对应的第1组(Group[1])会根据圆括号中的模式 [A-Z] 匹配三次--分别是 A、B 和 C。与此同时,将捕获到的字符分别保存在 Capture[0]、Capture[1] 和 Capturep[2] 中。注意,虽然捕获到了三个字符,但在第1组中每次都会用新捕获的字符替换上一次捕获的字符(即将上一次保存在 Group[1] 中的值丢弃。如上面第三幅插图所示),最终当匹配到数字 1 之前的位置时,Group[1] 中保存的是大写 C。这就是根据多个匹配项,创建多个分组和多个捕获的过程。
由此可见,在依据同一分组中的模式进行多次匹配时,只有通过 Capture 才能保存每次匹配的结果。这正是 .NET 中对文本操作精细化的一个解决方案。虽然通过多创建分组也能够使组中保存更少的字符(甚至单个字符),但毕竟不如增加一个新的 Capture 概念来得更直接(容易理解)、也更方便(例如可以通过编程遍历)。


为之漫笔(李松峰),本博客专注于Web前后端技术、移动平台开发技术、交互设计和技术翻译。 