C++20:如何使用 ranges 和 views 简化集合操作

在 C++20 中引入的 ranges 库为集合操作带来了全新的语法与思维方式。通过 ranges::view,我们可以在不复制容器的前提下,对数据流进行链式操作,提升代码可读性和性能。本文将从基本概念入手,展示如何使用 ranges 和 views 实现常见的集合操作,并提供完整可编译的示例。

1. 基础概念

  • range:一个可遍历的对象,例如 std::vector、std::array 或自定义容器。标准库中的容器都默认满足 range。
  • view:对 range 的一种无状态“视图”。视图是惰性求值的,只有在真正需要迭代时才计算结果。常见的 view 有 filter, transform, take, drop, reverse 等。

2. 常用 view

view 说明 示例
std::ranges::view::filter 只保留满足谓词的元素 auto evens = std::views::filter([](int x){ return x%2==0; });
std::ranges::view::transform 对每个元素应用函数 auto squares = std::views::transform([](int x){ return x*x; });
std::ranges::view::take 取前 n 个元素 auto first5 = std::views::take(5);
std::ranges::view::drop 跳过前 n 个元素 auto skip3 = std::views::drop(3);
std::ranges::view::reverse 反转顺序 auto rev = std::views::reverse;
std::ranges::view::split 以分隔符拆分字符串 auto words = std::views::split(' ');

3. 链式操作

将多个 view 链接在一起,形成一条数据流水线:

#include <iostream>
#include <vector>
#include <ranges>
#include <numeric>

int main() {
    std::vector <int> data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 取偶数 → 乘 3 → 取前 3 个 → 求和
    auto result = std::accumulate(
        data | std::views::filter([](int x){ return x % 2 == 0; })
             | std::views::transform([](int x){ return x * 3; })
             | std::views::take(3),
        0,
        std::plus<>()
    );

    std::cout << "Result: " << result << '\n';
}

输出:

Result: 42

解析:

  • filter 选出 2,4,6,8,10
  • transform 变为 6,12,18,24,30
  • take(3) 取前 3 个 6,12,18
  • accumulate 求和得到 42

4. 与传统算法对比

传统做法:

int sum = 0;
for (int x : data) {
    if (x % 2 == 0) sum += x * 3;
}

使用 ranges 可读性更强,且无需显式循环。若需要多步处理,链式 view 可以一次性完成,减少中间临时容器。

5. 性能优势

  • 惰性求值:view 在迭代时才会真正执行,避免不必要的拷贝或临时容器。
  • 编译期优化:标准库实现基于模板,编译器可将多层 view 直接内联,生成高度优化的代码。
  • 表达式简洁:可快速定位错误与优化点。

6. 进阶用法:自定义 view

可以自定义一个简单的 view 来实现特殊需求,例如 unique(去重):

#include <ranges>

template <std::ranges::input_range R>
requires std::ranges::viewable_range <R>
class unique_view : public std::ranges::view_interface<unique_view<R>> {
    R base_;
public:
    explicit unique_view(R base) : base_(std::move(base)) {}

    auto begin() const {
        return std::ranges::unique(base_).begin();
    }
    auto end() const {
        return std::ranges::unique(base_).end();
    }
};

namespace std::ranges::views {
    inline auto unique = [](auto&& rng) {
        return unique_view<std::views::all_t<decltype(rng)>>(std::views::all(std::forward<decltype(rng)>(rng)));
    };
}

使用示例:

std::vector <int> nums{1,1,2,3,3,3,4,5,5};
auto uniq = nums | std::views::unique;
for (int x : uniq) std::cout << x << ' ';

输出 1 2 3 4 5

7. 小结

  • C++20 ranges 与 views 通过惰性求值、链式操作和无状态设计,使集合处理更加声明式与高效。
  • 通过标准 view(filter、transform、take、drop 等)即可完成大部分需求。
  • 进阶者可自定义 view,进一步扩展功能。
  • 与传统循环相比,ranges 更易读、易维护,同时可以获得编译器级别的性能优化。

掌握 ranges 与 views 后,你会发现许多看似复杂的集合操作可以用极简的代码表达,极大提升开发效率与代码质量。祝你在 C++20 的新语法中玩得愉快!

发表评论