详解c++---模板(初阶)

news2024/10/5 16:32:06

这里写目录标题

  • 为什么会有模板
  • 函数模板
  • 如何解决类型不同而导致模板无法实例化的问题
  • 类的模板

为什么会有模板

c语言在面对同一个功能不同的类型的数据时得创建出来多个不同名的函数来依次达到目的,比如说我们下面的代码:

#include<stdio.h>
int add1(int x, int y)
{
	return x + y;
}
double add2(double x, double y)
{
	return x + y;
}
double add3(int x, double y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int b = 10;
	double c = 10.0;
	double d = 20.0;
	printf("%d ", add1(a, b));
	printf("%lf ", add2(c, d));
	printf("%lf ", add3(a, c));
	return 0;
}

这样我们才能正常的执行下面的三个打印函数:
在这里插入图片描述
很明显这个时非常的麻烦的,所以我们的c++为了解决上述取名字的问题就提出了函数重载这个概念,当函数参数个数,类型,顺序不同的时候我们就不用给这些函数取不同的名字,直接取成相同的名字就可以了,比如说上面的三个函数的参数类型不同,但是执行的功能确实一样的,那么我们这里就可以给这三个函数取成相同的名字,比如说这样:

int add(int x, int y)
{
	return x + y;
}
double add(double x, double y)
{
	return x + y;
}
double add(int x, double y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int b = 10;
	double c = 10.0;
	double d = 20.0;
	printf("%d ", add(a, b));
	printf("%lf ", add(c, d));
	printf("%lf ", add(a, c));
	return 0;
}

但是我们通过观察发现,就算我们这里有了函数的重载我们这里依然还是有点点的麻烦啊,当一名cv工程师还是非常的累的,所以既然这里的功能都是一样的,那我们能不能让编译器自己通过我们传的参数和功能的实现原理来自行生成对应的函数呢?答案是可以的,c++给出了一个全新的内容叫模板,这个模板就刻印通过我们给的参数的类型以及提供的实现的功能原理,来自动的生成对应的函数,那么接下来我们就来看看如何写一个函数的模板出来。

函数模板

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。函数模板的格式为:
template<typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表){}
那么这里就可以写一个一个交换数据的Swap函数,那么这个就非常的简单我们可以通过引用很好的实现,比如说我们下面的代码:

#include<iostream>
using namespace std;
void swap(int& x, int& y)
{
	int c = x;
	x = y;
	y = c;
}
int main()
{
	int a = 10;
	int b = 20;
	swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}

在这里插入图片描述
那么这就说明我们这里的函数实现的是正确的,但是这里有个问题就是这个函数只能交换整型的数据,如果我们想要交换其他类型的数据就只能再写一个函数来实现函数重载,但是这么做的话就又十分的麻烦,所以我们这里就可以采用模板的形式,首先在函数的前面加上这么一句话:

template<typename T>
void swap(int& x, int& y)
{
	int c = x;
	x = y;
	y = c;
}

因为我们这里的函数就只有一个所以我们在写模板的时候就只用写一个typename ,然后这个T就是所谓的类型,编译器会根据你传的参数来推出这个T是什么,如果你传两个double类型的数据过来,那么编译器就会推出这里的T类型为double类型,如果你传过来的是两个int类型,那么编译器就会推出这里的T类型为int类型,所以接下来我们就要这个函数的参数进行修改,将这里的int&改成T&,因为x的类型不同,所以在函数体里面我们也得将int c改成T c,那么我们完整的代码就如下:

template<typename T>
void Swap(T & x, T & y)
{
	T c = x;
	x = y;
	y = c;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(a, b);
	cout << a << " " << b << endl;
	double c = 10.1;
	double d = 20.2;
	Swap(c, d);
	cout << c << " " << d << endl;
	return 0;
}

我们可以通过下面的代码来看一下这里也是成功的交换两组不同类型的数据:在这里插入图片描述
那么这里有个问题就是我们这里两组数据调用的都是swap函数,那这里有个问题就是他们调用的是同一个函数吗?答案很显然不是的,我们这里调用的swap函数既不是上面的模板,也不是该模板生成的同一个函数,而是该模板生成的两个不同的函数,那么这里我们就可以通过汇编指令来进行验证:
在这里插入图片描述
在这里插入图片描述
这里我们就可以发现他这里call的两个函数是完全不一样的,所以这里就可以证明出我们调用的是两个不一样的函数,而不是一个函数或者模板,那看到这里想必大家心中一定有一个问题就是如果我们这里传的参数两个类型不一样的话,那又会出现什么样的情况呢?我们这里可以通过下面的代码来进行验证一下:

