利用C++20 Ranges进行高效数据过滤与变换

C++20 引入了 Range 库,使得对容器的遍历、过滤、变换等操作可以更直观、更高效地表达。相比传统的算法与迭代器组合,Range 让代码更简洁、易读,也更符合函数式编程的风格。下面通过几个典型例子,演示如何使用 Range 进行常见的数据处理任务。

1. 基础概念:Range 与 View

  • Range:一个可以返回起始、结束迭代器的对象,满足 begin()end() 接口。`std::vector v;` 本身就是一个 Range。
  • View:对原始 Range 进行“视图”变换的对象,例如 std::views::filterstd::views::transform 等。View 本身并不持有数据,而是对原始数据延迟执行。

使用 View 的好处是:

  1. 惰性求值:只有在真正迭代时才计算,避免不必要的拷贝或临时容器。
  2. 链式组合:可以像管道一样顺序拼接多步处理,代码更简洁。

2. 过滤与变换的链式写法

#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>

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

    // 1) 过滤偶数
    // 2) 乘以 3
    // 3) 只取前 3 个结果
    auto processed = nums 
        | std::views::filter([](int x){ return x % 2 == 0; })   // 偶数
        | std::views::transform([](int x){ return x * 3; })     // 乘以 3
        | std::views::take(3);                                 // 前 3 个

    for (int v : processed) {
        std::cout << v << ' ';
    }
    // 输出:6 12 18
}

说明

  • std::views::filter 返回一个可遍历的 View,仅包含满足条件的元素。
  • std::views::transform 对每个元素应用给定函数。
  • std::views::take 只取前 N 个元素,进一步实现惰性截断。

3. 结合 std::ranges::for_eachstd::ranges::accumulate

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

int main() {
    std::vector <int> data{ 3, 6, 9, 12, 15 };

    // 计算所有奇数的平方和
    auto sum = std::ranges::accumulate(
        data | std::views::filter([](int x){ return x % 2 == 1; })
             | std::views::transform([](int x){ return x * x; }),
        0);

    std::cout << "奇数平方和: " << sum << '\n';
}

这里 std::ranges::accumulate 在对 View 进行求和时,仍然保持惰性,避免构造临时容器。

4. 自定义 View

有时标准库的 View 并不能满足需求,可以自定义一个简单的 View:

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

namespace myviews {

template <std::ranges::input_range R>
auto square(R&& r) {
    return std::views::transform(std::forward <R>(r), [](auto x){ return x * x; });
}

} // namespace myviews

int main() {
    std::vector <int> v{1, 2, 3, 4};

    for (auto x : myviews::square(v)) {
        std::cout << x << ' ';  // 输出 1 4 9 16
    }
}

通过 myviews::square 我们把一个“平方”操作封装成了一个 View,既可复用,又保持了惰性求值。

5. 性能与实际应用

  • 内存占用:使用 View 不会产生额外的容器或拷贝,节省内存。
  • 延迟执行:组合多个 View 时,只在真正需要遍历时一次性评估,避免中间结果的临时存储。
  • 易读性:代码像流水线一样直观,适合大数据处理或需要多步筛选/转换的场景。

6. 结语

C++20 的 Range 与 View 给我们提供了一种新的“声明式”数据处理方式。与传统 for 循环 + STL 算法相比,代码更简洁、易维护,且在大多数情况下性能相当甚至更优。建议在项目中逐步引入 Range,替换繁琐的迭代器组合,提升代码质量与开发效率。

发表评论