如何使用C++20的范围库(Ranges)来简化算法?

C++20引入了强大的范围(Ranges)库,它将标准算法与容器的概念更紧密地结合在一起,提供了更简洁、可读性更高的代码。下面我们将通过几个常见场景,演示如何利用Range来替代传统的迭代器循环。

1. 过滤(filter)

假设我们有一个整数向量,想要得到所有大于10的元素:

std::vector <int> data = {1, 12, 5, 18, 7, 30};

auto result = data 
    | std::views::filter([](int n){ return n > 10; });

for(int n : result) {
    std::cout << n << ' ';   // 输出: 12 18 30
}

std::views::filter 会创建一个惰性视图,只有在遍历时才会真正执行过滤逻辑。相比传统的 std::copy_if,代码更简洁。

2. 转换(transform)

如果需要将向量中的每个数平方,可以使用 std::views::transform

auto squared = data | std::views::transform([](int n){ return n * n; });

for(int n : squared) {
    std::cout << n << ' ';
}

这里的 squared 是一个懒执行的视图,实际计算在遍历时进行。

3. 组合视图

C++20 的 Range 视图支持链式组合,几乎可以把所有标准算法复写为视图组合。例如,想得到所有偶数的平方和:

auto sum_of_even_squares = std::accumulate(
    data | std::views::filter([](int n){ return n % 2 == 0; })
        | std::views::transform([](int n){ return n * n; }),
    0
);
std::cout << sum_of_even_squares << '\n';

这段代码先过滤偶数,再平方,最后使用 std::accumulate 求和。相比使用 std::transform + std::copy_if + std::accumulate 的三步流程,视图链条更短、更直观。

4. 分区(partition)

如果需要把向量分成满足条件和不满足条件两部分,可以使用 std::views::take_whilestd::views::drop_while。但更直观的做法是使用 std::ranges::partition(C++23 之后正式纳入标准):

std::ranges::partition(data, [](int n){ return n % 2 == 0; });

分区后,偶数会被放到前面,奇数放到后面,原始顺序在各自组内保持不变。

5. 逆序(reverse)

想要逆序遍历容器,C++20 提供了 std::views::reverse

for(int n : data | std::views::reverse) {
    std::cout << n << ' ';
}

与手动使用 rbegin()/rend() 的方式相比,视图更统一。

6. 迭代器适配器(take、drop、zip)

  • std::views::take(n):取前 n 个元素。
  • std::views::drop(n):忽略前 n 个元素。
  • std::views::zip(C++23):把两个容器按索引配对。
auto first_three = data | std::views::take(3);
auto skip_two = data | std::views::drop(2);

7. 何时使用视图,何时使用算法

视图非常适合读操作:过滤、变换、取子集等。若需要修改原始容器中的元素,还是使用普通算法或迭代器。

另外,视图是惰性的。只要不遍历,内部函数不会被调用。若需要多次访问,建议使用 std::ranges::to<std::vector>()std::vector 直接存储结果。

8. 小结

  • 视图让算法更具表达性,减少模板噪声。
  • 通过链式组合,能用一行代码完成复杂操作。
  • 惰性求值提升效率,避免不必要的拷贝。

C++20 的 Range 库为现代 C++ 编程提供了更优雅、更安全的方式。下次写循环、过滤或变换时,别忘了先试试 Range 视图,或许能让你的代码更简洁。

发表评论