function包装器
function的介绍
function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
function类模板的定义如下:
cpp
Func();
上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能 是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下! 为什么呢?我们继续往下看
cpp
template<class F, class T>
T useF(F f, T n)
{
static int count = 0;
cout << "count: " << ++count << endl;
cout << "&count: " << &count << endl;
return f(n);
}
double f(double x)
{
return x;
}
struct Functor
{
double operator()(double i)
{
return i;
}
};
int main()
{
//使用函数指针
useF(f, 10.0);
//使用仿函数
useF(Functor(), 20.0);
//使用lambda表达式
useF([](double a)->double{return a; }, 30.0);
return 0;
}
运行测试:
由于函数指针,仿函数,lambda表达式是不同类型,因此useF模板类在编译阶段被实例化成了三份,开辟了三个不同栈帧
这里实际上没有必要实例化出三份useF函数,因为三次调用useF函数时传入的可调用对象虽然是不同类型的,但这三个可调用对象的返回值和形参类型都是相同的
为了解决上述问题,C++引入了包装器 function
function包装器同一类型
使用function需要包含functional头文件
类模板原型如下:
cpp
template <class T> function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args...:被调用函数的形参
function包装器可以对可调用的对象进行包装,包括函数指针(函数名),仿函数(函数对象),lambda表示式,类的成员函数(包括静态成员函数)。比如:
cpp
// 使用方法如下:
#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;
// 类的非静态成员函数
std::function<int(int, int)> func4 = &Plus::plusi;
cout << func4(1, 2) << endl;
// 类的静态成员函数
std::function<double(Plus, double, double)> func5 = &Plus::plusd;
cout << func5(Plus(), 1.1, 2.2) << endl;
return 0;
}
对以上代码的说明:
包装时指明返回值类型和各形参类型,然后将可调用对象赋值给function包装器即可,包装后function对象就可以向函数一样使用
取非静态成员函数的地址必须使用取地址运算符(&),但是取静态成员的地址可以不使用取地址运算符(&)。建议不管是静态还是非静态的成员函数,最好都加上取地址运算符(&)
包装非静态的成员函数是需要注意,非静态成员函数的第一个参数是隐藏的this指针,因此在包装时需要指明第一个形参的类型为类的类型,在使用时也需要也需要显示的传this指针
function简化代码
使用包装器解决上述实例化多份问题
使用包装器分别对着三个可调用对象进行包装,然后再用这三个包装后的可调用对象来调用useF函数,这时就只会实例化出一份useF函数
cpp
template<class F, class T>
T Func(F f, T n)
{
static int count = 0;
cout << "count: " << ++count << endl;
cout << "&count: " << &count << endl;
return f(n);
}
int f(int a)
{
return a;
}
struct Functor
{
public:
int operator() (int a)
{
return a;
}
};
int main()
{
// 函数名(函数指针)
std::function<int(int)> func1 = f;
// 函数对象
std::function<int(int)> func2 = Functor();
// lamber表达式
std::function<int(int)> func3 = [](const int a)
{return a; };
//三个函数被包装器包装,类型都是function<int(int)>
vector<function<int(int)>> f = {func1, func2, func3};
int x = 10;
for (auto F : f)
{
Func(F, x);
}
return 0;
}
运行测试:
可以看到使用包装器之后,现在调用Func时并没有开辟三个栈帧
**根本原因:**这三个函数起被包装后,类型得到了统一,都是function类型,所以它们只会开辟一个函数栈帧
我们可以使用包装器对逆波兰表达式进行优化
先看原先的代码:
cpp
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for(auto& str : tokens)
{
if(str == "+" || str == "-" || str == "*" || str == "/")
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
switch(str[0])
{
case '+':
st.push(left + right);
break;
case '-':
st.push(left - right);
break;
case '*':
st.push(left * right);
break;
case '/':
st.push(left / right);
break;
}
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
使用包装器修改之后:
cpp
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
map<string, function<int(int, int)>> f = {
{"+", [](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(f.count(str))
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
st.push(f[str](left, right)); //注意:左右操作数
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
function包装器的意义:
将可调用的对象的类型进行统一,便于我们进行统一化管理
包装之后明确了返回值类型和形参类型,更加方便使用者使用
bind(绑定)
bind的介绍
bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来"适应"原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M 可以大于N,但这么做没什么意义)参数的新函数。同时,使用bind函数还可以实现参数顺序调整等操作。
原型如下:
cpp
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);
fn : 可调用对象
args... : 要绑定的参数列表:值或占位符
调用bind的一般形式
调用bind的一般形式:auto newCallable = bind(callable,arg_list);
callable:需要包装的可调用对象
newCallable:一个新的可调用对象
arg_list:是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是"占位符",表示 newCallable的参数,它们占据了传递给newCallable的参数的"位置"。数值n表示生成的可调用对 象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。
此外,除了用auto接好包装后的可调用对象,也可以使用function接收包装好的可调用对象
bind函数无意义绑定
cpp
int Mul(int x, int y)
{
return x - y;
}
int main()
{
//无意义的绑定
function<int(int, int)> f = bind(Mul, placeholders::_1, placeholders::_2);
cout<<f(10, 20);
return 0;
}
placeholders::_1绑定的第一个参数,placeholders::_2绑定的第二个参数。和原来没有绑定的1可调用顺序是一样的,所以这种绑定是无意义
绑定固定参数
如果想把Plus函数的第二个参数固定绑定为10,可以在绑定时将参数列表的placeholders::_2设置为10。
比如:
cpp
int sub(int x, int y)
{
return x - y;
}
int main()
{
function<int(int)> f = bind(sub, placeholders::_1, 30);
cout << f(10) << endl;
return 0;
}
此时调用绑定后新生成的可调用对象时就只需要传入一个参数,它会将该值与10相加后的结果进行返回
调整传参顺序
如果向交换两个参数的相减的顺序,那么我们只需要将placeholders::_1和placeholders::_2的位置交换一下就可以了
cpp
int sub(int x, int y)
{
return x - y;
}
int main()
{
function<int(int, int)> f = bind(&Plus::plusi, placeholders::_2, placeholders::_1);
cout << f(10, 30) << endl;
return 0;
}
原本输出是-20,现在输出为:20
原因:再新生成的可调用对象时,传入第一个参数会传给placeholders::_1,传入第二个参数会传给placeholders::_2因此可以再绑定时通过控制placeholders::_n的位置,来控制第n个参数的传递位置
bind绑定的意义
将第一个函数的某些参数绑定为固定的值,让我们再调用时可以不用传递某系参数
可以对函数参数的顺序进行灵活的调整