C/C++语言基础--C++STL库之仿函数、函数对象、bind、function简介

本专栏目的

  • 更新C/C++的基础语法,包括C++的一些新特性

前言

  • STL无疑是C++史上一个重要的发明,未来我将更新STL有关的知识点,入门绝对够了(看目录就知道了👀)
  • 这是第二篇,讲仿函数
  • C语言后面也会继续更新知识点,如内联汇编;
  • 欢迎收藏 + 关注,本人将会持续更新。

文章目录

  • 仿函数
        • 可调用对象(Callable object)
        • 仿函数
        • 为什么要有仿函数?
        • 仿函数优点
        • 仿函数作用
    • 函数对象
      • 绑定函数
        • bind
        • not1
          • 一元函数又叫一元谓词
        • not
        • ref、cref
        • mem_fun
          • 示例
          • 总结
      • 包装类
        • funtion

仿函数

可调用对象(Callable object)

函数调用需要使用"()",这个“()”叫做函数调用用运算符

C++中的可调用对象有以下几种:

  • 函数(function)

  • 函数指针(function pointer)

  • 仿函数(Functor)

  • lambda表达式

  • bind 函数封装的函数对象

仿函数

仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类,仿函数是定义了一个含有operator()成员函数的对象,可以视为一个一般的函数,只不过这个函数是通过类重载()所实现的。

为什么要有仿函数?

1,假如客户有一个需求摆在我们的面前,编写一个函数:

  • 函数可以获得斐波拉契数列每项的值;
  • 每调用一次便返回一个值;
  • 函数可根据需要重复使用。

普通函数实现

int fibonacci()
{
	static int a0 = 0;	//第一项
	static int a1 = 1;	//第二项

	int ret = a1;		//保存
	a1 = a0 + a1;
	a0 = ret;

	return ret;
}
int main()
{
	for (size_t i = 0; i < 5; i++)
	{
		cout << fibonacci() << " ";		//1 1 2 3 5
	}
	cout << endl;
	for (size_t i = 0; i < 5; i++)
	{
		cout << fibonacci() << " ";		//8 13 21 34 55
	}
	return 0;
}

缺点 :无法根据需求使用,如:无法想要重复获取一项

解决: 每一次从将初始项从新赋值为0。

int a0 = 0;	//第一项
int a1 = 1;	//第二项
int fibonacci()
{
	int ret = a1;
	a1 = a0 + a1;
	a0 = ret;

	return ret;
}

int main()
{
	for (size_t i = 0; i < 5; i++)
	{
		cout << fibonacci() << " ";		//1 1 2 3 5 8
	}
	cout << endl;

	a0 = 0;
	a1 = 1;
	for (size_t i = 0; i < 5; i++)
	{
		cout << fibonacci() << " ";		//1 1 2 3 5 8
	}
	return 0;
}

缺点: 无法直接获取一项值,如直接获取第9项值,而且每一次获取的时候还需要将初始值手动赋值为0;

解决: 函数对象

结合本案例,说明函数对象特点:

a> 使用具体的类对象取代函数

b> 该类的对象具备函数调用的行为;

d>多个对象相互独立的求解数列项。

总结:函数对象利用运算符重载,使得对象可以想函数一样调用,但是对于类来说,他可以封装很多方法,很多变量,这个就可以实现普通函数完不成的东西,比如说:用成员变量储存过程数据。

解决:

class Fibonacci
{
public:
	Fibonacci() :_a0(0), _a1(1) {}
	Fibonacci(int n) :_a0(0), _a1(1) 
	{
		for (int i = 0; i < n; i++)
		{
			int ret = _a1;
			_a1 = _a0 + _a1;
			_a0 = ret;
		}
	}
	int operator()()
	{
		int ret = _a1;
		_a1 = _a0 + _a1;
		_a0 = ret;
		return ret;
	}
private:
	int _a0;
	int _a1;
};


int main()
{
	Fibonacci fib;
	for (size_t i = 0; i < 5; i++)
	{
		cout << fib() << " ";		//1 1 2 3 5 8
	}
	cout << endl;

	Fibonacci fib1(9);
	for (size_t i = 0; i < 5; i++)
	{
		cout << fib1() << " ";		//55 89 144 233 377
	}
	return 0;
}

