[C++] C++11详解 (四)lambda表达式

标题:[C++] C++11详解 (四)lambda表达式

@水墨不写bug


[C++] C++11详解 (四)lambda表达式_第1张图片


目录

一、lambda表达式

 lambda表达式语法

lambda表达式与仿函数关系


正文开始:

一、lambda表达式

        作为C++学习者,你一定对algorithm中的sort函数十分熟悉,sort函数默认可以对自定义类型的数据按照升序排序。在实际生活中,我们常常遇到的场景是需要对自定义类型对象排序。

        如何对自定义类型排序?其实就是按照某一个规则,比如是这种类型的某一成员变量的大小来决定大小顺序。如果想要实现这样的功能,我们首先想到的是对比较运算符“==,>,<”重载。

#include
using namespace std;
class STUDENT
{
public:
	STUDENT(const string& name = "",const string& id = "",double score = 0)
		: _name(name)
		, _id(id)
		,_score(score)
	{}
	bool operator>(const STUDENT& s)
	{
		return _score > s._score;
	}
	bool operator<(const STUDENT& s)
	{
		return _score < s._score;
	}
	bool operator==(const STUDENT& s)
	{
		return _score == s._score;
	}
private:
	string _name;
	string _id;
	double _score;
};
int main()
{
	STUDENT s1("zhangsan","23134",90);
	STUDENT s2("lisi", "23135", 89);
	if (s1 > s2)
		cout << "s1>s2";
	return 0;
}
s1>s2

        但是这样写的不足是代码写死了,只能按照score比较,如果想要按照id排序,就还要修改源码,十分不方便。

        其次,你可能还会想到这样写,用函数对象,仿函数来实现比较:

#include
#include
#include
using namespace std;
struct STUDENT
{
public:
	STUDENT(const string& name = "",const string& id = "",double score = 0)
		: _name(name)
		, _id(id)
		,_score(score)
	{}
	
	string _name;
	string _id;
	double _score;
};
struct scoreLESS
{
	bool operator()(const STUDENT& s1,const STUDENT& s2)
	{
		return s1._score < s2._score;
	}
};
struct scoreGREATER
{
	bool operator()(const STUDENT& s1, const STUDENT& s2)
	{
		return s1._score > s2._score;
	}
};

int main()
{
	vector vs = { {"zhangsan","123",80},{"lisi","231",90},{"wangwu","213",100}};
	sort(vs.begin(), vs.end(),scoreGREATER());
	for (int i = 0; i < vs.size(); ++i)
	{
		cout << vs[i]._name << " " << vs[i]._id << " 分数" << vs[i]._score<
wangwu 213 分数100
lisi 231 分数90
zhangsan 123 分数80

        但是,上面仿函数需要写很多,并且命名多又杂,为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。
 

        如果我们使用lambda表达式来实现上述的对分数的排序,如下所示:

#include
#include
#include
using namespace std;
struct STUDENT
{
public:
	STUDENT(const string& name = "",const string& id = "",double score = 0)
		: _name(name)
		, _id(id)
		,_score(score)
	{}
	
	string _name;
	string _id;
	double _score;
};
void Print(const vector& vs)
{
	for (int i = 0; i < vs.size(); ++i)
	{
		cout << vs[i]._name << " " << vs[i]._id << " 分数" << vs[i]._score << endl;
	}
	cout << "___________________________"< vs = { {"zhangsan","123",98},{"lisi","231",90},{"wangwu","913",100}};
	
	//分数升序
	sort(vs.begin(), vs.end(),[](const STUDENT& s1,const STUDENT& s2) ->bool{
		return s1._score < s2._score;
	});
	Print(vs);

	//分数降序
	sort(vs.begin(), vs.end(), [](const STUDENT& s1, const STUDENT& s2) ->bool {
		return s1._score > s2._score;
		});
	Print(vs);

	//学号升序
	sort(vs.begin(), vs.end(), [](const STUDENT& s1, const STUDENT& s2) ->bool {
		return s1._id < s2._id;
		});
	Print(vs);

	//学号降序
	sort(vs.begin(), vs.end(), [](const STUDENT& s1, const STUDENT& s2) ->bool {
		return s1._id > s2._id;
		});
	Print(vs);
	return 0;
}
lisi 231 分数90
zhangsan 123 分数98
wangwu 913 分数100
___________________________
wangwu 913 分数100
zhangsan 123 分数98
lisi 231 分数90
___________________________
zhangsan 123 分数98
lisi 231 分数90
wangwu 913 分数100
___________________________
wangwu 913 分数100
lisi 231 分数90
zhangsan 123 分数98
___________________________

 lambda表达式语法

        lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement}


lambda表达式各部分说明:


        [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
        (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
        mutable:(捕捉的变量是否可变)默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。

        ->return type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
        {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:
        在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数(lambda表达式实际是一个匿名函数)为:[]{}; 该lambda函数不能做任何事情。


接下来,我们分别逐个讲解:

        1.捕获列表:

    捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用:
                [var]:表示值传递方式捕捉变量var;
                [=]:表示值传递方式捕获父作用域中所有的变量(包括this);
                [&var]:表示引用传递捕捉变量var;
                [&]:表示引用传递捕捉父作用域中的所有变量(包括this);
                [this]:表示值传递方式捕捉当前的this指针;

        注意:

        i,父作用域是指包含这个lambda的语句块;

        ii,语法上,捕捉列表可以有多个捕捉项,捕捉项之间用“,”分割。

             比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
                        [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

        iii,捕捉列表不允许变量重复传递,否则就会导致编译错误。

        比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复(需要与ii区分,ii中的捕捉项之间没有交集,不是重复传递)

        iv,在块作用域以外的lambda函数捕捉列表必须为空。

        v,在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
        vi,lambda表达式之间不能相互赋值,即使看起来类型相同。

 


        2.参数列表:

        lambda表达式类似于一个函数,这里的参数列表就类似于函数的参数列表,在调用的时候传递的参数的类型,个数等。

        3.mutable:

        其实mutable具有误导性,lambda是一个匿名函数,如果传值捕捉,lambda内的参数实际上是一份拷贝,类似于传值调用,就类似于函数的形参变化不会影响实参:


#include
using namespace std;
int main()
{
	int a = 1, b = 2;
	cout << a << " " << b << endl;

	auto swap = [a, b]()mutable {
		int tem = a;
		a = b;
		b = tem;
		};
	swap();
	cout << a << " " << b << endl;
	return 0;
}
1 2
1 2

如果想要改变实参,就需要传引用,而穿引用就不需要加mutable了:

#include
using namespace std;
int main()
{
	int a = 1, b = 2;
	cout << a << " " << b << endl;

	auto swap = [&a, &b]() {
		int tem = a;
		a = b;
		b = tem;
		};
	swap();
	cout << a << " " << b << endl;
	return 0;
}
1 2
2 1

        换个角度想想,lambda设置为const函数,是有道理的,为的是防止我们误解:传值的时候把形参和实参错误联系起来。

        4.返回值类型:

        若为void,可以省略;若可以一眼看出,也可以不写,但是一般除了void,都要手动加上返回值类型。

        5.函数体:

        与函数相同。

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

lambda表达式与仿函数关系

        函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象
        从使用方式上来看,函数对象与lambda表达式完全一样;
        实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载operator()。


完~

未经作者同意禁止转载 

你可能感兴趣的:(Cpp,c++,开发语言)