C++20 引入了 Ranges 库,为处理容器、迭代器、算法等提供了更直观、更安全、更高效的接口。本文将带你从基础语法开始,逐步探索 Ranges 的高级用法,并给出实战示例,帮助你在项目中更好地利用这项技术。
1. Ranges 基础概念
1.1 Range vs Iterator
- Iterator:遍历元素的指针/引用,负责“谁来访问”,但不关心“遍历什么”。
- Range:由起始和结束迭代器组成的可遍历对象,更像是“容器的子集”。
Ranges 将 操作(如 filter, transform, take, drop)与 范围 分离,使得算法可以直接作用于范围而不是单独的迭代器。
1.2 标准 Ranges 头文件
#include <ranges> // 所有范围相关的视图、操作符
#include <algorithm> // 传统算法
1.3 范围视图(View)
- view::filter:按谓词过滤元素。
- view::transform:按函数映射元素。
- view::take / drop:取前 N 或忽略前 N 个元素。
- view::reverse:反转迭代顺序。
视图是 惰性 的:真正访问元素时才会执行。
2. 经典示例:过滤与映射
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
int main() {
std::vector <int> nums{1, 2, 3, 4, 5, 6};
auto even_squares = nums
| std::ranges::views::filter([](int n){ return n % 2 == 0; })
| std::ranges::views::transform([](int n){ return n * n; });
for (int x : even_squares) {
std::cout << x << ' ';
}
// 输出: 4 16 36
}
filter先筛选偶数。transform对每个偶数求平方。- 迭代器链式调用,语义清晰。
3. 视图与容器:subrange 与 iota_view
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector <char> letters{'a','b','c','d','e','f','g','h'};
// 取子范围
auto sub = std::ranges::subrange(letters.begin() + 2, letters.begin() + 6);
for (char c : sub) std::cout << c; // 输出 "cdef"
// 生成 1..10
auto numbers = std::ranges::iota_view(1, 11);
for (int n : numbers) std::cout << n << ' '; // 1 2 3 ... 10
}
subrange 让你在不拷贝容器的情况下取其一部分。
iota_view 生成连续整数范围,常用于循环。
4. 高级技巧:组合与管道化
4.1 复合视图
auto complex = std::ranges::views::iota(0, 100)
| std::ranges::views::filter([](int n){ return n % 3 == 0; })
| std::ranges::views::transform([](int n){ return std::string(n, '*'); })
| std::ranges::views::reverse;
这段代码生成 0-99 内能被 3 整除的数字,转换为对应数量的星号字符串,最终逆序输出。
4.2 std::ranges::join 与多维容器
std::vector<std::vector<int>> vv{{1,2},{3,4,5},{6}};
auto flattened = std::ranges::views::join(vv);
for (int x : flattened) std::cout << x << ' '; // 1 2 3 4 5 6
join 将二维结构“拉平”,类似 flatten()。
4.3 std::ranges::to(C++23)
在 C++23 中可直接将视图转换为容器:
auto vec = std::ranges::to<std::vector>(even_squares);
此功能可在 C++20 中通过自定义 to_container 实现。
5. 性能与懒加载
- 惰性求值:视图不会在定义时执行,而是在遍历时执行,减少不必要的计算。
- 按需迭代:一次遍历即可完成过滤与映射,减少临时容器。
- 编译期推导:视图的类型完全由编译器推导,运行时无额外开销。
实践建议:对大数据集使用视图链式操作;若需要多次遍历,请先 materialize(转为容器)或使用 std::ranges::to.
6. 与旧有 STL 的互操作
std::vector <int> v{1,2,3,4,5};
auto v_view = std::ranges::views::all(v); // 将容器包装为范围
auto sum = std::accumulate(v_view.begin(), v_view.end(), 0);
// 等价于 std::accumulate(v.begin(), v.end(), 0);
views::all 可将任何容器或迭代器对接为 Range,保持兼容性。
7. 常见陷阱
| 误区 | 说明 |
|---|---|
直接对 std::vector 使用 | views::filter |
vector 本身不是 Range,需要 views::all 包装 |
忘记 std::ranges::views::common |
某些视图的迭代器不是完整范围,需要 common 强制使其成为完整范围 |
误用 take(0) |
返回空范围,注意避免后续对空范围的操作导致异常 |
8. 小结
- Ranges 让算法与容器解耦,语义更清晰。
- 视图是惰性、组合性强的工具,能显著减少临时对象与复制。
- 在 C++20 项目中加入 Ranges 可提升代码可读性与性能。
接下来,你可以尝试将现有项目中的 for 循环、std::copy_if 等改写为 Ranges,感受不同的编码体验。祝你编码愉快!