int main()
{
	int a = 10;
	double b = 10.1;
	Swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}

然后我们运行一下就可以看到编译器报出了这样的错误:
在这里插入图片描述
大致的意思就是我们的编译器无法根据你传的参数来推导出你这个T是什么类型,也就无法根据这个模板生成对应的函数,那么这时有小伙伴说啊,之前没有学模板的时候我们函数在传参时因为强制类型转换所以我们是可以传不同类型的数据的,那这里为什么就不能强制类型转换一下呢?答案很简单首先强制类型转换会产生一个临时常量出来,而我们这里的参数是引用类型并且没有加const修饰,所以会报错,其次强制类型转换发生的过程是在实参传递给形参的过程,而我们这里压根就还没有等到传参的时候,因为类型T无法推出而导致函数无法生成,所以连函数都没有造出来又哪来的传参呢?所以这也就是为什么我们这里没有强制类型转换没有调用函数的原因,那我们该如何解决这个问题呢?我们接着往下看。

如何解决类型不同而导致模板无法实例化的问题

第一个方法:用户自己来强制转化
既然编译器因为函数无法实例化出来而导致无法强制类型转换,那这里我们就可以人为的先对其进行强制类型转换,将这里的double类型的数据转换为int类型,这样就可以让其正常的实例化函数出来,并调用函数进行传参,比如说我们下面的代码:

int main()
{
	int a = 10;
	double b = 10.1;
	Swap(a, (int)b);
	cout << a << " " << b << endl;
	return 0;
}

但是用这样的方法就会出现一个问题就是,我们这里是交换函数,而使用强制类型转换就必然会导致出现临时变量,而这样的话函数在接收的时候就得加个const出来以防权限放大,但是这样的话就与这个函数的功能相违背,因为这个是交换函数,加了一个const之后我们这里的数据就无法发生更改了,所以互相矛盾了,所以我们就换一个模板再来试试,我们来看看下面的这段函数:

template<typename T>
T Add(T& x, const T& y)
{
	return x + y;
}
int main()
{
	int a = 10;
	double b = 20.1;
	cout << Add(a, (int)b) << endl;
	return 0;
}

在这里插入图片描述
这样我们就可以打印30出来,并且不会报错。
第二个方法:显示实例化
之前我们都是让编译器自己来推参数的类型是什么,其实我们也可以不用编译器自己来推,我们来告诉他就行其形式为:在调用函数的函数名后面加上一个尖括号,在尖括号里面写入模板中所对应的类型,比如说下面的代码:

template<typename T>
T Add(const T& x,  const T& y)
{
	return x + y;
}
int main()
{
	int a = 10;
	double b = 20.1;
	cout << Add<int>(a, b) << endl;
	cout << Add<double>(a, b) << endl;
	return 0;
}

那么第一个打印函数里面的ab就都会变成int类型,第二个打印函数里面的ab就都会变成double类型,我们来看看这段代码的运行结果为;
在这里插入图片描述
第三个方法:多参模板
我们模板中的参数可以不止一个,所以面对这种不同类型的传参我们就可以使用多惨模板来进行解决,那么这里的形式就如下:

template<typename T1,typename T2>
T2 Add(const T1& x, const T2& y)
{
	return x + y;
}
int main()
{
	int a = 10;
	double b = 20.1;
	cout << Add<int,int>(a, b) << endl;
	cout << Add<double,double>(a, b) << endl;
	cout << Add(a, b) << endl;
	return 0;
}

打印的结果就如下:
在这里插入图片描述
那么这里就有一个问题,一个具体的函数可以和该函数对应的模板同时存在吗?答案是可以的,比如说我们下面的代码;

template<typename T1,typename T2>
T2 Add(const T1& x, const T2& y)
{
	return x + y;
}
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int x = 10;
	int y = 10;
	cout << Add(x, y) << endl;
	return 0;
}

我们将这段代码运行一下就可以看到这里并没有报错:
在这里插入图片描述
但是这里就有一个问题就是,这里调用的函数是我们自己写的那个函数,还是编译器通过模板自己生成的函数呢?那么这里大家要知道的一点就是,编译器也是一个懒人他能不自己生成就不会自己生成,所以我们这里调用的就是我们自己写的那个函数,而不是模板生成的。当然你要是非要编译器生成个与非模板函数一模一样的函数出来也不是不行比如说我们下面的代码:

