在 C++20 标准发布后,标准库中的 ranges 模块为容器的操作提供了更直观、更灵活的方式。相比传统的迭代器和算法组合,ranges 让代码更接近自然语言,且具备更好的可组合性和延迟求值特性。本文将从实战角度,演示如何在日常项目中应用 std::ranges,包括切片、视图、管道语法以及自定义适配器。
1. 什么是 ranges
ranges 主要由两部分构成:视图(view)和适配器(adaptor)。视图是一种轻量级的“虚”容器,它不持有数据,而是通过组合已有容器和适配器实现惰性求值;适配器则是对容器或视图进行变换、筛选、分组等操作的工具。
2. 基础使用:切片与过滤
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector <int> vec{1, 2, 3, 4, 5, 6};
// 取前四个元素
auto first_four = vec | std::ranges::views::take(4);
for (auto v : first_four) std::cout << v << ' '; // 输出 1 2 3 4
// 过滤偶数
auto evens = vec | std::ranges::views::filter([](int x){ return x % 2 == 0; });
for (auto v : evens) std::cout << v << ' '; // 输出 2 4 6
}
这里的 | 操作符是管道语法(pipeline),它把前一个容器传递给后一个适配器,形成链式调用。
3. 自定义适配器:去重视图
标准库提供了 views::unique,但如果你想在自定义排序或比较时去重,可以写一个适配器。
#include <ranges>
#include <vector>
#include <iostream>
#include <algorithm>
struct case_insensitive_less {
bool operator()(const std::string& a, const std::string& b) const {
return std::lexicographical_compare(
a.begin(), a.end(), b.begin(), b.end(),
[](char x, char y){ return std::tolower(x) < std::tolower(y); });
}
};
int main() {
std::vector<std::string> words{"Apple","apple","Banana","banana","Cherry"};
auto sorted = words | std::ranges::views::transform([](auto& s){ return s; })
| std::ranges::views::common; // 转成容器以便排序
std::vector<std::string> result;
std::ranges::inserter(result, result.end()) =
sorted | std::ranges::views::unique(case_insensitive_less{});
for(auto &s : result) std::cout << s << ' '; // 输出 Apple Banana Cherry
}
此示例展示了如何在视图链中插入自定义比较器,实现大小写不敏感的去重。
4. 延迟求值的优势
传统算法在执行前会遍历整个容器,而 ranges 的惰性求值意味着只有真正需要元素时才会计算。
auto large = std::views::iota(1, 1000000)
| std::ranges::views::filter([](int x){ return x % 1000 == 0; });
auto sum = std::accumulate(large.begin(), large.end(), 0LL);
// 只会计算 1000、2000、... 1000000 共 1000 次
这在需要组合多个过滤、映射时尤其高效。
5. 与 STL 算法的兼容
多数 STL 算法都接受范围(range)而非迭代器对。使用 ranges,代码更简洁。
std::vector <int> data{1,2,3,4,5};
auto result = std::ranges::transform_reduce(
data | std::ranges::views::filter([](int x){ return x > 2; }),
0, std::plus{}, [](int x){ return x * 2; });
std::cout << result; // 18
以上示例把过滤、变换与归约合并为一行。
6. 小结
- 视图:惰性、无副作用的容器包装。
- 适配器:对视图进行变换、筛选、排序、分组。
- 管道语法:
|让链式调用更直观。 - 延迟求值:提高性能,尤其在大数据流中。
掌握 std::ranges 后,你会发现 C++ 代码既简洁又强大。下一步可以尝试在自己的项目中替换部分传统循环,用 ranges 重新实现一次,体验它带来的代码可读性提升。祝编码愉快!