当前位置: 首页 > news >正文

C++进阶(6)——lambda表达式

目录

基本概念

lambda表达式的语法

捕捉列表

lambda表达式的具体应用

lambda表达式的实现原理


基本概念

我们的lambda表达式本质上就是一个匿名函数对象,和我们的普通函数不同的是他是可以定义在函数的内部的,其实不光光我们的C++支持了lambda表达式,我们的很多的高级语言(Java, Python, C#)都是支持的,而且这一语法是使用频率比较高的语法之一,它可以提高我们的代码可读性,下面我们就来看看吧。

lambda表达式的语法

[capture-list] (parameters)-> return type {function body
}

相关说明:

[capture-list]:捕捉列表,这个列表出现在我们的lambda函数的开始位置,编译器根据[ ]来判断接下来的代码是不是lambda函数,捕捉列表能够捕捉我们上下文中的变量以供lambda函数使用,捕捉列表可以传值或是传引用捕捉,即使列表为空也是不可以省略的(重点)。

(parameters):参数列表,和我们的普通参数列表的功能相类似,如果不要传递参数,就可以同我们的( )一起省略掉。

->return type:返回值类型,用来追踪返回类型形式声明函数的返回值类型,没有返回值的时候这个部分可以省略掉,一般的返回值类型明确的情况下,也是可以省略的,可以由编译器对返回值类型进行自我推导。

{function body}:函数体,函数体的实现和我们的普通函数完全类似,除了可以使用其参数外,我们还可以使用我们上面捕获到的变量,函数体不可以省略。

根据我们上面的介绍,我们可以写出我们最简单的lambda表达式了,虽然这个代码什么也没干:

#include <functional>
#include <iostream>
using namespace std;
int main() {[]{};return 0;
}

接下来我们就可以简单地使用一下我们的lambda表达式了:

示例代码:

#include <iostream>
using namespace std;
int main() {auto add = [](int x, int y)->int {return x + y;};cout << add(1, 2) << endl;auto func = [] {cout << "hello world!" << endl;};func();int a = 1, b = 2;auto swap = [](int& x, int& y) {int temp = x;x = y;y = temp;};swap(a, b);cout << a << ":" << b << endl;return 0;
}

测试效果如图:

捕捉列表

我们的lambda表达式默认只是泗洪lambda函数体和参数中的变量,想使用我们的外层变量就要进行捕捉操作了。我们这里有常见的三种捕捉方式:

第一种方式:

在捕捉列表中显示的传值或是传引用捕捉,多个变量就用逗号分隔。

[x, y, &z]:x和y是值捕捉,z是引用捕捉。

第二种方式:

我们在捕捉列表中可以显示那就可以隐式捕捉,有两种:

[=]:捕捉值

[&]:捕捉引用

这样我们的lambda表达式用了什么变量,编译器就会自动捕捉那些变量了。

第三种方式:

我们还可以将上面的两种方式混合起来。

[=, &x]:表示的是其他变量使用隐式值捕捉,但是x引用捕捉。

[&, x, y]:表示的是其他变量使用隐式引用捕捉,但是我们的x和y使用值捕捉。

这里要注意的是,我们的第一个元素必须是&或是=,是&的时候后面就必须是值捕捉,是=的时候后面就必须是引用捕捉。

敲黑板:

1、lambda表达式如果是在函数的局部域中,它可以捕捉lambda位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量本身也不需要捕捉,lambda表达式中可以直接使用,这也就意味着lambda表达式如果是定义在全局的位置,那么我们的捕捉列表就必须要是空的。

2、默认情况下,我们这里的lambda捕捉列表是被const修饰的,也就是说我们传值捕捉来的对象是不可以被修改的,我们可以使用mutable加在参数列表的后面来取消其常量性,也就是说我们这个时候的传值捕捉对象就是可以修改的了,但是修改的还是形参对象,并不会影响实参。使用这个修饰符后,参数列表就不可以省略了(即使参数为空)。

代码示例:

#include <iostream>
using namespace std;
int x = 0;
// 这里是全局,没有什么可以捕捉的变量
auto func1 = []() {x++;
};
int main() {int a = 0, b = 1, c = 2, d = 3;auto func2 = [a, &b] {// 值的捕捉不可以修改,但是引用捕捉的变量是可以修改的// a++; // 报错b++;int ret = a + b;return ret;};cout << func2() << endl; cout << b << endl;cout << "*********************" << endl;// 隐式值捕捉auto func3 = [=] {int ret = a + b + c;return ret;};cout << func3() << endl;cout << "*********************" << endl;// 隐式引用捕捉cout << a << " " << b << " " << c << " " << d << endl;auto func4 = [&] {a++;c++;d++;};func4();cout << a << " " << b << " " << c << " " << d << endl;cout << "*********************" << endl;// 混合捕捉1cout << a << " " << b << " " << c << " " << d << endl;auto func5 = [&, a, b] {// a++; // 报错// b++; // 报错c++;d++;};cout << a << " " << b << " " << c << " " << d << endl;cout << "*********************" << endl;// 混合捕捉2cout << a << " " << b << " " << c << " " << d << endl;auto func6 = [=, &a, &b] {a++;b++;// c++; // 报错// d++; // 报错};func6();cout << a << " " << b << " " << c << " " << d << endl;cout << "*********************" << endl;// 局部静态变量和全局变量不能捕捉,也不需要捕捉static int y = 2;auto func7 = [] {int ret = x + y;return ret;};cout << func7() << endl;cout << "*********************" << endl;// 传值捕捉是拷贝默认是被const修饰的// 使用mutable可以消去const属性,但是我们的改变并不会影响外面的值cout << a << " " << b << " " << c << " " << d << endl;auto func8 = [=]()mutable {a++;b++;c++;d++;return a + b + c + d;};cout << func8() << endl;cout << a << " " << b << " " << c << " " << d << endl;cout << "*********************" << endl;return 0;
}

测试效果如图:

lambda表达式的具体应用

我们在学习使用lambda表达式之前,我们使用的可调用对象就是函数指针和仿函数对象了,这两种方式都比较的麻烦,于是我们的就可以使用lambda表达式,不仅方便还具有很高的代码可读性。

我们在实现一些比较器的时候,使用lambda表达式就会比较方便,我们这里还是来举个栗子:

代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Goods {string _name; // 商品名double _price; // 商品价格int _evaluate; // 商品评价// 构造函数Goods(const char* name, double price, int evaluate):_name(name),_price(price),_evaluate(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 = {{"苹果", 1.2, 5}, {"香蕉", 3.3, 3}, {"橘子", 2.3, 4}, {"栗子", 1.1, 5}};// 使用我们的仿函数// 按价格升序sort(v.begin(), v.end(), ComparePriceLess());// 按价格降序sort(v.begin(), v.end(), ComparePriceLess());// 使用我们的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;});// 实现评价从低到高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;
}

lambda表达式的实现原理

其实聪明的友友可能已经猜到了lambda表达式的实现原理,它的实现原理其实和我们之间讲C++11中的for一样,编译之后从汇编的角度看,就没有什么范围for,范围for的底层就是调用迭代器,我们的lambda也是一样的,底层其实就是生成了对应的仿函数的类。

重点说明一下:

我们这里的底层仿函数是编译的时候按照一定的规则自动生成的,保证了不同的lambda表达式的类名是不一样的,lambda的参数/返回值类型/函数体其实就是我们的仿函数operator()的参数/返回值类型/函数体,lambda表达式的捕捉列表的本质就是生成仿函数的成员变量,也就是说我们的捕捉列表的变量都是lambda类构造函数的实参,隐式捕捉就要看传入了什么变量了。

我们这里还是来写个代码通过反汇编来验证一下:

代码示例:

#include <iostream>
using namespace std;
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.21;// 使用lambda表达式auto r1 = [rate](double money, int year) {return money * rate * year;};r1(10000, 3);// 使用函数对象Rate r2(rate);r2(10000, 3);return 0;
}

我们将这个代码转到反汇编来看:

我们通过下面这个图可以看到,我们一开始调用了Rate类的构造函数,然后我们在使用对象r2的时候调用了Rate类的()运算符重载函数。

同样的,如果我们将lambda表达式的代码也转到反汇编,我们可以看到这里的反汇编和上面的是很类似的,首先是调用了<lambda_uuid>类的构造函数,然后在使用对象r1的时候,就会调用<lambda_uuid>类的( )运算符重载函数。

我们这里为了严谨,将我们的可以验证一下我们不同的lambda表达式的类型名是不是一样的:

示例代码:

#include <iostream>
using namespace std;
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.12;auto r1 = [rate](double money, int year) {return money * rate * year; };auto r2 = [rate](double money, int year) {return money * rate * year; };cout << typeid(r1).name() << endl;cout << typeid(r2).name() << endl;return 0;
}

