自学内容网 自学内容网

C++20 STL CookBook 4:使用range在容器中创建view

目录

range view range_adaptor的三个概念

以std::string和std::string_view为例子

初次入手

补充

ranges的一些操作


range view range_adaptor的三个概念

新的范围库是 C++20 中更重要的新增功能之一。它为过滤和处理容器提供了新的范例。范围为更有效和可读的代码提供了简洁直观的构建块。

在range view编程当中,三个概念是vital of importance的:

  • 范围是可以迭代的对象集合。换句话说,任何支持 begin() 和 end() 迭代器的结构都是范围。这包括大多数 STL 容器。所以,毫无疑问的,std::vector是这样的,std::list是这样的,等等!也就是说,那些你一嘴能说出起始终末的容器都行

  • 视图是转换另一个基础范围的范围。视图是惰性的,这意味着它们仅在范围迭代时运行。视图从基础范围返回数据并且本身不拥有任何数据。视图在 O(1) 常数时间内运行。

  • 视图适配器是一个接受范围并返回视图对象的对象。可以使用 | 运算符将视图适配器与其他视图适配器链接起来。

我下面来进一步解释。

以std::string和std::string_view为例子

实际上我现在下一个结论:std::string就是一个range,std::string_view就是一个view,如何?

range实际上就是一个存储着实际对象的一个有限序列(什么是有限序列?很简单!只要你能说出来一串相同的东西,能找到第一个和最后一个的,就是有限序列),那这样看,我们就马上想到:std::string作为一个经典的字符串,实际上就是一个字符数组的高级封装。显然是一个有始有终的range。

我们知道在C++17,为了追求高效,我们引入了std::string_view。什么意思呢?

举个例子:我们阅读一个文件,我们实际上对文件的资源在谁手上完全不关心,我们只知道现在我们可以看到文件中的一串一串难懂的字符(透过库,自己搓的,标准库的接口),比如说一份介绍std::ranges的标准库说明手册。实际上我们不太关心这个资源需要被掌握在显示函数的手里,我们只是看一看,访问资源但不是占有资源。

聪明的同志很快就有想法了:这很简单,对于查看,我在保证字符串有效的情况下,直接偏移指针读到客户感兴趣的地方不就行了?也就是说,准备一个简单的struct:

struct FileBufferViewer{
     const char* index_view; // point to the string we cares
     unsigned long length; // the length user wanna read 
};

现在直接把指针怼到文件一长串字符里就好了!完全不需要我们拷贝这一串字符,然后幸幸苦苦的送到人家面前:“罢了!我不看了”,又灰头灰脸的扔掉!浪费时间!马上有人就知道了,这就是std::string_view的一个简单实现。

现在,View就是这个道理。Range是我们的目标源资源,我们有的时候不掌管资源的所属权,只是来转转看看,或者不骚扰的拷贝一份带走,或者只是远远的可玩不可亵渎。

初次入手

《C++20 STL CookBook》给出了一段例子。笔者这里列写一份

#include <iostream>
#include <vector>
#include <ranges>
​
int main() {
    const std::vector<int> v = {1, 2, 3, 4, 5};
​
    auto result = std::ranges::take_view(v, 3);
    
    for (int i : result) {
        std::cout << i << " ";
    }
​
    return 0;
}

我带着各位分析一次:

首先我们声明了一个非常安全的不动量std::vector<int>,这就是我们的资源池,里面放着5个整数。现在,客户说我要看前三个!

有人就会

std::vector<int> oh_bad_impl;
for(int i = 0; i < 3; i++) oh_bad_impl.push_back(v[i])

但是完全没必要!客户只是看前三个,何必大费周章的拷贝呢?万一下一次人家要的是一个几千万字符的前100万,那有该拷贝到何年何月。

所以一些人选择这种方式,那就是就地的入侵式的查:

#include <iostream>
#include <vector>
#include <ranges>
​
int main() {
    const std::vector<int> v = {1, 2, 3, 4, 5};
​
    const int index req = 3;
    
    for(int i = 0; i < req; i++)
        std::cout << v[i] << std::endl;
​
    return 0;
}

不差!甚至可以说是比较好的实现了。但是现在,我们可以优雅的解决这个问题

#include <iostream>
#include <vector>
#include <ranges>
​
int main() {
    const std::vector<int> v = {1, 2, 3, 4, 5};
​
    auto result = std::ranges::take_view(v, 3);
    
    for (int i : result) {
        std::cout << i << " ";
    }
​
    return 0;
}

好就好在,现在我们使用take_view将我们的查看行为跟资源本身解耦合了,使用的是View这个视窗!客户说:我要前三个:

