初识C++ · C++11(3)

news2024/9/20 20:22:37

前言:

本文介绍的是包装器以及线程库的简单了解,但是呢,线程是基于对Linux有一定的了解,所以本文就是简单介绍一下,介绍完包装器以及线程库的简单理解之后C++11的特性就到此为止,当然C++11远不止于此!


1 包装器

先来了解包装器的大体分类,分为两种,一种是类模板包装器,一种是函数模板包装器,说是两大类,今天介绍两个,一个是function包装器,一个是bind包装器。

1.1 function

在学习function之前,我们先来了解一下什么是可调用对象?

可调用对象就是指可以实例化的并且可以调用的对象呗:
仿函数是吧?但是缺点是要实例化出多个类,并且类型不太好控制,比如面对自定义类型的操作。

函数指针是吧?但是C++不太喜欢使用。

lambda表达式是吧?但是lambda表达式没有类型概念,所以每次需要auto接受,相对于其他两个来说,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的地址都是一样的,并且个数为1.

此时可以使用function了,function的头文件位于functional中,定义如下:

看着是有点看不懂吧,咱们直接使用一下:


struct Functor
{
	int operator()(const int& a, const int& b)
	{
		return a + b;
	}
};

int f(const int& a, const int& b)
{
	return a + b;
}


int main()
{
	function<int(int, int)> f1 = Functor();
	function<int(int, int)> f2 = f;
	function<int(int, int)> f3 = [](const int a, const int b) {return a + b; };
	
	f1(1, 2);
	f2(3, 4);
	f3(5, 4);

	return 0;
}

语法看起来有点怪的,最外层的尖括号里面是返回值和参数,圆括号括起来的是函数参数,外面的是函数返回值,注意,这里需要保持基本类型一致,比如前面写f2 = f,f函数的基本数据类型是Int,所以说包装器function的基本类型也是三个int。

那么怎么使用function解决类模板实例化多份的问题呢?

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()
{
	function<double(double)> func1 = f;
	cout << useF(func1, 11.11) << endl;
	
	function<double(double)> func2 = Functor();
	cout << useF(func2, 11.11) << endl;
	
	function<double(double)> func3 = [](double d)->double 
		{
			return d /4; 
		};
	cout << useF(func3, 11.11) << endl;

	return 0;
}

简单呀,包装一下就可以了,但是为什么经过包装了之后就可以完成只实例化一个对象呢?

前面实例化多份对象的原因是因为模板参数接受的参数不一样,函数指针啊 匿名对象啊 lambda表达式啊,但是最终完成的操作都是实现除,所以就实例化了多份对象。

经过function包装了之后,参数都是一样的,就不存在说一定要经过函数模板再去调用了,直接就调用了。

以上是function的基本用法,那么现在进阶一下。

上面的包装都是包装一整个类或者是函数对象等,如果一个类有多个成员函数,我们只想要包装其中一个成员函数呢?

这里就要分为静态成员函数和非静态成员函数了。

首先来看一下静态成员函数的包装:

class Master
{
public:
	static int Mass(int a, int b)
	{
		return a + b;
	}
	int Masn(int a,int b)
	{
		return a + b;
	}
};

int main()
{
	//&符号可以加可以不加
	function<int(int,int)> f1 = &Master::Mass;
	f1(2,2);

	return 0;
}

因为是静态函数,所以需要类域来访问,这里的语法就是这样的,对于&符号来说加不加都是可以的,重点在于非静态成员函数:

class Master
{
public:
	static int Mass(int a, int b)
	{
		return a + b;
	}
	int Masn(int a,int b)
	{
		return a + b;
	}
};

int main()
{
	//&符号可以加可以不加
	function<int(int,int)> f1 = &Master::Mass;
	f1(2,2);

	//&符号必须加
	function<int(Master*,int, int)> f2 = &Master::Masn;
	Master m1;
	f2(&m1, 1, 2);

	function<int(Master,int,int)> f3 = &Master::Masn;
	f3(Master(),1, 1);

	return 0;
}

先看语法使用,语法使用如上。

