C++第四十四弹---Lambda表达式的妙用:高效解决编程中的匿名函数问题

news2024/11/15 8:01:29

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1 lambda表达式

1.1 C++98中的一个例子

1.2 lambda表达式

1.3 lambda表达式语法

1.4 函数对象与lambda表达式


1 lambda表达式


1.1 C++98中的一个例子


在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。

代码演示

#include <algorithm>
#include <functional>
int main()
{
	int array[] = { 4,1,8,5,3,7,0,9,2,6 };
	// 默认按照小于比较,排出来结果是升序
	std::sort(array, array + sizeof(array) / sizeof(array[0]));
	// 如果需要降序,需要改变元素的比较规则,greater为functional头文件中类
	std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
	return 0;
}

运行结果 

如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

商品类 

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 } };

	// 按照价格升序
	sort(v.begin(), v.end(), ComparePriceLess());
	// 按照价格降序
	sort(v.begin(), v.end(), ComparePriceGreater());
}

运行结果

1.2 lambda表达式

商品类

struct Goods
{
	string _name; // 名字
	double _price; // 价格
	int _evaluate; // 评价
	// ...

	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{}
};

代码演示 

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
	// 价格降序
	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;
		});
}

运行结果 

 

上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个匿名函
数。 

1.3 lambda表达式语法


lambda表达式书写格式[capture-list] (parameters) mutable -> return-type { statement }
1. lambda表达式各部分说明

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

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

代码演示 

int main()
{
	// 最简单的lambda表达式, 该lambda表达式没有任何意义
	[] {};

	// 省略参数列表和返回值类型,返回值类型由编译器推导为int
	int a = 3, b = 4;
	[=] {return a + 3; };

	// 各部分都很完善的lambda函数
	auto add1 = [](int a, int b)->int {return a + b; };
	cout << add1(1, 2) << endl;
	
	// 返回值可以省略
	auto add2 = [](int a, int b) {return a + b; };
	cout << add2(1, 2) << endl;
	
	// 没有参数,参数列表可以省略
	auto func = [] {cout << "hello world" << endl; };
	func();

	cout << typeid(add1).name() << endl;
	cout << typeid(add2).name() << endl;
	cout << typeid(func).name() << endl;
}

运行结果 

add1和add2参数,返回值,返回类型都相同,但是add1和add2的类型是不同的,因此lambda表达式之间不能相互赋值。

通过上述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。


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

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

注意:

  • a. 父作用域指包含lambda函数的语句块
  • b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    • 比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
  • c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。
    • 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • d. 在块作用域以外的lambda函数捕捉列表必须为空。
  • e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  • f. lambda表达式之间不能相互赋值,即使看起来类型相同(typeid可以查看)

代码演示一

int main()
{
	int a = 1, b = 2;
	auto swap1 = [](int& x, int& y)
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	swap1(a, b);

	// 捕捉列表,给函数使用
	// 捕捉a b对象给lambda
	// mutable可以修改传值过来的捕捉对象(日常一般不用)
	// 因为a b是拷贝过来的,因此不会改变外面的a b
	auto swap2 = [a,b]() mutable
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	swap2();

	// 引用方式捕捉
	auto swap3 = [&a, &b]()
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	swap3();

	int* pa = &a, * pb = &b;
	// 指针方式捕捉,间接
	auto swap4 = [pa, pb]()
	{
		int tmp = *pa;
		*pa = *pb;
		*pb = tmp;
	};
	swap4();

	return 0;
}

运行结果

代码演示二

int main()
{
	int a = 1, b = 2, c = 3, d = 4, e = 5;
	// = 传值捕捉所有对象
	auto func1 = [=]()
	{
		return a + b - c * d;
	};
	
	cout << func1() << endl;

	// 传引用捕捉所有对象
	auto func2 = [&]()
	{
		a++;
		b++;
		c++;
		d++;
	};
	func2();
	cout << a << b << c << d << e << endl;

	// 混合捕捉,传引用捕捉所有对象,但是d e传值捕捉
	auto func3 = [&, d, e]()
	{
		a++;
		b++;
		//d++;
		//e++;
	};
	func3();
	cout << a << b << c << d << e << endl;

	// 混合捕捉,a b传引用,c d传值
	auto func4 = [&a, &b, c, d]()
	{
		a++;
		b++;
		//c++;
		//d++;
	};
	func4();
	cout << a << b << c << d << e << endl;

	return 0;
}

 运行结果

