理论介绍
-
什么是 ranges?
- ranges 是 C++20 引入的一个新模块,提供了一组强大的数据处理工具。
- ranges 建立在迭代器和算法的基础之上,提供了一种更加优雅和高效的数据处理方式。
-
ranges 的核心概念
- range: 表示一个元素序列,可以是容器、迭代器区间,甚至是无限序列。
- view: 是 ranges 的核心概念,表示一个惰性求值的 range。
- 算法: ranges 提供了一系列算法,如
filter
、transform
、take
、drop
等,用于处理 ranges。
-
ranges 的特点
- 惰性求值: ranges 中的操作都是惰性求值的,只有在需要结果时才会执行计算。这可以避免不必要的计算,提高性能。对于某些中间结果,例如在循环中使用的元素,ranges 会自动进行缓存,避免重复计算。这在某些场景下可以提高性能。(简单理解:不再是传统一次性全部求值,而是通过迭代器移动,移动一次求一次)
- 链式调用: views 之间可以进行链式调用,实现复杂的数据处理逻辑。比如 my_range | views::filter(pred) | views::transform(f) | views::take(n) 等。这样可以方便地组合多个操作。
- 自定义 views: 用户可以自定义自己的 views,扩展 ranges 的功能。用户也可以自定义自己的 views。只需实现 begin()、end() 等迭代器相关的成员函数即可。这为扩展 ranges 功能提供了灵活性。
- 性能优化: ranges 充分利用 C++20 的新特性,如 move semantics 和 NRVO 等,实现零开销抽象。
- 并行计算支持: ranges 提供了与
std::execution
兼容的并行计算支持。
-
ranges 的常用算法
-
std::views::all 这是一个适配器,它将任何可迭代对象转换为一个视图,保持原始元素顺序。 std::views::filter 这个适配器根据给定的谓词过滤范围中的元素,产生一个新的视图。 std::views::transform 这个适配器将给定的范围中的每个元素应用指定的转换函数,产生一个新的视图。 std::views::take 这个适配器从给定范围中取出前n个元素,产生一个新的视图。 std::views::take_while 这个适配器从给定范围中取出满足某个条件的元素,直到遇到第一个不满足条件的元素为止。 std::views::drop 这个适配器丢弃给定范围开头的n个元素,产生一个新的视图。 std::views::drop_while 这个适配器丢弃给定范围开头满足某个条件的元素,直到遇到第一个不满足条件的元素为止。 std::views::join 这个适配器将嵌套的范围连接成一个扁平的范围。 std::views::lazy_split 这个适配器将给定范围按指定的分隔符拆分成子范围,但不会立即执行拆分操作。 std::views::split 这个适配器将给定范围按指定的分隔符拆分成子范围,立即执行拆分操作。 std::views::common 这个适配器将一个不同类型的句柄和范围转换为一个公共范围。 std::views::reverse 这个适配器创建一个反向遍历给定范围的新视图。 std::views::as_const 这个适配器创建一个视图,该视图中的元素具有const属性。 std::views::elements 这个适配器从嵌套范围中提取特定元素,产生一个新的视图。 std::views::enumerate 这个适配器将给定范围与它的索引一起产生一个新的视图。 std::views::zip 这个适配器将多个范围合并成一个元组范围。 std::views::zip_transform 这个适配器将多个范围合并并应用一个转换函数,产生一个新的视图。 std::views::adjacent 这个适配器创建一个新视图,其中包含给定范围中相邻元素的对。 std::views::adjacent_transform 这个适配器创建一个新视图,其中包含给定范围中相邻元素的转换结果。 std::views::join_with 这个适配器将嵌套的范围连接成一个扁平的范围,使用指定的分隔符。
-
-
ranges 的应用场景
- 数据处理和分析
- 机器学习和信号处理
- 流式编程和并行计算
使用
命名空间别名:
namespace std { namespace views = ranges::views; }
std::views命名空间别名是std::ranges::views的简写形式。
包含的头文件:
<ranges>头文件包含了一些其他头文件,如<compare>、<initializer_list>和<iterator>。
实践
比较
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>
#include<unordered_map>
int main() {
std::vector<int> numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// 使用 range-based for 循环遍历
int target = 3;
for (int count = 0; int num : numbers) {
if (num % 2 == 0) {
std::cout << num << " ";
if (++count > target) break;
}
}
std::cout << std::endl; // 输出: 2 4 6 8
using namespace std::ranges::views;
for (int data : numbers | filter([](int n){return n %2 == 0;}) | take(3) )
{
std::cout << data;
}
std::cout << std::endl; //输出:246
// 使用视图过滤奇数
std::vector<int> odd_numbers;
std::ranges::copy(numbers | std::views::filter([](int n) { return n % 2 != 0; } ),std::back_inserter(odd_numbers));
for (int num : odd_numbers) {
std::cout << num << " ";
}
std::cout << std::endl; // 输出: 1 3 5 7 9
// 使用算法查找最大值
int max_value = *std::ranges::max_element(numbers);
std::cout << "最大值: " << max_value << std::endl; // 输出: 10
//排序
//传统 std::sort(odd_numbers.begin(), odd_numbers.end());
std::ranges::sort(odd_numbers, std::greater{});
for (int num : odd_numbers) {
std::cout << num << " "; //9 7 5 3 1
}
return 0;
}
使用zip来快速生成map
//使用zip来快速生成map
std::vector<int> numbers = { 1, 2, 3, 4, 5 };
std::vector<std::string> names = { "Alice", "Bob", "Charlie", "David", "Eve" };
std::vector<double> scores = { 90.5, 85.2, 92.1, 88.7, 93.0 };
auto zipped_view = std::views::zip(numbers, names, scores);
for (const auto& [number, name, score] : zipped_view) {
std::cout << "Number: " << number << ", Name: " << name << ", Score: " << score << std::endl;
}
zipped_view类型:
std::ranges::zip_view<
std::vector<int>&,
std::vector<std::string>&,
std::vector<double>& >
输出:
Number: 1, Name: Alice, Score: 90.5
Number: 2, Name: Bob, Score: 85.2
Number: 3, Name: Charlie, Score: 92.1
Number: 4, Name: David, Score: 88.7
Number: 5, Name: Eve, Score: 93
总结:<ranges>
头文件为C++20引入了一个强大的范围编程框架,提供了丰富的概念、视图和适配器,使得代码更加简洁、表达力强和可组合。这极大地增强了C++标准库在处理序列数据方面的能力。目前还是草案阶段,许多编译器不支持