C++中function,bind,lambda

news2024/9/22 9:28:34

c++11之前,STL中提供了bind1st以及bind2nd绑定器
首先来看一下他们如何使用:
如果我们要对vector中的元素排序,首先会想到sort,比如:

void output(const vector<int> &vec)
{
	for (auto v : vec) {
		cout << v << " ";
	}
	cout << endl;
}

int main() {
	vector<int> vec;
	srand(time(nullptr));
	for (int i = 0; i < 20; i++) {
		vec.push_back(rand() % 100 + 1);
	}
	output(vec);
	sort(vec.begin(), vec.end());
	output(vec);

	//greater 从大到小排序
	sort(vec.begin(), vec.end(), greater<int>());
	output(vec);
	//less 从小到大排序
	sort(vec.begin(), vec.end(), less<int>());
	output(vec);
	return;
}

sort最后一个参数传入的greater或less都被称为函数对象,顾名思义,表现像函数的对象,因为他们的调用都是在后面加上"()"。
其实这是因为他们都重载了operator()。
来看下greater的定义:

template<class _Ty = void>
	struct greater
	{	// functor for operator>
	_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty first_argument_type;
	_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty second_argument_type;
	_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool result_type;

	constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const
		{	// apply operator> to operands
		return (_Left > _Right);
		}
	};

可以看到确实重载了operator(),需要传入两个参数,所以它还是一个二元函数对象。函数对象的概念至关重要!
那什么时候会用到bind1st、bind2nd呢?
比如我们现在要找到第一个小于70的位置,插入70。
可以使用find_if:

	auto it1 = find_if(vec.begin(), vec.end(),
		bind1st(greater<int>(), 70));

	if (it1 != vec.end()) {
		vec.insert(it1, 70);
	}

这里使用bind1st(greater<int>(), 70)作为find_if的第三个参数,bind1st的作用,首先,将70绑定到二元函数对象(greater)的第一个参数上,其次,将二元函数对象(greater)转为一元函数对象(因为70已知了),传入到find_if的第三个参数中。这便是他的应用场景,或者还可以使用bind2nd和less的搭配:

	auto it1 = find_if(vec.begin(), vec.end(),
		bind2nd(less<int>(), 70));

	if (it1 != vec.end()) {
		vec.insert(it1, 70);
	}

关于bind1st(greater(), 70)和bind2nd(less(), 70)的理解
因为我们要找小于70的位置,所以,
对于greater来说,left > right,所以绑定到第一个位置
对于less来说,left < right,所以绑定到第二个位置

理解了绑定器后,再来看看function
function需要一个函数类型进行实例化:

void hello1()
{
	cout << "hello world!" << endl;
}

void hello2(string str)
{
	cout << str << endl;
}

class Test
{
public:
	void hello(string str) { cout << str << endl; }
};

int main() {
	function<void()> func1 = hello1;
	//function<void()> func1(hello1);
	func1();//func1.operator() => hello1();
	
	function<void(string)> func2 = hello2;
	func2("gao");//func2.operator()(string str) => hello2(str);

	function<int(int, int)> func3 = [](int a, int b) -> int { return a + b; };
	cout << func3(100, 200) << endl;

	//通过function调用类的成员方法
	function<void(Test*, string)> func5 = &Test::hello;
	func5(&Test(), "call Test::hello!");
	
	return 0;
}

对function的调用,实际上是调用了function的()重载,从而调用原函数。上面的例子中可以看到lambda表达式也可以通过function调用。这其实就说明了function的真正用途:保存函数对象的类型,也是对函数对象的封装。这也是它和c语言的函数指针的区别(lambda无法通过函数指针调用)。

现在有这样一个场景:
两(多)个函数,有大部分的代码都是一样的,其中只有一两行代码有不一样的地方,我们可以对这个不一样的地方,使用function做一个抽象,比如:
有两个vector打印函数,一个打印模5=0的元素,一个打印大于10的元素:

void print(vector<int> &number, function<bool(int)> filter) {
	for (const int &i : number) {
		if (filter(i)) {
			cout << i << endl;
		}
	}
}

print(numbers, [](int i){ return i % 5 == 0; });
print(numbers, [](int i){ return i > 10; });

这样就不用定义两个不同的打印函数了。

关于闭包的概念:
下面是维基百度对于闭包的定义:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。 这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

