第8.1章: ranges 与基于范围的算法

English

概述

range 是任何提供了起点与终点的对象。 目前介绍过的所有容器—— ::fast_io::vector、 ::fast_io::string、 ::fast_io::array、 ::fast_io::span、 ::fast_io::string_view、 ::fast_io::cstring_view、 ::fast_io::index_span 以及 C 风格数组—— 都是连续范围。

基于范围的算法是一次作用于整个范围的操作。不必手工 编写循环,你可以调用排序、查找、反转、复制或计数等函数。

在本章中,你将学到:

1. 什么是 range?

range 由两个迭代器定义:

你已经熟悉的 range 例子:


::fast_io::vector<int> v{1, 2, 3};
int a[3]{4, 5, 6};
::fast_io::span<int> s{a, 3};
::fast_io::string str{"abc"};
::fast_io::string_view sv{"hello"};

这些都是合法的 range。

2. 对 range 排序

最简单的例子就是排序:


::fast_io::vector<int> v{3, 1, 2};

::std::ranges::sort(v);   // ascending

要按逆序排序,请使用 ::std::ranges::greater:


::std::ranges::sort(v, ::std::ranges::greater{});

这对任何 range 都有效,包括 C 风格数组:


int a[5]{5, 1, 4, 3, 2};
::std::ranges::sort(a, ::std::ranges::greater{});

3. 带概念约束的比较器(<functional>)

C++20 在 <functional> 中提供了标准化的比较函数对象, 它们与 range 算法天然配合:

它们经过概念检查、可复用,并且不会生成额外代码。

4. 用 lambda 作为比较器

你可以用 lambda 作为比较器:


::std::ranges::sort(v, [](int a, int b) noexcept {
    return a > b;
});

请始终把比较器 lambda 标记为 noexcept。 如果比较器抛出异常,C++ 标准要求程序终止。

5. lambda 显然不是零开销

每个 lambda 表达式都是独有的类型,编译器会为每一个 生成独立的代码,即便它们看起来完全相同:


::std::ranges::sort(v,  [](int a, int b) noexcept { return a > b; });
::std::ranges::sort(v1, [](int a, int b) noexcept { return a > b; });
::std::ranges::sort(v2, [](int a, int b) noexcept { return a > b; });

这三个 lambda 看起来一样,但会产生三个不同的比较器对象 和三条独立的代码路径。这会增加:

当 lambda 出现在模板内部时(我们稍后会介绍),这种开销会变得 糟糕得多,因为模板会为每个不同的 lambda 类型 分别实例化。

如果 ::std::ranges::greater{} 这类标准比较器可用, 你应当始终优先使用它,而不是新写一个 lambda。

6. 在 range 中查找


int a[5]{10, 20, 30, 40, 50};

auto it = ::std::ranges::find(a, 30);

if (it != ::std::ranges::end(a)) {
    // found
}

7. 反转 range


::fast_io::string s{"hello"};

::std::ranges::reverse(s);   // "olleh"

8. 复制 range


int a[3]{1, 2, 3};
int b[3]{};

::std::ranges::copy(a, b);

9. 统计元素个数


::fast_io::vector<int> v{1, 2, 2, 3};

std::size_t n = ::std::ranges::count(v, 2);  // n = 2

10. 使用 ::std::ranges::begin 与 ::std::ranges::end

这些函数适用于所有 range,包括 C 风格数组:


int a[3]{10, 20, 30};

auto first = ::std::ranges::begin(a);
auto last  = ::std::ranges::end(a);

要点