c++20 range
C++20引入了Ranges库,这是对传统STL算法和迭代器的一大扩展,提供了更灵活、更现代的方式来处理序列数据。Ranges引入了几个关键的概念和组件,包括范围、视图、适配器和新的算法,这些都为C++程序员在数据处理方面带来了更多便利。
# Ranges的核心特性
# 1. 范围 (Ranges)
范围是一种抽象,代表了一个连续的、可迭代的元素序列。与传统的迭代器配对不同,范围通过单一的对象表示一个序列的开始和结束。这简化了很多算法的使用方式,因为你不再需要传递两个迭代器来指定一个序列的开始和结束,而只需要传递一个范围对象。
# 2. 视图 (Views)
视图是对一个范围的引用,提供了一种无需复制底层数据的方式来访问或修改数据。视图是懒加载的,它们不执行任何操作直到被遍历或在某种方式上被使用。这使得它们非常高效,尤其是在管道或链式操作中。常见的视图包括filter
, transform
, reverse
, take
, drop
等。
# 3. 适配器 (Adapters)
适配器是特殊类型的视图,用于变换数据。例如,std::views::transform
可以将一个函数应用于范围内的每个元素,而 std::views::take
可以产生一个新的视图,仅包含原范围的前N个元素。
# 4. 新的算法
Ranges为许多传统的STL算法提供了替代品,这些算法可以直接操作范围对象。这些算法包括但不限于ranges::sort
, ranges::find
, ranges::copy
等,它们都可以接受一个范围作为输入,使得代码更加直观和简洁。
# 使用场景
# 1. 数据处理和转换
当需要对数据集进行过滤、转换或任何形式的处理时,Ranges提供的视图和适配器可以非常方便地使用。例如,可以轻松地链式调用多个操作,而不会产生中间的临时容器。
auto result = data | std::views::filter(is_odd) | std::views::transform(square);
# 2. 复杂的查询表达式
对于需要从数据中构建复杂查询的场景,如从集合中提取满足特定条件的元素,Ranges使得表达这种查询变得非常简洁。
auto subset = data | std::views::filter([](const auto& item) { return item.isActive && item.value > 10; });
# 3. 性能敏感的场合
由于视图是懒加载的,它们可以用来提高性能,尤其是在处理大量数据或在只需要序列的一部分时。这可以避免不必要的数据复制和提前计算。
# 4. 与现代C++代码的集成
Ranges使得代码更现代化,易于维护和理解。它与现代C++的其他特性如智能指针、lambda表达式和并发API很好地集成在一起。
下面是几个使用 C++20 Ranges 的例子,展示如何在实际应用中使用它们来简化代码并提高表达力。这些示例覆盖了常见的数据处理任务,如过滤、转换和排序等。
# 示例 1: 过滤和转换
假设我们有一个整数数组,我们想要提取其中的偶数,并将它们每个都乘以2。使用 C++20 Ranges,我们可以很容易地实现这一点。
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 使用 std::views::filter 和 std::views::transform
auto even_numbers_transformed = numbers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * 2; });
// 输出结果
for (int n : even_numbers_transformed) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 示例 2: 串联多个视图
我们可以将多个视图串联起来处理字符串列表,例如,选取特定条件的字符串后,再转换它们的格式。
#include <iostream>
#include <string>
#include <vector>
#include <ranges>
int main() {
std::vector<std::string> names = {"Alice", "Bob", "Charlie", "David"};
// 过滤出长度大于3的名字,并转换为大写
auto long_names_upper = names
| std::views::filter([](const std::string& name) { return name.size() > 3; })
| std::views::transform([](const std::string& name) {
std::string upper_case;
std::ranges::transform(name, std::back_inserter(upper_case), ::toupper);
return upper_case;
});
// 输出结果
for (const auto& name : long_names_upper) {
std::cout << name << " ";
}
std::cout << std::endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 示例 3: 使用 Ranges 排序和去重
我们也可以使用 Ranges 对数据进行排序和去重。
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> nums = {4, 1, 3, 4, 2, 1, 3};
// 排序并去重
auto unique_sorted = nums
| std::views::sort // 排序
| std::views::unique; // 去重
// 输出结果
for (int n : unique_sorted) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 注意事项
- 在使用
std::views::sort
和其他需要修改数据的视图时,需要确保容器本身是可修改的。 - Ranges 库中的一些视图如
sort
和unique
需要在内部进行数据的修改,因此不能直接应用于const
容器或视图。
通过这些例子,我们可以看到 C++20 Ranges 提供了强大的工具,使得数据处理更加灵活和直观。