为什么 C++20 的 ranges 库能彻底改变我们的容器遍历方式?

C++20 引入的 ranges 库在很大程度上提升了 STL 的可读性、可维护性与表达力。它不再让我们像过去那样手写迭代器或依赖繁琐的 for‑loop,而是让“容器+谓词+变换”这三大要素天然耦合、可组合。下面从哲学实现细节常见误区实战案例四个角度拆解 ranges 的强大之处,帮助你快速上手。


1. 核心哲学:懒惰与分层

1.1 懒惰求值

ranges 的所有视图(view)本质上是懒惰求值的链式结构。只在需要的时候才计算下一个元素,而不是一次性生成整个序列。

好处:节省内存、支持无限序列、能够与其他惰性操作无缝拼接。

1.2 分层组合

ranges 提供了三层抽象:

  1. View:对已有序列做过滤、映射、切片等“视图”操作,返回新的范围对象。
  2. Adaptor:一类特殊的 View,用于把普通容器或迭代器包装成可被其他 View 处理的对象。
  3. Algorithm:对 View 进行终止性操作(for_eachcount_ifsort 等),与 View 的懒惰性兼容。

通过把“何时产生数据”和“如何处理数据”分离,代码变得更易读、易测试。


2. 关键组件详解

组件 作用 典型用法
std::views::filter 过滤器 auto evens = view | std::views::filter([](int n){return n%2==0;});
std::views::transform 映射 auto squares = view | std::views::transform([](int n){return n*n;});
std::views::take 截断 auto first10 = view | std::views::take(10);
std::views::reverse 反转 auto rev = view | std::views::reverse;
std::ranges::for_each 遍历执行 std::ranges::for_each(view, [](auto& x){std::cout<<x;});
std::ranges::sort 排序(需要可随机访问的 View) std::ranges::sort(view);

view 变量通常是一个容器(`std::vector

`)或迭代器范围(`std::begin(vec)` 到 `std::end(vec)`)。链式组合使用 `|`(管道符)操作符。

3. 常见误区与排查技巧

3.1 误区:直接把 View 传给 std::vector 的构造函数

std::vector <int> v = std::views::iota(0,10); // ❌

原因iota 返回的是 std::ranges::iota_view,不是容器。
解决:显式生成容器:`std::vector

v(std::begin(view), std::end(view));`

3.2 误区:对不支持随机访问的 View 调用 sort

auto v = std::views::iota(0,10) | std::views::reverse;
std::ranges::sort(v); // ❌

原因reverse 生成的是双向迭代器视图,sort 需要随机访问。
解决:先将 View 收集到容器,再排序:
`std::vector

arr(v.begin(), v.end()); std::ranges::sort(arr);`

3.3 误区:使用 std::views::filter 过滤后直接调用 size()

size() 只在容器上可用,视图没有此成员。
解决:使用 std::ranges::distance(v)std::ranges::count(v, predicate)


4. 实战案例:统计特定模式的整数

假设我们有一个巨大的整数序列(可能是文件中的行、网络流等),我们需要统计所有 质数奇数 的个数。传统实现可能如下:

int count = 0;
for (int n : numbers) {
    if (n % 2 == 1 && isPrime(n)) ++count;
}

使用 ranges,我们可以写成:

auto isPrime = [](int n) {
    if (n < 2) return false;
    for (int i = 2; i*i <= n; ++i)
        if (n % i == 0) return false;
    return true;
};

int count = std::ranges::count_if(
    numbers |
    std::views::filter([](int n){ return n % 2 == 1; }) |
    std::views::filter(isPrime),
    [](int){ return true; } // 过滤器的返回值会被忽略,只要返回 true
);

或更简洁:

int count = std::ranges::count_if(
    numbers | std::views::filter([](int n){ return n % 2 == 1 && isPrime(n); }),
    [](int){ return true; }
);

优点:代码更短、逻辑更直观;若需要进一步扩展(如先过滤出偶数再做平方等),只需在管道中插入相应的视图。


5. 小结

  1. ranges 通过懒惰求值和分层组合,使 STL 的容器遍历变得更加声明式。
  2. 只要掌握 View、Adaptor、Algorithm 的组合,即可完成过滤、映射、切片等常见需求。
  3. 牢记懒惰的本质,避免对视图执行不合法的操作(如直接 sortsize())。
  4. 在大数据量、流式数据处理场景下,ranges 能显著提升代码的可维护性与执行效率。

尝试把你手头的容器遍历代码重构为 ranges 版,感受它带来的“即写即跑”的乐趣吧!

发表评论