【C++11】lambda 表达式

一. 为什么要有 lambda 表达式?

在 C++98 中,如果想要对一个数据集合中的元素进行排序,可以使用 std::sort 方法。如果待排序元素为自定义类型,此时需要用户自定义排序时的比较规则:
在这里插入图片描述

后面人们开始觉得上面的写法太复杂了:

  • 如果每次比较的逻辑不一样,就要去分别实现多个仿函数
  • 仿函数命名不规范的话(比如:Cpmpare1、Compare2 这样的),会给编程者带来了极大的不便。因此,在 C++11 语法中引入了 lambda 表达式。

二. 什么是 lambda 表达式?

1. 概述

下面是 lambda 表达式各个部分的说明:

在这里插入图片描述

  • [capture-list] : 捕捉列表。该列表总是出现在 lambda 表达式的开始位置,它是 lambda 表达式的标识。编译器根据 [ ] 来判断接下来的代码是否为 lambda 表达式,捕捉列表能够捕捉上文作用区中的变量供 lambda 表达式使用。
  • (parameters):参数列表。与普通函数的参数列表一致(可以指定参数是传值还是传引用,或者是否是 const),如果不需要参数传递,则可以连同括号一起省略。
  • mutable:默认情况下,lambda 表达式的捕捉列表中,传值捕捉到的值都是 const 属性的,使用 mutable 可以取消这些传值捕捉对象的 const 量性。注意使用该修饰符时,参数列表不可省略。
  • ->returntype:返回值类型。用追踪返回类型形式声明表达式的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型自动进行推导。
  • {statement}:函数体。在该函数体内,除了可以使用参数列表中提供的形参外,还可以使用捕捉列表中,所有捕获到的变量。

注意: 在 lambda 表达式中,参数列表和返回值类型都可以省略不写,而捕捉列表和函数体可以为空,但不能省略不写。因此 C++11 中最简单的 lambda 表达式为:[]{}; 该 lambda 表达式不能做任何事情。

如果我们将 lambda 表达式写全,那便是如下的样子:

在这里插入图片描述

接下来,我们分别来看看,组成 lambda 表达式的这几个部分

参数列表 — ( )

示例:使用普通函数和 lambda 表达式,各自实现一个两数相加的功能:
在这里插入图片描述
可以看到,两者在使用上是没有区别的

有时,我们的 lambda 表达式不需要传入参数,此时连 ( ) 都可以省略掉:
在这里插入图片描述

捕捉列表 — [ ]

lambda 表达式和函数有一个区别是:函数先是在全局域中定义,然后到局部中去使用;而 lambda 表达式,它的定义和使用都在局部中,这个时候 lambda 表达式旁定然会存在着许多局部变量

int main()
{
	//函数体里有大量局部变量
	int a = 10;
	char c = 20;
 
	//lambda 表达式就定义在这些局部变量周围
	auto lambda_func = []() {};
}

为了方便获得他们,lambda 表达式里便引入了一个新的参数列表——捕捉列表,它专门用来捕捉所属局部域中的其它参数,比如:

int main()
{
	int a = 10;
	int b = 20;
	
	// 定义在 lambda 表达式之前的局部变量均可以捕捉到
	auto lambda_func = [a, b] {cout << a << ' ' << b << endl; };
	lambda_func();

	int c = 30; // 在 lambda 表达式之后定义,所以不能被捕捉到
	return 0;
}

------运行结果------
10 20

只需要在捕捉列表中输入在这个局部域中变量的名称,这个变量就能被捕捉了,可以在函数体中随意使用该变量,捕捉列表的本质其实也是传参,不要把它想的太神秘了。

但是,捕捉来的变量只是一个传值拷贝的局部变量,如果想要引用的方式捕捉一个变量,需要在待捕捉变量的前面加上取地址符号&:

// 引用捕捉 i,并修改它的值
auto lambda_func = [&i]() {i += 10;};

