在 C++20 中引入的 Range Views 为容器遍历和变换提供了一套无缝、懒执行的工具链。相比传统的迭代器循环,Views 让代码更具可读性、可组合性,并在内部保持了高效的延迟求值。本文将从基础概念入手,示例演示常用 Views 的使用方式,并给出实际场景中的最佳实践建议。
1. 视图(View)是什么?
视图是对容器或范围的非所有者包装,它不存储数据,而是对底层元素进行“视图化”处理。典型的操作包括:过滤 (filter)、映射 (transform)、切片 (slice)、反向 (reverse)、组合 (join) 等。所有这些操作都是 懒惰 的,只有在真正需要遍历时才会触发计算。
| 视图 | 功能 | 延迟求值 |
|---|---|---|
std::views::filter |
过滤元素 | 是 |
std::views::transform |
映射元素 | 是 |
std::views::take |
截取前 N 个元素 | 是 |
std::views::drop |
跳过前 N 个元素 | 是 |
std::views::reverse |
逆序遍历 | 是 |
std::views::stride |
步长访问 | 是 |
注意:Views 的定义不需要包含头文件 `
`;但要使用 `std::views` 命名空间,需要 `#include `。
2. 基础示例
2.1 过滤奇数
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector <int> data{1, 2, 3, 4, 5, 6};
auto odd = data | std::views::filter([](int x){ return x % 2 == 1; });
for (int n : odd)
std::cout << n << ' ';
// 输出: 1 3 5
}
2.2 映射平方
auto squares = data | std::views::transform([](int x){ return x * x; });
for (int n : squares)
std::cout << n << ' ';
2.3 组合过滤与映射
auto even_squares = data
| std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * x; });
for (int n : even_squares)
std::cout << n << ' '; // 输出: 4 16 36
3. 延迟求值的好处
- 性能:仅在遍历时执行操作,避免不必要的临时对象创建。
- 内存占用:不产生额外容器,减少内存使用。
- 组合灵活:通过管道符
|可以自由组合,生成新的视图链。
实践:如果你需要对同一容器多次做不同的筛选/变换,最好使用 Views 生成一个临时范围再遍历,而不是每次都复制或构造临时容器。
4. 与传统算法的对比
| 传统方式 | Views 方式 |
|---|---|
std::copy_if + std::transform |
| std::views::filter | std::views::transform |
| 需要临时容器 | 只需一次遍历 |
| 代码可读性低 | 代码直观、链式写法 |
5. 常见错误与调试技巧
-
**忘记 `#include
`** 编译器报错:`’views’ is not a member of ‘std’`。 解决:在文件顶部加 `#include `。 -
闭包捕获过大导致不必要的复制
例如auto big_view = data | std::views::transform([](const std::string& s){ return s; });
这里闭包捕获的是data的引用,导致每次访问都获取data。若闭包捕获了大对象,建议使用auto&&或std::move传递。 -
对非随机访问容器使用
reverse需要注意
std::vector支持reverse,但std::list不支持。确保容器满足RandomAccessRange。
6. 进阶技巧
6.1 自定义 View
template<std::ranges::input_range R>
auto my_view(R&& r) {
auto it = std::ranges::begin(r);
auto end = std::ranges::end(r);
return std::views::filter([&](auto x){ return *it++ == x; });
}
6.2 使用 std::views::join 合并多容器
std::vector<std::vector<int>> vv{{1,2},{3,4,5},{6}};
auto joined = vv | std::views::join;
for (int n : joined)
std::cout << n << ' '; // 输出: 1 2 3 4 5 6
7. 总结
C++20 的 Range Views 提供了一种极具表达力且高效的方式来处理容器数据。它们:
- 无所有权:不拷贝、不占用额外内存。
- 懒求值:仅在需要时才执行。
- 链式组合:代码简洁、可读性强。
在日常项目中,建议:
- 优先使用 Views 进行过滤、映射、切片等操作。
- 对于需要多次访问同一视图的情况,缓存 生成的视图。
- 结合 C++20 三点改进:
std::ranges::views::iota,std::views::take_while,std::views::drop_while等,进一步简化逻辑。
希望本文能帮助你在 C++20 项目中更好地利用 Range Views,实现代码既优雅又高效。祝编码愉快!