【C++】泛型编程——模板初阶
文章目录
- 1. 泛型编程
- 2. 函数模板
- 2.1 函数模板的概念
- 2.2 函数模板的使用
- 2.3 函数模板的原理
- 2.4 函数模板的实例化
- 隐式实例化
- 显式实例化
- 2.5 模板参数的匹配原则
- 3. 类模板
1. 泛型编程
首先我们来思考一个问题:如何实现一个通用的交换函数呢?
即我们想交换两个变量,这两个变量可以是整型,也可以是浮点型,或者其它内置类型,然后它们的交换都可以用一个函数完成。
那在C语言中肯定是没法解决这个问题的,不过我们之前学习过在C++里支持函数重载,所以呢,我们就可以这样搞:
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;
}
void Swap(char& left, char& right)
{char temp = left;left = right;right = temp;
}
...
这几个函数的函数名相同,只是参数列表不同,构成重载,这样我们想交换不同类型的变量,都是去调用
Swap
函数,然后根据参数类型的不同,会自动匹配去调用对应的交换函数。
这与C语言相比,确实有了一点进步。
但是呢,还是有一些不好的地方:
使用函数重载虽然可以实现,但是有一下几个不好的地方:
- 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
- 代码的可维护性比较低,一个出错可能所有的重载均出错
这些重载的函数呢,干的事情都是一样的,只是处理的数据的类型不同。
那我们想:
能否告诉编译器一个模子(模板),让编译器根据不同的类型利用该模子来生成不同的代码呢?
就类似于这样。
那如果在C++中,也能够存在这样一个模具就好了:
通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码),那将会节省许多头发。
巧的是前人早已将此树栽好,我们只需在此乘凉:
C++引入了泛型编程,就可以解决这个问题。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。
模板是泛型编程的基础,又分为函数模板和类模板。
借助模板,我们就可以解决上面的问题。
2. 函数模板
那我们先来学习一下函数模板。
2.1 函数模板的概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生特定类型版本的函数。
2.2 函数模板的使用
函数模板格式:
template<class T1, class T2,...,class Tn>
返回值类型 函数名(参数列表)
{
}
注意:class是用来定义模板参数的关键字,也可以使用typename(切记:不能使用struct代替class)
举个栗子,上面的Swap函数,有了模板,我们就可以这样搞:
template<class T>
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}
这里的T是我们定义的模板的类型名称,是自己起的,我们调用Swap时,传的参数是什么类型,T就会被替换成对应的类型,然后Swap函数就对该类型的参数进行相应的处理。
那现在我们交换不同类型的变量,还需要一种类型写一个嘛,不需要了,用这一个就够了:
是不是就搞定了啊。
那现在问大家一个问题:
我们上面的
Swap(a, b)
和Swap(c, d)
调用的是同一个函数吗?
我们调式去看的话会发现它们都进到Swap里面了。
但是:
我们刚才写的是个啥,是一个具体的函数吗?
是不是一个函数模板啊,并不是一个函数。
如果我们去观察汇编的话会发现它们两个去call的函数是不一样的,并不是一个。
其实大家想一下,函数要建立栈帧,它们的参数类型都不一样,那建立的栈帧都不一样大,怎么可能是同一个嘛。
2.3 函数模板的原理
那这样的话,大家再思考一下:
函数模板的原理是什么呢?
大家都知道,瓦特改良蒸汽机,人类开始了工业革命,解放了生产力。机器生产淘汰掉了很多手工产品。
本质是什么,重复的工作交给了机器去完成。
有人给出了论调:懒人创造世界。
🆗,那函数模板的原理呢其实也是这样:
函数模板是一个蓝图,它本身并不是函数,是编译器用来产生特定具体类型函数的模具。
所以其实模板就是将本来应该我们做的重复的事情交给了编译器去做。
那具体是怎么做的呢?
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于其它类型也是如此。
另外再给大家提一个东西就是:
其实
swap
这个函数C++库里面是提供了的,我们可以直接用:
不过库里面的是小写,我们自己刚才的写成大写区分一下,所以以后我们再用swap就不用自己写了。
当然这里我们自己写是拿它来给大家举例子帮助我们理解知识的。
2.4 函数模板的实例化
用不同类型的参数使用函数模板时,函数模板生成对应类型参数的具体函数,称为函数模板的实例化。
模板参数实例化分为:隐式实例化和显式实例化。
隐式实例化
让编译器根据实参推演模板参数的实际类型
我们来看这样一段代码:
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 10.0, d2 = 20.0;Add(a1, a2);Add(d1, d2);return 0;
}
我们提供了一个加法函数的模板,然后在main函数里分别加了两个整型和浮点型。
目前是没什么问题的。
那如果这样呢?
这样就不行了,为什么呢?
因为这时候函数模板在推演实例化的时候会出现歧义:
该语句不能通过编译,因为在编译期间,该函数模板实例化时,需要推演其实参类型。这时通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错。
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
那面对这种情况,有两种解决方式:
首先第一种方法就是我们自己去进行强制类型转换。
这样就没问题了。
那另一种方法呢?
显式实例化
在函数名后的<>中指定模板参数的实际类型
这样也可以解决。
这种情况如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功,编译器将会报错。
2.5 模板参数的匹配原则
来看这两个函数可以同时存在吗?
// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{return left + right;
}
🆗,是可以的, 一个非模板函数可以和一个同名的函数模板同时存在
然后再看:
这里会调用哪一个?
我们通过调式可以看到它调的是第一个。
为什么会调第一个,因为编译器在这个地方也会看调哪一个成本会更低一点,第一个呢可以直接调,但第二个的话是不是还要用模板实例化之后才能调啊。
所以在这里编译器选择了第一个。
那如果我们就想调函数模板生成的那个呢?可以做到吗?
当然可以,我们只要显示实例化就行了:
所以呢:
一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
另外:
对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。
但如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
举个栗子,在刚才的基础上,我们再增加一个模板函数:
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}
首先Add(a, b)
默认调用非模板函数,这个我们上面刚说过,那大家思考一下Add(1, 2.0)
会调用哪个?
我们看到这里调用了两个参数的模板函数生成的更加匹配的Add函数。
首先大家要知道这里其实第一个非模板函数也是可以调的,普通函数是可以进行自动类型转换的,而模板函数是不会自动类型转换的。像我们刚才上面就是强制类型转换的。
但是当前这种情况要调非模板函数毕竟还得进行一个类型转换,而我们得第二个函数模板有两个参数T1和T2
,那调用的时候模板是不是可以产生一个具有更好匹配的函数。
Add(1, 2.0)
,T1自动推演为int,T2自动推演为double。
所以这里就选择调用模板生成的函数了。
那除了函数模板之外呢,还有类模板。
3. 类模板
那学习了上面的内容,相信类模板大家就能很容易理解了。
举个栗子:
如果没有类模板的话,在C++里我们想写一个栈类一般是这样的:
typedef int DataType;
class Stack
{
public://构造函数Stack(size_t capacity = 4){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...
private:DataType* _array;int _capacity;int _size;
};
一般我们会
typedef
一下,这样如果我们想改变栈里存储数据的类型,就比较方便了。
但是:
如果我们在main函数里定义了2个或者多个栈,想让它们分别存储不同类型的数据,能不能做到呢?
显然是没法做到的,现在是int
,那它们两个里面就都只能存int,如果我们改成double,那就都只能存double。
如果想做到,那就只能定义两个栈的类,一个int的,一个double的。但是这样它们除了数据类型不一样,其它是不是都一样啊。
🆗,那这种没有什么技术含量的事情我们就可以交给编译器帮我们做。
怎么搞呢?用类模板就行了:
template<class T>
class Stack
{
public:Stack(int capaicty = 4){_a = new T[capaicty];_top = 0;_capacity = capaicty;}~Stack(){delete[] _a;_capacity = _top = 0;}private:T* _a;size_t _top;size_t _capacity;
};
要注意的是:
类模板实例化与函数模板实例化有些不同,类模板实例化只能显式实例化,即需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可。
类模板不是真正的类,其实例化的结果才是真正的类。
因为函数模板实例化可以根据参数类型去推演模板参数的类型,但是我们拿一个类去创建对象,就比如当前的栈,不会直接传数据类型是什么,所以要显式实例化:
Stack是类名,Stack才是类型
这样我们就可以让不同的栈对象里面存不同类型的数据了。
然后还需要注意的是:
如果类模板里的成员函数声明和定义分离的话:
正常我们是这样写的,但是在类模板里这样不行。
注意:类模板中成员函数放在类外进行定义时,需要加模板参数列表
这样就可以了。
其次:
我们定义一个类可能习惯头文件和源文件分开来,那普通类这样搞是没问题的,就像我们之前实现的日期类就是多文件管理的。
但是呢,类模板不行,类模板如果这样搞,会链接错误的,至于原因呢,我们后面到模板进阶的时候会讲,大家先了解一下。
🆗,那这篇文章就先到这里,欢迎大家指正!!!
相关文章:

