C++ 20 新特性 ranges 精讲

news2024/9/20 9:00:50

C++ 20 新特性 ranges 精讲

C++20 中的 ranges 库使得使用 STL 更加舒适和强大。ranges 库中的算法是惰性的,可以直接在容器上工作,并且可以很容易地组合。简而言之,ranges 库的舒适性和强大性都源于它的函数思想。

在深入细节之前,这里有一个 ranges 库的第一个示例:结合 transform 和 filter 函数。

示例代码:

#include <iostream>
#include <`ranges`>
#include <vector>
#include <format>

int main() {


    std::vector vec{1, 12, 32, 54, 10086, -1314};

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

    for (auto v: res) {

        std::cout << v << std::endl;
    }
}

应该从左到右读取这个表达式。管道符号代表函数组合:首先,所有偶数才能通过(std::views::filter([](int n){ return n % 2 == 0; }))。之后,每个剩余的数字都映射到它的两倍(std::views::transform([](int n){ return n * 2; }))。这个小示例展示了 ranges 库的两个新功能:函数组合应用于整个容器。

Ranges

Ranges的概念:

begin迭代器和 end 哨兵提供的 range 指定了一组可以遍历的项目。 STL 的容器是 range,但不是 views。 哨兵指定了 range 的结束。

Sentinel

对于 STL 的容器,end 迭代器是哨兵。在 C++20 中,哨兵的类型可以与 begin 迭代器的类型不同。

根据 range 的不同,一个空字符'\0'可能结束一个字符串,一个空字符串 std::string{} 可能结束一个单词列表,一个 std::nullptr 可能结束一个链表,或者数字 -1 可能结束一个非负数列表。

下面的例子使用哨兵来操作C-stringstd::vector<int>

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

struct Space {
    bool operator==(auto pos) const {
        return *pos == ' ';
    }
};

struct NegativeNumber {
    bool operator==(auto num) const {
        return *num < 0;
    }
};

struct Sum {
    void operator()(auto n) { sum += n; }

    int sum{0};
};

int main() {


    const char *codingriji = "subscribed to my wechat official account codingriji";

    std::ranges::for_each(codingriji, Space{}, [](char c) { std::cout << c; });
    std::cout << '\n';
    for (auto c: std::ranges::subrange{codingriji, Space{}}) std::cout << c;
    std::cout << '\n';

    std::ranges::subrange rainer{codingriji, Space{}};
    std::ranges::for_each(rainer, [](char c) { std::cout << c << ' '; });
    std::cout << '\n';
    for (auto c: rainer) std::cout << c << ' ';
    std::cout << '\n';


    std::cout << "\n";


    std::vector<int> myVec{5, 10, 33, -5, 10, 10086, 10010};

    for (auto v: myVec) std::cout << v << " ";
    std::cout << '\n';

    auto [tmp1, sum] = std::ranges::for_each(myVec, Sum{});
    std::cout << "Sum: " << sum.sum << '\n';

    auto [tmp2, sum2] = std::ranges::for_each(std::begin(myVec), NegativeNumber{},
                                              Sum{});
    std::cout << "Sum: " << sum2.sum << '\n';

    std::ranges::transform(std::begin(myVec), NegativeNumber{},

                           std::begin(myVec), [](auto num) { return num * num; });
    std::ranges::for_each(std::begin(myVec), NegativeNumber{},
                          [](int num) { std::cout << num << " "; });
    std::cout << '\n';
    for (auto v: std::ranges::subrange{std::begin(myVec), NegativeNumber{}}) {
        std::cout << v << " ";
    }

    std::cout << "\n\n";

}

定义了两个哨兵: SpaceNegativeNumber 。两者都定义了等于运算符。由于使用了 <compare> 头文件,编译器会自动生成不等运算符。当使用 std::ranges_for_eachstd::ranges::tranform 等算法时需要使用不等运算符。

