CPP初级:模板的运用!

news2024/12/23 10:45:35

目录

一.泛型编程

二.函数模板

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.coloradmin.cn/o/1808032.html

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

相关文章

Xcode 安装17.5 simulator 总是失败

升级到xcode15.4后需要安装ios17.5模拟器 但是在下载过程中会遇到报错 : The network connection is lost 解决方案&#xff1a; 先将模拟器下载到本地 Xcode 安装17.5 simulator 下载地址&#xff1a; Sign In - Applhttps://developer.apple.com/download/all/?qXcode 下…

【系统架构】架构演进

系列文章目录 第一章 系统架构的演进 本篇文章目录 系列文章目录前言一、原始分布式二、单体系统时代三、SOA时代烟囱架构微内核架构事件驱动架构 四、微服务架构五、后微服务时代六、无服务时代总结 前言 最近笔者一直在学习系统架构的相关知识&#xff0c;对系统架构的演进…

web前端:作业二

<!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>/* 1.将ul的子l…

《web应用技术》第11次课后作业

课后练习&#xff1a; 1、验证过滤器进行权限验证的原理。 2、将自己之前的项目&#xff0c;加上过滤器验证功能。参考以下文章&#xff1a; 采用JWT令牌和Filter进行登录拦截认证-CSDN博客 3、Apifox的使用 了解Apifox的工具特点和使用方法&#xff0c;使用Apifox辅助生成…

【机器学习】决策树模型(个人笔记)

文章目录 多样性指标基尼杂质指数&#xff08;Gini Impurity Index&#xff09;熵&#xff08;Entropy&#xff09; 决策树的应用 源代码文件请点击此处&#xff01; 多样性指标 基尼杂质指数&#xff08;Gini Impurity Index&#xff09; 若集合中包含 m m m 个元素和 n …

C++类与对象(拷贝与类的内存管理)

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 文章目录 前言一.对象的动态建立和释放二.多个对象的构造和析构三.深拷贝与浅拷贝四.C类的内存管理总结 前言 …

数据仓库核心:事实表深度解析与设计指南

文章目录 1. 引言1.1基本概念1.2 事实表定义 2. 设计原则2.1 原则一&#xff1a;全面覆盖业务相关事实2.2 原则二&#xff1a;精选与业务过程紧密相关的事实2.3 原则三&#xff1a;拆分不可加事实为可加度量2.4 原则四&#xff1a;明确声明事实表的粒度2.5 原则五&#xff1a;避…

如何有效防御.360勒索病毒:.360勒索病毒加密文件预防方法探讨

导言&#xff1a; 随着信息技术的飞速发展&#xff0c;网络安全问题也日益凸显。其中&#xff0c;勒索病毒作为一种新型的网络安全威胁&#xff0c;给用户和企业带来了极大的困扰和损失。特别是.360勒索病毒&#xff0c;以其独特的加密方式和恶劣的勒索手段&#xff0c;引起了…

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

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

Docker高级篇之Docker-compose容器编排

文章目录 1. Docker-compse介绍2. Docker-compse下载3. Docker-compse核心概念4. Docker-compse使用案例 1. Docker-compse介绍 Docker-compose时Docker官方的一个开源的项目&#xff0c;负责对Docker容器集群的快速编排。Docker-compose可以管理多个Docker容器组成一个应用&a…

【单片机毕业设计9-基于stm32c8t6的酒窖监测系统】

【单片机毕业设计9-基于stm32c8t6的酒窖监测系统】 前言一、功能介绍二、硬件部分三、软件部分总结 前言 &#x1f525;这里是小殷学长&#xff0c;单片机毕业设计篇9基于stm32的酒窖监测系统 &#x1f9ff;创作不易&#xff0c;拒绝白嫖可私 一、功能介绍 -------------------…

Docker高级篇之轻量化可视化工具Portainer

文章目录 1. 简介2. Portainer安装 1. 简介 Portianer是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便管理Docker环境&#xff0c;包括单机环境和集成环境。 2. Portainer安装 官网&#xff1a;https://www.portainer.io 这里我们使用docker命令安装&…

8.让画面动起来

一、Unity Shader中的内置变量&#xff08;时间篇&#xff09; 动画效果往往都是把时间添加到一些变量的计算中&#xff0c;以便在时间变化的同时也可以随之变化。Unity shader提供了一系列关于时间的内置变量来允许我们方便地在Shader中访问运行时间&#xff0c;实现各种动画…

STM32 | 独立看门狗 | RTC(实时时钟)

01、独立看门狗概述 在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状…

Elasticsearch:Open Crawler 发布技术预览版

作者&#xff1a;来自 Elastic Navarone Feekery 多年来&#xff0c;Elastic 已经经历了几次 Crawler 迭代。最初是 Swiftype 的 Site Search&#xff0c;后来发展成为 App Search Crawler&#xff0c;最近又发展成为 Elastic Crawler。这些 Crawler 功能丰富&#xff0c;允许以…

基于Java+SpringBoot制作一个景区导览小程序

基于Java+SpringBoot制作一个景区导览小程序。其中系统前端功能包括注册登录、景区采风、旅游导览、地图导航、发布采风、门票预订、修改个人信息;系统后台功能包括用户管理、景区管理、采风管理等模块。 摘要一、小程序1. 创建小程序2. 首页3. 景区采风页4. 旅游导览页5. 发布…

人工智能_机器学习097_PCA数据降维算法_数据去中心化_特征值_特征向量_EVD特征值分解原来和代码实现---人工智能工作笔记0222

降维算法的原理,一会咱们再看,现在先看一下,算法 可以看到PCA算法的,原理和过程,我们先看一下代码 为了说明PCA原理,这里,我们,先来计算一下X的方差,可以看到 先把数据进行去中心化,也就是用数据,减去数据的平均值. B = X-X.mean(axis=0) 这段代码是用于计算矩阵X的每一列减去该…

【Web世界探险家】3. CSS美学(二)文本样式

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

⌈ 传知代码 ⌋ 基于曲率的图重新布线

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

SQL159 每个创作者每月的涨粉率及截止当前的总粉丝量

描述 用户-视频互动表tb_user_video_log iduidvideo_idstart_timeend_timeif_followif_likeif_retweetcomment_id110120012021-09-01 10:00:002021-09-01 10:00:20011NULL210520022021-09-10 11:00:002021-09-10 11:00:30101NULL310120012021-10-01 10:00:002021-10-01 10:00…