【C++11】C++11新增语法 Lambda表达式/Lambda的底层原理

news2024/11/18 8:22:38

Lambda表达式

  • 1 Lambda使用的一个例子
  • 2 Lambda 表达式的语法
  • 3 初次体验Lambda表达式
  • 4 Lambda函数底层实现原理

1 Lambda使用的一个例子

在C++98中,如果我们想要对一个自定义类型进行排序,就需要用户自定义去书写比较的规则。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Goods
{
public:
	Goods(string name, double price, int evaluate)
		:_name(name), _price(price), _evaluate(evaluate)
	{}
	string _name;//名字
	double _price;//价格
	int _evaluate;//评价
};
struct ComparePriceLess
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._price < g2._price;
	}
};
struct ComparePriceGreater
{
	bool operator()(const Goods& g1, const Goods& g2)
	{
		return g1._price > g2._price;
	}
};
int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, 
		{ "菠萝", 1.5, 4 } };

	sort(v.begin(), v.end(), ComparePriceGreater());//按价格降序排列
	cout << "降序排列:" << endl;
	for (int i = 0; i < v.size(); i++)//打印结果
	{
		cout << v[i]._name << v[i]._price << " " <<  v[i]._evaluate << endl;
	}
	cout << endl;
	cout << "升序排列:" << endl;
	sort(v.begin(), v.end(), ComparePriceLess());//按价格升序排列
	for (int i = 0; i < v.size(); i++)//打印结果
	{
		cout << v[i]._name << v[i]._price << " " << v[i]._evaluate << endl;
	}
	return 0;
}

在这里插入图片描述

随着C++的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法就要去重新写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,C++11语法中新增了Lambda表达式。

//按价格降序排序
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; })

上述的代码就是Lambda表达式来解决,可以看出Lambda表达式实际上是一个匿名函数。


2 Lambda 表达式的语法

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

[cpature] 为捕获列表(不捕获时,[]不能省略)

①[]、[&]和[=]分别表示不捕获、按引用捕获、按值捕获所有父作用域中内的局部变量。(父作用域指包含lambda表达式的语句块,如main函数)。

◆lambda函数只能捕获父作用域中的局部变量,而捕获非父作用域或static变量都会报错(不是C++11的标准,其行为可能因编译器而不同)。(注意全局变量或static变量不能被捕获。即不能被写入捕获列表中,但可在lambda的函数体内直接访问)

◆默认下无法修改按值捕获的外部变量(因为lambda表达式的operator()默认是const函数,但捕获this指针后可修改成员变量的值,因为是通过this指针来修改的)。(涉及到lambda的底层原理,后面会讲)

◆在类中如果使用&或=捕获,会同时默认捕获this指针。

