在 C++20 之前,处理容器时往往需要手写循环或使用第三方库(如 Boost.Range)来完成链式的筛选、映射和折叠操作。随着 std::ranges 的引入,标准库提供了类似于 JavaScript Array.prototype.filter、map、reduce 的函数,且这些函数可以像流水线一样组合。本文将从基础到进阶,系统演示如何利用 std::ranges 实现高效、可读、类型安全的数据处理。
1. 先决条件
- 支持 C++20 的编译器(如 GCC 10+, Clang 11+, MSVC 16.10+)。
- 头文件:
#include <iostream> #include <vector> #include <ranges> #include <algorithm> #include <numeric> #include <string>
2. 基础示例:筛选与映射
假设我们有一个整数向量,想要:
- 筛选出偶数。
- 把每个偶数乘以 2。
- 输出结果。
传统写法:
std::vector <int> data = {1,2,3,4,5,6,7,8};
std::vector <int> result;
for(int n : data)
if(n % 2 == 0)
result.push_back(n * 2);
for(int n : result)
std::cout << n << ' ';
使用 std::ranges:
auto data = std::vector <int>{1,2,3,4,5,6,7,8};
auto even_times_two = data
| std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * 2; });
for(int n : even_times_two)
std::cout << n << ' ';
这里的 | 符号让视图链式组合,filter 与 transform 返回新的视图,迭代时才真正计算。
3. 结合 std::ranges::fold_left 实现折叠
折叠(reduce)是把一个序列压缩成单一值。C++20 标准库提供 std::ranges::fold_left(在旧版本中是 std::accumulate),但 fold_left 更加通用:
int sum = std::ranges::fold_left(
even_times_two, // 视图
0, // 初始值
std::plus{} // 运算符
);
std::cout << "\nSum: " << sum << '\n';
如果想计算乘积:
int product = std::ranges::fold_left(
even_times_two,
1,
std::multiplies{}
);
std::cout << "Product: " << product << '\n';
4. 处理多种容器与自定义类型
std::ranges 的优势之一是对任何符合 Range 要求的容器都适用。下面用 std::vector<std::string> 计算所有单词长度之和:
std::vector<std::string> words = {"hello","world","cpp","ranges"};
int total_len = std::ranges::fold_left(
words | std::views::transform([](const std::string& s){ return s.size(); }),
0,
std::plus{}
);
std::cout << "Total length: " << total_len << '\n';
5. 复合视图:去重、排序、取前 N
std::ranges 还提供 std::views::unique(需先排序)和 std::views::take:
auto processed = data
| std::views::transform([](int n){ return n * n; }) // 取平方
| std::views::filter([](int n){ return n % 3 == 0; }) // 只保留能被3整除的
| std::views::common(); // 把视图变成可复用
auto sorted = processed | std::views::common() | std::views::take(5);
for(int n : sorted)
std::cout << n << ' ';
注意:
views::unique只能在已排序的序列上使用,否则会得到意外结果。
6. 性能考量
- 惰性求值:视图链式组合是惰性的,只有真正迭代时才计算,减少不必要的中间容器。
- 分配优化:使用
views::common可避免多次遍历。 - 自定义视图:如果需要更复杂的处理,可通过
std::views::transform+ 自定义 lambda 或std::views::filter+ 自定义 predicate 组合。
7. 进阶:自定义 view(如“奇偶分离”)
假设我们想要一个视图,能够一次遍历得到两个序列:偶数和奇数。可以自定义一个 view:
#include <ranges>
template<class Rng>
struct even_odd_view : std::ranges::view_interface<even_odd_view<Rng>> {
Rng base_;
even_odd_view(Rng r) : base_(std::move(r)) {}
auto begin() const { return std::ranges::begin(base_); }
auto end() const { return std::ranges::end(base_); }
};
template<class Rng>
even_odd_view <Rng> even_odd(Rng&& r) {
return {std::forward <Rng>(r)};
}
然后:
auto evens = data | std::views::filter([](int n){ return n % 2 == 0; });
auto odds = data | std::views::filter([](int n){ return n % 2 == 1; });
for(int n : evens) std::cout << "E:" << n << ' ';
for(int n : odds) std::cout << "O:" << n << ' ';
由于
std::ranges::views生态庞大,很多自定义功能已在第三方库中实现,例如range-v3,但标准库的ranges已经足够日常使用。
8. 结语
std::ranges 让 C++ 的数据处理更像函数式编程风格,代码更简洁、可组合且类型安全。掌握视图链式组合、惰性求值以及标准折叠函数后,你可以轻松地在 C++ 项目中实现复杂的数据流,而无需担心性能或可读性。尝试将日常项目中的循环替换为 std::ranges,你会发现代码大幅简化且更易维护。