我们看到已经实现了所有需求,并且随时想从哪个数开始都行。

仿函数优点

如果可以用仿函数实现,那么你应该用仿函数,而不要用CallBack。原因在于:

  • 仿函数可以不带痕迹地传递上下文参数。而CallBack技术通常使用一个额外的void*参数传递。这也是多数人认为CallBack技术丑陋的原因。
  • 仿函数技术可以获得更好的性能,这点直观来讲比较难以理解。
仿函数作用

仿函数通常有下面四个作用:

  • 作为排序规则,在一些特殊情况下排序是不能直接使用运算符<或者>时,可以使用仿函数。
  • 作为判别式使用,即返回值为bool类型。
  • 同时拥有多种内部状态,比如返回一个值得同时并累加。
  • 作为算法for_each的返回值使用。

函数对象

头文件

函数对象是专门设计用于使用类似于函数的语法的对象。在 C++ 中,这是通过operator()在其类中定义成员函数来实现的,它们通常用作函数的参数,例如传递给标准算法的谓词或比较函数,这个提出来个人感觉是为了替代函数指针,函数指针写起来确实比较麻烦。

绑定函数

这些函数根据其参数创建包装类的对象

bind
  • 它的作用是将一个可调用对象(比如函数、函数指针、成员函数、成员函数指针等等)以及若干个参数绑定到一个新的函数对象上,形成一个新的可调用对象

  • 还可以用来绑定函数调用的某些参数,包括可以绑定参数顺序,可以将bind函数看作一个通用的函数包装器,它接受一个可调用对象,并返回函数对象

  • 常用,这个的出现和std::function,其实就相当于替代了C的函数指针,使用起来更方便了。

1. 绑定普通函数

void show(int number, const std::string& str)
{
	cout << number << " " << str << endl;
}
  • 顺序绑定参数

    auto bind_show = std::bind(show, placeholders::_1, placeholders::_2);
    bind_show(2,"world");
    
  • 交换参数位置

    auto bind_show1 = std::bind(show, placeholders::_2, placeholders::_1);
    bind_show1("world",1314520);
    
  • 绑定固定参数

    auto bind_show3 = std::bind(show, 888,placeholders::_1);   //在函数调用的时候传递一个参数,一个在绑定的时候加
    bind_show3("wy");
    

2. 绑定成员函数

😜 注意: 需要指明对象

struct Plus
{
	int plus(int a, int b)
	{
		return a * b;
	}
};	
{
	Plus plus;
	auto func1 = std::bind(&Plus::plus, &plus, placeholders::_1, placeholders::_2);	//绑定对象指针
    auto func2 = std::bind(&Plus::plus, plus, placeholders::_1, placeholders::_2);	//绑定对象或引用
	cout << func1(2, 4) << endl;
}

3. 绑定函数对象

struct Sub
{
	int operator()(int a, int b)
	{
		return a * b;
	}
};
{
	auto func2 = std::bind(Sub(), placeholders::_1, placeholders::_2);
	cout << func2(3, 4) << endl;
}

4. 绑定lambda表达式

{	
	auto func3 = std::bind([](int a, int b) {return a / b; }, placeholders::_1, placeholders::_2);
	cout << func3(6 ,2) << endl;
}
not1

一元函数对象的否定只能对仿函数进行否定,普通函数不行,这个有时候在看一些API的源码我们会看到有这个参数,其实这个就相当于bool函数,判断是否可行。

一元函数又叫一元谓词

谓词( predicate )是指普通函数或重载的operator()返回值是bool类型的函数对象(仿函数)。如果operator()接受一个参数,那么叫做一元谓词,如果接受两个参数,那么叫做二元谓词,谓词可作为一个判断式。

struct Greater5 
	:public unary_function<int, bool>		//必须继承自一元函数类
{
	bool operator()(int val) const			//必须加上const
	{
		return val > 5;
	}
};

int main()
{
	cout << boolalpha << Greater5()(10) << endl;
	auto _less5 = not1(Greater5());
	cout << boolalpha << _less5(10) << endl;
    return 0;
}
not

