C++ lambda 表达式深剖

article/2025/10/4 0:49:10

目录

    • 传统艺能😎
    • 概念🤔
    • 语法🤔
    • 捕获方式🤔
      • 相互赋值😎
    • mutable🤔
    • 底层原理🤔

传统艺能😎

小编是双非本科大一菜鸟不赘述,欢迎米娜桑来指点江山哦(QQ:1319365055)

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,打码路上一路向北,彼岸之前皆是疾苦
一个人的单打独斗不如一群人的砥砺前行
这是我和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我


在这里插入图片描述

概念🤔

自 C++11 开始,C++ 有三种方式可以创建/传递一个可调用的对象:

函数指针
仿函数
Lambda 表达式

lambda 表达式本质上就是一个匿名函数,它是一个强大的功能,他的使用可以简化代码,而且可以提高代码可读性

这里举一个实例,以一个物品为例:

struct Items
{string _name;  //名字double _price; //价格int _num;      //数量
};

如果要对若干对象分别按照价格和数量进行升序、降序排序。

首先想到可以使用 sort 函数,但由于这里待排序的元素为自定义类型因此需要用户自行定义排序时的比较规则,要控制 sort 比较方式有常见的两种方法,一是对商品类的的 () 运算符进行重载,二是通过仿函数来指定比较方式。

重载当前类的 () 运算符是不可行的,因为这里要求分别按照价格和数量进行升序、降序排序,每次排序都去修改一下比较方式是很低效且笨重的做法

比如我用仿函数的方式进行比较:

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;}
};
struct CompareNumLess//数量降序
{bool operator()(const Goods& g1, const Goods& g2){return g1._num < g2._num;}
};
struct CompareNumGreater//数量升序
{bool operator()(const Goods& g1, const Goods& g2){return g1._num > g2._num;}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 300 }, { "香蕉", 3.3, 100 }, { "橙子", 2.2, 1000 }, { "菠萝", 1.5, 1 } };sort(v.begin(), v.end(), ComparePriceLess());    //价格升序sort(v.begin(), v.end(), ComparePriceGreater()); //价格降序sort(v.begin(), v.end(), CompareNumLess());      //数量升序sort(v.begin(), v.end(), CompareNumGreater());   //数量降序return 0;
}

如你所见,仿函数也顺利解决了以上问题,但是如果定义和使用位置隔的很远就不好观察,这就要求取名的时候通俗易懂,这种情况下就更加推荐使用 lambda 表达式。

比如我这里有一个做加法的仿函数:

class Plus {public:int operator()(int a, int b) {return a + b;}   
};Plus plus; 
std::cout << plus(11, 22) << std::endl;

我们把这个加法仿函数改写成 lambda 表达式就是

auto Plus = [](int a, int b) { return a + b; };

因为 lambda 表达式是一个匿名函数,该函数无法直接调用,这里我们借助 auto 将其赋值给一个变量,此时这个变量就可以像普通函数一样使用

这前后一对比,显而易见两者的优劣就高下立判了

语法🤔

lambda 表达式定义

[ capture-list ] ( params ) mutable(optional) exception(optional) attribute(optional) -> ret(optional) { body } 

当然在书写格式上并不是必须写成一行,如果函数体太长可以进行换行
说明一下各个参数:

capture-list:捕捉列表。上面的例子 auto Plus = [](int a, int b) { return a + b; }; 就没有捕获任何变量
params:和普通函数一样的参数。
mutable:只有这个 Lambda 表达式是 mutable 的才允许修改按值捕获的参数。
-> ret:返回值类型,可省略,编译器可以通过 return 语句自动推导
body:具体函数

没说明的就暂时不必理解,其中 除了捕获列表, L a m b d a 表达式的其它地方其实和普通的函数基本一样 \color{red} {除了捕获列表,Lambda 表达式的其它地方其实和普通的函数基本一样} 除了捕获列表,Lambda表达式的其它地方其实和普通的函数基本一样lambda 参数列表和返回值类型都是可有可无的,但捕捉列表和函数体是不可省略的,因此最简单的lambda函数如下:

int main()
{[]{}; //最简单的lambda表达式return 0;
}

捕获方式🤔

Lambda 表达式最基本的两种捕获方式是:按值捕获按引用捕获

我们对捕获列表的说明描述了上下文中哪些数据可以被 lambda 函数使用,以及使用的方式是传值还是传引用

