C++模板从入门到入土

1. 泛型编程  

如果我们需要实现一个不同类型的交换函数,如果是学的C语言,你要交换哪些类型,不同的类型就需要重新写一个来实现,所以这是很麻烦的,虽然可以cv一下,有了模板就可以减轻负担。

下面写一个适合所有类型的交换就可以这样写。

template<typename T>
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 1.0, d2 = 2.2;swap(a1, a2);swap(d1, d2);return 0;
}

让我们先从文字上来了解什么是泛型编程,泛型指的是广泛类型的意思。

 泛型编程:编写与类型无关的调用代码,是代码复用的一种手段。 模板是泛型编程的基础。

问题:我们其实如果用函数重载也能解决问题,但是为什么我们还是有模板这个东西呢?

1.重载的只是函数类型不同,代码相同的部分很多,代码复用率很高。
2.如果有一行代码是有问题的话,这些重载的代码都是需要修改的
那我们就可以给编译器一个例子,然后让编译器自己去生成,就像古代的磨具一样,我们再磨具上印出东西,然后就拿的这个磨具去印出相同的东西,这不是很方便的东西。

函数模板

1.函数模板的概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特  类型版本。

2.函数模板的格式

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}

我们可以拿Swap这个例子来模拟

template<typename T>
void Swap( T& left, T& right)
{T temp = left;left = right;right = temp;
}

1.template是关键字

2.typename是修饰后面T的关键字,也有class这个关键字,class这个关键字比较短,所以我们用这个比较多。
3.T1, T2, ..., Tn 表示的是函数名,可以理解为模板的名字,名字你可以自己取。

注意事项:函数模板不是一个函数,而是我们的编译器拿的这个函数模板去实例化出一个一个的函数来的,我们可以理解为函数的模板

函数模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模 板就是将本来应该我们做的重复的事情交给了编译器

 

#include<iostream>using namespace std;
template<class T>
void Swap(T& x, T& y)
{T tmp(x);x = y;y = tmp;
}
int main()
{int x = 1;int  y = 2;double d1 = 2.2;double d2 = 3.3;cout << "交换前->" << x << " " << y << endl;cout << "交换前->" << d1 << " " << d2 << endl;Swap(x, y);Swap(d1, d2);cout << "交换后->" << x << " " << y << endl;cout << "交换后->" << d1 << " " << d2 << endl;return 0;
}

我们可以看到的是我们的数据也是成功的进行交换了,那我们来想想他的原理是什么呢,首先编译器是会根据函数模板生成不同的函数,他们的类型是不同的。而且他们的函数栈帧不是同一个。

 编译器是会根据这个函数模板去实例化不同的函数出来,所以在函数栈帧上调用的不是同一个函数栈帧,我们也可以来看汇编代码,看看call的地址是不是同一个地址。

所以可以看出我们不是调用的用一个函数。

在编译器编译阶段 ,对于模板函数的使用, 编译器需要根据传入的实参类型来推演生成对应类型的函数 以供调用。比如:当用 double 类型使用函数模板时,编译器通过对实参类型的推演,将 T 确定为 double 类型,然 后产生一份专门处理 double 类型的代码 ,对于字符类型也是如此。

那我们下面就来探讨编译器是怎么进行实例化的。

 

函数模板的实例化

 其实过程是很简单的,我们在编译阶段的时候,告诉我们的函数模板你要去根据类型进行实例化,然后因为T是函数模板的参数,所以如果我们传int过去的时候他就知道T是int,所以的T改成int去实例化出一个函数出来。

但是函数模板在实例化的过程中也是会出现问题的,比如我们可以来下面的这种情况,我们下一个简单的Add函数模板,然后在main函数里面进行相加计算出结果,我们可以来看看如果不是同一个类型的化会出现怎样的问题。

#include<iostream>using namespace std;
template<class T>
T Add(const T& x, const T& y)
{return x + y;
}int main()
{int x = 1;int  y = 2;double d1 = 2.2;double d2 = 3.3;int ret1 = Add(x, y);double ret2 = Add(d1, d2);cout << ret1 << " " << ret2;return 0;
}

首先这样的代码是没有问题的,但是如果我们是x+d1呢,我们来看看他的报错信息。、

 

如果是这样写的化报错信息就是这个样子的,所以我们需要怎么进行修改才行呢。

 显式实例化:在函数名后的<>中指定模板参数的实际类型

没错,我们是需要进行显示实例化的,但是我们应该如何进行显示实例化呢,规则很简单。

上面的Add就可以写成。

