Lambda表达式
lambda表达式(也称为lambda函数)是在调用或作为函数参数传递的位置处定义匿名函数对象的便捷方法。通常,lambda用于封装传递给算法或异步方法的几行代码 。
Lambda表达式
[外部变量访问方式说明符] (参数表) -> 返回值类型
{
语句块
}
其中,“外部变量访问方式说明符”
可以是=
或&
,表示{}
中用到的、定义在{}
外面的变量在{}
中是否允许被改变。=
表示不允许,&
表示允许。当然,在{}
中也可以不使用定义在外面的变量。“-> 返回值类型”
可以省略。
[ capture list ] ( parameter list) -> return type { function body }
capture list,捕获列表,是一个lambda所在函数中定义的局部变量的列表。lambda函数体中可以使用这些局部变量。捕获可以分为按值捕获和按引用捕获。非局部变量,如静态变量、全局变量等可以不经捕获,直接使用。
捕捉列表总是作为lambda的开始,即出现于lambda的开始处。它是lambda的引出符(即开始标志)。编译器可以根据该“标志”来作出判断出该函数是否为lambda函数。同时“捕捉列表”能够捕捉上下文中的变量以作为lambda函数使用。
parameter list,参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。所传参数和捕捉参数不一样,不自带const,可以修改从C++14开始,支持默认参数,并且参数列表中如果使用auto的话,该lambda称为泛化lambda(generic lambda)。
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空时,也要带上小括号)。mutable 只是让传值捕捉变量const属性去掉了,但是捕捉的a,b仍是拷贝,外部的a,b无法被修改,所以mutable 很少用,意义不大
return type,返回类型,这里使用了返回值类型尾序语法(trailing return type synax)。可以省略,这种情况下根据lambda函数体中的return语句推断出返回类型,就像普通函数使用decltype(auto)推导返回值类型一样;如果函数体中没有return,则返回类型为void。
function body,与任何普通函数一样,表示函数体。
注意:在lambda函数定义中,参数列表和返回值类型都是可省略部分,而捕捉列表和函数体可以为空,但不可省略,因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
auto f = []{return 42;}
cout << f() << endl;
上面的lambda表达式,定义了一个可调用对象f,它不接受参数,返回42。Lambda的调用方式与普通函数的调用方式相同。
int main()
{
int a = 0, b = 1;
auto Add1 = [](int x, int y)->int{return x+y;}; //完整写法
auto Add2 = [](int x, int y){return x+y;}; //省略写法
std::cout << Add1(a,b) << endl;
std::cout << Add2(a,b) << endl;
return 0;
}
捕获列表
[]:什么也不捕获
[=]:捕获所有一切变量,都按值捕获
[&]:捕获所有一切变量,都按引用捕获
[=, &a]:捕获所有一切变量,除了a按引用捕获,其余都按值捕获
[&, =a]:捕获所有一切变量,除了a按值捕获,其余都按引用捕获
[a]:只按值捕获a
[&a]:只按引用捕获a
[a, &b]:按值捕获a,按引用捕获b
[this] 表示值传递方式捕捉当前的this指针
[=]拷贝捕获
表示以值传递的方式捕获所有捕获范围内的对象(隐式捕获)
#include <iostream>
using namespace std;
int main()
{
int i = 1024;
auto func = [=] { // [=] 表明将外部的所有变量拷贝一份到该函数内部
cout << i << endl;
};
/* 错误写法
auto func2 = [] { //未指名捕获, i 不存在 运行会报错
cout << i << endl;
};
*/
func();
return 0;
}
[&]引用捕获
表示以地址传递的方式捕获所有捕获范围内的对象(隐式捕获)
#include <iostream>
using namespace std;
int main()
{
int i = 1024;
cout << &i << endl;
auto pfun = [&] { //
i = 1023;
cout << i << endl;
cout << &i << endl;
};
pfun();
return 0;
}
[&a]只按引用捕获a
按引用捕获可以让lambda表达式能够访问引用变量,这种捕获方式在编译期间不会把变量的值复制到lambda的局部作用域,而是直接对这个引用所指的变量进行操作。
#include <iostream>
using namespace std;
int main() {
int v1 = 1;
int v3 = 3;
cout << &v1 << endl;
auto f = [&v1] {
cout << &v1 << endl;
cout << v1 << endl;
};
f();
v1 = 2;
cout << v3 << endl;
return 0;
}
因为这是按引用捕获,lambda在访问时也将直接访问这个引用,也就是函数内的&v1,因此即使在lambda被创建之后v1的值有所改变,lambda也依旧是可以直接访问这个引用变量。
#include <iostream>
using namespace std;
int main() {
int v1 = 1;
int v3 = 3;
auto f = [&v1] { cout << v1 << endl; };
v1 = 2;
f();
cout << v3 << endl;
return 0;
}
值得一提的是,按引用捕获在实际应用中是有风险的,主要原因是lambda表达式在被创建后到被执行前这段期间里,这个引用有可能会变得不复存在了,这时候就很难保证这个引用变量在执行的时候一定存在。所以除非我们能确保它在lambda执行的时候是一定存在的,否则应该尽量避免用按引用捕获。
既然按引用捕获有风险,那它有没有存在的必要呢?也有,比如当我们想捕获的局部作用域变量是一个不可复制的变量的时候,就只能通过引用的方式来捕获了。以下的例子中,我们想让lambda可以访问ostream对象,但是它是不可复制的,于是我们将其引用给lambda:
#include <iostream>
using namespace std;
int main() {
ostream& os = cout;
auto f = [&os] { os << "hello"; };
f();
os << endl;
return 0;
}
但如果我们尝试将os按值捕获,则在编译时报错:
[this] 捕获 this 指针
可以使用this类型的成员变量和成员函数。如下所示,在test内的lambda捕获this后,可以使用class A的公有成员函数print和私有成员函数x。
#include <iostream>
class A
{
public:
void print() {
std::cout << "class A\n";
}
void test()
{
auto fun = [this] {
print(); //也可以this->print()
x = 5; //也可以this->x
std::cout << x << std::endl;
};
fun();
}
private:
int x;
};
int main()
{
A t;
t.test();
return 0;
}
lambda 表达式与function
lambda表达式的参数列表,即(…)部分。和我们使用普通函数的参数列表一样。
#include <iostream>
using namespace std;
int main()
{
int x = 3;
auto pfun = [](int x) {
cout << "x:" << x << endl;
};
pfun(x);
return 0;
}
捕获列表在lambda表达式定义时即发生捕获;而参数列表是在调用lambda表达式时才会传参。
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
int val = 1;
//定义lambda表达式
auto fun = [val](int c)
{
cout << "inner:" << val << " " << c << endl;
};
val = 11; //不会改变inner的val的值,因为在此之前已经捕获完成, 是值传递
fun(2); //调用
system("pause");
return 0;
}
mutable
mutable用来表示捕获列表中的对象可被改写。如果一个匿名函数声明为mutable,则在函数体中可以改写传值方式捕获的变量(传址方式捕获的变量不受mutable限制,是可以修改的。)
如果传值进匿名函数的变量只能使用,而不能修改。如下语句会报编译错误
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
int val = 1;
//定义lambda表达式
auto fun = [val](int c)
{
val = 12;
cout << "inner:" << val << " " << c << endl;
};
val = 11; //不会改变inner的val的值,因为在此之前已经捕获完成, 是值传递
fun(2); //调用
system("pause");
return 0;
}
但是用mutable声明过的匿名函数可以修改捕获的变量。如下所示:
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
int val = 1;
//定义lambda表达式
auto fun = [val](int c)mutable
{
val = 12;
cout << "inner:" << val << " " << c << endl;
};
val = 11; //不会改变inner的val的值,因为在此之前已经捕获完成, 是值传递
fun(2); //调用
system("pause");
return 0;
}
retType
对于返回值的使同普通函数,如下示例代码
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
//定义lambda表达式
auto fun = []()->string
{
string str = "hello world";
return str;
};
cout << fun() << endl;
return 0;
}