一篇博客搞定C++11之Lambda表达式(附案例代码+解析)

news2024/11/23 11:23:23

Lambda表达式

  • 1.lambda表达式语法
  • 2.捕获列表说明
  • 3.lambda表达式实现原理
  • 4.具体案例
  • 5.总结

1.lambda表达式语法

图1

  • ambda表达式的语法非常简单,具体定义如下:
    [ captures ] ( params ) specifiers exception -> ret { body }
    先不用急于解读这个定义,我们可以结合lambda表达式的例子来读
    懂它的语法:
#include <iostream>
int main()
{
int x = 5;
auto foo = [x](int y)->int { return x * y; };
std::cout << foo(8) << std::endl;
}
  • 在这个例子中,[x](int y)->int { return x * y; }是一个标准的lambda表
    达式,对应到lambda表达式的语法。
  1. [ captures ] —— 捕获列表,它可以捕获当前函数作用域的零个或多个变量,变量之间用逗号分隔。在对应的例子中,[x]是一个捕获列表,不过它只捕获了当前函数作用域的一个变量x,在捕获了变量之后,我们可以在lambda表达式函数体内使用这个变量,比如return x *y。另外,捕获列表的捕获方式有两种:按值捕获和引用捕获,下文会详细介绍。
  2. ( params ) —— 可选参数列表,语法和普通函数的参数列表一样,在不需要参数的时候可以忽略参数列表。对应例子中的(int y)。specifiers ——可选限定符,C++11中可以用mutable,它允许我们在lambda表达式函数体内改变按值捕获的变量,或者调用非const的成员函数。上面的例子中没有使用说明符。
  3. exception —— 可选异常说明符,我们可以使用noexcept来指明lambda是否会抛出异常。对应的例子中没有使用异常说明符。ret —— 可选返回值类型,不同于普通函数,lambda表达式使用返回类型后置的语法来表示返回类型,如果没有返回值(void类型),可以忽略包括->在内的整个部分另外,我们也可以在有返回值的情况下不指定返回类型,这时编译器会为我们推导出一个返回类型。对应到上面的例子是->int。{ body } —— lambda表达式的函数体,这个部分和普通函数的函数体一样。对应例子中的{ return x * y; }。