和上面不同的是,对二元函数对象的否定,还是只能对仿函数进行否定,普通函数不行

struct Greater
    :public binary_function<int,int,bool>	//必须继承自二元函数类
{
	bool operator()(int a, int b) const		//必须加上const
	{
		return a > b;
	}
};

int main()
{
	cout << boolalpha << Greater()(3, 1) << endl;;

	auto _less = not2(Greater());
	cout << boolalpha << _less(3,1) << endl;;

	return 0;
}
ref、cref

上面提到,std::bind可以绑定函数参数,但是想要绑定引用参数,那如何做呢? refcref这两个就是为了解决这个问题的。

  • ref 普通引用
  • cref 常引用
struct Inc
{
	mutable int number = 0;
	void operator()()const
	{
		cout << number << endl;
		number++;
	}
};

int main()
{
	Inc inc;
	auto func = bind(std::cref(inc));
	func();
	inc();
    return 0;
}
mem_fun

把成员函数转为函数对象,使用对象指针或对象(引用)进行绑定

class Foo
{
public:
	int a{ 100 };
	void print()
	{
		cout << a << endl;
	}
	void print2(int val)
	{
		cout << a << " val:" << val << endl;
	}
};

int main()
{
	Foo f;
	//把成员函数转为函数对象,使用对象指针或对象(引用)进行绑定
	auto func = mem_fn(&Foo::print);
	func(f);		//把对象传进去
	func(&f);		//对象指针也行
	func(Foo());	//临时对象也行

	//把成员函数转为函数对象,使用对象指针进行绑定
	auto func1 = mem_fun(&Foo::print2);
	func1(&f,666);

	//把成员函数转为函数对象,使用对象(引用)进行绑定
	auto func2 = mem_fun_ref(&Foo::print);
	func2(f);

	return 0;
}
示例
struct Foo
{
	int v;
	Foo(int val = -1)
		:v(val) {}
	void print()
	{
		cout <<v << endl;
	}
};

int main()
{
	//让每个对象都调用指定的成员函数
	std::vector<Foo> vec(5);	//存对象
	for_each(vec.begin(), vec.end(), mem_fn(&Foo::print));
	
	cout << endl;

	//让每个对象都调用指定的成员函数
	std::vector<Foo*> vec_ptr;	//存指针
	for (int i = 0; i < 5; i++)
	{
		vec_ptr.push_back(new Foo(i*3));
	}	
	for_each(vec_ptr.begin(), vec_ptr.end(), mem_fn(&Foo::print));

	return 0;
}
总结
函数作用
mem_fun把成员函数转为函数对象,使用对象指针进行绑定
mem_fun_ref把成员函数转为函数对象,使用对象(引用)进行绑定
mem_fn把成员函数转为函数对象,使用对象指针或对象(引用)进行绑定
bind包括但不限于mem_fn的功能,更为通用的解决方案

包装类

包装类是使不同的函数(普通函数、仿函数、bind绑定的一些函数…………)打包成具有统一接口一个类型,其中最常用的是function

funtion

函数包装器

概念: C++11 中引入了 std::function 类型,它是一个多态函数封装类,可以用来存储和调用任意可调用对象,包括函数指针、函数对象、Lambda 表达式等等。

原因:因为不管是函数名、函数指针、仿函数还是lambda表达式,它们都是一个“函数”function的作用是能够统一类型,从而提高效率。

