【C++11】lambda表达式保姆级详解(深度解析,小白一看就会!!)

目录

一、前言

二、lambda 表达式是什么? 

三、lambda 表达式的引入 --- 仿函数 

四、lambda表达式的语法 

五、lambda表达式的使用 

🔥 lambda 的经典用法🔥

🔥 lambda 中捕捉列表的使用🔥 

🔥lambda表达式的原理🔥 

六、lambda表达式的优点及适用场景 

七、共勉 


一、前言

   Lambda表达式:是 C++11引入的一种函数对象,可以方便地创建匿名函数。与传统的函数不同,Lambda表达式可以在定义时直接嵌入代码,无需单独定义函数名称、参数和返回类型等信息。Lambda表达式通常用于需要定义一些简单的回调函数或者函数对象。优点:简洁效率高更加灵活本文主要介绍Lambda的工作原理以及使用方法。

二、lambda 表达式是什么? 

lambda 表达式 源于数学中的 λ 演算,λ 演算是一种 基于函数的形式化系统,它由数学家 阿隆佐邱奇 提出,用于研究抽象计算和函数定义。对于编程领域来说,可以使用 lambda 表达式 快速 构建函数对象作为函数中的参数 

三、lambda 表达式的引入 --- 仿函数 

仿函数 是 C++ 中的概念,指借助 类】 + 【operator()】重载 创建的 函数对象仿函数 的使用场景如下 

  • 创建一个 vector,通过 sort 函数进行排序,至于结果为升序还是降序,可以通过 仿函数 控制 
#include <iostream>
#include <unordered_map>
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct cmpLess   
{
	bool operator()(int n1, int n2)
	{
		return n1 < n2;
	}
};

struct cmpGreater
{
	bool operator()(int n1, int n2)
	{
		return n1 > n2;
	}
};

int main()
{
	vector<int> arr = { 8,5,6,7,3,1,1,3 };

	sort(arr.begin(), arr.end(), cmpLess()); // 升序

	cout << "升序: ";
	for (auto e : arr)
		cout << e << " ";
	cout << endl;

	sort(arr.begin(), arr.end(), cmpGreater()); // 降序

	cout << "降序: ";
	for (auto e : arr)
		cout << e << " ";
	cout << endl;

	return 0;
}

注:sort 如果不传递函数对象,默认排序结果为升序 

结果为正确排序,但这种先创建一个仿函数对象,再调用的传统写法有点麻烦了,如果是直接使用 lambda 表达式 创建函数对象,整体逻辑会清楚很多

  • 使用 lambda 表达式 修改后的代码如下,最大的改变就是 可以直接在传参时直接编写函数对象的代码逻辑 
#include <iostream>
#include <unordered_map>
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
	vector<int> arr = { 8,5,6,7,3,1,1,3 };

	sort(arr.begin(), arr.end(), [](int n1, int n2) { return n1 < n2; }); // 升序

	cout << "升序: ";
	for (auto e : arr)
		cout << e << " ";
	cout << endl;

	sort(arr.begin(), arr.end(), [](int n1, int n2) { return n1 > n2; }); // 降序

	cout << "降序: ";
	for (auto e : arr)
		cout << e << " ";
	cout << endl;

	return 0;
}
  •  最终结果也是正常的

有了 lambda 表达式 之后,程序员不必再通过 仿函数 构建 函数对象,并且可以在一定程度上提高代码的可阅读性,比如一眼就可以看出回调函数是在干什么 

  • 接下来看看如何理解 lambda 表达式 语法 

四、lambda表达式的语法 

lambda 表达式 分为以下几部分: 

  • [ ] 捕捉列表
  • ( ) 参数列表
  • mutable 关键字
  • ->returntype 返回值类型
  • { } 函数体
lambda表达式:[ ]( ) mutable ->returntype { }

其中,( ) 参数列表、mutable->returntype 都可以省略 

  • 省略 ( )参数列表 表示当前是一个无参函数对象
  • 省略 mutable关键字 表示保持捕捉列表中参数的常量属性
  • 省略 ->returntype返回值类型 表示具体的返回值类型由函数体决定,编译器会自动推导出返回值类型