2.捕获列表说明

  • 捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
  1. [var]:表示值传递方式捕捉变量var
  2. [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  3. [&var]:表示引用传递捕捉变量var
  4. [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  5. [this]:表示值传递方式捕捉当前的this指针
  • 注意:
  1. 父作用域指包含lambda函数的语句块
  2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
  3. 捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复d. 在块作用域以外lambda函数捕捉列表必须为空。
  4. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
  5. lambda表达式之间不能相互赋值,即使看起来类型相同(因为lambda表达式底层是 :每一个lambda都会被底层转换成一个仿函数, 仿函数的名称=lambda表达式+uuid,uuid是独立的,所有每个lambda表达式再构造的时候都是独立且唯一的,那么在表达式相互赋值的时候编译器就会判断类型不同,无法进行相互赋值。)
  • 代码测试
void test()//lambda介绍
{
	int a = 10;
	int b = 5;
	//完整的lambda表达式
	auto func1 = [](int a, int b)->int{return a * b; };
	cout << func1(a, b) << endl;
	//缺省参数和返回值类型 
	auto func2 = [=]() {return a + b; };
	cout << func2() << endl;

	//捕捉值
	//auto func3 = [=]() {return a = a * b; }; //编译失败 a是常量 左值 不可修改

	auto func3 = [&a,b]() {return a = a * b; };//a传引用 b是捕捉值(相当于复制的) 编译通过 
	//a的值被修改
	cout << "a="<< func3() << endl;
}

3.lambda表达式实现原理

  • 先看一段代码
#include <iostream>
class Bar
{
public:
Bar(int x, int y) : x_(x), y_(y) {}
int operator () ()
{
return x_ * y_;
}
private:
int x_;
int y_;
};
int main()
{
int x = 5, y = 8;
auto foo = [x, y] { return x * y; };
Bar bar(x, y);
std::cout << "foo() = " << foo() << std::endl;
std::cout << "bar() = " << bar() << std::endl;
}

在上面的代码中,foo是一个lambda表达式,而bar是一个函数对
象。它们都能在初始化的时候获取main函数中变量x和y的值,并在调用
之后返回相同的结果。这两者比较明显的区别如下。
1.使用lambda表达式不需要我们去显式定义一个类,这一点在快
速实现功能上有较大的优势。
2.使用函数对象可以在初始化的时候有更加丰富的操作,例如Bar
bar(x+y, x * y),而这个操作在C++11标准的lambda表达式中是不允许
的。另外,在Bar初始化对象的时候使用全局或者静态局部变量也是没
有问题的。
这样看来在C++11标准中,lambda表达式的优势在于书写简单方便
且易于维护,而函数对象的优势在于使用更加灵活不受限制,但总的来
说它们非常相似。而实际上这也正是lambda表达式的实现原理。
lambda表达式在编译期会由编译器自动生成一个闭包类,在运行时
由这个闭包类产生一个对象,我们称它为闭包。在C++中,所谓的闭包
可以简单地理解为一个匿名且可以包含定义时作用域上下文的函数对
象。现在让我们抛开这些概念,观察lambda表达式究竟是什么样子的。
首先,定义一个简单的lambda表达式:

#include <iostream>
int main()
{
int x = 5, y = 8;
auto foo = [=] { return x * y; };
int z = foo();
}

接着,我们用GCC输出其GIMPLE的中间代码:

int main ()
{
int D.39253;
{
int x;
int y;
struct __lambda0 foo;
typedef struct __lambda0 __lambda0;
int z;
try
{
x = 5;
y = 8;
foo.__x = x;
foo.__y = y;
z = main()::<lambda()>::operator() (&foo);
}
finally
{
foo = {CLOBBER};
}
}
D.39253 = 0;
return D.39253;
}
main()::<lambda()>::operator() (const struct __lambda0 * const __closure)
{
int D.39255;
const int x [value-expr: __closure->__x];
const int y [value-expr: __closure->__y];
_1 = __closure->__x;
_2 = __closure->__y;
D.39255 = _1 * _2;
return D.39255;
}

从上面的中间代码可以看出lambda表达式的类型名为__lambda0,
通过这个类型实例化了对象foo,然后在函数内对foo对象的成员__x和
__y进行赋值,最后通过自定义的()运算符对表达式执行计算并将结果赋
值给变量z。在这个过程中,__lambda0是一个拥有operator()自定义运算
符的结构体,这也正是函数对象类型的特性。所以,在某种程度上来
说,lambda表达式是C++11给我们提供的一块语法糖而已,lambda表达
式的功能完全能够手动实现,而且如果实现合理,代码在运行效率上也
不会有差距,只不过实用lambda表达式让代码编写更加轻松了。

4.具体案例

在下面我会用代码演示lambda表达式在比较场景的使用:
在网购系统中,如果要针对不同商品的属性进行排序,C+11出现之前我们都是用实现仿函数的+调用sort方式来比较,总统来说比较复杂,可读性也不高,lambda表达式的出现就大大改进了书写的方式,lambda表达式实际上就是一块语法糖,提高代码的书写效率和可阅读性。

  • 代码:
#define _CRT_SECURE_NO_WARNINGS
using namespace std;
#include<iostream>
#include <algorithm>
#include<vector>
//lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

//[capture-list] -----捕捉列表 [=]:捕捉所有值、[&]:捕捉所有变量的引用、[&,a,b]:a、b为捕捉值,其余为引用

//(parameters) ----参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略

//mutable----默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。

//return-type---返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分
//可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。

//{ statement }---函数体
struct Goods
{
	string name;
	double price;//价格
	int Id;//编号
};



struct Priceless
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1.price < g2.price;
	}
};

struct IDLess
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1.Id < g2.Id;
	}
};

void compare() 
{
	//c++98
	cout << "C+98方式实现:" << endl;
	cout << endl;
	std::vector<Goods> v;
	v = { Goods{"苹果", 55.55, 1}, {"西瓜", 26.23, 2}, {"香蕉", 66.66, 3} };
	cout << "价格升序:" << endl;
	std::sort(v.begin(), v.end(), Priceless());
	for (const auto& e : v) {
		std::cout << "Name: " << e.name << ", Price: " << e.price << ", ID: " << e.Id << std::endl;
	}
	cout << endl;
	cout << "ID升序:" << endl;
	std::sort(v.begin(), v.end(), IDLess());
	for (const auto& e : v) {
		std::cout << "Name: " << e.name << ", Price: " << e.price << ", ID: " << e.Id << std::endl;
	}


	//C+11 lambda表达式
	cout << "C+11lambda表达式实现:" << endl;

	
	cout << "价格升序:" << endl;
	std::sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1.price < g2.price; });
	for (const auto& e : v) {
		std::cout << "Name: " << e.name << ", Price: " << e.price << ", ID: " << e.Id << std::endl;
	}
	cout << endl;
	cout << "ID升序:" << endl;
	std::sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
		return g1.Id < g2.Id; });
	for (const auto& e : v) {
		std::cout << "Name: " << e.name << ", Price: " << e.price << ", ID: " << e.Id << std::endl;
	}
	cout << endl;

}

