目录
一、function包装器
1.1 可调用对象
我们平时使用的普通函数、函数指针、仿函数和Lambda表达式都是可调用对象,它们不仅可以作为其他函数的参数传入,还可以作为其他函数的返回值
虽然这些可调用对象的使用方法基本都差不多,但其定义方式大有不同,例如:
void swap(int& a, int& b) //普通函数
{
int tmp = a;
a = b;
b = tmp;
}
void(*swap_p)(int&, int&) = swap; //函数指针
class Swap
{
public:
void operator()(int& a, int& b) //仿函数
{
int tmp = a;
a = b;
b = tmp;
}
};
auto lam_swap = [](int& a, int& b)-> void { //Lambda表达式
int tmp = a;
a = b;
b = tmp;
};
这就导致我们在对这些不同的函数包装器进行保存的时候无法以统一的方式保存
例如:
假设一个函数模板的参数需要我们传入一个可调用对象,当我们传入不同的可调用对象时,这个函数也会被实例化多次,例如:
template<class F>
int tem_func(F f)
{
static int count = 0;
cout << "count=" << ++count << endl; //打印count的值
cout << "&count=" << &count << endl; //打印count的地址
return f();
}
int func()
{
return 1;
}
class Func
{
public:
int operator()()
{
return 1;
}
};
auto lam_func = []()->int {return 1; };
可以看到,count是一个静态变量,但却有三个不同的地址,说明函数模板被实例化了三次
于是C++11引入了一种适配器,即function包装器,能够以统一的方式来保存这些可调用对象
1.2 概念
std::function包装器是C++11引入的一个针对可调用对象的适配器,其本质是一个类模板
我们可以把包装器看作一个壳子,内部存放不同类型的可调用对象,这样对外看来它们都是一样的
要使用function包装器,首先得引入头文件<functional>
具体使用方法也并没有那么复杂,例如:
void swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main()
{
function<void(int&, int&)> f1 = swap;
return 0;
}
可以看到,我们只需要以这种方式传入可调用对象的返回值类型和参数类型,即可将一个可调用对象用包装器包装起来
不止是普通函数,其他可调用对象也是一样
void swap(int& a, int& b) //普通函数
{
int tmp = a;
a = b;
b = tmp;
}
void(*swap_p)(int&, int&) = swap; //函数指针
class Swap
{
public:
void operator()(int& a, int& b) //仿函数
{
int tmp = a;
a = b;
b = tmp;
}
};
auto lam_swap = [](int& a, int& b)-> void { //Lambda表达式
int tmp = a;
a = b;
b = tmp;
};
int main()
{
function<void(int&, int&)> f1 = swap;
function<void(int&, int&)> f2 = swap_p;
function<void(int&, int&)> f3 = Swap();
function<void(int&, int&)> f4 = lam_swap;
return 0;
}
通过使用function包装器对可调用对象进行统一方式的包装,就可以避免前面的问题了:
1.3 应用场景
OJ链接:LCR 036. 逆波兰表达式求值 - 力扣(LeetCode)
在这道题的核心思想是使用栈,遍历tokens,遇到数字则入栈,遇到运算符则取出栈顶的两个数字进行运算,并将结果重新入栈
以前,我们需要通过switch语句判断符号的类型并运算,代码重复度高,不够优雅
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for(auto &s : tokens)
{
if(s == "+" || s == "-" || s == "*" || s == "/")
{
int r = st.top();
st.pop();
int l = st.top();
st.pop();
cout << l << " " << r << endl;
switch(s[0])
{
case '+':
st.push(l + r);
break;
case '-':
st.push(l - r);
break;
case '*':
st.push(l * r);
break;
case '/':
st.push(l / r);
break;
}
}
else
st.push(stoi(s));
}
return st.top();
}
};
现在我们就可以结合map、lambda表达式和function包装器重新做一下这道题了
代码如下:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
map<string, function<int(int, int)>> cmd = {
{"+", [](int a, int b)->int { return a + b; }},
{"-", [](int a, int b)->int { return a - b; }},
{"*", [](int a, int b)->int { return a * b; }},
{"/", [](int a, int b)->int { return a / b; }}
};
stack<int> st;
for(auto &s : tokens)
{
if(cmd.count(s))
{
int r = st.top();
st.pop();
int l = st.top();
st.pop();
st.push(cmd[s](l, r));
}
else
st.push(stoi(s));
}
return st.top();
}
};
需要注意的是,当我们使用包装器包装类成员函数的时候,需要加上类域并且取地址
对于非静态的类成员函数,除了其显式定义的参数外,还需要在参数中加上this指针,例如:
二、bind绑定器
std::bind是一个函数模板,可以接受一个可调用对象,并生成一个新的可调用对象
有人说了,这不是多此一举吗?实际上我们可以通过这种方式来完成修改原来的可调用对象参数的效果
例如我们想修改某个函数的顺序:
其中placeholders::_2代表原函数的第二个参数,placeholders::_1代表原函数的第一个参数
_n作为占位符代表了原来的可调用对象在bind之后生成的可调用对象的参数顺序
又例如我们想让某个参数是固定的值,就可以直接在bind中设置该参数的值
这种方式就能解决上面提到的function包装器包装非静态类成员函数时每次都要添加this指针的问题
例如:
完.