注意: 

  • 捕捉列表函数体 不可省略
  • 如果使用了 mutable关键字 或者 ->returntype 返回值,就不能省略 ( )参数列表,即使为空
  • 虽然返回值类型编译器可以推导,但最好还是注明返回值类型

 也就是说,最基本的 lambda表达式 只需书写 [ ]{ } 即可表示,比如这样

int main()
{
	// 最简单的 lambda表达式
	[]{};
	return 0;
}
  • 此时的 lambda表达式 相当于一个 参数为空、返回值为空、函数体为空 的匿名函数对象 
void func()
{}

func 的主要区别在于 lambda 表达式 构建出来的是一个 匿名函数对象,而 func 是一个 有名函数对象,可以直接调用

五、lambda表达式的使用 

🔥 lambda 的经典用法🔥

lambda 表达式 构建出的是一个 匿名函数对象,匿名函数对象也可以调用,不过需要在创建后立即调用,否则就会因为越出作用域而被销毁(匿名对象生命周期只有一行) 

  • 下面通过 lambda 表达式 构建一个简单的 两整数相加 函数对象并调用 
int main()
{
	int ret = [](int x, int y)->int { return x + y; }(1, 2);

	cout << ret << endl;
	return 0;
}

  • 直接使用 lambda 表达式 构建出的 匿名函数对象 比较抽象,一般都是将此 匿名函数对象 作为参数传递(比如 sort),如果需要显式调用,最好是将创建出来的 匿名函数对象 赋给一个 有名函数对象,调用时逻辑会清晰很多
  •  使用 auto 推导 匿名函数对象 的类型,然后创建 add 函数对象
int main()
{
	auto add = [](int x, int y)->int { return x + y; };

	int ret = add(1, 2);

	cout << ret << endl;
	return 0;
}

lambda 表达式 还有很多玩法,接下来逐一介绍,顺便学习其他组成部分 


🔥 lambda 中捕捉列表的使用🔥 

 利用 lambda 表达式 构建一个交换两个元素的 函数对象

  • 最经典的写法是 函数参数设为引用类型,传入两个元素,在函数体内完成交换 
int main()
{
	int x = 1;
	int y = 2;

	cout << "交换前" << endl;
	cout << "\tx: " << x << endl << "\ty: " << y << endl;

	auto swap = [](int& rx, int& ry)->void
				{
					auto tmp = rx;
					rx = ry;
					ry = tmp;
				};

	swap(x, y);

	cout << "交换后" << endl;
	cout << "\tx: " << x << endl << "\ty: " << y << endl;
	return 0;
}
  • 这种经典写法毋庸置疑,肯定能完成两数交换的任务 

  • 除此之外,还可以借助 lambda表达式 中的 捕捉列表 捕获外部变量进行交换 
int main()
{
	int x = 1;
	int y = 2;

	cout << "交换前" << endl;
	cout << "\tx: " << x << endl << "\ty: " << y << endl;

	auto swap = [x, y]() ->void
		{
			auto tmp = x;
			x = y;
			y = tmp;
		};

	swap();

	cout << "交换后" << endl;
	cout << "\tx: " << x << endl << "\ty: " << y << endl;
	return 0;
}
  •  因为现在 函数对象 是直接捕获外部变量进行操作,调用函数对象时,无需传参
  • 代码写完,编译器立马给出了报错:xy 不可修改 

  • 这是因为 捕捉列表 中的参数是一个值类型(传值捕捉),此时的捕获的是外部变量的内容,然后赋值到 x、y 中,捕捉列表 中的参数默认具有 常量属性,不能直接修改,但可以添加 mutable 关键字 取消常性 
