在寄快递的时候,快递会进行一次包装,这样我们就可以统一的在上面贴上快递信息,随后以统一的形式管理所有快递。包装器也是如此,包装器可以将具有相似属性的东西包装起来成为一个整体。
function
如果一个变量f,可以按照
f()
的形式调用函数,那么称f
是一个可调用对象
回顾一下,现在我们有那些可调用对象
:
- 函数指针,函数名(函数名的本质就是函数指针)
- 仿函数实例化出的对象
lambda
表达式
这三者,都可以直接加一对()
进行函数调用。它们都有各自的缺点:
- 函数指针,函数名:类型复杂,不好用
- 仿函数实例化出的对象:哪怕参数返回值都相同,仿函数之间的类型也不同
lambda
表达式:类型是随机的,必须用auto
接收
可以看到,这三者都有类型方面的大问题,我们也没有一种方式可以把所有参数类型和返回值类型相同的函数,统一的管理起来,让它们都变成一个类型?
包装器function
就可以做到该工作,function
被包含在头文件<functional>
中,是一个类模板,模板原型如下:
template <class T> function;
template <class Ret, class... Args>
class function<Ret(Args...)>;
其语法为:function<返回值(参数列表)>
,只要所有返回值和参数列表相同的可调用对象,经过这一层封装,都会变成相同的类型。
比如我们现在有如下三个函数:
double func(double x)
{
return x / 2;
}
struct Functor
{
double operator()(double x)
{
return x / 3;
}
};
int main()
{
auto lambadaFunc = [](double d) {return d / 4; };
return 0;
}
分别是func
函数,Functor
仿函数,以及lambda
表达式lambadaFunc
。它们的返回值都是double
,参数类型也是double
,因此可以经过包装器包装为function<double<double>>
。
如下:
function<double(double)> func1 = func;
function<double(double)> func2 = Functor();
function<double(double)> func3 = lambadaFunc;
此时,三者的类型就都是function<double(double)>
了。
有了这一层包装器,在需要统一管理函数时,就很方便了。比如说我现在要搞一个计算器的map
,往map
中输入哪一个操作符,就调用哪一个函数:
map<char, function<int(int, int)>> opFuncMap = {
{'+', [](int x, int y) {return x + y; }},
{'-', [](int x, int y) {return x - y; }},
{'*', [](int x, int y) {return x * y; }},
{'/', [](int x, int y) {return x / y; }}
};
由于+ - * /
的函数都是lambda
表达式,四个表达式的类型都是不可知的,map
的第二个模板参数就不知道是啥了。不过我们可以通过function
进行包装,把所有函数都包装成function<int(int, int)>
类型,最后就可以通过map
统一管理了。
我们最后就可以这样调用函数:
opFuncMap['+'](1, 2);
opFuncMap['-'](1, 2);
opFuncMap['*'](1, 2);
opFuncMap['/'](1, 2);
好的,我来补充一下关于 function
包装器的常用操作:
function 的常用操作
-
拷贝和赋值:
-
function
对象支持拷贝构造和赋值操作,可以将一个function
对象赋值给另一个。 - 拷贝或赋值
function
对象时,会拷贝/赋值底层的可调用对象。 - 例如:
function<int(int)> f1 = [](int x) { return x * x; }; function<int(int)> f2 = f1; // f2 现在也是一个 lambda 表达式
-
-
调用:
-
function
对象支持函数调用操作符()
,可以像调用普通函数一样调用function
对象。 - 调用
function
对象时,会调用底层的可调用对象。 - 例如:
function<int(int, int)> add = [](int x, int y) { return x + y; }; int result = add(3, 4); // result 为 7
-
-
判空:
-
function
对象支持bool
类型转换,可以用于判断function
对象是否为空(未初始化)。 - 例如:
function<int(int)> f; if (!f) { cout << "f is empty" << endl; }
-
-
重置:
-
function
对象提供reset()
成员函数,可以将function
对象重置为未初始化状态。 - 例如:
function<int(int)> f = [](int x) { return x * x; }; f.reset(); // f 现在为空
-
通过这些常用操作,我们可以更灵活地使用 function
包装器,满足各种需求。比如在需要统一管理不同类型的可调用对象时,function
就显得尤为有用。
bind
bind
翻译后为绑定,其可以对参数进行绑定。其主要有两个功能:改变参数顺序
,给指定参数绑定固定值
。
语法:bind
是一个函数模板,其接收多个参数,第一个参数为可调用对象,后续参数为该可调用对象的参数。这个参数的语法比较特别,C++11后新增一个命名空间域placeholders
,其内部会存储很多变量,这些变量用于函数的传参,变量的名字为_x
表示第x
个参数。
比如以下代码中:
int sub(int a, int b)
{
return a - b;
}
int main()
{
auto f1 = bind(sub, placeholders::_2, placeholders::_1);
f1(3, 5);
return 0;
}
对于bind(sub, placeholders::_2, placeholders::_1);
来说,sub
这个参数是一个可调用对象。placeholders::_2
表示第二个参数,placeholders::_1
表示第一个参数。
比如这个f1
最后拿到了这个bind
封装的函数,那么f1(3, 5)
执行的并不是3 - 5
,而是5 - 3
。
这是因为我们特地把placeholders::_2
写在前面,f1(3, 5)
把第二个5
传给了placeholders::_2
,把第一个3
传给了placeholders::_1
。
而最后调用sub
函数的时候,placeholders::_1
会被传给sub
的第一个参数,placeholders::_2
则会传给sub
的第而个参数。这样我们就完成了函数参数顺序的改变。
再比如以下代码:
int sub(int a, int b)
{
return a - b;
}
int main()
{
auto f2 = bind(sub, 3.14, placeholders::_1);
f2(10);
return 0;
}
bind(sub, 3.14, placeholders::_1)
第一个参数为可调用对象sub
,第二个参数是一个固定值3.14
,那么如果通过f2
调用该sub
函数,参数a
都固定为3.14
。比如f2(10)
就只传了一个参数,再去调用sub
时,就完成3.14 - 10
的操作。因此我们可以通过sub
把某个参数绑定为固定值。
处理函数返回值:
bind
函数还可以用来处理函数的返回值。示例如下:
int add(int x, int y) {
return x + y;
}
int main() {
// 绑定 add 函数,并将返回值乘以 2
auto doubleAdd = bind([](int result) { return result * 2; }, add(placeholders::_1, placeholders::_2));
int result = doubleAdd(3, 4);
cout << result << endl; // 输出 14
return 0;
}
在这个例子中,我们使用 bind
将 add
函数的返回值传递给一个 lambda 表达式,该 lambda 表达式将返回值乘以 2。最终,doubleAdd
函数会返回 add
函数返回值的两倍。
通过这种方式,我们可以对函数的返回值进行各种处理,如格式化、取模等,从而实现更复杂的功能。
总之,bind
不仅可以用于改变参数顺序和绑定参数,还可以用于绑定成员函数以及处理函数返回值,是一个非常强大的工具。