测试效果:

我们这里的结果是缩写的结果,但是还是显示了它们的不同之处。

敲黑板:

我们这里的类名处理成了<lambda_uuid>,这里的uuid即使我们的通用唯一识别码(Universally Unique Identifier),这个码在编程中经常被使用,这个码可以保证我们的类名唯一性。

http://www.dtcms.com/a/443107.html

相关文章:

  • 数据结构(2)-------- 线性表
  • 网站建设 源代码asp.net 做网站
  • C++ :std::bind 还能用吗?它和 Lambda 有什么区别?
  • 优秀网站特点广告制作安装工
  • 威海做网站的哪家好玉树电子商务网站建设
  • 网站建设 引导帮企业建设网站销售
  • 网站建设必备条件dw制作网页步骤
  • 网络协议分层与Socket编程详解
  • 【Svelte 5】当改变$state中的属性值,但是该属性并未在页面中使用,会导致页面重写渲染吗?
  • 基于SpringBoot+Vue的万佳连锁使利店库存管理系统(Echarts图形化分析)
  • 第二章:BI的原理与技术架构
  • 上海万网网站建设湖北住房和城乡建设厅网站
  • 学习Java第三十天——黑马点评37~42
  • 北京网站建设价钱公众号文章制作
  • 长沙机械网站建设昆明最新消息今天
  • 深度解析 Python 报错:TypeError: ‘NoneType‘ object is not subscriptable
  • 泉州市城乡和住房建设网站大连装修公司
  • 哈巴河网站制作今天的最新消息新闻
  • 做网站猫腻大吗电商网站建设功能
  • 站长论坛网站模板建站教程
  • 淮南网站建设服务免费wordpress中文主题下载
  • 印刷报价网站源码下载小笨鸟跨境电商平台
  • Product Hunt 每日热榜 | 2025-10-04
  • 电商购物网站模板下载来年做哪个网站致富
  • 什么是近场?什么是远场?
  • 【typora激活使用】mac操作方式
  • 免费网站seo优化接单网官网
  • 旅游商城网站订单处理网站空间 云端
  • 串扰12-串扰对信号的影响
  • 申报湖南创新型省份建设专项网站wordpress用户管理插件