当前位置: 首页 > article >正文

C++第四十五弹---深入理解包装器:提升代码复用性与安全性的利器

 ✨个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1 包装器

1.1、function包装器

1.2、bind


1 包装器


1.1、function包装器


function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。那么我们来看看,我们为什么需要function呢?

先看一句代码!!!

auto ret = func(x);

上面func可能是什么呢?那么func可能是函数名函数指针函数对象(仿函数对象)?也有可能是lambda表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下!为什么呢?我们继续往下看。

代码演示

// 函数模板
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;
}

运行结果

通过上面的程序验证,我们会发现useF函数模板实例化了三份,因为count的三个地址都不一样。 

包装器可以很好的解决上面的问题!!!

function原型

std::function在头文件<functional>
// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参

代码演示

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()
{
	// 普通函数
	std::function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	// 函数对象
	std::function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	// lamber表达式
	std::function<double(double)> func3 = [](double d)->double { return d /
		4; };
	cout << useF(func3, 11.11) << endl;

	return 0;
}

运行结果 

通过上面的程序验证,我们会发现useF函数模板只实例化了一份,因为count的三个地址都相同。  

包装成员函数指针

代码演示

class Plus
{
public:
	// 静态成员函数,没有this指针
	static int plusi(int a, int b)
	{
		return a + b;
	}

	double plusd(double a, double b)
	{
		return a + b;
	}
};
int main()
{
	function<int(int, int)> f1 = &Plus::plusi;
	cout << f1(1, 2) << endl;

	// 有三个参数
	function<double(Plus*,double, double)> f2 = &Plus::plusd;
	Plus plus;
	cout << f2(&plus, 1.1, 2.2) << endl;

	// 不能显示传this指针,因此语法层面只要类型匹配即可
	function<double(Plus, double, double)> f3 = &Plus::plusd;
	cout << f3(Plus(), 1.1, 2.2) << endl;

	return 0;
}

运行结果

 

1.2、bind


std::bind函数定义在#include<functional>头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

bind原型

// 原型如下:
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的一般形式:auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

代码演示一

int Sub(int a,int b)
{
	return a - b;
}

int main()
{
	auto f1 = Sub;
	cout << f1(10, 5) << endl;

	// 调整两个参数顺序
	auto f2 = bind(Sub, placeholders::_2, placeholders::_1);
	cout << f2(10, 5) << endl;

	cout << typeid(f1).name() << endl;
	cout << typeid(f2).name() << endl;

	return 0;
}

运行结果 

代码分析 

代码演示二

class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};

int main()
{
	// 调整参数个数,原本三个参数,加上this指针,第一个参数用匿名对象
	auto f4 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
	cout << f4(10, 5) << endl;

	Sub sub;
	// 第一个参数用对象地址
	auto f5 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2);
	cout << f5(10, 5) << endl;
	return 0;
}

运行结果

代码演示三

void fx(const string& name, int x, int y)
{
	cout << name << "-> [血量:" << x << ",蓝:" << y << ']' << endl;
}
int main()
{
	fx("王昭君", 80, 30);
	fx("王昭君", 77, 20);
	fx("王昭君", 60, 0);
	fx("王昭君", 30, 40);

	fx("亚瑟", 90, 20);
	fx("亚瑟", 77, 15);
	fx("亚瑟", 40, 0);
	fx("亚瑟", 2, 20);

	return 0;
}

 运行结果

从代码三我们能够打印一个英雄的属性,但是调用同一个名字太冗余,可以绑定名字。

代码三优化

int main()
{
	// 绑定名字
	auto f6 = bind(fx, "王昭君", placeholders::_1, placeholders::_2);
	f6(80, 30);
	f6(77, 20);
	f6(60, 0);
	f6(30, 40);

	auto f7 = bind(fx, "亚瑟", placeholders::_1, placeholders::_2);
	f7(90, 20);
	f7(77, 15);
	f7(40, 0);
	f7(2, 20);
	return 0;
}

运行结果 

除了绑定第一个参数我们还可以绑定中间的参数,比如我们可以将血量绑定成80,代码如下: 

代码演示四

int main()
{
	// 绑定血量
	auto f8 = bind(fx, placeholders::_1, 80, placeholders::_2);
	f8("武则天", 12);
	f8("妲己", 33);
	return 0;
}

运行结果

代码演示四也可以使用 包装器 + 绑定。 

// 包装器 + 绑定
function<void(std::string,int)> f8 = bind(fx, placeholders::_1, 80, placeholders::_2);


http://www.kler.cn/a/290006.html

相关文章:

  • RoseTTAFold MSA_emb类解读
  • JavaEE进阶----SpringMVC(三)---响应的获取
  • 信号量和线程池
  • 【深度学习】LSTM、BiLSTM详解
  • 记录日志中logback和log4j2不能共存的问题
  • window下安装rust 及 vscode配置
  • 浙大数据结构:01-复杂度3 二分查找
  • 一文读懂期权交易规则和操作方法分享
  • gitk无法打开
  • Python将两个Excel文件按相同字段合并到一起
  • gcc编译与Linux下的库
  • k8s dial tcp 10.97.0.1:443: i/o timeout
  • 帮招一名3C大佬机器视觉工程师,工作地:苏州,月薪25K-30K,30岁以下,Halcon独立开发,单休,有管理经验更佳有绩效奖
  • 飞利浦开放式耳机怎么样?飞利浦、西圣、漫步者爆火机型大对决!
  • SprinBoot+Vue宠物领养救助微信小程序的设计与实现
  • 解决firewalld启动状态下docker无法启动
  • AI时代的信仰是什么
  • macbook怎么换自定义壁纸?Mac怎么设置壁纸 macOS中如何轻松删除不需要的壁纸?
  • 86、pod部署策略
  • 动态爱心绘制:基于 turtle 库的实现
  • 7、Django Admin删除默认应用程序
  • 探索MongoDB的Python之钥:pymongo的魔力
  • WinCC Modbus TCP 通信
  • https和harbor仓库跟k8s
  • OpenAI API in node gives basic Await error. How do I fix?
  • Vue(十) 过渡动画、配置代理服务器,解决请求跨域的问题