区间range库

来源:互联网 发布:手机捕鱼游戏源码 编辑:程序博客网 时间:2024/06/07 07:20

《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~2auto r2 = make_iterator_range(v.begin() + 5,v.end()); //子区间5~9auto r3 = boost::join(r1,r2);//输出0,1,2,5,6,7,8,9boost::copy(r3,ostream_iterator<int>(cout,","));
原创粉丝点击