C++ std::views::drop

2025年3月24日 | 阅读12分钟

C++ 范围和视图简介

C++20 新增了范围(ranges)和视图(views)的概念,以改变开发人员处理容器的方式。范围是另一个定义元素序列的概念;算法可以作用于这些序列,而无需遍历它们。范围在处理 STL 算法时提高了代码的可读性和简洁性,且不会产生明显的干扰。

视图是一种范围,它为另一个范围提供一个非拥有(non-owning)、按需(on-demand)的视图。视图使得可以在不复制或修改集合的情况下,在函数中使用集合中的数据。由于视图是惰性的,诸如过滤、转换或跳过元素等操作仅在访问数据时执行。

范围和视图并不相同,但它们是互补的,因为两者的使用可以通过链式操作来简化许多操作。例如,可以使用单个表达式来过滤、映射和丢弃范围中的元素。这种新方法提高了C++ 代码的运行时间、可读性和模块化,并使该语言更接近函数式编程范例。

视图为何有助于高效数据处理?

  • C++ 编程语言提供了视图(Views)作为管理数据的一种方式,它们可以操作范围而不以任何方式修改数据。它们提供延迟执行,这意味着像过滤、转换或跳过序列元素这样的操作是在需要数据时执行的,而不是在定义视图时执行的。这样做是为了消除不必要结果的计算,而这些计算只会消耗程序中的内存。
  • 由于视图不拥有数据,它们只是对底层数据的薄包装,并且由于视图可以为任何对象创建,因此开发人员可以对数据执行多个转换而不必复制它。这有助于最小化内存消耗并提高效率,尤其是在处理大数据或一系列计算时。
  • 此外,视图还可以连接;这允许在单个视图中实现某些复杂操作的清晰简洁的表达式。例如,开发人员可以在一行代码中丢弃元素、按条件过滤并应用转换,以获得更好的可读性并减少过多的步骤;所有这些都使其运行得更快,并且更容易维护。

std::views::drop 的解释及其目的

std::view 是 C++20 标准库范围(ranges)的一部分,是一种视图适配器。它用于创建一个范围的视图,该视图跳过("drop")底层范围的前 N 个元素,从而可以从第 (N + 1) 个位置开始访问元素。这使得不必接触原始容器即可有效地跳过一定数量的元素。

目的

std::views::drop 的主要目的是提供一种简单快速的方法来处理数据的子集。而不是手动遍历集合并跳过元素,std::views drop 操作 < |db|>:views::drop 如此清晰、富有表现力且惰性地执行此操作。

它还可以与其他视图(如 std:::filter 和 std::views::transform)无缝协作,从而以简洁易读的方式对范围应用多个转换。由于其惰性和非拥有性,它有助于提高性能和内存使用。

它在 C++20 范围的上下文中如何工作?

在 C++20 中,**std::views::drop** 是一个在范围库中操作的操作,用于创建现有范围的惰性且非拥有的视图,其中前 N 个元素被过滤掉。这不会以任何方式扭曲原始数据,从而实现高效处理。工作原理如下:

  • **惰性求值:** 如果一个范围通过应用 std::views::drop(N) 进行转换,它不会立即丢弃元素。相反,它会创建一个视图,该视图不包含前 N 个元素,除非你开始访问范围中的元素。这种延迟执行提高了性能,尤其是在大型数据集的情况下,因为只执行了必要的部分。
  • **非拥有视图:** 我们认为使用 std::views::drop 创建的视图不保留数据;它只是在给定范围之上提供了一个薄抽象。这避免了原始容器的不希望的复制或修改。
  • **链式视图:** std::views::drop 可以轻松地与 std::views::transform 或 std::views::filter 等其他视图链式连接。

std::views::drop 的基本用法

语法

C++20 特性 std::<views::drop> 是一种视图适配器,它将一个范围作为输入并返回从第 N 个元素开始的范围视图。它在不影响原始范围的情况下,方便有效地选择较少数量的元素。

