深蓝学院C++基础与深度解析笔记 第 10 章 泛型算法与 Lambda 表达式

news2024/11/25 10:08:50

第 10 章 泛型算法与 Lambda 表达式

1. 泛型算法

1.1 泛型算法: 可以支持多种类型的算法。

int x[100];
std::sort(std: : begin(x), std : :end(x));

1.11 这里重点讨论 C++ 标准库中定义的算法: <algorithm > <numeric> <ranges>
1.12 为什么要引入泛型算法而不采用方法的形式?

1、 内建数据类型不支持方法
2、计算逻辑存在相似性,避免重复定义

1.13 如何实现支持多种类型:使用迭代器作为算法与数据的桥梁

1.2 泛型算法通常来说都不复杂,但优化足够好

1.3 一些泛型算法与方法同名,实现功能类似,此时建议调用特定方法而非算法:
– std::find() V.S. std::map::find()

1.4 泛型算法的分类

– 读算法:给定迭代区间,读取其中的元素并进行计算 :accumulate() / find() / count()

//版本一
template<class InputIt, class T>
constexpr // sinceC++20
T accumulate( InputIt first, InputIt last,T init)
{
      for (; first != last; ++first) {
            init= std ::move(init) + *first; // std::move since C++20
            }
     return init;
}
//版本二:
templatecclass InputIt, class T, class BinaryOperation>
constexpr // since C++20
T accumulate(InputIt first, InputIt last,T init, BinaryOperation op)
{
      for (; first != last; ++first) {
           init = op(std : : move(init)*first); // std : :move since C++20
           }
       return init;
}

– 写算法:向一个迭代区间中写入元素: 单纯写操作: fill() / fill_n()
– 读 + 写操作: transform() / copy()

template<class InputIt,class 0utputIt,class Unary0peration>
outputIt transform(InputIt firstl,InputIt last1,outputIt d_first, Unary0peration unary_op)
    while (first1 != last1) {
           *d_first++ = unary_op(*first1++);
    }
    return d_first;
}

● 注意:写算法一定要保证目标区间足够大!

std: :vector<int> x(10);
std:: fill_n(x.begin(), 100,3);   //越界未定义

– 排序算法:改变输入序列中元素的顺序 sor()) / unique()(获取有序区间,只保留一个重复元素)

template<class ForwardIt>
ForwardIt unique( ForwardIt first,ForwardIt last)
{
      if (first == last)
          return last;
      ForwardIt result = first;
      while (++first != last) {
          if( ! (*result == *first) &&  ++result != first) {
             *result = std ::move(*first);
          }
      }
      return ++result;
}

结果:在这里插入图片描述

加入删除重复后的代码:
在这里插入图片描述
结果:在这里插入图片描述

1.5 泛型算法使用迭代器实现元素访问

1.6 迭代器的分类:

– 输入迭代器:可读,可递增 典型应用为 —— find() 算法,用于在一个容器中查找指定值的元素:

template <class InputIt, class T>
InputIt find(InputIt first, InputIt last, const T& value);

– 输出迭代器:可写,可递增 典型应用为 —— copy()算法:用于将一个容器中的元素复制到另一个容器中。

template <class InputIt, class OutputIt>
OutputIt copy(InputIt first, InputIt last, OutputIt d_first);

– 前向迭代器:可读写,可递增 典型应用为 —— replace() 算法,用于在一个容器中将指定值的元素替换为新的值:

template <class ForwardIt, class T>
void replace(ForwardIt first, ForwardIt last, const T& old_value, const T& new_value);

– 双向迭代器:可读写,可递增递减 典型应用为 —— reverse() 算法,用于反转容器中元素的顺序:

template <class BidirectionalIt>
void reverse(BidirectionalIt first, BidirectionalIt last);

– 随机访问迭代器:可读写,可增减一个整数 典型应用为 —— sort()算法

● 一些算法会根据迭代器类型的不同引入相应的优化:如 distance 算法,用于计算两个迭代器之间的距离(元素个数),返回迭代器 first 和 last 之间的距离(last 减去 first 的差值):

