一、 lambda表达式
C++11:也是一种可调用对象。定义了一个匿名函数,并且可以捕获一定范围内的变量。
1. 格式
[捕捉列表] (参数) mutable -> 返回值类型 {函数体};
注意:
1. 返回类型后置(必须的,语法规定);
2. 返回类型可以省略(但是编译器不一定能推断出返回类型,此时显示指定即可);
3. 参数列表可以有默认值。没有参数的时候,参数列表可以省略,甚至括号()都可以省略。
4. 捕获列表[]和函数体不能省略,必须时刻包含。
5. lambda调用方法和普通函数相同,不返回任何类型就是void。
6. 函数体末尾的分号不能省略。
auto f = [](int a = 10)->int {
return a + 1;
};
auto f1 = [] {return 1; };
auto f2 = []() {return 2; };
cout << f(1) << endl;
2. 捕捉列表说明
[]是lambda的引出符,捕捉列表能够捕捉上下文中的变量,来供lambda函数使用:
捕捉列表 | 说明 |
---|---|
[ ] | 不捕获任何变量,但不包括静态局部变量。lambda可以直接使用静态局部变量 |
[var] | 如果多个变量名,则彼此之间用,分割,按值捕获变量名代表的变量var,其他的变量则不捕获 |
[=] | 捕获外部作用域中所有变量,并作为值(副本)在函数体内使用,但不许给它赋值 |
[&var] | 按引用捕获变量名代表的变量,同时不捕获其他变量 |
[&] | 捕获外部作用域中所有变量,并作为引用在函数体内使用 |
[this] | 表示值传递方式捕捉当前的this指针,让lambda表达式有和当前类成员函数同样的访问权限 |
[=,&a] | 按值捕获所有外部变量,但按引用捕获&中所指的变量,=必须在开头位置,表示默认以值捕获 |
[&,var] | 按引用捕获所有外部变量,但按值的方式类捕获变量名所代表的变量 |
注意:
捉列表不允许变量重复传递,如:[=,a]、[&,&this],会引起编译时期的错误
class Test
{
public:
void Func(int x, int y)
{
auto f = [this] {
return m_a;
};
cout << f() << endl;
}
private:
int m_a = 10;
};
int main(int argc, const char* argv[])
{
//1.[]不捕获任何变量
int j = 5;
//auto f = [] {return j; }; //编译报错
//2. 静态局部变量,可以直接使用
static int i = 9;
auto f1 = [] {return i; };
//3. [&]捕获外部作用域中所有变量,并作为引用在函数体内使用
int m = 9;
auto f2 = [&] {
m = 15;
return m;
};
cout << f2() << endl; //15
cout << m << endl; //15
//4. [=]捕获外部作用域中所有变量,并作为值(副本)在函数体内使用,但不许给它赋值
int k = 1;
auto f3 = [=] {
//k = 15; //非法,不可以给它赋值,因为是以值的方式捕获
};
//5. [this]捕获当前类中this指针,让lambda表达式有和当前类成员函数同样的访问权限。
//如果[]中已经使用了 & 或 =,那么默认已经使用了this,可以使用当前类的成员变量和成员函数。
Test test;
test.Func(10, 12);
//6. [变量名]如果多个变量名,则彼此之间用,分割,按值捕获变量名代表的变量,其他的变量则不捕获
// [&变量名]按引用捕获变量名代表的变量,同时不捕获其他变量
int a = 10, b = 5;
auto f4 = [a, b] { //[&a, &b]
cout << a <<" " << b << endl;
};
f4();
//7. [=,&变量名] 按值捕获所有外部变量,但按引用捕获&中所指的变量,
//=必须在开头位置,表示默认以值捕获(隐式捕获方式),后续其他的都是显示捕获
auto f5 = [=, &a, &b] {
cout << k << " " << a << " " << b << endl;
};
//8. [&, 变量名] 和7相反作用
auto f6 = [&, a, b] {
cout << k << " " << a << " " << b << endl;
};
return 0;
}
3. lambda表达式延迟调用易出错分析
int x = 5;
auto f = [=] { //当遇到auto这一行,也就是捕获这个时刻,x的值就已经被复制到这个f中了
return x;
};
x = 10;
cout << x << endl; //10
cout << f() << endl; //我们认为是10,但实际是5,该成&,值会被改成10
4. lambda表达式中的mutable(易变得)
int x = 5;
auto f = [=]() mutable { //注意要加mutable,则()参数列表之外的这个圆括号不能省略
x = 6; //加mutable就可以修改x的值,理论上以值的方式捕获是不能被修改
return x;
};
5. lambda表达式的类型及存储
C++11中,lambda表达式的类型被称呼为 “闭包类型”(函数中的函数)。可以直接调用(可调用对象)。可以认为他是一个带有operator()的类类型对象,也就是仿函数。
所以我们可以用std::function和std::bind来保存和调用lambda表达式。每个lambda都会触发编译器生成一个独一无二的类对象。
std::function<int(int)> f = [](int a) {return a; };
cout << f(15) << endl; //15
std::function<int(int)> f1 = std::bind( //bind第一个参数是函数指针,第二个参数才是真正的函数参数
[](int a) {
return a;
},
16
);
cout << f1(10) << endl; //16
//不捕获任何变量的lambda,也就是捕获列表为空,可以转换成一个普通的函数指针
using F = int (*)(int); //定义一个函数指针类型
F f2 = [](int a) {return a; };
cout << f(10) << endl; //10
6. lambda表达式和for_each配合
for_each简介:
for_each是一个函数模板,使用时要包含头文件
#include <algorithm>
void Func(int a)
{
cout << a << endl;
}
int main(int argc, const char* argv[])
{
vector<int> vec{ 10,12,13,14,10,25 };
for_each(vec.begin(), vec.end(), Func); //传统for_each用法
int sum = 0;
for_each(vec.begin(), vec.end(), [&sum](int a) {
sum += a;
cout << a << endl;
});
cout << "sum= "<< sum << endl;
return 0;
}
7. lambda表达式和find_if配合
find_if简介:
find_if是一个函数模板,使用时要包含头文件
#include <algorithm>
int main(int argc, const char* argv[])
{
vector<int> vec{ 10,12,13,14,10,25 };
auto iter = find_if(vec.begin(), vec.end(), [](int a) { //返回的是一个迭代器
cout << a << endl;
return false; //只要返回false,find_if会一直遍历,返回true就停止遍历
});
return 0;
}
二、捕获列表陷阱分析
1. 捕获列表的&
作用:捕获外部作用域中所有变量,并作为引用在lambda表达式中使用。
std::vector<std::function<bool(int)>> vec; //全局变量,每个元素都是一个function
void Func()
{
srand((unsigned)time(nullptr));
int a = rand() % 6; //产生一个0-5之间的随机数
vec.push_back(
//[&](int c)
//[&](auto c)
[=](int c)
{
if (c % a == 0)
{
return true;
}
return false;
});
}
int main(int argc, const char* argv[])
{
Func();
cout << vec[0](10) << endl; //非法调用,会出现不可预料的问题
return 0;
}
2. 形参列表可以使用auto
C++14允许在lambda表达式的形参列表中使用auto。
引用捕获超出返回的情形也叫 “引用悬空”。采用按值捕获可解决这个问题。
3. 成员变量捕获问题
vector<std::function<bool(int)>> vec;
class Test
{
public:
int m_a = 8;
void Func() {
auto tempv = m_a; //解决办法,定义一个零时变量接住成员变量的,以值传递副本
vec.push_back( //不能[=]值传递
[tempv](auto v) { //按值捕获,等于有this
cout << tempv << endl;
if (v % tempv == 0)
{
return true;
}
return false;
}
);
}
};
int main(int argc, const char* argv[])
{
Test* p = new Test();
p->Func();
cout << vec[0](10) << endl; //8,0
delete p; //结论:lambda表达式的执行正确与否,取决于p对象是否存在,只有p存在,这个lambda表达式执行才正确
cout << vec[0](10) << endl; //随机值,0
//解决办法,参考 Func()函数 或参考 下面 广义lambda捕获
return 0;
}
4. 广义lambda捕获
vector<std::function<bool(int)>> vec;
class Test
{
public:
int m_a = 8;
//void Func() {
// auto tempv = m_a;
// vec.push_back(
// [tempv](auto v) { //按值捕获,等于有this
// cout << tempv << endl;
// if (v % tempv == 0)
// {
// return true;
// }
// return false;
// }
// );
//}
void Func() {
vec.push_back(
[abc = m_a](auto v) { //将 m_a的值赋值到闭包里面来
cout << abc << endl;
if (v % abc == 0)
{
return true;
}
return false;
}
);
}
};
int main(int argc, const char* argv[])
{
Test* p = new Test();
p->Func();
delete p; //结论:lambda表达式的执行正确与否,取决于p对象是否存在,只有p存在,这个lambda表达式执行才正确
cout << vec[0](10) << endl; //8,0
return 0;
}
5. 静态局部变量
静态局部变量时不能被捕获的,但是可以在lambda中直接使用。静态局部变量时保存在静态存储区,它的有效期是一直到程序结束。
但是这种对static 变量使用有点类似于按 引用 捕获这种效果。
vector<std::function<bool(int)>> vec;
void Func()
{
srand((unsigned)time(nullptr));
//产生0-5之间的随机数
static int temp = 4; //静态局部变量不需要被捕获,也捕获不到
vec.push_back([](auto v) {
cout << temp << endl;
if (v % temp == 0)
{
return true;
}
return false;
});
temp++;
}
int main(int argc, const char* argv[])
{
Func();
cout << vec[0](10) << endl; //5,1
return 0;
}