在 C++20 中引入的范围(Ranges)库为我们处理容器提供了更自然、更高效的方式。相比传统的 STL 算法,Ranges 通过惰性求值、管道操作以及组合视图(views)让代码更加可读、可维护。本文将通过几个实用案例,演示如何利用范围库进行数据筛选、变换和聚合。
1. 先决条件
#include <iostream>
#include <vector>
#include <string>
#include <ranges>
#include <numeric> // accumulate
请确保使用支持 C++20 的编译器,例如 g++ -std=c++20 或 clang++ -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 范围库后,你将能以更少的代码完成复杂的数据处理任务,并获得更好的可读性与可维护性。祝你编码愉快!