首页 > 试题广场 >

以下哪个关于C++20 std::ranges的说法是正确的

[单选题]
以下哪个关于C++20 std::ranges的说法是正确的?
#include <ranges>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> v{1,2,3,4,5,6,7,8,9,10};
    auto result = v | std::views::filter([](int n){ return n % 2 == 0; })
    | std::views::transform([](int n){ return n * n; })
    | std::views::take(3);
    for (auto x : result) std::cout << x << " ";
}
  • 输出 4 16 36
  • 输出 1 4 9
  • 编译错误,views不支持链式组合
  • 输出 4 16 36 64 100

View(视图) 是 C++20 ranges 库中的一个核心概念。你可以把它理解为一个 “不拥有数据、惰性求值、可组合”的算法适配器

针对你给的这段代码,我们来拆解一下 view 到底是什么:

cpp
auto result = v | std::views::filter(...) // View 1 | std::views::transform(...) // View 2 | std::views::take(3); // View 3

这里的 result 就是一个 View 的组合,而不是一个新的 vector。

View 的三大核心特征

  1. 不拥有数据 (Non-owning)

    • View 就像一个“玻璃窗”或者“指针”,它只是观察原来的 vector<int> v 中的数据。

    • 它不会分配新的内存,也不会拷贝 v 里的整数。

    • 好处:创建 View 的开销极小(通常只是复制几个指针/迭代器)。

  2. 惰性求值 (Lazy Evaluation)

    • 当你写 auto result = ... 时,什么计算都没发生

    • 偶数没有被筛选出来,平方也没有被计算。

    • 只有当你开始遍历 result(比如 for (auto x : result) 或调用 .begin())时,才会“按需”执行计算:

      • 从 v 取一个元素 -> 检查是否是偶数? -> 是的话算平方 -> 计数是否到了3个?

      • 取下一个,重复。

  3. 可组合 (Composable)

    • 你可以用 | 运算符像管道一样把多个 View 串起来。

    • 逻辑清晰:筛选 -> 变换 -> 取前3个,读起来就像流水线。

类比帮你理解

  • 普通容器 (vector):就像一个装满实际数据的冰箱。你有一个冰箱,里面放着 1 到 10 号的蛋糕。它占地方,耗资源。

  • View:就像贴在冰箱上的一张便利贴,上面写着:“只看偶数编号的蛋糕,把它们想象成平方后的样子,而且只看前 3 个”。这张便利贴不改变冰箱,也不拿蛋糕出来,只是改变你看待冰箱内容的方式。

你的代码实际执行流程

cpp
// 此时只是定义了一套“查看规则”,没有真正计算 auto result = v | std::views::filter([](int n){ return n % 2 == 0; }) // 规则1:只看偶数 | std::views::transform([](int n){ return n * n; }) // 规则2:每个看到的数变平方 | std::views::take(3); // 规则3:只看前3个 // 循环开始,按规则实际执行 for (auto x : result) // x 依次为:4, 16, 36 std::cout << x << " ";

执行过程(懒加载):

  1. 看 v 的第一个元素 1:奇数,不符合规则1 -> 跳过。

  2. 第二个元素 2:偶数 -> 规则2 平方得 4 -> 规则3 这是第1个 -> 输出 4。

  3. 第三个元素 3:奇数 -> 跳过。

  4. 第四个元素 4:偶数 -> 平方得 16 -> 第2个 -> 输出 16。

  5. 第五个元素 5:跳过。

  6. 第六个元素 6:偶数 -> 平方得 36 -> 第3个 -> 输出 36。

  7. 达到 take(3) 数量,停止迭代(后续 7,8,9,10 不会再被检查)。

什么时候用 View?

  • :需要组合多个操作(filter, map, reverse, drop...),且不想产生中间临时数组的开销。

  • 不用:需要长期保存数据,或者数据会被修改而 View 依赖的原始容器已经销毁了(View 不拥有数据,所以原容器销毁后 View 就悬空了)。

发表于 2026-04-02 19:51:36 回复(0)