一、简介
lambda表达式又叫匿名函数。是现代C++的一种语法糖,并且仍在持续更新。
lambda表达式是在调用或作为参数传递的函数的位置处定义匿名函数对象的便捷方法。
lambda表达式的一般 形式如下
[...](...) mutable throwSpec ->retType {...}
如果使用上述的方式定义一个匿名函数,则此匿名函数只有一次调用机会,即在定义的时候就进行了调用:
[...](...) mutable throwSpec ->retType {...}();
但是一般情况下,我们可以使用一个auto 类型变量来接受这样一个匿名函数对象,这样我们可以在更合适的位置进行调用,也可以进行多次调用,更加方便。见如下形式
auto fun = [...](...) mutable throwSpec ->retType {...} //定义
fun(); //调用
下面简单介绍下各部分的意义,然后下面章节进行详细解说:
[...] 捕获列表
(...) 参数列表(如果mutable throwSpec retType 都不需要,且没有参数传入,则可省略)
mutable 捕获列表中(捕获进来)的对象可被改写(可选)
throwSpec 是否可以丢出异常(可选)
retType 返回值类型(可选)
二、捕获列表
Lambda表达式与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以被Lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在“[]”包括起来的是捕获列表,捕获列表由多个捕获项组成,并以逗号分隔。
1、lambda表达式的访问,和捕获列表的捕获范围
1)lambda表达式可以访问(所在函数体可以访问的)全局变量、静态变量;局部变量不捕获的情况下无法访问
在不捕获的情况下,lambda表达式只能使用其所在函数体所能访问的全局变量,而其所在函数体定义的局部变量是无法访问的。而这个捕获列表的作用就是让lambda表达式能够访问函数体内定义的局部变量。
int g_val = 1;
int main(int argc, char *argv[])
{
static int s_val = 2;
int val = 3;
auto fun1 = []()
{
cout << g_val << endl; //OK 全局变量可以访问
cout << s_val << endl; //OK 静态局部变量可以访问
cout << val << endl; //error 局部变量,无法被访问
};
auto fun2 = [val]()
{
cout << g_val << endl; //OK
cout << s_val << endl; //OK
cout << val << endl; //OK 局部变量val在捕获列表中,可以被访问
};
return 0
}
2)捕获列表只能捕获局部变量;全局变量、静态变量、类成员变量等无法放入捕获列表
捕获列表只能捕获局部变量。即上面的代码片段中,只有局部变量val可以放入[]中,s_val、g_val放进去会报编译错误
如果我们想要在lambda中使用静态变量和全局变量,直接使用即可。
3)捕获列表只能捕获在lambda表达式定义之前已经声明的变量
如下代码,val2无法被lambda表达式“认识”,会发生编译错误。
int main(int argc, char *argv[])
{
int val = 1;
auto fun1 = [val, val2]() //error 编译错误:val2无法识别
{
cout << val << endl; //OK
};
fun1();
int val2 = 2;
system("pause");
return 0;
}
2、捕获列表的种类
1)、[]表示不捕获任何对象
int main(int argc, char *argv[])
{
//定义匿名函数fun
auto fun = []()
{
cout << "hello c++" << endl;
};
//调用fun
fun();
system("pause");
return 0;
}
输出
2)、[var]表示使用值传递的方式捕获var变量(显式捕获);[=]表示以值传递的方式捕获所有捕获范围内的对象(隐式捕获)
单独捕获val只可以在lambda表达式中使用val,而不可以使用其他变量。
代码一
int main(int argc, char *argv[])
{
int val = 666;
int val2 = 111;
auto fun = [val]()
{
cout << val << endl; //OK
cout << val2 << endl; //error 捕获列表中没有val2
};
fun();
system("pause");
return 0;
}
而使用[=]可以捕获在捕获范围的所有变量。
在lambda表达式函数体中的是一个拷贝出的变量。如果我们输出lambda表达式里外的val,就可以发现它们的地址不同。
代码二
int main(int argc, char *argv[])
{
int val = 666;
int val2 = 111;
cout << &val << endl;
auto fun = [=]()
{
cout << val << " "<< &val <<endl; //OK
cout << val2 << endl; //OK val和val2全部被捕获
};
fun();
system("pause");
return 0;
}
代码二输出
3)[&val]表示以地址传递的方式捕获变量val(显式捕获);同样的,[&]表示以地址传递的方式捕获所有捕获范围内的对象(隐式捕获)
我们可以理解为用引用的方式传入某个函数中的变量,或者传入了变量的指针。在lambda表达式中可以对此变量进行修改
代码三
int main(int argc, char *argv[])
{
int val = 666;
int val2 = 111;
cout << &val << endl;
auto fun = [&]()
{
val = 888;
cout << val << " "<< &val <<endl; //OK
cout << val2 << endl; //OK val和val2全部被捕获
};
fun();
system("pause");
return 0;
}
代码三结果
4)[this]捕获this指针,可以使用this类型的成员变量和成员函数
如下所示,在test内的lambda捕获this后,可以使用class A的公有成员函数print和私有成员函数x。
class A
{
public:
void print(){std::<<cout << "class A\n";
void test()
{
auto fun = [this]{
print();
x = 5;
};
fun();
}
private:
int x;
}
5)在捕获列表中,我们可以使用“,”来对不同的变量使用不同的捕获方式
例如:[&val, val2] 表示对val传址,对val2传值
[=, &val] 表示对val传址,其他所有变量传值
[&, val, val2] 表示对val、val2传值,其他所有变量传址
示例代码
void main(int argc, char *argv[])
{
int val = 3;
int val2 = 4;
auto fun = [&val, val2]() //使用传址的方式捕获val,使用传值的方式捕获val2
{
val = 666;
cout << val << endl;
};
fun();
}
三、参数列表
lambda表达式的参数列表,即(...)部分。和我们使用普通函数的参数列表一样。
我们需要注意一下,lambda表达式捕获列表和参数列表的区别。
捕获列表在lambda表达式定义时即发生捕获;而参数列表是在调用lambda表达式时才会传参。
int main(int argc, char *argv[])
{
int val = 1;
//定义lambda表达式
auto fun = [val](int c)mutable
{
cout << "inner:" << val << " " << c << endl;
};
val = 11; //不会改变inner的val的值,因为在此之前已经捕获完成
fun(2); //调用
system("pause");
return 0;
}
四、mutable
1、基本含义:mutable用来表示捕获列表中的对象可被改写
如果一个匿名函数声明为mutable,则在函数体中可以改写传值方式捕获的变量(传址方式捕获的变量不受mutable限制,是可以修改的。)
我们知道,如果传值进匿名函数的变量只能使用,而不能修改。如下语句会报编译错误
int val = 1;
auto fun = [val]()
{
val = 11; //error val不可改写
};
但是用mutable声明过的匿名函数可以修改捕获的变量。如下所示
int val = 1;
auto fun = [val]() mutable
{
val = 11; //OK
};
2、mutable修饰的传值捕获和传址捕获的区别
使用传值方式捕获的变量,是拷贝捕获时刻的变量的,所以lambda之外此变量再变化不影响lambda内的变量值;同理,lambda内对此值的变化也不会影响外面。
但是传址方式捕获的变量,内外会相互影响。
int main(int argc, char *argv[])
{
int val1 = 1, val2 = 2;
auto fun = [val1, &val2]()mutable
{
cout << "inner:" << val1 << " " << val2 << endl;
val1 = 11; //val1传值方式捕获,修改的是拷贝的新变量val1
val2 = 22; //val2传址方式捕获,修改的是val2
};
val1 = 111;
val2 = 222;
fun(); //调用匿名函数
cout << "outer:" << val1 << " " << val2 << endl;
system("pause");
return 0;
}
输出
解析
由于val1是在定义fun时捕获的,即捕获的val1(匿名函数内)对val1(匿名函数外)进行了拷贝,数值为1。在调用此函数时,inner输出的是val1(匿名函数内)的值=1,outer输出的val1(匿名函数外)的值=111.
而val2“内外”是同一变量,所以“内外”的任何修改都会影响 输出。
五、retType
对于返回值的使同普通函数,如下示例代码
int main(int argc, char *argv[])
{
//定义lambda表达式
auto fun = []()->string
{
string str = "hello world";
return str;
};
cout << fun() << endl;
system("pause");
return 0;
}