目录
以std::string和std::string_view为例子
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::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<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_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 | 提供一种方法,将迭代器和一个值作为一个单元存储。 |