2009年3月21日星期六

【原】Boost库之tokenizer的使用

在tokenizer出现之前,如果我们要对一个字符串进行分割,可能要自己封装一个函数。如果有n种不同的分割规则,那么你要封装n个不同的分割函数……太麻烦了!现在好了,Boost库的tokenizer封装了统一的语法来分割字符串,并且提供了三种常用的分割方式,足以满足我们的绝大多数编程需求。
tokenizer主要由分割器和迭代器两部分组成。分割器用于指定字符串的分割规则,如果不指定任何分割器,则tokenizer默认将字符串以空格和标点(键盘上除26个字母(包括大小写)和数字外的其他可打印字符)为边界进行分割。迭代器用于保存字符串分割后的结果。下面着重介绍tokenizer的三种分割器。

■ char_separator是默认的分隔器,它以一组特定的字符来分隔字符串,其构造函数为:
explicit char_separator(); (默认构造函数)
explicit char_separator(const Char* dropped_delims, const Char* kept_delims = "", empty_token_policy empty_tokens = drop_empty_tokens);
其参数的详细说明如下:
◆ dropped_delims和kept_delims:是两个字符数组,这两个数组中的每个字符都是一个分割符。不同的是,dropped_delims中的分割符本身被丢弃,而kept_delims中的分割符本身作为一个单独的记号被保留在迭代器中。默认构造函数的dropped_delims为空格符,kept_delims为标点符号。
◆ empty_tokens:是否保留长度0的记号。它有两个可选值,keep_empty_tokens表示要保留空记号,而默认的drop_empty_tokens表示丢弃空记号。默认构造函数的empty_tokens为drop_empty_tokens。

■ offset_separator主要用于解析长度和格式都固定的字符串,如身份证或电话号码等。其构造函数为:
template<typename Iter> offset_separator(Iter begin,Iter end,bool bwrapoffsets = true, bool breturnpartiallast = true);
其参数的详细说明如下:
◆ begin和end:指向一个整数容器开始和末尾的迭代器,其中每个元素表示要分割的字段的字符数。
◆ bwrapoffsets: 如果字符串长度大于整数容器中所有字段长度的和,是否重复解析字符串。
◆ breturnpartiallast: 当字符串解析到末尾剩余部分的长度(注意:包括结尾的空字符)小于字段长度时,是保留还是丢弃剩余部分。

■ escaped_list_separator主要用于解析包含转义符、分隔符和引号的字符串。其构造函数为:
explicit escaped_list_separator(Char e = '\\', Char c = ',',Char q = '\"');
explicit escaped_list_separator(string_type e, string_type c, string_type q);
其参数的详细说明如下:
◆ e:为单个转义符或包含多个转义符的串。转义符后跟引号表示引号,转义符后跟n表示换行,连续两个转义符表示作为普通字符的转义符本身。
◆ c:为单个分割符或包含多个分割符的串,字符串以该分隔符为边界进行分割。
◆ q:为单个引号或包含多个引号的串,两个引号中间出现的分隔符不再作为分割标志,而是作为普通字符处理。

最后给出一组简单的测试题,大家既可以借此看看tokenizer的语法,也可以根据回答结果来考察一下你对tokenizer的用法有没有理解透彻。稍后我会在评论中给出正确答案。
a) string s = "hello, crearo-sw blog";
tokenizer<> tok(s);
for (tokenizer<>::iterator beg=tok.begin(); beg!=tok.end(); ++beg)
{
cout << *beg <<endl;
}

b) string str = ";!!;miaocl|zhangwh ||-hujian--terry|";
typedef tokenizer<char_separator<char> > tokenizers;
char_separator<char> sep("-;", "|", keep_empty_tokens);
tokenizers tokens(str, sep);
for (tokenizers::iterator tok_iter = tokens.begin(); tok_iter != tokens.end(); ++tok_iter)
{
cout << "<" << *tok_iter << "> ";
}

c) string s = "20090321";
int offsets[] = {4,2,3};
offset_separator f(offsets, offsets+3, true, false);
tokenizer<offset_separator> tok(s,f);
for(tokenizer<offset_separator>::iterator beg=tok.begin();
beg!=tok.end(); ++beg)
{
cout << *beg << endl;
}

6 条评论:

  1. 一个问题,“如果不指定任何分割器,则tokenizer默认将字符串以空格和标点为边界进行分割”,这里的标点都指哪些符号?

    回复删除
  2. 我刚才试验了一下,这里的标点符号应当是指键盘上除26个字母(包括大小写)和数字外的其他可打印字符。另外,char_separator还有一个默认的构造函数。文档已经更新。

    回复删除
  3. 没上机的答案:
    a)result:
    hello
    crearo
    sw
    blog

    b)result(用[]代替<>):
    [] [!!] [] [miaocl] [|] [zhangwh ] [|] [] [|] [] [hujian] [] [terry] [|] []

    c)result:
    2009
    03
    21

    回复删除
  4. a)对了,b)和c)有点问题。

    公布正确答案啦!
    a)result:
    hello
    crearo
    sw
    blog

    b)result(用[]代替<>):
    [] [!!] [miaocl] [|] [zhangwh ] [|] [] [|] [] [hujian] [] [terry] [|] []

    c)result:
    2009
    03

    回复删除
  5. 最后一道题我弄错了,应当是:
    2009
    03
    21

    回复删除
  6. 我答案的第二个[]确实是多余的,像是看走眼了...不过empty_tokens这个标记的确很重要。

    回复删除