标题:[C++] C++11详解 (四)lambda表达式
@水墨不写bug
目录
一、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表达式书写格式:[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将其赋值给一个变量。
函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的类对象。
从使用方式上来看,函数对象与lambda表达式完全一样;
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载operator()。
完~
未经作者同意禁止转载