1.4 函数对象与lambda表达式


函数对象,又称为仿函数,即可以像函数一样使用的对象,就是在类中重载了operator()运算符的
类对象。

代码演示

// 函数对象与lambda表达式
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);
	r1(10000, 2);
	// lambda
	auto r2 = [=](double monty, int year)->double 
		{return monty * rate * year;};
	r2(10000, 2);
	return 0;
}

反汇编 

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。 

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

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

相关文章

上门家政系统

上门家政系统是基于likeadmin开发的上门家政预约系统&#xff0c;提供全部前后台无加密源代码&#xff0c;拥有强大的地图定位、在线预约、系统派单、指定派单、下单支付、核销订单等功能模块&#xff0c;用户端和师傅端完美融合&#xff0c;随时随地都能接单&#xff1b;可自定…

vs中在工具箱添加自定义控件numberTextBox

在winform中没有numberTextBox控件&#xff0c;为了将numberTextBox添加到工具箱当中&#xff0c;于是参考以下博客封装了一个自定义numberTextBox控件&#xff1a; winform 自定义数值(数字)输入框_c#如何设置一个框输入值-CSDN博客 接下来介绍以下封装和引用的过程&#xf…

淘宝直通车投放优化(进阶版本)

相爱相杀何时休,只看今篇论英雄! 看目录上电梯,各去对应楼层耍。顶楼的风景最迷人。38719字诸君细读。 第一阶段:基础认识和推广(新手可从头看起) 第二阶段:明确原理与逻辑(运营/推广同学从此看做到心中有数) 第三阶段:实战中涅槃(知其然,知其所以然) 第一阶…

MySQL之DQL简单查询

1、结构化查询语言 1. 什么是SQL 结构化查询语言(Structured Query Language)&#xff0c;后续通常简称SQL。SQL是用于存取数据以及查询、更新和管理关系数据库系统的标准语言。20世纪70年代由IBM公司开发&#xff0c;目前应用于各种关系型数据库。SQL是一套标准&#xff0c;…

黄金市场步入数据驱动的关键周:多空角力聚焦非农就业

一、市场情绪分化&#xff0c;黄金波动趋于平稳 本周黄金市场在经历了一系列波动后&#xff0c;价格维持在2500美元/盎司上方波动&#xff0c;显示出相对稳定的交易状态。Kitco新闻黄金调查显示&#xff0c;分析师对下周金价走势的看法分歧较大&#xff0c;而零售投资者看涨情绪…

TCP/IP网络编程:第18章聊天室

服务端&#xff1a;负责连接客户端&#xff0c;转发客户端的信息给其他客户 客户端&#xff1a;发送信息给服务端&#xff0c;接收服务端传来的其他客户的信息 服务端代码&#xff1a; #include <stdio.h> #include <stdlib.h> #include <unistd.h> #incl…

OpenCV开发笔记(八十):基于特征点匹配实现全景图片拼接

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/141790116 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

YOLOS:大道至简,直接使用预训练ViT进行实时目标检测 | NeurIPS 2021

论文探索了在中型ImageNet-1k数据集上预训练的普通ViT到更具挑战性的COCO目标检测基准的可迁移性&#xff0c;提出了基于Vision Transformer的You Only Look at One Sequence(YOLOS)目标检测模型。在具有挑战性的COCO目标检测基准上的实验结果表明&#xff0c;2D目标检测可以以…

Windows系统通过WSL2安装Ubuntu22.04系统及图形化界面

1.通过WSL2安装Ubuntu22.04系统及图形化界面 WSL&#xff08;Windows Subsystem for Linux&#xff09;是一个为Windows用户设计的兼容层&#xff0c;它允许用户在Windows10和Windows11操作系统上直接运行GNU/Linux环境。WSL提供了一个微软开发的Linux兼容内核接口&#xff0c…

MySQL中的时间与当前时间相差8个小时,两步解决。

前言&#xff1a; 有朋友后台私信问&#xff0c;我服务器中的MySQL时间与本地时间差了8个小时&#xff0c;我在给表中字段设置根据时间戳自动更新时会出现时间与当前时间不符的原因。灰常简单&#xff0c;一起来看看吧。 第一步&#xff1a; 在MySQL中&#xff0c;输入命令&…

