CPP初级:模板的运用!

目录

一.泛型编程

二.函数模板

1.函数模板概念

2.函数模板格式

3.函数模板的原理

三.函数模板的实例化

1.隐式实例化

2.显式实例化

3.模板参数的匹配原则

四.类模板

1.类模板的定义格式

2.类模板的实例化


一.泛型编程

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。

泛型编程是一种编程范式,它允许程序员编写不依赖于特定数据类型的代码。

在泛型编程中,程序员可以定义一些通用的算法和数据结构,这些可以在不同的数据类型中使用。

比如交换函数,如果我们没有学习泛型编程,则我们就需要根据类型的交换,造出多个轮子:

typedef int Type;
void Swap(Type& left, Type& right)
{Type temp = left;left = right;right = temp;}void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}void Swap(double& left, double& right)
{double temp = left;left = right;right = temp;
}

使用函数重载固然可以实现这一问题,但是有几个不好的地方:

  •  代码空间会变大
  • 重载的函数只是类型不同,代码的复用率较低
  • 只要有新的类型需要使用这个函数,就需要重载新的函数
  • 代码的可维护性较低,一个出错可能全部的重载都出错

因为函数重载存在上述缺点,因此我们提出了”函数模板“

在现实生活中,我们可以通过往模具中填充不同的材料生成不同的铸件。

C++的开发者受到了启发,发明了模板。

模板:告诉编译器一个模子,让编译器根据不同的类型利用该模子生成代码。

模板可以分为函数模板和类模板:

模板是泛型编程的基础。

二.函数模板

1.函数模板概念

函数模板代表了一个函数家族,该家族模板与类型无关。

函数模板在使用时被参数化,根据实参类型产生的特定类型版本。

2.函数模板格式

我们用templata关键字来声明模板:

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

这里需要大家注意的是,我们的第一行后面并没有分号,也就代表着它并不是一条语句。

 现在我们举出一个实例:

template <typename T>//函数模板的声明
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}

此外,我们也可以使用class取代typename

template <class T>//函数模板的声明
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}

 现在我们使用一下我们定义的函数模板

#include <iostream>
using namespace std;
template <class T>//函数模板的声明
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}
int main()
{int a = 1, b = 2;Swap(a, b);cout << a << ' ' << b << endl;double c = 1.3, d = 2.5;Swap(c, d);cout << c << ' ' <<d << endl;return 0;
}

可以看到,这里圆满的完成了交换逻辑。 

3.函数模板的原理

那么,上述问题是如何解决的呢?

大家都知道,瓦特改良蒸汽机,人类开始了工业革命,解放了生产力。

机器生产淘汰掉了很多手工产品。

本质是什么,重复的工作交给了机器去完成。

因此有人给出了论调:懒人创造世界。

函数模板的本质也是如此

现在我们进入汇编来看一下上述代码运行的过程中编译器都干了什么事

 

可以看到,这里调用函数时,显式的规定了参数的类型。 

因此我们可以得到结论:函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。

我们的编译器根据这个模具帮我们做了这个事情。

注意:Swap在调用时,调用的不是void Swap(T& left, T& right),而是编译器预先根据要调用的类型进行推演。

编译器负责在编译时分析模板定义,并在需要时生成特定类型的代码,之后编译器会检查模板的语法,并确保模板的使用是合法的,之后编译器会根据实际使用的类型参数生成相应的函数或类的实现。

例如上图中的这两行代码:

00007FF6E7122423  call        Swap<int> (07FF6E7121352h)
00007FF6E7122480  call        Swap<double> (07FF6E7121398h) 

这两个函数模板就是编译器生成的。

在编译器的编译阶段,编译器就会根据传入的实参类型来推演生成对应类型的函数以供调用。

就比如上图: 当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码。

三.函数模板的实例化

用不同类型的参数使用函数模板称为函数模板的实例化

模板参数的实例化可以分为:隐式实例化和显式实例化

1.隐式实例化

