深入C++可调用对象:从function包装到bind参数适配的技术实现

🔥个人主页:胡萝卜3.0****

📖个人专栏:************************************************************************************************************************************************************************************************************************************************************《C语言》、《数据结构》 、《C++干货分享》、LeetCode&牛客代码强化刷题****************************************************************************************************************************************************************************************************************************************************************

《Linux系统编程》

⭐️人生格言:不试试怎么知道自己行不行


🎥胡萝卜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表达式

在我们没有学习包装器之前,我们无法在容器中存入可调用对象,学习了这块内容之后,我们就可以在容器中存入可调用对象了!!!

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 实战训练
  • 题目

150. 逆波兰表达式求值 - 力扣(LeetCode)

  • 解题思路
  • 代码演示
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的支持!

相关推荐
Echo_NGC22376 小时前
【KL 散度】深入理解 Kullback-Leibler Divergence:AI 如何衡量“像不像”的问题
人工智能·算法·机器学习·散度·kl
小a杰.6 小时前
Flutter 设计系统构建指南
开发语言·javascript·ecmascript
愤怒的可乐6 小时前
从零构建大模型智能体:OpenAI Function Calling智能体实战
人工智能·大模型·智能体
BD_Marathon6 小时前
【JavaWeb】Servlet_url-pattern的一些特殊写法问题
java·开发语言·servlet
XiaoMu_0016 小时前
基于深度学习的农作物叶片病害智能识别与防治系统
人工智能·深度学习
中文很快乐6 小时前
java开发--开发工具全面介绍--新手养成记
java·开发语言·java开发·开发工具介绍·idea开发工具
IMPYLH6 小时前
Lua 的 Coroutine(协程)模块
开发语言·笔记·后端·中间件·游戏引擎·lua
看见繁华6 小时前
C++ 高级
开发语言·c++
potato_15546 小时前
Windows11系统安装Isaac Sim和Isaac Lab记录
人工智能·学习·isaac sim·isaac lab