[var]:值传递捕捉变量var
[=]:值传递捕获所有父作用域中的变量(成员函数包括this指针)
[&var]:引用传递捕捉变量var
[&]:引用传递捕捉所有父作用域中的变量(成员函数包括this指针)
[this]:值传递捕捉当前的this指针

实际当我们以 [&](引用传递全捕获) 或 [=] (值传递全捕获)的方式捕获变量时,编译器也不一定会把父作用域中所有的变量捕获进来,编译器可能只会对 lambda 表达式中用到的变量进行捕获,实际要看编译器的具体实现

父作用域就是包含 lambda 表达式的语句块,语法上捕捉列表可由多个逗号分割的捕捉项组成,比如KaTeX parse error: Expected '}', got '&' at position 18: …olor{red} {[=, &̲a, &b]} ,我们需要清楚 3 点:

  1. 捕捉列表不允许重复传递,否则会导致编译错误,比如 [ = , a ] \color{red} {[=, a]} [=,a]重复传递了变量 a
  2. lambda表达式之间不能相互赋值,即使看起来类型相同
  3. 在块作用域以外的 lambda 表达式捕捉列表必须为空!即全局lambda函数的捕捉列表必须为空,且在块作用域中的 lambda 表达式仅能捕捉父作用域中的局部变量,除此以外的都会导致编译报错。

相互赋值😎

lambda表达式之间不能相互赋值,就算是两个一模一样的也不行,这不邪门儿了嘛,为啥呢?

因为 lambda 表达式底层处理方式和仿函数是一样的(本文后面的底层原理部分有细谈),在VS下 lambda 表达式会被处理为函数对象,该函数对象对应的类名叫做<lambda_uuid>

类名中的uuid叫做通用唯一识别码,简单来说就是通过算法生成的一串字符串,它具有随机性和不重复性,保证在当前程序中每次生成不同的 uuid,因为 lambda 表达式底层的类名包含 uuid,这就保证了每个 lambda 表达式底层类名都是唯一的!

我们可以通过 t y p e i d ( 变量名 ) . n a m e ( ) \color{red} { typeid(变量名).name() } typeid(变量名).name()的方式来获取lambda表达式的类型来验证上述结论:

int main()
{int a = 10, b = 20;auto Swap1 = [](int& x, int& y)->void{int tmp = x;x = y;y = tmp;};auto Swap2 = [](int& x, int& y)->void{int tmp = x;x = y;y = tmp;};cout << typeid(Swap1).name() << endl; //class <lambda_797a0f7342ee38a60521450c0863d41f>cout << typeid(Swap2).name() << endl; //class <lambda_f7574cd5b805c37a13a7dc214d824b1f>return 0;
}

如你所见,就算是一模一样的 lambda 表达式,它们的类型也是不同的

mutable🤔

在实际使用中,比如实现一个交换函数,我们用 lambda 表达式实现:

int main()
{int a = 1, b = 2;auto Swap = [a, b](){int tem = a;a = b;b = tem;};Swap(); return 0;
}

这里一眼就是传值捕获,但是真的可以吗?答案是 No,他的编译不会通过,因为传值捕获到的变量默认是不可修改的(const):

//值捕获的类型是 const 类型
int i = 100;
auto func = [i]() {i = 200;  // 编译错误:assignment of read-only variable ‘i’
}; 

如果要取消其常量属性,就需要在 lambda 表达式中加上 mutable 像这样:

auto Swap = [a, b]()mutable{int tem = a;a = b;b = tem;};

但由于是传值捕捉,lambda 表达式中对局部变量的修改不会影响本身的变量,与函数的传值传参是一个道理,因此这种方法无法完成交换功能。

底层原理🤔

实际编译器在底层对于 lambda 表达式的处理方式,完全就是按照函数的方式处理的,函数对象就是我们平常所说的仿函数,就是在类中对 () 运算符进行了重载的类对象

我们编写了一个 Add 类,然后对 () 运算符进行了重载,因此 Add 类实例化出的 add1 对象就是函数对象,add1 可以像函数一样使用。接着写了一个 lambda 表达式,并借助 auto 将其赋值给 add2 对象,这时 add1 和 add2 都可以像普通函数一样使用

class Add
{
public:Add(int base):_base(base){}int operator()(int num){return _base + num;}
private:int _base;
};
int main()
{int base = 1;//函数对象Add add1(base);add1(1000);//lambda表达式auto add2 = [base](int num)->int{return base + num;};add2(1000);return 0;
}

