**C++20 中的范围 for 与 std::ranges 库的使用技巧**

在 C++20 中,标准库新增了强大的 std::ranges 组件,为范围(range)操作提供了统一而灵活的接口。结合 for 循环中的范围基(range-based for),可以轻松实现更高效、可读性更强的代码。本文将从基础语法、常用范围视图、管道操作符以及性能注意点等方面,系统讲解如何在 C++20 中利用 std::ranges 进行范围处理。


1. 基础语法回顾

#include <vector>
#include <iostream>

int main() {
    std::vector <int> vec{1,2,3,4,5};

    // 传统 range-based for
    for (int n : vec) {
        std::cout << n << ' ';
    }

    // C++20 仍然可用相同语法
    for (int n : vec) {
        std::cout << n << ' ';
    }
}

C++20 仍支持传统的 for (auto& x : collection),但它们在内部使用了 std::begin/std::end,与 std::ranges 并不冲突。差别主要体现在我们可以直接在 std::ranges 提供的视图上使用同样的语法:

#include <ranges>
#include <vector>

int main() {
    std::vector <int> vec{1,2,3,4,5};

    // 直接对视图使用范围 for
    for (int n : vec | std::ranges::views::filter([](int v){ return v % 2 == 0; })) {
        std::cout << n << ' ';
    }
}

2. 常用视图(Views)

视图 说明 代码示例
views::filter 过滤元素 vec | std::ranges::views::filter([](int v){ return v > 2; })
views::transform 转换元素 vec | std::ranges::views::transform([](int v){ return v * v; })
views::take 取前 n 个 vec | std::ranges::views::take(3)
views::drop 跳过前 n 个 vec | std::ranges::views::drop(2)
views::reverse 反转 vec | std::ranges::views::reverse
views::enumerate 关联索引 vec | std::ranges::views::enumerate
views::zip 并列 std::views::zip(vec1, vec2)(C++23 版本)

视图是惰性求值的,即不立即执行操作,而是在迭代时一次性处理。由于其惰性特性,链式组合可以实现高效的数据流。


3. 管道操作符 |

C++20 引入的管道操作符 | 让视图链式调用变得直观:

auto filtered = vec | std::ranges::views::filter([](int v){ return v % 2 == 0; });
auto squared  = filtered | std::ranges::views::transform([](int v){ return v * v; });

管道操作符的使用类似 Unix shell 的管道,每一步返回一个新的视图对象,最终可传递给范围 for 或标准算法。


4. 与标准算法配合

C++20 还提供了新的 ranges 版本的算法,例如 std::ranges::for_eachstd::ranges::copy 等。这些算法接受一个视图作为范围,避免了额外的复制。

#include <ranges>
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector <int> vec{1,2,3,4,5};

    std::ranges::for_each(
        vec | std::ranges::views::filter([](int v){ return v % 2 != 0; }),
        [](int v){ std::cout << v << ' '; }
    );
}

使用 std::ranges::copy 将视图内容拷贝到目标容器:

std::vector <int> target;
std::ranges::copy(
    vec | std::ranges::views::transform([](int v){ return v * 2; }),
    std::back_inserter(target)
);

5. 性能注意点

场景 细节 建议
视图链式 由于惰性求值,单次迭代会多次访问底层容器 适用于单遍或短路操作,避免多重遍历
需要临时结果 如需要多次访问 可使用 std::ranges::views::commonstd::ranges::to<std::vector>()
并行算法 C++20 并行化 ranges 算法 需要确保底层容器线程安全

6. 典型案例:过滤、排序与取前 N

#include <vector>
#include <ranges>
#include <algorithm>
#include <iostream>

int main() {
    std::vector <int> data{5, 2, 8, 1, 9, 3, 4};

    auto result = data
        | std::ranges::views::filter([](int v){ return v % 2 == 0; })   // 只保留偶数
        | std::ranges::views::transform([](int v){ return v * v; })     // 平方
        | std::ranges::views::sort();                                  // 排序
    // 取前 3 个
    std::vector <int> top3;
    std::ranges::copy(
        result | std::ranges::views::take(3),
        std::back_inserter(top3)
    );

    for (int x : top3) std::cout << x << ' ';
}

7. 小结

  • 视图:惰性、无副作用、可链式组合,适用于单遍处理。
  • 管道:语法简洁,易于阅读。
  • 算法ranges 版本的算法与视图配合使用,避免不必要的复制。
  • 性能:在需要多次访问同一数据时,适度复制或使用 common 视图。

C++20 的 std::ranges 让范围处理变得更像函数式编程,既保持了 C++ 的性能优势,又提供了更高层次的抽象。熟练掌握它,将大大提升代码的可读性与可维护性。

发表评论