正则表达式

本文摘要翻译了几篇文章的内容,简单介绍 ATL CAtlRegExp,GRETA,Boost::regex 等正则表达式库,这些表达式库使我们可以方便地利用正则库的巨大威力,给我们的工作提供了便利。

正则表达式语法

字符元意义
.匹配单个字符
[ ]指定一个字符类,匹配方括号内的任意字符。例:[abc] 匹配 "a", "b"或 "c"。
^如果^出现在字符类的开始处,它否定了字符类,这个被否定的字符类匹配除却方括号内的字符的字符。如:[^abc]匹配除了"a", "b"和"c"之外的字符。如果^出现在正则表达式前边,它匹配输入的开头,例:^[abc]匹配以"a", "b"或"c"开头的输入。
-在字符类中,指定一个字符的范围。例如:[0-9]匹配"0"到"9"的数字。
?指明?前的表达式是可选的,它可以匹配一次或不进行匹配。例如: [0-9][0-9]? 匹配"2"或"12"。
+指明?前的表达式匹配一次或多次。例如:[0-9]+匹配"1", "13", "666"等。

*

指明*前的表达式匹配零次或多次。
??, +?, *??, +和*的非贪婪匹配版本,它们尽可能匹配较少的字符;而?, +和*则是贪婪版本,尽可能匹配较多的字符。例如:输入"<abc><def>", 则<.*?> 匹配"<abc>",而<.*>匹配"<abc><def>"。
( )分组操作符。例如:(/d+,)*/d+匹配一串由逗号分开的数字,例如: "1"或"1,23,456"。
/转义字符,转义紧跟的字符。例如,[0-9]+ 匹配一个或多个数字,而 [0-9]/+ 匹配一个数字后跟随一个加号的情况。反斜杠/也用于表示缩写,/a 就表示任何数字、字母。如果/后紧跟一个数字n,则它匹配第n个匹配群组(从0开始),例如,<{.*?}>.*?<//0>匹配"<head>Contents</head>"。注意,在C++字符串中,反斜杠/需要用双反斜杠//来表示: "//+", "//a", "<{.*?}>.*?<///0>"。
$放在正则表达式的最后,它匹配输入的末端。例如:[0-9]$匹配输入的最后一个数字。
|间隔符,分隔两个表达式,以正确匹配其中一个,例如:T|the匹配"The" 或"the"。

 


缩写匹配

