字符串相关库,对应书中第五章 字符串与文本处理,大大增强了C++在文本处理上的能力。 lexical_cast 实现了字符串和数字之间的方便转换;format库提供了C++ 类似 printf() 的能力,用以格式化输出;string_algo 是一个算法库,提供了大量与字符串和文本处理相关的算法;tokenizer 库专注于文本处理领域的分词功能;xpressive 是一个灵活且功能强大的正则表达式解析库。

lexical_cast

之前单独有一篇文章讲 lexical_cast 这里不再重复。

format

format 基本集成了 printf 的格式化语法,每个 printf 格式化选项都以 % 符号开始,后面是格式规则,例如

  • %05d :输出宽度为5的整数,不足位用0填充
  • %-8.3f :输出左对齐,总宽度为8,小数位3位浮点数
  • % 10s :输出10位字符串,不足位用空格填充
  • %05X :输出宽度为5的大写16进制整数,不足位用0填充。

新增格式:

  • %|spec| : 竖线分割,区分格式化选项和普通字符
  • %N% : 标记第N个参数,相当于占位符,不带任何其他的格式化选项

主要用法

需要包含头文件

#include <boost/format.hpp>
using namespace boost;

例子:

cout << format ("%s:%d+%d=%d\n" )% "sum" % 1 % 2 % (1+2);

format fmt("(%1% + %2%) * %2% = %3%\n" );
fmt % 2 % 5 % ((2+5) * 5);
cout << fmt.str();

/**
* 程序结果
* sum:1+2=3
* (2 + 5) * 5 = 35
*/

format 还有很多高级的用法,参见文档。

string_algo

C++98 标准库中提供了字符串标准类 std::string , 它有一些基本成员函数用以查询子串,访问字符,等基本功能。

主要特点

提供全面的字符串算法库

  • 大小写无关比较
  • 修剪
  • 特定模式子串查找

主要用法

包含头文件

#include <boost/algorithm/string.hpp>
using namespace boost;

例子:

#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost;

int main() {

    string str("readme.txt");
    if (ends_with(str, " txt")) {
    	cout << to_upper_copy(str) + "UPPER" << endl;              // upper case
    }

    replace_first(str, "readme ", "followme ");                       // replace
    cout << str << endl;

    vector<char> v(str.begin(), str.end());
    vector<char> v2 = to_upper_copy(erase_first_copy(v, "txt ")); // delete sub string
    for (int i = 0; i < v2.size(); ++i) {
    	cout << v2[i];
    }

    return 0;
}

string_algo 库命名遵循标准库惯例,算法名均为小写形式,并使用不同前缀或者后缀来区分不同版本,命名规则如下:

  • 前缀 i : 表示算法大小写不敏感,否则大小写敏感
  • 后缀_copy : 表示算法不变动输入,返回处理结果的拷贝,否则算法原地处理,输入即输出
  • 后缀_if : 需要判断式的谓词函数对象,否则使用默认的判断准则

string_algo 库提供算法共分为五大类:

  • 大小写转换
  • 判断式与分类
  • 修建
  • 查找与替换
  • 分割与合并

每一类算法中都会包含一系列函数。

tokenizer

主要特点

tokenizer 库是专门用于分词 token 字符串处理库,可以用简单方法把一个字符串分解成若干单词。

tokenizer 库可以容易地执行分词操作,但是它存在一些固有的缺陷。

  • 只支持单字符分割,当遇到“||”分割符时无能为力,智能自定义分词函数,或者使用 string_algo, 正则表达式等其他方法
  • 对wstring(unicode) 缺乏完善的考虑

主要用法

需包含头文件:

#include <boost/bokenizer.hpp>
using namespace boost;

例子:

#include <iostream>
#include <vector>
#include <cstring>
#include <boost/assign.hpp>
#include <boost/typeof/typeof.hpp>
#include <boost/tokenizer.hpp>
using namespace std;
using namespace boost;

template<typename T>
void print(T &tok) {
    for (BOOST_AUTO(pos, tok.begin()); pos != tok.end(); ++pos) {
    cout << *pos << "|\t";
    }
    cout << endl;
}
int main() {
    string str("Link radfe the dfafe -adfead");

    tokenizer<> tok(str);

    print(tok);

    /**
     * char_separator
     * 第一个参数 dropped_delims 分隔符集合,这个集合中的字符不会作为分词结果出现
     * 第二个参数 kept_delims 分隔符集合,其中的字符会保留在分词结果中
     * 第三个参数 empty_tokens 类似 split 算法 eCompress 参数,处理两个连续出现的分隔符。 keep_empty_tokens 表示连续出现的分隔符标识了一个空字符串。
     * 使用默认构造函数,不传入任何参数,行为等同于 char_separator(" ",标点符号字符, drop_empty_tokens) ,以空格和标点符号分词,保留标点符号,不输出空白单词。
     */
    char * s = "xxx ;; <yyy-zzz> !!!";
    char_separator<char> sep;
    tokenizer<char_separator<char>, char *> tok1(s, s + strlen(s), sep);
    print(tok1);

    char_separator<char> sep1(";-<>!", "", keep_empty_tokens);
    tok1.assign(s, s + strlen(s), sep1);
    print(tok1);

    char_separator<char> sep2(" ;-!", "<>", drop_empty_tokens);
    tok1.assign(s, s + strlen(s), sep2);
    print(tok1);

    /**
     * escaped_list_separator
     * 专门处理 CSV 格式(Comma Split Value,逗号分割值)的分词对象
     * 第一个参数 e 指定了字符串中的转义字符,默认是斜杠\
                     * 第二个参数是分隔符,默认是逗号
     * 第三个参数是引号字符,默认是"
     */

    string strcom = "id,100,name,\" mario\"";
    escaped_list_separator<char> sepcom;
    tokenizer<escaped_list_separator<char> > tokcom(strcom, sepcom);
    print(tokcom);

    /**
     * offset_separator
     * 不是基于分隔符,而是使用偏移量,在处理某些不使用分隔符,而使用固定字段宽度文本时非常有用。
     * 构造函数接受两个迭代器,也可以是数组指针begin end,指定分词用的整数偏移量序列,整个序列每个元素是分词的宽度
     *
     bool 参数 bwrapoffsets ,决定是否在偏移量用完之后继续分词
     *
     bool 参数 return_partial_last 决定在偏移量序列最后是否返回分词不足的部分
     * 最后两个参数默认值都是true
     */
    string stroffset = "2233344445566666666";
    int offsets[] = { 2, 3, 4 };
    offset_separator sepoff(offsets, offsets + 3, true, false);
    tokenizer<offset_separator> tokoff(stroffset, sepoff);
    print(tokoff);

    offset_separator sepoff2(offsets, offsets + 3, false);
    tokoff.assign(stroffset, sepoff2);
    print(tokoff);

    offset_separator sepoff3(offsets, offsets + 3, true, false);
    print(tokoff);
    return 0;
}