与bind区别

  • bind更多的是将函数和他的参数相互绑定在一起,可以使函数作为参数;
  • function主要是为“不同形状的函数”包装成统一类型,可以看下面的一些案例:
  1. 包装普通函数

    int add(int a, int b)
    {
    	return a + b;
    }
    
    {
        // 包装
    	std::function<int(int, int)> fun_add(add);
    	cout<<fun_add(2, 3);
    }
    
  2. 包装成员函数(通过bind绑定)

    class Wy
    {
    public:
    	int add(int a, int b)
    	{
    		return a + b;
    	}
    };
    
    {
    	Wy wy;
    	std::function<int(int, int)> fun_maye_add(std::bind(&Wy::add, &wy,placeholders::_1,placeholders::_2));
    	cout << fun_maye_add(3, 5);
    }
    
  3. 包装lambda表达式(刷算法题常用)

    {
        std::function<int(int, int)> fun_lambda_add([](int a, int b)->int 
                                                        {
                                                            return a + b; 
                                                        });
    	cout << fun_lambda_add(7, 8) << endl;    // 包装
    }
    
  4. 包装函数对象

    class Maye
    {
    public:
    	int operator()(int a, int b)
    	{
    		return a * b;
    	}
    };
    
    {		
    	Maye obj;
    	std::function<int(int, int)> fun_functor(obj);
    	cout << fun_functor(2,4);
    }
    

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python 读取win7 win10本机ipv6 地址转发到电邮(备份)

python 版本&#xff1a; 3.8.10 用于外网查询SMB服务器ipv6 地址。服务器定时查询本机ipv6地址&#xff0c;如地址变动则用电邮发送新地址。 import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart import ssl import socket…

多个JAVA环境变量安装配置

在做java代码审计时&#xff0c;为了要成功运行目标环境&#xff0c;时长要对于jdk版进行切换&#xff0c;且在装多个jdk时还时长会遇到安装配置后环境变量不生效的情况&#xff0c;下文介绍&#xff1b; 1、为什么安装了新的jdk&#xff0c;有的时候环境变量中的jdk版本确还是…

数字经济下的 AR 眼镜

目录 1. &#x1f4c2; AR 眼镜发展历史 1.1 AR 眼镜相关概念 1.2 市面主流 XR 眼镜 1.3 AR 眼镜大事记 1.4 国内外 XR 眼镜 1.5 国内 AR 眼镜四小龙 2. &#x1f531; 关键技术 2.1 AR 眼镜近眼显示原理 2.2 AR 眼镜关键技术 2.3 AR 眼镜技术难点 3. &#x1f4a…

maven-resources-production:ratel-fast: java.lang.IndexOutOfBoundsException

Maven生产环境中遇到java.lang.IndexOutOfBoundsException的问题&#xff0c;尝试了重启电脑、重启IDEA等常规方法无效&#xff0c;最终通过直接重建工程解决了问题。 Rebuild Project 再启动OK

TDesign:NavBar 导航栏

NavBar 导航栏 左图&#xff0c;右标 appBar: TDNavBar(padding: EdgeInsets.only(left: 0,right: 30.w), // 重写左右内边距centerTitle:false, // 不显示标题height: 45, // 高度titleWidget: TDImage( // 左图assetUrl: assets/img/logo.png,width: 147.w,height: 41.w,),ba…

【计算机网络】lab2 Ethernet(链路层Ethernet frame结构细节)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;计算机网络_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2.…

ansible剧本快速上手

playbook剧本介绍 是什么&#xff1a;能户长期保存&#xff0c;且能实现批量配置、部署…的文件格式&#xff1a;yaml格式。用 空格 冒号 头号 句号语法检测&#xff1a;ansible-playbook --syntax-check install-zabbix.yaml或则 -C检测取消默认任务&#xff1a;gather_facts…

【LeetCode每日一题】——434.字符串中的单词数

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【时空频度】八【代码实现】九【提交结果】 一【题目类别】 字符串 二【题目难度】 简单 三【题目编号】 434.字符串中的单词数 四【题目描述】 统计字符串中的单词个…

C++ OpenGL学习笔记(1、Hello World空窗口程序)

终于抽出时间系统学习OpenGL 教程&#xff0c;同时也一步一步记录怎样利用openGL进行加速计算。 目录 1、环境准备1.1、库的下载1.2、库的选择及安装 2、OpenGL第一个项目&#xff0c;Hello World!2.1、新建hello world控制台项目2.2、配置openGL环境2.2.1 包含目录配置2.2.2 …

MySQL复制问题和解决

目录 环境介绍 一&#xff0c;主库执行delete&#xff0c;从库没有该数据 模拟故障 修复故障 二&#xff0c;主库执行insert&#xff0c;从库已存在该数据 模拟故障 故障恢复 三&#xff0c;主库执行update&#xff0c;从库没有该数据 模拟故障 故障恢复 四&#xf…

