1- # six
1+ # 六、范围和视图
22
3- # 范围和视图
4-
5- 本章将从上一章关于算法及其局限性的地方继续。范围库中的视图是算法库的强大补充,它允许我们在一系列元素上将多个转换组合成一个惰性的评估视图。阅读本章后,您将了解什么是范围视图,以及如何将它们与标准库中的容器、迭代器和算法结合使用。
3+ 本章将从上一章关于算法及其局限性的地方继续。范围库中的视图是算法库的强大补充,它允许我们在一系列元素上将多个转换组合成一个延迟的评估视图。阅读本章后,您将了解什么是范围视图,以及如何将它们与标准库中的容器、迭代器和算法结合使用。
64
75具体来说,我们将涵盖以下主要主题:
86
@@ -84,7 +82,7 @@ auto get_max_score(const std::vector<Student>& students, int year) {
8482
8583虽然这在这个小例子中很容易实现,但我们希望能够通过组成小的算法构建块来实现这个算法,而不是使用单个`for`循环从头开始实现它。
8684
87- 我们想要的是像使用算法一样可读的语法,但是能够避免为算法中的每一步构建新的容器。这就是范围库中的视图发挥作用的地方。虽然范围库包含的不仅仅是视图,但与算法库的主要区别是能够将本质上是不同类型的迭代器组成一个惰性的求值范围 。
85+ 我们想要的是像使用算法一样可读的语法,但是能够避免为算法中的每一步构建新的容器。这就是范围库中的视图发挥作用的地方。虽然范围库包含的不仅仅是视图,但与算法库的主要区别是能够将本质上是不同类型的迭代器组成一个延迟的求值范围 。
8886
8987如果前面的示例是使用范围库中的视图编写的,那么它会是这样的:
9088
@@ -121,7 +119,7 @@ for (auto s : squared_view) { // The square lambda is invoked here
121119// Output: 1 4 9 16
122120```
123121
124- 变量` squared_view ` 不是数值平方的` numbers ` 向量的副本;它是数字的代理对象,只有一个细微的区别——每次你访问一个元素时,都会调用` std::transform() ` 函数。这就是为什么我们说一个视图是懒惰评估的 。
122+ 变量` squared_view ` 不是数值平方的` numbers ` 向量的副本;它是数字的代理对象,只有一个细微的区别——每次你访问一个元素时,都会调用` std::transform() ` 函数。这就是为什么我们说一个视图是延迟求值的 。
125123
126124从外部来看,您仍然可以像任何常规容器一样迭代` squared_view ` ,因此,您可以执行常规算法,如` find() ` 或` count() ` ,但是,在内部,您没有创建另一个容器。
127125
@@ -198,7 +196,7 @@ auto scores =
198196
199197## 范围视图配有范围适配器
200198
201- 正如您之前看到的,Ranges 库还允许我们使用范围适配器和管道操作符来构建视图,以获得更优雅的语法(您将在*第 10 章*、*代理对象和惰性评估 *中了解更多关于在自己的代码中使用管道操作符的信息)。前面的代码示例可以通过使用 range adaptor 对象来重写,我们将得到如下内容:
199+ 正如您之前看到的,Ranges 库还允许我们使用范围适配器和管道操作符来构建视图,以获得更优雅的语法(您将在*第 10 章*、*代理对象和延迟求值 *中了解更多关于在自己的代码中使用管道操作符的信息)。前面的代码示例可以通过使用 range adaptor 对象来重写,我们将得到如下内容:
202200
203201```cpp
204202using namespace std::views; // range adaptors live in std::views
@@ -293,13 +291,13 @@ auto strings = to_vector(r);
293291// strings is now a std::vector<std::string>
294292```
295293
296- 请记住,一旦视图被复制回容器,原始容器和转换后的容器之间就不再有任何依赖关系。这也意味着物化是一个急切的操作,而所有的视图操作都是懒惰的 。
294+ 请记住,一旦视图被复制回容器,原始容器和转换后的容器之间就不再有任何依赖关系。这也意味着物化是一个急切的操作,而所有的视图操作都是延迟的 。
297295
298296## 视图被偷懒评估
299297
300298由视图执行的所有工作都懒洋洋地发生。这与` <algorithm> ` 头中的函数相反,这些函数在被调用时会立即对所有元素执行工作。
301299
302- 你已经看到` std::views::filter ` 视图可以代替` std::copy_if() ` 算法,` std::views::transform ` 视图可以代替` std::transform() ` 算法。当我们使用视图作为构建块并将它们链接在一起时,通过避免急切算法所需的容器元素的不必要的副本,我们从惰性评估中受益 。
300+ 你已经看到` std::views::filter ` 视图可以代替` std::copy_if() ` 算法,` std::views::transform ` 视图可以代替` std::transform() ` 算法。当我们使用视图作为构建块并将它们链接在一起时,通过避免急切算法所需的容器元素的不必要的副本,我们从延迟求值中受益 。
303301
304302但是` std::sort() ` 呢?有相应的排序视图吗?答案是否定的,因为它需要视图首先急切地收集所有元素,以便找到要返回的第一个元素。相反,我们必须通过在我们的视图上明确地调用 sort 来实现这一点。在大多数情况下,我们还需要在排序之前物化视图。我们可以用一个例子来澄清这一点。假设我们有一个被谓词过滤的数字向量,如下所示:
305303
@@ -323,7 +321,7 @@ std::ranges::sort(v);
323321// v is now 1, 1, 5, 7
324322```
325323
326- 但是为什么有这个必要呢?答案是这是懒惰评价的结果 。当评估需要通过一次读取一个元素来偷懒时,过滤器视图(和许多其他视图)不能保留底层范围的迭代器类型(在本例中为`std::vector`)。
324+ 但是为什么有这个必要呢?答案是这是延迟评价的结果 。当评估需要通过一次读取一个元素来偷懒时,过滤器视图(和许多其他视图)不能保留底层范围的迭代器类型(在本例中为`std::vector`)。
327325
328326那么,有没有可以排序的视图呢?是的,一个例子是`std::views::take`,它返回一个范围内的第一个 *n* 元素。以下示例编译并运行良好,无需在排序前具体化视图:
329327
@@ -486,7 +484,7 @@ auto split(std::string_view s, char delim) {
486484
487485在 C++ 20 中被接受的 Ranges 库是基于 Eric Niebler 创作的一个库,可在[ https://github.com/ericniebler/range-v3 ] ( https://github.com/ericniebler/range-v3 ) 获得。目前,这个库的组件中只有一小部分进入了标准,但很可能很快会添加更多的东西。
488486
489- 除了许多尚未被接受的有用视图,如` group_by ` 、` zip ` 、` slice ` 、` unique ` 之外,还有** 动作** 的概念,可以像视图一样管道化。然而,行动不是像视图一样被懒惰地评估 ,而是执行范围的急切突变。排序是一个典型动作的例子。
487+ 除了许多尚未被接受的有用视图,如` group_by ` 、` zip ` 、` slice ` 、` unique ` 之外,还有** 动作** 的概念,可以像视图一样管道化。然而,行动不是像视图一样被延迟地评估 ,而是执行范围的急切突变。排序是一个典型动作的例子。
490488
491489如果您等不及将这些功能添加到标准库中,我建议您看一看 range-v3 库。
492490
0 commit comments