Lambda
Lambda 表达式很有意思,相信很多人初次见到 Lambda 表达式都会不能理解有什么用,我也一样,看了视频教程之后,突然意识到,Lambda 真的是太好用了,它可以在某些情况下可以很大程度上简化代码。
应用场景
下面的代码给我的启发:实现通过信号槽的方式实现点击按钮时,触发修改按钮的名字为“停止”。connect 的最后一个参数其实是函数指针,当按钮触发了 clicked 信号时,将会调用该函数指针,那么借助 Lambda 就不需要重新另外定义一个成员函数来作为参数传入。
void Widget::testLambda()
{
QPushButton *pButton = new QPushButton();
pButton->setParent(this);
pButton->setText("启动");
connect(pButton, &QPushButton::clicked, this, [=](){
pButton->setText("停止");
});
}
可以自己写一个函数回调的方式来对比
#include <iostream>
void onEvent()
{
printf("onEvent\n");
}
void notice(void (*pfunc)())
{
pfunc();
}
int main(int argc, char *argv[])
{
notice(onEvent);
return 0;
}
使用 Lambda 的方式,可以看到代码简化了
#include <iostream>
void notice(void (*pfunc)())
{
pfunc();
}
int main(int argc, char *argv[])
{
notice( [](){printf("Lambda2\n");} );
return 0;
}
介绍说明
从上面的例子可以看到,Lambda 其实本质就是一个函数,只不过这个函数没有名字,所以也叫匿名函数。适合在一些需要传递以函数为参数的表达式中,而函数本事的实现只有几句代码,可以不必另外声明定义函数,直接使用 Lambda 即可。
Lambda 原型说明:
[外部变量访问方式说明符] (参数表) 修饰符 ->返回值类型 {函数体} ()
Lambda 原型解析:
[外部变量访问方式说明符] :
用于确定 Lambda 函数体可以用何种方式去访问外部成员
- [] 中括号里面为空,即表示不访问任何外部成员变量。
- [&] 以引用方式使用 Lambda 所在作用范围内所有可见的局部变量(包括Lambda所在类的 this)。
- [=] 以值传递方式使用 Lambda 所在作用范围内所有可见的局部变量(包括Lambda所在类的 this)。
- [=, &a] 除了a以引用传递方式使用,其他的所有变量以值传递方式使用。
- [a] 只取a,并且以值传递方式使用。
- [this] 取 Lambda 所在的类中的 this 指针。如果已经使用了 & 或者 = 就默认添加此选项。
代码示例:
void Widget::showString(char *pString)
{
qDebug() << pString;
}
void Widget::testLambda()
{
int a = 1;
int b = 2;
// [] 方式
qDebug() << "[] 方式";
[]()
{
// int x = a + b; // 会报错, 因为 a、b 不可见
qDebug() << "调用中:";
}();
// [&] 方式
qDebug() << "\n[&] 方式";
qDebug() << "调用前: " << "a:" << a << " b:" << b;
[&]()
{
a = 5;
b = 3;
qDebug() << "调用中: "<< "a:" << a << " b:" << b;
this->showString("[&] 方式使用成员函数 showString()");
}();
qDebug() << "调用后: " << "a:" << a << " b:" << b;
// [=] 方式
qDebug() << "\n[=] 方式";
qDebug() << "调用前: " << "a:" << a << " b:" << b;
[=]()
{
// a = 5; // 会报错, 变量默认为只读属性, 需要使用 mutable 修饰符才可以修改
qDebug() << "调用中: "<< "a:" << a << " b:" << b;
this->showString("[] 方式使用成员函数 showString()");
}();
qDebug() << "调用后: " << "a:" << a << " b:" << b;
// [=, &a] 方式
qDebug() << "\n[=, &a] 方式";
qDebug() << "调用前: " << "a:" << a << " b:" << b;
[=, &a]()
{
a = 16;
// b = 7; // 会报错, b变量默认为只读属性, 需要使用 mutable 修饰符才可以修改
qDebug() << "调用中: "<< "a:" << a << " b:" << b;
this->showString("[=, &a] 方式使用成员函数 showString()");
}();
qDebug() << "调用后: " << "a:" << a << " b:" << b;
// [a] 方式
qDebug() << "\n[a] 方式";
qDebug() << "调用前: " << "a:" << a << " b:" << b;
[a]()
{
//a = 16; // 会报错, a变量默认为只读属性, 需要使用 mutable 修饰符才可以修改
//qDebug() << "调用中: "<< "a:" << a << " b:" << b; // 会报错, b 变量不可见
// this->showString("[a] 方式使用成员函数 showString()"); // 会报错, this 不可见
qDebug() << "调用中: "<< "a:" << a;
}();
qDebug() << "调用后: " << "a:" << a << " b:" << b;
// [this] 方式
qDebug() << "\n[this] 方式";
qDebug() << "调用前: " << "a:" << a << " b:" << b;
[this]()
{
// qDebug() << "调用中: "<< "a:" << a << " b:" << b; //会报错 a、b 都不可见
this->showString("[this] 方式使用成员函数 showString()");
}();
qDebug() << "调用后: " << "a:" << a << " b:" << b;
}
运行结果
[] 方式
调用中:
[&] 方式
调用前: a: 1 b: 2
调用中: a: 5 b: 3
[&] 方式使用成员函数 showString()
调用后: a: 5 b: 3
[=] 方式
调用前: a: 5 b: 3
调用中: a: 5 b: 3
[] 方式使用成员函数 showString()
调用后: a: 5 b: 3
[=, &a] 方式
调用前: a: 5 b: 3
调用中: a: 16 b: 3
[=, &a] 方式使用成员函数 showString()
调用后: a: 16 b: 3
[a] 方式
调用前: a: 16 b: 3
调用中: a: 16
调用后: a: 16 b: 3
[this] 方式
调用前: a: 16 b: 3
[this] 方式使用成员函数 showString()
调用后: a: 16 b: 3
(参数表) :
用于确定 Lambda 传入那些变量作为参数提供给 Lambda 函数体使用,如果没有可以省略 “()” 括号。
#include <iostream>
void onEvent(int type)
{
printf("onEvent:%d\n", type);
}
void notice(void (*pfunc)(int type), int type)
{
pfunc(type);
}
int main(int argc, char *argv[])
{
// 传统的方式, 回传参数
notice(onEvent, 5);
// lambda 方式 一 , 回传参数
notice([](int type) {printf("Lambda :%d\n", type); }, 15);
// lambda 方式 二 , 回传参数
auto func = [](int c, int d)
{
printf("AutoFunc: c: %d d:%d\n",c ,d);
};
// 调用
func(8, 9);
return 0;
}
修饰符:
如果没有修饰符则可以省略不写
mutable:
用于修饰 [外部变量访问方式说明符] 中列举的按值传递方式的变量时,可以修改其拷贝值。
相当于 void func(const int a, const int b) 变为 void func(int a, int b)
当有此修饰符时,引入的外部变量不能为空。
void Widget::testLambda()
{
int a = 1;
int b = 2;
// mutable
qDebug() << "\nmutable";
qDebug() << "调用前: " << "a:" << a << " b:" << b;
[a, b] () mutable
{
a = 16; // 由于是值传递,a 其实是 lambda 函数体内的局部变量,所以修改的只是 a 的拷贝
b = 100;
qDebug() << "调用中: "<< "a:" << a << " b:" << b; // 会报错, b 变量不可见
}();
qDebug() << "调用后: " << "a:" << a << " b:" << b;
}
mutable
调用前: a: 1 b: 2
调用中: a: 16 b: 100
调用后: a: 1 b: 2
exception:
说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于void f()throw(X, Y)
->返回值类型 :
说明 lambda 函数体可以返回什么样类型的值,实际测试中似乎无论什么情况都可以省略,编译器会自动推测出类型。
#include <iostream>
void notice(int (*pfunc)(int a, int b), int aa, int bb)
{
printf("notice:%d\n",pfunc(aa, bb));
}
int main(int argc, char *argv[])
{
// lambda, 回传参数
notice(
[](int a, int b)
{
printf("回调隐式返回类型: a:%d b:%d a+b:%d\n", a, b, a + b);
return a + b;
},
10, 15);
puts("");
// lambda, 回传参数
notice(
[](int a, int b)->int
{
printf("回调显式返回类型: a:%d b:%d a+b:%d\n", a, b, a + b);
return a + b;
},
10, 15);
puts("");
// lambda, 显式返回类型
auto func = [](int c, int d)->int
{
printf("显式返回类型: c:%d d:%d c+d:%d\n",c ,d, c + d);
return c + d;
};
// 调用
printf("func:%d\n", func(20, 10));
puts("");
// lambda, 隐式返回类型(视频中说只有一处才可以省略,但实际做的实验发现都可以)
auto func1 = [](int c, int d)
{
printf("隐式返回类型: c:%d d:%d c+d:%d\n", c, d, c + d);
if (c > 3)
return c + d;
else
return c;
};
// 调用
printf("func1:%d\n", func1(50, 10));
return 0;
}
{函数体} :
实际具体的实现,一般都只有几句代码。
():
最后这一个括号其实代表的是调用的意思,当需要执行时,加上括号即可。
// 只是定义, 并未调用
[]()
{
printf("11111111111111\n");
};
// 定义了且被调用了
[]()
{
printf("11111111111111\n");
}();
// 最简单的 lambda 表达式
[] {puts("lambda..."); };