template <class InputIt>
typename iterator_traits<InputIt>::difference_type distance(InputIt first, InputIt last);

1.7 一些特殊的迭代器:

插入迭代器: back_insert_iterator() / front_insert_iterator() / insert_iterator()

– 流迭代器: istream_iterator()输入 / ostream_iterator()输出
在这里插入图片描述
结果:
1
2

– 反向迭代器:rbegin() / rend()
示意图 :
在这里插入图片描述

反向迭代器+输出流迭代器:
在这里插入图片描述
输出:
在这里插入图片描述

– 移动迭代器: move_iterator()

在这里插入图片描述
会读一次,但是容器中的元素还会被移动掉(没了)。

1.8 迭代器与哨兵( Sentinel )
在C++中,迭代器和哨兵是用于在容器中访问元素的两种不同方式。

迭代器是一种对象,它允许你在容器中遍历和访问元素。你可以将迭代器视为指向容器元素的指针。迭代器可以用于遍历容器中的元素,执行插入、删除和修改等操作。

哨兵是一种特殊的迭代器值,它并不指向容器中的任何元素,而是用于表示遍历的结束。当你使用迭代器遍历容器时,你可以将哨兵作为终止条件来判断是否到达了容器的末尾。

迭代器和哨兵在C++标准库中被广泛使用,尤其是在容器类(例如vector、list、set等)中。通过使用迭代器和哨兵,你可以以一种通用的方式对不同类型的容器进行遍历和操作,而不需要关心底层容器的具体实现细节。

下面是一个使用迭代器和哨兵遍历vector容器的简单示例:

#include <iostream>
#include <vector>

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

    // 使用迭代器遍历容器
    for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        std::cout << *it << " ";
    }

    return 0;
}

在上面的示例中,vec.begin()返回一个指向vector容器第一个元素的迭代器,vec.end() 返回一个指向vector容器最后一个元素之后的位置的迭代器(即哨兵)。 通过使用迭代器遍历容器,我们可以输出vector中的所有元素。

需要注意的是,C++11引入了更方便的迭代器语法,即使用范围-based for循环。可以简化上面示例中的迭代器遍历部分:

for (const auto& element : vec) {
    std::cout << element << " ";
}

这样可以更简洁地遍历容器中的元素,而不需要显式使用迭代器和哨兵。
1.9 并发算法( C++17 / C++20 )
std::execution::seqstd::execution::parstd::execution::par_unseqstd::execution::unseq是C++17中引入的四种执行策略,用于指定算法的执行方式。它们与并行算法和顺序算法有关。

  1. std::execution::seq(顺序执行策略):该策略指定算法在单个线程上按顺序执行。它适用于不需要并行化的算法,或者在某些情况下,使用并行化可能会导致错误或性能下降。

  2. std::execution::par(并行执行策略):该策略指定算法在多个线程上并行执行。算法可以尝试并行化操作以提高性能。使用此策略时,算法应该是无副作用的(没有竞争条件或依赖关系),以确保并行执行的正确性。

  3. std::execution::par_unseq(并行非顺序执行策略):该策略指定算法在多个线程上并行执行,并且可以使用非顺序执行(例如乱序执行)。这意味着算法可以在并行执行的同时,无需保持原始的顺序。这对于可以并行化且不依赖顺序的算法是有益的,但需要注意的是,结果的顺序可能与输入不同。

  4. std::execution::unseq(非顺序执行策略):该策略指定算法可以以非顺序方式执行,但不一定并行化。这个策略通常适用于那些可以在单个线程上进行向量化或优化的算法。

这些执行策略可以在支持并行算法的标准库算法中使用,例如std::for_eachstd::transformstd::reduce等。通过指定适当的执行策略,可以根据算法的要求选择适当的并行性和顺序性,以达到最佳性能。

以下是一个简单的示例,展示了使用std::execution::parstd::execution::seq执行策略的区别:

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

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

    // 使用 std::execution::par 并行执行算法
    std::for_each(std::execution::par, numbers.begin(), numbers.end(), [](int num) {
        std::cout << num << " ";
    });
    std::cout << std::endl;

    // 使用 std::execution::seq 顺序执行算法
    std::for_each(std::execution::seq, numbers.begin(), numbers.end(), [](int num) {
        std::cout << num << " ";
    });
    std::cout << std::endl;

    return 0;
}