缩写匹配
/a字母、数字([a-zA-Z0-9])
/b空格(blank): ([ //t])
/c字母([a-zA-Z])
/d十进制数 ([0-9])
/h十六进制数([0-9a-fA-F])
/n换行: (/r|(/r?/n))
/q引用字符串(/"[^/"]*/")|(/''''[^/'''']*/'''')
/w一段文字 ([a-zA-Z]+)
/z一个整数([0-9]+)

(1)boost::regex

 

VC利用boost库解析正则表达式
最近做数据库涉及到解析sql语句,觉得最好的办法是写正则表达式解析,由于vc6没有解析函数,自己写又不甘心,后来从网上找到了boost库,解决了这个问题.
boost下载地址:http://www.boost.org

boost库安装比较麻烦,需要自己编译源文件,我整理了一下,如果仅仅需要做正则表达式,按下面的代码敲就行了.
cmd
vcvars32.bat
cd D:/boost_1_32_0/libs/regex/build
d:
nmake -fvc6.mak
nmake -fvc6.mak install

注意,别看下载下来的数据包没有多大,解压缩之后达到了100多M,编译完之后为109M,占用131M,所以安装时一定注意空出足够的空间,敲入nmake -fvc6.mak后等待的时间比较长,屏幕上还会出现一大堆英语,可以不做考虑.按照步骤往下敲就行了.压缩包内文档很详细,参照文档继续就可以了.

在VC6中集成:Tools->Options->Directories->Include files
加入:D:/boost_1_32_0

编写一个源程序测试一下:

#include "stdafx.h"
#include <cstdlib>
#include <stdlib.h>
#include <boost/regex.hpp>
#include <string>
#include <iostream>

using namespace std;
using namespace boost;

regex expression("^select ([a-zA-Z]*) from ([a-zA-Z]*)");

int main(int argc, char* argv[])
{


        std::string in;
        cmatch what;
        cout << "enter test string" << endl;
        getline(cin,in);
        if(regex_match(in.c_str(), what, expression))
        {
                for(int i=0;i<what.size();i++)
                        cout<<"str :"<<what[i].str()<<endl;
        }
        else
        {
                cout<<"Error Input"<<endl;
        }
   return 0;
}

输入: select name from table
输出: str:select name from table
         str:name
         str:table

 

  Boost提供了boost::basic_regex来支持正则表达式。boost::basic_regex的设计非常类似std::basic_string:

namespace boost{
template <class charT, 
 class traits = regex_traits<charT>, 
 class Allocator = std::allocator<charT> > class basic_regex;
typedef basic_regex<char> regex;
typedef basic_regex<wchar_t> wregex;
}      

  Boost Regex 库附带的文档非常丰富,示例更是精彩,比如有两个例子程序,不多的代码,程序就可以直接对 C++ 文件进行语法高亮标记,生成相应的 HTML (converts a C++ file to syntax highlighted HTML)。下面的例子可以分割一个字符串到一串标记符号(split a string into tokens)。

#include <list>
#include <boost/regex.hpp>
unsigned tokenise(std::list<std::string>& l, std::string& s)
{
    return boost::regex_split(std::back_inserter(l), s);
}

#include <iostream>
using namespace std;
#if defined(BOOST_MSVC) || (defined(__BORLANDC__) && (__BORLANDC__ == 0x550))
// problem with std::getline under MSVC6sp3
istream& getline(istream& is, std::string& s)
{
    s.erase();
    char c = is.get();
    while(c != ''''/n'''')
    {
       s.append(1, c);
       c = is.get();
    }
    return is;
}
#endif
int main(int argc)
{
    string s;
    list<string> l;
    do{
       if(argc == 1)
       {
          cout << "Enter text to split (or /"quit/" to exit): ";
          getline(cin, s);
          if(s == "quit") break;
       }
       else
          s = "This is a string of tokens";
       unsigned result = tokenise(l, s);
       cout << result << " tokens found" << endl;
       cout << "The remaining text is: /"" << s << "/"" << endl;
       while(l.size())
       {
          s = *(l.begin());
          l.pop_front();
          cout << s << endl;
       }
    }while(argc == 1);
    return 0;
}

 

这个已经被c++标准库采用了。

(2)Regex++

这里有个单独的Regex++包,是从boost库中抽出来的。
这个包,下载下来后,你可以使用dll的方式在工程中使用,也可以把它用静态库编译到你的项目中,也可以直接作为你项目的一个子工程直接使用。
方便实用,而且这个库可以自动识别不同版本的STL库,并且兼容。

http://ourworld.compuserve.com/homepages/John_Maddock/regexpp.htm

 

隔段时间没沾VC,再回过头来用Regex++时,竟然不知道怎么安装了,虽然以前安装使用的次数多了,但还是没记住,看来还是好记性不如烂笔头啊,再总结一次吧。环境: XP+VC6.0(SP6)+MS SDK ,且记环境变量中Include的值中MS SDK的位置要先于VC6.0的位置。如:C:/MicrosoftSDK/Include/.;C:/MVS/VC98/atl/include;C:/MVS/VC98/mfc/include;C:/MVS/VC98/include;

注:我机器上VC安装在了C:/MVS/VC98目录,你的可能是C:/Program Files/Microsoft Visual Studio/VC98
MS SDK安装在了C:/MicrosoftSDK目录

下载地址:http://ourworld.compuserve.com/homepages/John_Maddock/regexpp3.zip

然后解压缩到: C:/Regex++

然后进入到dos窗口

cd C:/Regex++/libs/regex/build

set MSVCDIR="C:/MVS/VC98"

nmake -fvc6.mak
nmake -fvc6.mak install
nmake -fvc6.mak clean

Note: To add the Regex++ library to your project select Project | Settings.... In the ensuing dialog, select the C/C++ tab. In the Category drop down list, select Preprocessor. In the Additional include directories: edit box enter C:/Regex++. Now select the Link tab. In the Category drop down list, select Input. In the Additional library path: edit box enter C:/Regex++.

 

 

Regex++ 簡介
(歡迎轉載,但請註明出處)

作者:八方齋
出處:http://akk.fengsoft.com/regex++.htm

E-mail:make.sense@msa.hinet.net

現職:某遊戲公司程式設計師。

P.S 感謝智峰軟體友情贊助網頁空間。

緣起:
C++是一種強大無比的語言,但是有一樣功能卻是從 C 語言時代就一直被人詬病,那就是「字串」。當然,有些朋友認為 sprintf, sscanf 等就已經夠用了,甚至到了 C++ 時代,這些函數仍是程式員的最愛(包括在下),但是不可諱言的,這些函數的確有些跟不上時代(這些東西起碼有2X年的歷史了)。



首先,這些函數都缺乏 type-safe 的特性,因為這些函數的操作幾乎都依靠格式化字串,僅有的除錯資訊就是這些函數 run-time 時的回傳值,但是這些函數的回傳值只有告訴你成功轉換了幾個引數,卻沒有告訴你轉換失敗的是哪幾個,甚至應該轉換失敗的也轉換成功(如 float 不小心轉成 int ),這些也許都還可以歸咎程式員自己寫程式不夠細心,但是還有一個更糟糕的問題是,格式化字串的語法並非放諸四海而皆準。



讓我們來看看問題最嚴重的 sscanf (個人看法),讓我們思考下面這段程式:

char test_str[]=”aaa bbb”;

sscanf(test_str, “%s %s”, p1, p2); //p1, p2 is char*

你會發現引數 p2 永遠不會得到他想要的值, why?很簡單,因為 C 語言的字串是以 0 作為結尾,所以當 sscanf 看到第一個 %s 時,他會開始掃瞄 test_str 直到0為止,此時將字串拷貝給p1,結束執行,而p2什麼也得不到。老練的 C 程式員的解法是改用 strtok 將字串分解,這在C語言中是合理的解法,但是在C++中將 std::string::c_str() 丟給 strtok() 執行將是一場浩劫,原因是 strtok 對字串通常採取破壞的手段(填入0進行分隔)。除此之外,各位不妨思考一下為何C++ Builder、Delphi的String只有 sprintf 而沒有實作 sscanf ?



Regular Expression
讓我們看看另一種語言Perl,Perl眾所皆知對字串處理的能力超強,而程式可與Regular Expression[1]溶為一體,而Regular Expression(後面簡稱regex)更不用說了,熟習Linux操作的人很少沒有人用過它(grep, sed…),根據你所下的expression,可以很方便的對文字進行精確的search/replace;不要以為這樣的功能只存在於forbidden language,事實上regex已經是UN*X標準配備(POSIX),連C語言也可以享用到!



就算在windows環境下,我們也有免費的C regex lib可用,PCRE(http://www.pcre.org/)就是十分知名的一套,大家常用的php, python等都有引用做為核心元件之一,不過PCRE本身並不好操作,參數多是一個原因,重點是PCRE操作的仍是C-style 字串,而非C++程式員熟習的std::string,當然我們可以透過一些包裝的手段來使得PCRE也可用在std::string之上;但是有了Regex++,我們就何苦重新發明輪子?



Regex++
Regex++[2]是一套完全以C++寫成的regex lib,支援VC、BCB、GCC等等C++ compiler,由於使用template的方式設計,於是不但可以用在std::string上,char*、甚至是stlport特有的rope都沒問題,聰明的你一定已經猜到,沒錯,他也是利用iterator的方式對字串操作,如此一來你在STL學到的知識仍然能用在Regex++身上,讓我們看看下面這個簡單的例子[3]:



bool validate_card_format(const std::string& s)

{

        static const boost::regex e(“//d{15,16}”);

        return regex_match(s ,e);

}



這個函數的用途是檢查字串長度是不是15or16個字元,並且每個字元都必須是數字(0~9),沒記錯的話這是用來檢查信用卡號碼的格式正不正確,//d代表必須為數字,{15,16}則限制了長度必須在15~16之間。



現在我們討論一個更複雜的例子:



const boost::regex e(“//A(//d{3,4})[- ]?(//d{4})[- ]?(//d{4})[- ]?(//d{4})//z”);



const std::string machine_format(“//1//2//3//4”);

const std::string human_format(“//1-//2-//3-//4”);



std::string

machine_readable_card_number(const std::string& s)

{

std::string result = regex_merge(s, e, machine_format, boost::match_default | boost::format_sed | boost::format_no_copy);



if(result.size()==0)

        throw std::runtime_error(“string is not a credit card number”);

        return result;

}



std::string

human_readable_card_number(const std::string& s)

{

        std::string result = regex_merge(s, e, human_format, boost, boost::match_default | boost::format_sed | boost::format_no_copy);



if(result.size()==0)

        throw std::runtime_error(“string is not a credit card number”);

        return result;

}



machine_readable_card_number()的功能是把信用卡號碼轉成適合機器讀取的格式,比方說XXXX-XXXX-XXXX-XXXX是我們一般人習慣的格式,假設我們想把它存進database做為索引,以字串作為欄位格式似乎不是個好主意(效能比較差),所以你可以利用machine_readable_card_number()進行轉換,然後再用我們的老朋友atoi()或是istringstream轉成整數存入資料庫中。同樣的,為了讓user心情愉快,當我們從database中讀出這個整數時,可以利用human_readable_card_number()轉成一般人習慣的格式J



這裡大略解釋一下程式內容(假如你學過Perl,可以跳過這段),regex_merge的用途是「當字串符合pattern時,依據format進行合併」,pattern指的是第二個參數,format是三個參數,至於旗標暫時不用去管他,pattern中的//A與//z限制字串必須全部match,而不是片段,第一個小括號(//d{3,4})對應到format中的//1(以此類推),[]代表一個集合,[- ]代表”-“與space,加上”?”代表”-“與space只能有一個或零個。



Overloaded Operator >>
如果剛剛舉的例子還不能讓你感受到regex的強大威力,那接下來的範例肯定會讓你耳目一新,大家都知道istringstream的用法如下:

int a, b;

string str=”123 567”;

istringstream iss(str);

iss>>a>>b;

很顯然易見,istringstream是以空白作為區隔讀取字串,假設我們今天不想用空白而想用”,”呢?雖然istringstream也能設定format state讓我們作一些小改變,不過要是逗號旁多了一些空白,istringstream也能輕鬆處理嗎(假設這個字串是從某個設定檔中讀出,這個設定檔是由user自行輸入產生的)?起碼以筆者現在的C++功力,是無法簡單用istringstream完成的。



但是我們現在有了Regex++這個強力的武器,問題就簡單多了,Regex++有個template function - regex_search()可以對string進行搜尋,之前曾經提到可以要求Regex++對字串的比對是「完全吻合」,當然也可以要求Regex++「部分吻合」,這樣我們便可以設計一個pattern來搜尋以逗號作為分隔資料的字串,也許這樣講還是很模糊,我們來看看實際的程式:



typedef pair ConstStdStrPtrPair;



template

ConstStdStrPtrPair operator>>(ConstStdStrPtrPair lhs, T& rhs)

{    

        boost::match_results what;

    static boost::regex expression(",* *([^,//s]+) *,*");

    unsigned int flags = boost::match_default;    



    if(boost::regex_search(lhs.first, lhs.second, what, expression, flags) && lhs.first!=lhs.second)

    {        

        string buf(what[1].first, what[1].second);

        istringstream ss(buf);

        if( !(ss>>rhs) )

        throw runtime_error("Convert Fail!");

        lhs.first = what[0].second;        

    }        

    else

        throw runtime_error("Format Error or String End");

        return lhs;

}





template

ConstStdStrPtrPair operator>>(const std::string& lhs, T& rhs)

{    

        boost::match_results what;

    static boost::regex expression(",* *([^,//s]+) *,*");

    unsigned int flags = boost::match_default;    

    ConstStdStrPtrPair ptrPair;

        

    ptrPair.first = lhs.begin();

    ptrPair.second = lhs.end();

        

    if(boost::regex_search(ptrPair.first, ptrPair.second, what, expression, flags) && ptrPair.first!=ptrPair.second)

    {        

        string buf(what[1].first, what[1].second);

        istringstream ss(buf);

        if( !(ss>>rhs) )

        throw runtime_error("Convert Fail!");

        ptrPair.first = what[0].second;        

    }        

    else

        throw runtime_error("Format Error or String End");

        return ptrPair;

}



我先解釋第二個operator>>,第一個參數是string,第二個是欲取得的值,將字串的開頭跟結尾傳入regex_search()進行搜尋,若是符合格式,what會指向符合格式的子字串,what[0].second會指向符合格式的子字串結尾的下一個iterator,可以作為下一次搜尋的起點,所以我把它與字串結尾存入一pair作為回傳值,然後把這個pair寫成第一個operator>>的第一個參數,這樣從字串讀出資料時就如同istringstream一樣簡單:



//example

int a,b,c;

string testString=”123,456,789”;

testString>>a>>b>>c;



至於what[1].first與what[1].second可以很明白的看出是指向符合格式的子字串的開頭與結尾,這邊的作法是另外存成字串,透過istringstream轉出,最後檢查有沒有轉換成功。



心得:
如何?Rgex++是不是替你的C++程式設計生涯帶來了曙光?其實剛剛的例子還有改進的空間,最顯而易見的就是pattern是寫死在operator>>裡,有沒有辦法讓user自行決定?或是「另外存成字串,透過istringstream轉出」這樣的動作會不會造成記憶體上額外的開銷?有沒有什麼降低成本的辦法?這些就留給讀者去研究吧(假如你有什麼好點子請聯絡我,謝謝!)



事實上,作者已經利用Regex++完成了不少工作,例如html mail(透過indy發信,regex++則用來搜尋html中的影像檔並修改為outlook可讀取的格式),自訂的設定檔讀取模組(like INI),parse C語言函數參數的型別與參數名稱…就是因為它這麼好用,才有感而發覺得該寫篇文章推廣一下,希望對大家的平日的工作能有所幫助J

參考資料:
[1] 學習Regular Expression,國內就有幾個不錯的網站:

http://www.cyut.edu.tw/~ckhung/olbook/gnulinux/regexp.shtml

這是朝陽科技大學資管系洪朝貴老師寫的,內容有點亂,不過用Perl配合著練習會好很多。

http://phi.sinica.edu.tw/aspac/reports/94/94019/

中央研究院計算中心寫的一份簡介

http://main.rtfiber.com.tw/~changyj/

事實上,國內我還沒看過比龍門少尉寫的更淺顯易懂的(強力推薦)



[2] http://ourworld.compuserve.com/homepages/John_Maddock/regexpp.htm



[3] 這兩個例子取自Regex++作者在Dr. Dobb’s Journal October 2001發表的文章”Regular Expressions in C++”,個人認為這篇文章是個很好的入門教材,Regex++本身的範例實在有點嚇人。

(3)CAtlRegExp

 

ATL中,由于ATL Server的需要,需要对Client发送过来的地址、命令等复杂文字字段信息解码,而正则表达式是公认的最强大的文字解析工具,所以,ATL提供了一些用于正则表达式的库方便了我们的工作。

1、 CATLRegExp类

声明:

template <class CharTraits=CAtlRECharTraits>
class CAtlRegExp; 

初始化:

与微软的GRETA类库(微软研究院推出的另一个正则表达式类库)不同,CATLRegExp并没有在构造函数中提供初始化匹配字符串的方法,而是让使用者通过调用它的Parse()方法,使用正则表达式字符串作为参数,就可以构造出一个我们所需要的用于匹配的类,例如我们需要匹配一种时间格式,可以是h:mm、也可以是hh:mm,那么我们可以这样构造我们的CAtlRegExp类:

CAtlRegExp <> re;
re.Parse( "{[0-9]?[0-9]}:{[0-9][0-9]}" ); 

ATL的正则表达式语法和Perl的正则表达式语法大同小异,不过有一个值得注意的地方就 是ATL中用大括号({ })表示其匹配字符串中的Group,我们上面的表达式中,就声明了2 个Group,一个是[0-9]?[0-9],另一个是[0-9][0-9]。

匹配:
调用CATLRegExp的Match()方法,就可以用该类来进行匹配了,Match方法的原型如下:

BOOL Match(const RECHAR *szIn, CAtlREMatchContext<CharTraits> *pContext, const RECHAR **ppszEnd=NULL)

参数的含义很明显,不过需要注意到第一个参数的型别是:const RECHAR * szIN,是一个 const指针,这表明我们可以方便得使用std::string类的c_str()方法给其传递参数。

Match的结果通过第二个参数pContext所指向的CAtlREMatchContext<>类来返回,Match 的结果及其相关信息都被存放在CAtlREMatchContext类中,我们只要访问CAtlREMatchContext的方法和成员就可以得到匹配的结果。

2、 CAtlREMatchContext类

声明:

template <class CharTraits=CAtlRECharTraits>
class CAtlREMatchContext

使用:

CAtlREMatchContext通过m_uNumGroups成员以及GetMatch()方法向调用者提供匹配的结果信息。m_uNumGroups代表匹配上的Group有多少组,GetMatch()则根据传递给它的Group
的Index值,返回匹配上的字符串的pStart和pEnd指针,调用者有了这两个指针,自然可以很方便的得到匹配结果。

3、 一个小示例

下面这个例子来源于MSDN,演示了CATLRegExp和CAtlREMatchContext类的典型使用方法:

#include "stdafx.h"
#include <atlrx.h>

int main(int argc, char* argv[])
{
   CAtlRegExp<> reUrl;

   // five match groups: scheme, authority, path, query, fragment
   REParseError status = reUrl.Parse(
        "({[^:/?#]+}:)?(//{[^/?#]*})?{[^?#]*}(?{[^#]*})?(#{.*})?" );
   if (REPARSE_ERROR_OK != status)
   {
      // Unexpected error.
      return 0;
   }

   CAtlREMatchContext<> mcUrl;
   if (!reUrl.Match( "http://search.microsoft.com/us/Search.asp?qu=atl&boolean=ALL#results",
      &mcUrl))
   {
      // Unexpected error.
      return 0;
   }

   for (UINT nGroupIndex = 0; nGroupIndex < mcUrl.m_uNumGroups;
        ++nGroupIndex)
   {
      const CAtlREMatchContext<>::RECHAR* szStart = 0;
      const CAtlREMatchContext<>::RECHAR* szEnd = 0;
      mcUrl.GetMatch(nGroupIndex, &szStart, &szEnd);

      ptrdiff_t nLength = szEnd - szStart;
      printf("%d: /"%.*s/"/n", nGroupIndex, nLength, szStart);
   }
}

Output

0: "http"
1: "search.microsoft.com"
2: "/us/Search.asp"
3: "qu=atl&boolean=ALL"
4: "results"

例子中所用的正则表达式为:

({[^:/?#]+}:)?(//{[^/?#]*})?{[^?#]*}(?{[^#]*})?(#{.*})?

以()为分界标志,共分成5组,第一组是{[^:/?#]+}:,^是“非”后面成员的意思,那么也就是说第一组从开头开始,一直到:、/、?、#其中任何一个结束。联系后面的待匹配字符串就可以得出所匹配的结果是http。

4、 自定义匹配字符串的缩写形式

为了方便,ATL已经帮我们定义了一些经常用到的正则表达式的简略形式。例如:/d代表([0-9])、/n代表(/r|(/r?/n))等。这些缩写形式都体现在CAtlRECharTraitsA/CAtlRECharTraitsW等类中,把这些类作为模板参数传递给CATLRegExp和 CAtlREMatchContext,我们就可以定义自己的匹配字符串缩写了。

class CAtlRECharTraitsA
{
    static const RECHARTYPE** GetAbbrevs()
    {
        static const RECHARTYPE *s_szAbbrevs[] = 
        {
            "a([a-zA-Z0-9])",   // alpha numeric
            "b([ //t])",        // white space (blank)
            "c([a-zA-Z])",  // alpha
            "d([0-9])",     // digit
            "h([0-9a-fA-F])",   // hex digit
            "n(/r|(/r?/n))",    // newline
            "q(/"[^/"]*/")|(/''''[^/'''']*/'''')",   // quoted string
            "w([a-zA-Z]+)", // simple word
            "z([0-9]+)",        // integer
            NULL
        };

        return s_szAbbrevs;
    }
};

以上是atlrx.h摘录下来的代码,可以很清楚地看到ATL是通过一个GetAbbrevs()函数来定义字符串缩写的。要定义新的缩写形式,我们只需要这样:

class   MyRegTraits : public ATL::CAtlRECharTraitsA
{
public:
    static const RECHARTYPE** GetAbbrevs()
    {
        static const RECHARTYPE *s_szAbbrevs[] = 
        {
            "a([a-zA-Z0-9])",   // alpha numeric
            "b([ //t])",        // white space (blank)
            "c([a-zA-Z])",  // alpha
            "d([0-9])",     // digit
            "h([0-9a-fA-F])",   // hex digit
            "n(/r|(/r?/n))",    // newline
            "q(/"[^/"]*/")|(/''''[^/'''']*/'''')",   // quoted string
            "w([a-zA-Z]+)", // simple word
            "z([0-9]+)",        // integer
            "e([0-8]+)",        //自己添加
            NULL
        };
        return s_szAbbrevs;
    }

};

让我们自己定义的Trait类继承自CAtlRECharTraitsA,然后改写GetAbbrevs()函数,增加 一些需要的简写就可以被使用了。下面的代码示例了使用了我们自己的类中定义的“/e”简 略表达:

int main ( )
{
    ATL::CAtlRegExp < MyRegTraits > re ;
    re.Parse( "//e+" ); 
    ATL::CAtlREMatchContext < MyRegTraits > mc ;

    BOOL res1 = re.Match( "678", &mc );  // returns TRUE: successful match
    res1 = re.Match ( "999" , &mc ) ;    //  returns FALSE:match fail
}

只要在构造ATL::CAtlRegExp和 ATL::CAtlREMatchContext类时,传递过去我们的 MyRegTraits类作为Traits的参数,就可以直接使用自己定义的简略符号了。

5、 结尾

虽然现在C++的社区里已经拥有了Boost::regex,GRETA等非常著名的正则表达式库,可是 作为VC++自带的模板库,ATL中的正则表达式库仍然给我们的工作提供了极大的便利。由于 ATL是微软官方发布的Library,所以它拥有良好的文档说明、严格的测试以及微软官方的 技术支持。另外在用ATL开发COM组件的时候,更可以方便地利用正则库的巨大威力。

 

 

 (4)TPL

TPL: 一个新的正则表达式(regex)库

概要

C++ 中正则表达式(regex)库已经很多。光 boost 中就有3个:regex、spirit、xpressive。那么我们为什么还需要一个新的呢?

字串2

多数正则表达式库都需要一个编译(compile)过程。即:通过解释一个正则表达式的字符串(pattern)来生成该正则表达式的内部表示(字节码)。例如 boost regex 就是这样。这类我们称之为动态正则表达式库。

字串9

spirit、xpressive 例外。他们直接通过重载 C++ 的操作符来表达一个正则表达式。在你用C++语法描述完一个正则表达式,它已经是内部表示(被C++编译器编译成了机器码)。这一类我们称之为静态正则表达式库。 黑蚂蚁安全网(www.hackant.com)

静态正则表达式库的好处主要有二:

字串2

  • 性能好。由于匹配代码直接编译成为了机器码,故此通常性能会好过动态的正则表达式。
  • 与 C++ 语言可形成良好的互动。可以非常容易在正则表达式中获得执行C++代码的时机。

缺点: 字串7

  • 正则表达式必须在编译期确定。如果你希望用户可以输入一个正则表达式,那么静态正则表达式库不能直接满足你的需求。

TPL 属于静态正则表达式库。本文也不准备讨论动态正则表达式。需要指出,xpressive 既支持动态正则表达式,也支持静态的正则表达式,但是我们并不考虑其动态正则表达式部分。

字串7

TPL 全称为 Text Processing Library(文本处理库)。spirit、xpressive 是很好的东西,实现 TPL 库中对这两者有所借鉴。 字串4

说起来开发 TPL 库的理由看起来挺好笑的:原因是 spirit、xpressive 太慢。不是执行慢,而是编译慢。我的机器算起来也不算差,但是每次修改一点点代码,编译过程都等待半天,实在受不了这样的开发效率。

字串3

从机理上讲,TPL 并无特别让人振奋之处。该有的 spirit、xpressive 相信都有了。三者都基于“表达式模板(Expression Templates)” 这样的技术。

字串6

闲话少说,这里给几个实际的样例让大家感受下: 字串8

样例一:识别以空格分隔的浮点数并放入vector中

代码:tpl/test/testtpl/Simplest.cpp

字串2

#include <vector>
#
include <tpl/RegExp.h>

using namespace tpl;

// What we use:
// * Rules: /assign(), %, real(), ws()
// * Matching: tpl::simple::match() 字串2

void simplest()
{
std::vector<double> values; // you can change vector to other stl containers.

if ( simple::match(
"-.1 -0.1 +32. -22323.2e+12", 黑蚂蚁安全网(www.hackant.com)
real()/assign(values) % ws()) )
{
for (
std::vector<double>::iterator it = values.begin();
黑蚂蚁安全网(www.hackant.com)

it != values.end(); ++it)
{
std::cout << *it << "/n";
}
}
} 字串4

输出: 字串4

-0.1
-
0.1
-
32
-
2.23232e+016
字串8

解释: 字串6

以上代码我相信比较难以理解的是 / 和 % 算符。 字串8

/ 符号我称之为“约束”或“动作”。它是在一个规则(Rule)匹配成功后执行的额外操作。这个额外的操作可能是: 字串3

  • 使用另一个Rule进行进一步的数据合法性检查。
  • 赋值(本例就是)。
  • 打印调试信息(正则表达式匹配比较难以跟踪,故此 Debug 能力也是 TPL 的一个关注点)。
  • 其他用户自定义动作。

% 符号是列表算符(非常有用)。A % B 等价于 A (B A)* 这样的正则表达式。可匹配 ABABAB..A 这样的串。一个典型案例是用它匹配函数参数列表。 字串5

样例二:识别以逗号分隔的浮点数并放入vector中

代码:tpl/test/testtpl/SimpleGrammar.cpp 字串2

// A simple grammar example.

// What we use:
// * Rules: /assign(), %, real(), gr(','), skipws()
// * Matching: tpl::simple::match()

void simple_grammar()
{
std::vector<double> values; // you can change vector to other stl containers. 黑蚂蚁安全网(www.hackant.com)

if ( simple::match(
" -.1 , -0.1 , +32. , -22323.2e+12 ",
real()/assign(values) % gr(','), skipws()) ) 黑蚂蚁安全网(www.hackant.com)
{
for (
std::vector<double>::iterator it = values.begin();
it != values.end(); ++it)
字串9

{
std::cout << *it << "/n";
}
}
} 字串5

输出:与样例一相同。

字串9

解释:尽管看起来好像没有发生太大的变化。但是这两个样例本质上是不同的。主要体现在:

字串8

  • 正则表达式的类型不同。real()/assign(values) % ws() 是一个Rule。而 real()/assign(values) % gr(',') 是一个 Grammar。简单来说,Rule 可以认为是词法级别的东西。Grammar 是语法级别的东西。Grammar 的特点在于,它匹配一个语法单元前,总会先调用一个名为Skipper的特殊Rule。上例中 Skipper 为 skipws()。
  • 两个 match 的原型不同。第一个match的原型是:match(Source, Rule), 第二个match的原型是:match(Source, Grammar, Skipper)。

第二个例子如果用 Rule 而不是用 Grammar 写,看起来是这样的:

字串8

if ( simple::match(
" -.1 , -0.1 , +32. , -22323.2e+12 ",
(skipws() + real()/assign(values)) % (skipws() + ',')) ) ... 字串3

你可能认为这并不复杂。单对这个例子而言,确实看起来如此。但是如果你这样想,不妨用 Rule 做下下面这个例子。 黑蚂蚁安全网(www.hackant.com)

样例三:运算器(Calculator)

功能:可处理+-*/四则运算、()、函数调用(sin, cos, pow)。代码:tpl/test/testtpl/Calculator2.cpp (呵呵,只有60行代码哦!) 字串3

#include <stack>
#
include <tpl/RegExp.h>
#
include <tpl/ext/Calculator.h>
#
include <cmath>

字串8



using namespace tpl;

void calculate2()
{
typedef SimpleImplementation impl;

// ---- define rules ----

impl::Allocator alloc; 字串4

std::stack<double> stk;

impl::Grammar::Var rFactor;

impl::Grammar rMul( alloc, '*' + rFactor/calc<std::multiplies>(stk) );

黑蚂蚁安全网(www.hackant.com)


impl::Grammar rDiv( alloc, '/' + rFactor/calc<std::divides>(stk) );
impl::Grammar rTerm( alloc, rFactor + *(rMul | rDiv) );

字串6



impl::Grammar rAdd( alloc, '+' + rTerm/calc<std::plus>(stk) );
impl::Grammar rSub( alloc, '-' + rTerm/calc<std::minus>(stk) );
黑蚂蚁安全网(www.hackant.com)

impl::Grammar rExpr( alloc, rTerm + *(rAdd | rSub) );

impl::Rule rFun( alloc,
"sin"/calc(stk, sin) | "cos"/calc(stk, cos) | "pow"/calc(stk, pow) );

字串2



rFactor.assign( alloc,
real()/assign(stk) |
'-' + rFactor/calc<std::negate>(stk) |
字串8

'(' + rExpr + ')' |
(gr(c_symbol()) + '(' + rExpr % ',' + ')')/(gr(rFun) + '(') |
字串6

'+' + rFactor );

// ---- do match ----

for (;;)
{
std::string strExp;
std::cout << "input an expression (q to quit): ";

字串8


if (!std::getline(std::cin, strExp) || strExp == "q") {
std::cout << '/n';
字串7

break;
}

try {
while ( !stk.empty() )
stk.pop();
if ( !impl::match(strExp.c_str(), rExpr + eos(), skipws()) )

字串5


std::cout << ">>> ERROR: invalid expression!/n";
else
std::cout << stk.top() << "/n";
}
字串5

catch (const std::logic_error& e) {
std::cout << ">>> ERROR: " << e.what() << "/n";
字串3

}
}
}

// ------------------------------------------------------------------------- 字串8

解释: 字串9

  • Grammar::Var 用于定义一个未赋值即被引用的Grammar。相应地,我们也有 Rule::Var。
  • gr(Rule) 是将一个 Rule 转换为 Grammar。
  • SimpleImplementation 是什么?嗯,这个下回聊。
  • <tpl/ext/Calculator.h> 并不属于 tpl regex 库。代码也不多。参见:tpl/ext/Calculator.h 

 

(5)GRETA

GRETA是微软研究院推出的一个正则表达式模板类库,GRETA 包含的 C++ 对象和函数,使字符串的模式匹配和替换变得很容易,它们是:

  • " rpattern: 搜索的模式
  • " match_results/subst_results: 放置匹配、替换结果的容器
  •   为了执行搜索和替换的操作,用户首先需要用一个描述匹配规则的字符串来显式初始化一个rpattern对象,然后把需要匹配的字符串作为参数,调用rpattern的函数,比如match()或者substitute(),就可以得到匹配后的结果。如果match()/substitute()调用失败,函数返回false,如果调用成功,函数返回true,此时,match_results对象存储了匹配结果。请看例子代码:

    #include <iostream>
    #include <string>
    #include "regexpr2.h"
    using namespace std;
    using namespace regex;
    int main() {
         match_results results;
         string str( "The book cost $12.34" );
         rpattern pat( "//$(//d+)(//.(//d//d))?" );   
         // Match a dollar sign followed by one or more digits,
         // optionally followed by a period and two more digits.
         // The double-escapes are necessary to satisfy the compiler.
         match_results::backref_type br = pat.match( str, results );
         if( br.matched ) {
             cout << "match success!" << endl;
             cout << "price: " << br << endl;
         } else {
             cout << "match failed!" << endl;
         }
         return 0;
    }      

    程序输出将是:

    match success!
    price: $12.34

      您可以阅读GRETA文档,获知rpattern对象的细节内容,并掌握如何自定义搜索策略来得到更好的效率。
      注意:所有在头文件regexpr2.h里的声明都在名称空间regex之中,用户使用其中的对象和函数时,必须加上前缀"regex::",或者预先"using namespace regex;" 一下,为了简单起见,下文的示例代码中将省略"regex::" 前缀。 作者生成了greta.lib和regexpr2.h文件,只需这两个文件的支持即可使用greta来解析正则表达式。

    匹配速度小议
      不同的正则表达式匹配引擎擅长于不同匹配模式。作为一个基准,当用模式:"^([0-9]+)(/-| |$)(.*)$" 匹配字符串"100- this is a line of ftp response which contains a message string"时,GRETA的匹配速度比boost(http://www.boost.org)正则表达式库大约快7倍,比ATL7的CATLRegExp快10倍之多! Boost Regex 的说明文档带有一个很多模式的匹配测试Performance结果。比较这个结果后,我发现GRETA在大部分情况下和Boost Regex性能差不多,但是在用Visual Studio.Net 2003编译的情况下,GRETA还略胜一筹。

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值