C++—range

C++20 引入了 range 来简化对元素序列的处理(可以省略掉许多的循环遍历)。
1. range 和 view range
range concept 通过提供一个迭代器以及一个哨兵来表示一个元素范围,以允许对某个类型进行遍历。

templateconcept range = requires(T& t) { ranges::begin(t); ranges::end(t); };

【C++—range】如,vector 就是一个 range:
std::vector vec{ 1, 2, 3 }; auto it1 = std::ranges::begin(vec); auto it2 = std::ranges::end(vec);

view
view 是一个 range,且具有常数时间复杂度的的拷贝、移动和赋值操作(如,直接操作一对迭代器、或使用生成器来按需生成元素)。
template concept view = ranges::range && std::movable && ranges::enable_view; templateinline constexpr bool enable_view = std::derived_from || /*is-derived-from-view-interface*/;

如,可通过 iota 来创建一个 view,类似于 Python 中的 range:
auto v1 = std::views::iota(1); // [1, +inf) auto v2 = std::views::iota(1, 10); // [1, 10)for (int i : v2) { std::cout << i << ' '; }

2. 范围工厂 会创建一个 view。
iota:创建一个不断递增的元素序列,可以是有界的,也可以是无界的。
#include #include int main() { auto v1 = std::views::iota(10); // [10, +inf) auto v2 = std::views::iota(1, 10); // [1, 10)for (int i = 0; i < 10; i++) { std::cout << v1[i] << ' '; } std::cout << '\n'; for (int i : v2) { std::cout << i << ' '; } }

10 11 12 13 14 15 16 17 18 19 1 2 3 4 5 6 7 8 9

istream_view:对某个输入流不断地应用 operator >>,从而获得一系列的元素。
#include #include #include int main() { std::istringstream nums("1.1 2.2 3.3\t4.4\n5.5"); auto v = std::ranges::istream_view(nums); for (float f : v) { std::cout << f << ", "; } }

1.1, 2.2, 3.3, 4.4, 5.5,

3. 范围适配器 接受一个 range,对其执行某些操作后,返回结果 view。
counted:从指定位置开始获取 n 个元素。
std::vector vec{ 1, 2, 3, 4, 5, 6, 7 }; for (int i : std::views::counted(vec.begin() + 1, 3)) { std::cout << i << ' '; }

2 3 4

drop:丢弃 range 中的前 n 个元素。
int nums[] = { 0, 1, 2, 3, 4, 5, 6 }; for (int i : std::views::drop(nums, 3)) { std::cout << i << ' '; }

3 4 5 6

elements:获取第 n 列元素。
注:view 中的元素需要是 tuple-like 的。
std::vector> vecs { {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, }; for (int i : std::views::elements<1>(vecs)) { std::cout << i << ' '; }

2 5 8

filter:对 range 进行过滤,只保留符合条件的元素。
int nums[] = { 0, 1, 2, 3, 4, 5 }; auto even = [](int i) { return i % 2 == 0; }; for (int i : std::views::filter(nums, even)) { std::cout << i << ' '; }

0 2 4

join:合并多个 range。
std::vector vec1{ 1, 2, 3 }; std::vector vec2{ 4, 5, 6 }; std::vector> vecs{vec1, vec2}; for (int i : std::views::join(vecs)) { std::cout << i << ' '; }

1 2 3 4 5 6

reverse:反转 range 中的元素。
const int a[] = { 1, 2, 3, 4, 5, 6, 7 }; for (int i : std::views::reverse(a)) { std::cout << i << ' '; }

7 6 5 4 3 2 1

split:按指定的元素分割 range。
std::vector vec{ 1, 2, 3, 4, 5, 6, 7 }; for (const auto& v : std::views::split(vec, 4)) { for (int i : v) { std::cout << i << ' '; } std::cout << '\n'; }

1 2 3 5 6 7

take:获取 range 中的前 n 个元素。
int nums[] = { 0, 1, 2, 3, 4, 5 }; for (int i : std::views::take(nums, 3)) { std::cout << i << ' '; }

0 1 2

transform:对 range 中的每个元素执行指定的转换操作,转换操作的返回值就是结果 view 中的元素。
int nums[] = { 0, 1, 2, 3, 4, 5 }; auto square = [](int i) { return i * i; }; for (int i : std::views::transform(nums, square)) { std::cout << i << ' '; }

0 1 4 9 16 25

4. 管道运算符 如果 C 是一个范围适配器闭包对象,R 是一个可以转换为 view 的 range,则 C(R) 等价于 R | C
范围适配器闭包对象包括:一元范围适配器(只接受一个参数)、绑定了余下参数的多元范围适配器(只留下第一个参数未指定)和 R | C 的返回值。
int nums[] = { 0, 1, 2, 3, 4, 5 }; auto even = [](int i) { return i % 2 == 0; }; auto square = [](int i) { return i * i; }; // 先过滤,然后对过滤得到的元素求平方 for (int i : nums | std::views::filter(even) | std::views::transform(square)) { std::cout << i << ' '; }

0 4 16

    推荐阅读