目录
一.lambda介绍
(一).总体介绍
(二).参数列表
(三).尾置返回
(四).捕捉列表
①值捕捉
②引用捕捉
③隐式捕捉
(五).lambda与捕捉列表的本质
二. 参数绑定bind
(一).什么情况下会用bind
(二).使用方式
①参数列表
②参数顺序
③引用类型参数
lambda是C++11所规定的一种新方法。一般用于泛型算法传递自定义函数。
其形式如下:
[ 捕捉列表 ] ( 参数列表 ) ->返回值类型 { 函数体 }
lambda表达式会返回一个函数对象。
举个例子:
int a = 1, b = 3;
auto f = [](int i, int j)->int {return i + j; };
f(a, b);
等价于下列函数
int f(int i, int j)
{
return i + j;
}
实际开发中,一般用于给算法传仿函数,比如:
vector a = { 4,3,5,6,2,5,7,8,5,3 };
//按降序排序
sort(a.begin(), a.end(), [](const int i, const int j)->bool { return i > j; });
使用lambda表达式时,有几点需要注意:
1.捕捉列表可以为空
2.尾置返回类型和参数列表可以忽略,即[]{};
3.捕捉列表和函数体不能忽略。
使用参数列表时,按照普通函数的参数列表使用即可,唯一的不同是不能有缺省参数。
因此,lambda的实参数量永远与形参保持一致。
当然,参数可以是引用、指针类型,const修饰也没问题。
与普通函数不同,lambda只能使用尾置返回类型,如果忽略尾置返回,那么编译器会自行推断返回类型,当然这个推断也是有条件的:
1.函数体没有return语句,自动推断为void类型,即无需返回。
2.只有一条return语句,根据return类型推断返回类型。
3.多条return语句,返回void(实际上在vs环境下也会根据return自行推到返回类型)
顾名思义,捕捉列表就是捕捉已存在的局部变量,因此使用捕捉列表必须捕捉那些已存在且可访问的对象。
同时,即便lambda在函数作用域内,如果没有进行捕捉或列表传参,那么也不能直接使用该变量。
尤其需要注意的是,捕捉列表只能捕捉临时变量,static、全局变量因为具有静态属性不能捕捉。
即声明周期不随函数作用域的变量不能捕捉,因为静态变量本来就可以直接使用自然不需要捕捉。
示例如下:
int i = 2, j = 4;
//捕捉i和j
auto f = [i, j] { return i < j; };
int i = 2;
static int j = 4;
auto f = [i, j] { return i < j; };//错误,j是静态变量
当然捕捉也分三种情况:
默认情况下,捕捉列表按照传值的方式捕捉变量,因此,传递给lambda的并不是变量本身,而是变量的一份拷贝,lambda的修改不会影响变量本身。
在捕捉参数前加上&即引用捕捉。
在这种情况下,传递给lambda的即是变量本身,当在lambda函数体中进行修改时,变量本身也进行修改。
引用捕捉的常用场景是那些拷贝代价很大的对象,或者不支持拷贝的对象,比如iostream对象就不支持拷贝,只能引用捕捉。
除了可以显式捕捉变量的方式外,还可以使用=或&进行隐式捕捉。
使用=是值捕捉,使用&是引用捕捉。
int i = 0, j = 4;
//将i和j全部按值捕捉
auto f = [=] { return i < j; };
//将i和j全部按引用捕捉
auto f = [&] { return i < j; };
使用时,隐式捕捉可以和显式捕捉(值、引用捕捉)混合使用,但是有如下要求:
1.隐式捕捉必须在捕捉列表第一个位置。
2.定义的隐式捕捉方式即是指定默认捕捉方式,捕捉列表后续参数默认该方式捕捉。
3.后续显式捕捉的方式必须和隐式捕捉不同。
int i = 0, j = 4;
auto f = [=, &j] { return i < j; };//j按引用捕捉,其余全部按值捕捉
auto f = [&, j] { return i < j; };//j按值捕捉,其余全部按引用捕捉
auto f = [&, &j] { return i < j; };//错误
auto f = [=, j] { return i < j; };//错误
《C++ Primer》中指出,lambda本质是一个函数对象,或者是仿函数。默认情况下,只有一个operator()函数,没有默认构造、赋值、析构函数。
当我们使用lambda时,会生成一个未命名对象,使用时通过该对象调用重载的()完成函数功能。
大致如下:
auto f = [](const int& a, const int& b) { return a < b; };
//等价于
class XXXXX {
public:
bool operator()(const int& a, const int& b) {
return a < b;
}
};
当使用捕捉列表时,情况分为两种:
1.按引用捕捉,编译器会将捕捉对象直接在类内使用,无需定义成员。
2.按值捕捉,因为有拷贝的发生,不能直接使用捕捉的对象。
此时,lambda类会定义一个带参的构造函数并在类内定义值捕捉的成员变量。构造函数使用值捕捉对象给成员变量赋值。
auto f = [i, j]{ return i < j; };//值捕捉
class XXXXX {
public:
XXXXX(int a, int b)//生成构造函数
:i(a), j(b)
{}
bool operator()(const int& a, const int& b) {
return a < b;
}
private:
//值捕捉的变量的拷贝
int i;
int j;
};
lambda是否具有构造、拷贝构造、析构函数,要看具体捕捉成员类型而定。
以find_if函数举例,其传入的第三个是函数对象,且函数参数只能有一个。
但加入我们需要两个参数呢,比如想通过比较来判断是否是要找的值呢?
//通过判断i是否大于j,来确定我们要找的值
bool judge(int i, int j) {
return i > j;
}
vector a = { 4,3,5,6,2,5,7,8,5,3 };
auto x = find_if(a.begin(), a.end(), judge);//错误,judge参数只能唯一
这种情况下,可以通过使用lambda表达式解决,将j传进捕捉列表。
vector a = { 4,3,5,6,2,5,7,8,5,3 };
//lambda方式解决
int j = 7;
auto x = find_if(a.begin(), a.end(), [j](int i) {return i > j; });
但是,如果我们就想用judge函数代替lambda呢,使用bing参数绑定。
bind函数定义在头文件
使用形式为:
auto 返回对象 = bind(函数名, ...);
其中返回类型为_Binder类(vs环境下),但具体函数会有具体的类型,因为_Binder是类模板。
...省略符所代表的就是函数参数列表,其中需要直接绑定给函数的在bind中给出,需要使用函数传参的使用占位符代替。
占位符在placeholders命名空间中,placeholders又在std中。
占位符使用时如下:
std::placeholders::_1 //1号占位符
以judge为例,使用bind函数方式如下:
int j = 7;
//bind返回值类型使用auto即可
//直接将j绑定到judge第二参数,第一参数需要函数传参
//生成x对象代替judge函数
auto x = bind(judge, std::placeholders::_1, j);
//此时x只需要一个参数即可
auto y = find_if(a.begin(), a.end(), x);
占位符表示的参数,不受出现顺序限制。
比如如下函数:
bool judge(int i, int j, int z) {
return i > j && i > z;
}
int a = 2, b = 3, c = 1;
auto x = bind(judge, std::placeholders::_1, a, std::placeholders::_2);
auto y = bind(judge, std::placeholders::_2, a, std::placeholders::_1);
当调用x和y时可以表示成下面情况:
//x
x(1, 2) == judge(1, a, 2);
//y
y(1, 2) == judge(2, a, 1);
即_1占位符表示的永远是函数第一个需要传入的参数,与bind中位置无关。
但是bind中直接绑定的参数,与函数中对应位置绑定。
如果函数参数是引用类型呢,比如传递iostream对象(禁止拷贝)。
那么bind中需要绑定引用类型参数,就要使用标准库中的ref函数。
int a = 0;
//传a的引用
auto x = bind(judge, std::placeholders::_1, ref(a), std::placeholders::_2);
参考书籍:
《C++ Primer》P349-P357、P507-P509
一个好的程序员应该是那种过单行线都要往两边看的人———Doug Linder
如有错误,敬请斧正