在这里,original_range 是我们要操作的容器或范围,N 是要跳过的元素数量。运算符 | 用于将范围通过管道传递给视图适配器 std::views::drop(N)。

使用示例

1. 从 vector 中丢弃前 N 个元素

输出

 
4 5 6 7 8 9 10    

在此示例中,std::views::drop(3) 会丢弃数字 vector 的前 3 个元素,并从第 4 个元素开始输出。

2. 从数组中丢弃元素

输出

 
30 40 50 60    

在此,将跳过 {10, 20} 数组的前两个元素,并显示其余元素。

3. 将 std::views::drop 与其他视图适配器一起使用

输出

 
10 12 14 16 18 20    

在这种情况下,在丢弃前 4 个元素后,使用 std::views::transform 将剩余的每个元素都乘以 2。

将 std::views::drop 与 std::views::take 以及 std::views::filter 等其他视图适配器进行比较

视图适配器目的使用示例场景
std::views::drop跳过范围的**前 N** 个元素。auto dropped = range | std::views::drop(N);当你想要忽略范围的**前 N** 个元素时。
std::views::take获取范围的**前 N** 个元素,忽略其余部分。auto taken = range | std::views::take(N);当你只需要范围的**前 N** 个元素时。
std::views::filter根据谓词(条件)选择元素。auto filtered = range | std::views::filter(predicate);当你只想选择满足条件的元素时。
行为返回一个从**第 (N+1)** 个元素开始的视图。跳过前 N 个元素并处理其余部分。
惰性求值是的,元素仅在访问时进行处理。是。是。
非拥有是的,视图不拥有数据,仅引用它。是。是。
链式可能性可以与其他视图链式连接,以实现复杂的操作。是。是。
示例vec | std::views::drop(2) 的结果是 {3, 4, 5, 6}vec | std::views::take(3) 的结果是 {1, 2, 3}vec | std::views::filter([](int n) { return n % 2 == 0; }) 的结果是 {2, 4, 6}
用例在大型数据集中高效地跳过前导元素。仅获取序列的初始部分,而无需遍历整个范围。根据特定条件选择元素(例如,偶数或大于 X 的元素)。

关键区别

  • **std::views::drop:** 忽略一定数量的元素,并在该忽略之后启动视图。
  • **std::views::take:** 将视图限制为前 N 个元素。
  • **std::views::filter:** 过滤掉其他元素,选择选定的元素。

所有三个视图都是惰性的(操作在范围被迭代时执行)且非拥有的(它们不复制数据)。这些特性使它们适用于处理大量范围数据和低效数据操作。

std::views::drop 如何与迭代器和范围交互?

C++20 中的 std::views::drop 以非常灵活的方式与迭代器和范围一起工作,并支持 COT(连续存储)和非 COT。了解它与其他容器类型(如链表和集合)的交互方式对于充分发挥其能力至关重要。

1. 迭代器

  • std::views::drop 依赖于底层范围的迭代器接口。它可以与任何提供标准迭代器的容器一起工作,包括双向(如链表)和随机访问(如 vector)的容器。
  • 当应用于一个范围时,std::views::drop(N) 会创建一个新视图,该视图从第 (N+1) 个元素开始迭代,从而有效地跳过前 N 个元素。

2. 惰性求值

  • 丢弃操作是惰性的,这意味着它不会真正遍历范围,直到结果视图被迭代。这对于大型或不连续的容器尤其有利,因为它避免了不必要的计算和内存分配。

与非连续容器的交互

1. 链表

  • 行为
    • 当 std::views::drop 应用于链表时,它将有效地跳过指定的节点数。
    • 由于链表不提供随机访问,std::views::drop 将逐个节点遍历链表以找到第 (N+1) 个元素。
  • 示例

输出

 
3 4 5   

在此示例中,丢弃了前两个元素,视图从第三个元素开始。

