C++20引入了强大的范围(Ranges)库,它将标准算法与容器的概念更紧密地结合在一起,提供了更简洁、可读性更高的代码。下面我们将通过几个常见场景,演示如何利用Range来替代传统的迭代器循环。
1. 过滤(filter)
假设我们有一个整数向量,想要得到所有大于10的元素:
std::vector <int> data = {1, 12, 5, 18, 7, 30};
auto result = data
| std::views::filter([](int n){ return n > 10; });
for(int n : result) {
std::cout << n << ' '; // 输出: 12 18 30
}
std::views::filter 会创建一个惰性视图,只有在遍历时才会真正执行过滤逻辑。相比传统的 std::copy_if,代码更简洁。
2. 转换(transform)
如果需要将向量中的每个数平方,可以使用 std::views::transform:
auto squared = data | std::views::transform([](int n){ return n * n; });
for(int n : squared) {
std::cout << n << ' ';
}
这里的 squared 是一个懒执行的视图,实际计算在遍历时进行。
3. 组合视图
C++20 的 Range 视图支持链式组合,几乎可以把所有标准算法复写为视图组合。例如,想得到所有偶数的平方和:
auto sum_of_even_squares = std::accumulate(
data | std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * n; }),
0
);
std::cout << sum_of_even_squares << '\n';
这段代码先过滤偶数,再平方,最后使用 std::accumulate 求和。相比使用 std::transform + std::copy_if + std::accumulate 的三步流程,视图链条更短、更直观。
4. 分区(partition)
如果需要把向量分成满足条件和不满足条件两部分,可以使用 std::views::take_while 或 std::views::drop_while。但更直观的做法是使用 std::ranges::partition(C++23 之后正式纳入标准):
std::ranges::partition(data, [](int n){ return n % 2 == 0; });
分区后,偶数会被放到前面,奇数放到后面,原始顺序在各自组内保持不变。
5. 逆序(reverse)
想要逆序遍历容器,C++20 提供了 std::views::reverse:
for(int n : data | std::views::reverse) {
std::cout << n << ' ';
}
与手动使用 rbegin()/rend() 的方式相比,视图更统一。
6. 迭代器适配器(take、drop、zip)
std::views::take(n):取前 n 个元素。std::views::drop(n):忽略前 n 个元素。std::views::zip(C++23):把两个容器按索引配对。
auto first_three = data | std::views::take(3);
auto skip_two = data | std::views::drop(2);
7. 何时使用视图,何时使用算法
视图非常适合读操作:过滤、变换、取子集等。若需要修改原始容器中的元素,还是使用普通算法或迭代器。
另外,视图是惰性的。只要不遍历,内部函数不会被调用。若需要多次访问,建议使用 std::ranges::to<std::vector>() 或 std::vector 直接存储结果。
8. 小结
- 视图让算法更具表达性,减少模板噪声。
- 通过链式组合,能用一行代码完成复杂操作。
- 惰性求值提升效率,避免不必要的拷贝。
C++20 的 Range 库为现代 C++ 编程提供了更优雅、更安全的方式。下次写循环、过滤或变换时,别忘了先试试 Range 视图,或许能让你的代码更简洁。