C++中的lambda表达式

在这里插入图片描述

引入:
首先来看一个例子

struct fruit
{
	double _price;
	int _evalute;
	string _name;
	fruit(const char* str, int a, double price)
		:_name(str)
		,_evalute(a)
		,_price(price)
	{

	}
};
struct ComparePriceGreater
{
	bool operator()(const fruit& g1, const fruit& gr)
	{
		return g1._price > gr._price;
	}
};
struct ComparePriceLess
{
	bool operator()(const fruit& g1, const fruit& gr)
	{
		return g1._price < gr._price;
	}
};

int main()
{
	vector<fruit> v = { {"苹果",3,2.5},{"香蕉",2,3.5},{"梨子",5,5.5} };
	sort(v.begin(), v.end(), ComparePriceGreater());//不知道怎么去比较,所以我们要传入一个仿函数
	sort(v.begin(), v.end(), ComparePriceLess());
	return 0;
}

在这里插入图片描述

 如图,我们知道,std::sort函数在排序时,如果是内置类型,就默认是升序排列,但是如果是自定义类型呢?在lambda表达式引入之前,我们通常是写一个仿函数,然后重载(),从而达到排序的效果。
 但是,每次为了实现这样一个算法,就都要去重新写出一个类。特别是相同类的命名问题,都给我们带来了很大的不方便。
 lambda表达式是在C++11引入的语法,一般用于定义匿名函数,使代码更加灵活方便。
lambda表达式的格式

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

各个部分的说明

  • capture-list:捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量提供给lambda表达式使用。

  • parameters:参数列表,与普通的函数很是相似,如果不需要传递参数,直接省略即可,甚至可以连()一起省略。

  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表括号不能省略。

  • returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。前边的->不可以忽略。

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

 如上边所说,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
捕捉列表说明
 捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
 [var]:表示值传递方式捕捉变量var
在这里插入图片描述

 [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
在这里插入图片描述

 [&var]:表示引用传递捕捉变量var
在这里插入图片描述

 [&]:表示引用传递捕捉所有父作用域中的变量(包括this)。
在这里插入图片描述

 [this]:表示值传递方式捕捉当前的this指针,上述例子中=改为this,大家可以尝试一下。
注意事项
a. 父作用域指包含lambda函数的语句块,即包含该lambda表达式的大括号中。

b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
在这里插入图片描述

 当然可以不同类型的捕捉。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量。[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复。
在这里插入图片描述

d. 在块作用域以外的lambda函数捕捉列表必须为空。
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
f. lambda表达式之间不能相互赋值,即使看起来类型相同。
使用lambda表达式改进上边的代码

struct fruit
{
	double _price;
	int _evalute;
	string _name;
	fruit(const char* str, int a, double price)
		:_name(str)
		,_evalute(a)
		,_price(price)
	{

	}
};
int main()
{
	vector<fruit> v = { {"苹果",3,2.5},{"香蕉",2,3.5},{"梨子",5,5.5} };
	sort(v.begin(), v.end(), [](const fruit& f1, const fruit& f2) {return f1._price > f2._price; });
	sort(v.begin(), v.end(), [](const fruit& f1, const fruit& f2) {return f1._price < f2._price; });
	return 0;
}

通过上述代码,我们可以很清晰的看出代码更加简洁,没有那么冗余了。
仿函数和lambda表达式
 仿函数就是重载()运算符的对象,lambda表达式和仿函数功能很是相似,都可以作为仿函数表达式进行传递,那么他们的底层都是如何运转的呢?
我们来细细探究探究
 其实我们可以将lambda当做一个匿名函数对象
在这里插入图片描述
转到反汇编来观察是如何实现的。
在这里插入图片描述
 可以看出,lambda实际上会作为一个匿名函数对象进行传递,函数名省略,函数参数和函数体和仿函数相同。就像范围for一样,看着感觉会很复杂,其实底层还是使用迭代器进行遍历,一切为了方便而已。
我们还可以利用typeid来查看函数类型。
在这里插入图片描述

编译器是如何看lambda呢?其实就像仿函数一样。上边观察在2017年VS下更加直观,会使用UUid为每一个lambda添加一个唯一标识符。

class LambdaClass
{
public:
    int operator () (int a, int b) const
    {
        return a + b;
    }
};

LambdaClass plus;
int c = plus(1, 2);

 lambda表达式会把主要的部分提取出来,让我们的代码编写更加简便。
 那么捕获列表中捕获的变量呢?其实就是作为类的成员变量,如果是值拷贝,那么我们重载的()函数就是const函数的,是无法修改捕获的值的。

class LambdaClass
{
public:
    LambdaClass(int xx, int yy)
    : x(xx), y(yy) {}

    int operator () (int a, int b) const
    {
        return x + y + a + b;
    }

private:
    int x;
    int y;
}

int x = 1; int y = 2;
LambdaClass plus(x, y);
int c = plus(1, 2);

 如果我们想要修改捕获的值呢?如改变上边的x,只需要加上关键字mutable。加上mutable后,在lambda中可以修改捕捉到的数据,但是不会影响外界变量的值。

int x = 1; int y = 2;
auto plus = [=] (int a, int b) mutable -> int { x++; return x + y + a + b; };
int c = plus(1, 2);

如果是值引用呢?
我们只需要进行引用捕获即可。

class LambdaClass
{
public:
    LambdaClass(int& xx, int& yy)
    : x(xx), y(yy) {}

    int operator () (int a, int b)
    {
        x++;
        return x + y + a + b;
    }

private:
    int &x;
    int &y;
};

 此时在函数中改变传入的值就可以改变捕获的值。
在这里插入图片描述

 对应仿函数,我们可以最后再把lambda表达式和仿函数类的各个成分对引起来就是如下关系。

捕获列表,对应LambdaClass类的private成员。
参数列表,对应LambdaClass类的成员函数的operator()的形参列表
mutable,对应 LambdaClass类成员函数 operator() 的const属性 ,但是只有在捕获列表捕获的参数不含有引用捕获的情况下才会生效,因为捕获列表只要包含引用捕获,那operator()函数就一定是非const函数。
返回类型,对应 LambdaClass类成员函数 operator() 的返回类型
函数体,对应 LambdaClass类成员函数 operator() 的函数体。

注意:引用捕获和值捕获不同的一点就是,对应的成员是否为引用类型。
本文结束,如果有问题还请及时提出,我会虚心改正,感谢大家的观看。

  • 27
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

we will rise.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值