2. 集合(Sets)

  • 行为
    • C++ 中的集合通常实现为排序容器(如平衡树),这意味着它们保持顺序但不允许重复元素。
    • 当 std::views::drop 应用于集合时,它也将通过根据集合的排序顺序跳过前 N 个元素来工作。

示例

输出

 
3 4 5   

这里,跳过了前两个元素(1 和 2),视图从有序集合的第三个元素开始。

关键注意事项

  • **性能:** std::views 的性能可能因容器类型而异。将元素存储在 vector 等随机访问容器中是一个好主意,因为它可以轻松地在寻找正确迭代器时丢弃元素。然而,对于链表,该操作所需的时间可能会更长,因为它需要从一个链表节点移动到另一个节点。
  • **非拥有:** 与其他视图一样,std::views::drop 创建的视图是非拥有的。这意味着它不会复制原始容器,这在处理大数据时尤其有用。
  • **链式:** std::views::drop 可以与其他视图适配器(如 std::views::filter 和 std::views::transform)结合使用,从而可以执行各种数据重塑操作。交互仍然非常高效且惰性,根据需要利用迭代器底层。

总而言之,std::views::drop 使跳过范围中的元素变得容易,并能与连续和非连续容器一起工作。它是一个方便的现代 C++ 编程工具,因为它是一种惰性求值的非拥有迭代器,并可用于不同类型的容器。

实施

让我们举一个例子来说明 C++ 中的 **std::views::drop 函数。

输出

 
Original numbers: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 
After dropping first 10 elements: 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 
After filtering out even numbers: 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99 
After squaring the remaining numbers: 121 169 225 289 361 441 529 625 729 841 961 1089 1225 1369 1521 1681 1849 2025 2209 2401 2601 2809 3025 3249 3481 3721 3969 4225 4489 4761 5041 5329 5625 5929 6241 6561 6889 7225 7569 7921 8281 8649 9025 9409 9801 
Sum of squared values: 166485   

std::views::drop 的特性

  • **元素丢弃:** 它允许你在范围的左侧忽略一定数量的元素。当你想要排除序列中的某个特定前缀时,这很有用。
  • **惰性求值:** 丢弃操作也以惰性的方式执行,这意味着元素实际上并未从原始容器中移除;只有它们的引用被删除。相反,std::views::drop 创建了一个包含所有剩余元素的新视图,而无需复制。
  • **范围兼容性:** 它能与任何范围库支持的范围类型无缝集成,这些范围的基本要求是基本容器,例如 std::vector、std::list 等,静态创建的数组以及一些其他范围适配器。
  • **constexpr 和编译时求值:** std::views::drop 可与 constexpr 一起使用,因此在需要时可以进行编译时求值。
  • **可组合性:** 在下面的示例中,你可以将 std::views::drop 与其他视图适配器(如 std::views::filter、std::views::transform 和 std::views::take)一起使用。以这种方式进行组合,可以对数据操作进行高级描述,并且相对简洁。
  • **转发迭代器:** 底层范围必须至少包含要丢弃的元素数量;否则,创建的视图将为空。然而,它不一定是连续的,并且无论容器是离散的还是不同类型的,它都适用。
  • **简洁性和可读性:** 在代码中应用 std::views::drop 可以提高其可读性,因为它明确表达了意图,从而可以创建高效且可维护的数据处理代码。
  • **无副作用:** 它不会修改旧容器或范围;它只是创建一个新的视图来容纳剩余的部分,但保持数据不变。

结论

总之,**std::views::drop** 是 C++20 范围库的一部分,它能够无需移动任何数据即可移除序列中的一定数量的项,甚至可以用最少的精力实现。它还具有惰性求值的优势,因为丢弃操作在视图使用时执行,并且可能需要大量计算时间,尤其是在处理大型数据集时。由于视图不拥有数据,该机制对于管理各种持有者(包括连续和非连续结构)中的数据来说,既灵活又快速。