《C++11/14高级编程:Boost程序库探秘》笔记
range库在迭代器和容器上抽象出了“区间”的概念,基于迭代器和容器,但要求比容器低很多,不需要容纳元素,只含有区间的两个首末端点位置。vector是区间,string是区间,但stack不是区间,一个pair也是区间,数组也是区间,用元函数has_range_iterator可以判断一个类型是否是区间。
区间都是左闭右开的,可以用成员函数begin()/end()或者自由函数begin()/end()获得其两个端点。
range库的特征元函数
- range_iterator<R> :返回区间的迭代器类型
- range_value<R> :返回区间的值类型
- range_reference<R> :返回区间的引用类型
- range_pointer<R> :返回区间的指针类型
- range_category<R> :返回区间的迭代器分类
- range_size<R> :返回区间的长度类型(无符号整数)
- range_difference<R> :返回区间的距离类型(有符号整数)
- range_reverse_iterator<R> :返回区间的逆向迭代器(仅对双向区间)
range库的操作函数
- begin() :返回区间的起点
- end() :返回区间的终点
- rbegin() :返回逆向区间的起点(双向区间)
- rend() :返回逆向区间的终点(双向区间)
- empty() :判断区间是否为空
- distance() :返回区间两端的距离
- size() :返回区间的大小
begin()和end()对于没有begin()和end()成员函数的符合区间概念的类提供了方便的操作,比如说数组。这两个函数已经被收入了C++11标准。
标准算法
range库在名字空间boost里提供了所有标准算法的区间版本,分成三个头文件:
- <boost/range/algorithm.hpp> :C++中所有标准算法
- <boost/range/algorithm_ext.hpp> :一些扩展算法,如erase、itoa
- <boost/range/numeric.hpp> :数值标准算法
这些区间算法的参数除了把两个迭代器改为一个区间外,与标准算法基本相同,只是减少了写出两个迭代器位置的代码,更加简单:
std::vector<int> v{8,16,1,3,7,3,42}
assert(boost::count(v,3) == 2); //统计元素数量
//实际调用标准算法std::count(boost::begin(v),boost::end(v),3)
assert(boost::find(v,7) != v.end()); //查找元素
boost::sort(v);
基本上这些区间算法根据返回类型分为两类,第一类算法返回原区间,一般都是变动性算法,处理整个区间,然后再返回原区间,返回值继续能够直接被其他算法使用。
std::vector<int> v(10);
boost::rand48 rnd;
boost::sort( //3.排序
boost::random_shuffle( //2.随机打乱
boost::generate(v,rnd))); //1.用随机数填充区间
(变动性算法包括以下几种:fill/fill_n、generate/generate_n、inplace_merge、random_shuffle、replace/replace_if、reverse/rotate、sort/stable_sort、partial_sort/nth_element、make_heap/sort_heap/push_heap/pop_heap)
第二类算法使用一个模板参数定制返回的区间,一般都与某种形式的查找/分割操作有关。这些算法的普通版本返回一个处理后的迭代器位置(如find算法),我们可以将这个迭代器位置成为found,依据found位置就可以得到两个常用的子区间:[begin(rng),found]和[found,end(rng)]。除了found位置,range库还定义了next(found)和prior(found)位置,和起点终点一起分割成四个部分。
range库在boost名字空间还声明了一个枚举类型range_return_value,作为区间算法的模板参数,为元计算定制返回的区间类型:
enum range_return_value
{
return_found,
return_next,
return_prior,
return_begin_found,
return_begin_next,
return_begin_prior,
return_found_end,
return_next_end,
return_prior_end,
return_begin_end
};
迭代器区间类
区间的概念本质上就是一对迭代器,可以用std::pair<I,I>表示,但是std::pair<I,I>又过于简单,不能明确地表述出区间的含义,所以range库提供一个专门的区间类:iterator_range,它封装了两个迭代器,接口更标准化,而且轻量级。
std::vector<int> v(10);
typedef iterator_range<vector<int>::iterator> vec_range; //区间类型定义
vec_range r1(v); //从容器构造一个区间
assert(!r1.empty());
assert(r1.size() == 10);
int a[10];
typedef iterator_range<int*> int_range;
int_range r2(a,a+5); //从两个迭代器构造区间
assert(r2.size() == 5);
range库提供工厂函数make_iterator_range(),搭配关键字auto使用,可以省去创建iterator_range时需要手动指定迭代器类型的麻烦:
std::vector<int> v(10);
auto r1 = make_iterator_range(v); //从区间构造
assert(has_range_iterator<decltype(r1)>::value);
int a[10];
auto r2 = make_iterator_range(a,a+5); //从迭代器构造
auto r3 = make_iterator_range(a,1,-1); //从指定迭代器的位置
assert(r3.size() == 8)
range库中还有一个模板函数copy_range,可以把区间里的元素拷贝到一个新的容器里:
char a[] = "iterator range";
auto r = boost::find_first(a," "); //使用find_first字符串算法
assert(r.front() == ' '); //返回查找到的区间
auto r2 = make_iterator_range(a,r.begin()); //取字符串的前半部分
assert(copy_range<string>(r2) == "iterator");
range库辅助工具
介绍了很多辅助工具(函数),但是get不到用途精髓,而且大部分与迭代器的工具很像。
- sub_range
是iterator_range的一个子类,目的在于表示一个子区间,和iterator_range有区别,sub_range的模板参数是一个前向区间类型,iterator_range是一个迭代器类型。sub_range有iterator_range全部功能 - counting_range
与counting_iterator相似,返回一个计数迭代器的区间 - istream_range
封装了输入流迭代器std::istream_iterator,把输入流的输入过程转化成一个区间 - irange
类似counting_range,多了指定步长的功能 - combined_range
利用zip_iterator,把多个区间“打包”为一个区间,相当于zip_iterator的区间强化版本。 - any_range
使用类型擦除技术,可以应用在符合要求的“任意”容器上。这个有点有趣,就举个例子讲讲。
它的一个类摘要如下:
template<
class Vaule, //值类型
class Traversal, //迭代器便利类型
class Reference = Value&, //引用类型
class Difference = std::ptrdiff_t,
class Buffer = use_default
>
class any_range: public iterator_range<...>
{...};
前面两个模板参数分别确定了区间的值类型和遍历类型,不需要关心区间下的容器的具体类型,只要这个容器满足Value和Traversal的要求即可,比如说一个单遍整数的any_range区间,可以任意处理list、vector甚至是set等容器。
typedef any_range<int, //区间的值类型是int
boost::single_pass_traversal_tag> range_type; //区间要求可单向遍历
list<int> l = {1,3,5,7,9};
range_type r(l);
for(const auto& x:r)
{ cout << x << ","; }
vector<int> v = {2,4,6,8,0};
r = v;
for(const auto& x:r)
{ cout << x << ","; }
range库还提供了一个工厂元函数,注意是元函数,它可以由一个区间或容器类型产生合适的any_range:
template<
class WrappedRange, //区间要包装的类型
class Value = use_default, //值类型
class Traversal = use_default, //迭代器遍历类型
class Reference = use_default, //引用类型
class Difference = use_default,
class Buffer = use_default
>
struct any_range_type_generator
{
typedef any_range<...> type; //元计算返回any_range
};
any_range_type_generator只需要提供第一个模板参数区间或容器类型,其余值会自动推导,最后使用::type得到any_range类型,如:
typedef any_range_type_generator<decltype(l)>::type range_type
但any_range_type_generator由于需要提供区间或容器类型,降低了any_range的适配性,导致只能用在特定Traversal属性的容器上,如上面使用list类型产生的any_range,就不能用到vector上了。
区间适配器
把一个区间适配成另一个区间。
同迭代器类似,区间适配器使用boost::copy来驱动数据通过区间和迭代器流动,而由于区间的自表达特性,它还重载了operator| (),支持类似UNIX管道操作符的形式连接多个区间,每个区间执行一定的工作。
range库区间适配器位于名字空间boost::adaptors,需要头文件<boost/range/adaptos.hpp>
range库提供的区间适配器包含以下几种:
- adjacent_filtered(P):使用谓词P过滤两个邻接的元素
- copied(n,m) :取子区间[n,m),只能用于随机访问区间
- filtered(P):使用谓词P过滤元素
- indexed(i):为迭代器添加一个从i开始的索引号
- map_keys:取出map容器里的key
- map_values:取出map容器里的value
- replaced(x,y):把区间里的x替换为y
- replaced_if(P,v):把区间里符合谓词P的元素替换为v
- reversed:逆序区间
- sliced(n,m):同copied
- strided(n):在区间上以步长n跳跃前进
- tokenized():使用boost::regex正则处理
- transformed(F):类似于std::transform算法,用F处理每一个元素
- uniqued:过滤掉相邻重复的元素
使用时,把原始区间想象成一个数据源,经过适配器后得到处理的数据,最后由算法来处理这些数据,有点类似流处理:
std::vector<int> v{7,8,4,6,53,2,6};
boost::copy( //copy算法
boost::sort(v) | //先排序,区间改变
adaptors::uniqued, //去重,原区间不变
ostream_iterator<int>(std::cout,",")); //去重后数据输出到标准流
assert(boost::count(v,6) == 2); //原区间排序但未去重
auto even = [](int x){ return x%2 == 0; };
assert(boost::distance( //计算区间长度
v | adaptors::filtered(even)) == 5); //仅计算偶数
可以灵活组合区间算法和区间适配器,但要注意,有些组合方式不可取,比如v | filtered | copied,i那位filtered返回的是双向区间——即使原始区间是随机访问区间,而copied和sliced都要求是随机访问区间,强行搭配会引发编译错误。
连接区间
range库里还有一个比较常用的函数join(),它可以把两个区间连接为一个区间并不要求两个区间是相邻的。
std::vector<int> v;
boost::copy(boost::irange(0,10),std::back_inserter(v));
auto r1 = make_iterator_range(v.begin(),v.begin() + 3); //子区间0~2
auto r2 = make_iterator_range(v.begin() + 5,v.end()); //子区间5~9
auto r3 = boost::join(r1,r2);
//输出0,1,2,5,6,7,8,9
boost::copy(r3,ostream_iterator<int>(cout,","));