自学内容网 自学内容网

c++标准11(3)——lambda表达式

欢迎来到博主的专栏:c++杂谈
博主ID:代码小豪

lambda表达式

c++中的可调用函数对象有两种,分别是函数指针和仿函数,c++11标准则新增了一个全新的可调用函数对象,称之为lambda表达式

一个lambda表达式可以定义在函数里面(包括main函数),与函数类似,lamba表达式有函数返回值,参数列表,以及函数体。lambda函数的形式如下:

[captrue_list](argsments)->return_type{function_body};

lambda表达式的形式可以说是颠覆性的了,如果让一个没接触过c++11的人来看,打死也不会相信这是一个c++语言写出来的代码。所以,博主以例子来讲讲lambda表达式的形式的规则。

int add(int x,int y)
{
return x + y;
}

这是一个很简单的加法函数,如果写成lambda表达式,那么按照上面的格式分析,首先captrue_list是写在方括号中捕获值,先不管捕获值是什么,反正这个add函数没有捕获值,因此我们可以省略方括号中的内容,arguments是指参数列表,即(int x,int y)。return_type是int类型。那么add函数的lambda形式就是:

[](int x, int y)->int {return x + y; };

lambda表达式肯定是一个表达式,表达式会产生一个值,所以说我们是可以接收这个lambda表达式的值的。但是这个lambda表达式的值是什么类型的呢?我们暂时不知道,好在c++11有一个auto关键字可以自动匹配类型。所以我们可以使用auto类型的对象来接收lambda表达式产生的值,这个对象称为可调用函数对象

auto add=[](int x, int y)->int {return x + y; };

这个add被叫做可调用函数对象,那么他肯定可以被调用,其调用结果就是将实参传入到lambda表达式当中作为形参,调用该lambda表达式。

auto add=[](int x, int y)->int {return x + y; };
int a = 10;
int b = 20;
cout<<add(a, b);//30

lambda表达式的使用场景

通过上面这个例子,lambda表达式好像有点鸡肋啊,写一个看起来奇奇怪怪的表达式,其实作用无非也就是一个函数,函数这东西从c语言就开始用,用了60多年了,那么这个lambda表达式到底是有什么过人之处吗?

当然了,比如当我们使用标准库中std::sort函数对序列式容器vector容器进行排序

template <class RandomAccessIterator, class Compare>
  void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

在没有lambda表达式之前,为了控制比较大小的方式,通常需要我们传入一个comp仿函数来控制排序的结果,虽然标准库中也存在std::greater,std::less等仿函数,但是这两个仿函数并非能对所有类都起到作用,特别是对于自定义类型,为了写出正确的比较逻辑,通常都是要我们自己去写仿函数的。

以一个自定义类person为例:

struct person
{
person(string name,string ID,int age)
:_name(name)
,_ID(ID)
,_age(age)
{}
string _name;
string _ID;
int _age;
};

vector<person> _class{ {"LY","0102",21}, { "小明","0118",19 }, { "小红","0101",20 } };//班级名单

比如上面的_class 就是一个班级名单,现在我们要对该名单进行排序,排序的方式有三种,分别为
(1)按姓名排序
(2)按座号排序
(3)按年龄排序
此时老师要求我们把三种方式都写出来,那么通常我们就要写出多种排序逻辑的仿函数。

struct CompNameGreater//按姓名排降序
{
bool operator()(const person& p1, const person& p2)
{
return p1._name > p2._name;
}
};

struct CompNameLess//按姓名排升序
{
bool operator()(const person& p1, const person& p2)
{
return p1._name < p2._name;
}
};

struct CompAgeGreater//按年龄排降序
{
bool operator()(const person& p1, const person& p2)
{
return p1._age > p2._age;
}
};

struct CompAgeLess//按年龄排升序
{
bool operator()(const person& p1, const person& p2)
{
return p1._age < p2._age;
}
};

struct CompIDGreater//按学号排降序
{
bool operator()(const person& p1, const person& p2)
{
return p1._ID > p2._ID;
}
};

struct CompIDLess//按学号排升序
{
bool operator()(const person& p1, const person& p2)
{
return p1._ID < p2._ID;
}
};

然后如此调用std::sort函数

