在 C++20 引入的 库后,操作序列变得更像函数式编程。通过 std::views::filter、std::views::transform 等视图(view),可以在不产生临时容器的情况下,链式地对数据进行过滤、变换、排序等操作。下面我们用一个完整的例子演示如何在 C++20 中使用 std::ranges 来完成“从整数序列中过滤出偶数,再将其平方,最后求和”的任务,并比较传统方式与新方式的代码可读性与性能差异。
1. 基础示例:传统方式
#include <vector>
#include <numeric>
#include <iostream>
int main() {
std::vector <int> nums{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector <int> evens;
for (int n : nums)
if (n % 2 == 0) evens.push_back(n);
std::vector <int> squares;
for (int e : evens)
squares.push_back(e * e);
int sum = std::accumulate(squares.begin(), squares.end(), 0);
std::cout << "sum = " << sum << '\n';
}
该代码在每一步都显式创建了中间容器,阅读时需要逐行跟踪 evens、squares 的生成过程。
2. 使用 std::ranges 的链式写法
#include <vector>
#include <numeric>
#include <iostream>
#include <ranges>
int main() {
std::vector <int> nums{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = std::ranges::accumulate(
nums
| std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * n; }),
0);
std::cout << "sum = " << sum << '\n';
}
关键点说明
|运算符用于将视图链式组合,读取时从左到右,像流水线一样。std::views::filter接受一个可调用对象,返回一个可迭代对象,仅包含满足条件的元素。std::views::transform对每个元素应用变换函数,返回变换后的值。std::ranges::accumulate与std::accumulate类似,但接受任何可迭代对象。
3. 性能对比
使用视图时,C++20 的实现通常会采用 惰性求值(lazy evaluation):在调用 accumulate 时,整个视图链会按需逐元素处理,避免了中间容器的构造。实际上编译器会生成类似以下的伪代码:
for each element e in nums:
if e % 2 == 0:
sum += e * e;
这与手写的循环完全等价,且编译器可以进一步优化,例如消除多余的函数调用、指令重排等。
如果使用传统方式,虽然可以通过 reserve 或 in-place 操作来减少临时容器,但代码仍然显得冗长且不够直观。通过 std::ranges,我们将关注点从“如何存储中间结果”转移到“如何表达业务逻辑”,代码更易维护。
4. 进阶使用:自定义视图
除了标准视图,还可以自己实现一个简单的视图来完成特定需求。例如,下面的 std::views::inplace_transform 直接在原序列上进行变换,而不产生新容器:
namespace std::views {
template<class F>
struct inplace_transform_view {
std::vector <int>& data;
F f;
auto begin() { return data.begin(); }
auto end() { return data.end(); }
struct iterator {
std::vector <int>::iterator it;
F* f;
iterator(std::vector <int>::iterator it, F* f) : it(it), f(f) {}
int& operator*() const { return *it; }
iterator& operator++() { ++it; return *this; }
bool operator!=(const iterator& other) const { return it != other.it; }
};
iterator begin_it() { return {begin(), &f}; }
iterator end_it() { return {end(), &f}; }
};
template<class F>
auto inplace_transform(std::vector <int>& v, F f) {
return inplace_transform_view <F>{v, f};
}
}
使用方式:
std::vector <int> nums{1,2,3,4,5,6};
for (auto& x : std::views::inplace_transform(nums, [](int& n){ n *= n; })) {}
// nums 现在已经是平方后的序列
5. 小结
std::ranges让 C++20 的容器与算法更像函数式语言的管道。- 通过
views::filter、views::transform等组合,代码可读性大幅提升,且性能不落后于手写循环。 - 视图采用惰性求值,避免不必要的中间容器。
- 自定义视图也可根据需求扩展更细粒度的功能。
如果你正在维护一段需要多次对数据进行变换的 C++ 代码,建议先把逻辑拆解成一组视图,尝试用 std::ranges 重写。你会发现,代码不仅更简洁,也更容易推理和测试。祝编码愉快!