1、综述
lambda表达式是一个匿名函数,它可以在函数内部定义,编译器会将lambda表达式当做一个函数对象。lambda表达式的形式为:[捕获列表] (参数列表) -> 返回类型{ 函数体 },其中“捕获列表”是表达式所在函数中定义的局部变量的列表,可以为空,而且lambda必须使用尾置返回来指定函数返回类型,eg:
int main()
{
function<int(int)> func = [](int x)->int { return x * x; };
cout << func(5) << endl;
auto Func = [](int x)->int { return x * x; };
cout << Func(10) << endl;
int(*FuncPtr)(int) = Func;
cout << (*FuncPtr)(20) << endl;
return 0;
}
View Code
lambda表达式的函数返回类型也可以省略,这时候lambda根据函数体中return语句自动推断出返回类型,如果函数体中没有return语句的话则会认为返回类型为void,代码示例如下。有些情况下,lambda不能自动推断出返回类型,比如函数体内含有多个return语句(if(x) return i; else return -1;),这个时候编译会出错,所以最保险的是不省略返回类型。
function<int(int)> func = [](int x)/*->int*/{ return x * x; };
auto Func = [](int x){ return x * x; };
View Code
如果lambda表达式的没有参数的话还可以忽略参数列表,eg
function<int()> func = [] { return 100; };
cout << func() << endl;
auto Func = [] { return 100; };
cout << Func() << endl;
return 0;
View Code
“捕获列表”是lambda表达式所在函数中定义的局部变量的列表,它用来指出在表达式内部可以使用这些变量。“捕获列表”中的变量只能是局部变量,且不能static类型,对于static类型的局部变量,表达式内部可以直接使用,而且static类型变量是引用捕获。而且捕获的变量在lambda中不能改变其值,除非使用mutable声明该lambda(在参数列表所在的括号后加mutable,如[]()mutable{ ... } )。前面说过编译器会将lambda表达式当做一个函数对象,所以“捕获列表”中的变量相当于是将其保存在了函数对象中。eg:
int main()
{
int i = 10;
int j = 10;
static int k = 0;
auto Func = [i, j](int x)
{
k;
return x * i * j;
};
cout << Func(5) << endl; //输出500
return 0;
}
View Code
lambda表达式可以用在STL算法中,如下代码展示了sort()算法分别使用函数指针,函数对象,lambda表达式的示例:
bool myFunction(const int& i, const int& j)
{
return i > j;
}
class myClass
{
public:
bool operator() (const int& i, const int& j) { return i > j; }
};
int main()
{
vector<int> v = list_of(5) (2) (4) (3) (1) (5);
sort(v.begin(), v.end(), myFunction);
sort(v.begin(), v.end(), myClass());
sort(v.begin(), v.end(), [](const int& i, const int& j){ return i > j; });
for (auto iter = v.begin(); iter != v.end(); iter++)
cout << *iter << endl;
return 0;
}
View Code
2、再谈变量的捕获
“捕获列表”中的变量的捕获也可以是值捕获或引用捕获,如果是值捕获的话,传给捕获列表的变量在Lambda中实际上为变量的一个副本,而且被捕获的变量的值是在lambda表达式创建的时候就拷贝,而不是像函数参数的值传递那样在调用时拷贝,而且采用值捕获的变量如果会在函数体内修改的话还要使用mutable关键字来声明方法,eg:
int main()
{
int n = 10;
auto func = [n]()mutable{ return ++n;}; //lambda函数体内会修改n,所以应该加mutable关键字
n = 0;
auto num = func(); //n是lambda创建的时候拷贝而不是调用的时候拷贝,所以num是11而不是0
return 0;
}
View Code
如果是引用捕获的话在“捕获列表”中的捕获变量需要加&,eg:
int main()
{
int n = 10;
auto func = [&n](){ return ++n;}; //n是引用捕获
n = 0;
auto num = func(); //lambda调用的时候n为0,执行完lambda函数体后,num值为1,n为1
return 0;
}
View Code
我们也可以不传递“捕获列表”,让编译器根据lambda体中的变量使用代码来推断出哪些变量是捕获变量,即隐式捕获。为了指示由编译器推断捕获变量,应该在捕获列表中写一个=或&,=表示使用值引用方式,&表示采用引用捕获方式。如果我们希望对一部分变量采用值捕获,另一部分变量采用引用捕获,还可以混合使用隐式捕获和显示捕获。eg:
int main()
{
int n = 10;
auto func1 = [=]()mutable{ return ++n;}; //n是隐式值捕获
auto func2 = [&]() {return ++n;}; //n是隐式引用捕获
int i = 1, j = 2;
auto func3 = [=, &i, &j]() {return n * i * j;}; //n是隐式值捕获,i和j是显示引用捕获
auto func4 = [&, i, j]() {return n * i * j;}; //n是隐式引用捕获,i和j是显示值捕获
return 0;
}
View Code
可以在捕获列表中传入当前类的指针this,这样就可以在lamda中直接使用当前类的成员函数和成员变量:
class Foo
{
public:
Foo()
{
Bar* b = new Bar;
b->onClick = [this]{
cout << "onClick" << endl;
func();
};
}
void func(){}
};
class Bar
{
public:
std::function<void()> onClick;
};
View Code
捕获总结:
1)、[]不捕获任何变量。
2)、[bar]按值捕获bar变量。
3)、[&foo]按引用捕获foo变量。
4)、[=]捕获外部作用域中所有变量,并作为副本在函数体中使用,即按值捕获。
5)、[&]捕获外部作用域中所有变量,并作为引用在函数体中使用,即按引用捕获。
6)、[=,&foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量。
7)、[this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限,如果已经使用了&或者=,就默认添加此选项。
8)、捕获的变量默认在lambda中不能被修改,除非使用mutable声明该lambda方法(在参数列表括号后加mutable)。
3、lambda与函数
对于那些只在一两个地方使用的简单操作,可以直接使用lambda表达式。如果一个操作会被多个地方会使用,或者操作很复杂(包含多条语句),最好还是定义一个函数来使用。
lambda表达式捕获变量的功能与bind绑定参数有相似的效果。
对于子函数可以使用父函数中的局部变量这种行为,我们可以称做“闭包”,即“闭包”就是能够读取其他函数(父函数)中内部变量的函数(子函数),通常就是定义在一个函数内部的函数。可见lambda表达式和bind绑定参数都可以实现“闭包”。