本文摘要翻译了几篇文章的内容,简单介绍 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++
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
概要
C++ 中正则表达式(regex)库已经很多。光 boost 中就有3个:regex、spirit、xpressive。那么我们为什么还需要一个新的呢?
字串2
多数正则表达式库都需要一个编译(compile)过程。即:通过解释一个正则表达式的字符串(pattern)来生成该正则表达式的内部表示(字节码)。例如 boost regex 就是这样。这类我们称之为动态正则表达式库。
spirit、xpressive 例外。他们直接通过重载 C++ 的操作符来表达一个正则表达式。在你用C++语法描述完一个正则表达式,它已经是内部表示(被C++编译器编译成了机器码)。这一类我们称之为静态正则表达式库。 黑蚂蚁安全网(www.hackant.com)
静态正则表达式库的好处主要有二:
- 性能好。由于匹配代码直接编译成为了机器码,故此通常性能会好过动态的正则表达式。
- 与 C++ 语言可形成良好的互动。可以非常容易在正则表达式中获得执行C++代码的时机。
缺点: 字串7
- 正则表达式必须在编译期确定。如果你希望用户可以输入一个正则表达式,那么静态正则表达式库不能直接满足你的需求。
TPL 属于静态正则表达式库。本文也不准备讨论动态正则表达式。需要指出,xpressive 既支持动态正则表达式,也支持静态的正则表达式,但是我们并不考虑其动态正则表达式部分。
字串7
TPL 全称为 Text Processing Library(文本处理库)。spirit、xpressive 是很好的东西,实现 TPL 库中对这两者有所借鉴。 字串4
说起来开发 TPL 库的理由看起来挺好笑的:原因是 spirit、xpressive 太慢。不是执行慢,而是编译慢。我的机器算起来也不算差,但是每次修改一点点代码,编译过程都等待半天,实在受不了这样的开发效率。
字串3
从机理上讲,TPL 并无特别让人振奋之处。该有的 spirit、xpressive 相信都有了。三者都基于“表达式模板(Expression Templates)” 这样的技术。
闲话少说,这里给几个实际的样例让大家感受下: 字串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
输出:与样例一相同。
解释:尽管看起来好像没有发生太大的变化。但是这两个样例本质上是不同的。主要体现在:
- 正则表达式的类型不同。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对象,然后把需要匹配的字符串作为参数,调用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还略胜一筹。
366

被折叠的 条评论
为什么被折叠?



