正则表达式的使用

本文内容主要来自网络上其他的人的总结,自己略微综合


标准的C和C++都不支持正则表达式,但有一些函数库可以辅助C/C++程序员完成这一功能,其中最著名的当数Philip Hazel的Perl-Compatible Regular Expression库,许多Linux发行版本都带有这个函数库。 

编译正则表达式 

为了提高效率,在将一个字符串与正则表达式进行比较之前,首先要用regcomp()函数对它进行编译,将其转化为regex_t结构: 

int regcomp(regex_t *preg, const char *regex, int cflags);

参数regex是一个字符串,它代表将要被编译的正则表达式;参数preg指向一个声明为regex_t的数据结构,用来保存编译结果;参数cflags决定了正则表达式该如何被处理的细节。 

如果函数regcomp()执行成功,并且编译结果被正确填充到preg中后,函数将返回0,任何其它的返回结果都代表有某种错误产生。 


匹配正则表达式 

一旦用regcomp()函数成功地编译了正则表达式,接下来就可以调用regexec()函数完成模式匹配: 

int regexec(const  regex_t  *preg,  const  char *string,  size_t nmatch, regmatch_t pmatch[], int eflags);
typedef struct {

  regoff_t rm_so;
  regoff_t rm_eo;
} regmatch_t;

参数preg指向编译后的正则表达式,参数string是将要进行匹配的字符串,而参数nmatch和pmatch则用于把匹配结果返回给调用程序,最后一个参数eflags决定了匹配的细节。 

在调用函数regexec()进行模式匹配的过程中,可能在字符串string中会有多处与给定的正则表达式相匹配,参数pmatch就是用来保存这些匹配位置的,而参数nmatch则告诉函数regexec()最多可以把多少个匹配结果填充到pmatch数组中。当regexec()函数成功返回时,从string+pmatch[0].rm_so到string+pmatch[0].rm_eo是第一个匹配的字符串,而从string+pmatch[1].rm_so到string+pmatch[1].rm_eo,则是第二个匹配的字符串,依此类推。


释放正则表达式 


无论什么时候,当不再需要已经编译过的正则表达式时,都应该调用函数regfree()将其释放,以免产生内存泄漏。 

void regfree(regex_t *preg);

函数regfree()不会返回任何结果,它仅接收一个指向regex_t数据类型的指针,这是之前调用regcomp()函数所得到的编译结果。 

如果在程序中针对同一个regex_t结构调用了多次regcomp()函数,POSIX标准并没有规定是否每次都必须调用regfree()函数进行释放,但建议每次调用regcomp()函数对正则表达式进行编译后都调用一次regfree()函数,以尽早释放占用的存储空间。


报告错误信息 

如果调用函数regcomp()或regexec()得到的是一个非0的返回值,则表明在对正则表达式的处理过程中出现了某种错误,此时可以通过调用函数regerror()得到详细的错误信息。 

size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size);

参数errcode是来自函数regcomp()或regexec()的错误代码,而参数preg则是由函数regcomp()得到的编译结果,其目的是把格式化消息所必须的上下文提供给regerror()函数。在执行函数regerror()时,将按照参数errbuf_size指明的最大字节数,在errbuf缓冲区中填入格式化后的错误信息,同时返回错误信息的长度。 


应用正则表达式 

下面是一个简单的regex的例子,看一下基本就明白了。

#include 
#include 
#include 

#define SUBSLEN 10              /* 匹配子串的数量 */
#define EBUFLEN 128             /* 错误消息buffer长度 */
#define BUFLEN 1024             /* 匹配到的字符串buffer长度 */