在上面的示例中,std::for_each算法用于遍历容器中的元素,并对每个元素执行lambda函数。通过指定不同的执行策略,可以选择并行执行或顺序执行。
使用std::execution::par并行执行策略时,输出的结果可能是无序的,因为算法在多个线程上并行执行,输出顺序不确定。例如,可能的输出为:

Copy code
3 5 1 2 4 

使用std::execution::seq顺序执行策略时,输出的结果是有序的,因为算法在单个线程上按顺序执行。输出的顺序与容器中元素的顺序一致。例如,可能的输出为:

Copy code
1 2 3 4 5

2. bind 与 lambda 表达式

● 很多算法允许通过可调用对象自定义计算逻辑的细节
transform / copy/copy_if / sort

● 如何定义可调用对象

– 函数指针: 概念直观,但定义位置受限,函数内部无法定义函数
在这里插入图片描述
输出: 4 5 6 7 8 9 10

– 类: 功能强大,但书写麻烦
– bind :
基于已有的逻辑灵活适配,但描述复杂逻辑时语法可能会比较复杂难懂, 主要是通过绑定的方式修改可以调用的方式
● 早期的 bind 雏形: std::bind1st / std::bind2nd, 用于绑定第一/第二个参数,具有了 bind 的基本思想,但功能有限,
std::bind ( C++11 引入):用于修改可调用对象的调用方式
在这里插入图片描述

注释:50 作为可调用对象的MyPredict()函数的第为一个参数调用,依据_1或_2
确定此参数的位置

– 调用 std::bind 时,传入的参数会被复制,这可能会产生一些调用风险,可以尝试智能指针
– 可以使用 std::ref() 或 std::cref() 避免复制的行为

● std::bind_front ( C++20 引入): std::bind 的简化形式
在这里插入图片描述
– lambda 表达式: 小巧灵活,功能强大
●lambda 表达式:

– 为了更灵活地实现可调用对象而引入
– C++11 ~ C++20 持续更新
  ● C++11 引入 lambda 表达式
  ● C++14 支持初始化捕获、泛型 lambda
  ● C++17 引入 constexpr lambda , *this 捕获
  ● C++20 引入 concepts ,模板 lambda

●lambda 表达式会被编译器翻译成类进行处理

●lambda 表达式的基本组成部分:

auto x = [](int val) { return val > 3; };  //注意末尾分号;
std::cout << x(5) << std::endl;
– 参数与函数体
– 返回类型:如有多分支return,类型需要一致,不然那auto推导不出来返回值类型
– 捕获:针对函数体中使用的局部自动对象进行捕获
  ● 值捕获、引用捕获与混合捕获
  ● this 捕获
  ● 初始化捕获( C++14 )
  ●*this 捕获( C++17

捕获: 在内部修改不会改变外部值,属于值传递
在这里插入图片描述
this: 在这里插入图片描述

在这里插入图片描述
C++17:
在这里插入图片描述

– 说明符
● mutable (可变)/ constexpr (C++17) / consteval (C++20)……
– 模板形参( C++20 )
在这里插入图片描述

●lambda 表达式的深入应用
– 即调用函数表达式( Immediately-Invoked Function Expression, IIFE )
– 捕获时计算( C++14 )
– 使用 auto 避免复制( C++14 )
– Lifting ( C++14 )
– 递归调用( C++14 )

3. 泛型算法的改进——ranges

需要头文件 #include <ranges>( c++20)

    std::vector<int> x{1, 2, 3, 4, 5}; 
    auto it = std::ranges::find(x,3); 
    std::cout <*it<< std::endl; 

● 可以使用容器而非迭代器作为输入

– 通过 std::ranges::dangling 避免返回无效的迭代器

● 引入映射概念,简化代码编写
● 引入 view ,灵活组织程序逻辑
● 从类型上区分迭代器与哨兵

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

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

相关文章

简洁清新后台模板推荐(光年)

目录 前言一、后台模板介绍1.作者介绍2.模板介绍 二、界面展示1.登录2.首页3.UI元素4.表单5.工具类6.示例页面7.主题选择 三、入口总结 前言 作为后端开发人员&#xff0c;前端技术确实不精通&#xff0c;也没有太多的精力搞前端。所以一直在搜寻一些现成的模板。最近发现一个…

基于单片机的盲人导航智能拐杖老人防丢防摔倒发短息定位

功能介绍 以STM32单片机作为主控系统&#xff1b; OLED液晶当前实时距离&#xff0c;安全距离&#xff0c;当前经纬度信息&#xff1b;超声波检测小于设置的安全距离&#xff0c;蜂鸣器报警提示&#xff1a;低于安全距离&#xff01;超声波检测当前障碍物距离&#xff0c;GPS进…

Linux-CentOS7版本的系统搭建达梦数据库

目录 VMware中安装CentOS 7.5并做相关的配置搭建Docker环境搭建达梦数据库拉取镜像查看镜像命令为镜像设置一个别名根据镜像 创建一个容器 根据镜像 创建一个容器启动并进入容器 VMware中安装CentOS 7.5并做相关的配置 使用VMware安装CentOS7的虚拟机可以参考另外一篇文章&…

netwox构造免费ARP数据包【网络工程】(保姆级图文)

目录 构造免费的 ARP 数据包。1) 构造免费的 ARP 数据包2) 使用 Wireshark 进行抓包 总结 欢迎关注 『网络工程专业』 系列&#xff0c;持续更新中 欢迎关注 『网络工程专业』 系列&#xff0c;持续更新中 温馨提示&#xff1a;对虚拟机做任何设置&#xff0c;建议都要先快照备…

Android修改符盘名称与蓝牙名称

修改符盘名称 android\frameworks\base\media\java\android\mtp\MtpDatabase.java 修改蓝牙名称 android\system\bt\btif\src\btif_dm.cc 注&#xff1a;尽量不在build与device文件下修改设备名称&#xff0c;因为这是套指纹的软件 这里的值必须和之前的软件是一样的

Spring Cloud Feign: 了解、原理和使用

Spring Cloud Feign: 了解、原理和使用 Spring Cloud Feign 是 Spring Cloud 生态系统中的一个重要组件&#xff0c;它提供了一种声明式的、基于注解的 REST 客户端&#xff0c;能够方便地实现服务之间的调用和通信。在本文中&#xff0c;我们将介绍 Spring Cloud Feign 的概念…

链码的打包与升级

目录 1、链码的打包与签名 ​编辑 对链码的签名 1、安装已经添加签名的链码 2、安装成功之后进行链码的实例化操作&#xff0c;同时指定其背书策略 测试 1、查询链码 2、调用链码 3、查询链码 链码的升级 1、安装链码 2、升级链码 3、测试 1、查询 2、调用 3、…

【花雕】全国青少年机器人技术一级考试备考实操搭建手册3

目录 1、秋千 2、跷跷板 3、搅拌器 4、奇怪的钟 5、起重机 6、烤肉架 7、手摇风扇 8、履带车 9、直升机 10、后轮驱动车 搅拌器是一种可以帮助我们将不同的物质混合在一起的机器。它通常由一个电动机和一个搅拌器头组成。当我们把需要混合的物质放入容器中&#xff0c;打开搅拌…

C语言之函数递归

前言   从前有座山&#xff0c;山里有座庙&#xff0c;庙里有个老和尚&#xff0c;正在给小和尚讲故事呢&#xff01;故事是什么呢&#xff1f;"从前有座山&#xff0c;山里有座庙&#xff0c;庙里有个老和尚&#xff0c;正在给小和尚讲故事呢&#xff01;故事是什么呢&…

字典树的数据结构

Trie字典树主要用于存储字符串&#xff0c;Trie 的每个 Node 保存一个字符。用链表来描述的话&#xff0c;就是一个字符串就是一个链表。每个Node都保存了它的所有子节点。 例如我们往字典树中插入see、pain、paint三个单词&#xff0c;Trie字典树如下所示&#xff1a; 也就是…