首先,取地址符号是一定要加的,其次,域名访问限定符也是要加的,那么为什么要传类类型的指针或匿名对象呢?

思考一个问题,非静态成员函数的参数有多少个?这里要特别注意的是,除了显式的两个int,还有this指针!所以,为什么保持参数一致,我们就应该传类类型的指针。

但是为什么传匿名对象也可以呢?思考一个问题,参数传给的是funtion吗?参数是传给类对象的,然后通过类对象调用函数,funtion只是起到了一个包装的作用,实际上的参数调用还是要通过类对象来实现,那么函数由谁调用,由函数指针,或者是函数对象调用,所以这里包装非静态成员函数的实质还是要通过对象来调用函数,所以两种传值的方式都是可以的。


1.2 bind

bind属于funtional里面的Functions部分,function属于classes部分,这也说明了它们一个是类模板包装器,一个是函数模板包装器。

那么bind的作用是什么呢?

bind一般有两个作用,一个是调整参数的顺序,一个是调整参数的个数,其中调整顺序一般不太用,毕竟用处没那么大,调整参数的个数是很有用的。

但是要注意,这里的调整参数个数不是删除某个参数或者是添加参数什么的,这样干的话已经破坏了函数本身了,这里的调整参数,比如让参数从4个到3个的意思是将其中的一个参数固定,就像缺省值那样,你不用传值,函数调用的时候已经固定了要传这个值,这是调整参数个数的本质。

调用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为第二个参数,以此类推。

而_1 _2所在的命名空间是在placeholders里面,该命名空间同样也在functional里面,对应的1和2就是参数的顺序。

int Sub(int a, int b)
{
	return a - b;
}
int main()
{
	//正常调用
	auto f1 = Sub;
	f1(1, 2);
	//未调整顺序 -> bind
	auto f2 = bind(Sub, placeholders::_1, placeholders::_2);
	cout << f2(10, 5) << endl;
	//调整顺序 -> bind
	auto f3 = bind(Sub, placeholders::_2, placeholders::_1);
	cout << f3(10, 5) << endl;

	return 0;
}

根据bind的参数,第一个参数是可调用对象,后面的是参数列表,所以第一个参数传的就是可调用对象,调整参数顺序就是调整_1 _2的位置,可以理解为_2 代表的是第二个参数,所以_1 _2交换位置就是调整参数的顺序了,这里呢,多参数也是一样的,都可以进行相应的顺序调整。

这里需要注意的是,如果是对一个类的话,是不能传一整个类的,只能传类的某个函数。

接下来是参数个数的调整,说白了,就是固定参数:

class Sub
{
public:
	Sub(int x)
		:_x(x)
	{}

	int sub(int a, int b)
	{
		return (a - b) * _x;
	}

private:
	int _x;
};
int main()
{
	
	auto f2 = bind(&Sub::sub, placeholders::_1, placeholders::_2,placeholders::_3);
	cout << f2(Sub(1), 10, 5) << endl;
	Sub sub(1);
	cout << f2(&sub, 10, 5) << endl;

	auto f3 = bind(&Sub::sub, &sub, placeholders::_1, placeholders::_2);//参数的顺序是相对的
	cout << f3(10, 5) << endl;

	return 0;
}

对于类来说,bind的时候,语法规定需要域名访问限定符和取地址符号,那么调用的时候,同function一样,选择指针调用或者是对象调用,所以可以传匿名对象,也可以传地址过去。

f3就是一种固定参数,因为成员函数的参数列表第一个就是this指针,所以第一个参数固定的话就是sub的地址,那么为什么后面是_1 _2呢?因为第一个参数固定了,相当于只有两个参数了,所以说是相对的。

对于固定参数来说,可以选择固定任意的,比如可以固定第二个参数,其他参数不固定的话顺序和上面的一样的。
至此,引入一个让人意想不到的事实:

包装器的底层,同样是仿函数

 00007FF6CB3BF0C1  call  std::_Binder<std::_Unforced,int (__cdecl Sub::*)(int,int) __ptr64,std::_Ph<1> const & __ptr64,std::_Ph<2> const & __ptr64,std::_Ph<3> const & __ptr64>::operator()<Sub,int,int,0> (07FF6CB3B191Fh)

第一个是function的底层,第二个是bind的底层,不太好截图。

很神秘吧! 


感谢阅读!->因为作者对于线程的理解确实不够,所以有关线程的介绍留在后面,嘿嘿。

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

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

相关文章

Pixel Adventure Unity2D开发完整指南

本文参考&#xff1a;2-2. Get and Setup Assets_哔哩哔哩_bilibili 1、下载资源 在Asset Store中下载Pix Adventure1 2的资源&#xff1a; 在import的时候&#xff0c;不用到Scene import进来&#xff0c;如下图所示&#xff0c;Scenes目录反勾选一下。 两个资源都下载完成后…

朱利亚集合和曼德布洛特集合及其图像

朱利亚集合和曼德布洛特集合及其图像 朱利亚集合&#xff08;Julia Set&#xff09;和 曼德布洛特集合&#xff08;Mandelbrot Set&#xff09;除了数学理论上的意义&#xff0c;所生成的分形图像&#xff0c;因其独特的几何美感和无限的复杂性&#xff0c;还被广泛应用于计算机…

增强现实系列—深入探索ARKit:平面检测、三维模型放置与增强现实交互

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

技术研究:Redis 实现消息队列

综述 我们先看看消息队列的消息存取到底有哪些需求吧&#xff1a; 需求1&#xff1a;消息保序&#xff1a;由于消费者是异步处理消息&#xff0c;但是消费者需要按照生产者发送消息的顺序来处理消息&#xff0c;避免后发送的消息被先处理了。 需求2&#xff1a;重复消息处理&…

【区块链+金融服务】河北股权交易所综合金融服务平台 | FISCO BCOS应用案例

区域性股权市场是我国资本市场的重要组成部分&#xff0c;是多层次资本市场体系的基石。河北股权交易所&#xff08;简称&#xff1a;河交所&#xff09; 作为河北省唯一一家区域性股权市场运营机构&#xff0c;打造河北股权交易所综合金融服务平台&#xff0c;将区块链技术与区…

信号与系统MATLAB实验:连续信号的采样与恢复

一、实验内容 &#xff08;1&#xff09;修改示例中的门信号宽度、采样周期等参数&#xff0c;重新运行程序&#xff0c;观察得到的采样信号时域和频域特性&#xff0c;以及重构信号与误差信号的变化。 示例1&#xff1a;选取门信号f(t) g2(t)为被采样信号。利用MATLAB实现对…

搭建超好用的个人网盘

目录 前言filebrowser下载地址介绍优点缺点部署效果 cloudreve官网介绍优点缺点部署效果 总结 前言 目前我使用过的文件存储管理软件&#xff0c;包括fastdfs、minio、filebrowser、cloudreve&#xff0c;这4款软件&#xff0c;我自己亲自搭建且都使用过&#xff0c;fastdfs很…

Datawhale X 魔搭 AI夏令营第四期 AIGC方向 task02笔记

AI工具使用 1. baseline 代码2. 使用通义千问理解代码2.1 工作流程2.2 逐行释意 3. 使用通义千问生成 Prompt3.1 生成的 Prompt3.1 根据 Prompt 生成的图片 1. baseline 代码 !pip install simple-aesthetics-predictor!pip install -v -e data-juicer!pip uninstall pytorch-…

docker的基本管理和应用

1、概念 docker是一个开源的应用容器引擎&#xff0c;基于go语言开发的。 docker是运行在linux的容器化工具&#xff0c;可以理解为轻量级的虚拟机。 可以在任何主机上轻松创建的一个轻量级、可移植的、自给自足的容器 2、设计的理念 鲸鱼——宿主机 集装箱——独立运行的…

07:【stm32】中断一:NVIC的配置

中断 1、中断的简介1.1、什么是中断1.2、为什么需要中断 2、中断的优先级2.1、中断优先级的表示方法 3、NVIC3.1、什么的NVIC3.2、NVIC的内部结构3.3、中断向量表3.4、程序实现①开启中断源②配置NVIC③中断响应函数 1、中断的简介 1.1、什么是中断 正在进行的事务被突发事件打…