Add<int>(x,d1);

 我们的代码是可以运行的,但是会有这样的警告,其实是可以忽略的,因为我们本生就是不同类型的相加,肯定会产生强转的。

对于模板函数的使用,编译器需要根据传入的实参类型来推演,生成对应类型的函数以供调用。但是我们可以显示的去实例化,规则和Add是一样的道理。 

像第一个 Add<int>(a1, a2)  ,a2 是 double,它就要转换成 int 。

第二个 Add<double>(a1, a2),a1 是 int,它就要转换成 double。

这种地方就是类型不匹配的情况,编译器会尝试进行隐式类型转换。

像 double 和 int 这种相近的类型,是完全可以通过隐式类型转换的。

🔺 总结:

  • 函数模板你可以让它自己去推,但是推的时候不能自相矛盾。
  • 你也可以选择去显式实例化,去指定具体的类型。

 模板参数的匹配原则

场景:我们会写一个关于Add的函数模板和实现一个Add的函数,类型是int那他到底会配对那个呢。

#include<iostream>using namespace std;
template<class T>
T Add(const T& x, const T& y)
{return x + y;
}
int Add(int x, int y)
{return x + y;
}
int main()
{int x = 1;int  y = 2;double d1 = 2.2;double d2 = 3.3;int ret1 = Add(x, y);double ret2 = Add(d1, d2);Add<int>(x, d1);cout << ret1 << " " << ret2;return 0;
}

就是像这样的场景,那我们如果函数是Add(int ,int)的时候是调用哪个呢。

规则:有现成的就用现成的呗,我们函数模板进行实例化是要根据类型去实例化的,但是我们已经有一个关于它的函数了,这个函数是最适合你的,你还要去生成一个,都多余了。

 

所以我们就不会再去麻烦编译器去再生成一个函数来实现了。

总结:

① 一个非模板函数可以和一个同名的模板函数同时存在,

而且该函数模板还可以被实例化为这个非模板函数:

② 对于非模板函数和同名函数模板,如果其他条件都相同,

在调用时会优先调用非模板函数,而不会从该模板生成一个实例。

如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

 

第三点解释一下,就是我们再根据模板生成的时候,只会根据你给的类型去生成,而不存在强转这些,除非是隐式类型转换,隐式类型转换是会存在强转的可能性的。

类模板

1 类模板的定义格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};

 规则其实和函数模板是差不多的。

1.template是关键字

2.typename是修饰后面T的关键字,也有class这个关键字,class这个关键字比较短,所以我们用这个比较多。
3.T1, T2, ..., Tn 表示的是函数名,可以理解为模板的名字,名字你可以自己取。

这里要强调一下函数模板和类模板都是不支持分离声明和定义的,你可以再同一个文件里,但是不能在不同的文件进行声明和定义(指的是在一个.h进行声明,在一个.cpp进行定义)这个情况是会我们程序进行链接的时候出现找不到这个地址的现象,因为我们的模板函数是不知我们要实例化的类型是什么,所以就会出现最后链接的时候Call(没地址),所以就会链接错误,后面会深入的讲解。

继续回归我们对类模板的认识,首先是引出问题,我们没有类模板的栈是怎么写的。来看看吧。

class Stack {
public:Stack(int capacity = 4) : _top(0) , _capacity(capacity) {_arr = new int[capacity];}~Stack() {delete[] _arr;_arr = nullptr;_capacity = _top = 0;}
private:int* _arr;int _top;int _capacity;
};

这个栈是只能来存int,有人就会说,如果我们对int进行typedef不就行了,如果我想要其他类型的时候就只需要改类型就行了,但是这样就有了第二个问题,那就是如果我们需要的是一个存放int的栈,一个存放的是node* 节点的栈,或者一个日期的时候,那问题就很大了,每当我们需要这个类型的时候就需要ctrl c + v然后改一下类型这个操作其实很简单,也很快,但是最终结果就是造成代码相同的还是很多,这样和我们之前的函数模板是一样的问题,所以就有了我们的类模板,那我们来改造一下上面的代码吧。

template<class T>
class Stack {
public:Stack(int capacity = 4) : _top(0) , _capacity(capacity) {_arr = new T[capacity];}~Stack() {delete[] _arr;_arr = nullptr;_capacity = _top = 0;}
private:T* _arr;int _top;int _capacity;
};int main(void)
{Stack<int> st1;   // 存储intStack<double> st2;   // 存储doublereturn 0;
}

这样就可以解决了我们要存放int和double或者其他类型的问题了。

但是我们发现,类模板他好像不支持自动推出类型,

 它不像函数模板,不指定它也可以根据传入的实参去推出对应的类型的函数以供调用