②[=,&foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量。注意,捕获列表不允许变量重复传递,如[=,var],var被按值捕获了两次,这是不允许的。

③[bar]按值捕获bar变量(注意只捕获bar,其他变量不被捕获)

④[this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样访问权限,可以使用当前类的成员函数和成员变量(注意也可修改non-const成员变量的值)。


(parameters): Lambda表达式的参数列表(不需要参数传递时,()可以省略)
①如果省略,则类似于无参函数func()。

②参数列表中不能有默认参数。所有的参数必须有参数名。

③不支持可变参数。


opt选项(可省略)
①mutable修饰符:默认下,lambda表达式的operator()是const函数,mutable可以取消其常量性,让body内的代码可以修改被捕获的变量,并可以访问被捕获对象的non-const函数。在使用该修饰符时,参数列表不可省略(即使参数为空)。

②exception:说明lambda表达式是否抛出异常(noexcept),以及抛出何种异常。如抛出整数类型的异常,可以使用throw(int)。

③attribute用来声明属性


-return-type :返回值类型(没有返回值时可省略)
①如果被省略了,则由return语句的返回类型确定

②如果没有return语句,则类似于void func(…)函数。


{statement}:函数体。
在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。


3 初次体验Lambda表达式

在Lambda函数定义中,参数列表和返回值类型都是可选部分,而捕获列表和函数体部分可以为空,所有最简单的Lambda函数为:[]{};但是该Lambda不能做任何事。


省略参数列表和返回值类型

//省略参数列表和返回值类型,返回值类型由编译器自动推导为int
int a = 1, b = 2;
[=] {return a + 3; };//[=] 表示按值捕获 函数体里的a是一份拷贝

省略返回值类型

//省略返回值类型,无返回值类型
int a = 1, b = 2;
auto fun = [&](int c) {b = a + c; };//[&]表示按引用捕获a和b
fun(10);//调用Lambda函数
cout << "a=" << a << endl;//a=1
cout << "b=" << b << endl;//b=11

各部分都很完善的Lambda函数

//各部分都很完善的Lambda函数
int a = 1, b = 2;
//[=,&b]表示除了b按引用捕获,其他都按值捕获
auto fun = [=, &b](int c) {b += a + c; };
fun(10);
cout << "b=" << b << endl;//b=13

mutable

int x = 10;
//mutable表示取消按值捕获的x的常量性,不加mutable无法修改x的值
//但是一般都是捕获x的引用,即[&x]
auto add_x = [x](int a) mutable {return x += a; };
cout << "x=" << add_x(10) << endl;//x=20

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


在类中使用Lambda表达式

#include <iostream>

using namespace std;

int g = 0;

class Test
{
private:
    int i = 0;
public:
    
    void func(int x, int y)
    {
        //auto x1 = []{return i;};   //error,没有捕获任何变量。当然也无法捕获父作用域(func)以外的变量,因此i是不能访问到的!
        //auto x1 = [&i]{return i;}; //error,无法捕获父作用域(func)以外的变量
        auto x1 = [=]{return i++;}; //ok,因为“=”或“&”默认会捕获this指针,也就可以访问到成员变量。根据按值捕获的特点,此时
                                    //在lambda的函数体内不能修改this指针本身。但这不影响我们通过this指针修改成员变量i的值!
        
        auto x2 = []{return g++;};  //ok,g不在lambda的父作用域,不能被捕获。
                                    //如auto x2 = [&g]{return g++;}。但由于g是全局变量
                                    //所以在lambda的body内仍是可见的!
                                    
        auto x3 = [=]{return i++, i + x + y;};//ok,按值捕获所有外部变量,由于&或=捕获时会默认地同时传入this,所以可改变i的值
        auto x4 = [&]{return i + x + y;}; //ok,按引用捕获所有变量:x, y以及this指针
                                          //但注意,i没有被捕获,也不可捕获到。它是通过this指针来访问到的。
                                          
        auto x5 = [this]{return i++;};    //ok,捕获了this指针,也就可以修改成员的值
        //auto x6 = [this, x]{return i + x + y;}; //error,没捕获y
    }
};

4 Lambda函数底层实现原理

仿函数是编译器实现lambda表达式的一种方式。在现阶段,通常编译器会把lambda表达式转化成一个仿函数对象。因此在C++11中,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表达式完全一样。
函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可 以直接将该变量捕获到。

在这里插入图片描述

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


从底层延申出来的一些问题

1.由于每定义一个lambda表达式,编译器会自动生成一个类,所以Lambda表达式之间是不能互相赋值的,即使看起来类型一样。

int main()
{
	auto f1 = [] {cout << "hello world" << endl; };
	auto f2 = [] {cout << "hello world" << endl; };
    //f1 = f2;   // 编译失败--->提示找不到operator=()
	// 允许使用一个lambda表达式拷贝构造一个新的副本
	auto f3(f2);
	f3();
	return 0;
}

2.一个Lambda表达式大小由捕获的变量决定,如果无捕获变量,则大小为1字节。

int main()
{
	int a = 1;
	auto f1 = [&a] {};
	auto f2 = [] {};
	cout << "f1的大小:" << sizeof(f1) << endl;//4
	cout << "f2的大小:" << sizeof(f2) << endl;//1
	return 0;
}

原因如该图所示:

在这里插入图片描述
底层会由捕获列表创建出相应的变量。
至于为什么无捕获时大小为1,很简单,C++标准规定的。

想要深究的话可以看看这篇文章
为什么lambda无捕获时大小为1字节

lambda表达式在C++11中被称为“闭包类型(Closure Type)”,可以认为它是一个带有operator()的类(即仿函数),它的捕获列表捕获的任何外部变量最终均会变为闭合类型的成员函数。没有捕获变量的lambda表达式可以直接转换为函数指针,而捕获变量的lambda表达式则不能转换为函数指针。

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

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

相关文章

排序算法——直接插入排序

直接插入排序 基本思想 直接插入排序是一种简单明了的插入排序法&#xff0c;其基本思想是&#xff1a;把待排序的数据按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有数据插入完为止。 在现实生活中&#xff0c;我们玩扑克对牌进行排序就运用了…

开源、易扩展、方便集成的Web绘图工具(流程图、架构图、组态、SCADA、大屏)

乐吾乐2D可视化Meta2d.js是一个基于typescript canvas 实现的开源在线绘图软件。采用引擎 图形库中间件的思路能够方便、快速的扩展、集成到前端项目。 集实时数据展示、动态交互、数据管理等一体的全功能可视化平台。帮助物联网、工业互联网、电力能源、水利工程、智慧农业…

【unity之c#】所以迭代器的原理知识你还清楚吗?

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

day46-动态规划8-单词拆分问题

139.单词拆分-完全背包问题区分求组合数和排列数 本题可以使用回溯算法进行暴力搜索&#xff0c;但是如何使用动态规划的思路进行求解呢。将字符串可以理解成一个容器&#xff0c;将单词可以当成物品&#xff0c;那么此时问题转化成利用物品能否装满容器的问题。这个时候由于返…

chatgpt赋能python:Python列表框-理解列表框的功能和用途

Python列表框 -理解列表框的功能和用途 列表框可以说是Python编程中最常用的控件之一。列表框用于在桌面应用程序中显示和管理数据。与其他编程语言一样&#xff0c;Python编程语言提供了许多列表框控件&#xff0c;可以使用它们来管理和显示各种数据。 列表框是一种视觉化的…

软件工程导论(5)软件编码测试与维护

一、软件编程 2.1良好的编程习惯 变量命名有意义并且使用统一的命名规则 编写自文档代码&#xff08;序言性注释 or 行内注释&#xff09; 提前进行可维护性考量&#xff08;可以用常量的方式存在的数值最好以变量的方式存在&#xff09; 良好的视觉安排可以提高代码的可读性(…

【服务器】零基础搭建私有云盘并内网穿透远程访问

文章目录 摘要视频教程1. 环境搭建2. 测试局域网访问3. 内网穿透3.1 ubuntu本地安装cpolar3.2 创建隧道3.3 测试公网访问 4 配置固定http公网地址4.1 保留一个二级子域名4.1 配置固定二级子域名4.3 测试访问公网固定二级子域名 转载自cpolar极点云的文章&#xff1a;使用Nextcl…

Hx711称重模块+STM32+CubeMX

文章目录 一、模块和接线二、CubeMX配置1.时钟及sys2.IO口1&#xff09;数据线DT设置为Input2&#xff09;时钟线SCK设置为Output 3.串口4.后续配置 三、程序1.main.c2.hx711.c3.hx711.h4.串口重定向 总结参考文章 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例…

【消息中间件】比较Redis,Kafka和RabbitMQ

对微服务使用异步通信时&#xff0c;通常使用消息代理。代理确保不同微服务之间的通信可靠且稳定&#xff0c;消息在系统内得到管理和监控&#xff0c;并且消息不会丢失。您可以从几个消息代理中进行选择&#xff0c;它们的规模和数据功能各不相同。这篇博文将比较三种最受欢迎…

云服务器CPU内存/带宽配置怎么选择?

云服务器配置怎么选择&#xff1f;个人如何选择&#xff1f;企业怎么选择云服务器配置&#xff1f;腾讯云服务器CPU内存、带宽和系统盘怎么选择合适&#xff1f;个人用户可以选择轻量应用服务器&#xff0c;企业用户可以选择云服务器CVM&#xff0c;企业用户可以选择标准型S5云…

5.1 树和二叉树的定义

博主简介&#xff1a;一个爱打游戏的计算机专业学生博主主页&#xff1a; 夏驰和徐策所属专栏&#xff1a;算法设计与分析 5.1.1 树的定义 我的理解&#xff1a; 在计算机科学中&#xff0c;树是一种非线性数据结构&#xff0c;由节点&#xff08;或称为顶点&#xff09;和边…

ChatGPT国内镜像网站大盘点(国内可用免费GPT-3.5或GPT-4镜像站点)

目录 方法1&#xff1a;使用灵动Ai Chat网页版 方法2&#xff1a;使用AI CHATGPT 公益站 方法3&#xff1a;使用Chat8 方法4&#xff1a;使用https://steamship.com 方法5&#xff1a;使用AI文本工具站 方法6&#xff1a;使用AIDuTu 很多网友想要国内可用免费ChatGPT镜像站…

【AI实战】开源且可商用的 40B 大语言模型 Falcon 40B

【AI实战】开源且可商用的 40B 大语言模型 Falcon 40B Falcon 40B 介绍开源地址Falcon 40B 的测评开源协议 Falcon 40B 介绍 官网 https://www.tii.ae/news/uaes-technology-innovation-institute-launches-open-source-falcon-40b-large-language-model Abu Dhabi-UAE: 25 Ma…

spring cloud搭建(hystrix)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

安装指定版本docker [centos]

在安装k8s时&#xff0c;对其docker版本有要求&#xff0c;因为在v1.4后对docker不再支持。在安装v1.36版本时&#xff0c;需要提前安装对应版本的docker&#xff0c;这里安装20.10版本 一 先卸载原来安装的docker yum remove docker-ce docker-ce-cli containerd.io 再删除对…

vivado中的Video timing controller IP核参数计算方法

一、参数的计算 直入正题&#xff0c;已知某一1024*600的LCD屏幕&#xff0c;屏幕参数大致如下&#xff1a; 如何设置IP核配置界面的参数呢&#xff1f; 细调参数几乎用不到&#xff0c;我们主要说一下水平设置和垂直设置的8个参数如何配置。取LCD屏幕的典型值作为参考值&#…

自动驾驶之行泊一体

行泊一体技术是一种集成了自动泊车和无人驾驶技术的新型汽车技术,该技术可以使汽车更加智能化和自动化,提高驾驶的安全性和便捷性。从芯片和BEV技术门槛方面来看,我们可以更好地理解这项技术的优势和挑战。 一、芯片限制 实现行泊一体技术需要依靠一些关键的芯片技术,其…

谷歌浏览器Software Reporter Tool长时间占用CPU解决办法

如下图所示&#xff0c;大家是否在使用谷歌浏览器的过程中发现CPU风扇狂转&#xff0c;打开任务管理器发现一个名为software_reporter_tool.exe的软件占用了一半的CPU资源&#xff01;这简直不能忍&#xff0c;按照下面教程&#xff0c;可以解决这个令人苦恼的问题。 什么是Sof…

【LeetCode】693. 交替位二进制数

693. 交替位二进制数&#xff08;简单&#xff09; 方法一 思路 如果一个数的二进制表示总是 0、1 交替出现&#xff0c;那么这个数字可能有两种表示情况&#xff0c;...101010 或 ...010101 &#xff0c;即对应最低位从 0 或 1 开始的两种情况。 因此&#xff0c;我们先确定…

港科夜闻|香港科大出席一流大学建设系列研讨会-2022暨中国大学校长联谊会...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大出席一流大学建设系列研讨会—2022暨中国大学校长联谊会。5月25日至26日&#xff0c;香港科技大学副校长汪扬教授出席由中国科学技术大学主办的一流大学建设系列研讨会—2022暨中国大学校长联谊会。参会代表来自…