C++20 为标准库引入了 ranges 与 views 的概念,使得对容器、迭代器等序列数据的操作变得更像函数式编程。相比传统的 std::copy_if、std::transform 等算法,ranges 的语义更直观,同时可以在编译期优化,提升性能。下面从基本概念、常用视图、以及实际编码技巧四个角度,来系统地介绍这部分内容。
1. 基础概念
1.1 Range
在 C++20 中,Range 是一个对象,能够提供 begin() 和 end() 迭代器,或者直接满足 std::ranges::range 语义。几乎所有容器(std::vector、std::list 等)以及数组都已经是 Range。
1.2 View
View 是对 Range 的“视图”,它不拥有数据,而是对底层 Range 进行惰性变换。典型的 View 有 std::views::filter、std::views::transform、std::views::take 等。由于 View 本身不存储元素,它可以链式组合,产生一个新的 Range,直到最终消费。
1.3 容器适配器
与 C++17 的 std::ranges::subrange 相比,C++20 更倾向于使用 std::views::all 对任何 Range 进行适配,保证后续操作能统一处理。
2. 常用 View 详解
| View | 用途 | 示例 |
|---|---|---|
filter |
过滤元素 | auto evens = std::views::filter([](int n){return n%2==0;}); |
transform |
变换元素 | auto squared = std::views::transform([](int n){return n*n;}); |
take |
取前 N 个 | auto first3 = std::views::take(3); |
drop |
跳过前 N 个 | auto skip2 = std::views::drop(2); |
reverse |
反转 | auto rev = std::views::reverse; |
concat |
合并 | auto merged = std::views::concat(vec1, vec2); |
zip |
并行遍历 | auto zipped = std::views::zip(vec1, vec2); |
注意:
zip在 C++20 标准库中并不存在,需自行实现或使用第三方库。
3. 代码实战
3.1 过滤奇数并平方
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
int main() {
std::vector <int> nums{1,2,3,4,5,6,7,8,9,10};
auto processed = nums
| std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * n; });
std::for_each(processed.begin(), processed.end(),
[](int n){ std::cout << n << ' '; });
// 输出: 4 16 36 64 100
}
这里
processed是一个惰性视图,只有在for_each访问时才真正执行过滤与变换。
3.2 取前 5 个平方数
auto top5 = processed | std::views::take(5);
std::ranges::copy(top5, std::ostream_iterator <int>(std::cout, " "));
// 输出: 4 16 36 64 100
3.3 反转序列
auto reversed = nums | std::views::reverse;
std::ranges::copy(reversed, std::ostream_iterator <int>(std::cout, " "));
4. 性能与编译期优化
- 惰性求值:除非对视图进行终止操作(如
copy、for_each),否则不会产生任何运行时开销。 - 编译期常量:如果视图参数是常量表达式,编译器可以在编译期展开,进一步减少运行时成本。
- SFINAE 与 constexpr:C++20 的
std::ranges::enable_view让 View 成为 constexpr 友好,适用于constexpr std::vector等场景。
5. 常见坑点
- 不支持所有容器:例如
std::forward_list的begin()是bidirectional_iterator,不支持reverse。 - 多次复制:
std::views::all在某些情况下会产生额外的包装对象,导致链式调用中不必要的复制。 - 递归视图:链式深度过大时,编译器可能会生成巨大的模板实例化,导致编译时间拉长。
6. 结语
C++20 的 ranges 与 views 为集合操作注入了“函数式”的语法糖,既保持了 C++ 传统的性能优势,又极大提升了代码可读性与维护性。随着标准库的完善,未来的 C++ 程序员将不再需要手写繁琐的循环和算法,而是能用更简洁、直观的方式表达复杂的逻辑。
如果你在实际项目中遇到对 View 组合或性能瓶颈的问题,欢迎继续提问。祝编码愉快!