function包装器的意义
一:function包装器的概念
二:需要function包装器的场景
// 包装器
// 函数模板
template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}//一个普通函数
double f(double i)
{return i / 2;
}//一个仿函数
struct Functor
{double operator()(double d){return d / 3;}
};int main()
{// 函数名cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}
运行结果:
从这个运行结果,要看出两个点:
①:因为实例化出了三份不同的类型的类,所以conut进行++是互不影响的
②:因为实例化出了三份不同的类型的类,所以count的地址是不同的
Q:为什么这段代码需要包装器 function
?
A:这段代码演示了 C++ 中不同类型的可调用对象(函数指针、函数对象、Lambda)在模板实例化时实例化出不同类型的行为(模板 useF
会生成多个实例)
Q:那如果我们可能希望 count 共享,也就是说希望 useF 无论传入什么可调用对象,都共享同一个 count,那怎么办呢?
A:这就需要用到function包装器了!
三:function包装器的作用
1:function包装器的书写格式
看图不好理解,直接上例子吧:
比如我们对上面的f函数进行包装,就写成这样就行:
double f(double i)
{return i / 2;
}#include<functional>int main()
{//对f函数进行封装function<double(double)> fc1 = f;return 0;
}
语法解析:
(1) 头文件
#include<functional> // 必须包含此头文件
(2) 声明格式
std::function<返回值类型(参数类型1, 参数类型2, ...)> 变量名;
返回值类型:被包装的可调用对象的返回类型
参数类型:被包装的可调用对象的参数类型(可以有多个)
所以我们对f函数进行包装,才写作如下:
function<double(double)> fc1 = f;
2:对场景进行包装
#include<iostream>
using namespace std;// 包装器
// 函数模板
template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}double f(double i)
{return i / 2;
}struct Functor
{double operator()(double d){return d / 3;}
};#include<functional>int main()
{// 函数指针function<double(double)> fc1 = f;/*fc1(11.11);*/cout << useF(fc1, 11.11) << endl;// 函数对象function<double(double)> fc2 = Functor();//fc2(11.11);cout << useF(fc2, 11.11) << endl;// lambda表达式function<double(double)> fc3 = [](double d)->double { return d / 4; };//fc3(11.11);cout << useF(fc3, 11.11) << endl;return 0;
}
运行结果:
从这个运行结果,要看出两个点:
①:因为实例化出的是一份公共的类,所以conut进行++是互相影响的
②:因为实例化出的是一份公共的类,所以count的地址是相同的
四:用包装器解决逆波兰表达式求值
题目介绍及以前的解法:逆波兰表达式求值-CSDN博客
1:思路对比
a:原本思路:
遇到操作数时,压入栈中;遇到运算符时,弹出栈顶的两个操作数进行运算,计算得到的结果再压入栈;最终栈顶元素即为结果。
b:包装器思路:
用map来
创建了一个从字符串(操作符)到对应操作函数的映射。这使得我们可以通过操作符字符串直接查找并调用相应的运算函数。
且map的v值类型是function<int(int,int)>,其用于包装接受两个整数参数并返回一个整数的操作函数;对应的操作函数呢,我们采取lambda表达式,显得简洁;
如图:
map<string, 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;}}};
Q:对应的操作函数一定要用lambda表达式吗?
A:不一定,但是lambda一定是最简洁的,以下展示了v值对应的操作函数用其他方法的冗余效果
a:使用普通函数
int add(int x, int y) { return x + y; }
int sub(int x, int y) { return x - y; }
int mul(int x, int y) { return x * y; }
int divi(int x, int y) { return x / y; }map<string, function<int(int,int)>> opFuncMap = {{"+", add},{"-", sub},{"*", mul},{"/", divi}
};
b:使用仿函数
struct Add {int operator()(int x, int y) { return x + y; }
};
struct Sub {int operator()(int x, int y) { return x - y; }
};
// 同理定义Mul和Divmap<string, function<int(int,int)>> opFuncMap = {{"+", Add()},{"-", Sub()},{"*", Mul()},{"/", Div()}
};
解释:
可以看出,不管是普通函数还是仿函数,都需要额外定义函数或类,并不优秀
而lambda直接在初始化映射表时完成定义,非常优秀
2:代码
class Solution {
public:int evalRPN(vector<string>& tokens) {// 定义操作符到函数的映射map<string, 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;}}};stack<int> st; // 用于存储操作数的栈for(auto& str : tokens) {if(opFuncMap.count(str)) { // 如果是操作符int right = st.top(); st.pop();int left = st.top(); st.pop();st.push(opFuncMap[str](left, right));}else { // 如果是操作数st.push(stoi(str));}}return st.top();}
};
3:代码解读
相比于老方法,我们的设计显得非常的细腻;而不是以前直接暴力判断字符是否为操作字符。
值得再说一句的是,map定义时候:
string是一个类型,而function<int(int,int)>也是一个类型;
string接收"+"这种字符串,而function<int(int,int)>接收[](int x, int y){return x + y;}这样一个lambda
五:包装器的几种用法的格式
1:普通函数的包装
int f(int a, int b) {return a + b;
}// 包装普通函数
function<int(int, int)> fc1 = f;
特点:
-
直接使用函数名
f
赋值给function
-
调用方式与原函数完全相同
-
最简单直接的包装方式
2:静态成员函数的包装
class Plus {
public:static int plusi(int a, int b) {return a + b;}
};// 包装静态成员函数
function<int(int, int)> fc2 = &Plus::plusi;
特点:
-
需要使用
&类名::函数名
的语法获取函数指针(&一定得要,语法规定) -
调用方式与普通函数相同
3:非静态成员函数的包装
静态成员函数代码:
class Plus
{
public:double plusd(double a, double b){return a + b;}
};//对其中的plusd函数进行包装
方式一:使用对象值
使用对象值去包装非静态函数的两种写法:写法1:
function<double(Plus, double, double)> fc3 = &Plus::plusd;
//使用的时候,第一个参数匿名构造一个对象
cout << fc3(Plus(), 1, 1) << endl;或者写法2:
Plus plus;
function<double(Plus, double, double)> fc4 = &Plus::plusd;
cout << fc4(plus, 1, 1) << endl;
特点:
-
包装器的第一个参数的类型是类类型(类这个类型),所以是Plus;因为成员函数的第一个参数是
this
指针,所以要用类类型Plus去对应 -
调用时,第一个参数要么是之前实例化的对象(如plus);要么就是匿名构造一个对象(Plus())
方式二:使用对象指针
//一定要实例化Plus plus;function<double(Plus*, double, double)> fc5 = &Plus::plusd;//第一个参数是堆对象取地址cout << fc5(&plus, 1, 1) << endl;
特点:
-
function第一个参数的类型必须是类指针
Plus*
-
一定需要先创建对象实例
plus
-
调用时,第一个参数需要传入对象地址
&plus
六:bind绑定对于包装器的优化
bind叫作绑定,有两个作用:
①:更改参数的顺序(不重要)
②:更改参数的个数(重要)
1:更改参数的顺序
int Sub(int a, int b)
{return a - b;
}int main()
{int x = 10, y = 20;cout << Sub(x, y) << endl; // 正常调用,输出 -10auto f1 = bind(Sub, placeholders::_2, placeholders::_1);//用bind更改参数的顺序后cout << f1(x, y) << endl; // 输出 10return 0;
}
运行结果:
解释:
-
placeholders
是标准库定义的命名空间,位于<functional>
头文件中。 -
bind(Sub, placeholders::_2, placeholders::_1)
将第二个参数作为第一个参数,第一个参数作为第二个参数() -
原本
Sub(10,20)
是 10-20=-10 -
调整后变为
Sub(20,10)
是 20-10=10
2:更改参数的个数
我们之前五中的例子,对于非静态成员函数的包装后,使用的时候,第一个参数需要传我们不想手动传的东西,如:
cout << fc3(Plus(), 1, 1) << endl;cout << fc4(plus, 1, 1) << endl;cout << fc5(&plus, 1, 1) << endl;
那我们现在就可以用bind,去更改参数的个数,让我们传的时候只传后面的1,1,如下
cout << fc3(1, 1) << endl;
cout << fc4(1, 1) << endl;
cout << fc5(1, 1) << endl;
更改参数个数的代码:
class Plus
{
public:double plusd(double a, double b){return a + b;}
};int main()
{//1:使用对象值绑定 (plus)//原先调用的第一个参数是plus 被省略了Plus plus;function<double(double, double)> fc4 = bind(&Plus::plusd, plus, placeholders::_1, placeholders::_2);cout << fc4(1, 1) << endl;//2:使用临时对象值绑定 (Plus())//原先调用的第一个参数是Plus() 被省略了function<double(double, double)> fc3 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);cout << fc3(1, 1) << endl;//3:使用对象指针绑定 (&plus)//原先调用的第一个参数是&plus 被省略了function<double(double, double)> fc5 = bind(&Plus::plusd, &plus, placeholders::_1, placeholders::_2);cout << fc5(1, 1) << endl;return 0;
}
运行结果: