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 
#include 
#include 
​
int main() {
    const std::vector 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,这就是我们的资源池,里面放着5个整数。现在,客户说我要看前三个!

有人就会

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

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

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

#include 
#include 
#include 
​
int main() {
    const std::vector 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 
#include 
#include 
​
int main() {
    const std::vector 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::all C++20 返回一个视图,表示输入范围的所有元素。
views::empty C++20 返回一个空视图。
views::take C++20 创建一个视图,仅包含输入范围的前 N 个元素。
views::take_while C++20 创建一个视图,直到谓词返回 false 为止,包含输入范围的元素。
views::drop C++20 创建一个视图,忽略输入范围的前 N 个元素。
views::drop_while C++20 创建一个视图,忽略满足谓词的元素,直到遇到第一个不满足的元素。
views::filter C++20 创建一个视图,仅包含满足谓词的元素。
views::transform C++20 创建一个视图,应用给定的转换函数到每个元素。
views::reverse C++20 创建一个视图,元素顺序反转。
views::join C++20 将多个范围连接成一个单一的视图。
views::split C++20 将范围分割成多个子范围,基于指定的分隔符。
views::zip C++23 创建一个视图,将多个范围“打包”在一起,以便可以同时迭代。
views::iota C++20 创建一个视图,表示从起始值开始的连续递增序列。
views::take_exactly C++23 创建一个视图,包含输入范围中的前 N 个元素,但要求必须恰好有 N 个元素。
views::drop_exactly C++23 创建一个视图,忽略输入范围中的前 N 个元素,但要求必须至少有 N 个元素。
views::slide C++20 创建一个滑动窗口视图,允许以特定的步长遍历元素。
views::chunk C++23 创建一个视图,将输入范围分成固定大小的块。
views::cartesian_product C++23 创建一个视图,表示输入范围的笛卡尔积。
views::transform_view C++20 创建一个基于视图的变换,允许对元素进行转换。
views::filter_view C++20 创建一个基于视图的过滤,允许筛选元素。
views::take_view C++20 创建一个视图,仅包含输入范围的前 N 个元素。
views::drop_view C++20 创建一个视图,忽略输入范围的前 N 个元素。
views::reverse_view C++20 返回输入范围的元素反向视图。
views::transform_view C++20 返回应用某个变换函数的视图。
views::filter_view C++20 过滤出满足某个条件的元素视图。
views::cartesian_product C++23 创建一个视图,表示输入范围的笛卡尔积。
views::concat C++20 返回一个视图,连接多个范围的元素。
views::subrange C++20 允许对一个范围的子区间进行视图操作。
views::transform C++20 返回对每个元素应用指定函数的视图。
views::cycle C++20 创建一个循环视图,允许重复迭代给定的范围。

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

using namespace std::views;

int main() {
    const std::vector v1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    const std::vector v2 = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
    const std::vector> 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_of C++20 检查范围内的所有元素是否满足给定的谓词。
ranges::any_of C++20 检查范围内的任一元素是否满足给定的谓词。
ranges::none_of C++20 检查范围内是否没有任何元素满足给定的谓词。
ranges::for_each C++20 对范围内的每个元素应用给定的函数。
ranges::for_each_n C++20 对序列中的前 N 个元素应用函数对象。
ranges::count C++20 返回范围内满足特定条件的元素数量。
ranges::count_if C++20 返回范围内满足给定谓词的元素数量。
ranges::mismatch C++20 找到两个范围内元素不同的第一个位置。
ranges::equal C++20 判断两个元素集是否相同。
ranges::lexicographical_compare C++20 如果一个范围在字典序上小于另一个范围,则返回 true。
ranges::find C++20 查找范围内第一个满足特定条件的元素。
ranges::find_if C++20 查找范围内第一个满足给定谓词的元素。
ranges::find_if_not C++20 查找范围内第一个不满足给定谓词的元素。
ranges::find_last C++23 查找范围内最后一个满足特定条件的元素。
ranges::find_last_if C++23 查找范围内最后一个满足给定谓词的元素。
ranges::find_last_if_not C++23 查找范围内最后一个不满足给定谓词的元素。
ranges::find_end C++20 查找范围内最后一组元素的结束位置。
ranges::find_first_of C++20 查找任何一组元素中的第一个元素。
ranges::adjacent_find C++20 查找第一个相邻元素相等的项(或满足给定谓词的项)。
ranges::search C++20 搜索范围内元素的第一次出现。
ranges::search_n C++20 搜索范围内某个元素的第一次出现的连续 N 次副本。
ranges::contains C++23 检查范围是否包含给定的元素。
ranges::contains_subrange C++23 检查范围是否包含给定的子范围。
ranges::starts_with C++23 检查一个范围是否以另一个范围开始。
ranges::ends_with C++23 检查一个范围是否以另一个范围结束。
ranges::copy C++20 将范围内的元素复制到新位置。
ranges::copy_if C++20 复制范围内满足特定条件的元素到新位置。
ranges::copy_n C++20 将数量为 N 的元素复制到新位置。
ranges::copy_backward C++20 以反向顺序复制范围内的元素。
ranges::move C++20 将范围内的元素移动到新位置。
ranges::move_backward C++20 以反向顺序移动范围内的元素。
ranges::fill C++20 将范围内的元素赋值为特定值。
ranges::fill_n C++20 将数量为 N 的元素赋值为特定值。
ranges::transform C++20 对范围内的每个元素应用函数并生成新范围。
ranges::generate C++20 在范围内保存函数的结果。
ranges::generate_n C++20 保存函数 N 次应用的结果。
ranges::remove C++20 移除范围内满足特定条件的元素。
ranges::remove_if C++20 移除范围内所有满足给定谓词的元素。
ranges::remove_copy C++20 复制范围元素,省略满足特定条件的元素。
ranges::remove_copy_if C++20 复制范围元素,省略所有满足给定谓词的元素。
ranges::replace C++20 替换范围内所有满足特定条件的值为另一个值。
ranges::replace_if C++20 替换范围内所有满足给定谓词的值为另一个值。
ranges::replace_copy C++20 复制范围,并将满足特定条件的元素替换为另一个值。
ranges::replace_copy_if C++20 复制范围,并将满足给定谓词的元素替换为另一个值。
ranges::swap_ranges C++20 交换两个范围内的元素。
ranges::reverse C++20 反转范围内的元素顺序。
ranges::reverse_copy C++20 创建一个反转的范围副本。
ranges::rotate C++20 旋转范围内元素的顺序。
ranges::rotate_copy C++20 复制并旋转范围内的元素。
ranges::shuffle C++20 随机重新排列范围内的元素。
ranges::shift_left C++23 在范围内向左移动元素。
ranges::shift_right C++23 在范围内向右移动元素。
ranges::sample C++20 从序列中随机选择 N 个元素。
ranges::unique C++20 移除范围内的连续重复元素。
ranges::unique_copy C++20 创建一个没有连续重复元素的范围副本。
ranges::is_partitioned C++20 确定范围是否按照给定的谓词分区。
ranges::partition C++20 将范围元素分为两个组。
ranges::partition_copy C++20 复制范围并将元素分为两个组。
ranges::stable_partition C++20 将元素分为两个组,并保持其相对顺序。
ranges::partition_point C++20 定位分区范围的分割点。
ranges::is_sorted C++20 检查范围是否按升序排序。
ranges::is_sorted_until C++20 找到最大的已排序子范围。
ranges::sort C++20 将范围按升序排序。
ranges::partial_sort C++20 排序范围中的前 N 个元素。
ranges::partial_sort_copy C++20 复制并部分排序范围内的元素。
ranges::stable_sort C++20 按升序排序范围,并保持相等元素的顺序。
ranges::nth_element C++20 部分排序范围,使其按给定元素进行分区。
ranges::lower_bound C++20 返回不小于给定值的第一个元素的迭代器。
ranges::upper_bound C++20 返回第一个大于某个值的元素的迭代器。
ranges::binary_search C++20 确定元素是否存在于部分排序的范围内。
ranges::equal_range C++20 返回匹配特定键的元素范围。
ranges::merge C++20 合并两个已排序的范围。
ranges::inplace_merge C++20 原地合并两个有序范围。
ranges::includes C++20 如果一个序列是另一个序列的子序列,则返回 true。
ranges::set_difference C++20 计算两个集合之间的差集。
ranges::set_intersection C++20 计算两个集合之间的交集。
ranges::set_symmetric_difference C++20 计算两个集合之间的对称差集。
ranges::set_union C++20 计算两个集合之间的并集。
ranges::is_heap C++20 检查给定范围是否为最大堆。
ranges::is_heap_until C++20 找到最大的堆的子范围。
ranges::make_heap C++20 从范围内元素创建最大堆。
ranges::push_heap C++20 向最大堆中添加元素。
ranges::pop_heap C++20 从最大堆中移除最大的元素。
ranges::sort_heap C++20 将最大堆转换为按升序排序的元素范围。
ranges::max C++20 返回给定值中的较大者。
ranges::max_element C++20 返回范围内的最大元素。
ranges::min C++20 返回给定值中的较小者。
ranges::min_element C++20 返回范围内的最小元素。
ranges::minmax C++20 返回两个元素中的较小和较大的元素。
ranges::minmax_element C++20 返回范围内的最小和最大元素。
ranges::clamp C++20 将值限制在一对边界值之间。
ranges::is_permutation C++20 确定一个序列是否是另一个序列的排列。
ranges::next_permutation C++20 生成范围内元素的下一个更大字典序排列。
ranges::prev_permutation C++20 生成范围内元素的下一个更小字典序排列。
ranges::iota C++23 用起始值的连续递增填充范围。
ranges::fold_left C++23 对范围元素进行左折叠。
ranges::fold_left_first C++23 使用第一个元素作为初始值对范围元素进行左折叠。
ranges::fold_right C++23 对范围元素进行右折叠。
ranges::fold_right_last C++23 使用最后一个元素作为初始值对范围元素进行右折叠。
ranges::fold_left_with_iter C++23 左折叠范围元素,并返回一个对(迭代器,值)。
ranges::fold_left_first_with_iter C++23 使用第一个元素作为初始值进行左折叠,并返回一个对(迭代器,可选)。
ranges::uninitialized_copy C++20 将一系列对象复制到未初始化的内存区域。
ranges::uninitialized_copy_n C++20 将一定数量的对象复制到未初始化的内存区域。
ranges::uninitialized_fill C++20 在未初始化的内存区域复制对象。
ranges::uninitialized_fill_n C++20 在未初始化的内存区域复制对象,定义了开始和计数。
ranges::uninitialized_move C++20 将一系列对象移动到未初始化的内存区域。
ranges::uninitialized_move_n C++20 将一定数量的对象移动到未初始化的内存区域。
ranges::uninitialized_default_construct C++20 在未初始化的内存区域通过默认初始化构造对象。
ranges::uninitialized_default_construct_n C++20 在未初始化的内存区域通过默认初始化构造对象,定义了开始和计数。
ranges::uninitialized_value_construct C++20 在未初始化的内存区域通过值初始化构造对象。
ranges::uninitialized_value_construct_n C++20 在未初始化的内存区域通过值初始化构造对象,定义了开始和计数。
ranges::destroy C++20 销毁一系列对象。
ranges::destroy_n C++20 销毁范围内的一定数量的对象。
ranges::destroy_at C++20 销毁给定地址的对象。
ranges::construct_at C++20 在给定地址创建对象。
ranges::generate_random C++26 用均匀随机位生成器填充范围。
ranges::in_fun_result C++20 提供一种方法,将迭代器和函数对象作为一个单元存储。
ranges::in_in_result C++20 提供一种方法,将两个迭代器作为一个单元存储。
ranges::in_out_result C++20 提供一种方法,将两个迭代器作为一个单元存储。
ranges::in_in_out_result C++20 提供一种方法,将三个迭代器作为一个单元存储。
ranges::in_out_out_result C++20 提供一种方法,将三个迭代器作为一个单元存储。
ranges::min_max_result C++20 提供一种方法,将两个相同类型的对象或引用作为一个单元存储。
ranges::in_found_result C++20 提供一种方法,将迭代器和布尔标志作为一个单元存储。
ranges::in_value_result C++23 提供一种方法,将迭代器和一个值作为一个单元存储。
ranges::out_value_result C++23 提供一种方法,将迭代器和一个值作为一个单元存储。

你可能感兴趣的:(现代C++笔记杂谈系列,c++20,c++,开发语言)