int main()
{
	int x = 1;
	int y = 2;

	cout << "交换前" << endl;
	cout << "\tx: " << x << endl << "\ty: " << y << endl;

	auto swap = [x, y]()mutable ->void
		{
			auto tmp = x;
			x = y;
			y = tmp;
		};

	swap();

	cout << "交换后" << endl;
	cout << "\tx: " << x << endl << "\ty: " << y << endl;
	return 0;
}
  • 但是程序运行结果不尽人意,外部的 x、y 并没有被交换,证明此时 捕捉列表 中的参数 x、y 是独立的值(类似函数中的值传递) 

  • 想让外部的 x、y 被真正捕获,需要使用 引用捕捉 
int main()
{
	int x = 1;
	int y = 2;

	cout << "交换前" << endl;
	cout << "\tx: " << x << endl << "\ty: " << y << endl;

	auto swap = [&x, &y]() ->void
	{
		auto tmp = x;
		x = y;
		y = tmp;
	};

	swap();

	cout << "交换后" << endl;
	cout << "\tx: " << x << endl << "\ty: " << y << endl;
	return 0;
}
  •  现在 x、y 被成功交换了

注意: 捕捉列表中的 &x 表示引用捕捉外部的 x 变量,并非取地址(特例) 

所以说 mutable 关键字不常用,因为它取消的是值类型的常性,即使修改了,对外部也没有什么意义,如果想修改,直接使用 引用捕捉 就好了 


捕捉列表 支持 混合捕捉,同时使用 引用捕捉 + 传值捕捉 

int main()
{
	int x = 1;
	int y = 2;

	cout << "调用前" << endl;
	cout << "\tx: " << x << endl << "\ty: " << y << endl;

	// 混合捕捉
	auto func = [&x, y]()mutable ->void
		{
			x = 100;
			y = 200;
		};

	func();

	cout << "调用后" << endl;
	cout << "\tx: " << x << endl << "\ty: " << y << endl;
	return 0;
}
  • x 被修改了,而 y 没有 


除了 混合捕捉 外,捕捉列表 还支持 全部引用捕捉 和 全部传值捕捉 

全部引用捕捉 

int main()
{
	int x, y, z, a, b, c;
	x = y = z = 0;
	a = b = c = 1;
	string str = "Hello lambda!";
	cout << "&str: " << &str << endl << endl;

	auto func = [&]()->void
				{
					cout << x << " " << y << " " << z << " " << endl;
					cout << a << " " << b << " " << c << " " << endl;
					cout << str << endl;
					cout << "&str: " << &str << endl << endl;
				};

	func();

	return 0;
}
  • 无需指定 捕捉列表 中的参数,& 可以一键 引用捕捉 外部所有变量 

注:只能捕捉已经定义或声明的变量 


全部传值捕捉 

int main()
{
	int x, y, z, a, b, c;
	x = y = z = 0;
	a = b = c = 1;
	string str = "Hello lambda!";
	cout << "&str: " << &str << endl << endl;

	auto func = [=]()->void
				{
					cout << x << " " << y << " " << z << " " << endl;
					cout << a << " " << b << " " << c << " " << endl;
					cout << str << endl;
					cout << "&str: " << &str << endl << endl;
				};

	func();

	return 0;
}
  • 全部传值捕捉 也能一键捕捉外部变量,不过此时捕获的是外部变量的值,并非变量本身,无法对其进行修改(可以通过 mutable关键字 取消常性)

注意: [=] 表示全部传值捕捉,[] 表示不进行捕捉,两者不等价 


捕捉列表 的使用非常灵活,比如 [&, x] 表示 x 使用 传值捕捉,其他变量使用 引用捕捉 [=, &str] 表示 str 使用 引用捕捉,其他变量使用 传值捕捉 

  • 捕捉列表 就像一个 “大师球”,可以直接捕捉到外部的变量,在需要大量使用外部变量的场景中很实用,有效避免了繁琐的参数传递与接收 


有没有 全部引用捕捉 + 全部传值捕捉 ? 

  • 当然没有,这是相互矛盾的,一个变量不可能同时进行 引用传递 和 值传递,即便传递成功了,编译器在使用时也不知道使用哪一个,存在二义性,所以不被允许