隐式实例化即我们刚刚实例化的方法,这里不再过多赘述。

#include <iostream>
using namespace std;
template <class T>//函数模板的声明
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;Add(a1, a2);cout << Add(a1,a2) << endl;return 0;
}

 

 现在我们来看一下这段代码:

T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;Add(a1, d2);cout << Add(a1, d2) << endl;return 0;
}

这段代码在大部分编译器下是无法运行的,在VS2022中爆出了如下警告:

 

 为什么在大部分编译器下无法通过编译呢?

这是因为在编译期间,当编译器看到该实例化后,会去推演其实参的类型。

通过实参a1将T推演为了int通过实参d1将T推演为double类型

但是模板参数列表中只有一个T,编译器就无法判断T在这里是int还是double。

为什么在vs2022中可以编译成功呢?

这是因为编译器进行了类型转换操作,但是类型转换操作的风险是极大的,因为不知道此处你想要的是double还是int。

那么我们应该处理这个问题呢?

处理方式1:用户自己强制转换

Add(a1, (int)d2);//想要int类型的,我们直接将d2强转为int

 

处理方式2:采用显式实例化

那么,如何显式实例化呢?

2.显式实例化

显式实例化:在函数名的后面,参数列表的前面加一对尖括号<>,尖括号内部指定模板参数的实际类型。如下:

Add<int>(a, b);

 

如果参数类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

3.模板参数的匹配原则

  • 在我们的程序中,一个非模板函数是可以和一个同名的函数模板同时存在的,而且该函数模板还可以被实例化为这个非模板函数
T Add(const T& left, const T& right)
{cout << "T Add(T& left,T& right)" << endl;return left + right;
}
int Add(const int& left, const int& right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}
int main()
{Add(1, 2);Add<int>(1, 2);return 0;
}

我们运行之后,可以看到如下的结果: 

 

我们发现,第一个Add函数调用了专门处理int类型的加法函数,而第二个Add函数调用了模板。

那么,为什么第一个Add函数不调用函数模板呢?

这是因为如下内容:

  • 对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从该模板产生出一个实例。 
  • 如果条件不同的话,则会选择模板。
  • 也就是说,编译器会优先调用更加匹配的版本调用!

我们可以看一下下面的这段代码:

int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}
// 通用加法函数
template<class T1, class T2>//注意这里有两个类型
T1 Add(T1 left, T2 right)
{cout << "T1 Add(T1 left, T2 right)" << endl;return left + right;
}
int main()
{Add(1, 2);// 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0);// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数return 0;
}

 

可以看到,第一个调用了非模板函数,第二个调用了模板函数。

 

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

对于模版T1 Add(T1 left, T2 right)不知道返回值是T1或T2,可以选择auto,auto虽然不太适合做返回值,但是对于简单普通函数操作,可以进行自动类型转换。

int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}//auto可作简单处理的函数返回值
template<class T1, class T2>
auto Add(const T1& left, const T2& right)
{cout << "auto Add(const T1& left, const T2& right)" << endl;return left + right;
}
int main()
{Add(1, 2);// 与非函数模板类型完全匹配,不需要函数模板实例化cout << Add(1, 2) << endl;Add(1, 2.0);// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数cout << Add(1, 2.0) << endl;return 0;
}

四.类模板

1.类模板的定义格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};
// 动态顺序表
// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具
template<class T>
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>
Vector<T>::~Vector()
{if (_pData)delete[] _pData;_size = _capacity = 0;
}

模版Vector中只是提供了一个模具,具体印刷出什么模型,是由编译器最终实例化决定的。

注意:模版不建议声明和定义分离到.h 和.cpp,会出现链接错误,要分离也分离在.h。

2.类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字和变量名字中间加一个尖括号<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

Vector<int> intvector;
Vector<string> stringvector;

码字不易,如果你觉得博主写的不错的话,可以关注一下博主哦。

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

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

相关文章

vs2019 c++20规范 全局函数 ref 及模板类 reference_wrapper<_Ty> 的源码分析