auto result = std::ranges::take_view(v, 3);

这个result你拿去好了!你访问必是前三个!

客户说我要倒着看!

auto result = std::ranges::reverse_view(v)

客户说不光倒着看,我要倒着看它的平方值!

auto result = std::ranges::reverse_view(std::ranges::transform_view(v, [](int i){return i * i;}));

我承认上面写的太恶心了。所以这就要引出我们的重载的range_view操作符号 |,也叫管道操作符号。

    auto result = v | 
        std::views::reverse | 
        std::views::transform([](int i) {return i * i; });

现在我们就舒服一些了!代码从左往右看:首先是对我们的容器做:反转视图操作,再做平方变换操作。最后得到的view结果就是我们的视图结果。现在我们安心的查看,就是我们的要求!

标准库中有大量的views,这里列一些:

操作版本说明
views::allC++20返回一个视图,表示输入范围的所有元素。
views::emptyC++20返回一个空视图。
views::takeC++20创建一个视图,仅包含输入范围的前 N 个元素。
views::take_whileC++20创建一个视图,直到谓词返回 false 为止,包含输入范围的元素。
views::dropC++20创建一个视图,忽略输入范围的前 N 个元素。
views::drop_whileC++20创建一个视图,忽略满足谓词的元素,直到遇到第一个不满足的元素。
views::filterC++20创建一个视图,仅包含满足谓词的元素。
views::transformC++20创建一个视图,应用给定的转换函数到每个元素。
views::reverseC++20创建一个视图,元素顺序反转。
views::joinC++20将多个范围连接成一个单一的视图。
views::splitC++20将范围分割成多个子范围,基于指定的分隔符。
views::zipC++23创建一个视图,将多个范围“打包”在一起,以便可以同时迭代。
views::iotaC++20创建一个视图,表示从起始值开始的连续递增序列。
views::take_exactlyC++23创建一个视图,包含输入范围中的前 N 个元素,但要求必须恰好有 N 个元素。
views::drop_exactlyC++23创建一个视图,忽略输入范围中的前 N 个元素,但要求必须至少有 N 个元素。
views::slideC++20创建一个滑动窗口视图,允许以特定的步长遍历元素。
views::chunkC++23创建一个视图,将输入范围分成固定大小的块。
views::cartesian_productC++23创建一个视图,表示输入范围的笛卡尔积。
views::transform_viewC++20创建一个基于视图的变换,允许对元素进行转换。
views::filter_viewC++20创建一个基于视图的过滤,允许筛选元素。
views::take_viewC++20创建一个视图,仅包含输入范围的前 N 个元素。
views::drop_viewC++20创建一个视图,忽略输入范围的前 N 个元素。
views::reverse_viewC++20返回输入范围的元素反向视图。
views::transform_viewC++20返回应用某个变换函数的视图。
views::filter_viewC++20过滤出满足某个条件的元素视图。
views::cartesian_productC++23创建一个视图,表示输入范围的笛卡尔积。
views::concatC++20返回一个视图,连接多个范围的元素。
views::subrangeC++20允许对一个范围的子区间进行视图操作。
views::transformC++20返回对每个元素应用指定函数的视图。
views::cycleC++20创建一个循环视图,允许重复迭代给定的范围。

下面可以练习一下:给定两个vector<int>,每一个vector都有10个整数,他们放在一个复杂的叫做std::vector<std::vector<int>>当中,你需要拼接起来 + 平方 + 去除前五个和后五个后筛掉奇数显示出来!

using namespace std::views;

int main() {
    const std::vector<int> v1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    const std::vector<int> v2 = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
    const std::vector<std::vector<int>> v3 = { v1, v2 };
    auto result = v3 | join | 
        drop(5) |  reverse | drop(5) | reverse |
        transform([](int i) {return i * i;}) | 
        filter([](int i) {return i % 2 == 1;});

    for (int i : result) {
        std::cout << i << " ";
    }

    return 0;
}

补充

ranges的一些操作

