C++进阶篇6---lambda表达式

目录

一、lambda表达式

1.引入 

2、lambda表达式语法

二、包装器---function

1.引入

2.包装器介绍

三、bind


一、lambda表达式

1.引入 

class Person {
public:Person(int age,string name):_age(age),_name(name){}
//private://方便后面的举例int _age;string _name;
};int main()
{vector<Person>v = { {10,"zhangsan"},{40,"wangwu"},{20,"lisi"} };//当我们要通过姓名/年龄排序时,我们应该怎么办?//sort(v.begin(),v.end());return 0;
}

根据我们之前学过的知识,我们可以写两个仿函数,分别对应姓名和年龄的比较,如下

struct comp_age {bool operator()(const Person& x, const Person& y) {return x._age < y._age;}
};struct comp_name {bool operator()(const Person& x, const Person& y) {return x._name < y._name;}
};

但这种方法太麻烦了,而且一旦排序的标准多起来,给仿函数起什么名字都是个问题,所以出了lambada表达式,如下

int main()
{vector<Person>v = { {10,"zhangsan"},{40,"wangwu"},{20,"lisi"} };sort(v.begin(), v.end(), [](const Person& x, const Person& y) {x._age < y._age; });sort(v.begin(), v.end(), [](const Person& x, const Person& y) {x._name < y._name; });return 0;
}

可以看出lambda表达式实际上一个匿名函数,跟函数很相似

2、lambda表达式语法

lambda表达式书写格式:

        [capture-list] (parameters) mutable -> return-type { statement }

lambda表达式各部分说明

  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:

在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情

其实就是看着比较复杂,跟一般的函数比起来,就只是少了一个函数名,多了一个捕捉列表而已,所以只要把捕捉列表的功能能清楚就能很好的掌握lambda表达式

捕获列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针
int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[]{}; // 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=]{return a + 3; }; // 省略了返回值类型,无返回值类型auto fun1 = [&](int c){b = a + c; }; fun1(10)cout<<a<<" "<<b<<endl;// 各部分都很完善的lambda函数,除了b是引用捕捉,其他全是传值方式捕捉auto fun2 = [=, &b](int c)->int{return b += a+ c; }; cout<<fun2(10)<<endl;//这里提醒一下:lambda表达式只能捕捉在它上面定义的变量,在它之后定义的无法捕捉// 复制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; }; cout << add_x(10) << endl; return 0;
}

注意:

a. 父作用域指包含lambda函数的语句块

b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割

比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量

[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

c. 捕捉列表不允许变量重复传递,否则就会导致编译错误

比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复

d. 在块作用域以外的lambda函数捕捉列表必须为空

e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。

f. lambda表达式之间不能相互赋值,即使看起来类型相同

void (*PF)();
int main()
{auto f1 = [] {cout << "hello world" << endl; };auto f2 = [] {cout << "hello world" << endl; };f1();f2();// 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了//f1 = f2;   // 编译失败--->提示找不到operator=()// 允许使用一个lambda表达式拷贝构造一个新的副本auto f3(f2);f3();// 可以将lambda表达式赋值给相同类型的函数指针PF = f2;PF();return 0;
}

 规则其实并不复杂,就是细节比较多,主要是没咋见识过,用多了就会发现它真的很香

这是上面代码的反汇编调试信息,其实lambda表达式的底层实现就是仿函数,只是类型名很奇怪,但都是调用的operator()这个成员函数

 注意:一般lambda表达式的类型名都是class lambda_XXXXX这种样式的,采用的是lambda_uuid的编码方式,不同编译器的命名方式不同,但是我们也能看出它是一个类的函数调用

对比仿函数

我们就能进一步理解捕捉列表,它其实和仿函数的成员变量很相似,一个是自己初始化,一个是捕捉已经存在的

二、包装器---function

1.引入

截止到目前,我们已经学了很3种"函数"---普通函数、仿函数、lambda表达式,它们的类型各不相同,一旦有模板需要传函数,就会导致一个问题,传不同类型的"函数",会产生多个实例,如下
template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}

而这一切都是因为类型不同,所以我们需要将它们的类型进行统一包装,避免这种情况发生,所以出了function包装器

2.包装器介绍

std::function 在头文件 < functional >
// 类模板 原型如下
template < class T > function ;     // undefined
template < class Ret , class ... Args >
class function < Ret ( Args ...) > ;
模板参数说明:
  • Ret: 被调用函数的返回类型
  • Args…:被调用函数的形参

function类型的对象能用函数指针,lambda表达式,仿函数赋值,前提是参数及返回值和function类型一样,如下

int main()
{// 函数名function<double(double)> func1 = f;cout << useF(func1, 11.11) << endl;cout << typeid(func1).name() << endl;//函数对象function<double(double)> func2 = Functor();cout << useF(func2, 11.11) << endl;cout << typeid(func2).name() << endl;//lamber表达式function<double(double)> func3 = [](double d)->double { return d / 4; };cout << useF(func3, 11.11) << endl;cout << typeid(func3).name() << endl;return 0;
}

