lambda及参数绑定
一、介绍
对于STL中的算法,我们都可以传递任何类别的可调用对象
。对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用的。即,如果e是一个可调用的表达式,则我们可以编写代码e(args),其中args是一个逗号分隔的一个或多个参数的列表。
一般来说,有四种可调用对象:函数,函数指针,重载了函数调用运算符的类,以及lambda表达式。
二、lambda表达式
1. 概述
一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。
一个lambda的通用表达式为:
[capture list] (parameter list) -> return type { function body }
举个简单的例子:
auto f = [] { return 42; };
cout << f() << endl; //打印42
注意,忽略括号和参数列表相当于指定了空的参数列表。如果函数体为一条return语句,lambda将根据代码推断返回类型,否则类型为void。
2. 传递参数
如函数一样,lambda表达式也可以传递参数,但是不同的是,其不能有默认参数。一个lambda调用的实参数目永远要和形参数目相等。
举个例子:
auto f = [](const string &lhs, const string &rhs)
{ return a.size() < b.size(); };
3. 使用捕获列表
所谓捕获列表,则是中括号中的参数,表示使用其所在函数中的任何局部变量。捕获的方式有三种:值捕获
,引用捕获
和隐式捕获
。
三、Lambda捕获列表
1、空,没有使用任何函数对象参数。
2、=,函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
3、&,函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量),不过要注意是const引用。
size_t v1 = 42;
auto f = [&]() {//捕捉所有代码块中对象并以引用方式捕捉
cout << "Lambda " << v1 << endl;
v1 = 1000;//修改引用所绑定对象的值
return v1 + 10;
};
// v1 = 0;
auto j = f();
cout << "end:" << j << endl;
cout << v1 << endl;
捕捉列表按引用传递可以修改传递对象的值,v1的值成了1000
4、this,函数体内可以使用Lambda所在类中的成员变量。同理this的含义,按*this传递,可修改类成员age的值,变成了10000
class Person {
public:
void touch() {
auto f = [this]() {
int cout = this->age;
this->age = 10000;
return ++cout;
};
auto j = f();
}
int age = 10;
int sex = 1;
};
5、a,将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
6、&a,将a按引用进行传递。
7、a, &b,将a按值进行传递,b按引用进行传递。
8、=,&a, &b,除a和b按引用进行传递外,其他参数都按值进行传递。
9、&, a, b,除a和b按值进行传递外,其他参数都按引用进行传递。
四、 指定返回类型
如前面所提到,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。
//错误示范:编译器推断为void,实际为int
auto f = [](int i) { if(i < 0) return -i; else return i; };
//可以修改为如下
auto f = [](int i) -> int { if(i < 0) return -i; else return i; };
五、关于bind的用法:
1.可将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
调用bind的一般形式为:
auto newCallable = bind(callable,arg_list);
2.arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推
占位符类型作用域
using namespace std::placeholders;
例子:
int f(int a, int b, int c) {
cout << "a " << a << " b " << b << " c " << c << endl;
return a - b + c;
}
//调用处
auto g = bind(f, _2, _1, 11);//注意:此处参数顺序.._2,_1),但是bind函数参数顺序是(20,10)
int k = g(10, 20);
cout << k << endl; //输出21
实际应用中:
bool check_size(const int x, int sz) {
return x > sz;
}
//调用处
int n = 5;
//有bind函数新封装生成的函数,其内部调用了check_size
auto new_check_size = bind(check_size, std::placeholders::_1, n);
auto it = find_if(v.begin(), v.end(), new_check_size);
cout << "第一个大于n的数为:" << *it << endl;
例如cocos中也是类似实现方式:
进入CC_CALLBACK_1实现:
为了与不支持拷贝的参数绑定,bind经常和ref一起用,ref也定义在头文件functional中,作用是返回一个引用对象。
ostream &print(ostream &os, const string &s, char c) {
return os << s << c;
}
//错误示范
for_each(words.begin(), words.end(), bind(print, os, _1, ' '));
//正确示范
for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));