
🔥个人主页:胡萝卜3.0****
📖个人专栏:************************************************************************************************************************************************************************************************************************************************************《C语言》、《数据结构》 、《C++干货分享》、LeetCode&牛客代码强化刷题****************************************************************************************************************************************************************************************************************************************************************
⭐️人生格言:不试试怎么知道自己行不行
🎥胡萝卜3.0🌸的简介:


目录
[8.1 function](#8.1 function)
[8.1.1 实战训练](#8.1.1 实战训练)
[8.2 bind](#8.2 bind)
[8.2.1 调整参数顺序](#8.2.1 调整参数顺序)
[8.2.2 调整参数个数](#8.2.2 调整参数个数)
八、包装器
8.1 function
std::function****是一个类模板,也是一个包装器,对可调用对象进行封装,本质是一层封装
那为什么要对可调用对象进行封转呢?
因为可调用对象太多了,可调用对象有:
- 函数指针;
- 仿函数;
- lambda表达式
在我们没有学习包装器之前,我们无法在容器中存入可调用对象,学习了这块内容之后,我们就可以在容器中存入可调用对象了!!!
- function 的原型,被定义在<functional> - C++ Reference头文件中。
cpp
template <class T>
class function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

std::function****的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、lambda以及bind表达式等,存储的可调用对象被称为std::function的目标
若std::function不含目标,则称他为空,调用空的std::function的目标导致抛出std::bad_function_call异常
函数指针、仿函数、lambda等可调用对象的类型各不相同,std::function的优势就在于统一类型,可以对他们进行包装,这样在很多地方就方便声明可调用对象的类型
cpp
#include<functional>
//函数
int add(int x, int y)
{
return x + y;
}
//仿函数
struct Functor
{
int operator()(int x, int y)
{
return x + y;
}
};
int main()
{
function<int(int, int)> f1 = add;//函数指针
function<int(int, int)> f2 = Functor();//仿函数
function<int(int, int)> f3 = [](int x, int y){ return x + y; };//lambda表达式
return 0;
}

那这里有个问题,我们这些可调用对象都存在f1、f2、f3中,那这个f1、f2、f3是怎么实现像函数一样被调用呢?
ok,这是因为function是一个仿函数

当function对象使用 () 运算符时,就相当于去回调这些可调用对象

ok,那现在,我们就可以实现将这些可调用对象放在一个容器里面了------
cpp
#include<functional>
#include<vector>
//函数
int add(int x, int y)
{
return x + y;
}
//仿函数
struct Functor
{
int operator()(int x, int y)
{
return x + y;
}
};
class A
{
public:
A(int _n)
:n(_n)
{}
int ADD(int x, int y)
{
return x + y;
}
static double func(double a, double b)
{
return a + b;
}
private:
int n;
};
int main()
{
function<int(int, int)> f1 = add;//函数指针
function<int(int, int)> f2 = Functor();//仿函数
function<int(int, int)> f3 = [](int x, int y){ return x + y; };//lambda表达式
//将这些可调用对象放在vector容器中
vector<function<int(int, int)>> v;
v.push_back(f1);
v.push_back(f2);
v.push_back(f3);
return 0;
}

那如果我们需要包装的是普通成员变量或者静态成员变量呢?
cpp
class A
{
public:
A(int _n)
:n(_n)
{}
//普通成员函数
int ADD(int x, int y)
{
return x + y;
}
//静态成员函数
static double func(double a, double b)
{
return a + b;
}
private:
int n;
};
int main()
{
//包装静态成员函数,需要指定类域
//成员函数要指定类域并且前面加&才能获取函数地址
function<double(double, double)> f4 = &A::func;
//包装普通成员函数
//成员函数要指定类域并且前面加&才能获取函数地址
//普通成员函数还有一个隐含的this指针参数
// 所以绑定时传对象或者对象的指针到参数类型中
function<int(A,int, int)> f5 = &A::ADD;
function<int(A*, int, int)> f6 = &A::ADD;
return 0;
}

那我们该怎么调用这个被包装好的function对象呢?

ok,接下来,让我们来实战一下------
8.1.1 实战训练
- 题目

- 解题思路

- 代码演示
cpp
class Solution {
public:
int evalRPN(vector<string>& tokens) {
map<string,function<int(int,int)>> kv{
{"+",[](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& e:tokens)
{
if(kv.count(e))
{
//操作符,进行运算操作
int right=st.top();
st.pop();
int left=st.top();
st.pop();
//进行运算
st.push(kv[e](left,right));
}
else
{
//操作数
st.push(stoi(e));
}
}
return st.top();
}
};
在上面的代码中,我们通过map映射string和function的方式实现,通过对应的key返回对应的函数
8.2 bind
cpp
simple(1)
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);
bind 是一个函数模板,它也是一个可调用对象的包装器,用可调用对象和它的参数进行绑定,返回一个可调用对象,可以把他看成一个函数适配器。
bind可以用来调整参数个数和参数顺序,bind也在<functional> - C++ Reference,<functional>头文件中。
- 调用bind的一般形式:
cpp
auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象,callable是要绑定的可调用对象,arg_list是一个逗号分隔的参数列表,对应callable中的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数
arg_list中的参数可能包含形如 _n 的名字,其中 n 是一个整数,这些参数是占位符(单纯的占位),表示newCallable的参数,它们占据了传递给newCallable的参数的位置。
数值n表示生成的可调用对象中的参数的位置:
- _1表示newCallable的第一个参数,_2表示newCallable的第二个参数,以此类推。
_1/_2/_3......这些占位符放到placeholders的一个命名空间中

ok,我们先来看bind的第一个功能:
8.2.1 调整参数顺序
在看调整参数顺序之前,我们先来看不调整参数顺序------
cpp
#include<functional>
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int x, int y)
{
return (x - y) * 10;
}
int main()
{
auto sub1 = bind(Sub, _1, _2);
cout<<sub1(10, 20)<<endl;
return 0;
}


运行结果------

ok,接下来我们来看看调整参数顺序------
cpp
#include<functional>
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Sub(int x, int y)
{
return (x - y) * 10;
}
int main()
{
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 20);
return 0;
}
也许会有uu想说,这好像没有啥区别啊!
ok,我们运行一下,看看结果------

我们来看一下,这是怎么做成的------

注意:_1表示第一个实参,_2表示第二个实参(这是指newCallable中的实参)
ok,调整参数顺序不是很常用,更常用的是调整参数个数------
- 注意:

8.2.2 调整参数个数
就是有时候我期望把某些参数绑死,就可以使用
cpp
#include<functional>
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Subx(int a, int b, int c)
{
return (a - b - c) * 10;
}
int main()
{
//我现在想绑死第一个参数就为10
auto sub1 = bind(Subx, 10, _1, _2);
//现在,我去调用sub1只需传2个参数
cout << sub1(20, 30) << endl;
return 0;
}

接下来,我们分别绑死这三个参数------
cpp
#include<functional>
using placeholders::_1;
using placeholders::_2;
using placeholders::_3;
int Subx(int a, int b, int c)
{
return (a - b - c) * 10;
}
int main()
{
//我现在想绑死第一个参数就为10
auto sub1 = bind(Subx, 10, _1, _2);
//现在,我去调用sub1只需传2个参数
cout << sub1(20, 30) << endl;
//绑死第二个参数
auto sub2 = bind(Subx, _1, 10, _2);
cout << sub2(20, 30) << endl;
//绑死第三个参数
auto sub3 = bind(Subx, _1, _2, 10);
cout << sub3(20, 30) << endl;
return 0;
}

我们可以写出相应的sub1、sub2、sub3的类型------

- ok,有了这个,我们是不是就可以对上面的调用普通成员函数的代码进行改写了------

我们看到,这个调用普通成员函数的代码中,我们是不是每次都要传这个成员函数对象,对这个成员函数对象进行绑死,就不需要每次都传了------

通过绑定,我们本来要传3个参数,现在只需要传2个参数即可------

那我们再来看一个例子------
cpp
int main()
{
// 计算复利的lambda
auto func1 = [](double rate, double money, int year)->double {
double ret = money;
for (int i = 0; i < year; i++)
{
ret += ret * rate;
}
return ret - money;
};
// 绑死⼀些参数,实现出⽀持不同年华利率,不同⾦额和不同年份计算出复利的结算利息
function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);
cout << func3_1_5(1000000) << endl;
cout << func5_1_5(1000000) << endl;
cout << func10_2_5(1000000) << endl;
cout << func20_3_5(1000000) << endl;
return 0;
}
既然bind可以绑死某个参数,那么我们就可以实现复利的计算,通过绑死年利率,实现出⽀持不同年华利率,不同⾦额和不同年份计算出复利的结算利息

九、智能指针预告
C++专栏的主线内容马上就要结束啦,博主将把智能指针作为C++干货专栏的主线(C++98、C++11部分)的终章,之后本专栏还会更新,内容以C++11、C++14、C++17、C++20、以及其它的加餐内容为主了。感谢各位uu的支持!