这就是为什么我们需要在类模板后面根生类型,这里大家就要记住的是类模板必须要显示实例化的方法写,它不能像函数模板一样去推演类型。

类模板实例化

模板实例化在类模板名字后跟 < >,然后将实例化的类型放在 < > 中即可。

注意事项:

① Stack 不是具体的类,是编译器根据被实例化的类型生成具体类的模具。

② Stack 是类名,Stack<int> 才是类型:

我们上面说过类模板不能在两个文件里声明和定义分离,但是没说不能在同一个文件了,但是在同一个文件里有些讲究,我们得来探究一下。

就继续拿我们栈来说话。

#include<iostream>using namespace std;
template<class T>
class Stack {
public:Stack(int capacity = 4): _top(0), _capacity(capacity) {_arr = new T[capacity];}// 这里我们让析构函数放在类外定义void Push(const T& x);~Stack();
private:T* _arr;int _top;int _capacity;
};/* 类外 */void Stack::Push(const T& x) {//::::
}

 如果我们是这样写的化就是会存在一些小的问题,编译器是不认识外面的这个T,那我们要改的话是需要在下面函数上加上模板的参数的,

 

template<class T>
class Stack {
public:Stack(T capacity = 4): _top(0), _capacity(capacity) {_arr = new T[capacity];}// 这里我们让析构函数放在类外定义void Push(const T& x);~Stack();
private:T* _arr;int _top;int _capacity;
};/* 类外 */
template<class T>
void Stack<T>::Push(const T& x) {//::::
}

虽然是能编译通过,但是链接的时候又是会存在问题的,所以我的建议就是大家声明和定义都放在类模板里,多一事不如少一事。

对于这个需要记住的是----------> Stack 是类名,不是类型,Stack<T> 才是类型! 

初阶模板就分享到这里了,我们后面还有进阶的模板,今天的分享就到这里了,下次再见了~

 

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

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

相关文章

基于springboot+vue的中小企业设备管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

H 桥逆变方式介绍(双极性)

单极性控制和双极性控制是说IGBT四个管子的控制 前面所说的单极性控制是其中一个管子开通、关闭另外一个管子持续开通 而双极性是四个管子中的两个管子同时导通&#xff0c;同时关断。彼此交替变化 所以当方波出现低电平时&#xff0c;是一对管子同时导通&#xff0c;出现高电…

2.21 Qt day2 菜单栏/工具栏/状态栏/浮动窗口、UI界面、信号与槽

思维导图 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;…

golang实现延迟队列(delay queue)

golang实现延迟队列 1 延迟队列&#xff1a;邮件提醒、订单自动取消 延迟队列&#xff1a;处理需要在未来某个特定时间执行的任务。这些任务被添加到队列中&#xff0c;并且指定了一个执行时间&#xff0c;只有达到指定的时间点时才能从队列中取出并执行。 应用场景&#xff1…

智慧驿站_智慧文旅驿站_轻松的驿站智慧公厕_5G智慧公厕驿站_5G模块化智慧公厕

多功能城市智慧驿站是在智慧城市建设背景下&#xff0c;所涌现的一种创新型社会配套设施。其中&#xff0c;智慧公厕作为城市智慧驿站的重要功能基础&#xff0c;具备社会配套不可缺少的特点&#xff0c;所以在应用场景上&#xff0c;拥有广泛的需求和要求。那么&#xff0c;城…

#12解决request中getReader()和getInputStream()只能调用一次的问题

目录 1、背景 2、解决方案 2.1、自定义HttpServletRequestWrapper 2.2、JsonRequestHeaderParamsHelper 2.3、HttpServletRequestReplacedFilter 2.4、使用 1、背景 当前系统Content-Type为application/json&#xff0c;参数接收方式采用RequestBody和RequestParam&#…

平时积累的FPGA知识点(10)

平时在FPGA群聊等积累的FPGA知识点&#xff0c;第10期&#xff1a; 41 ZYNQ系列芯片的PL中使用PS端送过来的时钟&#xff0c;这些时钟名字是自动生成的吗&#xff1f; 解释&#xff1a;是的。PS端设置的是ps_clk&#xff0c;用report_clocks查出来的时钟名变成了clk_fpga_0&a…

Linux篇:进程

一. 前置知识 1.1冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系 为什么计算机要采用冯诺依曼体系呢&#xff1f; 在计算机出现之前有很多人都提出过计算机体系结构&#xff0c;但最…

时序数据库TimescaleDB,实战部署全攻略

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