AWTK 在树莓派 pico 上的移植笔记

1. 配置文件 (awtk_config.h) pico 和 stm32f103 的配置差不多&#xff0c;虽然 pico 的内存要大不少&#xff0c;但是也不足提供一个完整的 FrameBuffer&#xff0c;所以只能使用片段 LCD。 我们在 awtk-stm32f103 的配置 基础稍作修改即可。 /* 使用片段 LCD */#define FRA…

构建MacOS应用小白教程(打包 签名 公证 上架)

打包 在package.json中&#xff0c;dependencies会被打进 Electron 应用的包里&#xff0c;而devDependencies则不会&#xff0c;所以必要的依赖需要放到dependencies中。files中定义自己需要被打进 Electron 包里的文件。以下是一个完整的 mac electron-builder的配置文件。 …

flink sink doris

接上文&#xff1a;一文说清flink从编码到部署上线 网上关于flink sink drois的例子较多&#xff0c;大部分不太全面&#xff0c;故本文详细说明&#xff0c;且提供完整代码。 1.添加依赖 <!--doris cdc--><!-- 参考&#xff1a;"https://doris.apache.org/zh-C…

GhostRace: Exploiting and Mitigating Speculative Race Conditions-记录

文章目录 论文背景Spectre-PHT&#xff08;Transient Execution &#xff09;Concurrency BugsSRC/SCUAF和实验条件 流程Creating an Unbounded UAF WindowCrafting Speculative Race ConditionsExploiting Speculative Race Conditions poc修复flush and reload 论文 https:/…

【STM32 Modbus编程】-作为主设备写入多个线圈和寄存器

作为主设备写入多个线圈和寄存器 文章目录 作为主设备写入多个线圈和寄存器1、硬件准备与连接1.1 RS485模块介绍1.2 硬件配置与接线1.3 软件准备2、写入多个线圈2.1 数据格式2.2 发送数据2.3 结果3、写入多个寄存器3.1 数据格式3.2 发送数据3.3 结果本文将实现STM32作为ModBus主…

国标GB28181协议平台Liveweb:搭建建筑工地无线视频联网监控系统方案

随着科技高速发展&#xff0c;视频信号经过数字压缩&#xff0c;通过互联网宽带或者移动4G网络传递&#xff0c;可实现远程视频监控功能。将这一功能运用于施工现场安全管理&#xff0c;势必会大大提高管理效率&#xff0c;提升监管层次。而这些&#xff0c;通过Liveweb监控系统…

AS-REP Roasting离线爆破攻击

针对一个域内用户&#xff0c;其账户选项有个设置叫作 “不要求 kerberos 预身份验证”&#xff0c;它默认是关闭的。 当 “不要求 kerberos 预身份验证” 选项被勾选&#xff0c;会出现以下效果&#xff1a; as-req 报文中不需要添加用户 hash 加密的时间戳&#xff0c;自动返…

python中的局部变量、全局变量问题的思考(对比于c语言)

今天在运行python时遇到了局部变量和全局变量的问题&#xff0c;令我很迷惑。 首先&#xff0c;我在学习python之前先学习了c语言&#xff0c;所以c语言的一些东西影响了我对这个问题的思考。 在c语言中 局部变量和全局变量的区别就在于作用域的范围大小。在c语言中&#xf…

进网许可认证、交换路由设备检测项目更新25年1月起

实施时间 2025年1月1日起实施 涉及设备范围 核心路由器、边缘路由器、以太网交换机、三层交换机、宽带网络接入服务器&#xff08;BNAS&#xff09; 新增检测依据 GBT41266-2022网络关键设备安全检测方法交换机设备 GBT41267-2022网络关键设备安全技术要求交换机设备 GB/…

文件,IO流

目录 一 java 1. IO流 1&#xff09;输入输出&#xff08;以程序的视角判断 &#xff09; 1.1 IO流的分类 1&#xff09;字符流效率高于字节流 1.2 流和文件的关系 2. inputstream--字节输入流 2.1 fileinputstream 2.1.1常用方法&#xff1a; 1&#xff09;单个字符…