lambda函数是C++11标准新增的语法糖,也称为lambda表达式或匿名函数。
lambda函数的特点是:距离近、简洁、高效和功能强大。
示例: [] ( const int& no ) -> void { cout << "我是第" << no << "名。\n"; };
语法:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//函数
void show1(const int& no){
cout<<"我给"<<no<<"号来一拳。\n";
}
//仿函数
class show2{
public:
void operator()(const int& no){
cout<<"我也给"<<no<<"号来一拳。\n";
}
};
int main(){
vector<int> vv={4,3,5};//存放挨打者编号的容器
//第三个参数是普通函数
for_each(vv.begin(),vv.end(),show1);
//第三个参数是仿函数
for_each(vv.begin(),vv.end(),show2());
//第三个参数是LAMBDA表达式。
/*for_each(vv.begin(),vv.end(),
[](const int& no){
cout<<"我还给"<<no<<"号来一拳。\n";
}
); */
auto f=[](const int& no)->double{
cout<<"我还给"<<no<<"号来一拳。\n";
return 1.5;
};
for_each(vv.begin(),vv.end(),f);
//也可以像调用普通函数一样,调用Lambda表达式
f(222);
}
一、参数列表
参数列表是可选的,类似普通函数的参数列表,如果没有参数列表,()可以省略不写
与普通函数的不同:
-
lambda函数不能有默认参数
-
所有参数必须有参数名
-
不支持可变参数
二、返回类型
用后置的方法书写返回类型,类似于普通函数的返回类型,如果不写返回类型,编译器会根据函数体中的代码推断出来。
如果有返回类型,建议显式的指定,自动推断可能与预期不一致。
三、函数体
类似于普通函数的函数体
四、捕获列表
通过捕获列表,lambda函数可以访问父作用域中的非静态局部变量(静态局部变量可以直接访问,不能访问全局变量)。
捕获列表书写在[]中,与函数参数的传递类似,捕获方式可以是值和引用。
以下列出了不同的捕获列表的方式。
1)值捕获
与传递参数类似,采用值捕获的前提是变量可以拷贝。
与传递参数不同,变量的值是在lambda函数创建时拷贝,而不是调用时拷贝。
例如:
size_t v1 =42;
auto f = [v1] { return v1; }; // 使用了值捕获,将v1拷贝到名为f的可调用对象。
v1 = 0;
auto j = f(); // j为42,f保存了我们创建它是v1的拷贝。
由于被捕获的值是在lambda函数创建时拷贝,因此在随后对其修改不会影响到lambda内部的值。
默认情况下,如果以传值方式捕获变量,则在lambda函数中不能修改变量的值。
2)引用捕获
和函数引用参数一样,引用变量的值在lambda函数体中改变时,将影响被引用的对象。
size_t v1 =42;
auto f = [&v1]{ return v1; };// 引用捕获,将v1拷贝到名为f的可调用对象。
v1 = 0;
auto j = f(); // j为0。
如果采用引用方式捕获变量,就必须保证被引用的对象在lambda执行的时候是存在的。
3)隐式捕获
除了显式列出我们希望使用的父作域的变量之外,还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。
隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。
int a = 123;
auto f = [=] { cout << a << endl; }; //值捕获
f(); // 输出:123
auto f1 = [&] { cout << a++ << endl; }; //引用捕获
f1(); //输出:123(采用了后++)
cout << a<< endl; //输出 124
4)混合方式捕获
lambda函数还支持混合方式捕获,即同时使用显式捕获和隐式捕获。
混合捕获时,捕获列表中的第一个元素必须是 = 或 &,此符号指定了默认捕获的方式是值捕获或引用捕获。
需要注意的是:显式捕获的变量必须使用和默认捕获不同的方式捕获。例如:
int a = 123;
auto f = [=] { cout << a << endl; }; //值捕获
f(); // 输出:123
auto f1 = [&] { cout << a++ << endl; }; //引用捕获
f1(); //输出:123(采用了后++)
cout << a<< endl; //输出 124
5)修改值捕获变量的值
在lambda函数中,如果以传值方式捕获变量,则函数体中不能修改该变量,否则会引发编译错误。
在lambda函数中,如果希望修改值捕获变量的值,可以加mutable选项,但是,在lambda函数的外部,变量的值不会被修改。
int a = 123;
auto f =[a]()mutable { cout << ++a << endl; }; // 不会报错
cout << a<< endl; // 输出:123
f(); // 输出:124
cout << a<< endl; // 输出:123
6)异常说明
lambda可以抛出异常,用throw(…)指示异常的类型,用noexcept指示不抛出任何异常。
五、lambda函数本质
当我们编写了一个lambda函数之后,编译器将它翻译成一个类,该类中有一个重载了()的函数。
1)采用值捕获
采用值捕获时,lambda函数生成的类用捕获变量的值初始化自己的成员变量。
int a =10;
int b = 20;
auto addfun = [=] (const int c ) -> int { return a+c; };
int c = addfun(b);
cout << c << endl;
等同于:
class Myclass
{
int m_a; // 该成员变量对应通过值捕获的变量。
public:
Myclass( int a ) : m_a(a){}; // 该形参对应捕获的变量。
// 重载了()运算符的函数,返回类型、形参和函数体都与lambda函数一致。
int operator()(const int c) const
{
return a + c;
}
};
默认情况下,有lambda函数生成的类是const成员函数,所以变量的值不能修改。如果加上mutable,相当于去掉const。这样上面的限制就能讲通了。
2)采用引用捕获
如果lambda函数采用引用捕获的方式,编译器直接引用就行了。
唯一需要注意的是,lambda函数执行时,程序必须保证引用的对象有效。
六、Lambda表达式底层原理
实际就是编译器在底层对于Lambda表达式的处理方式,完全就是按照函数对象的方式处理的,就是对()进行了重载:
class Rate
{
private:
public:
Rate(double rate)
:_rate(rate)
{}
double operator()(double money, int year)
{
return money * _rate * year;
}
double _rate;
};
int main()
{
double rate = 0.49;
Rate r1(rate);
r1(10000, 2);
auto r2 = [=](double money, int year)->
double {return money * rate * year; };
r2(10000, 2);
return 0;
}
当创建Rate对象的时候是构造函数,使用Rate对象的时候就是会调用 Rate 类的 operator() 运算符重载函数;
而Lambda 表达式实际也是这样子的:会调用 < lambda_uuid > 类的构造函数,在使用Rate r2对象时,会调用< lambda_uuid >类的 operator()运算符重载函数。
lambda表达式和范围for是类似的,它们在语法层面上看起来都很神奇,但实际范围for底层就是通过迭代器实现的,lambda表达式底层的处理方式和函数对象是一样的。我们定义一个lambda表达式后,编译器会自动生成一个类,在该类中对operator () 运算符进行重载.
注意:lambda表达式之间不能赋值,每个lambda表达式的类型都是不同的(在VS下,lambda表达式会被处理为函数对象,该函数对象对应的类名叫做< lambda_uuid >。),这也是lambda表达式之间不能相互赋值的原因
int main()
{
int x = 10, y = 20;
auto Swap1 = [&x,&y]()
{
int tmp = x;
x = y;
y = tmp;
};
auto Swap2 = [&x,&y]()
{
int tmp = x;
x = y;
y = tmp;
};
cout << typeid(Swap1).name() << endl;
cout << typeid(Swap2).name() << endl;
return 0;
}