这是个引用&#xff0c;可以包裹一个对象&#xff0c;相当于引用该对象&#xff0c;而不是在作为函数形参时产生值传递。因为模板 reference_wrapper<_Ty> 其实是封装了该对象的地址。下面以图示形式给出其重要的成员函数。模板其实都差不多&#xff0c;跟人也一样&#…

Linux | buildrootfs 添加mkfs.ext3/mkfs.ext4 支持

因个人需要&#xff0c;mkfs.ext3 但是项目中还没有这个命令 所以琢磨了半天 这里将其小记一下 在buildrootfsz中&#xff0c;需要将e2fsprogs 勾选上然后重新编译就好了 make menuconfig Target packages-> Filesystem and flash utilities-> e2fsprogs

JVMの静、动态绑定异常捕获JIT即时编译

在说明静态绑定和动态绑定之前&#xff0c;我们首先要了解在字节码指令的层面&#xff0c;JVM是如何调用方法的&#xff1a; 例如我有以下的代码&#xff0c;很简单就是在main方法中调用了另一个静态方法&#xff1a; public class MethodTest {public static void main(Strin…

论文阅读——MIRNet

项目地址&#xff1a; GitHub - swz30/MIRNet: [ECCV 2020] Learning Enriched Features for Real Image Restoration and Enhancement. SOTA results for image denoising, super-resolution, and image enhancement.GitHub - soumik12345/MIRNet: Tensorflow implementation…

数据库(29)——子查询

概念 SQL语句中嵌套SELECT语句&#xff0c;称为嵌套查询&#xff0c;又称子查询。 SELECT * FROM t1 WHERE column1 (SELECT column1 FROM t2); 子查询外部语句可以是INSERT/UPDATE/DELETE/SELECT的任何一个。 标量子查询 子查询返回的结果是单个值&#xff08;数字&#xff…

电子设计入门教程硬件篇之集成电路IC(二)

前言&#xff1a;本文为手把手教学的电子设计入门教程硬件类的博客&#xff0c;该博客侧重针对电子设计中的硬件电路进行介绍。本篇博客将根据电子设计实战中的情况去详细讲解集成电路IC&#xff0c;这些集成电路IC包括&#xff1a;逻辑门芯片、运算放大器与电子零件。电子设计…

31、matlab卷积运算:卷积运算、二维卷积、N维卷积

1、conv 卷积和多项式乘法 语法 语法1&#xff1a;w conv(u,v) 返回向量 u 和 v 的卷积。 语法2&#xff1a;w conv(u,v,shape) 返回如 shape 指定的卷积的分段。 参数 u,v — 输入向量 shape — 卷积的分段 full (默认) | same | valid full&#xff1a;全卷积 ‘same…

UnityXR Interaction Toolkit 如何使用XRHand手部识别

前言 Unity的XR Interaction Toolkit是一个强大的框架,允许开发者快速构建沉浸式的VR和AR体验。随着虚拟现实技术的发展,手部追踪成为了提升用户交互体验的关键技术之一。 本文将介绍如何在Unity中使用XR Interaction Toolkit实现手部识别功能。 准备工作 在开始之前,请…

统信UOS1070上配置文件管理器默认属性01

原文链接&#xff1a;统信UOS 1070上配置文件管理器默认属性01 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在统信UOS 1070上配置文件管理器默认属性的文章。文件管理器是我们日常操作系统使用中非常重要的工具&#xff0c;了解如何配置其默认属性可以极大地…

apache poi 插入“下一页分节符”并设置下一节纸张横向的一种方法

一、需求描述 我们知道&#xff0c;有时在word中需要同时存在不同的节&#xff0c;部分页面需要竖向、部分页面需要横向。本文就是用java调用apache poi来实现用代码生成上述效果。下图是本文实现的效果&#xff0c;供各位看官查阅&#xff0c;本文以一篇课文为例&#xff0c;…

Linux系统推出VB6开发IDE了?Gambas,Linux脚本编写