注意: 关于 捕获列表 有以下几点注意事项 

  1. 捕捉列表不允许变量重复传递,否则就会导致编译错误
  2. 在块作用域以外的 lambda 函数捕捉列表必须为空
  3. 在块作用域中的 lambda 函数不仅能捕捉父作用域中局部变量,也能捕捉到爷爷作用域中的局部变量


 lambda表达式 还可以完美用作 线程回调函数,比如接下来使用 C++11 中的 thread 线程类,创建一个线程,并使用 lambda 表达式 创建一个线程回调函数对象

int main()
{
	// 创建线程,并打印线程id
	thread t([] { cout << "thread running... " << this_thread::get_id() << endl; });
	
	t.join();
	return 0;
}

 总之 lambda 表达式 在实际开发中非常好用,关于 thread类的相关知识放到后面讲解,接下来先看看 lambda 表达式 的实现原理

🔥lambda表达式的原理🔥 

 lambda 表达式 生成的函数对象有多大呢?

  • 是像 普通的函数对象指针 一样占 4/8 字节,还是像 仿函数 一样占 1 字节,通过 sizeof 计算大小就可以一探究竟 
// 普通函数
int add(int x, int y)
{
	return x + y;
}

// 仿函数
class addFunc
{
public:
	int operator()(int x, int y)
	{
		return x + y;
	}
};

int main()
{
	auto typeA = add;
	addFunc typeB;
	auto typeC = [](int x, int y)->int { return x + y; };

	cout << "普通函数: " << sizeof(typeA) << endl;
	cout << "仿函数: " << sizeof(typeB) << endl;
	cout << "lambda表达式: " << sizeof(typeC) << endl;

	return 0;
}
  •  结果显示,lambda 表达式 生成的函数对象与 仿函数 生成的函数对象大小是一样的,都是 1字节

  • 仿函数 生成的函数对象大小为 1字节是因为其生成了一个空类,实际调用时是通过 operator() 重载实现的,比如上面的 addFunc 类,空类因为没有成员变量,所以大小只为 1字节

由此可以推断 lambda 表达式 本质上也是生成了一个空类,分别查看使用 仿函数 和 lambda 表达式 时的汇编代码 

  • 可以看到,这两段汇编代码的内容是一模一样的,都是先 call 一个函数(operator() 重载函数),然后再执行主体逻辑(两数相加),只不过使用 仿函数 需要自己编写一个 空类,而 使用 lambda 表达式 时由编译器生成一个 空类,为了避免这个自动生成的 空类 引发冲突,会将这个 空类 命名为 lambda_uuid

uuid 是 通用唯一标识码,可以生成一个重复率极低的辨识信息,避免类名冲突,这也意味着即便是两个功能完全一样的 lambda 表达式,也无法进行赋值,因为 lambda_uuid 肯定不一样 

所以在编译器看来,lambda 表达式 本质上就是一个 仿函数 


六、lambda表达式的优点及适用场景 

lambda 表达式 作为一种轻量级的匿名函数表示方式,具备以下优点: 

  1. 简洁性: 对于简单的函数操作,无需再手动创建函数、调用,只需要编写一个 lambda 表达式生成函数对象
  2. 方便些: lambda 表达式具有 捕捉列表,可以轻松捕获外部的变量,避免繁琐的参数传递与接收
  3. 函数编程支持: lambda 表达式可以作为函数的参数、返回值或存储在数据结构中
  4. 内联定义: lambda 表达式 lambda表达式可以作为函数的参数、返回值或存储在数据结构中
  5. 简化代码: 对于一些简单的操作,使用 lambda 表达式可以减少代码的行数,提高代码的可读性

总的来说,lambda 表达式 可以替代一些代码量少的函数,使用起来十分方便,如果 lambda 表达式 编写出来的代码过于复杂时,可以考虑转为普通函数,确保代码的清晰性和可读性 

七、共勉 

以下就是我对 【C++11】lambda表达式 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 【C++11】 的理解,请持续关注我哦!!!    

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值