标题:[C++] C++11详解 (五)function包装器、bind
@水墨不写bug
目录
一、function包装器
二、bind绑定
正文开始:
一、function包装器
function包装器,function实现在<functional>头文件中。C++中的function本质上是一个类模板。
function包装器可以包装函数指针,仿函数,lambda表达式, 在一定程度上可以起到简化代码逻辑和实现的作用。
//function包装lambda表达式
int x = 10, y = 11;
function<int(int, int)> f1 = [](int a, int b)->int {return a + b; };
cout << f1(x, y) << endl;
//输出21
//function包装函数指针
int add(int a,int b)
{
return a + b;
}
int main()
{
function<int(int, int)> f2 = add;
cout << f2(12, 24) << endl;
return 0;
}
//输出36
//function包装仿函数
struct ADD
{
int operator()(int a,int b)
{
return a + b;
}
};
int main()
{
function<int(int, int)> f3 = ADD();
cout << f3(1, 19) << endl;
return 0;
}
//输出20
function语法:
std::function在头文件<functional>
// 类模板原型如下
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
之前我们已经讲解过可变参数模板, 知道了Args代表任意类型,任意个数的参数。在具体使用时,需要为function传递模板参数,格式:
function<返回值类型(形参列表)>
有了包装器,我们就可以讲返回值和形参列表相同的函数指针,仿函数,lambda表达式看作是同一种类型了。
比如:初始化一个map<string,function<int(int,int)>>:(函数指针,仿函数,lambda由于被function包装过,所以可以识别为同类型)
#include<functional>
#include<iostream>
#include<map>
using namespace std;
int add(int a,int b)
{
return a + b;
}
struct ADD
{
int operator()(int a,int b)
{
return a + b;
}
};
int main()
{
map<string, function<int(int, int)>> opmap =
{
{"函数指针",add},
{"仿函数",ADD()},
{"lambda",[](int a, int b)->int {return a + b; }}
};
//函数指针,仿函数,lambda由于被function包装过,所以可以识别为同类型
return 0;
}
这样一来,我们只需要使用map的string来使用对应的功能即可,不需要知道内部的具体是什么实现的:
int main()
{
map<string, function<int(int, int)>> opmap =
{
{"函数指针",add},
{"仿函数",ADD()},
{"lambda",[](int a, int b)->int {return a + b; }}
};//函数指针,仿函数,lambda由于被function包装过,所以可以识别为同类型
//只要根据string的标识提示使用功能即可,不需要知道具体是 函数指针,仿函数,lambda哪一个实现的。
cout << opmap["函数指针"](1, 18) << endl;
cout << opmap["仿函数"](9, 28) << endl;
cout << opmap["lambda"](1, 23) << endl;
return 0;
}
如果用function包装成员函数,就需要注意一些其他的规则了:
成员函数有静态成员函数和非静态成员函数(普通成员函数),由于有类域的限制,这两种都需要用到域作用限定符。
function包装普通成员函数时,对普通成员函数取地址需要加上“&”运算符:
struct A
{
int mu_fuc(int a,int b)
{
return a + b;
}
};
int main()
{
function<int(int, int)> f = &A::mu_fuc;
return 0;
}
但是编译失败,正确的写法是:需要在参数列表前面加上一个类指针:
function<int(A*,int, int)> f = &A::mu_fuc;
另外的一种写法也是可以达到相同的效果:
function<int(A, int, int)> f1 = &A::mu_fuc;
这两种写法的区别在于调用的时候,传参不同:
第一种,调用时候需要传递对象的地址:
function<int(A*,int, int)> f = &A::mu_fuc;
A a;
cout << f(&a, 1, 3) << endl;
第二种,调用的时候需要传递对象:
//普通成员函数
function<int(A*,int, int)> f = &A::mu_fuc;
A a;
cout << f(&a, 1, 3) << endl;
function<int(A, int, int)> f1 = &A::mu_fuc;
cout << f1(a, 3, 5) << endl;
cout << f1(A(), 8, 9) << endl;
已经实例化的有名对象、匿名对象都是可以的。
而对于静态成员函数,可以不加上“&”操作符,但是为了统一记忆,加上“&”比较好。
正确写法:
function<int(int, int)> f2 = &A::no_mu_fuc;
二、bind绑定
std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。
// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
调用bind的一般形式:
auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象(比如:函数指针,仿函数,lambda),arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。
其实上面的讲述比较抽象,我们可以从特殊开始理解,从具体的实例来理解bind的作用;
double Div(double a, double b)
{
if (b == 0)
throw "div by 0";
else
return a / b;
}
//bind:第一个参数位置是一个可调用对象,接下来的两个参数是
//placeholders命名空间内部的唯一标识符_1,_2,_3,...
auto Newdiv = bind(Div, placeholders::_1,placeholders::_2);
在调用的时候,_1代表传给Newdiv函数参数的第一个,_2代表传给Newdiv函数参数的第二个;这样就实现了顺序调整:
//_1代表Newdiv调用传参的第一个参数:10
//_2代表Newdiv调用传参的第二个参数:8
auto Newdiv = bind(Div, placeholders::_1,placeholders::_2);
cout << Newdiv(10, 8);
//最终传递给Div(10,8)
/*--------------------------------------------------------------*/
//_1代表Newdiv调用传参的第一个参数:10
//_2代表Newdiv调用传参的第二个参数:8
auto Newdiv = bind(Div, placeholders::_2, placeholders::_1);
cout << Newdiv(10, 8);
//最终传递给Div(8,10)
除了顺序调整,也可以实现参数个数调整;
int func(int a, int b, int c)
{
return a + b + c;
}
int main()
{
auto newfunc = bind(func, 100, placeholders::_1, placeholders::_2);
cout << newfunc(9, 4);
return 0;
}
在上述例子中,我们固定第一个参数为100,后面两个参数需要我们自己传递。
当然,调整参数个数和参数顺序也可以混合使用。
完~
未经作者同意禁止转载