在 C++20 标准中,ranges 库为我们提供了全新的视图(view)概念,允许我们以惰性方式对容器进行链式变换。与传统的迭代器/算法组合相比,视图可以让代码更简洁、表达力更强,并且可以通过管道(|)操作符形成直观的流水线。本文将通过一个完整的实例来演示如何利用 ranges::view 实现对整数序列的过滤、映射、排序、去重等常见操作,并展示如何用自定义视图进一步扩展功能。
1. 基础视图:过滤(filter)与映射(transform)
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector <int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto result = nums
| std::ranges::views::filter([](int n){ return n % 2 == 0; }) // 只保留偶数
| std::ranges::views::transform([](int n){ return n * n; }) // 求平方
for (int x : result) std::cout << x << ' ';
}
运行结果:4 16 36 64 100
上述代码通过两次管道调用依次过滤偶数,然后对每个元素平方。注意,views::filter 和 views::transform 都是惰性视图,只有当我们遍历 result 时才会真正执行。
2. 排序与去重:视图无法直接完成
C++20 ranges 标准库并未为视图提供排序或去重的直接视图。通常需要先将视图转成容器再调用算法。示例:
#include <algorithm>
#include <vector>
#include <ranges>
auto sorted_unique = std::vector <int>(result.begin(), result.end());
std::ranges::sort(sorted_unique);
sorted_unique.erase(
std::unique(sorted_unique.begin(), sorted_unique.end()),
sorted_unique.end()
);
这样得到的 sorted_unique 既去重又排序。若想保持管道式写法,可使用 views::transform 生成临时容器后再排序:
auto sorted_unique =
result | std::ranges::to<std::vector>() | std::ranges::sort | std::ranges::unique;
但需要注意,to、sort、unique 并不是标准库中自带的视图,而是来自 cppcoro 或其他第三方库。
3. 自定义视图:increasing_view(只保留单调递增子序列)
标准库提供了大量视图,但若需要特殊逻辑,完全可以自己实现。下面演示一个 increasing_view:
#include <iostream>
#include <vector>
#include <ranges>
template<std::input_iterator Iter>
class increasing_view : public std::ranges::view_interface<increasing_view<Iter>> {
Iter first_, last_;
public:
using value_type = std::iter_value_t <Iter>;
increasing_view(Iter first, Iter last) : first_(first), last_(last) {}
class iterator {
Iter current_;
value_type prev_value_;
bool has_prev_ = false;
void advance() {
while (current_ != last_) {
value_type cur = *current_;
if (!has_prev_ || cur >= prev_value_) {
prev_value_ = cur;
has_prev_ = true;
return;
}
++current_;
}
}
public:
using iterator_category = std::input_iterator_tag;
using value_type = std::iter_value_t <Iter>;
using difference_type = std::iter_difference_t <Iter>;
using pointer = std::iter_pointer_t <Iter>;
using reference = std::iter_reference_t <Iter>;
iterator(Iter current, Iter last) : current_(current), last_(last) { advance(); }
reference operator*() const { return *current_; }
pointer operator->() const { return std::addressof(*current_); }
iterator& operator++() { ++current_; advance(); return *this; }
iterator operator++(int) { auto tmp = *this; ++(*this); return tmp; }
friend bool operator==(const iterator& a, const iterator& b) {
return a.current_ == b.current_;
}
friend bool operator!=(const iterator& a, const iterator& b) { return !(a == b); }
};
iterator begin() const { return iterator(first_, last_); }
iterator end() const { return iterator(last_, last_); }
};
template<std::input_iterator Iter>
auto increasing_view(Iter first, Iter last) {
return increasing_view <Iter>(first, last);
}
int main() {
std::vector <int> data = {3, 5, 2, 2, 8, 9, 7, 10, 12};
for (int v : increasing_view(data.begin(), data.end()))
std::cout << v << ' ';
}
输出:3 5 5 8 9 9 10 12
上述视图会保留所有非递减的子序列。实现时我们在内部维护一个 prev_value_,只在满足递增条件时才推进 current_。
4. 管道式组合:完整示例
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector <int> nums = {1,2,3,4,5,6,7,8,9,10};
auto processed =
nums
| std::ranges::views::filter([](int n){ return n % 2 == 0; })
| std::ranges::views::transform([](int n){ return n * n; })
| std::ranges::views::take(3) // 只取前三个
| std::ranges::views::increasing_view; // 自定义视图
for (int x : processed)
std::cout << x << ' ';
}
此时输出为 4 16 36,因为 take(3) 先截取前 3 个平方值,再通过 increasing_view 确保递增。
5. 小结
- 视图(view):惰性、无副作用,能够用管道式语法串联各种变换。
- 标准视图:
filter,transform,take,drop,reverse等已足够常用。 - 自定义视图:可以通过
view_interface轻松实现满足特定业务需求的视图。 - 管道组合:把视图串起来即可得到清晰、表达力强的流水线代码,极大提升可读性和可维护性。
在日常 C++ 开发中,充分利用 ranges::view 可以让代码更加优雅,减少显式循环与临时容器。下一步可以尝试将视图与并行算法(std::ranges::parallel::)结合,实现更高效的并行数据处理。祝编码愉快!