为什么要有包装器
我们现在学过的可调用对象太多了:
- 函数指针
- 仿函数对象
- lambda表达式对象
ret = func(x);
这里的func可能是面的任意一种
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;
}
上面的T useF(F f, T x)
的f用来接收一个可调用对象,x用来接收一个值,函数内定义了一个静态变量,初始化为0,每次调用这个这个函数就会把静态变量++;
main函数分别传入三种可调用对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uDDo6LRB-1670438758976)(https://massachusetts-pubic.oss-cn-guangzhou.aliyuncs.com/%E5%9B%BE%E5%BA%8A/202210102133749.png)]
可以发现:同一个静态变量三次++,一直都是1,三次的地址也不同,所以这个静态变量是有三份的,也就是说,函数重载了三份。
所以说,编译器在推演的时候,因为F可能是三种类型的调用,所以就要实例化三份useF
函数
那有没有办法让这里useF只实例化成一份呢?
C++11为了将可调用类型统一,就提出了包装器概念
function包装器
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
std::function在头文件
类模板原型:
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
- Ret: 被调用函数的返回类型
- Args…:被调用函数的形参
使用方法:
function<int(int, int)> func1 = f;
使用的时候我们可以把它看成是一个类模板,模板参数用这样一个特殊的方式
#include <functional>
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
// 函数名(函数指针)
std::function<int(int, int)> func1 = f;
cout << func1(1, 2) << endl;
// 函数对象
std::function<int(int, int)> func2 = Functor();
cout << func2(1, 2) << endl;
// lamber表达式
std::function<int(int, int)> func3 = [](const int a, const int b){return a + b; };
cout << func3(1, 2) << endl;
// 类的成员函数
//静态成员函数——没有this指针
std::function<int(int, int)> func4 = &Plus::plusi;//可以不写&
cout << func4(1, 2) << endl;
//非静态——有this指针
std::function<double(Plus, double, double)> func5 = &Plus::plusd;//包装的时候必须有一个this的位置(必须写&)
cout << func5(Plus(), 1.1, 2.2) << endl;//通过匿名对象调用成员函数
return 0;
}
赋值nullptr&bool判断
一个function对象可以不进行显式初始化,
也可以用nullptr进行初始化,
甚至可以用nullptr对一个已经定义的对象进行赋值,
但显然初始化为空的function对象是不允许调用的(强制调用,内部会抛出异常)
所以,在调用之前需要进行检测,好在function内提供了operator bool
,
直接对一个function对象进行if()
,即可判断此包装器是否可以调用
int main()
{
std::function<int(int, int)> foo, bar;
foo = [](int, int) -> int { return 0; }; // 用lamabda表达式进行赋值
bar = nullptr; // 赋值为空
foo.swap(bar); // 同类function支持swap
std::cout << "foo is " << (foo ? "callable" : "not callable") << ".\n";
std::cout << "bar is " << (bar ? "callable" : "not callable") << ".\n";
return 0;
}
运行结果:
foo is not callable.
bar is callable.
包装器的使用场景:字符串对方法的索引
我们可以在如下这道题中体会一下包装器的使用
这道题会给一个vector每个元素都是一个字符串,每个字符串存一个数字或一个运算符
这些数字与符号排列组合成的表达式并非我们平时使用的如:
(1+2)*3
(中缀表达式)而是
1 2 + 3 *
(后缀表达式,也称逆波兰表达式)要计算下面的逆波兰表达式,
只需要从前向后将值依次取出值,放入一个栈中
当遇到运算符,就把栈顶的两个元素取出,进行当前运算符的运算
如:当前栈中存入的元素有
1 2
,字符串读到了+
,则取出栈顶的1 2
,进行1+2的运算
把运算得数放到栈中
读完整个字符串,栈顶的元素就是运算结果
思路很简单,但是会遇到一个问题:如何把一个“+”
的字符串变为一个加法算呢?
当然,我们可以像这样
if(strcmp(str,”+”)==0)
{
st.push(a+b);
}
else if...
把每个运算符都设置一个分支
但是写起来多少是有一点恶心的
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> stk;
int n = tokens.size();
for (int i = 0; i < n; i++) {
string& token = tokens[i];
if (isNumber(token)) {
stk.push(atoi(token.c_str()));
} else {
int num2 = stk.top();
stk.pop();
int num1 = stk.top();
stk.pop();
switch (token[0]) {
case '+':
stk.push(num1 + num2);
break;
case '-':
stk.push(num1 - num2);
break;
case '*':
stk.push(num1 * num2);
break;
case '/':
stk.push(num1 / num2);
break;
}
}
}
return stk.top();
}
bool isNumber(string& token) {
return !(token == "+" || token == "-" || token == "*" || token == "/");
}
};
//作者:LeetCode-Solution
//链接:https://leetcode.cn/problems/evaluate-reverse-polish-notation/solution/ni-bo-lan-biao-da-shi-qiu-zhi-by-leetcod-wue9/
//来源:力扣(LeetCode)
我们是否可以用一个map,把字符串和对应的方法联系起来?
map<string,方法类型> opFuncMap = {{”+“,加法运算},{”-“,减法运算}…}
使用的时候就可以:
opFuncMap[str](a,b);
但是问题来了,如果方法用一个lambda表达式来写,那模板参数的类型接收什么?
有四种运算,也就是个lambda表达式,对应四个类型,有没有一个类型能接收这四种lambda表达式呢?
那就是包装器
包装方法:
map<string, function<int(int, int)>> opFuncMap =
{
{“+”,[](long long x,long long y) {return x + y; }},
{“-”,[](long long x,long long y) {return x - y; }},
{“*”,[](long long x,long long y) {return x * y; }},
{“/”,[](long long x,long long y) {return x / y; }}
};
使用方法:
st.push(stoi(str));
多少又有点多态的味道了,虽然说这个语法长相越来越不像C++了,但是用着爽啊
题解:
class Solution {
public:
long long evalRPN(vector<string>& tokens) {
stack<long long> st;
map<string, function<int(int, int)>> opFuncMap =
{
{"+",[](long long x,long long y) {return x + y; }},
{"-",[](long long x,long long y) {return x - y; }},
{"*",[](long long x,long long y) {return x * y; }},
{"/",[](long long x,long long y) {return x / y; }}
};
for (auto& str : tokens)
{
if (opFuncMap.count(str))//是操作符
{
long long right = st.top();
st.pop();
long long left = st.top();
st.pop();
st.push(opFuncMap[str](left, right));
}
else//是操作数
{
st.push(stoi(str));
}
}
return st.top();
}
};
虽然说代码没变多短,但是如果学会了map、包装器、lambda表达式,下面这段代码的封装性更强,可读性是更高;
相比C语言的面向过程,C++11新增的的这些语法把C++这门语言的面向对象更深化了几分。
bind
情景
我们看如下这种情况
#include <functional>
#include <map>
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
double plusd(double a, double b)
{
return (a + b);
}
private:
};
int main()
{
map<string, std::function<int(int, int)>> opFuncMap = {
{"普通函数指针",f},
{"函数对象",Functor()},
//{"成员函数指针", &Plus::plusd}//无法匹配
};
return 0;
}
我们想用一个map存储各种可调用类型,
当存到成员函数的时候出现了问题
因为成员函数的参数默认是存在一个this指针的,所以包装器接收的时候就得为这个this指针留位置:
std::function<double(Plus, double, double)> func = &Plus::plusd;
于是就出现了,明明是同一类调用方式,却不能用一个function接收
所以,有没有办法吧那个this指针隐藏掉呢?
概念
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);
使用方法:
int f_src(int a, int b, int c)
{
return a * (b - c);
}
int main()
{
cout << f_src(10, 1, 2) << endl; // 10 * (1 - 2) = -30
function<int(int, int)> f_dst = bind(f_src, 10, placeholders::_2, placeholders::_1);
cout << f_dst(2, 1) << endl; // 10 * (1 - 2) = -30
return 0;
}
运行结果:
-30
-30
如上,bind的参数:
- 第一个参数:需要被绑定修改的调用对象(传入函数名)
- 后面再输入n个参数,n是被绑定函数的参数个数
上面调用f_dst(2, 1);
时,参数按照如下方式传递:
- 2 --> placeholders::_1 --> c
- 1 --> placeholders::_2 --> b
- 10 --> a
相当于调用f_src(10, 1, 2);
其中 placeholders::_1
, placeholders::_2
(_1和_2属于placeholders
命名空间域)相当于两个占位参数
如果需要写死的参数是this指针,可以传入一个匿名对象、非匿名对象的指针或者非匿名对象
此时我们就可以通过写死this指针,把绑定后的对象赋给map的function了:
#include <functional>
#include <map>
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
double plusd(double a, double b)
{
return (a + b);
}
private:
};
int main()
{
map<string, std::function<int(int, int)>> opFuncMap = {
{"普通函数指针",f},
{"函数对象",Functor()},
{"成员函数指针", bind(&Plus::plusd/*函数指针*/, Plus()/*匿名对象*/, placeholders::_1, placeholders::_2)}//绑定返回一个两个参数的调用对象
};
return 0;
}