简单来说:闭包可以记忆住创建它时候的那些变量。
下面,我们再通过一个例子来说明。
现在,假设我们的需求是:获取一个集合中最小和最大值,并在稍后的时候(可能是另外一个函数中)打印它们。 这里,我们常规的做法通常是:通过一个函数获取集合的最大,最小值,然后保存住,最后在需要的时候访问这两个值,然后打印它们。
这样做就会需要解决:如何保存和传递最大,最小这两个值。
但实际上,这里我们可以考虑用闭包来实现这个功能,让闭包把最大,最小两个值捕获下来,然后在需要的地方调用就可以了。

请看一下下面这段代码:

void getMinMax(vector<int>& number, function<void ()>& printer) {
	int min = number.front();
	int max = number.front();
	for (int i : number) {
	    if (i < min) {
	        min = i;
	    }
	    if (i > max) {
	        max = i;
	    }
	}
	printer = [=] () {
	  cout << "min:" <<min<< endl;
	  cout << "max:" << max << endl;
	};
}

这里,我们通过function<void ()>& printer传递出这个闭包。 然后,在需要的地方,这样即可:

function<void()> printer;
getMinMax(numbers, printer);
......

printer();

这里的printer其实是我们前面从getMinMax函数出传出的闭包,这个闭包捕获了min和max。我们直接传递这个闭包给需要的地方使用,而不用传递裸的两个数值,是不是优雅的不少?

bind

/*
c++11 bind绑定器 -> 返回的结果还是一个函数对象
*/

void hello(string str) { cout << str << endl; }
int sum(int a, int b) { return a + b; }

class Test 
{
public:
	int sum(int a, int b) { return a + b; }
};

int main()
{
	bind(hello, "hello, bind!")();
	cout << bind(sum, 10, 20)() << endl;
	cout << bind(&Test::sum, Test(), 20, 30)() << endl;

	//参数占位符 绑定器出了语句,无法继续使用
	bind(hello, placeholders::_1)("hello bind 2!");
	cout << bind(sum, placeholders::_1, placeholders::_2)(200, 300) << endl;

	//此处把bind返回的绑定器binder就复用起来了
	function<void(string)> func1 = bind(hello, placeholders::_1);
	func1("hello gao");


	return 0;
}

使用bind和function的线程池例子:

class Thread
{
public:
	//接收一个函数对象,参数都绑定了,所以不需要参数
	Thread(function<void()> func) : _func(func) {}
	thread start()
	{
		thread t(_func);
		return t;
	}
private:
	function<void()> _func;
};

class ThreadPool
{
public:
	ThreadPool() {}
	~ThreadPool() {
		for (int i = 0; i < _pool.size(); i++) {
			delete _pool[i];
		}
	}

	void startPool(int size)
	{
		for (int i = 0; i < size; i++) {
			//成员方法充当线程函数,绑定this指针
			_pool.push_back(new Thread(bind(&ThreadPool::runInThread, this, i)));
		}
		for (int i = 0; i < size; i++) {
			_handler.push_back(_pool[i]->start());
		}

		for (thread &t : _handler) {
			t.join();
		}
	}
private:
	vector<Thread*> _pool;
	vector<thread> _handler;
	void runInThread(int id) {
		cout << "call runInThread! id:" << id << endl;
	}
};

int main()
{
	ThreadPool pool;
	pool.startPool(10);
	return 0;
}

lambda(匿名函数对象)
lambda表达式的语法

[捕获外部变量](形参列表)->返回值{操作代码};

[]:表示不捕获任何外部变量
[=]:表示以传值的方式捕获外部的所有变量
[&]:表示以传引用的方式捕获外部的所有变量
[this]:捕获外部的this指针
[=,&a]: 表示以传值的方式捕获外部的所有变量,但是a变量以传引用的方式捕获
[a,b]:表示以值传递的方式捕获外部变量a和b
[a,&b]:a以值传递捕获,b以引用捕获

使用举例:

int main()
{
	auto func1 = []()->void {cout << "hello world!" << endl; };
	func1();

	//[]:表示不捕获任何外部变量
	//编译报错
	/*int a = 10;
	int b = 20;
	auto func3 = []()
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
	*/

	//以值传递a,b,lambda实现的重载函数operator()中,是const方法,不能修改成员变量
	//如果一定要修改,将lambda修饰成mutable,但是这并不会改变a的值,因为这是值传递
	//int a = 10;
	//int b = 20;
	//auto func3 = [a, b]() /*mutable*/
	//{
	//	int tmp = a;
	//	a = b;
	//	b = tmp;
	//};

	vector<int> vec;
	vec.push_back(1);
	vec.push_back(2);
	vec.push_back(3);

	for_each(vec.begin(), vec.end(), [](int a) {
		cout << a << endl;
	});
	return 0;
}
class Data
{
public:
	Data(int a, int b) : ma(a), mb(b) {}
	int ma;
	int mb;
};