int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}
void Test()
{
	Add(1, 2); // 与非模板函数匹配,编译器不需要特化
	Add<int>(1, 2); // 调用编译器特化的Add版本
}
int main()
{
	Test();
	return 0;
}

我们通过汇编指令就可以看到:
在这里插入图片描述
这里调用的两个函数地址不一样,那这就说明我们这里调用了两个不同的函数,这也就进一步说明了我们上面说的那个性质。

类的模板

有函数模板的同时就会有类的模板,大家想象一下这个场景,我写了一个栈的数据结构,然后我用栈来存储了一些整型的数据,然后过了一会我发现我又得存储一些浮点型的数据,那这时候你是不是得把之前写的那个栈的数据结构复制一份出来,将里面int全部都改成double啊,当然我们会用typedef来进行简化,但是这依然很麻烦还是得cv一下,我们说当一名cv工程师是非常累的,所以我们这里就有了类的模板,比如说下面的模板:

template<class T>//模板中既可以使用typename也可以使用class
class Vector
{ 
public :
 Vector(size_t capacity = 10)
 : _pData(new T[capacity])
 , _size(0)
 , _capacity(capacity)
 {}
 
 // 使用析构函数演示:在类中声明,在类外定义。
 ~Vector();
 void PushBack(const T& data)void PopBack()// ...
 
 size_t Size()
 {
 return _size;
 }
 
 T& operator[](size_t pos)
 {
 assert(pos < _size);
 return _pData[pos];
 }
 
private:
 T* _pData;
 size_t _size;
 size_t _capacity;
};

跟函数的模板差不多,都是在头上加一个template<class T>,然后在类中进行对应类型的替换,将原来的一些数据的类型修改成T即可,但是这里有一点不同的就是类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。比如说我们下面的代码:

// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;

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

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

相关文章

计算机毕业设计ssm+vue基本微信小程序的手机预约维修系统

项目介绍 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势&#xff1a;对于电脑维修预约当然也不能排除在外,随着网络技术的不断成熟,带动了电脑维修预约,它彻底改变了过去传统的管理方式,不仅使服务管理难度变低了,还提升了管理的灵活…

通俗理解数据治理之主数据

1. 定义 1&#xff09;国家标准GB/T 36073-2018 《数据管理能力成熟度评估模型》中对主数据的定义&#xff1a;主数据是组织中需 要跨系统、跨部门进行共享的核心业务实体数据。 2&#xff09;IBM 公司在其有关主 数据管理的红皮书《Master Data Manangement:Rapid Deploymen…

软件测试web自动化测试

今天目标 1、自动化相关概念 2、自动化相关环境搭建 3、元素定位 课程大纲 1.核心重点&#xff08;第二章&#xff09; 2,提高代码质量&#xff0c;自动化水平(第三、四、五、六章) 3.项目实战&#xff08;第七章&#xff09; 4.理论及环境与定位&#xff08;第一章&am…

Linux服务器读写python环境变量

在公司项目开发过程中&#xff0c;代码都是放在服务器中进行运行的&#xff0c;使用本地的idea工具连接到服务器。 如python开发中&#xff0c;将使用pycharm工具连接服务器&#xff0c;如下图所示&#xff1a; 在项目中有线上正式环境、测试环境等&#xff0c;都是用不同环境变…

[附源码]计算机毕业设计计算机相关专业考研资料管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【Keras+计算机视觉+Tensorflow】实现基于YOLO和Deep Sort的目标检测与跟踪实战(附源码和数据集)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 一、YOLO目标检测算法 YOLO是端到端的物体检测深度卷积神经网络&#xff0c;YOLO可以一次性预测多个候选框&#xff0c;并直接在输出层回归物体位置区域和区域内物体所属类别&#xff0c;而Faster R-CNN仍然是采用R-CNN那种…

Windows 下Zookeeper 配置参数解读 和查看注册了哪些服务

zookeeper 配置文件解读 本地配置文件奉上: # The number of milliseconds of each tick tickTime2000 # The number of ticks that the initial # synchronization phase can take initLimit10 # The number of ticks that can pass between # sending a request and gett…

图像处理学习笔记-10-图像分割与边缘检测

图像分割的三大类方法&#xff1a;根据区域间灰度不连续搜寻区域之间的边界&#xff0c;在奇异性检测、边缘连接和边界检测介绍&#xff1b;以像素性质的分布进行阈值处理&#xff0c;在阈值处理介绍&#xff1b;直接搜寻区域进行分割&#xff0c;在基于区域的分割中介绍 奇异…

数据库、计算机网络,操作系统刷题笔记8

数据库、计算机网络&#xff0c;操作系统刷题笔记8 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle&…