sort(_class.begin(), _class.end(), CompNameGreater());//按姓名排降序
sort(_class.begin(), _class.end(), CompNameLess());//按姓名排升序
sort(_class.begin(), _class.end(), CompIDGreater());//按学号排降序
sort(_class.begin(), _class.end(), CompIDLess());//按学号排升序
sort(_class.begin(), _class.end(), CompAgeGreater());//按年龄排降序
sort(_class.begin(), _class.end(), CompAgeLess());//按年龄排升序

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

如果我们使用lambda表达式,那么上面的程序就能写成这样:

sort(_class.begin(), _class.end(), 
[](const person& p1, const person& p2)->bool 
{return p1._name > p2._name; });//按姓名排降序
sort(_class.begin(), _class.end(), 
[](const person& p1,const person&p2)->bool
{return p1._name < p2._name; });//按姓名排升序
sort(_class.begin(), _class.end(), 
[](const person& p1, const person& p2)
{return p1._ID > p2._ID;});//按学号排降序
sort(_class.begin(), _class.end(),
[](const person& p1,const person& p2)->bool
{return p1._ID < p2._ID; });//按学号排升序
sort(_class.begin(), _class.end(), 
[](const person& p1, const person& p2)->bool
{return p1._age > p2._age;});//按年龄排降序
sort(_class.begin(), _class.end(), 
[](const person& p1, const person& p2)
{return p1._age < p2._age; });//按年龄排升序

这样做最大的好处在于,函数的逻辑更直观的表现出来了,看代码的人不用去记那些冗长的函数名具体的逻辑是什么,使用lambda函数可以将这种简单逻辑(比如比较)的函数直接写出来,如果是复杂逻辑的代码还是老老实实的用函数比较好。

lambda表达式语法

[captrue_list]——捕获列表
捕获列表中的值称为捕获值,捕获列表可以捕获全局变量或者lambda表达式所在函数的局部变量(如果不捕获就为空),比如我们可以写出如下的一个加法函数:

int x = 10;
int y = 20;
auto add = [x, y]()->int {return x + y; };

cout << add();//30

在本例中,lambda表达式捕获了变量x和y的值。并且返回x+y的结果。这种仅捕获变量的捕获方式称为值捕获,注意在值捕获的lambda表达式中不能对捕获值进行修改。如果想要在lambda表达式中对捕获值进行修改,就要在lambda表达式的参数列表后面加上mutable关键字。

int x = 10;
int y = 20;
auto add = [x, y]()mutable->int {
x = 20;
return x + y; 
};

cout << add();//40

在值捕获下的lambda表达式中的捕获参数都是默认是const修饰的常量,加上mutable就相当于去掉了这些const修饰,但是对于lambda表达式来说,这些捕获值的本质任然是原值的一份临时拷贝,即在lambda表达式中对捕获值进行修改,不会改变源对象。

如果想要在lambda表达式中对捕获的源对象进行修改,就要使用引用捕获的方式(就和函数使用传引用的方式一样。),在捕获值之前加上(&)修饰,表示这个捕获值是源对象的引用,而非拷贝,从而对捕获值的修改会影响源对象。

int x = 10;
int y = 20;
auto add = [&x, &y]()->int {
x = 20;
y = 30;
return x + y; 
};

cout << add()<<endl;//50
cout << x << " " <<y<< endl;//20 30

如果想要捕获的对象太多,嫌写起来费劲,可以使用全值捕获的方式,即将所有的全局变量,以及当前的局部变量都捕获进来。[=]:表示值传递方式捕获所有父作用域中的变量(包括this).

int x = 10;
int y = 20;
auto add = [=]()->int {
return x + y; 
};

cout << add()<<endl;//30
cout << x << " " <<y<< endl;//10 20

其捕获的值遵从值捕获的特性。即默认无法修改,加上mutable关键字可以修改,修改捕获值不会影响源对象。

与其对应的,当然也就有全引用捕获。
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)

int x = 10;
int y = 20;
auto add = [&]()->int {
x = 20;
y = 30;
return x + y; 
};

cout << add()<<endl;//50
cout << x << " " <<y<< endl;//20 30

其捕获值的规则遵从引用捕获的特性,即可以修改捕获值,对捕获值的修改会对源对象造成影响。


原文地址:https://blog.csdn.net/2301_77239666/article/details/141903034

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!