可以看出模板只实例化了一份,有点类似多态,传不同的函数,会有不同的效果,而这都是因为function包装器的类型是统一的

有人可能觉得这个用处好像不是很大,但其实在大型项目中,还是很有必要的,而且它的应用场景远不止于此,我们来看看下面的应用场景

正常来说,我们得写if-else语句或者switch语句一个符号一个符号的匹配 

但是现在我们可以用包装器function来简化代码,使得它看起来更加优雅

3.类成员函数的包装

class Plus {
public:int ADDi(int x, int y){return x, y;}static double ADDd(double x, double y){return x + y;}
private:int a;
};int main()
{
//1.注意类的成员函数的地址怎么取,&域名::函数名
//2.非静态成员函数,第一个参数可以是类,也可以是指针function<int(Plus, int, int)>func1 = &Plus::ADDi;//可以是Plus,也可以是Plus*function<int(Plus*, int, int)>func2 = &Plus::ADDi;function<double(double, double)>func3 = &Plus::ADDd;return 0;
}

 

三、bind

std::bind函数定义在头文件中,是一个 函数模板 ,它就像一个函数包装器(适配器),接受一个可 调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而 言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M 可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺 序调整等操作。
// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

看着概念很复杂,我们来写几个看看,就大致明白了

int test(int x, int y)
{return x * 2 + y * 3;
}int main()
{//placeholders::_xx代表的是funcion中的参数,_1代表第一个,_2代表第二个以此类推function<int(int)>fun1 = bind(test, 2, placeholders::_1);cout << fun1(1) << endl;function<int(int)>fun2 = bind(test, placeholders::_1, 2);cout << fun2(1) << endl;function<int(int,int)>fun3 = bind(test, placeholders::_2, placeholders::_1);cout << fun3(2, 3) << endl;return 0;
}

 

这里解释一下,这三个打印结果,传参的对应关系如下

很显然,placeholders::_1,_2,…… 对应的是function中写的参数顺序,bind中第一个参数填函数名(当然仿函数对象,lambda表达式都可以),后面的参数分别对应函数参数的顺序。

function包装器的参数可以少于函数的参数,但是bind中传的参数一般要和原函数参数个数对应,我们可以通过bind来固定一些默认的参数值,或者调换一下参数的顺序,让我们用起函数来更加的"舒服"

成员函数的bind

class Plus {
public:int ADDi(int x, int y){return x, y;}static double ADDd(double x, double y){return x + y;}
private:int a = 0;
};int main()
{//如果只是单纯想想使用该函数的功能,可以将第一个参数写死,虽然第一个参数是指针,这里也可以传对象,可以看作是特例function<int(int, int)>func1 = bind(&Plus::ADDi, Plus(), placeholders::_1, placeholders::_2);cout << func1(1, 2) << endl;//这种是不行的,右值不能被取地址!!!//function<int(int, int)>func2 = bind(&Plus::ADDi, &Plus(), placeholders::_1, placeholders::_2);//cout << func2(1, 2) << endl;function<double(double, double)>func3 = bind(&Plus::ADDd, placeholders::_1, placeholders::_2);cout << func3(1.1, 2.3) << endl;return 0;
}

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

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

相关文章

JS生成登录验证码

采用js生成登录的验证码 采用的技术点有html&#xff0c;css&#xff0c;JS&#xff0c;jQuery HTML&#xff1a; <div class"box_b"><img src"./img/0775639c-c82c-4a29-937f-d2a3bae5151a.png" alt""><div class"regist…

智能变压器监控系统

智能变压器监控系统是一种先进的物联网技术和智能设备&#xff0c;能够实现对变压器的实时监测和管理&#xff0c;提高变压器的运行效率和可靠性&#xff0c;为用户提供及时、准确的变压器运行状态信息和故障预警。 力安科技A30变压器云控终端是一款集变压器温控仪、变压器运行…

LabVIEW开发工业设备远程在线状态监测

LabVIEW开发工业设备远程在线状态监测 项目需要减少意外停机和维护费用、提供更完整的机器操作和状态图、改进设备使用情况跟踪。 该解决方案是一个多节点&#xff08;即多站点&#xff09;远程监控系统&#xff0c;它利用了基于NI cRIO的控制器和定制的LabVIEW监测软件。 方…

【Lustre相关】应用部署-03-Lustre集群部署实践(软raid方案)

文章目录 一、前言1、硬件配置2、组网拓扑3、总体方案 二、软件安装三、集群部署1、配置多路径2、配置高可用集群3、配置zpool4、部署lustre5、配置Lustre角色高可用6、配置Lustre状态监控6.1、Lustre网络状态监控6.2、Lustre集群状态监控6.3、配置优化6.3.1、设置故障恢复不回…

JDK安装太麻烦?一篇文章搞定

JDK是 Java 语言的软件开发工具包&#xff0c;主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心&#xff0c;它包含了JAVA的运行环境&#xff08;JVMJava系统类库&#xff09;和JAVA工具。 JDK包含的基本组件包括&#xff1a; javac – 编译器&#xf…

FCRP第二题