基于asp.net软件缺陷跟踪系统设计与实现

系统分析 本章讲述了本软件缺陷跟踪系统的前期分析方法和分析结论。 3.1 可靠性分析 在软件研发企业或研发团队中&#xff0c;开发人员和测试人员所应用的软件缺陷管理的主要方法流程是&#xff1a; 1&#xff0e;测试人员发现软件存在的缺陷&#xff0c;填写缺陷报告&…

【正点原子K210连载】第三十四章 image图像滤波实验 摘自【正点原子】DNK210使用指南-CanMV版指南

第三十四章 image图像滤波实验 在上一章节中&#xff0c;介绍了image模块中元素绘制方法给的使用&#xff0c;本章将继续介绍image模块中图像滤波方法的使用。通过本章的学习&#xff0c;读者将学习到image模块中图像滤波的使用。 本章分为如下几个小节&#xff1a; 34.1 imag…

Transformer直接预测完整数学表达式,推理速度提高多个数量级

前言 来自 Mata AI、法国索邦大学、巴黎高师的研究者成功让 Transformer 直接预测出完整的数学表达式。 转载自丨机器之心 符号回归&#xff0c;即根据观察函数值来预测函数数学表达式的任务&#xff0c;通常涉及两步过程&#xff1a;预测表达式的「主干」并选择数值常数&am…

jfif怎么改成jpg格式?这几种转换方法请务必学会!

jfif怎么改成jpg格式&#xff1f;JFIF&#xff0c;作为一种相对不常见的图像存储格式&#xff0c;其在实际应用中确实存在一系列不容忽视的局限&#xff0c;首要问题在于&#xff0c;当尝试将JFIF图片转换为其他格式时&#xff0c;往往会伴随着图像压缩的副作用&#xff0c;这意…

Vue学习笔记 一

Vue学习笔记 1、Vue基础指令 1.1 什么是Vue? Vue.js 是一套响应式的 JavaScript 开发库。Vue.js 自问世以来所受关注度不断提高,在现在的市场上,Vue.js 是非常流行的 JavaScript 技术开发框架之一。 Vue是一款国产前端框架,它的作者尤雨溪(Evan You)是一位美籍华人,…

Android使用addr2line分析Native Crash

NDK提供的工具将函数地址解析为具体的函数名和行数才能进一步分析问题。 常用的地址转换工具有addr2line、ndk-stack等&#xff0c;个人比较喜欢addr2line&#xff0c;所以接下来介绍下该工具的基本使用方式 日常使用过程中&#xff0c;只需要关注-C -f -e三个参数即可 // -…

LaViT:这也行,微软提出直接用上一层的注意力权重生成当前层的注意力权重 | CVPR 2024

Less-Attention Vision Transformer利用了在多头自注意力&#xff08;MHSA&#xff09;块中计算的依赖关系&#xff0c;通过重复使用先前MSA块的注意力来绕过注意力计算&#xff0c;还额外增加了一个简单的保持对角性的损失函数&#xff0c;旨在促进注意力矩阵在表示标记之间关…

注意力机制(Attention mechanism)(中篇)

模型的输入是一组向量&#xff0c;它可以是文字&#xff0c;可以是语音&#xff0c;可以是图。而输出有三种可能性&#xff0c; 第一种可能性是每一个向量都有一个对应的标签。如图1所示&#xff0c;当模型看到输入是4个向 量的时候&#xff0c;它就要输出4个标签。如果是回归问…

React项目通过jsmind实现思维导图以及相关功能

jsMind jsMind 是一个用于显示和编辑思维导图的纯 JavaScript 类库。它基于 Canvas 和 SVG 进行设计&#xff0c;能够在现代浏览器中高效地运行。jsMind 以 BSD 协议开源&#xff0c;这意味着可以在遵守该协议的前提下&#xff0c;将其嵌入到任何项目中使用。 功能特点 jsMi…

从0到1搭建用户管理系统

手把手教你搭建前后端框架 新手对于很多成熟框架&#xff0c;不知道如何搭建的&#xff0c;不知道如何实现等等&#xff0c;忙碌之余&#xff0c;写了一篇博客 手把手教你搭建前后端框架源码&#xff0c; springbootmysqlelementuivue 从0到1&#xff0c;搭建springboot框架&am…