1688商品详情API返回值中的供应商信息

在使用1688&#xff08;阿里巴巴中国站&#xff09;的商品详情API时&#xff0c;API的返回值中通常会包含丰富的产品信息&#xff0c;包括供应商&#xff08;卖家&#xff09;的信息。不过&#xff0c;具体的返回值内容可能会根据API的版本、调用参数以及API的更新情况有所不同…

什么是国际网络组网?

国际网络组网是指通过互联网技术将全球各地的不同网络相互连接&#xff0c;以实现信息交换与资源共享的过程。在这一过程中&#xff0c;涉及到数据传输、协议转换、跨网络通信等多个技术领域&#xff0c;旨在实现全球信息的无缝互联互通。国际网络组网的主要目标是扩大网络覆盖…

半导体RFID识别系统134.2K低频读写器|读写头JY-V610之SECS协议通信说明

什么是SECS协议&#xff1f; SECS&#xff08;Semiconductor Equipment Communication Standard&#xff09;协议是半导体设备通讯标准&#xff0c;用来统一各个生产设备之间以及生产设备和控制设备之间的通讯&#xff0c;由SEMI&#xff08;Semiconductor Equipment and Mate…

手搓滑动窗口

前言&#xff1a;好久没写滑动窗口&#xff0c;导致一些边界问题处理不好&#xff0c;back和top的初始值都搞不好 #include<bits/stdc.h> using namespace std;const int N (int)1e65; int n,m; int a[N],b[N]; int back,top;int main(){cin >> n >> m;for(…

【前端设计方案】H5 图片懒加载 SDK

实现思路 定义<img srcloading.png data-srcxxx.png/>页面滚动&#xff0c;图片露出时&#xff0c;将 data-src 赋值给 src 注意事项&#xff1a;滚动要节流 技术要点 获取图片的位置 elem.getBoundingClientRect() 图片 top < window.innerHeight 时&#xff0c;图片…

【学习笔记】爱立信SPO 1400 CRAFT软件基础知识9——Bridge(网桥)显示参数

一、前期准备 条件1.确认已正确使用爱立信SPO 1400 CRAFT软件通过网络登录设备&#xff08;以下简称NE&#xff09; 具体登录教程参考&#xff1a;使用爱立信SPO 1400 CRAFT软件通过网络登录设备的详细过程 二、学习内容&#xff1a; 提示&#xff1a;学习爱立信SPO 1400 CRA…

阿里员工:33岁,房贷还剩223万,每月还1.5W,失业中

中年失业 中年失业&#xff0c;真的很难。 虽然人到中年&#xff0c;一般多少都会有些储蓄&#xff0c;但也意味着会有更多的支出。 最近&#xff0c;一位阿里员工&#xff08;这会可能是前阿里员工了&#xff09;在社区分享到自己的经历。 贴主 33 岁&#xff0c;作为已结婚有…

搬瓦工日本软银线路VPS测评

搬瓦工日本VPS支持softbank/软银&#xff0c;Japan: Osaka (Softbank) &#xff0c;网络在2.5Gbps-10Gbps之间&#xff0c;底层为KVM虚拟、纯SSD阵列、支持在多机房之间切换。搬瓦工软银来国内的网络情况怎么样&#xff1f;测评数据大致如下&#xff1a; CPU具体型号不知道&…

深入了解指针(6)

文章目录 1.函数指针数组2.转移表3.回调函数 1.函数指针数组 存放函数指针的数组 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> int add(int x, int y) {return x y; } int sub(int x, int y) {return x - y; }int main() {int (*p1)(int x, int y) add;int (…

基于python的百度迁徙迁入、迁出数据分析(八)

副标题&#xff1a;从百度迁徙数据看——重大公共卫生事件的影响 先来回顾一下&#xff0c;迁徙规模指数定义&#xff1a;反映迁入或迁出人口规模&#xff0c;城市间可横向对比。 2019年—2022年的部分春运数据已经不可查&#xff0c;用的环哥的数据&#xff0c;可参考环哥的…