lambda表达式
1. Lambda表达式基础
lambda 表达式是c++11最重要的新特性之一,lambda表达式实际上就是提供了一个类似匿名函数的特性,而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下使用的。这样的场景有很多。所以匿名函数几乎是现代编程语言的标配。
应用场景:
Qt中的槽函数,槽函数通常只在连接信号槽的地方使用,没有必要命名一个函数。
基本语法
[捕获列表](参数列表) mutable(可选) 异常属性 ->返回值类型{
//函数体
}
//例如:
[](int a, int b) -> int{
return a+b;
}
- Lambda表达式以一对中括号[]开始。
- 和函数定义一样,有参数列表()
- 和函数定义一样,有函数体{}。
- 一般不需要说明返回值->(相当于auto),有特殊情况需要说明时,则应使用箭头语法的方式。
- 每个Lambda表达式都有一个全局唯一的类型,要精确捕获lambda表达式到一个变量中,只能通过auto声明的方式。
2. 用法
2.1 可以嵌套调用
//1. 直接调用
int c = [](int n){
return [n](int x){
return n+x;
}(1);//内部的lambda表达式捕获了外部的参数n,并传参参数1.
}(2);// 外部的lambda表达式调用内部的lambda表达式并传参2.
//结果返回3.
//2.保存到一个变量中,在适当的地方再调用。
auto f = [](int n){
return [n](int x){
return n+x;
};//先不传参数, 不调用.
};// 先不传参数, 不调用.
int c = f(1)(2);//分别传两个参数。效果和上面一样。
2.2 捕获列表
捕获列表可以理解为参数的一种类型,lambda表达式函数体内部默认是不能使用外部的变量,捕获列表可以起到传递外部参数的作用,根据传递的行为, 捕获列表分为以下几种:
1.[var]表示值传递方式捕捉变量var;
2.[=]表示值传递方式捕捉所有父作用域的变量(包括this);
3.[&var]表示引用传递捕捉变量var;
4.[&]表示引用传递方式捕捉所有父作用域的变量(包括this);
5.[this]表示值传递方式捕捉当前的this指针。
6.[=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量;
7.[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。
值得注意的是,捕捉列表不允许变量重复传递,会导致编译时期的错误.
2.2.1按值捕获:
与参数传值类似,是一份拷贝,拷贝的是匿名函数定义时(非调用时)外部变量的值。在匿名函数内部修改该变量不会影响外面的值,同样,在匿名函数定义体之后修改该变量的值,也不会影响匿名函数内部该变量的值。被捕获的变量在lambda表达式被创建时拷贝,而非调用时才拷贝。
int main()
{
int t = 10;
//按值捕获,捕获的是声明匿名函数时,捕获列表参数的值。
auto f = [t](){
cout<<t<<endl;
}
t = 11;
f(); //此时打印出t的值是10, 而不是11.
}
同一个匿名函数多次被调用时,会维持上一个值(相当于c中的static变量),匿名函数退出时,值捕获/引用捕获的参数不释放:
int main()
{
int t = 10;
//按值捕获,捕获的是声明匿名函数时,捕获列表参数的值。
auto f = [t]()mutable{
++t;
}
cout<<f()<<endl;//输出为11,在定义匿名函数的时候,t的值就在内部拷贝了一份,并复制10。
cout<<f()<<endl;//输出为12,第二次进来的时候,t的值维持之前的拷贝。
cout<<t<<endl;//输出为10,内部不影响外部。
}
mutable关键字
mutalbe的中文意思是“可变的,易变的”,跟const是反义词。默认情况下按值捕获的参数是const类型的,不可修改。如果要修改t的值,要加上mutable关键字。
2.2.2按引用捕获:
与引用传参类似,引用捕获传的是外部变量的引用,用的是同一份资源,在匿名函数内部改变该变量会影响外部的值,在匿名函数外部改变该变量的值也会影响内部的值。
int main()
{
int t = 10;
//按引用捕获,捕获列表参数的引用,内部修改会影响外部,外部修改会影响内部。
auto f = [&t](){
cout<<t<<endl;
t = 13;
}
t = 11;
f(); //此时打印出t的值是11,因为t在匿名函数外部被修改。
cout<<t<<endl;//此时打印出t的值是13,因为t在匿名函数被修改.
}
同一个匿名函数多次被调用时,会维持上一个值(相当于C/C++中的static变量),匿名函数退出时,值捕获/引用捕获的参数不释放:
int main()
{
int t = 10;
//按引用捕获,捕获的是调用匿名函数时,捕获列表参数的值。
auto f = [&t]()mutable{
return ++t;
};
t=12;
cout<<f()<<endl;//输出为13
cout<<f()<<endl;//输出为14
cout<<t<<endl;//输出为14
}
3场景示例
int main()
{
//遍历vector,并分别打印出奇数和偶数。
vector<int> v = {1,2,3,4,5,6};
for_each(v.begin(),v.end(),[=](int n){
if(n%2)
{
cout <<"this is odd"<<endl;
}
else
{
cout <<"this is even"<<endl;
}
4 函数对象包装器
为函数提供了一种容器(封装)
4.1 std::function
Lambda表达式的本质是一个函数对象,当lambda表达式的捕获列表为空时,lambda表达式还能够作为一个函数指针进行传递。
C++11 std::functional 是一种通用、多态的函数封装,它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作,它也是对C++中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的),换句话说,就是函数的容器。当我们有了函数的容器之后就能更加方便地将函数,函数指针作为对象进行处理。
支持4中函数的封装:
- 普通函数
- 匿名函数
- 类成员函数
- 仿函数
#include<functional> //引用functional头文件。
int test(int n)
{
cout<<n<<endl;
return n++;
}
class CTest{
public:
CTest(int a){this->a = a;};
int test(int a)
{
cout<<"this is a member function a="<<a<<endl;
return a++;
};
int operator()(int n)
{
cout<<n<<endl;
return n;
}
private:
int a;
};
int main()
{
//使用函数对象包装器
//std::function<返回值类型(参数类型)>
//普通函数
std::function<int(int)>f = test;
f(123);
//匿名函数:
std::function<int(int)>f2 = [](int n)->int{
cout<<"this is a anonymous function n="<<n<<endl;
return n++;
};
f2(123);
//类成员函数:
std::function<int(CTest*, int)> f3=&CTest::test;
CTest t(10);
f3(&t,10);
//仿函数:
std::function<int(CTest*, int)> f4=&CTest::operator();
CTest t2(11);
f3(&t2,11);
}
4.2 bind机制
bind机制可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体,这种机制在回调函数的使用过程中也颇为有用。
(1)bind预先绑定的参数需要传具体的变量或值进去,对于预先绑定的参数,是pass-by-value的
(2)对于不事先绑定的参数,需要传std::placeholders进去,从_1开始,依次递增。placeholder是pass-by-reference的
(3)bind的返回值是可调用实体,可以直接赋给std::function对象
(4)对于绑定的指针、引用类型的参数,使用者需要保证在可调用实体调用之前,这些参数是可用的
(5)类的this可以通过对象或者指针来绑定
int add(int a, int b, int c, int d)
{
int ret = a+b+c+d;
cout<<ret<<endl;
return ret;
}
int main()
{
auto f1 = std::bind(add,1,2,3,4);
f1();
auto f2 = std::bind(add, 1, std::placeholders::_1,3,4);
f2(20);
}