我先来介绍一下哨兵 Space。 第 31 行直接在字符串应用了哨兵 Space{}。创建 std::ranges::subrange 可以在范围循环中使用哨兵。你也可以定义 std::ranges::subrange 并直接在 std::ranges::for_each 算法 或范围循环 中使用它。

第二个例子使用了 std::vector<int>,填充了值 {5, 10, 33, -5, 10}。哨兵 NegativeNumber 检查数字是否为负数。首先,我使用函数对象 Sum (第 20 - 23 行) 对所有值求和。std::ranges::for_each 返回一对 (it, func)it 是哨兵的后继,func 是应用于范围的函数对象。

由于结构化绑定,可以直接定义变量 sumsum2 并显示它们的值 。std::ranges::for_each 使用了哨兵 NegativeNumber。因此,sum2 是到哨兵的和。调用 std::ranges::transform 将每个元素转换为它的平方: [](auto num){ return num * num}。转换在哨兵 NegativeNumber 处停止

输出:

image-20230119201325106

Views

视图是一种在范围上应用并执行某些操作的东西。视图不拥有数据,它的复制、移动或赋值时间复杂度为常数。

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

int main() {

    std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
    auto results = numbers | std::views::filter([](int n) { return n % 2 == 0; })
                   | std::views::transform([](int n) { return n * 2; });

    for (auto v: results) {

    }

}

在此代码片段中,numbers 是范围,std::views::filterstd::views::transform 是视图。此外,std::string_viewstd::span 也是视图。

由于视图的存在,C++20 允许以函数式风格编程。视图可以组合并且是懒惰的。

Standard library header (C++20) - cppreference.com

image-20230119201941920

注意

视图不拥有数据。因此,视图不会延长其数据的生命周期。因此,视图只能对左值操作。如果在临时范围上定义视图,则编译将失败。

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

int main() {
    const auto numbers = {1, 2, 3, 4, 5};

    auto firstThree = numbers | std::views::drop(3);
//     auto firstThree = {1, 2, 3, 4, 5} | std::views::drop(3); 错
    for (auto v: firstThree) {
        std::cout << v << std::endl;
    }


    std::ranges::drop_view firstFour{numbers, 4};
//     std::ranges::drop_view firstFour{{1, 2, 3, 4, 5}, 4}; 错
}

应用于容器

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

int main() {
    std::vector vec{1, 3, 5, 2, 3, 5, 6, 7, 8, 9,67, 5, 23, 4, 2, 6456, 4};
    std::sort(vec.begin(), vec.end()); //不方便
    std::ranges::sort(vec);
    for (auto v: vec) {
        std::cout << v << std::endl;
    }
}

Projection(投影)

std::ranges::sort 有两个重载:

template< std::random_access_iterator I, std::sentinel_for<I> S,
          class Comp = ranges::less, class Proj = std::identity >
requires std::sortable<I, Comp, Proj>
constexpr I
sort( I first, S last, Comp comp = {}, Proj proj = {} );
(1)	(since C++20)
template< ranges::random_access_range R, class Comp = ranges::less,
          class Proj = std::identity >
requires std::sortable<ranges::iterator_t<R>, Comp, Proj>
constexpr ranges::borrowed_iterator_t<R>
sort( R&& r, Comp comp = {}, Proj proj = {} );

当你研究第二个重载时,你会注意到它接受一个可排序的范围R,一个谓词Comp和一个投影Proj。默认的谓词Comp使用less,而投影Proj使用返回其参数不变的身份std :: identity。投影是将集合映射到子集的映射。

struct Student {
    std::string name;
    int id;
};

void printStudent(const std::vector<Student> &studentCollection) {
    for (const auto &student: studentCollection) {
        std::cout << std::format(" ({} , {}) ", student.name, student.id);
    }
    std::cout << "\n\n";
}


