在 C++20 之前,处理序列的方式通常是手写循环、使用 STL 算法配合迭代器、或利用 lambda 表达式来实现过滤、转换等操作。C++20 引入了 Ranges(范围)库,为我们提供了更直观、更安全、更强大的序列处理工具。本文将从概念、语法、实例三个维度,帮助你快速掌握并应用 Ranges。
1. 什么是 Ranges?
Ranges 不是一个新算法,而是一套围绕范围(Range)概念的工具。范围相当于一个有起点与终点的序列,可以是容器、迭代器、或自定义生成器。Rangess 通过组合 View(视图)与 Pipe(管道)实现对范围的惰性变换。
- View:对原始范围的无副作用、惰性的包装。例如
std::views::filter、std::views::transform。 - Pipe:通过
|运算符串联多个 View,形成链式调用。
与传统算法的“迭代器-算法”组合相比,Ranges 让代码更具声明性,意图更明确。
2. 基础语法
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
int main() {
std::vector <int> v{1,2,3,4,5,6,7,8,9,10};
// 过滤偶数,然后平方
auto result = v | std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * x; });
for (int x : result) std::cout << x << ' ';
std::cout << '\n';
}
关键点
std::views::filter接收一个谓词,返回满足条件的子范围。std::views::transform接收一个函数,返回转换后的子范围。result本身不存储数据,而是惰性评估;当for循环遍历时才真正执行。
3. 进阶示例:链式管道与自定义 View
3.1 组合多重操作
auto processed = v | std::views::filter([](int x){ return x > 3; })
| std::views::transform([](int x){ return x * 10; })
| std::views::take(5);
std::views::take只保留前 5 个元素。- 所有 View 都是惰性执行,只有在需要结果时才会一次性遍历一次。
3.2 自定义 View
struct square_view {
auto begin(auto&& rng) const {
return std::ranges::begin(rng);
}
auto end(auto&& rng) const {
return std::ranges::end(rng);
}
struct iterator {
std::ranges::iterator_t<decltype(std::declval<auto&&>())> it;
auto operator*() const { return (*it) * (*it); }
iterator& operator++() { ++it; return *this; }
bool operator==(const iterator& other) const { return it == other.it; }
};
};
auto sq = v | square_view{};
通过自定义 iterator,我们实现了一个完整的 View,使得任何范围都能被平方。
4. 性能与兼容性
- 惰性求值:只有在遍历时才会真正计算,避免不必要的中间结果。
- 内联优化:现代编译器对 Ranges 的内联非常友好,往往可以消除额外的函数调用。
- C++20 兼容:需要 C++20 标准,使用
-std=c++20编译。
5. 实战场景
- 数据清洗:读取文件行后过滤空行、转换为数字并做统计。
- 并行计算:
std::views::transform与std::execution::par搭配,实现并行映射。 - 管道式编程:把复杂业务流程拆成一系列 View,形成清晰的“数据流”图。
6. 小结
C++20 Ranges 通过 View 与 Pipe 的组合,提供了更简洁、可组合、惰性求值的序列处理方式。掌握基本的 filter、transform、take 等 View,并了解如何自定义 View,你就能在项目中显著提升代码可读性和维护性。
赶快尝试把 Ranges 引入你现有的项目,体验“声明式”编程的乐趣吧!