C++20 在迭代器与容器操作方面引入了“Ranges”库,彻底改变了我们对集合遍历、过滤与变换的思维方式。相比 C++17 以前的手写循环和 std::transform、std::copy_if 等算法,Ranges 提供了更为直观、可组合且类型安全的接口。
1. Ranges 的核心概念
| 术语 | 定义 | 作用 |
|---|---|---|
std::ranges::range |
一个对象支持 begin()、end() 并返回迭代器 |
表示可遍历的序列 |
view |
对范围进行惰性转换的对象 | 例如 std::views::filter、std::views::transform |
view adaptor |
可链式调用的视图生成器 | 通过 | 运算符组合 |
action |
对范围进行立即计算的操作 | 例如 std::ranges::for_each、std::ranges::copy |
2. 传统迭代 vs Ranges 示例
传统 C++17
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector <int> v{1,2,3,4,5,6};
std::vector <int> result;
std::copy_if(v.begin(), v.end(),
std::back_inserter(result),
[](int x){ return x % 2 == 0; });
std::transform(result.begin(), result.end(),
result.begin(),
[](int x){ return x * 10; });
for(int n : result) std::cout << n << ' ';
}
C++20 Ranges
#include <vector>
#include <iostream>
#include <ranges>
int main() {
std::vector <int> v{1,2,3,4,5,6};
auto even_times10 = v | std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * 10; });
for(int n : even_times10) std::cout << n << ' ';
}
对比
- 可读性:Ranges 直接展示了“过滤再变换”的思路,代码更像自然语言。
- 惰性求值:
even_times10并未立即生成新容器,只有在迭代时才计算。 - 组合性:可以随意添加更多
views::操作(如take,drop,reverse等),而不需要手写循环。
3. Ranges 的优势与限制
| 优势 | 说明 |
|---|---|
| 惰性 | 只在需要时计算,节省内存与 CPU |
| 类型安全 | 编译期检查返回类型,避免运行时错误 |
| 链式调用 | 通过 | 运算符组合多种视图,逻辑清晰 |
| 易于维护 | 代码短小,易于读者快速理解业务逻辑 |
| 限制 | 说明 |
|---|---|
| 性能开销 | 对于极度性能敏感的场景,手写循环可能更快 |
| 编译时间 | 大量模板展开会导致编译时间增长 |
| 学习成本 | 对于习惯传统 STL 的程序员,需要时间适应新语法 |
4. 常用视图与适配器
using namespace std::ranges::views;
// filter: 过滤
auto evens = v | filter([](int x){ return x % 2 == 0; });
// transform: 转换
auto doubled = v | transform([](int x){ return x * 2; });
// take: 取前 N 个
auto first3 = v | take(3);
// drop: 跳过前 N 个
auto skip2 = v | drop(2);
// reverse: 反转
auto rev = v | reverse;
// join: 拼接多个范围
auto joined = std::views::join(std::array{v, v});
// split: 按分隔符分割
auto words = "a,b,c" | split(',');
// concat: 连接多个视图
auto all = v | concat(evens, doubled);
5. 结合 std::ranges::action 的实用技巧
// 直接输出所有偶数乘 10 的结果
v | std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * 10; })
| std::ranges::for_each([](int n){ std::cout << n << ' '; });
// 写入文件(假设已打开 std::ofstream out)
v | std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return std::to_string(x * 10); })
| std::ranges::copy(out);
6. 小结
C++20 的 Ranges 通过提供惰性、可组合的视图,让集合操作更像数据流处理。它显著提升了代码的可读性与维护性,同时保持了与传统 STL 一样的性能(或更高)。虽然在极端性能场景下手写循环仍可能更优,但对于大多数业务代码,Ranges 已经成为更现代、更安全的首选。
下一步建议:
- 在实际项目中用 Ranges 逐步替换旧的
std::copy_if、std::transform等。 - 关注编译时间与生成二进制文件大小,必要时使用
-O2或-O3。 - 学习
std::ranges::subrange与std::views::common,进一步利用 Ranges 的强大功能。