目录
一、lexical_cast
与C语言、C++的对比
二、format
1.format类
2.格式化语法
三、string_ref
1.背景
2.boost::string_ref
3.remove_prefix( )和remove_suffix( )
四、string_algo
1.大小写转换
2.字符串判断
a.函数
b.函数对象
3.分类函数
4.修剪
5.查找
6.替换与删除
7.分割
8.合并
9.查找分割迭代器
五、xpressive
lexical_cast库可以进行字符串与整数\浮点数之间的互相转换。
lexical_cast的标准形式有两个模板参数 template
字符串转数字时,字符串中只能有数字和小数点,不能出现字母或其他非数字字符 (表示指数的e\E 除外) 。
lexical_cast可以将整型或字符串的0、1转换为bool类型,注意不能使用true\false字面值。
当lexical_cast无法执行转换操作时会抛出异常 bad_lexical_cast,它是std::bad_cast的派生类。我们可以使用try/catch块来保护代码。当然,我们可以使用try_lexical_convert( )安全的转换字面值,避免抛出异常,它以bool返回值来表示是否转换成功。
lexical_cast内部使用了标准库的流操作,因此,对于它的转换对象有以下要求,标准容器和其他自定义类型需满足这些条件,否则不能使用 lexical_cast 。
- 转换起点对象是可流输出的,即定义了 opreator< < 。
- 转换终点对象是可流输入的,即定义了 opreator> >。
- 转换终点对象必须是可默认构造、可拷贝构造的。
#include
void TestLexical_Cast()
{
std::string str = boost::lexical_cast(0x96);
std::cout << str << std::endl; //150
std::string str1 = boost::lexical_cast(30);
std::cout << str1 << std::endl; //30
float pi = boost::lexical_cast("3.141592653");
std::cout << pi << std::endl; //3.14159
//只支持整型、字符串的0、1转bool
bool bo = boost::lexical_cast("0");
std::cout << bo << std::endl;
try {
//错误,待转换的字符串中只能有数字和小数点及指数的e/E,不能有其他的字符
int num = boost::lexical_cast("0x96");
std::cout << num << std::endl;
}
catch (boost::bad_lexical_cast e)
{
std::cout << e.what() << std::endl; //bad lexical cast: source type value could not be interpreted as target
}
//使用try_lexical_convert安全的转换字面值,避免抛出异常
int num;
bool is_success = boost::conversion::try_lexical_convert("0x96", num);
std::cout << "is_success:" << is_success << ",num:" << num << std::endl;//0,96
}
C 语言中的 atoi ( )和 atof( )系列的函数是不对称的,仅可以把字符串转换成数值,不存在把数值转换为字符串的转换。
c++11 增强了字符串与数字的互操作性,提供stoX ( ) 和 to_string ( ) 函数实现了字符串与数字之间的转换。它无需写模板参数,而且允许字符串里出现非数字字符——它们会忽略起始的空格,直到遇到无法转换的字符为止。但如果字符串不是以空格、数字开头,或者超出了数字类型的范围,那么这些函数会抛出 std::invalid_argument 或 std::out_of_range 异常。
int a = 10;
std::string a_str = std::to_string(a);
std::cout << a_str << std::endl; //10
int b = std::stoi("12ss");
std::cout << b << std::endl; //12
C 语言中经典的 printf ( )使用了 C 语言里的可变参数,缺乏类型安全检查(速度快),但它的语法简单高效,并且被广泛地接受和使用,影响深远。
boost.format 库 " 扬弃 " 了 printf ( ),实现了类似的格式化对象,可以把参数格式化到一个字符串,而且此操作是完全类型安全的(速度慢)。format 模仿了流操作符 "<<" ,重载了二元操作符operator%作为参数输入符,它同样可以串联任意数量的参数。它已经被收入C ++20 标准。
format并不是一个真正的类,而是一个 typedef ,其真正的实现是 basic_format 。
成员函数 str( ) 返回 format 对象内部已经格式化好的字符串(不清空),如果没有得到所有格式化字符串要求的参数则会抛出异常。 format 库还提供一个同名的自由函数 str( ),它位于 boost 名字空间,返回 format 对象内部已格式化好的字符串。
成员函数 size( ) 可以获得已格式化好的字符串的长度,如果没有得到所有格式化字符串要求的参数则会抛出异常。
成员函数 parse ( ) 清空 format 对象的内部缓存,并改用一个新的格式化字符串。如果仅仅想清空缓存,则可以使用 clear ( ),它把 format 对象恢复到初始化状态。这两个函数执行后再调用 str ( )或size( )则会抛出异常,因为此时没有输入格式化参数。
format基本继承了 printf 的格式化语法,如%05d、%-8.3f等。除经典的 printf 格式化外,format 还增加了新的格式:
#include
void TestFormat()
{
std::cout << boost::format("%s:%d+%d=%d\n") % "sum" % 1 % 2 % (1 + 2); //sum:1+2=3
//对比printf
printf("%s:%d+%d=%d\n", "sum", 1, 2, 1 + 2); //sum:1+2=3
//%N% 标记第N个参数,相当于占位符,不带任何其他的格式化选项
boost::format fmt("(%1%+%2%)*%3%=%4%"); //预先创建一个format格式化对象
fmt % 4 % 6 % 2; //分多次输入被格式化的参数
fmt % ((4 + 6) * 2);
std::cout << "格式化字符串:" << fmt.str() << ",字符串长度:" << fmt.size();//格式化字符串:(4+6)*2=20,字符串长度:10 //(4+6)*2=20
fmt.clear();
fmt % 3 % 6 % 4 % ((3 + 6) * 4);
std::cout << "格式化字符串:" << fmt.str() << ",字符串长度:" << fmt.size();//格式化字符串:(3+6)*4=36,字符串长度:10
//对比printf
printf("(%d+%d)*%d=%d", 4, 6, 2, (4 + 6) * 2); //(4+6)*2=20
//%|spec| 增加竖线分割,更好地区分格式化选项与普通字符
boost::format fmt1("%|05d|-%|-8.3f|-%|10s|-%|05X|");
fmt1 % 10 % 3.14%"hello" % 150;
std::cout << fmt1.str() << std::endl;//00010-3.140 - hello-00096
}
在C ++中处理字符串的基本工具是标准字符串std::string,但构造一个 std::string 成本较高, 因为它必须完全持有字符串的内容,极端的时候会有很高的内存拷贝代价,影响程序效率。使用const std::string& 可以避免一些问题,但它在处理 C 字符串、提取子串时却无能为力。总而言之, std::string 显得有些 " 重 ",我们需要一种更 " 轻 " 的字符串工具——boost::string_ref。
boost::string_ref它只持有字符串的引用,没有内存拷贝代价,所以运行效率很高,是更好的 const std::string &。 它已经被收入 C ++17 标准 ( 但更名为string_view)。
string_ref库定义了 basic_string_ref,它不拷贝字符串,所以也不分配内存,只用两个成员变量 ptr_ 和len_ 标记字符串的起始位置和长度,这样就实现了对字符串的表示。basic_string_ref是一个字符串的“常量视图”,大部分成员函数都是由 const 修饰的,我们只能像const std::string & 那样去观察字符串而无法修改字符串。
由于 string_ref 的接口与 string 完全相同,所以它的一个重要用途是代替 const std::string & 类型作为函数参数或返回值(必须保证被引用的字符串对象可用,尽量避免长期持有或延后使用。在确实需要持有或修改字符串的时候,可以调用成员函数 to_string ( )获得一个拷贝副本来保证安全。),可以完全避免字符串拷贝代价,提高字符串的处理效率。
虽然 string_ref 不能直接改变原字符串,但它可以使用remove_prefix( )和remove_suffix( ) 这两个函数调整 string _ ref 内部的字符串指针和长度,达到变动字符串引用的目的——但原始字符串仍然没有被修改。
#include
void TestStringRef()
{
const char* ch = "Study C++ in library";
std::string str(ch); //标准字符串,有拷贝成本
boost::string_ref str_ref(ch); //零拷贝
if (str_ref == str)
{
std::cout << "两个字符串相等" << std::endl; //两个字符串相等
}
std::cout << "第一个字符:" << str_ref.front() << std::endl; //第一个字符:S
std::cout << "最后一个字符:" << str_ref[str_ref.length()-1] << std::endl;//最后一个字符:y
int index = str_ref.find('+');
std::cout << "+索引" << index << std::endl; //+索引7
boost::string_ref substr = str_ref.substr(6, 3);
std::cout << substr << std::endl; //C++
std::string str2 = str_ref.to_string();
if (str2 == str&& str_ref==str2)
{
std::cout << "三个字符串相等" << std::endl; //三个字符串相等
}
str_ref.remove_prefix(6);
std::cout <<"移除前6个字符:"<< str_ref << std::endl; //移除前6个字符:C++ in library
str_ref.remove_suffix(8);
std::cout << "移除后8个字符:" << str_ref << std::endl;//移除后8个字符:C++ in
}
string_algo 库的出现改变了这个局面。它是一个非常全面的字符串算法库,提供了大量的字符串操作函数,如大小写转换、字符串判断、去空格、查找替换、分割与合并等, string_algo 库可以在不使用正则表达式的情况下处理大多数字符串的相关问题。
string_algo 库中的算法命名遵循了标准库的惯例,算法名均为小写形式,并使用不同的词缀来区分不同的版本,命名规则如下。
#include
std::string str("c://test/readme.json");
//大小写转换
std::cout << boost::to_upper_copy(str) << std::endl; //结果 C://TEST/README.JSON
std::string str("c://test/readme.json");
if (boost::ends_with(str, "json"))
{
std::cout << "是个json文件" << std::endl; //是个json文件
}
if (boost::icontains(str, "C://"))//忽略大小写
{
std::cout << "文件在C盘中" << std::endl; //文件在C盘中
}
if (boost::all(str.substr(9, 6), boost::is_lower()))
{
std::cout << "文件名是小写" << std::endl; //文件名是小写
}
if (boost::is_equal()("hello", "hello"))
{
std::cout << "两个字符串相同" << std::endl; ///两个字符串相同
}
if (boost::is_less()("hello", "world"))
{
std::cout << "hello小于world" << std::endl; ///hello小于world
}
if (boost::is_not_greater()("hello", "Hello"))
{
std::cout << "两个字符串具有不大于关系" << std::endl;///两个字符串具有不大于关系
}
注意函数对象名称后的两个括号,第一个括号调用了函数对象的构造函数,产生了一个临时对象,第二个括号才是真正的函数调用操作符operator ( ) 。
string_algo 提供一组分类函数,可以用于检测一个字符是否符合某种特性,主要用于搭配其它算法 ,如字符串判断中的 all 算法。string_algo 库的分类函数有下列数种。
需要注意的是这些函数并不真正地检测字符,而是返回一个类型为 detail::is_classifiedF 的函数对象,这个函数对象的 operator ( )才是真正的分类函数(因此,这些函数都属于工厂函数)。函数对象 is_classifiedF 重载了逻辑运算符||、&&和!,可以使用逻辑运算符把它们组合成逻辑表达式,以实现更复杂的条件判断。
修剪算法可以删除字符串开头或结尾部分的空格:
std::string str2(" hello world | ");
boost::trim_if(str2, boost::is_space() || boost::is_punct());//修建开头、结尾部分的空格、标点符号
std::cout << str2 << std::endl; ///hello world
std::string str3(" hello world | ");
boost::trim(str3);//修建开头、结尾部分的空格
std::cout << str3 << std::endl; ///hello world |
string_algo 提供的查找算法包括下列几种:
std::string str4("D://tal/ABC.txt");
boost::iterator_range range = boost::find_first(str4, "tal");
boost::format fmt("%1%.pos=%2%");
fmt %range % (range.begin() - str4.begin());
std::cout << fmt.str() << std::endl;//tal.pos=4
std::cout << str4.substr(str4.find_last_of('/') + 1, str4.find_last_of('.') - str4.find_last_of('/') - 1) << std::endl;///ABC
替换、删除操作与查找算法非常接近,是在查找到结果后再对字符串进行处理,所以它们的算法名称很相似,具体如下。
std::string str("c://test/readme.json");
boost::replace_first(str, "readme", "test");
std::cout << str << std::endl; ///C://Test/test.json
boost::ierase_first(str, "test");
std::cout << str << std::endl; ///c:///test.json
std::cout << boost::erase_first_copy(str, ".json") << std::endl;///c:///test
boost中find_all因为其功能而被归类为分割算法,但经过我测试,它并不能分割,而是查找所有匹配的字符串并将其加入容器。
split()支持四个参数,最后一个参数token_compress_mode_type eCompress 是个枚举对象,可以取值为token_compress_on(常用,当两个分隔符连续出现时,她们将被视为一个分隔符)、token_compress_off(默认,两个连续的分隔符标记了一个空字符串)
std::string split_str("textLily,Mary,Sarisy,text chinese food");
std::vector name_vector;
boost::find_all(name_vector, split_str, "text");
for (auto a : name_vector)
{
std::cout << a << std::endl;//name_vector:两个text
}
name_vector.clear();
boost::split(name_vector, split_str, boost::is_any_of(","));
for (auto a : name_vector)
{
std::cout << a << std::endl;//name_vector:四个元素:textLily、Mary、Sarisy、text chinese food
}
合并算法 join 是分割算法的逆运算,它把存储在容器中的字符串连接成一个新的字符串,并且可以指定连接的分隔符。join()、join_if()
std::vector join_str{ "I", "am","a","chinese" };
std::string joinStr = boost::join(join_str, " ");
std::cout << joinStr << std::endl;//I am a chinese
除通用的 find_all 或 split 之外, string_algo 库还提供了两个查找迭代器 find_iterator 和
split_iterator ,它们可以在字符串中像迭代器那样遍历匹配,执行查找或分割,无须使用容器来容
纳。这里要特别注意分割迭代器的运用,它可以以任意长度的字符串作为分隔符进行分割,而普通的 split算法则只能以字符作为分隔符。
使用查找迭代器首先要声明迭代器对象find_iterator 或 split_iterator ,它们的模板类型参数是一个迭代器类型,如 string::iterator 或char * 。
为了获得迭代器的起始位置,我们需要先调用first_finder函数,它用于判断匹配的对象,再用 make_find_iterator 或 make_split_iterator 来真正地创建迭代器。同族的查找函数还有last_finder 、 nth_finder 、 token_finder 等,它们的含义与查找算法类似,从不同的位置开始查找返回迭代器。
初始化工作完成后,我们就可以像使用标准迭代器或指针那样,不断地遍历迭代器对象,使用解引用操作符获取查找的内容,直到找不到匹配的对象。
std::string sss = "I||am||a||chinese,I||love||China";
boost::find_iterator pos = boost::make_find_iterator(sss, boost::first_finder("||"));
for (pos; !pos.eof(); ++pos)
{
std::cout << *pos << std::endl;
}
boost::split_iterator pos1, end1;
for (pos1 = boost::make_split_iterator(sss, boost::first_finder("||", boost::is_iequal())); pos1 != end1; ++pos1)
{
std::cout << *pos1 << std::endl;
}
//结果
||
||
||
||
||
I
am
a
chinese,I
love
China
xpressive是一个灵活且功能强大的正则表达式解析库,它可以构建小型语法,同时提供动态方式与静态方式两种用法。借助正则表达式的强大能力,xpressive能够轻松解决文本处理领域绝大多数的问题,它基本可以替代 string_algo 库。
但正则表达式本身是一门复杂的技术,要考虑它的调试和维护成本。解决同样的问题, string_algo 可能速度更快,编写的代码也易于理解,所以只有当它们确实无法解决问题时才应该使用正则表达式。