总结一下:捕捉列表描述了上文中那些数据可以被 lambda 使用,以及捕捉的方式是传值捕捉还是传引用捕捉。

  • [var]:表示以传值的方式捕捉变量 var
  • [&var]:表示以引用的方式捕捉变量 var
  • [=]:表示以传值的方式捕获上文父作用域中的所有变量(包括成员函数中的 this)
  • [&]:表示以引用的方式捕捉上文父作用域中的所有变量(包括成员函数中的 this)
  • [this]:以值传递的方式传递 this 指针

代码示例

class Lambda
{
public:
	void Func()
	{
		int i = 0;
 
		//单个值传递
		[i]{};
 
		//所有值传递
		[=]{};
 
		//单个引用传递
		[&i]{};
 
		//所有引用传递
		[&]{};
 
		//传递 this指针
		[this]{/*可以访问类中的_a*/ cout << _a << endl; };
	}
private:
	int _a;
};

同时,我们也可以传值和传引用一起搭配使用:

  • 捕捉列表可由多个捕捉项组成,并以逗号分割
    比如:[=, &a, &b] 代表以值传递方式捕捉除 a,b 以外的所有变量,以引用传递方式捕捉 a,b
  • 捕捉列表不能重复捕捉
    [=, a, b] 重复捕捉了 a,b,此时编译器会报错

捕捉参数的性质 — mutable

看下面例子,我们以值传递方式捕捉变量 a 之后,想在函数体中修改 a 的值,但是运行后报错了:
在这里插入图片描述

这是因为,在 lambda 表达式的捕捉列表中,传值捕捉到的值默认都是 const 的,我们无法直接对其进行修改 ,想要修改的话,需要使用 mutable 关键字来改变它们的常属性。
在这里插入图片描述

关于 mutable 使用的几点注意事项:

  • 使用该修饰符时,参数列表不可省略,即使没有参数也要写上 ()
  • 捕捉列表中传值捕捉到的对象默认都是 const 的,而传引用捕捉的对象是非 const 的
  • mutable 只针对传值捕捉的对象,对捕捉列表中的引用捕捉和参数列表中的形参没有任何影响。

->返回值

有人可能会好奇,为什么上面的例子都没有见到 -> 这个东西?

这是因为,C++ 抄 python 作业的时候,也借鉴它的弱类型语法的特点——当我们不指定返回值的类型时,编译器会自动推倒返回值的类型。所以,我们没有必要特意去定义返回值的类型,既然编译器会自动帮我们推导,那还要我们多笔干什么呢?

2. lambda 表达式的底层实现

每一个 lambda 表达式的类型名称都是不一样的,即使它们的定义是相同的:
在这里插入图片描述

所以,我们定义出来的 lambda 表达式是不能相互赋值的,因为其类型无法相互转换。但是,可以用一个已经定义好的 lambda 表达式去构造另一个新的 lambda 表达式:
在这里插入图片描述

下面我们调用一个 lambda 表达式,然后去汇编代码中看看它的底层到底是怎么调用的:
在这里插入图片描述

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

三. lambda 表达式的使用

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

使用 lambda 表达式实现一个两数交换的功能:

int main()
{
	// 标准写法
	int a = 10, b = 20;
	auto swap1 = [](int& x, int& y)->void 
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	
	// 错误写法(传值捕捉,实际两个数并没有真正的交换)
	auto swap2 = [a, b]()mutable
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	
	// 传引用捕捉写法
	auto swap3 = [&] //[&a, &b]
	{
		int tmp = a;
		a = b;
		b = tmp;
	};

	return 0;
}

在处理一些小功能的时候,lambda 表达式可以极大提高程序的可读性:

int main()
{
	vector<int> v = { 1,3,9,5,8,4,2 };
 
	// 最初写法:传仿函数对象,然后内部在调用 operator()
	std::sort(v.begin(), v.end(), greater<int>());
 
	// lambda写法:简化程序,增强可读性
	// 在底层也是调用 operator()
	std::sort(v.begin(), v.end(), [](int num1, int num2)->bool 
                                    {return num1 < num2; });
}
  • 25
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值