正则表达式表示字符串的格式。正则表达式r完全由它所匹配的串集来定义。这个集合称
为由正则表达式生成的语言( language generated by the regular expression),写作L(r)。此处
的语言只表示“串的集合”,它与程序设计语言并无特殊关系(至少在此处是这样的)。该语言
首先依赖于适用的字符集,它一般是A S C I I字符的集合或它的某个子集。有时该集比A S C I I字
符的集合更普通一些,此处集合的元素称作符号( s y m b o l)。这个正规符号的集合称作字母表
(a l p h a b e t)并且常写作希腊符号å(s i g m a)。
正则表达式r还包括字母表中的字符,但这些字符具有不同的含义:在正则表达式中,所
有的符号指的都是模式。在本章中,所有模式都是用黑体写出以与作为模式的字符区分开来。
因此,a 就是一个作为模式的字符a。
最后,正则表达式r还可包括有特殊含义的字符。这样的字符称作元字符( m e t a c h a r a c t e r)
或元符号(m e t a s y m b o l)。它们并不是字母表中的正规字符,否则当其作为元字符时就与作为
字母表中的字符时很难区分了。但是通常不可能要求这种排斥,而且也必须使用一个惯例来区
分元字符的这两种用途。在很多情况下,它是通过使用可“关掉”元字符的特殊意义的转义字
符(escape character)做到的。源代码层一般是反斜杠和引号。如果转义字符是字母表中的正
规字符,则请注意它们本身也就是元字符了。
正则表达式的定义
现在通过讲解每个模式所生成的不同语言来描述正则表达式的含义。首先讲一下基本正则
表达式的集合(它是由单个符号组成),之后再描述从已有的正则表达式生成一个新的正则表达
式的运算。它同构造算术表达式的方法类似:基本的算术表达式是由数字组成,如4 3和2 . 5。算
术运算的加法和乘法等可用来从已有的表达式中产生新的表达式,如在43 * 2.5和43 * 2.5 + 1.4
中。
从它们只包括了最基本的运算和元符号这一方面而言,这里所讲到的一组正则表达式都是
最小的,以后还会讲得更深一些。
1) 基本正则表达式它们是字母表中的单个字符且自身匹配。假设a是字母表å中的任一字
符,则指定正则表达式a 通过书写L (a) = {a}来匹配a字符。而特殊情况还要用到另外两个字符。
有时需要指出空串( empty string)的匹配,空串就是不包含任何字符的串。空串用( e p s i l o n )
来表示,元符号(黑体)是通过设定L( ) = { }来定义的。偶尔还需要写出一个与任何串都
不匹配的符号,它的语言为空集(empty set),写作{ }。我们用符号来表示,并写作L( ) = {}。
请注意{ }和{ }的区别:{ }集不包括任何串,而{ }则包含一个没有任何字符的串。
2) 正则表达式运算在正则表达式中有3种基本运算:① 从各选择对象中选择,用元字符|
(竖线)表示。②连结,由并置表示(不用元字符)。③重复或“闭包”,由元字符*表示。后面
通过为匹配了的串的语言提供相应的集合结构依次讨论以上3种基本运算。
3) 从各选择对象中选择如果r 和s 是正则表达式,那么正则表达式r | s 可匹配被r 或s 匹
配的任意串。从语言方面来看,r | s 语言是r 语言和s 语言的联合(u n i o n),或L (r | s) = L (r)
ÈL (s)。以下是一个简单例子:正则表达式a | b匹配了a 或b 中的任一字符,即L (a | b) = L (a)
ÈL (b) = {a}È{b} = {a, b}。又例如表达式a | 匹配单个字符a 或空串(不包括任何字符),也
就是L (a | ) = {a, }。
还可在多个选择对象中选择,因此L (a | b | c | d) = { a, b, c, d} 也成立。有时还用点号表示
选择的一个长序列,如a | b |...| z,它表示匹配a~z 的任何小写字母。
4) 连结正则表达式r 和正则表达式s 的连结可写作r s,它匹配两串连结的任何一个串,其
中第1个匹配r,第2个匹配s。例如:正则表达式a b只匹配a b,而正则表达式( a | b ) c 则匹配
串ac 和b c(下面将简要介绍括号在这个正则表达式中作为元字符的作用)。
可通过由定义串的两个集合的连结所生成的语言来讲解连结的功能。假设有串S1 和S2 的两
个集合,串S1S2 的连结集合是将串S2 完全附加到串S1 上的集合。例如若S1 = {aa, b}, S2 = {a, bb},
则S1S2 = { aaa, aabb, ba, bbb}。现在可将正则表达式的连结运算定义如下:L (r s) = L (r) L (s),
因此(利用前面的示例),L (( a | b ) c) = L ( a | b ) L (c) = {a, b} {c} = {ac, bc}。
连结还可扩展到两个以上的正则表达式:L (r1r2 ... rn ) = L (r1 ) L (r2 ) ... L (rn ) = 由将每一个
L (r1 ), ..., L (rn ) 串连结而成的串集合。
5) 重复正则表达式的重复有时称为K l e e n e闭包((Kleene) closure),写作r *,其中r 是一
个正则表达式。正则表达式r * 匹配串的任意有穷连结,每个连结均匹配r。例如a *匹配串、a、
a a、a aa . . .(它与匹配是因为是与a匹配的非串连结)。通过为串集合定义一个相似运算*,
就可用生成的语言定义重复运算了。假设有一个串的集合S,则
S* = { } È S È SS È SSS È. . .
这是一个无穷集的联合,但是其中的每一个元素都是S中串的有穷连结。有时集合S*可写作:
其中Sn = S . . . S,它是S 的n 次连结(S0 = { })。
现在可如下定义正则表达式的重复运算:
L (r*) = L (r) *
例:在正则表达式(a | b b) * (括号作为元字符的原因将在后面解释)中,该正则表达式与以
下串任意匹配: 、a、b b、a a、a b b、b b a、b b b b、a a a、aabb 等等。在语言方面,L (( a | b b ) *)
= L (a | b b)* = {a, bb}* = { , a, b b, a a, a b b, b b a, b b b b, a a a, a a b b, a b b a, a b b b b, b b a a, . . . }。
6) 运算的优先和括号的使用前面的内容忽略了选择、连结和重复运算的优先问题。例如
对于正则表达式a | b *,是将它解释为( a | b )* 还是a | ( b* ) 呢(这里有一个很大的差别,因
为L (( a | b )*) = { , a, b, aa, ab, ba, bb, . . . },但L ( a | ( b* )) = { , a, b, bb, bbb, . . . } )?标准
惯例是重复的优先权较高,所以第2个解释是正确的。实际上,在这3个运算中, *优先权最
高,连结其次,| 最末。因此,a | b c * 就可解释为a | ( b ( c* )),而a b | c * d 却解释为( a b )
| (( c* ) d )。
当需指出与上述不同的优先顺序时,就必须使用括号。这就是为什么用( a | b ) c 能表示
选择比连结有更高的优先权的原因。而a | b c 则被解释为与a 或bc 匹配。类似地,没有括号的
( a | b b )* 应解释为a | b b*,它匹配a、b、b b、b b b. . . 。此处括号的用法与其在算术中类似:
(3 + 4) * 5 =35,而3 + 4 * 5 = 23,这是因为* 的优先权比+ 的高。
7) 正则表达式的名字为较长的正则表达式提供一个简化了的名字很有用处,这样就不再
需要在每次使用正则表达式时书写长长的表达式本身了。例如:如要为一个或多个数字序列写
一个正则表达式,则可写作:
( 0 | 1 | 2 | . . . | 9 ) ( 0 | 1 | 2 | . . . | 9 ) *
或写作
digit digit*
其中
digit = 0|1|2|...|9
就是名字d i g i t的正则定义(regular definition)了。
正则定义的使用带来了巨大的方便,但是它却增加了复杂性,它的名字本身也变成一个元
符号,而且必须要找到一个方法能将这个名字与它的字符连结相区分开。在我们的例子中是用
斜体来表示它的名字。请注意,在名字的定义中不能使用名字(也就是递归地)——必须能够
用它代表的正则表达式替换它们。
在考虑用一些例子来详细描述正则表达式的定义之前,先将有关正则表达式定义的所有信
息收集在一起。
定义 正则表达式(regular expression)是以下的一种:
1. 基本(b a s i c)正则表达式由一个单字符a(其中a 在正规字符的字母表å中),以及
元字符或元字符组成。在第1种情况下,L (a) = {a};在第2种情况下,L ( ) =
{ };在第3种情况下,L ( ) = {}。
2. r | s 格式的表达式:其中r 和s 均是正则表达式。在这种情况下,L (r | s) = L (r) È L (s)。
3. rs 格式的表达式:其中r 和s 是正则表达式。在这种情况下,L (r s) = L (r) L (s)。
4. r* 格式的表达式:其中r 是正则表达式。在这种情况下,L (r*) = L (r) *。
5. (r)格式的表达式:其中r 是正则表达式。在这种情况下,L ( (r)) = L (r),因此,括
号并不改变语言,它们只调整运算的优先权。
我们注意到在上面这个定义中,(2)、(3)和(4)的优先权与它们所列的顺序相反,也就
是:|的优先权最低,连结次之,* 则最高。另外还注意到在这个定义中, 6个符号—— 、、
|、*、( 和) 都有了元字符的含义。