【C++】泛型编程——模板初阶
文章目录1. 泛型编程2. 函数模板2.1 函数模板的概念2.2 函数模板的使用2.3 函数模板的原理2.4 函数模板的实例化隐式实例化显式实例化2.5 模板参数的匹配原则3. 类模板1. 泛型编程 首先我们来思考一个问题:如何实现一个通用的交换函数呢? 即我们想交换两…...

数据结构入门--时间 空间复杂度
数据结构入门 时间 空间复杂度解析 目录 一. 算法效率 二. 时间复杂度 2.1 时间复杂度的概念 2.2 大O的渐进表示法 2.3 题目练习 题目一 题目二 题目三 题目四 题目五 题目六 题目七 三. 空间复杂度 3.1 题目练习 题目一 题目二 题目三 一. 算法效率 算法效率…...

计算机操作系统第一章
操作系统引论1.1操作系统的目标和作用定义:操作系统是控制管理计算机系统的硬软件,分配调度资源的系统软件。目标:方便性,有效性(提高系统资源的利用率、提高系统的吞吐量),可扩充性,…...

ARM LDREX/STREX指令以及独占监控器详解
一、目的Linux驱动开发中有一个特别重要的知识点必须掌握,即并发、竞态以及同步。什么是并发?多个执行单元(进程、线程、中断)同时对一个共享资源的进行访问;此处的共享资源可以是外设、内存或者软件层面的全局变量静态…...
吉林大学 程序设计基础 2022级 实验复盘 2.23
本人能力有限,发出只为帮助有需要的人。 以下为实验课的复盘,内容会有大量失真,请多多包涵。 此次实验限时一个小时,时间很紧张,很多内容可能并不准确。 1.输出有规律的字母串 输入输出如下; 输入&…...

