文章目录
引言
在C++的发展历程中,每一个新版本都会带来一些令人期待的特性和改进。C++23作为C++20的增量更新,聚焦于简化代码、增强类型安全和填补功能缺口。其中,std::views::chunk_by
作为范围适配器的新成员,为我们处理数据序列提供了更加便捷和高效的方式。本文将详细介绍 std::views::chunk_by
的基本概念、特性、使用场景以及示例代码。
C++23 范围库概述
在深入了解 std::views::chunk_by
之前,我们先来简单回顾一下C++20引入的范围库(Ranges library)。范围库是C++20的一个主要组成部分,它提供了一种新的方法来处理容器中的元素序列,使得代码更加简洁和可读。范围库主要包含以下几个关键概念:
范围视图(Range Views)
范围视图提供了一种不修改原始数据的情况下,对容器进行变换、过滤和切片的能力。常见的视图包括 std::views::all
、std::views::filter
、std::views::transform
等。例如:
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto even_numbers = vec | std::views::filter([](int x) { return x % 2 == 0; });
auto squared = even_numbers | std::views::transform([](int x) { return x * x; });
auto first_three = squared | std::views::take(3);
for (int x : first_three) {
std::cout << x << " ";
}
// 输出: 4 16 36
return 0;
}
范围算法(Range Algorithms)
C++20引入了新的范围版本的算法,这些算法可以直接在范围对象上操作,而不需要显式循环。例如 std::ranges::sort
、std::ranges::find
等。示例如下:
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> vec = {5, 3, 1, 4, 2};
std::ranges::sort(vec);
std::cout << "Sorted vector: ";
for (int x : vec) {
std::cout << x << " ";
}
// 输出: Sorted vector: 1 2 3 4 5
auto min_val = std::ranges::min(vec);
std::cout << "\nMinimum value: " << min_val << std::endl;
// 输出: Minimum value: 1
return 0;
}
范围适配器(Range Adapters)
范围适配器类似于视图,但它们通常用于创建新的范围对象。常见的适配器包括 std::ranges::views::join
、std::ranges::views::chunk_by_size
等。
std::views::chunk_by 介绍
基本概念
std::views::chunk_by
是C++23中引入的一个范围适配器,它接受一个视图和一个可调用对象(二元谓词),并通过在每对相邻元素之间拆分底层视图来生成一个子范围(块)的视图。对于这些元素,谓词返回 false
。每对元素中的第一个元素属于前一个块,第二个元素属于下一个块。
在标题 <ranges>
中定义如下:
template< ranges::forward_range V, std::indirect_binary_predicate<iterator_t<V>, ranges::iterator_t<V>> Pred >
requires ranges::view<V> && std::is_object_v<Pred>
class chunk_by_view
: public ranges::view_interface<chunk_by_view<V, Pred>> (since C++23)
namespace views {
inline constexpr /* unspecified */ chunk_by = /* 未指定 */ ;
} (since C++23)
特性
- 谓词而非等价关系:与D语言的
chunkBy
不同,std::views::chunk_by
不要求谓词是一个等价关系。例如,D的chunkBy
要求谓词满足自反性、对称性和传递性,而std::views::chunk_by
则没有这个限制。 - 不支持输入范围:由于
chunk_by
需要对相邻元素计算谓词,因此它要求底层范围是前向范围(forward range)。缓存元素不是一种可行的方法,主要原因在 [P2321R2] 的第4.3.4节中有讨论。 - 范围属性:如果底层范围是双向范围(bidirectional range),则
chunk_by
也是双向范围;否则,它是前向范围。如果底层范围是通用范围(common range),则chunk_by
也是通用范围。它永远不会是借用范围(borrowed range)或有大小范围(sized range)。 - 缓存机制:类似于
split
,chunk_by
在其begin
中计算第一个范围的末尾并进行缓存,以满足摊销的 O ( 1 ) O(1) O(1) 要求。这意味着它不支持常量迭代。
使用场景
std::views::chunk_by
适用于需要将一个序列按照某种规则进行分组的场景。例如,将一个整数序列按照奇偶性进行分组,或者将一个字符串序列按照长度进行分组等。
示例代码
简单示例
#include <iostream>
#include <vector>
#include <ranges>
#include <fmt/core.h>
int main() {
std::vector<int> v = {1, 2, 2, 3, 0, 4, 5, 2};
fmt::print("{}\n", v | std::views::chunk_by(std::ranges::less_equal{})); // [[1, 2, 2, 3], [0, 4, 5], [2]]
return 0;
}
在这个示例中,我们使用 std::views::chunk_by
和 std::ranges::less_equal{}
谓词将整数序列 v
进行分组。当相邻元素不满足小于等于关系时,就会进行分组。
自定义谓词示例
#include <iostream>
#include <vector>
#include <ranges>
#include <fmt/core.h>
int main() {
std::vector<int> v = {1, 3, 5, 2, 4, 6, 7, 9};
auto is_odd = [](int x) { return x % 2 == 1; };
auto chunked = v | std::views::chunk_by([&is_odd](int a, int b) { return is_odd(a) == is_odd(b); });
for (const auto& chunk : chunked) {
for (int x : chunk) {
std::cout << x << " ";
}
std::cout << std::endl;
}
return 0;
}
在这个示例中,我们自定义了一个谓词 is_odd
来判断一个数是否为奇数。然后使用 std::views::chunk_by
将整数序列按照奇偶性进行分组。
总结
std::views::chunk_by
是C++23范围库中一个非常实用的范围适配器,它为我们处理数据序列提供了更加灵活和高效的方式。通过使用 std::views::chunk_by
,我们可以轻松地将一个序列按照某种规则进行分组,而不需要手动编写复杂的循环和逻辑。同时,范围库的惰性求值特性也使得代码更加高效,避免了不必要的计算和内存消耗。在实际开发中,我们可以根据具体的需求灵活运用 std::views::chunk_by
来简化代码,提高开发效率。