网站各个功能基本实现

1.前面已经介绍前后端的交互 2.今天实现网站功能的基本实现 也就是查询数据库。 网站类型为展示型网站。 页面如下&#xff1a; 点击政府公告显示&#xff1a; 点击机构设置显示&#xff1a; 后面不一一展示&#xff0c;主要实现六大功能的展示功能。 后续就实现管理员维…

PostgREST的安装部署(Windows和Linux环境)

下载地址&#xff1a;https://github.com/PostgREST/postgrest/releases 官方文档地址&#xff1a;Overview of Role System — PostgREST 9.0.0 documentation Windows 先下载对应系统的安装包&#xff1a; 下载之后解压会得到一个postgrest.exe可执行文件 创建配置文件&a…

解决 Android 开发过程中 出现 Duplicate class(包冲突)

1、现在大部分的项目都是支持 Androidx 的&#xff0c;所以出现 Duplicate 的时候 先把 gradle.properties 文件中添加参数&#xff0c;支持使用AndroidX android.useAndroidXtrue android.enableJetifiertrue 2、有些 *.jar/*.aar 不支持 AndroidX 的时候&#xff0c;将上面…

抽取_内插_半带滤波器_多相滤波器

文章目录半带滤波器多相抽取滤波器多相内插滤波器半带抽取器和半带内插器参考资料&#xff1a;Xilinx FIR Compiler v7.2 LogiCORE IP Product Guide PG149半带滤波器 半带滤波器的阶数为偶数&#xff0c;系数长度为奇数&#xff0c;且除了中间系数为0.5外&#xff0c;其余偶数…

mybatisplus 使用mybatis中的配置、mapper配置文件

1、在application.properties中配置mybatis的配置文件路径&#xff0c;例如&#xff1a; #指定mybatis-config.xml的位置 mybatis-plus.config-location classpath:mybatis/mybatis-config.xml 即在和application.properties同级目录下的mybatis目录中创建mybatis的配置文件m…

数制编码详解:二进制八进制十六进制的转换,原码、补码、反码、移码的定义

参考资料&#xff1a;《深入理解计算机网络&#xff08;王达&#xff09;》 文章目录一&#xff0c;数制1.1 基本数制1.2 不同数制之间的相互转换二&#xff0c;编码一&#xff0c;数制 1.1 基本数制 “数制”是“数据进制”的简称&#xff0c;也就是表示数据逢几进位的意思&a…

chatGPT的体验,是不是真智能?

目录 &#x1f3c6;一、前言 &#x1f3c6;二、安装 &#x1f3c6;三、普通对话 &#x1f6a9;1、chatGPT的ikun性 &#x1f6a9;2、chatGPT的日常对话 &#x1f3c6;四、实用能力 &#x1f3c6;五、代码改正 &#x1f3c6;六、写代码 &#x1f3c6;七、讲解代码 &#x1f3c6;…

ESXI精简thin磁盘迁移存储位置保留磁盘类型不变-无vc方式

运行2年了ESXI的SSD存储上很多VM并带多层快照&#xff0c;最近磁盘速度异常&#xff0c;迁移到新存储&#xff0c;都是thin磁盘&#xff1b;如有vCenter条件&#xff0c;采用“迁移”即可完美解决&#xff0c;既使磁盘类型thin不变又保留快照结构。如无vCenter条件的操作方式细…

Unity-iOS工程导出Xcode自动构建方法

Unity-iOS发布基本流程首先在Unity中导出Xcode工程&#xff0c;然后在Xcode工程中设置IOS打包的一些流程&#xff0c;诸如引入lib、framework或其他资源、设置签名及其他编译设置、加入编译脚本等等操作。 这些操作如果每次都是在导出Xcode后手动操作&#xff0c;一来浪费时间…

【C++ STL】-- 红黑树的插入实现

目录 红黑树的概念 二叉树搜索树的应用 红黑树节点的定义 红黑树结构 insert 需调整的多情况的核心思维&#xff1a; 需调整的多情况分类讲解&#xff1a; 情况一: 情况二: 情况三: 总结&#xff1a; 代码实现&#xff1a; 对于红黑树是否建立成功的检查 升序打印…

C++--类型转换--1128

1.C语言中的类型转换 分为隐式类型转化、显示强制类型转化。 隐式类型转化用于意义相近的类型&#xff0c;比如int,double,short都是表示数值的类型 int i1; double di; //编译、结果无问题 这里是隐式类型转换。 显示强制类型转换 显示强制类型用于意义不相近的类型&…