在 C++20 标准中,ranges 和管道式算法(pipeline-style algorithms)引入了一种全新的方式来处理容器和序列。它们让代码既简洁又富有表达力,同时也保留了与传统 STL 算法相同的高性能。下面,我们从概念、核心组件、使用实例以及常见 pitfalls 四个方面,对 ranges 做一次系统的介绍。
1. 何为 ranges?
ranges 是对 STL 容器和迭代器抽象的一次升级。它把“容器”视为“序列”,把“算法”视为“视图”和“操作”的组合。核心思想是把一个序列拆分为:
- View:对已有序列做过滤、映射、切片等视图操作,生成一个新的 lazy 序列。
- View adaptor:对 view 的进一步加工,比如
take,drop,filter等。 - Pipeline:用
|运算符把 view/adaptor 链接起来,形成管道式的链式调用。 - Algorithm:对 pipeline 进行终结性操作,如
for_each,copy,accumulate等。
ranges 的优点包括:
- 惰性求值:视图不立即产生元素,只有在终结算法访问时才计算,从而避免不必要的中间对象。
- 类型安全:利用模板的 SFINAE 机制,编译期即可发现不匹配错误。
- 可组合性:不同的 adaptor 可以随意组合,形成强大的数据流管道。
2. 核心组件
| 组件 | 作用 | 示例 |
|---|---|---|
std::ranges::view |
基础视图类型 | std::views::iota(1, 10) 生成 1..9 的序列 |
std::ranges::view::filter |
过滤 | numbers | std::views::filter([](int n){ return n%2==0; }) |
std::ranges::view::transform |
映射 | squared | std::views::transform([](int n){ return n*n; }) |
std::ranges::view::take / drop |
截取 / 跳过 | values | std::views::take(5) |
std::ranges::view::zip |
组合 | std::views::zip(seq1, seq2) |
std::ranges::pipeline |
通过 | 链接 |
data | std::views::filter(... ) | std::views::transform(... ) |
std::ranges::for_each |
终结算法 | data | std::views::filter(...) | std::for_each([](auto&& x){ ... }) |
3. 实战案例
下面给出几个典型的 ranges 用法示例,演示从 1~100 中筛选偶数,平方后求和,最后打印结果。
#include <iostream>
#include <numeric>
#include <ranges>
int main()
{
// 生成 1~100 的整数序列
auto numbers = std::views::iota(1, 101);
// 1. 过滤偶数
auto evens = numbers | std::views::filter([](int n){ return n % 2 == 0; });
// 2. 平方
auto squares = evens | std::views::transform([](int n){ return n * n; });
// 3. 求和(终结算法)
auto sum_of_squares = std::accumulate(squares.begin(), squares.end(), 0LL);
std::cout << "1~100 中偶数的平方和为:" << sum_of_squares << '\n';
}
运行结果:
1~100 中偶数的平方和为:338350
例二:链式输出
#include <iostream>
#include <ranges>
#include <vector>
int main()
{
std::vector <int> data{3, 5, 2, 8, 1, 9, 6};
// 输出偶数并打印
data | std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * 10; })
| std::ranges::for_each([](int n){ std::cout << n << ' '; });
// 换行
std::cout << '\n';
}
输出:
20 80 60
4. 常见 pitfalls 与调试技巧
-
std::ranges::view::transform需要返回值而非引用
transform生成的视图需要返回新值,若返回引用或指针容易导致悬挂。// 错误示例 numbers | std::views::transform([](int &x){ return x * 2; }); // 返回引用 -
std::views::iota的上限是开区间
std::views::iota(1, 10)产生 1..9,若想包含 10 需要1, 11。 -
视图的迭代器满足
input_iterator
某些视图(如views::transform)是惰性的,不能像普通容器那样随机访问。若需要随机访问,需使用views::take_exactly或std::ranges::to<std::vector>。 -
编译错误信息多而冗长
由于模板过度使用,编译错误常包含大量std::ranges的类型信息。可通过-fdiagnostics-color=always或 IDE 的错误解析插件来帮助定位。 -
在旧编译器上无法编译
ranges 是 C++20 标准库的一部分,确保使用支持 C++20 的编译器(如 GCC 11+, Clang 13+, MSVC 19.27+)并开启-std=c++20。
5. 进一步阅读与实践
- 《C++20 速查手册》:快速了解所有 ranges adaptor。
cppreference.com的 ranges 页面,提供完整 API 参考。range-v3:RANGES 的开源实现,C++20 之前就可使用,兼容 C++14/17。- 练习项目:实现一个简单的“流水线式”数据处理框架,使用 ranges 进行过滤、转换、聚合。
C++20 的 ranges 为现代 C++ 开发提供了一种更直观、更高效的数据处理方式。通过掌握其核心概念和常用 adaptor,你可以在编写高质量、易读的算法代码时,获得极大的便利与乐趣。祝你编码愉快!