操作版本说明
ranges::all_ofC++20检查范围内的所有元素是否满足给定的谓词。
ranges::any_ofC++20检查范围内的任一元素是否满足给定的谓词。
ranges::none_ofC++20检查范围内是否没有任何元素满足给定的谓词。
ranges::for_eachC++20对范围内的每个元素应用给定的函数。
ranges::for_each_nC++20对序列中的前 N 个元素应用函数对象。
ranges::countC++20返回范围内满足特定条件的元素数量。
ranges::count_ifC++20返回范围内满足给定谓词的元素数量。
ranges::mismatchC++20找到两个范围内元素不同的第一个位置。
ranges::equalC++20判断两个元素集是否相同。
ranges::lexicographical_compareC++20如果一个范围在字典序上小于另一个范围,则返回 true。
ranges::findC++20查找范围内第一个满足特定条件的元素。
ranges::find_ifC++20查找范围内第一个满足给定谓词的元素。
ranges::find_if_notC++20查找范围内第一个不满足给定谓词的元素。
ranges::find_lastC++23查找范围内最后一个满足特定条件的元素。
ranges::find_last_ifC++23查找范围内最后一个满足给定谓词的元素。
ranges::find_last_if_notC++23查找范围内最后一个不满足给定谓词的元素。
ranges::find_endC++20查找范围内最后一组元素的结束位置。
ranges::find_first_ofC++20查找任何一组元素中的第一个元素。
ranges::adjacent_findC++20查找第一个相邻元素相等的项(或满足给定谓词的项)。
ranges::searchC++20搜索范围内元素的第一次出现。
ranges::search_nC++20搜索范围内某个元素的第一次出现的连续 N 次副本。
ranges::containsC++23检查范围是否包含给定的元素。
ranges::contains_subrangeC++23检查范围是否包含给定的子范围。
ranges::starts_withC++23检查一个范围是否以另一个范围开始。
ranges::ends_withC++23检查一个范围是否以另一个范围结束。
ranges::copyC++20将范围内的元素复制到新位置。
ranges::copy_ifC++20复制范围内满足特定条件的元素到新位置。
ranges::copy_nC++20将数量为 N 的元素复制到新位置。
ranges::copy_backwardC++20以反向顺序复制范围内的元素。
ranges::moveC++20将范围内的元素移动到新位置。
ranges::move_backwardC++20以反向顺序移动范围内的元素。
ranges::fillC++20将范围内的元素赋值为特定值。
ranges::fill_nC++20将数量为 N 的元素赋值为特定值。
ranges::transformC++20对范围内的每个元素应用函数并生成新范围。
ranges::generateC++20在范围内保存函数的结果。
ranges::generate_nC++20保存函数 N 次应用的结果。
ranges::removeC++20移除范围内满足特定条件的元素。
ranges::remove_ifC++20移除范围内所有满足给定谓词的元素。
ranges::remove_copyC++20复制范围元素,省略满足特定条件的元素。
ranges::remove_copy_ifC++20复制范围元素,省略所有满足给定谓词的元素。
ranges::replaceC++20替换范围内所有满足特定条件的值为另一个值。
ranges::replace_ifC++20替换范围内所有满足给定谓词的值为另一个值。
ranges::replace_copyC++20复制范围,并将满足特定条件的元素替换为另一个值。
ranges::replace_copy_ifC++20复制范围,并将满足给定谓词的元素替换为另一个值。
ranges::swap_rangesC++20交换两个范围内的元素。
ranges::reverseC++20反转范围内的元素顺序。
ranges::reverse_copyC++20创建一个反转的范围副本。
ranges::rotateC++20旋转范围内元素的顺序。
ranges::rotate_copyC++20复制并旋转范围内的元素。
ranges::shuffleC++20随机重新排列范围内的元素。
ranges::shift_leftC++23在范围内向左移动元素。
ranges::shift_rightC++23在范围内向右移动元素。
ranges::sampleC++20从序列中随机选择 N 个元素。
ranges::uniqueC++20移除范围内的连续重复元素。
ranges::unique_copyC++20创建一个没有连续重复元素的范围副本。
ranges::is_partitionedC++20确定范围是否按照给定的谓词分区。
ranges::partitionC++20将范围元素分为两个组。
ranges::partition_copyC++20复制范围并将元素分为两个组。
ranges::stable_partitionC++20将元素分为两个组,并保持其相对顺序。
ranges::partition_pointC++20定位分区范围的分割点。
ranges::is_sortedC++20检查范围是否按升序排序。
ranges::is_sorted_untilC++20找到最大的已排序子范围。
ranges::sortC++20将范围按升序排序。
ranges::partial_sortC++20排序范围中的前 N 个元素。
ranges::partial_sort_copyC++20复制并部分排序范围内的元素。
ranges::stable_sortC++20按升序排序范围,并保持相等元素的顺序。
ranges::nth_elementC++20部分排序范围,使其按给定元素进行分区。
ranges::lower_boundC++20返回不小于给定值的第一个元素的迭代器。
ranges::upper_boundC++20返回第一个大于某个值的元素的迭代器。
ranges::binary_searchC++20确定元素是否存在于部分排序的范围内。
ranges::equal_rangeC++20返回匹配特定键的元素范围。
ranges::mergeC++20合并两个已排序的范围。
ranges::inplace_mergeC++20原地合并两个有序范围。
ranges::includesC++20如果一个序列是另一个序列的子序列,则返回 true。
ranges::set_differenceC++20计算两个集合之间的差集。
ranges::set_intersectionC++20计算两个集合之间的交集。
ranges::set_symmetric_differenceC++20计算两个集合之间的对称差集。
ranges::set_unionC++20计算两个集合之间的并集。
ranges::is_heapC++20检查给定范围是否为最大堆。
ranges::is_heap_untilC++20找到最大的堆的子范围。
ranges::make_heapC++20从范围内元素创建最大堆。
ranges::push_heapC++20向最大堆中添加元素。
ranges::pop_heapC++20从最大堆中移除最大的元素。
ranges::sort_heapC++20将最大堆转换为按升序排序的元素范围。
ranges::maxC++20返回给定值中的较大者。
ranges::max_elementC++20返回范围内的最大元素。
ranges::minC++20返回给定值中的较小者。
ranges::min_elementC++20返回范围内的最小元素。
ranges::minmaxC++20返回两个元素中的较小和较大的元素。
ranges::minmax_elementC++20返回范围内的最小和最大元素。
ranges::clampC++20将值限制在一对边界值之间。
ranges::is_permutationC++20确定一个序列是否是另一个序列的排列。
ranges::next_permutationC++20生成范围内元素的下一个更大字典序排列。
ranges::prev_permutationC++20生成范围内元素的下一个更小字典序排列。
ranges::iotaC++23用起始值的连续递增填充范围。
ranges::fold_leftC++23对范围元素进行左折叠。
ranges::fold_left_firstC++23使用第一个元素作为初始值对范围元素进行左折叠。
ranges::fold_rightC++23对范围元素进行右折叠。
ranges::fold_right_lastC++23使用最后一个元素作为初始值对范围元素进行右折叠。
ranges::fold_left_with_iterC++23左折叠范围元素,并返回一个对(迭代器,值)。
ranges::fold_left_first_with_iterC++23使用第一个元素作为初始值进行左折叠,并返回一个对(迭代器,可选)。
ranges::uninitialized_copyC++20将一系列对象复制到未初始化的内存区域。
ranges::uninitialized_copy_nC++20将一定数量的对象复制到未初始化的内存区域。
ranges::uninitialized_fillC++20在未初始化的内存区域复制对象。
ranges::uninitialized_fill_nC++20在未初始化的内存区域复制对象,定义了开始和计数。
ranges::uninitialized_moveC++20将一系列对象移动到未初始化的内存区域。
ranges::uninitialized_move_nC++20将一定数量的对象移动到未初始化的内存区域。
ranges::uninitialized_default_constructC++20在未初始化的内存区域通过默认初始化构造对象。
ranges::uninitialized_default_construct_nC++20在未初始化的内存区域通过默认初始化构造对象,定义了开始和计数。
ranges::uninitialized_value_constructC++20在未初始化的内存区域通过值初始化构造对象。
ranges::uninitialized_value_construct_nC++20在未初始化的内存区域通过值初始化构造对象,定义了开始和计数。
ranges::destroyC++20销毁一系列对象。
ranges::destroy_nC++20销毁范围内的一定数量的对象。
ranges::destroy_atC++20销毁给定地址的对象。
ranges::construct_atC++20在给定地址创建对象。
ranges::generate_randomC++26用均匀随机位生成器填充范围。
ranges::in_fun_resultC++20提供一种方法,将迭代器和函数对象作为一个单元存储。
ranges::in_in_resultC++20提供一种方法,将两个迭代器作为一个单元存储。
ranges::in_out_resultC++20提供一种方法,将两个迭代器作为一个单元存储。
ranges::in_in_out_resultC++20提供一种方法,将三个迭代器作为一个单元存储。
ranges::in_out_out_resultC++20提供一种方法,将三个迭代器作为一个单元存储。
ranges::min_max_resultC++20提供一种方法,将两个相同类型的对象或引用作为一个单元存储。
ranges::in_found_resultC++20提供一种方法,将迭代器和布尔标志作为一个单元存储。
ranges::in_value_resultC++23提供一种方法,将迭代器和一个值作为一个单元存储。
ranges::out_value_resultC++23提供一种方法,将迭代器和一个值作为一个单元存储。

原文地址:https://blog.csdn.net/charlie114514191/article/details/143492981

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!