xpressive

正则表达式是处理文本强有力的工具,使用复杂的语法规则,能够解决文本处理领域绝大多数问题,诸如验证、匹配、查找、替换等等。xpressive 是一个先进的、灵活的、功能强大的正则表达式库,提供了对正则表达式全面的支持,而且比原正则表达式库 boost.regex 要好的是它不需要编译,速度快,同时语法又很类似。

xpressive 提供动态和静态两种方式。静态方式使用操作符重载生成编译期的表达对象,可以在编译期进行正则表达式的语法检查。动态的方式则是较传统的用法,与 boost.regex 和 Python 中的 re 模块相似,以字符串作为一个表达式对象,在运行时进行语法检查和处理。

正则表达式介绍

正则表达式定义了一套完善而复杂的语法规则,用于匹配特定模式的字符串,少量字符被用于定义特殊匹配模式语法,它们是: .^$()*+?{}[]\|。

  • 点号 (.) 匹配任意单个字符
  • ^ 尖角号 行开头
  • $ 行尾
  • () 括号,子表达式,可重复
  • * 星号,表前面元素可以重复任意多次 (n>=0)
  • + 加号,表前面元素可以重复一次或多次(n>0)
  • ? 问号,表前面的元素可以重复0次或者1次 (n=0,1)
  • {} 手动指定元素重复次数, {n}重复n次, {n,} 重复 >=n次, {n,m } 重复 n 到 m 次之间的次数, 即 n <= x <=m 次。
  • [] 定义字符集合
  • \ 转义字符
  • | 逻辑或的概念,匹配两侧的元素之一。

其他经常使用 \d 匹配数字 [0-9] , \w 匹配字母 [a-z] , \s 匹配空格等。

C++ 代码中的斜杠需要变成双斜杠,在使用正则表达式时,在语句前使用注释保存原始表达式,以方便未来的调试和维护。

  • basic_regex 是正则表达式的基本类,常用 sregexcregex 用于操作std::string ,和 C风格字符串。
  • match_results 保存正则匹配结果,常用 smatch 和 cmatch 用来支持 std::string 和 字符串。
  • sub_match 模板类类似迭代器对的对象,继承自 std::pair ,可以把它当成作一个字符串的区间。

主要用法

  • 混用两种方式,包含头文件 <boost/xpressive/xpressive.hpp>
  • 仅仅想使用静态方式,可以只包含头文件 <boost/xpressive/xpressive_static.hpp>
  • 仅仅想使用动态方式,可以只包含头文件 <boost/xpressive/xpressive_dynamic.hpp>

须有如下命名空间:

using namespace boost::xpressive;

例子:

#include <iostream>
#include <vector>
#include <cstring>
#include <boost/assign.hpp>
#include <boost/typeof/typeof.hpp>
#include <boost/tokenizer.hpp>
#include <boost/xpressive/xpressive_dynamic.hpp>
using namespace std;
using namespace boost;

int main() {
    using namespace boost::xpressive;

    string s = "Hi world, I am from Mars!";
    sregex reg = sregex::compile("(M\\w{3})");
    bool ret = regex_match(s, reg);

    // match identity card number
    // 18 number , first 6 area code, middle 8 birthday, last 4 random number possible x
    // \d{6}(1|2)\d{3}(0|1)\d[0-3]\d\d{3}(X|\d)

    // regex_search
    // regex_search 检测输入表达式中是否包含正则表达式,即存在一个匹配正则表达式的子串
    char* str = "there is a power-suit item";
    cregex creg = cregex::compile("(power)-(.{4})\\s(\\w{4})", icase);
    ret = regex_search(str, creg);

    cmatch what;
    regex_search(str , what, creg);
    for (int i = 0; i < what.size() ; ++i){
    	cout << what[i] << endl;
    }

    // 替换
    // regex_replace()
    cout << regex_replace(s , reg , "Earth") << endl; // replace Mars with Earth
    cout << regex_replace(s , reg , "$1 haha") << endl;
    cout << regex_replace(s , reg , "$1 $&") << endl;

    s = regex_replace(s , reg , "Earth");
    cout << s << endl;
    // regex_iterator<>

    string ss = "boost1, Boost2, BoOst3, etc";
    sregex ssreg = sregex::compile("boost\\d",icase);
    sregex_iterator pos(ss.begin(), ss.end(), ssreg);
    sregex_iterator end;
    while(pos != end){
        cout << (*pos)[0] << "\t";
        ++pos;
    }

    return 0;
}