C++20视图(ranges)探究

出现的原因

我们在容器进行数据的处理时,不免的会修改容器本身,此时我们要常常创建一个原容器一摸一样的拷贝来维护原先的状态,这样不仅浪费了空间,并且会引起代码的不直观。因此范围库的引入为过滤和处理容器提供了一种新范例。

下方解释了几个术语:

  1. "范围"是一个可以迭代的对象的集合,支持begin()和end()迭代器的结构都是范围。这包括大多数STL容器。

  1. "视图"是转换另一个基础范围的范围。视图是惰性的,只在范围迭代时操作。视图从底层范围返回数据,不拥有任何数据。视图的运行时间复杂度是O(1)。

  1. 视图适配器是一个对象,可接受一个范围,并返回一个视图对象。视图适配器可以使用|操作符连接到其他视图适配器。

几个简单例子

范围和视图类在头文件中,中定义了std::ranges和std::ranges::views命名空间。

  1. 将视图应用于范围

const vector nums{1,2,3,4,5,6,7,8,9,10};
auto result=std::ranges::take_view(nums,5);
for(auto it:result)cout<

输出为:

1 2 3 4 5

std::ranges::take_view(range,n):返回前n个元素的视图

视图适配器版本的take_view():

auto result=nums|views::take(5);
for(auto it:result)cout<

输出为:

1 2 3 4 5

视图适配器位于std::ranges::views命名空间中。视图适配器从|操作符的左侧获得范围操作数。|操作符会从左到右求值。

  1. 因为视图适配器可迭代,所以也有资格作为范围。这使得他们可以连续使用:

const vector nums{1,2,3,4,5,6,7,8,9,10};
auto result =nums|views::take(5)::views::reverse;

输出为:

5 4 3 2 1

  1. filter()视图使用一个谓词函数:

auto result =nums|views::filter([](int i){return 0==i%2;});

输出为:

2 4 6 8 10

  1. transform()视图使用了一个转换函数:

auto result =nums|views::transform([](int&i){return i*=i;});

输出为:

1 4 9 16 25 36 49 64 81 100

  1. 视图和适配器可以使用于任何范围:

const vector words{"one","two","three","four","five"};
auto result=words|views::reverse;

输出为:

five four three two one

  1. 范围库还包括一些factory函数。itoa将生成一系列递增的值:

auto rnums =view::iota(1,10);

输出为:

1 2 3 4 5 6 7 8 9

视图的工作原理

为了满足范围的基本要求,对象必须至少有两个迭代器begin()和end()。大部分STL都范围的要求,包括vector,string,array,map等。不过容器适配器除外,如stack和queue等,因为他们没有起始迭代器和结束迭代器。

视图是一个对象,操作一个范围并且返回一个修改后的范围。视图为惰性操作的,不包含自己的数据。不保留底层数据的副本,只是根据需要返回底层元素的迭代器。

vectorvi{0,1,2,3,4,5};
std::ranges::take_view tv{vi,2};
for(int i:tv)cout<

输出为:

0 1

本例中,take_view对象接受两个参数,一个范围和一个计数,结果是一个包含vector中第一个count对象的视图。在for循环中的迭代过程中,take_view对象会根据对象需要返回指向vector对象的元素的迭代器。

在此过程中不修改vector对象。

范围命名空间中的许多视图在视图命名中间中都有相应的范围适配器,这些适配器可以使用按位或(|)作为管道。

vector vi{0,1,2,3,4,5,6,7,8,9};
auto tview =vi|views::take(2);
for(int i:tview)cout<

输出为:

0 1

|操作符从左到右求值。因为范围是适配器的结果是另一个范围,所以这些适配器表达式可以连接起来。

vectorvi{0,1,2,3,4,5,6,7,8,9};
auto tview =vi|views::reverse|views::take(5);
for(int i:tview)cout<

输出为:

9 8 7 6 5

标准库包括一个过滤视图,用于定义简单的过滤器:

vectorvi{0,1,2,3,4,5,6,7,8,9};
auto even =[](int i){return 0==i%2;};
auto tview =vi|views::filter(even);
for(int it:tview)cout<

输出为:

0 2 4 6 8

库中有相当多的有用的视图和视图适配器。可访问(https://j.bw.org/ranges)获取完整的列表。

还有一些内容

从C++20开始头文件中的大多数算法都会基于范围。这些版本在头文件中,但是在std::ranges命名空间中,这将他们与传统算法分离开。所以无需再调用两个迭代器的算法

sort(v.begin(),v.end());

现在可以用范围来调用

std::ranges::sort(v);

这当然更方便,但它真正的意义在哪里呢?

当对vector一部分排序时的情况。可以用老方法做:

sort(v.begin()+5,v.end());

这将对vector的前5个元素进行排序。范围版本中,可以使用视图来跳过前5个元素。

std::ranges::sort(std::ranges::views::drop(v,5));

甚至可以组合视图:

std::ranges::sort(std::ranges::views::drop(std::ranges::views::reverse(v),5));

也可以使用范围迭代器作为std::ranges::sort的参数:

std::ranges::sort(v|std::ranges::view::reverse|std::ranges::views::drop(5));

用传统的排序算法和vector迭代器完成:

std::ranges::sort(v.begin()+5,v.end());

可以在cppreference(https://j.bw.org/algoranges)上找到限制使用范围的完整算法列表.

你可能感兴趣的:(现代C++探索,c++)