int main() {
    std::vector<Student> studentCollection{{"jack",  10086},
                                           {"black", 10010},
                                           {"trump", 12345},
                                           {"job",   143235}};
    std::ranges::sort(studentCollection, {}, &Student::name);
    printStudent(studentCollection);

    std::ranges::sort(studentCollection, std::ranges::greater(), &Student::name);
    printStudent(studentCollection);

    std::ranges::sort(studentCollection, std::ranges::greater(), &Student::id);
    printStudent(studentCollection);

    std::ranges::sort(studentCollection, std::ranges::greater(), [](auto p) {
        return std::to_string(p.id) + p.name;
    });
    printStudent(studentCollection);


}

image-20230119202814397

map中key&value操作

#include <iostream>
#include <ranges>
#include <unordered_map>

int main() {
    std::unordered_map<std::string, int> m{{"jack",  10086},
                                           {"black", 10010},
                                           {"trump", 12345},
                                           {"job",   143235}};

    auto names = std::views::keys(m);
    for (const auto &name: names) {
        std::cout << name << " ";
    }
    std::cout << "\n";

    auto values = std::views::values(m);
    for (const auto &value: values) {
        std::cout << value << " ";
    }
    std::cout << "\n";

}

image-20230119202930646

函数组合

#include <iostream>
#include <ranges>
#include <unordered_map>

int main() {
    std::unordered_map<std::string, int> m{{"jack",    10086},
                                           {"black",   10010},
                                           {"fdasjkh", 10010},
                                           {"dfsjhb",  10010},
                                           {"trump",   12345},
                                           {"job",     143235}};

    auto firstb = [](const std::string &name) { return name[0] == 'b'; };
    for (const auto &name: std::views::keys(m)
                           | std::views::reverse
                           | std::views::take(4)
                           | std::views::filter(firstb)) {
//        auto rev1 = std::views::reverse(std::views::keys(m));
        std::cout << name << std::endl;
    }

}

管道符号|是函数组合的语法糖。您可以写R | C,而不是C (R)。因此,接下来的等价的。

std::views::keys(m) | std::views::reverse

auto rev1 = std::views::reverse(std::views::keys(m));

惰性计算

std :: views :: iota是一个范围工厂,用于通过逐渐增加初始值来创建元素序列。这个序列可以是有限的或无限的。程序rangesIota.cpp使用10个int填充std :: vector,从0开始。

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

bool isPrime(int i) {
    for (int j = 2; j * j <= i; ++j) {
        if (i % j == 0) return false;
    }
    return true;
}

auto odd = [](int i) {
    return
            i % 2 == 1;
};

int main() {
    std::cout << std::boolalpha;

    std::vector<int> vec;
    std::vector<int> vec2;

    for (int i: std::views::iota(0, 10)) vec.push_back(i);

    for (int i: std::views::iota(0) | std::views::take(10)) vec2.push_back(i);
    std::cout << "vec == vec2: " << (vec == vec2) << '\n';

    for (int i: vec) std::cout << i << " ";

    std::cout << "求质数" << std::endl;
    for (int i: std::views::iota(1'000'000) | std::views::filter(odd)
                | std::views::filter(isPrime)
                | std::views::take(20)) {
        std::cout << i << std::endl;
    }
}

第一个iota调用创建从0到9的所有数字,增加1.第二个iota调用创建从0开始的无限数据流,每次增加1.std :: views :: iota(0)是懒惰的。在请求时才会得到新值。请求了十次。因此,两个数组是相同的。