【题目要求】 数据库中有一张地区数据统计表&#xff0c;但是并不规则 &#xff0c;记录类似于&#xff0c;225100:02:3:20160725是一串代码&#xff0c;以&#xff1a;分割&#xff0c;第1位为地区代码&#xff0c;第2位为分类代码&#xff0c;第3位为数量&#xff0c;第4位为…

X540t2关于手动安装intel驱动

首先去intel驱动官网下载&#xff0c;win10和win11驱动一样 https://www.intel.cn/content/www/cn/zh/download/18293/intel-network-adapter-driver-for-windows-10.html 然后下载下来解压 将Wired_driver_28.2_x64.exe修改成Wired_driver_28.2_x64.zip文件再解压 打开设备管…

基于springboot + vue 学生网上请假系统

qq&#xff08;2829419543&#xff09;获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;springboot 前端&#xff1a;采用vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xf…

GeoServer本地部署与远程访问Web管理页面——“cpolar内网穿透”

文章目录 前言1.安装GeoServer2. windows 安装 cpolar3. 创建公网访问地址4. 公网访问Geo Servcer服务5. 固定公网HTTP地址 前言 GeoServer是OGC Web服务器规范的J2EE实现&#xff0c;利用GeoServer可以方便地发布地图数据&#xff0c;允许用户对要素数据进行更新、删除、插入…

(C语言)求出1,2,5三个数不同个数组合为100的组合个数

#include<stdio.h> int main() {int count;for(int i 0;i < 100;i )for(int j 0;j < 50;j )for(int k 0;k < 20;k ){if(i j*2 k*5 100){count;printf("100可以拆分为%d个1元&#xff0c;%d个2元&#xff0c;%d个5元\n",i,j,k);} }printf("…

数据接口测试工具 Postman 介绍!

此文介绍好用的数据接口测试工具 Postman&#xff0c;能帮助您方便、快速、统一地管理项目中使用以及测试的数据接口。 1. Postman 简介 Postman 一款非常流行的 API 调试工具。其实&#xff0c;开发人员用的更多。因为测试人员做接口测试会有更多选择&#xff0c;例如 Jmeter…

探索人工智能领域——每日20个名词详解【day6】

目录 前言 正文 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请事先与我联系以…

HCIP——交换综合实验

一、实验拓扑图 二、实验需求 1、PC1和PC3所在接口为access&#xff0c;属于vlan2&#xff1b;PC2/4/5/6处于同一网段&#xff0c;其中PC2可以访问PC4/5/6&#xff1b;但PC4可以访问PC5&#xff0c;不能访问PC6 2、PC5不能访问PC6 3、PC1/3与PC2/4/5/6/不在同一网段 4、所有PC通…

CleanMyMac X2024破解注册激活码

CleanMyMac X for Mac中文2024版只需两个简单步骤就可以把系统里那些乱七八糟的无用文件统统清理掉&#xff0c;节省宝贵的磁盘空间。 cleanmymac x个人认为X代表界面上的最大升级&#xff0c;功能方面有更多增加&#xff0c;与最新macOS系统更加兼容&#xff0c;流畅地与系统性…

选择排序、插入排序、希尔排序

1.选择排序 算法描述 将数组分为两个子集&#xff0c;排序的和未排序的&#xff0c;每一轮从未排序的子集中选出最小的元素&#xff0c;放入排序子集 重复以上步骤&#xff0c;直到整个数组有序 选择排序呢&#xff0c;就是首先在循环中&#xff0c;找到数组中最小的元素。在…

Docker 安装部署 Sentinel Dashboard

1、下载 jar 包 官方 jar 包下载地址&#xff1a;https://github.com/alibaba/Sentinel/releases 或者点击 链接 直接跳转到下载页 进入链接下载你需要的版本 下载完毕&#xff08;我这里统一放在一个sentinel目录内&#xff09; 2、编写 Dockerfile 文件&#xff08;这里我不…

详解—[C++数据结构]—红黑树

目录 一、红黑树的概念 ​编辑二、红黑树的性质 三、红黑树节点的定义 四、红黑树结构 五、红黑树的插入操作 5.1. 按照二叉搜索的树规则插入新节点 5.2、检测新节点插入后&#xff0c;红黑树的性质是否造到破坏 情况一: cur为红&#xff0c;p为红&#xff0c;g为黑&…

一键式紧急报警柱系统

随着科技的不断发展&#xff0c;一键式紧急报警柱在我们的生活和工作中扮演着越来越重要的角色。在这篇文章中&#xff0c;我们将一起探究与一键式紧急报警柱有关的知识。 一键式紧急报警柱是一种常见的安全防护设备&#xff0c;能够在紧急情况下快速发出警报&#xff0c;保护…

探索数据之美:优雅权重计算方法与Python实践

写在开头 在数据的世界里,我们常常需要通过各种方法为不同的数据点分配合理的权重。这是数据分析中至关重要的一环,它决定了模型的准确性和结果的可信度。本文将引导您探索数据分析中常用的权重计算方法,并通过清晰的Python代码实现,让您轻松驾驭权重的奥秘。 1.常见分类…

XUbuntu22.04之安装OBS30.0强大录屏工具(一百九十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…