**C++20 标准中的 Ranges 与传统迭代的对比**

C++20 在迭代器与容器操作方面引入了“Ranges”库,彻底改变了我们对集合遍历、过滤与变换的思维方式。相比 C++17 以前的手写循环和 std::transformstd::copy_if 等算法,Ranges 提供了更为直观、可组合且类型安全的接口。


1. Ranges 的核心概念

术语 定义 作用
std::ranges::range 一个对象支持 begin()end() 并返回迭代器 表示可遍历的序列
view 对范围进行惰性转换的对象 例如 std::views::filterstd::views::transform
view adaptor 可链式调用的视图生成器 通过 | 运算符组合
action 对范围进行立即计算的操作 例如 std::ranges::for_eachstd::ranges::copy

2. 传统迭代 vs Ranges 示例

传统 C++17

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

int main() {
    std::vector <int> v{1,2,3,4,5,6};
    std::vector <int> result;
    std::copy_if(v.begin(), v.end(),
                 std::back_inserter(result),
                 [](int x){ return x % 2 == 0; });

    std::transform(result.begin(), result.end(),
                   result.begin(),
                   [](int x){ return x * 10; });

    for(int n : result) std::cout << n << ' ';
}

C++20 Ranges

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

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

    auto even_times10 = v | std::views::filter([](int x){ return x % 2 == 0; })
                          | std::views::transform([](int x){ return x * 10; });

    for(int n : even_times10) std::cout << n << ' ';
}

对比

  • 可读性:Ranges 直接展示了“过滤再变换”的思路,代码更像自然语言。
  • 惰性求值even_times10 并未立即生成新容器,只有在迭代时才计算。
  • 组合性:可以随意添加更多 views:: 操作(如 take, drop, reverse 等),而不需要手写循环。

3. Ranges 的优势与限制

优势 说明
惰性 只在需要时计算,节省内存与 CPU
类型安全 编译期检查返回类型,避免运行时错误
链式调用 通过 | 运算符组合多种视图,逻辑清晰
易于维护 代码短小,易于读者快速理解业务逻辑
限制 说明
性能开销 对于极度性能敏感的场景,手写循环可能更快
编译时间 大量模板展开会导致编译时间增长
学习成本 对于习惯传统 STL 的程序员,需要时间适应新语法

4. 常用视图与适配器

using namespace std::ranges::views;

// filter: 过滤
auto evens = v | filter([](int x){ return x % 2 == 0; });

// transform: 转换
auto doubled = v | transform([](int x){ return x * 2; });

// take: 取前 N 个
auto first3 = v | take(3);

// drop: 跳过前 N 个
auto skip2 = v | drop(2);

// reverse: 反转
auto rev = v | reverse;

// join: 拼接多个范围
auto joined = std::views::join(std::array{v, v});

// split: 按分隔符分割
auto words = "a,b,c" | split(',');

// concat: 连接多个视图
auto all = v | concat(evens, doubled);

5. 结合 std::ranges::action 的实用技巧

// 直接输出所有偶数乘 10 的结果
v | std::views::filter([](int x){ return x % 2 == 0; })
 | std::views::transform([](int x){ return x * 10; })
 | std::ranges::for_each([](int n){ std::cout << n << ' '; });

// 写入文件(假设已打开 std::ofstream out)
v | std::views::filter([](int x){ return x % 2 == 0; })
 | std::views::transform([](int x){ return std::to_string(x * 10); })
 | std::ranges::copy(out);

6. 小结

C++20 的 Ranges 通过提供惰性、可组合的视图,让集合操作更像数据流处理。它显著提升了代码的可读性与维护性,同时保持了与传统 STL 一样的性能(或更高)。虽然在极端性能场景下手写循环仍可能更优,但对于大多数业务代码,Ranges 已经成为更现代、更安全的首选。

下一步建议:

  1. 在实际项目中用 Ranges 逐步替换旧的 std::copy_ifstd::transform 等。
  2. 关注编译时间与生成二进制文件大小,必要时使用 -O2-O3
  3. 学习 std::ranges::subrangestd::views::common,进一步利用 Ranges 的强大功能。

发表评论