C++20 引入的 ranges 库在很大程度上提升了 STL 的可读性、可维护性与表达力。它不再让我们像过去那样手写迭代器或依赖繁琐的 for‑loop,而是让“容器+谓词+变换”这三大要素天然耦合、可组合。下面从哲学、实现细节、常见误区和实战案例四个角度拆解 ranges 的强大之处,帮助你快速上手。
1. 核心哲学:懒惰与分层
1.1 懒惰求值
ranges 的所有视图(view)本质上是懒惰求值的链式结构。只在需要的时候才计算下一个元素,而不是一次性生成整个序列。
好处:节省内存、支持无限序列、能够与其他惰性操作无缝拼接。
1.2 分层组合
ranges 提供了三层抽象:
- View:对已有序列做过滤、映射、切片等“视图”操作,返回新的范围对象。
- Adaptor:一类特殊的 View,用于把普通容器或迭代器包装成可被其他 View 处理的对象。
- Algorithm:对 View 进行终止性操作(
for_each、count_if、sort等),与 View 的懒惰性兼容。
通过把“何时产生数据”和“如何处理数据”分离,代码变得更易读、易测试。
2. 关键组件详解
| 组件 | 作用 | 典型用法 |
|---|---|---|
std::views::filter |
过滤器 | auto evens = view | std::views::filter([](int n){return n%2==0;}); |
std::views::transform |
映射 | auto squares = view | std::views::transform([](int n){return n*n;}); |
std::views::take |
截断 | auto first10 = view | std::views::take(10); |
std::views::reverse |
反转 | auto rev = view | std::views::reverse; |
std::ranges::for_each |
遍历执行 | std::ranges::for_each(view, [](auto& x){std::cout<<x;}); |
std::ranges::sort |
排序(需要可随机访问的 View) | std::ranges::sort(view); |
`)或迭代器范围(`std::begin(vec)` 到 `std::end(vec)`)。链式组合使用 `|`(管道符)操作符。
view变量通常是一个容器(`std::vector
3. 常见误区与排查技巧
3.1 误区:直接把 View 传给 std::vector 的构造函数
std::vector <int> v = std::views::iota(0,10); // ❌
原因:
v(std::begin(view), std::end(view));`iota返回的是std::ranges::iota_view,不是容器。
解决:显式生成容器:`std::vector
3.2 误区:对不支持随机访问的 View 调用 sort
auto v = std::views::iota(0,10) | std::views::reverse;
std::ranges::sort(v); // ❌
原因:
arr(v.begin(), v.end()); std::ranges::sort(arr);`reverse生成的是双向迭代器视图,sort需要随机访问。
解决:先将 View 收集到容器,再排序:
`std::vector
3.3 误区:使用 std::views::filter 过滤后直接调用 size()
size()只在容器上可用,视图没有此成员。
解决:使用std::ranges::distance(v)或std::ranges::count(v, predicate)。
4. 实战案例:统计特定模式的整数
假设我们有一个巨大的整数序列(可能是文件中的行、网络流等),我们需要统计所有 质数 且 奇数 的个数。传统实现可能如下:
int count = 0;
for (int n : numbers) {
if (n % 2 == 1 && isPrime(n)) ++count;
}
使用 ranges,我们可以写成:
auto isPrime = [](int n) {
if (n < 2) return false;
for (int i = 2; i*i <= n; ++i)
if (n % i == 0) return false;
return true;
};
int count = std::ranges::count_if(
numbers |
std::views::filter([](int n){ return n % 2 == 1; }) |
std::views::filter(isPrime),
[](int){ return true; } // 过滤器的返回值会被忽略,只要返回 true
);
或更简洁:
int count = std::ranges::count_if(
numbers | std::views::filter([](int n){ return n % 2 == 1 && isPrime(n); }),
[](int){ return true; }
);
优点:代码更短、逻辑更直观;若需要进一步扩展(如先过滤出偶数再做平方等),只需在管道中插入相应的视图。
5. 小结
- ranges 通过懒惰求值和分层组合,使 STL 的容器遍历变得更加声明式。
- 只要掌握 View、Adaptor、Algorithm 的组合,即可完成过滤、映射、切片等常见需求。
- 牢记懒惰的本质,避免对视图执行不合法的操作(如直接
sort或size())。 - 在大数据量、流式数据处理场景下,ranges 能显著提升代码的可维护性与执行效率。
尝试把你手头的容器遍历代码重构为 ranges 版,感受它带来的“即写即跑”的乐趣吧!