首页 > 编程语言 >C++笔记---lambda表达式

C++笔记---lambda表达式

时间:2024-11-10 17:19:46浏览次数:3  
标签:return 函数 int C++ --- 捕捉 const lambda

1. 简单介绍及语法

Lambda表达式是C++11引入的一种便捷的匿名函数定义机制。

lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。

lambda 表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接收 lambda 对象。

Lambda表达式的基本语法

Lambda表达式的基本语法格式如下:

[capture_list](parameters) -> return_type { function_body }
  • capture_list:捕获列表(不可省略),该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数用于指定Lambda表达式可以访问的外部变量,以及捕获的方式(按值或按引用)。
  • parameters:参数列表(可以省略),定义传递给Lambda表达式的参数。如果不需要参数传递,则可以连同()⼀起省略
  • -> return_type可选的尾部返回类型指定(可以省略)。如果省略,则由函数体中的返回语句推断返回值。
  • function_bodyLambda表达式的函数体(不可省略),包含执行的代码,函数体为空也不能省略。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
#include<iostream>
using namespace std;

int main()
{
	// ⼀个简单的lambda表达式
	auto add = [](int x, int y)->int {return x + y; };
	cout << add(1, 2) << endl;
	// 1、捕捉为空也不能省略
	// 2、参数为空可以省略
	// 3、返回值可以省略,可以通过返回对象自动推导
	// 4、函数题不能省略
	auto hello = []
		{
			cout << "hello World" << endl;
			return 0;
		};
	hello();
	int a = 0, b = 1;
	auto swap1 = [](int& x, int& y)
		{
			int tmp = x;
			x = y;
			y = tmp;
		};
	swap1(a, b);
	cout << a << ":" << b << endl;

	return 0;
}

2. 捕捉列表

在lambda表达式中不可以直接使用外部变量,因为其本质上是一个函数,要使用其所在域内的变量,需要用捕捉列表进行捕捉。

捕捉的方式有:显式传值捕捉,显式传引用捕捉,隐式传值捕捉,隐式传引用捕捉。

显式传值捕捉与传引用捕捉

第一种捕捉方式是在捕捉列表中显式的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。

直接将变量写到捕捉列表中时为传值捕捉,加上 "&" 则表示传引用捕捉:

[x,y,&z] 表示x和y值捕捉,z引用捕捉。

注意:传值捕捉的变量是被const修饰的,不可修改。

int main()
{
    // 只能用当前lambda局部域和捕捉的对象和全局对象
    int a = 0, b = 1, c = 2, d = 3;
    auto func1 = [a, &b]
	    {
		    // 值捕捉的变量不能修改,引用捕捉的变量可以修改
		    //a++;
		    b++;
		    int ret = a + b;
		    return ret;
	    };
    cout << func1() << endl;
    return 0;
}
隐式传值捕捉与传引用捕捉

"[=]" :捕捉列表中传入 "=" 表示自动传值捕捉在函数体内被用到的变量;

"[&]":捕捉列表中传入 "&" 表示自动传引用捕捉在函数体内被用到的变量。

int main()
{
    // 只能用当前lambda局部域和捕捉的对象和全局对象
    int a = 0, b = 1, c = 2, d = 3;
    // 隐式值捕捉
    // 用了哪些变量就捕捉哪些变量
    auto func2 = [=]
	    {
		    int ret = a + b + c;
		    return ret;
	    };
    cout << func2() << endl;
    // 隐式引用捕捉
    // 用了哪些变量就捕捉哪些变量
    auto func3 = [&]
	    {
		    a++;
		    c++;
		    d++;
	    };
    func3();
    cout << a << " " << b << " " << c << " " << d << endl;
    return 0;
}
 混合使用显式捕捉和隐式捕捉

在捕捉列表中混合使用隐式捕捉和显式捕捉:

[=,&x] 表示其他变量隐式值捕捉,x引用捕捉;

[&, x, y] 表示其他变量引用捕捉,x和y值捕捉。

当使用混合捕捉时,第一个元素必须是&或=,并且&混合捕捉时,后面的捕捉变量必须是值捕捉同理=混合捕捉时,后面的捕捉变量必须是引用捕捉