第一个Linux程序&#xff0c;加法计算加弹窗对话框,Gambas,linux版的类似VB6的IDE开发环境 一开始想用VB6的Clng函数转成整数&#xff0c;没这函数。 输入3个字母才有智能提示&#xff0c;这点没做好 没有msgbox函数&#xff0c;要用messagebox.warning 如果可以添加函数别名就…

[书生·浦语大模型实战营]——第六节 Lagent AgentLego 智能体应用搭建

1. 概述和前期准备 1.1 Lagent是什么 Lagent 是一个轻量级开源智能体框架&#xff0c;旨在让用户可以高效地构建基于大语言模型的智能体。同时它也提供了一些典型工具以增强大语言模型的能力。 Lagent 目前已经支持了包括 AutoGPT、ReAct 等在内的多个经典智能体范式&#x…

通过双模式对抗提示越狱视觉语言模型

最近&#xff0c;将视觉整合到大型语言模型&#xff08;LLMs&#xff09;中的兴趣显著增加&#xff0c;催生了大型视觉语言模型&#xff08;LVLMs&#xff09;。这些模型结合了视觉和文本信息&#xff0c;如LLaVA和Gemini&#xff0c;已经在包括图像字幕、视觉问题回答和图像检…

论文阅读:All-In-One Image Restoration for Unknown Corruption

发表时间&#xff1a;2022 cvpr 论文地址&#xff1a;https://openaccess.thecvf.com/content/CVPR2022/papers/Li_All-in-One_Image_Restoration_for_Unknown_Corruption_CVPR_2022_paper.pdf 项目地址&#xff1a;https://github.com/XLearning-SCU/2022-CVPR-AirNet 代码解读…

c++中, 直接写浮点数, 是float 还是 double?

如果直接一个浮点数, 那么他默认是float还是double呢? 测试用例 #include <iostream> using namespace std;int main() {auto x 0.2;float f 0.2;double d 0.2;cout << "x Size : " << sizeof(x) << " bytes" << endl…

vue28:组件化开发和根组件

简单写个点击事件 <template> <div class"app"><div class"box" click"fn"></div></div> </template><script> export default {//导出当前组件的配置项//里面可以提供 data methods computed wat…

AtCoder Beginner Contest 356 G. Freestyle(凸包+二分)

题目 思路来源 quality代码 题解 对n个泳姿点(ai,bi)建凸包&#xff0c;实际上是一个上凸壳&#xff0c; 对于询问(ci,di)来说&#xff0c;抽象画一下这个图&#xff0c;箭头方向表示询问向量 按x轴排增序&#xff0c;并且使得后面的y不小于前面的y&#xff0c;因为总可以多…

C++ Easyx案例实战:Cookie Maker工作室1.0V

前言 //制作属于自己的工作室&#xff01; 注&#xff1a;运行效果以及下载见Cookie Maker 工作室成立程序。 关于Cookie Maker工作室成立的信息&#xff0c;I am very happy&#xff08;唔……改不过来了&#xff09;。 OKOK&#xff0c;第一次用图形库写程序&#xff08;图形…

在开源处理器架构RISC-V中发现可远程利用的中危漏洞

在RISC-V SonicBOOM处理器设计中发现中度危险的漏洞 最近&#xff0c;西北工业大学的网络空间安全学院胡伟教授团队在RISC-V SonicBOOM处理器设计中发现了一个中度危险的漏洞。这个团队的研究人员发现了一个可远程利用的漏洞&#xff0c;该漏洞存在于开源处理器架构RISC-V中。…

单灯双控开关原理

什么是单灯双控&#xff1f;顾名思义&#xff0c;指的是一个灯具可以通过两个不同的开关或控制器进行控制。 例如客厅的主灯可能会设置成单灯双控&#xff0c;一个开关位于门口&#xff0c;另一个位于房间内的另一侧&#xff0c;这样无论你是从门口进入还是从房间内出来&#x…