C++20 中最优雅的那个小特性 - Ranges

C++20 中最优雅的那个小特性 - Ranges

大家好,今天我们来聊聊 C++20 的一项非常重要的新特性------Ranges,可以让你的代码更优雅、更高效、更炫酷,如果你是一个对代码有所追求的小伙伴,那么这个特性你绝对值得拥有!

啥是 Ranges和std::views ?

首先,我们来说说什么是 Range。简单来说,Range 就是一种可以遍历的序列。你可以把它想象成更智能、更灵活的数组或者容器。C++20 引入了 Ranges 这个概念,让我们可以更方便地操作这些序列。

再来说说 std::views,这其实是 C++20 里提供的一系列工具函数,用来对序列进行各种变换。std::views 可以帮我们过滤、转换、拼接等等,让我们以一种非常直观的方式对序列进行操作。

Range和std::views啥关系?

其实,Range和std::views就像是给你一盘水果(Range),然后你拿着各种刀子和工具(std::views)把这些水果处理成你想要的样子。

  • Range:数据的集合。
  • std::views:处理数据的各种工具,比如过滤、变换、切片等等。

举个例子

假如我们有一个数组,我们想要过滤出其中的偶数,然后再把这些偶数加倍。这在以前需要写挺多代码,但有了 Ranges 和 std::views 后,变得非常简单明了:

cpp 复制代码
#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    auto result = numbers | std::views::filter([](int n) { return n % 2 == 0; })
                          | std::views::transform([](int n) { return n * 2; });

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

通过这段代码,我们可以看到,std::views::filter 和 std::views::transform 让我们能一步步地对序列进行处理,代码不仅简洁,而且非常直观,整个过程编码体验真叫一个舒畅啊。

Ranges 与函数式编程

接下来聊聊 Ranges 是怎么有助于函数式编程的。函数式编程的一个核心理念是通过一系列函数的组合来处理数据,而不是通过改变变量的状态。Ranges 恰好和这个理念非常契合。

首先,Ranges 提供了惰性计算的特性,这意味着序列的变换操作在真正需要用到的时候才会执行,不会提前执行。这样做不仅效率高,而且能避免很多不必要的计算。其次,Ranges 让我们可以很自然地把一连串操作用管道的方式串起来。这种方式让代码看起来像流水线一样,每一步都在变换数据。而且每一步的操作都是独立的函数,没有副作用,这和函数式编程的"纯函数"理念非常吻合。

以下将详细探讨 Ranges 和函数式编程之间的关系,以及如何在 C++20 中利用这些新特性来写出更具函数式风格的代码。

1. 声明式代码

在函数式编程中,开发者通常使用声明式代码来描述"做什么",而不是"怎么做"。C++20 的 Ranges 库通过视图适配器和算法,使代码更具声明性。

示例

传统迭代器代码:

cpp 复制代码
#include <vector>
#include <iostream>

void traditional_example(const std::vector<int>& numbers) {
    std::vector<int> result;
    for (auto n : numbers) {
        if (n % 2 == 0) {
            result.push_back(n * n);
        }
    }

    for (auto n : result) {
        std::cout << n << ' ';
    }
    std::cout << std::endl;
}

使用 Ranges 的声明式代码:

cpp 复制代码
#include <vector>
#include <iostream>
#include <ranges>

void ranges_example(const std::vector<int>& numbers) {
    auto result = numbers
                  | std::views::filter([](int n) { return n % 2 == 0; })
                  | std::views::transform([](int n) { return n * n; });

    for (auto n : result) {
        std::cout << n << ' ';
    }
    std::cout << std::endl;
}

通过 Ranges,代码变得更简洁、更具声明性,且易读性和维护性大大提高。

2. 惰性求值

函数式编程的一个重要概念是惰性求值(Lazy Evaluation)。惰性求值只有在需要时才进行计算,避免不必要的计算和内存消耗。

示例

C++20 的 Ranges 通过视图实现了惰性求值:

cpp 复制代码
#include <iostream>
#include <ranges>

int main() {
    auto numbers = std::views::iota(1, 1000000) 
                   | std::views::filter([](int n) { return n % 2 == 0; })
                   | std::views::transform([](int n) { return n * n; })
                   | std::views::take(5); // 仅获取前5个结果

    for (auto n : numbers) {
        std::cout << n << ' ';
    }
    std::cout << std::endl;

    return 0;
}

在这个例子中,将 iota 生成的一个范围进行筛选、转换和截取,仅在实际使用时才执行这些操作,实现了惰性求值。

4 16 36 64 100 

...Program finished with exit code 0
Press ENTER to exit console.

3. 高阶函数

函数式编程中,高阶函数是指可以接受函数作为参数或返回函数的函数。在 C++20 的 Ranges 库中,视图适配器和算法本质上可以看作高阶函数。

示例

cpp 复制代码
#include <vector>
#include <iostream>
#include <ranges>

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

    auto even_filter = [](int n) { return n % 2 == 0; };
    auto square_transform = [](int n) { return n * n; };

    auto result = numbers 
                  | std::views::filter(even_filter)
                  | std::views::transform(square_transform);

    for (auto n : result) {
        std::cout << n << ' ';
    }
    std::cout << std::endl;

    return 0;
}