zookeeper的动态扩容

附属意义的扩容&#xff1a;扩容的新增节点为观察者observer 1.观察者概念&#xff1a; a.在zookeeper引入此新的zookeeper节点类型为observer&#xff0c;是为了帮助处理投票成本随着追随者增加而增加的问题并且进一步完善了zookeeper的可扩展性 b.观察者不参与投票&#x…

【机器学习】基于卷积神经网络 CNN 的猫狗分类问题

文章目录 一、卷积神经网络的介绍1.1 什么是卷积神经网络1.2 重要层的说明1.3 应用领域二、 软件、环境配置2.1 安装Anaconda2.2 环境准备 三、猫狗分类示例3.1 图像数据预处理3.2 基准模型3.3 数据增强3.4 dropout层四、总结 一、卷积神经网络的介绍 1.1 什么是卷积神经网络 …

决策树ID3

文章目录 题目一基础知识解题过程①算总的信息量②求解各个指标的信息增益&#xff0c;以此比较得出根节点③ 从根节点下的晴天节点出发循环上述步骤④ 从根节点下的多云节点出发&#xff0c;循环上述步骤⑤ 从根节点下的雨节点出发&#xff0c;循环上述步骤⑥画出最终的决策树…

ChatGPT实战:职业生涯规划

ChatGPT的出现&#xff0c;不仅改变了人们对人工智能技术的认识&#xff0c;也对经济社会发展产生了深远的影响。那么&#xff0c;在ChatGPT时代&#xff0c;人们应该如何规划自己的职业呢&#xff1f; 职业规划是一个有意义且重要的过程&#xff0c;它可以帮助你在职业生涯中取…

避坑指南:当你将 Django 项目部署到 Heroku 你需要避多少坑?

文章目录 Cors 跨域问题localhost 阶段Heroku 部署阶段 Procfile 启动文件Database 数据库相关内容localhost 阶段Heroku 部署阶段settings.py 中 正确的设置方式官方给出的 settings.py makemigration & migrate 数据迁移 requirements.txt & runtime.txt 版本和库总结…

如何记录程序运行时间

使用c标准库中时钟类来实现。 使用模板类&#xff1a; chrono::duration<int,ratio<1,2>(20)>前面的int限定了延时单位是一个整数。只要1小时&#xff0c;2小时&#xff0c;但是没有1.5小时。ratio<1,2>代表一个分数。后面的2代表分母&#xff0c;前面的1为分…

Keil5中写的软件延时函数不起作用现象解析_ARM_Compiler_volatile关键字

一、问题描述 在学习野火霸天虎F407寄存器点亮LED时&#xff0c;出现实验现象&#xff1a;LED灯不亮&#xff0c;野火霸天虎F407资料。 main.c代码如下&#xff1a; #include "stm32f4xx.h"void Delay(unsigned int count);int main(void) { #if 0/* 第一步&a…

Axure教程——循环倒计时

本文介绍的是用Axure制作的循环倒计时 效果 预览&#xff1a;https://zhgcck.axshare.com 功能 1、点击“开始”按钮&#xff0c;倒计时开始 2、数值到1时&#xff0c;从10重新倒计时 制作 一、需要的元件 矩形、动态面板 二、制作过程 拖入一个动态面板&#xff0c;命名为…

gnuplot 命令行绘图工具命令

gnuplot命令行绘图工具命令 绘图示例预览 gnuplot工具非常强大&#xff0c;可以在命令行进行曲线绘图&#xff0c;当然也可以在UI界面绘图。 绘图命令&#xff1a; gnuplot> plot test.csv u ($0):1 w lp t c1, test.csv u ($0):2 w lp t c2绘图效果&#xff1a; 数据文…

CSDN 成长记

博客之星入围排行榜 - 2023.5.7 博文 PaddleVideo 简介以及文件目录详解 - 入选内容榜咯 - 2023.5.9 付费专栏 微机系统与接口上机实验_TD PITE型 终于开张咯 - 2023.5.15 博文 ResNet 论文理解含视频 - 入选内容榜第13名 - 2023.5.16 博文 ResNet 论文理解含视频 - 入选全站综…