int main()
{
        size_t          len;
        regex_t         re;                     /* 存储编译好的正则表达式,正则表达式在使用之前要经过编译 */
        regmatch_t      subs [SUBSLEN];         /* 存储匹配到的字符串位置 */
        char            matched   [BUFLEN];     /* 存储匹配到的字符串 */
        char            errbuf    [EBUFLEN];    /* 存储错误消息 */
        int             err, i;

        char            src       [] = "111 Hello World 222";        /* 源字符串 */
        char            pattern   [] = "(.*)";        /* pattern字符串 */

        printf("String : %s\n", src);
        printf("Pattern: \"%s\"\n", pattern);

        /* 编译正则表达式 */
        err = regcomp(&re, pattern, REG_EXTENDED);

        if (err) {
                len = regerror(err, &re, errbuf, sizeof(errbuf));
                printf("error: regcomp: %s\n", errbuf);
                return 1;
        }
        printf("Total has subexpression: %d\n", re.re_nsub);
        /* 执行模式匹配 */
        err = regexec(&re, src, (size_t) SUBSLEN, subs, 0);

        if (err == REG_NOMATCH) { /* 没有匹配成功 */
                printf("Sorry, no match ...\n");
                regfree(&re);
                return 0;
        } else if (err) {  /* 其它错误 */
                len = regerror(err, &re, errbuf, sizeof(errbuf));
                printf("error: regexec: %s\n", errbuf);
                return 1;
        }

        /* 如果不是REG_NOMATCH并且没有其它错误,则模式匹配上 */
        printf("\nOK, has matched ...\n\n");
        for (i = 0; i <= re.re_nsub; i++) {
                len = subs[i].rm_eo - subs[i].rm_so;
                if (i == 0) {
                        printf ("begin: %d, len = %d  ", subs[i].rm_so, len); /* 注释1 */
                } else {
                        printf("subexpression %d begin: %d, len = %d  ", i, subs[i].rm_so, len); 
                }
                memcpy (matched, src + subs[i].rm_so, len);
                matched[len] = '\0';
                printf("match: %s\n", matched);
        }

        regfree(&re);   /* 用完了别忘了释放 */
        return (0);
}
执行结果是:

String : 111 Hello World 222
Pattern: "(.*)"
Total has subexpression: 1

OK, has matched ...

begin: %, len = 4  match: Hello World
subexpression 1 begin: 11, len = 11  match: Hello World


示例程序可以看出,使用之前先用regcomp()编译一下,然后调用regexec()进行实际匹配。如果只是看有没有匹配成功,掌握这2个函数的用法即可。有时候我们想要取得匹配后的子表达式,比如示例中想获得title是什么,需要用小括号 "( )"把子表达式括起来"(.*)",表达式引擎会将小括号 "( )" 包含的表达式所匹配到的字符串记录下来。在获取匹配结果的时候,小括号包含的表达式所匹配到

的字符串可以单独获取,示例程序就是我用来获取http网页的主题(title)的方式。  

如果src[]="111 Hello World 222 Hi";pattern[]="(.*)";则匹配的字符串是Hello World 222 Hi

如果逐个匹配src[]="111 Hello World 222 Hi";则pattern[]="((.*))[ ]+([0-9])[ ]+(()(.*))";最后不要加[ ],因后面什么也没有,[ ]是匹配空格的。

可以查看一下src[] = "-rwxrw-rw- 1 oracle oinstall 13176 9月 7 18:45:47 2012 client.c";pattern[] = "([-rwx]+)[ ]+([0-9]+)[ ]+([A-Za-z]+)[ ]+([A-Za-z]+)[ ]+([0-9]+)[ ]+([A-Za-z]+|([0-9]+)月)[ ]+([0-9]+)[ ]+([0-9]{2}):([0-9]{2}):([0-9]{2})[ ]+([0-9]+)[ ]+(.+)";和src[] = "-rwxrw-rw- 1 oracle oinstall 13176 Sep 7 18:45:47 2012 client.c";pattern[] = "([-rwx]+)[ ]+([0-9]+)[ ]+([A-Za-z]+)[ ]+([A-Za-z]+)[ ]+([0-9]+)[ ]+([A-Za-z]+|([0-9]+)月)[ ]+([0-9]+)[ ]+([0-9]{2}):([0-9]{2}):([0-9]{2})[ ]+([0-9]+)[ ]+(.+)";的区别。


regmatch_t subs[SUBSLEN]是用来存放匹配位置的,subs[0]里存放这个匹配的字符串位置,subs[1]里存放第一个子表达式的匹配位置,也就是例子中的title,通过结构里的rm_so和rm_eo可以取到,这一点很多人不太注意,应该强调一下。

注释1:开始调试代码的时候是在FreeBSD 6.2上进行的,print出来的len总是0,但print出来的字符串又没错,很是迷惑,把它放到Linux上则完全正常,后来仔细检查才发现rm_so在Linux上是32位,在FreeBSD上是64位,用%d的话实际取的是rm_so的高32位,而不是实际的len,把print rm_so的地方改为%llu就可以了。

