C++20 的 <ranges> 库为我们提供了一套强大的、延迟求值的视图(views)与适配器(adapters),使得数据处理变得既简洁又高效。相比传统的 std::transform、std::copy_if 等函数,ranges 通过链式调用让代码更具可读性,同时避免了不必要的临时对象,提升了性能。下面我们将从基本概念、常用视图、适配器以及实际案例几个方面,深入探讨如何在 C++20 中充分利用 std::ranges。
1. 基本概念
- Range:任何满足
begin()与end()成员函数或全局函数的对象,或者能被std::begin()、std::end()接收的对象,都可以视为一个 Range。典型的如 `std::vector `、`std::array` 等。 - View:对一个 Range 的“视图”,不存储数据,只在访问时根据底层 Range 生成元素。典型的如
std::views::filter、std::views::transform等。 - Adaptor:对 View 的进一步包装,如
std::views::take、std::views::drop等。 - Pipe:通过
|操作符将多个视图、适配器连接起来,形成一个完整的数据处理链。
2. 常用视图(Views)
| 视图 | 功能 | 示例 |
|---|---|---|
std::views::all |
直接引用原始 Range | auto v = std::views::all(vec); |
std::views::iota |
生成数值序列 | auto seq = std::views::iota(1, 10); |
std::views::filter |
过滤元素 | auto evens = vec | std::views::filter([](int x){ return x%2==0; }); |
std::views::transform |
转换元素 | auto squares = vec | std::views::transform([](int x){ return x*x; }); |
std::views::reverse |
反转 | auto rev = vec | std::views::reverse; |
std::views::take / drop |
截取/跳过 | auto first3 = vec | std::views::take(3); |
3. 常用适配器(Adapters)
适配器主要作用是改变迭代器的特性,常见的有:
std::views::common:将不具备common_range的 Range 转为具备common_range,方便在for循环中使用。std::views::unique:去重(要求已排序)。std::views::zip(C++23):并行遍历多个 Range。
4. 延迟求值与性能
- 延迟求值:只有在真正访问元素(如
for循环、std::copy等)时,视图才会计算对应的元素。中间结果不会被立即产生,避免了临时容器。 - 复用:同一个 View 可以多次遍历,且每次遍历都是新的迭代器链,保证了数据的一致性。
- 内联优化:编译器能够对视图链进行内联展开,将多层调用压缩成单层循环,极大地提升执行速度。
5. 实际案例
5.1 统计整数序列中大于阈值的平方和
#include <iostream>
#include <vector>
#include <numeric>
#include <ranges>
int main() {
std::vector <int> data{1, 3, 5, 7, 9, 11};
int threshold = 5;
// 统计所有 > threshold 的平方和
auto sum_of_squares =
std::accumulate(
data | std::views::filter([threshold](int x){ return x > threshold; }) |
std::views::transform([](int x){ return x * x; }),
0);
std::cout << "Sum of squares: " << sum_of_squares << '\n';
}
5.2 生成斐波那契序列前 20 项
#include <iostream>
#include <ranges>
#include <vector>
int main() {
auto fib_seq = std::views::iota(0, 20) |
std::views::transform([](int n){
if (n < 2) return n;
int a = 0, b = 1, c;
for (int i = 2; i <= n; ++i) {
c = a + b;
a = b; b = c;
}
return b;
});
for (auto f : fib_seq)
std::cout << f << ' ';
std::cout << '\n';
}
5.3 统计字符串中所有单词出现次数
#include <iostream>
#include <sstream>
#include <unordered_map>
#include <ranges>
int main() {
std::string text = "hello world hello cpp ranges world";
std::istringstream iss{text};
std::unordered_map<std::string, int> freq;
std::for_each(
std::istream_iterator<std::string>{iss},
std::istream_iterator<std::string>{},
[&freq](const std::string& word){ ++freq[word]; });
for (auto [word, count] : freq)
std::cout << word << ": " << count << '\n';
}
6. 进阶使用
6.1 组合多个视图
auto processed = vec |
std::views::filter([](int x){ return x % 3 == 0; }) |
std::views::transform([](int x){ return x * 2; }) |
std::views::reverse |
std::views::common;
6.2 自定义视图
通过 std::ranges::view_facade 可以实现自定义的视图。示例:
template<typename Iter>
class even_view : public std::ranges::view_facade<even_view<Iter>> {
Iter curr_;
public:
even_view(Iter first, Iter last) : curr_{first} {
if (curr_ != last && (*curr_ & 1)) ++curr_;
}
auto begin() { return curr_; }
auto end() { return std::end(*this); }
private:
friend std::ranges::range_access;
Iter next(Iter i) {
// 寻找下一个偶数
for (++i; i != std::end(*this) && (*i & 1); ++i);
return i;
}
};
7. 小结
std::ranges让 C++20 的数据处理更接近函数式编程的风格,代码可读性大幅提升。- 通过链式视图与适配器,可在保持延迟求值的同时完成多步数据处理。
- 对性能要求较高的场景,ranges 通过消除不必要的临时对象和延迟计算,实现了高效的运行时表现。
掌握 std::ranges 后,你可以轻松完成复杂的数据筛选、转换与聚合任务,真正做到“少写代码,多做事”。祝你在 C++20 的世界里玩得愉快!