<ranges>
概括而言,范围是可以迭代的某种属性。 范围由迭代器(标记范围的开头)和 sentinel(标记范围的末尾)表示。 sentinel 的类型可能与开始迭代器相同,也可能不同。 C++ 标准库中的容器(例如,vector
和list
)是范围。 范围以一种简化和增强标准模板库 (STL) 使用能力的方式来抽象迭代器。
STL 算法通常采用指向其应该操作的集合部分的迭代器。 例如,请考虑如何使用std::sort()
对vector
进行排序。 传递两个迭代器来标记vector
的开头和末尾。 这可以获得灵活性,但将迭代器传递给算法会造成额外的工作,因为你可能只想对整个对象进行排序。
使用范围,可以调用std::ranges::sort(myVector);
,这就相当于调用了std::sort(myVector.begin(), myVector.end());
。 在范围库中,算法将范围用作参数(不过,如果需要的话,它们也可以使用迭代器)。 它们可以直接对集合进行操作。 <algorithm>
中可用的范围算法示例包括copy
、copy_n
、copy_if
、all_of
、any_of
、none_of
、find
、find_if
、find_if_not
、count
、count_if
、for_each
、for_each_n
、equal
和mismatch
。
但也许范围最重要的好处是,可以编写 STL 算法,这些算法以让人想起函数编程的样式对范围进行操作。
范围示例
在范围问世之前,如果想转换集合中满足特定条件的元素,需要引入中间步骤以保存运算之间的结果。 例如,如果想从另一个可被三整除的向量中的元素生成平方向量,可以编写如下内容:
std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
std::vector<int> intermediate, output;
std::copy_if(input.begin(), input.end(), std::back_inserter(intermediate), [](const int i) { return i%3 == 0; });
std::transform(intermediate.begin(), intermediate.end(), std::back_inserter(output), [](const int i) {return i*i; });
使用范围,可以在不需要 intermediate
向量的情况下达到相同的目的:
// requires /std:c++20
std::vector<int> input = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto output = input
| std::views::filter([](const int n) {return n % 3 == 0; })
| std::views::transform([](const int n) {return n * n; });
除了更易于阅读之外,此代码还避免了intermediate
向量及其内容所需的内存分配。 它还允许组合两个操作。
在前面的代码中,每个可被 3 整除的元素都与对该元素求平方的运算相结合。 管道 (|
) 符号将运算链接在一起,从左到右读取。
结果output
本身是称为视图的范围。
视图
视图是一个轻量范围。 例如默认构造、移动构造/赋值、复制构造/赋值(如果存在)、销毁、开始和结束等视图操作都在恒定时间内发生,无论视图中有多少元素。
视图由范围适配器创建,下一部分将予以介绍。 有关实现各种视图的类的详细信息,请参阅视图类。
视图中元素的显示方式取决于用于创建该视图的范围适配器。 在以上示例中,范围适配器采用范围并返回可被 3 整除的元素的视图。 基础范围保持不变。
视图可组合,这一点很强大。 在以上示例中,可被 3 整除的向量元素的视图与对这些元素求平方的视图合并。
视图的元素是惰性计算的。 也就是说,在请求元素之前,不会计算应用于视图中每个元素的变换。 例如,如果在调试器中运行以下代码并在行 auto divisible_by_three = ...
和 auto square = ...
上放置一个断点,则会命中 divisible_by_three
lambda 断点,因为 input
中的每个元素经测试可被 3 整除。 将命中 square
lambda 断点,因为可被 3 整除的元素已求平方。
// requires /std:c++20
#include <ranges>
#include <vector>
#include <iostream>
int main()
{
std::vector<int> input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
auto divisible_by_three = [](const int n) {return n % 3 == 0; };
auto square = [](const int n) {return n * n; };
auto x = input | std::views::filter(divisible_by_three)
| std::views::transform(square);
for (int i : x)
{
std::cout << i << '\n';
}
return 0;
}
有关视图的详细信息,请参阅<ranges>
视图类。
范围适配器
范围适配器采用范围并生成视图。 范围适配器生成惰性计算的视图。 也就是说,不会产生变换范围内每个元素以生成视图的相关开销。 访问该元素时,只需支付在视图中处理元素的成本。
在以上示例中,filter
范围适配器创建名为input
的视图,其中包含可被 3 整除的元素。 transform
范围适配器采用可被 3 整除的元素的视图,并创建这些元素的平方视图。
范围适配器可以链接在一起(组合),这是范围的强大和灵活性的核心。 组合范围适配器可以克服不容易组合的旧 STL 算法的问题。
有关创建视图的详细信息,请参阅范围适配器。
范围算法
一些范围算法采用范围参数。 示例为 std::ranges::sort(myVector);
。
范围算法与std
命名空间中的相应迭代器对算法几乎完全相同。 区别在于,它们具有概念强制约束,它们接受范围参数或更多迭代器-sentinel 参数对。 它们可以直接针对容器运行,并可以轻松链接在一起。
<ranges>
函数
以下函数用于为范围创建迭代器和 sentinel,并获取范围大小。
函数 | 说明 |
---|---|
begin C++20 |
获取指向范围中第一个元素的迭代器。 |
cbegin C++20 |
获取指向范围中第一个元素的 const 迭代器。 |
cend C++20 |
获取 const 选定的范围末尾的 sentinel。 |
cdata C++20 |
获取指向连续范围中第一个元素的 const 指针。 |
crbegin C++20 |
获取指向范围开头的反向 const 迭代器。 |
crend C++20 |
获取 crbegin() 返回结果末尾的 sentinel。 |
data C++20 |
获取指向连续范围中第一个元素的指针。 |
empty C++20 |
确定范围是否为空。 |
end C++20 |
获取范围末尾的 sentinel。 |
rbegin C++20 |
获取指向范围开头的反向迭代器。 |
rend C++20 |
获取指向范围末尾的 sentinel 的反向迭代器。 |
size C++20 |
获取范围的大小作为无符号值。 |
ssize C++20 |
获取范围的大小作为带符号值。 |
有关详细信息,请参阅 <ranges>
函数。
范围概念
循环访问范围元素的方式取决于其基础迭代器类型。 范围使用 C++ 概念指定它们支持的迭代器。
在 C++20 中,假设概念X细化概念Y意味着满足概念Y的所有内容也满足概念X。例如:轿车、公共汽车和卡车都细化车辆。
一些范围概念反映了迭代器类别的层次结构。 下表列出了范围概念,以及它们可以应用到的容器类型。
范围概念 | 说明 | 支持的容器 |
---|---|---|
std::ranges::output_range |
可以向前迭代。 | |
std::ranges::input_range |
至少可以从头到尾迭代一次。 | std::forward_list std::unordered_map std::unordered_multimap std::unordered_set std::unordered_multiset basic_istream_view |
std::ranges::forward_range |
可以从头到尾迭代多次。 | std::forward_list std::unordered_map std::unordered_multimap std::unordered_set std::unordered_multiset |
std::ranges::bidirectional_range |
可以多次向前和向后迭代。 | std::list std::map std::multimap std::multiset std::set |
std::ranges::random_access_range |
可以使用[] 运算符访问任意元素(在恒定时间内)。 |
std::deque |
std::ranges::contiguous_range |
元素按顺序存储在内存中。 | std::array std::string std::vector |
有关这些概念的详细信息,请参阅<ranges>
概念。
<ranges>
别名模板
以下别名模板确定了范围的迭代器和 sentinel 类型:
别名模板 | 说明 |
---|---|
borrowed_iterator_t C++20 |
确定为 range 返回的迭代器是否引用生存期已结束的范围。 |
borrowed_subrange_t C++20 |
确定为subrange 返回的迭代器是否引用生存期已结束的子范围。 |
dangling C++20 |
指示返回的 range /subrange 迭代器大于所引用 range /subrange 的生存期。 |
iterator_t C++20 |
返回指定的范围类型的迭代器类型。 |
range_difference_t C++20 |
返回指定范围的迭代器类型的差异类型。 |
range_reference_t C++20 |
返回指定范围的迭代器类型的引用类型。 |
range_rvalue_reference_t C++20 |
返回指定范围的迭代器类型的 rvalue 引用类型。 换句话说,范围元素的 rvalue 引用类型。 |
range_size_t C++20 |
返回用于报告指定范围大小的类型。 |
range_value_t C++20 |
返回指定范围的迭代器类型的值类型。 或者换句话说,范围中元素的类型。 |
sentinel_t C++20 |
返回指定范围的 sentinel 类型。 |
有关这些别名模板的详细信息,请参阅<ranges>
别名模板。