C++ Primer 笔记(总结,摘要,概括)——第5章 语句

目录 5.1 简单语句 5.2 语句作用域 5.3 条件语句 5.3.1 if语句 5.3.2 switch语句 5.4 迭代语句 5.4.1 while语句 5.4.2 传统的for语句 5.4.3 范围for语句 5.4.4 do while语句 5.5 跳转语句 5.5.1 break语句 5.5.2 continue语句 5.5.3 goto语句 5.6 try语句块和异常处理 5…

2024华北医院信息网络大会第二轮更新通知

大会背景 近年来&#xff0c;我国医疗行业信息化取得了飞跃式的发展&#xff0c;医疗信息化对医疗行业有着重要的支撑作用。2021年国家卫健委、中医药管理局联合印发《公立医院高质量发展促进行动&#xff08;2021-2025年&#xff09;》&#xff0c;提出重点建设“三位一体”智…

【青龙】快速搭建青龙面板,部署属于你自己的应用!

青龙面板是一个支持 Python3、JavaScript、Shell、Typescript 的定时任务管理平台。 废话不多说&#xff0c;直接开始。 这里使用一台 雨云 的云服务器作为演示。雨云注册地址&#xff1a;https://www.rainyun.com/ 优惠码&#xff1a;lz932 使用优惠码注册后绑定微信可获得8折…

【Chrono Engine学习总结】4-vehicle-4.3-两个vehicle碰撞测试

由于Chrono的官方教程在一些细节方面解释的并不清楚&#xff0c;自己做了一些尝试&#xff0c;做学习总结。 今天突发奇想&#xff0c;想试一下&#xff0c;是否可以实现两个vehicle的碰撞&#xff1f; 1、两辆vehicle的仿真 官方提供了demo_VEH_TwoCars这个demo&#xff0c…

C++入门04 函数的参数传递、引用类型与重载

图源&#xff1a;文心一言 听课笔记简单整理&#xff0c;供小伙伴们参考&#xff0c;包含以下内容“&#x1f40b;3.11 引用类型、&#x1f40b;3.14 内联函数、&#x1f40b;3.15 默认参数值、&#x1f40b;3.16 函数重载、&#x1f40b;3.17 C系统函数”~&#x1f95d;&…

LabVIEW多通道压力传感器实时动态检测

LabVIEW多通道压力传感器实时动态检测 介绍了一种基于LabVIEW的多通道压力传感器实时动态检测系统&#xff0c;解决压阻式压力传感器温度补偿过程的复杂度&#xff0c;提高测量的准确性。通过自动轮询检测方法&#xff0c;结合硬件检测模型和多通道检测系统设计&#xff0c;本…

集合框架之List集合

目录 ​编辑 一、什么是UML 二、集合框架 三、List集合 1.特点 2.遍历方式 3.删除 4.优化 四、迭代器原理 五、泛型 六、装拆箱 七、ArrayList、LinkedList和Vector的区别 ArrayList和Vector的区别 LinkedList和Vector的区别 一、什么是UML UML&#xff08;Unif…

基于ORB-SLAM2与YOLOv8剔除动态特征点(三种方法)

基于ORB-SLAM2与YOLOv8剔除动态特征点(三种方法) 写上篇文章时测试过程比较乱&#xff0c;写的时候有些地方有点失误&#xff0c;所以重新写了这篇 本文内容均在RGB-D环境下进行程序测试 本文涉及到的动态特征点剔除速度均是以https://cvg.cit.tum.de/data/datasets/rgbd-dat…

编写程序,实现shell功能——项目训练——day08

c c今天做了一个实战项目训练&#xff0c;编写一个程序&#xff0c;实现shell功能&#xff0c;我们称之为minishell。 主要是利用Linux中IO接口实现&#xff0c;实现的功能有&#xff1a; 1.ls ls -a ls -l cd cp mv pwd c…

软件License授权原理

软件License授权原理 你知道License是如何防止别人破解的吗&#xff1f;本文将介绍License的生成原理&#xff0c;理解了License的授权原理你不但可以防止别人破解你的License&#xff0c;你甚至可以研究别人的License找到它们的漏洞。喜欢本文的朋友建议收藏关注&#xff0c;…

【Linux】进程状态

进程状态 进程状态的简要介绍运行状态进程排队 阻塞状态挂起状态Linux中的进程状态 进程状态的简要介绍 进程状态指的是一个操作系统中正在运行的进程当前所处的状态。根据不同的操作系统&#xff0c;进程状态可能会有一些细微的差别&#xff0c;但最主要的是以下三种状态 运行…