int main()
{
	//test();
	compare();
	return 0;
}
  • 运行结果:
    图2

5.总结

总的来说lambda表达式不但容易使用,而且原理也容易理解。它很好地解决了过去C++中无法直接编写内嵌函数的尴尬。虽然在GCC中提供了一个叫作nest function的C语言扩展,这个扩展允许我们在函数内部编写内嵌函数,但这个特性一直没有被纳入标准当中。当然我们也并不用为此遗憾因为现在提供的lambda表达式无论在语法简易程度上,还是用途广泛程度上都要优于nest function。合理地使用lambda表达式,可以让代码更加短小精悍的同时也具有良好的可读性。

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

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

相关文章

面向对象内部类

概念 将一个类 A 定义在另一个类 B 里面&#xff0c;里面的那个类 A 就称为内部类 &#xff08;InnerClass&#xff09;&#xff0c;类 B 则称为外部类&#xff08;OuterClass&#xff09; 根据内部类声明的位置&#xff08;如同变量的分类&#xff09;&#xff0c;我们可以分…

首次使用云服务器搭建网站(二)

书接上文&#xff0c;我们已经完成了服务器的租赁&#xff0c;宝塔面板的下载与安装。 接下来我们将正式开始网站搭建。 一、网站创建 点击网站、添加站点 输入网站域名、数据库选择MySQL数据库&#xff0c;选择utf8&#xff0c;数据库账号密码会自动生成。无论你要创建什么样…

【linux网络配置】多个网卡一起使用,一个网卡连内网,一个网卡连外网

一、问题背景 因为有一个工作站在内网中&#xff0c;但是没有办法联网&#xff08;校园网账户有限&#xff09;。 虽然工作站没有联网&#xff0c;但是我仍然可以通过局域网远程控制工作站&#xff0c;使其访问校园网验证页面实现上网。 当给工作站安装软件或依赖项时&#…

SpringBoot配置文件application.properties的理解

一、存放位置分类 1.当前项目根目录下的config目录下 2.当前项目的根目录下 3.resources目录下的config目录下 4.resources目录下 按照这上面的顺序&#xff0c;4个配置文件的优先级依次降低。 我们在项目里面4个位置分别设置了各种的application.properties文件。每个文件…

MySQL查看和修改最大连接数

标题&#xff1a;MySQL查看和修改最大连接数 MySQL 是一种广泛使用的开源关系型数据库管理系统&#xff0c;被许多应用程序用作其后端存储解决方案。在高并发的环境下&#xff0c;MySQL 的最大连接数变得尤为重要。本文将介绍如何查看当前的最大连接数&#xff0c;并详细说明每…

安卓实战开发之——使用 WIFI 进行设备搜索并获取相应信息

目录 一、前言 二、准备条件 三、功能要求 四、显示效果 五、关键代码 一、前言 此玩意是本人很早很早&#xff08;记不清有多早了&#xff0c;反正很早&#xff09;做过的一个课程任务了&#xff0c;无意之中翻到了&#xff0c;所以把它放上来。不愿再找以前写的代码了…

大数据环境搭建 Hadoop+Hive+Flume+Sqoop+Azkaban

目录 零&#xff1a;版本说明一、安装CentOS二、Hadoop单机配置三、Hive安装部署 零&#xff1a;版本说明 Hadoop&#xff1a;3.1.0 CentOS&#xff1a;7.6 JDK&#xff1a;1.8 一、安装CentOS 这里网上教程很多&#xff0c;就不贴图了 【内存可以尽量大一些&#xff0c;不然…

DeepBIO:一个自动化和可解释的深度学习平台,用于高通量生物序列预测,功能注释和可视化分析

DeepBIO: an automated and interpretable deep-learning platform for high-throughput biological sequence prediction, functional annotation and visualization analysis 期刊&#xff1a;Nucleic Acids Research 中科院分区&#xff1a;2区 影像因子&#xff1a;19.1…

面向对象抽象

