目录
4. function、bind和lambda表达式的配合使用
1. 什么是闭包?
一个函数,带上了状态,就变成了闭包了。那什么叫 “带上状态” 呢? 意思是这个闭包有属于自己的变量,这些个变量的值是创建闭包的时候设置的,并在调用闭包的时候,可以访问这些变量。函数是代码,状态是一组变量,将代码和一组变量捆绑 (bind) ,就形成了闭包。
1.1 闭包的实现方式有哪几种?
1.仿函数
2.std::function()
3.std::bind()
4.lambda表达式
2. 函数对象包装器function和bind有啥用?
所谓函数对象包装器,其实和vector和list等容器类似,只不过函数对象包装器是对函数的包装,容器是对各种数据类型的包装,简单说function和bind为函数提供了一种容器;用于需要使用回调函数的情况下,将参数从函数指针类型,转变为被函数容器封装的对象,通过仿函数的形式来调用被绑定的可调用对象,便于操作;
例如:
#include <functional>
using namespace std;
int test (int n) {
cout<< n << endl;
return n;
}
int main () {
test(123);
std::function<int (int)> f = test;
f(123);
return 0;
}
通过function函数,绑定到test函数上,将test函数包装成一个对象f,后续便可以通过该对象f对test函数进行调用;效果一样;有了function,
2.1 function支持对四种函数的封装:
1.普通函数
2.匿名函数 (lambda表达式)
3.类的成员函数
4.仿函数(重载了operator()运算符的函数 )
#include <functional>
using namespace std;
int test (int n) {
cout<< n << endl;
return n;
}
class A {
public:
A(){};
int MyTest(int n) {
cout<<n<<endl;
return n;
}
int operator() (int n) {
cout<<n<<endl;
return n;
}
};
int main () {
test(123);
//普通函数
std::function<int (int)> f1 = test;
f1(123);
//匿名函数
std::function<int (int)> f2 = [](int n)->int {
cout<<n<<endl;
return n;
};
//类的成员函数,因为类的成员函数默认会传递该对象的this指针,所以需要写出&A
std::function<int (&A,int)> f3 = &A::MyTest;
A a; //需要先定义对象
f3(&a,123);
//仿函数
std::function<int (&A, int)> f4 = &A::operator();
A b;
f4(&b,123);
return 0;
}
无论是什么类型的函数,使用std::function进行绑定之后,对该可调用对象的使用操作都是一致的,绑定后通过仿函数来调用被绑定的可调用对象;
std::function对象最大的用处就是在实现函数回调
2.2 bind机制:
bind与function作用一致,都是将函数包装成一个对象,使用对象进行函数调用,将可调用对象的操作统一化。但bind又有其特殊点,bind机制把一个函数的函数体和它的参数一起进行绑定封装,绑定后形成一个新的可操作的对象,适用于函数参数较多的函数,根据不同的情况,可以对参数的值进行随意指定;这种机制在回调函数的使用过程中也颇为有用
#include<functional>
using namespace std;
void test(int a, int b, int c){
cout<<a<<b<<c<<endl;
}
int main(){
auto a = std::bind(test, 1, 2, 3);//将test函数和它的三个给定参数打包封装成一个新的对象a
a();通过函数对象调用test函数
auto b = std::bind(test, std::placeholders::_1, 2, std::placeholders::_2)//将函数test和它的三个参数全都绑定在一起,封装成一个新的对象b,函数的第2参数已经给定,第1,3个参数使用占位符placeholders占位,等运行时根据传入的实际参数再确定参数的值
b(1, 3);//1是传入的第一个参数,即placeholders_1, 3是第二个参数,即placeholders::_2的值
}
3. lambda表达式
C++11中的lambda表达式用于定义并创建匿名的函数对象,以简化编程工作
3.1 lambda表达式的构成
① 函数对象参数
[],标识一个lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义lambda为止时lambda所在作用范围内可见的局部变量(包括lambda所在类的this)。函数对象参数有以下形式:
-
- 空。没有使用任何函数对象参数。
- =。函数体内可以使用lambda所在作用范围内所有可见的局部变量(包括lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
- &。函数体内可以使用lambda所在作用范围内所有可见的局部变量(包括lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
- this。函数体内可以使用lambda所在类中的成员变量。
- a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
- &a。将a按引用进行传递。
- a, &b。将a按值进行传递,b按引用进行传递。
- =,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
- &, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
② 操作符重载函数参数
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
③ 可修改标示符
mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
④ 错误抛出标示符
exception声明,这部分也可以省略。exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用throw(int)
⑤ 函数返回值
->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
⑥ 是函数体
{},标识函数的实现,这部分不能省略,但函数体可以为空。
3.2 lambda与仿函数的关系
class MyFunctor
{
public:
MyFunctor(int tmp) : round(tmp) {}
int operator()(int tmp) { return tmp + round; }
private:
int round;
};
int main()
{
//仿函数
int round = 2;
MyFunctor f1(round);//调用构造函数
cout << "result1 = " << f1(1) << endl; //operator()(int tmp)
//lambda表达式
auto f2 = [=](int tmp) -> int { return tmp + round; } ;
cout << "result2 = " << f2(1) << endl;
return 0;
}
除去在语法层面上的不同,lambda和仿函数有着相同的内涵——都可以捕获一些变量作为初始化状态,并接受参数进行运行。
而事实上,在C++11中,lambda可以视为仿函数的一种等价形式,编译器内部依然将lamda表达式转化为仿函数,只是lamda的使用要更方便,代码更易读,使用仿函数还要先在类中声明和定义,lambda则可以直接使用。
仿函数是类中重载了()运算符,使得类的对象在使用operator()时表达式形式像函数一样,例如:obj(i),而lamda表达式作为仿函数的等价形式,在使用的时候也是lmd_obj(i), i为接收的参数,
即[](){}中()内的参数;
3.3 lambda的类型
lambda表达式的类型在C++11中被称为“闭包类型”,每一个lambda表达式会产生一个临时对象(右值)。因此,严格地将,lambda函数并非函数指针。
不过C++11标准却允许lambda表达式向函数指针的转换,但提前是lambda函数没有捕获任何变量,且函数指针所示的函数原型,必须跟lambda函数有着相同的调用方式(参数的个数和类型要一致)。
int main()
{
//使用std::function和std::bind来存储和操作lambda表达式
function<int(int)> f1 = [](int a) { return a; };
function<int()> f2 = bind([](int a){ return a; }, 123);
cout << "f1 = " << f1(123) << endl;
cout << "f2 = " << f2() << endl;
auto f3 = [](int x, int y)->int{ return x + y; }; //lambda表达式,没有捕获任何外部变量
typedef int (*PF1)(int x, int y); //函数指针类型
typedef int (*PF2)(int x);
PF1 p1; //函数指针变量
p1 = f3; //ok, lambda表达式向函数指针的转换
cout << "p1 = " << p1(3, 4) << endl;
PF2 p2;
p2 = f3; //err, 编译失败,参数必须一致
decltype(f3) p3 = f3; // 需通过decltype获得lambda的类型
decltype(f3) p4 = p1; // err 编译失败,函数指针无法转换为lambda
return 0;
}
3.4 lambda的优势
lambda表达式的价值在于,就地封装短小的功能闭包,可以及其方便地表达出我们希望执行的具体操作,并让上下文结合更加紧密。
4. function、bind和lambda表达式的配合使用
//使用std::function和std::bind来存储和操作lambda表达式
function<int(int)> f1 = [](int a) { return a; };
function<int()> f2 = bind([](int a){ return a; }, 123);
cout << "f1 = " << f1(123) << endl;
cout << "f2 = " << f2() << endl;
auto f3 = [](int x, int y)->int{ return x + y; }; //lambda表达式,没有捕获任何外部变量
如上代码所示,函数包装器将lambda表达式包装成一个对象,对象以仿函数的形式来调用lambda表达式;