C++20 中引入的范围(Ranges)库为我们提供了一种更优雅、更安全的方式来处理序列数据。与传统的迭代器和算法组合相比,范围视图(views)能够以懒加载、链式组合的形式表达复杂的数据转换,极大地提升代码可读性与可维护性。下面从基础用法到高级技巧,逐步展开 C++20 范围视图的实战指南。
1. 范围视图的基本概念
- View:对序列的只读、懒惰的视图。不会产生副本,而是延迟计算结果。
- ViewAdaptor:一种包装器,将已有容器或视图转化为另一种视图。
- Range:任何支持
begin()、end()的对象。
#include <ranges>
#include <vector>
#include <iostream>
std::vector <int> vec{1,2,3,4,5,6,7,8,9,10};
auto even = vec | std::views::filter([](int x){ return x % 2 == 0; });
for (int n : even) std::cout << n << ' ';
输出:2 4 6 8 10
2. 常用视图适配器
| 适配器 | 说明 | 示例 |
|---|---|---|
std::views::filter |
根据谓词过滤元素 | vec | std::views::filter([](int n){ return n>5; }) |
std::views::transform |
对每个元素做变换 | vec | std::views::transform([](int n){ return n*n; }) |
std::views::take |
取前 N 个元素 | vec | std::views::take(3) |
std::views::drop |
跳过前 N 个元素 | vec | std::views::drop(2) |
std::views::reverse |
逆序遍历 | vec | std::views::reverse |
std::views::unique |
去重 | sorted_vec | std::views::unique |
std::views::zip |
并行遍历多个序列 | std::views::zip(a,b) |
2.1 组合使用
auto result = vec
| std::views::filter([](int n){ return n % 3 == 0; })
| std::views::transform([](int n){ return n/3; })
| std::views::take(4);
for (int x : result) std::cout << x << ' ';
3. 创建自定义视图
如果标准视图不满足需求,可以自定义一个视图。关键步骤:
- 定义视图结构:继承
std::ranges::view_interface,实现begin(),end(). - 提供
size():可选,但有利于std::ranges::sized_range特性。 - 使用
std::ranges::subrange:简化迭代器包装。
#include <ranges>
#include <vector>
template<std::ranges::input_range R>
class square_view : public std::ranges::view_interface<square_view<R>> {
R base_;
public:
explicit square_view(R r) : base_(std::move(r)) {}
auto begin() { return std::views::transform(base_, [](auto x){ return x*x; }).begin(); }
auto end() { return std::views::transform(base_, [](auto x){ return x*x; }).end(); }
};
template<std::ranges::input_range R>
auto operator|(R&& r, std::type_identity_t<square_view<R>> v)
{
return square_view <R>(std::forward<R>(r));
}
用法:
std::vector <int> v{1,2,3,4};
for (int n : v | square_view{}) std::cout << n << ' '; // 1 4 9 16
4. 视图与算法的协同
虽然范围视图是懒加载的,但与标准算法配合使用时,它们会自动展开为高效的迭代器。
#include <algorithm>
auto sum = std::accumulate(
vec | std::views::filter([](int n){ return n%2==0; }),
0
);
如果你需要在视图链中加入 std::views::common,可确保在算法需要 begin()、end() 的时候得到可用的迭代器。
5. 性能注意事项
- 懒计算:每次迭代都会重新计算所有适配器,可能导致多余计算。若需要多次访问,可先
to_vector()或to_array(). - 链式视图:过深的链式调用会导致额外的迭代器包装。若性能敏感,可将中间结果缓存为
std::vector。 - 并行算法:从 C++20 起,
std::ranges::parallel适配器可与视图一起使用。
auto max = std::ranges::max(
vec | std::views::transform([](int n){ return n*n; }),
std::execution::par
);
6. 小技巧汇总
| 技巧 | 说明 |
|---|---|
链式 take 与 drop |
vec | std::views::drop(3) | std::views::take(5) 等价于 vec[3:8] |
| 多维数据 flatten | std::views::join 可将二维容器变为一维 |
| 索引访问 | std::views::iota 生成可迭代索引序列 |
组合 zip 与 transform |
std::views::zip(a,b) | std::views::transform([](auto&& pair){ return pair.first + pair.second; }) |
7. 结语
C++20 的范围视图为我们提供了强大且简洁的方式来处理序列数据。掌握视图适配器的组合与自定义,将使代码更加优雅、易读。实践中,记得关注性能开销,必要时缓存中间结果。希望本篇文章能帮助你在 C++20 之旅中更上一层楼。祝编码愉快!