在 C++20 中,标准库新增了强大的 std::ranges 组件,为范围(range)操作提供了统一而灵活的接口。结合 for 循环中的范围基(range-based for),可以轻松实现更高效、可读性更强的代码。本文将从基础语法、常用范围视图、管道操作符以及性能注意点等方面,系统讲解如何在 C++20 中利用 std::ranges 进行范围处理。
1. 基础语法回顾
#include <vector>
#include <iostream>
int main() {
std::vector <int> vec{1,2,3,4,5};
// 传统 range-based for
for (int n : vec) {
std::cout << n << ' ';
}
// C++20 仍然可用相同语法
for (int n : vec) {
std::cout << n << ' ';
}
}
C++20 仍支持传统的 for (auto& x : collection),但它们在内部使用了 std::begin/std::end,与 std::ranges 并不冲突。差别主要体现在我们可以直接在 std::ranges 提供的视图上使用同样的语法:
#include <ranges>
#include <vector>
int main() {
std::vector <int> vec{1,2,3,4,5};
// 直接对视图使用范围 for
for (int n : vec | std::ranges::views::filter([](int v){ return v % 2 == 0; })) {
std::cout << n << ' ';
}
}
2. 常用视图(Views)
| 视图 | 说明 | 代码示例 |
|---|---|---|
views::filter |
过滤元素 | vec | std::ranges::views::filter([](int v){ return v > 2; }) |
views::transform |
转换元素 | vec | std::ranges::views::transform([](int v){ return v * v; }) |
views::take |
取前 n 个 | vec | std::ranges::views::take(3) |
views::drop |
跳过前 n 个 | vec | std::ranges::views::drop(2) |
views::reverse |
反转 | vec | std::ranges::views::reverse |
views::enumerate |
关联索引 | vec | std::ranges::views::enumerate |
views::zip |
并列 | std::views::zip(vec1, vec2)(C++23 版本) |
视图是惰性求值的,即不立即执行操作,而是在迭代时一次性处理。由于其惰性特性,链式组合可以实现高效的数据流。
3. 管道操作符 |
C++20 引入的管道操作符 | 让视图链式调用变得直观:
auto filtered = vec | std::ranges::views::filter([](int v){ return v % 2 == 0; });
auto squared = filtered | std::ranges::views::transform([](int v){ return v * v; });
管道操作符的使用类似 Unix shell 的管道,每一步返回一个新的视图对象,最终可传递给范围 for 或标准算法。
4. 与标准算法配合
C++20 还提供了新的 ranges 版本的算法,例如 std::ranges::for_each、std::ranges::copy 等。这些算法接受一个视图作为范围,避免了额外的复制。
#include <ranges>
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector <int> vec{1,2,3,4,5};
std::ranges::for_each(
vec | std::ranges::views::filter([](int v){ return v % 2 != 0; }),
[](int v){ std::cout << v << ' '; }
);
}
使用 std::ranges::copy 将视图内容拷贝到目标容器:
std::vector <int> target;
std::ranges::copy(
vec | std::ranges::views::transform([](int v){ return v * 2; }),
std::back_inserter(target)
);
5. 性能注意点
| 场景 | 细节 | 建议 |
|---|---|---|
| 视图链式 | 由于惰性求值,单次迭代会多次访问底层容器 | 适用于单遍或短路操作,避免多重遍历 |
| 需要临时结果 | 如需要多次访问 | 可使用 std::ranges::views::common 或 std::ranges::to<std::vector>() |
| 并行算法 | C++20 并行化 ranges 算法 |
需要确保底层容器线程安全 |
6. 典型案例:过滤、排序与取前 N
#include <vector>
#include <ranges>
#include <algorithm>
#include <iostream>
int main() {
std::vector <int> data{5, 2, 8, 1, 9, 3, 4};
auto result = data
| std::ranges::views::filter([](int v){ return v % 2 == 0; }) // 只保留偶数
| std::ranges::views::transform([](int v){ return v * v; }) // 平方
| std::ranges::views::sort(); // 排序
// 取前 3 个
std::vector <int> top3;
std::ranges::copy(
result | std::ranges::views::take(3),
std::back_inserter(top3)
);
for (int x : top3) std::cout << x << ' ';
}
7. 小结
- 视图:惰性、无副作用、可链式组合,适用于单遍处理。
- 管道:语法简洁,易于阅读。
- 算法:
ranges版本的算法与视图配合使用,避免不必要的复制。 - 性能:在需要多次访问同一数据时,适度复制或使用
common视图。
C++20 的 std::ranges 让范围处理变得更像函数式编程,既保持了 C++ 的性能优势,又提供了更高层次的抽象。熟练掌握它,将大大提升代码的可读性与可维护性。