C++20 引入了 ranges(范围)库,它彻底改变了我们遍历容器、组合算法以及构建可复用、表达式直观代码的方式。本文将从概念、核心组件、常用算法以及与旧式迭代器的对比四个角度,浅析 ranges 的设计哲学与实际应用。
1. 什么是 ranges?
ranges 不是一种新的容器,而是一种对现有容器、迭代器以及算法的统一抽象。它把“可遍历对象”定义为range,把“算法”拆分为“视图(view)”与“算法(algorithm)”两部分。视图是对已有 range 的延迟、不可变转换,算法是对视图或范围进行操作的最终步骤。
2. 核心概念
| 名词 | 定义 | 典型示例 |
|---|---|---|
range |
一组连续的元素,具有 begin()、end()、size() 等成员或对应的非成员函数 |
`std::vector |
| v;` | ||
view |
对一个 range 的非持久化、惰性变换 | std::views::filter, std::views::transform, std::views::take |
view adaptor |
一种函数式对象,返回对应的 view | std::views::filter([](auto x){return x%2==0;}) |
algorithm |
对一个 view 或 range 进行终端操作 | std::ranges::for_each, std::ranges::copy, std::ranges::reduce |
3. 视图与算法的组合
使用视图可以像链式调用一样串联多种变换,代码既简洁又保持惰性。例如:
#include <ranges>
#include <iostream>
#include <vector>
int main() {
std::vector <int> v{1,2,3,4,5,6,7,8,9,10};
auto even_sum = v | std::views::filter([](int x){return x % 2 == 0;}) |
std::views::transform([](int x){return x * x;}) |
std::ranges::fold_left(0, std::plus<>{});
std::cout << "偶数平方和: " << even_sum << '\n';
}
该程序先过滤偶数,再平方,最后累加。所有操作都在一次遍历中完成,避免了中间容器的产生。
4. 与旧式迭代器的对比
| 维度 | 旧式代码 | ranges 代码 |
|---|---|---|
| 迭代 | for(auto it=v.begin(); it!=v.end(); ++it) |
for(auto& x: v) 或视图链 |
| 过滤 | for(auto x: v) if(cond) ... |
v | std::views::filter(cond) | std::ranges::for_each(...) |
| 组合 | 多次遍历 | 单次惰性链 |
| 可读性 | 较差 | 高度声明式 |
5. 性能与实践
由于视图是惰性的,除非最终被一个需要终止的算法触发,否则不产生额外遍历。与旧式 std::transform + std::copy_if 相比,ranges 可以省去中间缓冲区,降低内存占用;但若链中存在副作用(例如 views::filter 内的 std::cout),会在单遍历中产生多次副作用,需注意。
6. 小结
C++20 ranges 通过统一视图与算法,提供了一种声明式、惰性且可组合的遍历与变换方式。它使代码更接近问题的直观描述,减少重复遍历与临时容器的开销。掌握 ranges 不仅能让你写出更简洁的现代 C++ 代码,也为将来的算法与容器创新打下坚实基础。