**标题:使用 C++20 范围库(Ranges)实现更简洁的数据处理**

在 C++20 中引入的范围(Ranges)库为我们处理容器提供了更自然、更高效的方式。相比传统的 STL 算法,Ranges 通过惰性求值、管道操作以及组合视图(views)让代码更加可读、可维护。本文将通过几个实用案例,演示如何利用范围库进行数据筛选、变换和聚合。


1. 先决条件

#include <iostream>
#include <vector>
#include <string>
#include <ranges>
#include <numeric>   // accumulate

请确保使用支持 C++20 的编译器,例如 g++ -std=c++20clang++ -std=c++20


2. 基本语法

传统方式:

std::vector <int> v{1, 2, 3, 4, 5};
std::vector <int> evens;
for (int x : v) {
    if (x % 2 == 0) evens.push_back(x);
}

范围库方式:

auto evens = v | std::views::filter([](int n){ return n % 2 == 0; })
               | std::ranges::to<std::vector>();

这里的 | 代表管道操作符,将 v 通过一系列视图(views)进行转换。最后 to<std::vector>() 把惰性视图 materialize 成具体容器。


3. 变换(Transformation)与组合

假设我们有一个学生成绩列表,需要先过滤出及格学生,再将分数转化为等级(A、B、C 等),最终统计各等级人数。

struct Student {
    std::string name;
    int score;          // 0~100
};

std::vector <Student> students = {
    {"张三", 92}, {"李四", 76}, {"王五", 65},
    {"赵六", 48}, {"钱七", 83}, {"孙八", 73}
};

auto grade = [](int score) {
    if (score >= 90) return 'A';
    if (score >= 80) return 'B';
    if (score >= 70) return 'C';
    if (score >= 60) return 'D';
    return 'F';
};

auto pass = std::views::filter([](const Student& s){ return s.score >= 60; });
auto grade_view = std::views::transform([](const Student& s){ return grade(s.score); });

auto grade_counts = std::views::zip(pass, grade_view)
    | std::views::transform([](auto pair){ return pair.second; })
    | std::ranges::to<std::vector>();

// 统计
std::map<char, int> count_map;
for (char g : grade_counts) ++count_map[g];

for (auto [g, cnt] : count_map) {
    std::cout << "Grade " << g << ": " << cnt << " student(s)\n";
}

输出示例:

Grade A: 1 student(s)
Grade B: 2 student(s)
Grade C: 1 student(s)
Grade D: 1 student(s)

4. 过滤、变换与聚合的组合

需求:从整数序列中挑选偶数,求它们平方和,再取平均值。

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

auto sum = std::accumulate(
    nums | std::views::filter([](int n){ return n % 2 == 0; })
         | std::views::transform([](int n){ return n * n; })
         | std::ranges::begin(),
    nums | std::views::filter([](int n){ return n % 2 == 0; })
         | std::views::transform([](int n){ return n * n; })
         | std::ranges::end(),
    0
);

int count = std::ranges::count_if(nums, [](int n){ return n % 2 == 0; });
double average = static_cast <double>(sum) / count;

std::cout << "Even squares sum = " << sum << ", average = " << average << '\n';

5. 延迟求值与性能

范围库采用惰性求值,意味着每一步视图不会立即产生新容器,而是仅在需要时生成下一个元素。这样可以在链式操作中避免不必要的临时对象,提高性能。

对比
传统做法:

std::vector <int> tmp;
std::copy_if(...);
std::transform(...);

范围库:

auto res = vec | std::views::filter(...) | std::views::transform(...);

6. 小结

  • 视图(Views)std::views::filter, std::views::transform, std::views::take 等,均为惰性操作。
  • 管道操作符| 让链式调用自然流畅。
  • materialize:`std::ranges::to ()` 将惰性视图转换为具体容器。
  • 组合:多个视图可以自由组合,形成直观的“流水线”。

掌握 C++20 范围库后,你将能以更少的代码完成复杂的数据处理任务,并获得更好的可读性与可维护性。祝你编码愉快!


发表评论