int main()
{
	map<int, function<int(int, int)>> caculateMap;
	caculateMap[1] = [](int a, int b)->int {return a + b; };
	caculateMap[2] = [](int a, int b)->int {return a - b; };

	cout << caculateMap[1](1, 2) << endl;

	//智能指针自定义删除器
	unique_ptr<FILE, function<void(FILE*)>>
		ptr1(fopen("data.txt", "w"), [](FILE *pf) { fclose(pf); });

	//优先队列
	using FUNC = function<bool(Data&, Data&)>;
	priority_queue<Data, vector<Data>, FUNC>
		maxHeap([](Data &d1, Data &d2)->bool
		{
		return d1.mb > d2.mb;
		});

	maxHeap.push(Data(10, 20));
	return 0;
}

lambda表达式是如何实现的?
其实是编译器为我们了创建了一个类,这个类重载了(),让我们可以像调用函数一样使用。所以,你写的lambda表达式和真正的实现,是这个样子的:
在这里插入图片描述
而对于捕获变量的lambda表达式来说,编译器在创建类的时候,通过成员函数的形式保存了需要捕获的变量,所以看起来是这个样子:
在这里插入图片描述
似乎也没有什么神奇的地方。但正是由于编译器帮我们实现了细节,使我们的代码变得优雅和简洁了许多。

参考文章:https://paul.pub/cpp-lambda-function-bind/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/901923.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C# 基础精讲】异步和同步的区别

异步&#xff08;Asynchronous&#xff09;和同步&#xff08;Synchronous&#xff09;是在编程中经常遇到的两种执行模式。它们涉及到程序中任务的执行方式以及对资源的管理方式。在本文中&#xff0c;我们将深入探讨异步和同步的区别、使用场景以及在 C# 中如何实现异步编程。…

|个人信息保护影响评估的概念与实践-二

一、什么是“个人信息保护影响评估” “个人信息保护影响评估”或“隐私影响评估”等相关概念最早起源于加拿大、澳大利亚等数据保护相关条例较为发达的国家&#xff0c;在广泛推行和应用之后成为一项对个人信息必要的保护准则。 我国在2017年发布的《信息安全技术 个人信息安…

EMO实战:使用EMO实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看关于EMA设置为True时…

机器学习之概率论

最近&#xff0c;在了解机器学习相关的数学知识&#xff0c;包括线性代数和概率论的知识&#xff0c;今天&#xff0c;回顾了概率论的知识&#xff0c;贴上几张其他博客的关于概率论的图片&#xff0c;记录学习过程。

【Freertos基础入门】深入浅出freertos互斥量

文章目录 前言一、互斥量是什么&#xff1f;二、互斥量的使用场景三、互斥量的使用1.创建 2.删除互斥量3.give和take四、示例代码总结 前言 FreeRTOS是一款开源的实时操作系统&#xff0c;提供了许多基本的内核对象&#xff0c;其中包括互斥锁&#xff08;Mutex&#xff09;。…

Jmeter性能测试 —— 压力模式

压力模式 性能测试中的压力模式有两种。 第一种是并发用户模式&#xff08;虚拟用户模式&#xff09;并发用户是指虚拟并发用户数&#xff0c;从业务角度&#xff0c;也可以理解为同时在线的用户数。 从客户端的角度出发&#xff0c;摸底业务系统各节点能同时承载的在线用户…

RK3588平台开发系列讲解(内存篇)伙伴系统如何分配页面

文章目录 一、通过接口找到内存节点二、开始分配三、准备分配页面的参数四、快速分配路径五、慢速分配路径沉淀、分享、成长,让自己和他人都能有所收获!😄 📢分配物理内存页面的过程很好推理:首先要找到内存节点,接着找到内存区,然后合适的空闲链表,最后在其中找到页…

WebGL的剪裁空间

推荐&#xff1a;使用NSDT场景编辑器助你快速搭建可二次编辑的3D应用场景 什么是WebGL的剪裁空间 WebGL的剪裁空间&#xff08;Clipping Space&#xff09;是在图形渲染过程中处理视图体积裁剪的一种特定空间。它是指定义在3D世界坐标系和屏幕窗口之间的虚拟空间&#xff0c;用…

