在 C++20 中,std::ranges 库的引入彻底改变了我们处理容器、序列和算法的方式。相比传统的 STL,std::ranges 让算法的调用更加直观、表达式更简洁,同时大大提升了可组合性。本文将从几个关键概念入手,展示如何利用 std::ranges 改进代码。
1. 范围视图(Views)
范围视图是一种惰性评估的容器适配器,它不复制元素,只在访问时生成结果。例如,std::views::filter 与 std::views::transform 可以组合使用:
#include <vector>
#include <ranges>
#include <iostream>
int main() {
std::vector <int> data = {1, 2, 3, 4, 5, 6};
auto even = data | std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * x; });
for (int v : even) {
std::cout << v << ' '; // 输出 4 16 36
}
}
这里,data 本身不被修改,even 是一个轻量级的视图。惰性求值意味着只有在遍历时才会执行过滤和变换,避免了不必要的复制开销。
2. 范围适配器组合(Range Adapters)
C++20 允许通过管道符 | 将适配器串联,形成链式调用。常见的适配器包括:
std::views::drop(n):跳过前 n 个元素。std::views::take(n):取前 n 个元素。std::views::reverse:反转序列。std::views::keys/std::views::values:用于std::map或std::unordered_map。
使用 take 与 drop 的组合可以轻松实现分页:
constexpr std::size_t pageSize = 10;
auto page1 = data | std::views::take(pageSize); // 前 10 个
auto page2 = data | std::views::drop(pageSize) | std::views::take(pageSize); // 第 11-20 个
3. 范围算法(Algorithms)
std::ranges 重新定义了 STL 算法,接收范围而非迭代器对。其优点是不需要显式指定开始/结束迭代器,减少错误。示例:
#include <ranges>
#include <algorithm>
auto [min_it, max_it] = std::ranges::minmax(data);
如果你只想获取最小值,直接用 std::ranges::min:
int min_val = std::ranges::min(data); // 返回 1
4. 范围工具(Tools)
std::ranges::subrange:把两个迭代器包装成范围,方便与视图配合使用。std::ranges::view_interface:用于自定义视图时的基类。std::ranges::iota_view:生成等差数列。
自定义视图可以让你在保持惰性的同时,实现复杂的业务逻辑。例如,生成一个斐波那契数列视图:
struct fib_view : std::ranges::view_interface <fib_view> {
class iterator {
unsigned long long a{0}, b{1};
public:
using iterator_category = std::input_iterator_tag;
using value_type = unsigned long long;
using difference_type = std::ptrdiff_t;
unsigned long long operator*() const { return a; }
iterator& operator++() { std::swap(a, b); b += a; return *this; }
};
iterator begin() const { return {}; }
std::ranges::sentinel_t<std::vector<int>> end() const { return {}; }
};
5. 性能与内存
std::ranges 通过惰性求值,避免了中间容器的创建,降低了内存占用和复制成本。同时,适配器链可以在编译期进行优化,生成高效代码。需要注意的是,过度链式调用可能导致编译时间增长,但大多数项目中收益远大于成本。
6. 典型案例
6.1 过滤、排序与取前 N
auto top3 = data | std::views::filter([](int x){ return x > 10; })
| std::views::sort(std::greater{})
| std::views::take(3);
6.2 计算容器中所有字符串的长度平均值
auto lengths = strings | std::views::transform([](const std::string& s){ return s.size(); });
double avg = std::accumulate(lengths.begin(), lengths.end(), 0.0) / std::ranges::size(lengths);
7. 结语
std::ranges 的出现,让 C++ 代码在保持性能的同时变得更易读、更易维护。它将算法与数据分离,提供了更自然的表达式,尤其适合大规模数据处理与函数式风格的编程。熟练掌握 std::ranges 的视图、适配器与算法,你将能写出更优雅、更高效的 C++20 代码。