Linux系列 常用命令(目录和文件管理)vi和vim 编辑使用,(笔记)
作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页 目录 前言 一.常用命令(目录和文件管理) 1.查看文件内容 2.统计…...

OpenCV入门(一)Python环境的搭建
OpenCV入门(一)Python环境的搭建 因为有点Python基础,并且Python是比较好入门的编程语言,所以,机器视觉后面打算在Python这个平台下进行。 Windows平台OpenCV的Python开发环境搭建 1、Python 的下载与安装 Python是…...

3.查找算法:顺序查找和二分查找
查找查找,是指在一些数据元素中,通过一定的方法找出与给定关键字相同的数据元素的过程。列表查找(线性表查找):从列表中查找指定元素输入:列表,待查找元素输出:元素下标(…...

攻不下dfs不参加比赛(七)
标题 为什么练dfs题目总结重点为什么练dfs 相信学过数据结构的朋友都知道dfs(深度优先搜索)是里面相当重要的一种搜索算法,可能直接说大家感受不到有条件的大家可以去看看一些算法比赛。这些比赛中每一届或多或少都会牵扯到dfs,可能提到dfs大家都知道但是我们为了避免眼高手…...

精确光度预测计算工具:AGi32 Crack
什么是AGi32? AGi32首先是一种用于精确光度预测的计算工具:一种技术工具,可以计算任何情况下的照度,协助灯具放置和瞄准,并验证是否符合任意数量的照明标准。 然而,要增强对光度学结果的理解,还…...

47个SQL性能优化技巧,看到就是赚到
1、先了解MySQL的执行过程 了解了MySQL的执行过程,我们才知道如何进行sql优化。 (1)客户端发送一条查询语句到服务器; (2)服务器先查询缓存,如果命中缓存,则立即返回存储在缓存中的…...

汇川SV660N与基恩士 KV7500 控制器调试说明
1. 伺服相关部分配置 1.1 伺服相关版本 SV660N 试机建议使用“SV660N-Ecat_v0.09.xml”及以上设备描述文件。 SV660N 单板软件版本建议为“H0100901.4”及更高版本号。 1.2 相关参数说明 SV660N 对象字典中 60FD 的含义较 IS620N 有所更改:bit0、1、2 分别为负限位…...

图观 | ChatGTP是如何通过知识图谱回答问题的?
文/Emma Z1950年,图灵发表了具有里程碑意义的论文《计算机器与智能》(Computing Machinery and Intelligence),提出了一个关于机器人的著名判断原则——图灵测试,也被称为图灵判断,它指出如果第三者无法辨别…...

Mysql的索引
为什么写这篇文章呢~最近在梳理公司的数据库,在查看表结构的时候发现了这个 CREATE TABLE esp_5_N (ID int(11) NOT NULL AUTO_INCREMENT,pId int(11) DEFAULT NULL,EsFileId varchar(32) DEFAULT NULL,obligate1 varchar(45) DEFAULT NULL,obligate2 varchar(45) …...

计算机的发展
个人简介:云计算网络运维专业人员,了解运维知识,掌握TCP/IP协议,每天分享网络运维知识与技能。个人爱好: 编程,打篮球,计算机知识个人名言:海不辞水,故能成其大;山不辞石…...

理解Spring中的依赖注入和控制反转
依赖注入(Dependency Injection)是一种面向对象编程的设计模式,用于解决对象之间的依赖关系。它的基本思想是将对象的创建和管理工作交给容器来完成,而不是在应用程序中手动创建和管理对象,从而达到松耦合、易维护、易…...

XXL-JOB
XXL-JOB介绍 XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。 官网:https://www.xuxueli.com/xxl-job/ 文档:分布式任务调度…...
「牛客网C」初学者入门训练BC134,BC136
🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️🔥专栏系列:线性代数,C初学者入门训练 🔥座右铭:“不要等到什么都没有了,才下定决心去做” 🚀🚀🚀大家觉不错…...

华为OD机试题【翻转单词顺序】用 C++ 进行编码 (2023.Q1)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明翻转单…...

4.Spring【Java面试第三季】
4.Spring【Java面试第三季】前言推荐4.Spring27_Aop的题目说明要求Spring的AOP顺序AOP常用注解面试题28_spring4下的aop测试案例业务类新建一个切面类MyAspect并为切面类新增两个注解:spring4springboot1.5.9pom测试类29_spring4下的aop测试结果aop正常顺序异常顺序…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要
根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分: 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...

Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...

2.3 物理层设备
在这个视频中,我们要学习工作在物理层的两种网络设备,分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间,需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质,假设A节点要给…...
用鸿蒙HarmonyOS5实现国际象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码,使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...
基于Uniapp的HarmonyOS 5.0体育应用开发攻略
一、技术架构设计 1.混合开发框架选型 (1)使用Uniapp 3.8版本支持ArkTS编译 (2)通过uni-harmony插件调用原生能力 (3)分层架构设计: graph TDA[UI层] -->|Vue语法| B(Uniapp框架)B --&g…...