std::function 包装器,也叫做 适配器。
头文件如下:#include
- 1
类模板原型如下:
template <class T> function; // undefined template <class Ret, class... Args> class function<Ret(Args...)>;
- 1
- 2
- 3
- 4
作用是 对 可调用对象类型(callable object) 进行 再封装适配,
C++ 中的 function 本质上是一个类模板。
首先我们如果针对相同的功能实现,可以选用下面的方法,但他们都有不同的类型:
举例下面两个代码,都实现了数据的相加,使用方法是一样的,但是他们的类型完全不同。
#include
#include // 包装器的头文件
// 函数写法
int func(int a, int b)
{
cout << "int f(int a, int b)" << endl;
return a + b;
}
// 仿函数写法
struct Functor
{
public:
int operator() (int a, int b)
{
cout << "int operator() (int a, int b)" << endl;
return a + b;
}
};
即使他们的 返回值、参数列表 都相同。
如果有需求要这两个 函数 和 仿函数 声明出一个统一的类型,即 使用 map 的数据结构,把他们放入其中管理,他们类型完全不同,怎么写呢?
// map // 需要声明一个可调用的类型,xxx 这个类型怎么写呢
// int(*pf1)(int,int) = func; // 这样写就没法和别的统一一个类型,无法和 map 规定的模板类型匹配
用 包装器 就可以适配出可调用类型。
包装器定义格式:function<返回值(参数列表)> func = 可调用对象
可适配对象包括:
函数指针 / 函数名、仿函数、lambda
即 function<返回值(参数列表)>
这个类型,就可以标识一系列 相同返回值、参数列表 的可适配对象了。
🌰具体用法如下图举例:
int main()
{
function<int(int, int)> f1 = func;
function<int(int, int)> f2 = Functor(); // 经过包装后这俩对象的类型就是一样的了
function<int(int, int)> f3 = [](int a, int b) {
cout << "[](int a, int b) {return a + b;}" << endl;
return a + b;};
cout << f1(1, 2) << endl; // int f(int a, int b)
cout << f2(10, 20) << endl; // int operator() (int a, int b)
cout << f3(100, 200) << endl; // [](int a, int b) {return a + b;}
return 0;
}
int main()
{
map<string, function<int(int, int)>> opFuncMap;
opFuncMap["函数指针"] = func;
opFuncMap["仿函数"] = Functor();
opFuncMap["lambda"] = [](int a, int b) {
cout << "[](int a, int b) {return a + b;}" << endl;
return a + b;};
cout << opFuncMap["lambda"](1, 2) << endl; // [](int a, int b) {return a + b;}
return 0;
}
给你一个字符串数组 tokens,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式,返回一个表示表达式值的整数。注意:
- 有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
- 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
- 两个整数之间的除法总是向零截断,表达式中不含除零运算。
- 输入是一个根据逆波兰表示法表示的算术表达式。
- 答案及所有中间计算结果可以用 32 位 整数表示。
示例:
- 输入:tokens = [“4”,“13”,“5”,“/”,“+”]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
#include
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
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; }}
};
for (auto str : tokens) // 遍历给出的 逆波兰表达式 字符串
{
// 看是否是算数操作符
if (opfuncMap.count(str)) // 是操作符,将 st 中最 top 的两个数据 pop 出来,用 opfuncMap[str] 函数进行计算,把结果 push 进 st
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
st.push(opfuncMap[str](left, right));
}
else // 不是操作符,即是操作数,push 到 st 中,等待操作符出现后进行计算
{
st.push(stoi(str)); // string 转 int
}
}
return st.top();
}
};
以这个类举例,里面有一个静态成员函数 plusi,一个非静态成员函数plusd:
class Plus
{
public:
Plus(int rate = 2)
:_rate(rate) {}
static int plusi(int a, int b) {return a + b;}
double plusd(double a, double b) {return (a + b) * _rate;}
private:
int _rate = 2;
};
静态成员函数:
非静态成员函数:
&函数名
int main()
{
// 静态成员函数,函数名就行
function<int(int, int)> f1 = Plus::plusi;
// 非静态成员函数,&函数名,另 多一个参数
function<double(Plus, double, double)> f2 = &Plus::plusd; // 这里其实是,Plus是类型,是通过我们创建的对象调用的 plusd
// 调用
cout << f1(1, 2) << endl;
cout << f2(Plus(), 20, 20) << endl; // 非静态,多传一个匿名对象
cout << f2(Plus(3), 20, 20) << endl; // 还可以给匿名对象一个构造值
Plus pl(3); // 给有名对象当然也ok
cout << f2(pl, 20, 20) << endl;
return 0;
}
// 包装器的本质也是仿函数
至于,包装器定义非静态函数成员,传模板参数时,多传一个类类型作为参数,为了填补 this*,为啥不直接写成指针的格式?
因为,如果这里声明成指针,我们使用的时候就不能用匿名对象(因为匿名对象是右值不能取地址)这样调用了,所以把这类型包装器第一个参数设计成指针的写法不方便。
// 不推荐这样写 ~~~
function<double(Plus*, double, double)> f3 = &Plus::plusd;
Plus plus();
cout << f3(&plus, 5, 3) << endl;
std::bind 函数定义在头文件中,是一个函数模板,原型如下:
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);
- 1
- 2
- 3
- 4
- 5
- 6
它就像一个函数包装器,接受一个可调用对象,生成一个新的可调用对
象来 适应和调整 原对象的 参数列表(个数、顺序)
- 一般而言,我们用它可以把一个原本接收 N 个参数的函数 fn,通过绑定一些参数,返回一个接收 M 个参数的新函数(M 可以大于 N,但这么做没什么意义)。
- 同时,使用 std::bind 函数还可以实现参数顺序调整等操作。
调用 bind 的一般形式:
auto newCallable = bind(callable, arg_list);
当我们调用 newCallable 时,newCallable 会调用 callable,并传给它 arg_list 中的参数。
占位符:
arg_list 中的参数可能包含形如 xxx_n
的名字,其中 n 是一个整数,这些参数就是 占位符,表示 newCallable 的参数,它们占据了传递给 newCallable 的参数的 “位置”。
数值 n 表示生成的可调用对象中参数的位置:_1 为 newCallable 的第一个参数,_2 为第二个参数,以此类推。
综上,我们能知道 bind 实现的是对绑定函数参数的调整。
使用占位符用来进行顺序的调整
🌰举例使用如下:
void print(int a, int b)
{
cout << a << endl;
cout << b << endl;
}
int main()
{
print(10, 20);
// _1 表示第一个参数,_2 是第二个
function<void(int,int)> Rprint = bind(print, placeholders::_2, placeholders::_1);
//auto Rprint = bind(print, placeholders::_2, placeholders::_1);
Rprint(10, 20); // 20 10
return 0;
}
auto newCallable = bind(callable, arg_list);
arg_list 部分传入具体的参数,就代表该位置被 指定 了,调用调整后的函数就可以少传部分参数了。
🌰举例使用如下:
/*
class Sub{
public:
Sub(int rate = 3):_rate(rate) {}
int func(int a, int b) {return (a - b)*_rate;} // 这里 func 加 this* 有三个参数
int operator()(int x, int y) {return x * y;}
private:
int _rate;
};
*/
int main()
{
// 不调整参数时
function<int(Sub, int, int)> fsub = &Sub::func; // 要加上取地址符
cout << fsub(Sub(1), 10, 20) << endl;
// bind 第一个参数
function<int(int, int)> fsub2 = bind(&Sub::func, Sub(3), placeholders::_1, placeholders::_2);
cout << fsub2(10, 20) << endl;
// bind 第二个参数:注意
function<int(Sub, int)> fsub3 = bind(&Sub::func, placeholders::_1, 100, placeholders::_2);
cout << fsub3(Sub(2), 20) << endl;
return 0;
}
还是之前 给逆波兰表达式求值的代码,在用法上面扩展一下(只为使用语法):
int plus(int a, int b)
{
return a + b;
}
class Sub{
public:
Sub(int rate = 3):_rate(rate) {}
int func(int a, int b) {return (a - b)*_rate;} // 这里 func 加 this* 有三个参数
int operator()(int x, int y) {return x * y;}
private:
int _rate;
};
int mul(int x, int y)
{
return x * y;
}
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
map<string, function<int(int, int)>> opfuncMap =
{
{"+",[](int x, int y) {return x + y; }}, // lambda 表达式
{"-",Sub()}, // 可以接收仿函数
{"*",mul}, // 可以接受函数指针
{"/",[](int x, int y) {return x / y; }},
{"&",bind(&Sub::func,Sub(3),placeholders::_1,placeholders::_2)}
}; // 别的参数都是两个,最后一个恰好是三个并且有一个能绑死,就刚好用在这里!
// 代码略
// 遍历给出的 逆波兰表达式 字符串
// 看是否是算数操作符
// 是操作符,将 st 中最 top 的两个数据 pop 出来,用 opfuncMap[str] 函数进行计算,把结果 push 进 st
// 不是操作符,即是操作数,push 到 st 中,等待操作符出现后进行计算
// return st.top();
}
};
🥰如果本文对你有些帮助,请给个赞或收藏,你的支持是对作者大大莫大的鼓励!!(✿◡‿◡) 欢迎评论留言~~