int main()
{
    // 只能用当前lambda局部域和捕捉的对象和全局对象
    int a = 0, b = 1, c = 2, d = 3;
    // 混合捕捉1
    auto func4 = [&, a, b]
	    {
	    	//a++;
		    //b++;
		    c++;
		    d++;
		    return a + b + c + d;
	    };
    func4();
    cout << a << " " << b << " " << c << " " << d << endl;
    // 混合捕捉2
    auto func5 = [=, &a, &b]
	    {
		    a++;
		    b++;
		    /*c++;
		    d++;*/
		    return a + b + c + d;
	    };
    func5();
    cout << a << " " << b << " " << c << " " << d << endl;
    return 0;
}
不能捕捉静态局部变量和全局变量

lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使用。

这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。

int x = 0;

// 捕捉列表必须为空,因为全局变量不用捕捉就可以用,没有可被捕捉的变量
auto func1 = []()
	{
		x++;
	};

int main()
{
    // 只能用当前lambda局部域和捕捉的对象和全局对象
    int a = 0, b = 1, c = 2, d = 3;
    // 局部的静态和全局变量不能捕捉,也不需要捕捉
    static int m = 0;
    auto func6 = []
	    {
		    int ret = x + m;
		    return ret;
	    };
    return 0;
}
mutable 修饰符

默认情况下, 传值捕捉的对象是被const修饰的,也就是说传值捕捉过来的对象不能修改。

mutable加在参数列表的后面可以取消其常量性,也就说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。

使用该修饰符后,参数列表不可省略(即使参数为空)。

int main()
{
    // 只能用当前lambda局部域和捕捉的对象和全局对象
    int a = 0, b = 1, c = 2, d = 3;
    // 传值捕捉本质是⼀种拷被,并且被const修饰了
    // mutable相当于去掉const属性,可以修改了
    // 但是修改了不会影响外面被捕捉的值,因为是⼀种拷贝
    auto func7 = [=]()mutable
    	{
		    a++;
		    b++;
		    c++;
		    d++;
		    return a + b + c + d;
	    };
    cout << func7() << endl;
    cout << a << " " << b << " " << c << " " << d << endl;
    return 0;
}

3. lambda表达式的应用

在学习 lambda 表达式之前,我们的使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义一个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既简单又方便。

lambda 在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等, lambda 的应用还是很广泛的,以后我们会不断接触到。

例如在下面的例子中,将lambda表达式直接作为可调用对象传给sort函数,不仅省去了仿函数的定义,而且将比较逻辑与sort函数绑定到一起,提高了可读性。

struct Goods
{
	string _name; // 名字
	double _price; // 价格
	int _evaluate; // 评价
	// ...
	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};
struct ComparePriceLess
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
};
struct ComparePriceGreater
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
	// 类似这样的场景,我们实现仿函数对象或者函数指针支持商品中
	// 不同项的比较,相对还是⽐较麻烦的,那么这里lambda就很好用了
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price < g2._price;
		});

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._price > g2._price;
		});

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate < g2._evaluate;
		});

	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1._evaluate > g2._evaluate;
		});
	return 0;
}

4. lambda表达式的原理

编译器在编译时,会根据我们所写的lambda表达式生成一个仿函数的类,并返回该类的对象。

仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体。

lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些就传那些对象。

在下面这个例子中,r1和r2除了类名不同以外,是完全等价的。

class Rate
{
public:
	Rate(double rate)
		: _rate(rate)
	{}

	double operator()(double money, int year)
	{
		return money * _rate * year;
	}
private:
	double _rate;
};

int main()
{
	double rate = 0.49;
	// 函数对象
	Rate r1(rate);
	// lambda
	auto r2 = [rate](double money, int year) {
		return money * rate * year;
		};

	r1(10000, 2);

	r2(10000, 2);

	return 0;
}

我们可以使用编译器的转到反汇编的功能来看二者的底层是否一样:

	r1(10000, 2);
00007FF71CBD1A18  mov         r8d,2  
00007FF71CBD1A1E  movsd       xmm1,mmword ptr [__real@40c3880000000000 (07FF71CBDBDA8h)]  
00007FF71CBD1A26  lea         rcx,[r1]  
00007FF71CBD1A2A  call        Rate::operator() (07FF71CBD1172h)  
00007FF71CBD1A2F  nop  

	r2(10000, 2);
00007FF71CBD1A30  mov         r8d,2  
00007FF71CBD1A36  movsd       xmm1,mmword ptr [__real@40c3880000000000 (07FF71CBDBDA8h)]  
00007FF71CBD1A3E  lea         rcx,[r2]  
00007FF71CBD1A42  call        `main'::`2'::<lambda_1>::operator() (07FF71CBD1EA0h)  
00007FF71CBD1A47  nop  

可以看到,二者都调用了 "operator()" 。

标签:return,函数,int,C++,---,捕捉,const,lambda
From: https://blog.csdn.net/2302_80372340/article/details/143452790

相关文章

  • USDA生物基认证趋势-化妆品和个人护理产品
    化妆品和个人护理产品作为快速消费品,正在迅速产生废弃的塑料瓶,环保行动迫在眉睫。无论是在原材料的选择上,还是在包装的改进上,越来越多的品牌将可持续发展和环保理念纳入品牌规划。好的理念+好的产品,为化妆品和个人护理产品行业树立了良好的环保榜样。消费者越来越多地寻求含......
  • CSP-2024游记
    考前把这个看一遍:考场策略应该是,10min解压+读题+建文件夹,先打暴力,别被一道题卡死(!!就比如去年的J组T1)别死磕,别死磕,别死磕尤其早上的J组只有3.5h,一定要注意时间分配。相信自己,心态不要炸,当成正常的模拟赛对待。如果非常慌可以选择深呼吸或者先打自己会的分。下考前5min反复......
  • 洛古---越狱问题【快速幂的使用】
    今天和大家讲一个洛古的算法题,我觉得还是比较有含金量的,今天给大家分享一下题目描述监狱有 ......
  • 最详细的devServer.proxy的配置讲解,看完你就明白为何会报No ‘Access-control-Allow-0
    devServer.proxy用于开发环境中的API请求转发。它并不会实际处理跨域问题,而是通过代理将前端发出的请求重定向到不同的服务器。这样,前端和后端的交互都由devServer处理,从而避免浏览器的同源策略限制。工作原理:客户端请求发到devServer。devServer根据proxy配置将请求转发......
  • Vue2中使用Element-ui封装表单(el-form)组件动态渲染数据
    1.创建一个searchForm组件,将需要封装的searchForm组件全局注册,为了方便下次直接使用在main.js文件中全局注册importSearchFormfrom'./components/SearchForm'Vue.component('SearchForm',SearchForm)2.在searchForm组件中创建基本结构<template><divclass="ces-......
  • (附源码)NodeJS高校篮球队管理系统-计算机毕设 27088
    NodeJS高校篮球队管理系统目录1绪论1.1研究背景1.2国内外研究现状1.3论文结构与章节安排2平台分析2.1系统可行性分析2.1.1硬件可行性分析2.1.2软件可行性分析2.1.3经济可行性2.1.4 操作可行性2.2系统功能分析2.3 系统用例分析2.4本章小结3......
  • 操作系统学习笔记-5.2设备独立性软件
    文章目录假脱机技术1.假脱机技术的基本概念2.工作原理4.典型应用场景设备的分配和回收设备分配方式安全分配模式和不安全分配模式1.安全分配模式2.不安全分配模式3.安全与不安全模式的区别分配策略1.3分配方法数据结构设备控制表(DCT)设备控制表的组成控制器......
  • C++程序设计大作业-学生管理系统-计算机自考实践
    目录源码C++程序设计报告开发环境系统运行导入初始数据显示信息输入记录编辑记录删除记录批量导出数据源码#include<iostream>#include<string>#include<fstream>#include<sstream>#include<list>#include<map>//定义最大值#defineMAX100using......
  • DAY109代码审计-PHP模型开发篇&动态调试&反序列化&变量覆盖&TP框架&原生POP链
    知识点1、PHP审计-动态调试-变量覆盖2、PHP审计-动态调试-原生反序列化3、PHP审计-动态调试-框架反序列化PHP常见漏洞关键字SQL注入:selectinsertupdate deletemysql_querymysqli等文件上传:$_FILES,type="file",上传,move_uploaded_file()等XSS跨站:printprint_r......
  • 数据库系统原理大作业要求-计算机实践课程题目要求
    目录背景一、内容要求学生成绩管理系统需求要求二、提交要求背景    本文记录了自考计算机科学与技术专业的时候,遇到的C++程序设计的题目。分享出来,让同学们提前了解一下实践课程的难度。为自己的实践课程提前做准备,提前预习。一、内容要求学生成绩管理系......