我们再通过反汇编对代码进行观察:
在这里插入图片描述
创建函数对象 add1 时,会调用 Add 类的构造函数,使用 add1 时,会调用 Add 类的 () 运算符重载函数。
在这里插入图片描述

然后 lambda 表达式这边也是和函数的过程非常类似:在借助 auto 将 lambda 表达式赋值给 add2 对象时,会调用 <lambda_uuid> 类的构造函数,在使用add2对象时,会调用<lambda_uuid>类的()运算符重载函数

本质就是因为 l a m b d a 表达式在底层被转换成了仿函数 \color{red} {本质就是因为lambda表达式在底层被转换成了仿函数} 本质就是因为lambda表达式在底层被转换成了仿函数

当我们定义一个lambda表达式后,编译器会自动生成一个类,在该类中对 () 运算符进行重载,实际 lambda 函数体的实现就是这个仿函数 operator() 的实现,在调用 lambda 表达式时,参数列表和捕获列表的参数,最终都传递给了仿函数的 operator()。

aqa 芭蕾 eqe 亏内,代表着开心代表着快乐,ok 了家人们。


http://chatgpt.dhexx.cn/article/v5vWAcKI.shtml

相关文章

Lambda表达式的本质

一直想写一篇文章&#xff0c;来总结lambda表达式&#xff0c;但是之前感觉总结的不是特别到位&#xff0c;现在看了几篇文章和视频后&#xff0c;感觉对lambda表达式有了比较深刻的认识&#xff0c;现在进行记录总结如下&#xff1a; lambda表达式又叫做匿名函数&#xff0c;…

Java Lambda 表达式

目录 一、说明二、理解三、演示1.常规方法实现2.静态内部类3.局部内部类4.匿名内部类5.Lambda表达式6.Lambda再简化 一、说明 Lambda表达式是什么 Lambda 表达式也称为闭包&#xff0c;是Java 8 发布的新特性Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中) …

C++ Lambda表达式

在C11和更高版本中&#xff0c;Lambda表达式&#xff08;通常称为Lambda&#xff09;是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象&#xff08;闭包&#xff09;的简便方法。Lambda通常用于封装传递给算法或异步函数的少量代码行。1 Lambda表达式是C11中一…

java Lambda表达式详解

文章目录 一、背景1.1语法1.2函数式接口 二、Lambda的基本使用2.1引子2.2常见的使用方式2.3语法小结 三、变量捕获3.1 匿名内部类3.2 匿名内部类的变量捕获3.3Lambda的变量捕获 四、Lambda在集合当中的使用4.1 Collection接口4.2 list接口4.3 Map接口 总结 提示&#xff1a;以下…

Lambda表达式详解

Lambda表达式 1. 为什么使用lambda表达式2. 入门案例3. lambda表达式组成4. lambda表达式使用4.1 语法格式一4.2 语法格式二4.3 语法格式三4.4 语法格式四4.5 语法格式五4.6 语法格式六 5. 总结 1. 为什么使用lambda表达式 lambda是一个匿名函数&#xff0c;我们可以吧lambda表…

Lambda 表达式

一.什么事Lambda表达式 Lambda 表达式是一种匿名函数&#xff0c;也可称为闭包&#xff0c;简单地说&#xff0c;它是没有声明的方法&#xff0c;也即没有访问修饰符、返回值声明和名字。 它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格&#xff0c;使 Java 语言…

Lambda表达式超详细总结(简单易懂)

文章目录 1、什么是Lambda表达式2、为什么使用Lambda表达式3、函数式接口&#xff08;lambda表达式的使用前提&#xff09;4、推导Lambda表达式5、Lambda表达式语法 1、什么是Lambda表达式 Lambda表达式&#xff0c;也可称为闭包。其本质属于函数式编程的概念&#xff0c;是Ja…

Lambda表达式超详细总结

文章目录 1. 什么是Lambda表达式2. 为什么使用Lambda表达式3. Lambda表达式语法4. 函数式接口4.1 什么是函数式接口4.2 自定义函数式接口4.3 Java内置函数式接口 5. 方法引用6. 构造器引用7. 数组引用8. Lambda表达式的作用域8.1 访问局部变量8.2 访问局部引用&#xff0c;静态…