参考Modernes C++ (modernescpp.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/172677.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

程序员的7个被动收入途径——我如何每月赚 5万

每个人都想过时间和财富自由的生活&#xff0c;世界上有70亿人&#xff0c;但只有不到18000人能做到这一点&#xff0c;大多数人一生都在为钱工作。 研究表明&#xff0c;全世界65.8万富人至少有三种收入来源&#xff0c;而且都是被动收入。换句话说&#xff0c;大多数富人知道…

Create Realtime-chat app

Tech:React,Node.js,Socket.io,MongoDB styled-component ​​​​​​​ 目录 Base setup Register funcitonality Login funcitonality set Avatar/profile picture Chat container setup useEffect basic hook ChatHeader ChatInput ChatMessage Set socket an…

I.MX6ULL裸机开发笔记2:镜像文件

目录 一、boot ROM程序 二、镜像文件五要素 三、芯片手册 四、芯片手册数据解读 1、空偏移 2、IVT表 3、DCD表 一、boot ROM程序 选择内部启动方式&#xff0c;启动boot ROM程序 初始化时钟&#xff0c;外部DDR3从外部存储介质加载代码 boot ROM程序是芯片厂…

十五天学会Autodesk Inventor,看完这一系列就够了(十一),放样和螺旋扫掠(绘弹簧)

众所周知&#xff0c;Autocad是一款用于二维绘图、详细绘制、设计文档和基本三维设计&#xff0c;现已经成为国际上广为流行的绘图工具。Autodesk Inventor软件也是美国AutoDesk公司推出的三维可视化实体模拟软件。因为很多人都熟悉Autocad&#xff0c;所以再学习Inventor&…

Redis缓存数据 | 黑马点评

目录 一、什么是缓存 二、添加Redis缓存操作 三、缓存更新策略 缓存的更新策略 ​编辑 业务场景 主动更新策略 案例 四、缓存穿透 1、是什么 2、解决方案 &#xff08;1&#xff09;缓存空对象 &#xff08;2&#xff09;布隆过滤器 &#xff08;3&#xff09;其…

【春节安全保障有我们】安全狗春节放假值班通知

兔年纳福 辛勤拼搏了一年 终于迎来了福兔吉祥年 众人沉浸于准备过年的氛围中 却有些人为春节期间的网络安全担忧 因为春节也是不法分子们 伺机而动、“搞事情”的“好时机” 2023 NEW YEAR 不得不防的安全风险 1、主机安全遭受威胁 &#xff08;云&#xff09;主机系统…

Rust语言基础

安装 Rust 官网&#xff1a;https://www.rust-lang.org/Linux or Mac: curl https://rustup.rs -sSf | sh Windows: 按官网指示操作 Windows Subsystem for Linux: curl --proto ‘https’ --tlsv1.2 -sSf https://sh.rustup.rs | sh 查看是否安装成功 rustc --version 更…

【openEuler】x2openEuler工具使用

文章目录一、关于x2openEuler二、工具准备三、前期准备1、安装CentOS72、下载x2openEuler3、安装x2openEuler4、执行bash5、访问上述网站6、安装x2openEuler-client&#xff08;1&#xff09;在CentOS-7.6Evetything1上找到x2openEuler-client&#xff08;2&#xff09;把x2ope…

Android Studio 支持手机投屏电脑

有时当我们在线上做技术分享或者功能演示时&#xff0c;希望共享连接中的手机屏幕&#xff0c;此时我们会求助 ApowerMirror&#xff0c;LetsView&#xff0c;Vysor&#xff0c;Scrcpy 等工具。如果你是一个 Android Developer&#xff0c;那么现在你有了更好的选择。 Android…

蓝桥杯--快排+队列+尺取法

&#x1f603;这只松鼠如约而至 - 许嵩 - 单曲 - 网易云音乐 &#x1f603;你买菜吗玫瑰 - 要不要买菜 - 单曲 - 网易云音乐 &#x1f603;一起玩吧这世界那么多人&#xff08;电影《我要我们在一起》主题曲&#xff09; - 莫文蔚 - 单曲 - 网易云音乐 前言 这是我在CSD…

一文讲透单点登录架构思想(SSO)

目录什么是单点登录&#xff1f;非单点登录架构单点登录架构什么是CAS单点登录SSO演进1.同域2.同父域3.跨域CASCAS术语CAS场景单点登录优缺点优点缺点什么是单点登录&#xff1f; 单点登录(SingleSignOn&#xff0c;SSO)&#xff0c;就是通过用户的一次性鉴别登录。当用户在身份…

【数据结构与算法理论知识点】 4、树和二叉树

4、树和二叉树 逻辑结构 4.1、树的定义和基本术语 树是n个结点的有限集 树的其他表示方式 基本术语 根——即根结点&#xff08;没有前驱&#xff09; 叶子——即终端结点&#xff08;没有后继&#xff09; 森林——指m棵不相交的树的集合&#xff08;例如删除根节点A后的…

Apache Solr 9.1-(二)集群模式运行

Apache Solr 9.1-&#xff08;二&#xff09;集群模式运行 Solr是一个基于Apache Lucene的搜索服务器&#xff0c;Apache Lucene是开源的、基于Java的信息检索库&#xff0c;Solr能为用户提供无论在任何时候都可以根据用户的查询请求返回结果&#xff0c;它被设计为一个强大的文…

synchronized锁升级

假如 synchronized 是「王」身边的「大总管」&#xff0c;那么 Thread 就像是他后宫的王妃。「王」每日只能选择一个王妃陪伴&#xff0c;王妃们会想方设法争宠获得陪伴权&#xff0c;大总管需要通过一定的手段让王「翻牌」一个「王妃」与王相伴。 今日听「码哥」胡言乱语解开…

1. Linux 磁盘管理(分区、格式化、挂载)

目录 1. Linux 内核版与发行版 2. Linux中磁盘的管理(分区、格式化、挂载) 2.1 磁盘定义、分类和命名 2.2 分区的定义和划分 2.3 磁盘格式化(高级/逻辑格式化) 2.4 挂载操作 1. Linux 内核版与发行版 内核版&#xff1a;Linus Torvalds最初组织很多人完成的Linux操作系统只…

Ubuntu20.04下安装显卡驱动

环境配置 系统: Ubuntu 20.04 CPU: i5 GPU:Geforce 960M Ubuntu安装显卡驱动 1、查看当前显卡安装情况 使用glxinfo查看 https://dri.freedesktop.org/wiki/glxinfo/ $ glxinfo Command glxinfo not found, but can be installed with: sudo apt install mesa-utils需要安…

postgresql FDW概念、用法与原理小结

最近突然遇到了一批使用fdw的场景&#xff0c;整理记录一把。 一、 强大的FDW FDW (foreign-data wrapper&#xff0c;外部数据包装器)&#xff0c;可以让我们在PG中使用SQL查询极为丰富的外部数据&#xff1a; 本实例和其他pg实例中的pg库主流关系型数据库&#xff1a;Oracle…

装饰模式(decorator-pattern)

装饰模式(decorator-pattern) 文章目录装饰模式(decorator-pattern)一、手抓饼点餐系统二、要求进阶三、装饰模式概要四、装饰模式的优劣及应用场景1. 优点2.缺点3.应用场景一、手抓饼点餐系统 请设计一个手抓饼点餐系统&#xff0c;支持加配菜&#xff0c;比如里脊、肉松、火…

C++ STL

目录 1.STL诞生 2.STL概念 3.STL六大主件 4.STL容器 算法 迭代器 5.容器算法迭代器初识&#xff0c;vector 5.1vector存放内置数据类型&#xff0c; 5.2vector存放自定义数据类型&#xff0c;解引用.访问&#xff0c;指针->访问&#xff0c;存放自定义数据类型指针。迭代器…

LeetCode(Array)1365. How Many Numbers Are Smaller Than the Current Number

1.问题 Given the array nums, for each nums[i] find out how many numbers in the array are smaller than it. That is, for each nums[i] you have to count the number of valid j’s such that j ! i and nums[j] < nums[i]. Return the answer in an array. Examp…