字符串标准类std::string 有一些成员函数可以查找子串,访问字符,可以执行基本的字符串处理功能。由于std::string符合容器的定义,也可以把它看做是一个元素类型为char的序列容器,可以使用标准算法来对它进行运算,但是标准算法并不是为字符串处理定制的,很多时候会显得有些“笨拙”。
string_algo库时一个非常全面的字符串算法库,提供了大量的字符串操作函数,如大小写无关比较、修剪、特定模式的子串查找等,可以在不使用正则表达式的情况下处理大多数字符串相关问题。
string_algo概述
string_algo 被设计用于处理字符串,然而它的处理对象并不一定是string或者basic_string<T>,可以是任何符boost.range(容器半开区间),容器内的元素也不一定是char或者wchar_t,任何可拷贝构造和赋值的类型均可,但如果类型的拷贝赋值代价很大,那么string_algo的性能也会下降。
当然,我们使用string_algo库主要的工作对象还是字符串string和wstring,它也可以工作在vector,deque,list和string_ref上。
string_algo库中的算法命名遵循了标准库的惯例,算法名均为小写形式,并使用不同的词缀来区分不同的版本,命名规则是:
- 前缀i : 大小写不敏感(忽略大小写),否则是大小写敏感的。
- 后缀_copy : 不变动输入,返回处理结果的拷贝,否则原地处理,输入即输出。
- 后缀_if : 需要一个作为判断的谓词函数对象,否则使用默认的判断准则。
string_algo 库提供的算法分为5大类:
- 大小写转换:
- 判断式与分类:
- 修剪:
- 查找与替换:
- 分割与合并。
接下来将对这些算法介绍(但不包含regex库相关的算法)
大小写转换
string_algo库可以高效地实现字符串的大小写转换,包括两组算法:to_upper()和to_lower().
这两个算法的声明是:
template<typename T> void to_upper(T & Input);
template<typename T> void to_lower(T & Input);
(由于附加了前缀和后缀的算法的声明形式和原地处理的算法区别不大,此处不再列出)
判断式(算法)
判断式算法可以检测两个字符串之间的关系,包括以下6种:
- lexicographical_compare: 根据字典顺序检测一个字符串是否小于另一个。
- starts_with : 检测一个字符串是否以另一个为前缀。
- ends_with : 检测一个字符串是否以另一个为后缀。
- contains : 检测一个字符串是否包含另一个。
- equals : 检测两个字符串是否相等
- all : 检测一个字符串中的所有元素是否满足制定的判断式。
判断式(函数对象)
string_algo 增强了标准库中equal_to 和less函数对象,允许对不同类型的参数进行比较,并提供大小写无关的形式。
- is_equal : 类似equals算法,比较两个对象是否相等
- is_less : 比较两个对象是否具有小于关系
- is_not_greater : 比较两个对象是否具有不大于关系
分类
string_algo 提供了一组分类函数,可以用于检测一个字符是否符合某特性,主要用于搭配其他算法。
- is_space : 字符是否为空格或者制表符(tab)。
- is_alnum : 字符是否为字母或者数字字符。
- is_alpha : 字符是否为字母。
- is_cntrl : 字符是否为控制字符。
- is_digit : 字符是否为十进制数字。
- is_graph : 字符是否为图形字符。
- is_lower : 字符是否为小写字符
- is_print : 字符是否为可打印字符
- is_punct : 字符是否为标点符号字符
- is_upper : 字符是否为大写字符
- is_xdigit: : 字符是否为十六进制数字
- is_any_of : 字符是否是参数字符序列中的任意字符
- if_from_ange: 字符是否位于制定区间内。即from<=ch <=to.
修剪
string_algo提供三个修剪算法:trim_left,trim_right和trim。 修剪算法可以删除字符串开头或者结尾部分的空格,它有_if和—copy两种后缀,因此每个算法都有四个版本。_if版本接受一个判断式IsSpace,将所有被判定为空格的字符删除。
template<typename SequenceT>
void trim_left(SequenceT & Input);
template<typename SequenceT>
void trim_right(SequenceT & Input);
template<typename SequenceT>
void trim(SequenceT & Input);
查找
string_algo的查找算法提供与std::search()类似的功能,但接口不太一样。它不是返回一个迭代器(查找到的位置),而使用了boost.range库中的iterator_range返回查找到的整个区间,获得了更多的信息,便于算法串联和其他处理。
简单地介绍一个iterator_range。它概念上类似std::pair<iterator,iterator>,包装了两个迭代器,可以用begin()和end()访问,相当于定义了一个容器的子区间,并可以像原容器一样使用
- find_first : 查找字符串在输入中第一次出现的位置
- find_last : 查找字符串在输入中最后一次出现的位置
- find_nth : 查找字符串在输入中的第N次(从0开始计数)出现的位置。
- find_head: 取一个字符串开头N个字符的子串,相当于substr(0,n)
- find_tail : 取一个字符串末尾N个字符的子串
替换与删除
替换、删除操作与查找算法非常接近,是在查找到结果后再对字符串进行处理,因此它们的算法名称很相似。
- replace /erase_first : 替换/删除一个字符串在输入中第一次出现。
- replace/erase_last : 替换/删除一个字符串在输入中的最后一个出现。
- replace/erase_nth : 替换/删除一个字符串在输入中的第N次出现
- replace/erase_all : 替换/删除一个字符串在输入中的所有出现
- replace/erase_head : 替换/删除输入的开头
- replace/erase_tail : 替换/删除输入的末尾
分割与合并
string_algo提供了两个字符串分给算法:find_all和split。可以使用某种策略把字符串分割成若干部分,并将分割后的字符串拷贝存入指定的容器。
分割算法对容器类型的要求必须是能够持有查找结果的拷贝或者引用,因此容器的元素类型必须是string或者itreator_range<string::iterator>.容器则可以是vector,list,deque等标准容器。
合并算法join是分割算法的逆运算,它把存储在容器中的字符串连城一个新的字符串,并且可以指定连接的分隔符。
查找(分割)迭代器
在通用的find_all或者split之外,string_algo库提供两个查找迭代器find_iterator和split_iterator,它们可以在字符串中像迭代器那样遍历匹配,执行查找或者分割,无需使用容器来容纳。
#pragma once
#include "std.hpp"
using namespace std;
#include <boost\utility\string_ref.hpp>
#include <boost\algorithm\string.hpp>
using namespace boost;
void string_algo_case1()
{
string str("readme.txt");
if(algorithm::ends_with(str,".txt"))
{
cout << algorithm::to_upper_copy(str) + " UPPER" << endl;
assert(ends_with(str, "txt"));
}
algorithm::replace_first(str, "readme", "followme");
cout << str << endl;
std::vector<char> v(str.begin(), str.end());
std::vector<char> v2 = boost::algorithm::to_upper_copy(
erase_first_copy(v, "text"));
for (auto ch : v2)
cout << ch;
std::cout << endl;
}
void string_algo_case2()
{
std::string str("I Don't Know.\n");
std::cout << algorithm::to_upper_copy(str) << endl;
cout << str << endl;
algorithm::to_lower(str);
cout << str << endl;
}
void string_algo_case3()
{
std::string str("Power Bomb");
assert(algorithm::iends_with(str, "bomb"));
assert(!algorithm::ends_with(str, "bomb"));
assert(algorithm::starts_with(str,"Pow"));
assert(algorithm::contains(str, "er"));
string str2 = to_lower_copy(str);
assert(algorithm::iequals(str, str2));
string str3("power suit");
assert(algorithm::ilexicographical_compare(str, str3));
assert(algorithm::all(str2.substr(0, 5), is_lower()));
}
void string_algo_case4()
{
string str1("Samus"), str2("samus");
assert(!is_equal()(str1, str2));
assert(is_less()(str1, str2));
assert(!is_equal()(str1, string_ref(str2)));
}
#include <boost/format.hpp>
void string_algo_case5()
{
boost::basic_format<char> fmt("|%s|\n");
string str = " samus aran ";
cout << fmt % trim_copy(str);
cout << fmt % trim_left_copy(str);
trim_right(str);
cout << str << endl;
string str2 = "2017 Happy new Year!!!";
cout << fmt % trim_left_copy_if(str2, is_digit());
cout << fmt % trim_right_copy_if(str2, is_punct());
cout << fmt % trim_copy_if(str2,
is_punct() || is_digit() || is_space());
}
void string_algo_case6()
{
format fmt("|%s|. pos = %d\n");
string str = "Long long ago, there was a king.";
iterator_range<string::iterator> rge;
rge = find_first(str, "long");
cout << fmt % rge % (rge.begin() - str.begin());
rge = ifind_first(str, "long");
cout << fmt % rge % (rge.begin() - str.begin());
rge = find_nth(str, "ng", 2);
cout << fmt % rge % (rge.begin() - str.begin());
rge = find_head(str, 4);
cout << fmt % rge % (rge.begin() - str.begin());
rge = find_tail(str, 5);
cout << fmt % rge % (rge.begin() - str.begin());
rge = find_first(str, "samus");
assert(rge.empty() && !rge);
}
void string_algo_case7()
{
string str = "Samus beat the monster.\n";
cout << replace_first_copy(str, "Samus", "samus");
replace_last(str, "beat", "kill");
cout << str;
replace_tail(str, 9, "ridley.\n");
cout << str;
cout << ierase_all_copy(str, "samus");
cout << replace_nth_copy(str, "l", 1, "L");
cout << erase_tail_copy(str, 8);
}
void string_algo_case8()
{
string str = "Samus,Link.Zelda::Mario-Luigi+zelda";
std::deque<string> d;
ifind_all(d, str, "zELDA");
assert(d.size() == 2);
for (auto x : d)
cout << "[" << x << "]";
cout << endl;
list<iterator_range<string::iterator> > l;
split(l, str, is_any_of(",.:-+"));
for (auto x : l)
{
cout << "[" << x << "]";
}
cout << endl;
l.clear();
split(l, str, is_any_of(".:-"), token_compress_on);
for (auto x : l)
{
cout << "[" << x << "]";
}
cout << endl;
}
void string_algo_case9()
{
using namespace boost::assign;
vector<string> v = list_of("Samus")("Link")("Zelda")("Mario");
cout << join(v, "+") << endl;
cout << join_if(v, "**",
[](string_ref s)
{ return contains(s, "a"); }
);
cout << endl;
}
void string_algo_case10()
{
string str("Samus||samus||mario||||Link");
auto pos = make_find_iterator(str, first_finder("samus", is_iequal()));
decltype(pos) end;
for (; pos != end; ++pos)
{
cout << "[" << *pos << "]";
}
cout << endl;
typedef split_iterator<string::iterator> string_split_iterator;
string_split_iterator p, endp;
for (p = make_split_iterator(str, first_finder("||", is_iequal()));
p != endp; ++p)
{
cout << "[" << *p << "]";
}
cout << endl;
}
void test_string_algo()
{
string_algo_case1();
string_algo_case2();
string_algo_case3();
string_algo_case4();
string_algo_case5();
string_algo_case6();
string_algo_case7();
string_algo_case8();
string_algo_case9();
string_algo_case10();
}