SQL语句学习之SQL基础的表创建以及添加数据

SQL语句学习之SQL基础的表创建以及添加数据 学习目标1&#xff1a; 一周内掌握SQL基础语句 tip:主要是在牛客网&#xff08;牛客网&#xff09;上进行练习&#xff0c;里面有在线编程&#xff0c;可以直接运行&#xff0c;而且有解题的思路&#xff0c;比较清晰&#xff0c;而…

Hive SQL之表与建表

Hive数据模型总览 用户通过数据库访问Hive后&#xff0c;首先选择哪个数据库&#xff0c;然后在库的下面选择的是一张张表&#xff0c;表是管理数据的的最基本的所在&#xff0c;在表中的一行行记录&#xff0c;在现实中就是一条条数据&#xff0c;里面有我们的字段字段类型和它…

SQLite 创建表SQL语句

SQLite 创建表 创表语法 CREATE TABLE [表名称](--主键列不可为空[列1] [类型] PRIMARY KEY NOT NULL,--列可为空[列2] [类型],--列不可为空[列3] [类型] NOT NULL );创表示例 CREATE TABLE User (Id INT PRIMARY KEY NOT NULL,Name Text,Sex INT NOT NULL )在线Sqlite查看器…

SQL创建表为啥不显示

这里新建表 左上角报存 保存完以后点击刷新就会出来刚创建的表格

SQL 创建表的备份

1. SELECT INTO 语句 SELECT INTO 语句从一个表中选取数据&#xff0c;然后把数据插入另一个表中。 SELECT INTO 语句常用于创建表的备份复件或者用于对记录进行存档&#xff1b; SQL SELECT INTO 语法 您可以把所有的列插入新表&#xff1a; SELECT * INTO new_table_name…

SQL Server创建表

我们要怎么在数据库中创建表呢&#xff01;首先&#xff0c;表在数据库和模式中唯一命名。每个表包含一个或多个列。 每列都有一个相关的数据类型&#xff0c;用于定义它可以存储的数据类型&#xff0c;例如&#xff1a;数字&#xff0c;字符串和日期。 要创建新表&#xff0c;…

SQL Server 创建表

我们在上一节中完成了数据库的创建&#xff0c;在本节&#xff0c;我们要往这个新的数据库中加入点数据&#xff0c;要想将数据添加到数据库&#xff0c;我们就必须在数据库中添加一个表&#xff0c;接下来来看看具体的操作。 我们的数据库是一个任务跟踪数据库&#xff0c;那…

ORACLE SQL 创建表

1.创建表&#xff1a; 1.1表名和列名&#xff1a; 一定要以字母开头 一定在 1-30 个字符之间 只能包含 A–Z, a–z, 0–9, _, $, 和 # 一定不能和用户定义的其他对象重名 一定不能是Oracle 的保留字 一定要有CREATE TABLE权限 而且需要一定的存储空间 还要指定的&…

利用SQL创建表结构

一、创建图书管理系统&#xff0c;其中涉及到的对象有&#xff08;图书分类&#xff0c;图书&#xff0c;学生&#xff0c;借书记录&#xff09; 1、列出关系模式 (1) 书本类别&#xff08;种类编号&#xff0c;种类名称&#xff09; (2) 学生&#xff08;学生编号&#xff0…

SQL创建表

要创建新的表&#xff0c;就要使用create table语句。 1、第一&#xff0c;要指定数据库的名称&#xff0c;必须是数据库有的&#xff0c;如果没有指定&#xff0c;那就默认是当前数据库。&#xff08;如图下所示是没有指定数据库的&#xff09; 2、第二&#xff0c;指定表的模…

SQL表的创建

一&#xff0c;创建表 1.使用普通方法创建表 1&#xff0c;进入SQL进行连接 2&#xff0c;在左边会有一个对象资源管理器&#xff0c;右键数据库&#xff0c;在弹出的窗口中选择新建数据库 3&#xff0c;给这个包取个名字&#xff0c;在这个界面可以给这个表选择存储地方&…

SQL语句之表的创建和使用

表 一、表的创建&#xff08;DDL&#xff09;1.建表的语法格式创建一个学生表 2.mysql中的数据类型3.删除表 二、在表中插入数据insert&#xff08;DML&#xff09;1.insert2.insert插入日期3.date和datetime区别 三、修改(update)DML1.语法格式 四、删除数据(delete)DML1.语法…