在这个例子中,filtertransform 高阶函数接受筛选和转换的函数,并将它们应用于范围上。

4 16 36 

...Program finished with exit code 0
Press ENTER to exit console.

4. 不可变性

函数式编程强调数据的不可变性(Immutability)。虽然 C++ 默认是一个可变性较强的语言,但在使用 Ranges 时,通过惰性求值和组合操作,我们可以更接近数据的不可变性。

示例

传统方式:

cpp 复制代码
#include <vector>
#include <algorithm>
#include <iostream>

void traditional_example(std::vector<int>& numbers) {
    std::vector<int> result;
    std::copy_if(numbers.begin(), numbers.end(), std::back_inserter(result), [](int n) { return n % 2 == 0; });
    std::transform(result.begin(), result.end(), result.begin(), [](int n) { return n * n; });
    
    for (auto n : result) {
        std::cout << n << ' ';
    }
    std::cout << std::endl;
}

使用 Ranges:

cpp 复制代码
#include <vector>
#include <ranges>
#include <iostream>

void ranges_example(const std::vector<int>& numbers) {
    auto result = numbers
                  | std::views::filter([](int n) { return n % 2 == 0; })
                  | std::views::transform([](int n) { return n * n; });

    for (auto n : result) {
        std::cout << n << ' ';
    }
    std::cout << std::endl;
}

在 Ranges 示例中,输入的 numbers 是不可变的。我们创建了一个新的视图,而不是改变原始数据。

5. 管道操作

函数式编程中一个流行的概念是管道(Pipeline)。它允许将一系列操作按顺序组合起来,形成一个数据处理流水线。

示例

通过 | 管道操作符,Ranges 实现了函数式编程中的管道概念:

cpp 复制代码
#include <iostream>
#include <vector>
#include <ranges>

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

    auto result = numbers
                  | std::views::filter([](int n) { return n % 2 == 0; })
                  | std::views::transform([](int n) { return n * n; });

    for (auto n : result) {
        std::cout << n << ' ';
    }
    std::cout << std::endl;

    return 0;
}

这种方式使得代码语义更加清晰,数据流的处理步骤一目了然。讲到这儿,估计很多朋友会好奇了,这个 | 运算符是怎么做到这般神奇的?这就得说到运算符重载了。C++20 的 Pipeline 运算符 | 通过运算符重载,实现了高效、简洁的链式操作。这非常有助于函数式编程,让代码简洁易读,同时让数据变换变得更加纯粹、透明。

标准库支持的视图

C++20 Ranges 库提供了多种视图,可以满足各种序列操作需求,比如:

  • std::views::iota:生成一个从某个值开始的无限序列。
  • std::views::take:获取视图中的前几个元素。
  • std::views::drop:跳过视图中的前几个元素。
  • std::views::split:根据特定的分隔符将序列分割成多个子序列。
  • std::views::reverse:将序列逆序输出。
  • std::views::join:将嵌套范围扁平化。

再来一个扁平化的例子

cpp 复制代码
#include <iostream>
#include <ranges>
#include <vector>

int main() {
    std::vector<std::vector<int>> nested = { {1, 2}, {3, 4}, {5, 6} };
    auto flat_view = nested | std::views::join;

    for (auto n : flat_view) {
        std::cout << n << ' ';
    }

    std::cout << std::endl;
}
cmd 复制代码
1 2 3 4 5 6 

...Program finished with exit code 0
Press ENTER to exit console.

结语

C++20 的 Ranges 库为 C++ 提供了许多函数式编程的特性,使得代码更加声明性、易读、易维护。通过 Ranges 和 std::views,开发者可以利用高阶函数、惰性求值、管道操作等函数式编程概念来处理数据流和集合,编写出更高效和优雅的代码。

这些特性不仅增强了 C++ 的表达能力,也让开发者能够以更简洁和自然的方式来进行复杂的数据操作和变换。希望上述内容能帮助你理解和应用 C++20 的 Ranges 库,更好地结合函数式编程理念进行开发。

相关推荐
gma99914 分钟前
brpc 与 Etcd 二次封装
数据库·c++·rpc·etcd
ö Constancy17 分钟前
设计LRU缓存
c++·算法·缓存
C++忠实粉丝1 小时前
计算机网络socket编程(2)_UDP网络编程实现网络字典
linux·网络·c++·网络协议·计算机网络·udp
Mongxin_Chan1 小时前
【Cpp】指针与引用
c++·算法
SSL_lwz2 小时前
P11290 【MX-S6-T2】「KDOI-11」飞船
c++·学习·算法·动态规划
熬夜学编程的小王2 小时前
【C++篇】从基础到进阶:全面掌握C++ List容器的使用
开发语言·c++·list·双向链表·迭代器失效
悄悄敲敲敲2 小时前
C++:智能指针
开发语言·c++
zhangpz_2 小时前
c ++零基础可视化——vector
c++·算法
萨达大2 小时前
23种设计模式-模板方法(Template Method)设计模式
java·c++·设计模式·软考·模板方法模式·软件设计师·行为型设计模式
刀鋒偏冷3 小时前
ninja: error: ‘/opt/homebrew/Cellar/opensslxxx/xx/lib/libssl.dylib
c++