C++20 Ranges:告别手写循环,像 SQL 一样操作数据

你是否还在为嵌套的 for 循环和繁琐的 STL 算法调用而烦恼?是否觉得数据处理代码的意图不够直观?C++20 Ranges 库的到来,将彻底改变你操作数据序列的方式。


系列文章索引


0. 前言:数据处理的新范式

在 C++20 之前,我们处理一个数据序列(如 std::vector)通常有两种方式:

  1. 手写 for 循环:命令式风格,步骤清晰,但容易将业务逻辑与循环控制混在一起,代码冗长且难以复用。
  2. STL 算法std::sort, std::transform 等):函数式风格,但常常需要配合迭代器(begin(), end()),并且当需要组合多个操作时,会产生许多临时的中间容器,既影响性能又让代码变得支离破碎。

C++20 引入的 Ranges(范围)库 ,提供了一种全新的声明式数据处理范式。它允许你像写 SQL 查询或 Linux 管道一样,用一种清晰、可组合且高效的方式来表达对数据的操作。

1. "之前"的世界:命令式循环的痛苦

让我们来看一个经典的需求:从一个整数集合中,筛选出所有偶数,将它们平方,然后取出前 5 个结果。

传统 for 循环实现
cpp 复制代码
#include <vector>
#include <iostream>

std::vector<int> process_numbers(const std::vector<int>& input) {
    std::vector<int> result;
    for (int num : input) {
        if (num % 2 == 0) {
            int squared = num * num;
            if (result.size() < 5) {
                result.push_back(squared);
            } else {
                break; // 手动控制循环退出
            }
        }
    }
    return result;
}

痛点分析:

  • 逻辑混杂:筛选、转换、限制数量的逻辑都挤在一个循环里。
  • 可读性差:需要阅读整个循环才能理解最终结果是如何得出的。
  • 手动控制 :需要手动管理 result 容器和循环的退出条件。

2. Ranges 的核心思想:视图与管道

Ranges 库的核心是两个概念:视图管道

  • 视图:视图是一个"懒加载"的、非拥有的范围。它不持有数据,只是对现有数据的一种"投影"或"滤镜"。对视图的操作不会立即执行,也不会产生新的容器,因此几乎没有性能开销。
  • 管道 :使用管道操作符 |,你可以将多个视图串联起来,形成一个数据处理管道。数据会像水流一样,依次通过管道中的每一个过滤器。

这种组合方式,让代码的读法和写法完全一致,极具表现力。

3. "之后"的世界:Ranges 的优雅

现在,让我们用 Ranges 来重写上面的需求。

cpp 复制代码
#include <vector>
#include <ranges>   // Ranges 库的头文件
#include <algorithm> // for std::ranges::to (C++23) 或其他动作
#include <iostream>

// 为了在 C++20 中使用 to_vector,需要一个简单的辅助函数
// (C++23 将直接提供 std::ranges::to)
template<std::ranges::range R>
auto to_vector(R&& r) {
    std::vector<std::ranges::range_value_t<R>> v;
    for (auto&& e : r) {
        v.push_back(std::forward<decltype(e)>(e));
    }
    return v;
}

std::vector<int> process_numbers_ranges(const std::vector<int>& input) {
    auto pipeline = input 
        | std::views::filter([](int n) { return n % 2 == 0; }) // 筛选偶数
        | std::views::transform([](int n) { return n * n; })    // 平方
        | std::views::take(5);                                // 取前 5 个

    // 管道是懒加载的,只有在这里(一个"动作")才会真正执行计算
    return to_vector(pipeline); 
}

优点分析:

  • 声明式:代码清晰地"声明"了我们想要做什么:筛选、转换、取 5 个。它没有描述"如何"做。
  • 可读性极高:代码从左到右读,就像一句自然语言。
  • 可组合性强:每个视图都是独立的,可以像乐高积木一样随意组合。
  • 性能优异 :由于视图的懒加载特性,整个管道操作不会产生任何中间 std::vector,数据是逐个元素流过整个管道的,效率极高。

4. 常用视图一览

Ranges 库提供了大量实用的视图,以下是一些最常用的:

  • std::views::filter(pred) :只保留满足谓词 pred 的元素。
  • std::views::transform(func) :对每个元素应用函数 func
  • std::views::take(n) / std::views::drop(n) :取前 n 个元素 / 跳过前 n 个元素。
  • std::views::keys / std::views::values :对于一个键值对的范围(如 std::map),分别提取所有的键或所有的值。
cpp 复制代码
#include <map>
std::map<std::string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};

// 提取所有学生姓名
auto names = scores | std::views::keys;
// 提取所有分数
auto values = scores | std::views::values;
  • std::views::split(delimiter) :按分隔符 delimiter 分割一个范围,常用于字符串处理。
cpp 复制代码
std::string s = "hello,world,cpp";
auto parts = s | std::views::split(',');
// parts 的结果是三个子范围的视图:['h','e','l','l','o'], ['w','o','r','l','d'], ['c','p','p']

5. 总结与展望

Ranges 库通过引入声明式的管道操作,彻底革新了 C++ 中数据处理的范式。它让我们能够告别繁琐的 for 循环和低效的中间容器,写出既简洁又高性能的代码。

它的核心优势在于:

  • 可读性:代码即意图,一目了然。
  • 可组合性:像搭积木一样构建复杂的数据处理逻辑。
  • 高性能:懒加载视图避免了不必要的计算和内存分配。

Ranges 是 C++20 中最激动人心的特性之一,它让 C++ 在数据处理领域向现代函数式编程语言看齐。

在你的下一个数据处理任务中,放弃 for 循环,尝试用 Ranges 管道来构建它吧!

你会发现,代码的逻辑从未如此清晰。在下一篇文章中,我们将探索 C++20 的另一大杀器:协程,看看它如何用同步的思维来处理异步任务。

相关推荐
郝学胜-神的一滴2 小时前
现代OpenGL窗口管理:GLFW从入门到实战
开发语言·c++·程序人生·图形渲染·个人开发
Bona Sun2 小时前
单片机手搓掌上游戏机(十六)—pico运行fc模拟器之程序修改烧录
c语言·c++·单片机·游戏机
谁刺我心2 小时前
C++三种智能指针unique、shared、weak
开发语言·c++
9ilk3 小时前
【C++】 --- 哈希
c++·后端·算法·哈希算法
小邓   ༽3 小时前
50道C++编程练习题及解答-C编程例题
c语言·汇编·c++·编程练习·c语言练习题
报错小能手3 小时前
数据结构 定长顺序表
数据结构·c++
qq_419203233 小时前
深浅拷贝、STL迭代器失效
c++·深浅拷贝·stl迭代器失效
再卷也是菜4 小时前
C++篇(21)图
数据结构·c++·算法
星轨初途4 小时前
C++入门(算法竞赛类)
c++·经验分享·笔记·算法