抽象类 1 概念 Java中可以定义被abstract关键字修饰的方法,这种方法只有声明,没有方法体,叫做抽象方法. Java中可以定义被abstract关键字修饰的类,被abstract关键字修饰的类叫做抽象类 如果一个类含有抽象方法,那么它一定是抽象类 抽象类中的方法实现交给子类来完成 2 抽象…

redisTemplate海量数据批量存储

有个表的数据需初始化到redis, 目前18w, 后期会达到千万1. 使用redisTemplate批量存储数据 Autowiredprivate RedisTemplate redisTemplate;public void init(){List<WxPois> list SpringUtils.getBean(WxPoisMapper.class).selectWxPoisList(new WxPois());if (Collect…

华为OD机试之数组拼接(Java源码)

数组拼接 题目描述 现在有多组整数数组&#xff0c;需要将它们合并成一个新的数组。 合并规则&#xff0c;从每个数组里按顺序取出固定长度的内容合并到新的数组中&#xff0c;取完的内容会删除掉&#xff0c;如果该行不足固定长度或者已经为空&#xff0c;则直接取出剩余部…

APP、Web自动化测试面试题

App/Web自动化的面试题 备注&#xff1a;一般面试的时候是结合简历上的项目一步一步深入进行问的问题&#xff0c;只要你实际做过一个项目的&#xff0c;加上自己面试准备下&#xff0c;问题不会太大 【APP自动化问题&#xff1a;1.3.6.10.11.12.13.14.19】 【Web自动化问题&…

意向共享锁和意向排他锁

InnoDB表级锁 在绝大部分情况下都应该使用行锁&#xff0c;因为事务和行锁往往是选择InnoDB的理由&#xff0c;但个别情况下也使用表级锁&#xff1a; 1&#xff09;事务需要更新大部分或全部数据&#xff0c;表又比较大&#xff0c;如果使用默认的行锁&#xff0c;不仅这个事…

【Android开发基础】蓝牙信息的获取(Bluetooth)

文章目录 一、引言二、操作1、权限2、开启蓝牙3、可检测4、搜索蓝牙5、广播 三、附件1、UI界面设计2、总代码 一、引言 描述&#xff1a;蓝牙技术是一种无线数据和语音通信开放的全球规范&#xff0c;它是基于低成本的近距离无线连接&#xff0c;为固定和移动设备建立通信环境…

华为OD机试真题 JavaScript 实现【知识图谱新词挖掘1】【2023Q1 100分】

一、题目描述 小华负责公司知识图谱产品&#xff0c;现在要通过新词挖掘完善知识图谱。 新词挖掘: 给出一个待挖掘文本内容字符串Content和一个词的字符串word&#xff0c;找到content中所有word的新词。 新词&#xff1a;使用词word的字符排列形成的字符串。 请帮小华实现新词…

cesium学习(相机)

飞到一个地方 如果你知道位置的经纬度和高度&#xff0c;你可以使用相机的flyTo功能直接飞到CesiumJS中的那个位置。 viewer.camera.flyTo({destination: Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0), });更改相机的方向 flyTo要在完成后更改相机的方向&#x…

一、网络协议和常用的网络工具

文章目录 1.计算机概论1.1 计算机网络是什么&#xff1f;1.2 计算机网络的层次结构 2.TCP/IP详解2.1 TCP/IP协议族2.2 网络传输中的数据2.3 网路通信中的地址和端口号2.4 TCP三次握手建立连接2.5 TCP四次挥手建立连接 3.网络工具Wireshark和tcpdump4.一次完整的Http请求过程 1.…

Live Demo精彩大放送,演绎openGauss技术创新

技术是开源社区的根基&#xff0c;创新是推动技术发展的源动力。openGauss开源三周年&#xff0c;携手开发者、社区伙伴、用户&#xff0c;持续聚焦内核竞争力、用户场景架构创新、完善周边生态工具。5月26日&#xff0c;openGauss Developer Day 2023 主论坛现场&#xff0c;1…

自定义包含天时分的时长选择器组件

场景&#xff1a;项目是reactantd 实现的。如果传感器超过3天4小时没有上报数据&#xff0c;则认为设备已经坏了&#xff0c;需要发出告警。 3天4小时这是由用户在前端页面输入的&#xff0c;因此需要有一个时长选择器。antd 原生的TimePicker 组件只能实现选择 时、分、秒&…

(Qt)day4

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QDebug> #include <QMainWindow> #include <QMessageBox> #include <QTimer> //定时器类头文件 #include <QTime> //时间类的头文件 #include <QTimerEvent> //定时器…