windows下redis设置redis开机自启动方法(保姆级)

1.找到Redis所在的目录&#xff0c;在文件路径框中输入cmd: 2.进入到控制台下的Redis所在目录,输入下列命令: redis-server --service-install redis.windows-service.conf --loglevel verbose 3.找到redis.windows-service.conf文件,双击打开设置redis服务的密码: (不想设置密…

JavaScript对象知识总结

一、创建对象的三种方式 1、字面量创建对象 2、new关键字&#xff0b;构造函数创建对象 3、Object.create()创建对象 二、查看对象属性和更改对象值的两种方式 1、形如&#xff1a;obj.keyvalue 2、形如&#xff1a;obj[key]value 三、删除对象属性 1、delete obj.key …

【Redis从头学-5】Redis中的List数据类型实战场景之天猫热销榜单

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Re…

ModaHub魔搭社区:WinPlan经营大脑简介

WinPlan是面向企业经营场景的EPM平台(企业绩效管理),帮助企业解决经营不透明、决策拍脑袋、执行不到位、绩效凭感觉等问题,让销售、财务、生产、组织等有机地协同工作,提升企业的经营管理效率。 WinPlan平台面向各个行业,提供了丰富的经营样板间。 企业经营决策系统 算力…

linux设备驱动模型:kobject、kobj_type

内核版本发展 2.4版本之前内核没有统一的设备驱动模型&#xff0c;但是可以用&#xff08;例如先前的led字符设备驱动实验&#xff0c;使用前需要手动调用mknod命令创建设备文件&#xff0c;从而进一步控制硬件&#xff09;。 2.4~2.6版本内核使用devfs&#xff0c;挂载在/dev目…

第9次获得微软最有价值专家(MVP)奖励

Microsoft 最有价值专家 (MVP) 是热情地与社区分享知识的技术专家群体。他们总是处于技术前沿&#xff0c;并且有不可阻挡的冲劲&#xff0c;想要获得令人兴奋的新技术。他们对 Microsoft 产品和服务有深入的了解&#xff0c;同时还能够将各种平台、产品和解决方案整合在一起&a…

使用Arthues分析高CPU问题

Arthas是阿里开源的 Java 诊断工具&#xff0c;相比 JDK 内置的诊断工具&#xff0c;要更人性化&#xff0c;并且功能强大&#xff0c;可以实现许多问题的一键定位&#xff0c;而且可以一键反编译类查看源码&#xff0c;甚至是直接进行生产代码热修复&#xff0c;实现在一个工具…

async/await 编程理解

博客主要是参考 Asynchronous Programming in Rust &#xff0c;会结合简单的例子&#xff0c;对 async 和 await 做比较系统的理解&#xff0c;如何使用 async 和 await 是本节的重点。 async 和 await 主要用来写异步代码&#xff0c;async 声明的代码块实现了 Future 特性&a…

嵌入式设备应用开发(qt界面开发)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 linux界面开发有很多的方案可以选。比如说lvgl、minigui、ftk之类的。但是,这么多年来,一直屹立不倒的还是qt。相比较其他几种方案,qt支持多个平台,这里面就包括了linux平台。此…

aardio简单网站css或js下载练习

import win.ui; /*DSG{{*/ var winform win.form(text"下载网站css或js";right664;bottom290;maxfalse) winform.add( buttonClose{cls"button";text"退出";left348;top204;right498;bottom262;color14120960;fontLOGFONT(h-14);note" &qu…

【Alibaba中间件技术系列】「RocketMQ技术专题」让我们一起实践一下RocketMQ服务及其控制台安装指南

64位操作系统&#xff0c;生产环境建议Linux/Unix/MacOS&#xff08;Windows操作系统安装说明详见 Windows操作系统安装教程&#xff09;64位JDK 1.84G的可用磁盘 unzip rocketmq-all-4.5.1-bin-release.zip cd rocketmq-all-4.5.1-bin-release nohup sh bin/mqnamesrv & t…

了解生成对抗网络 (GAN)

一、介绍 Yann LeCun将其描述为“过去10年来机器学习中最有趣的想法”。当然&#xff0c;来自深度学习领域如此杰出的研究人员的赞美总是对我们谈论的主题的一个很好的广告&#xff01;事实上&#xff0c;生成对抗网络&#xff08;简称GAN&#xff09;自2014年由Ian J. Goodfel…