regex虽然简单易用,但对正则表达式的支持不够强大,中文处理也有问题,于是引出了下面要说的PCRE。

2、PCRE  (http://www.pcre.org
PCRE的名字就说明了是Perl Compatible,熟悉Perl、PHP的人使用起来完全没有问题。PCRE有非常丰富的使用说明和示例代码(看看
pcredemo.c就能明白基本的用法),下面的程序只是把上面regex改为pcre。

/* Compile thuswise:    
 *   gcc -Wall pcre1.c -I/usr/local/include -L/usr/local/lib -R/usr/local/lib -lpcre
 *      
 */     

#include 
#include 
#include  
                
#define OVECCOUNT 30    /* should be a multiple of 3 */
#define EBUFLEN 128            
#define BUFLEN 1024           
        
int main() 
{               
        pcre            *re; 
        const char      *error;
        int             erroffset;
        int             ovector[OVECCOUNT];
        int             rc, i;
        
        char            src    [] = "111 Hello World 222";
        char            pattern   [] = "(.*)";
                
        printf("String : %s\n", src);
        printf("Pattern: \"%s\"\n", pattern);
        

        re = pcre_compile(pattern, 0, &error, &erroffset, NULL);
        if (re == NULL) {
                printf("PCRE compilation failed at offset %d: %s\n", erroffset, error);
                return 1;
        }

        rc = pcre_exec(re, NULL, src, strlen(src), 0, 0, ovector, OVECCOUNT);
        if (rc < 0) {
                if (rc == PCRE_ERROR_NOMATCH) printf("Sorry, no match ...\n");
                else    printf("Matching error %d\n", rc);
                free(re);
                return 1;
        }

        printf("\nOK, has matched ...\n\n");

        for (i = 0; i < rc; i++) {
                char *substring_start = src + ovector[2*i];
                int substring_length = ovector[2*i+1] - ovector[2*i];
                printf("%2d: %.*s\n", i, substring_length, substring_start);
        }

        free(re);
        return 0;
}
执行结果:

String : 111 Hello World 222
Pattern: "(.*)"

OK, has matched ...

 0: Hello World
 1: Hello World 

比较这2个例子可以看出,在regex用的是regcomp()、regexec(),pcre则使用pcre_compile()、pcre_exec(),用法几乎完全一致。
pcre_compile()有很多选项,详细说明参见 http://www.pcre.org/pcre.txt 。如果是多行文本,可以设置PCRE_DOTALL的选项pcre_complie(re,  PCRE_DOTALL,....),表示'.'也匹配回车换行"\r\n"。

3、pcre++
pcre++( http://www.daemon.de/PCRE )对pcre做了c++封装,使用起来更加方便。

/*
 * g++ pcre2.cpp -I/usr/local/include -L/usr/local/lib -R/usr/local/lib -lpcre++ -lpcre
 */
#include 
#include 
#include 

using namespace std;
using namespace pcrepp;

int main()
{
        string src("111 Hello World 222");
        string pattern("(.*)");

        cout << "String : " << src << endl;
        cout << "Pattern : " << pattern << endl;

        Pcre reg(pattern, PCRE_DOTALL);
        if (reg.search(src) == true) { //
                cout << "\nOK, has matched ...\n\n";
                for(int pos = 0; pos < reg.matches(); pos++) {
                        cout << pos << ": " << reg[pos] << endl;
                }
        } else {
                cout << "Sorry, no match ...\n";
                return 1;
        }

        return 0;
}
执行结果:

String : 111 Hello World 222
Pattern : (.*)

OK, has matched ...

0: Hello World

4、oniguruma
还有一个正则表达式的库oniguruma( http://www.geocities.jp/kosako3/oniguruma/ ),对于东亚文字支持比较好,开始是用在ruby上,也可用于C++,是日本的开发人员编写的。大多数人都不会用到,也就不做介绍了。如果有疑问可以通过email来讨论它的用法。

5、Regular Expression的内部实现
关于Regular Expression的实现,用到了不少自动机理论(Automata Theory)的知识,有兴趣的可以找这方面的资料来看,这本书“ 
Introduction to Automata Theory, Languages, and Computation”写的很好,编译原理的书也有这方面的内容。


你可能感兴趣的:(C++基础)