【C++】泛型编程——模板初阶

news2024/9/23 11:25:51

文章目录

    • 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语言相比,确实有了一点进步。

但是呢,还是有一些不好的地方:

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

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

这些重载的函数呢,干的事情都是一样的,只是处理的数据的类型不同。

那我们想:

能否告诉编译器一个模子(模板),让编译器根据不同的类型利用该模子来生成不同的代码呢?
在这里插入图片描述
就类似于这样。

那如果在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才是类型
这样我们就可以让不同的栈对象里面存不同类型的数据了。

然后还需要注意的是:

如果类模板里的成员函数声明和定义分离的话:
在这里插入图片描述
正常我们是这样写的,但是在类模板里这样不行。
注意:类模板中成员函数放在类外进行定义时,需要加模板参数列表
在这里插入图片描述
这样就可以了。

其次:

我们定义一个类可能习惯头文件和源文件分开来,那普通类这样搞是没问题的,就像我们之前实现的日期类就是多文件管理的。
但是呢,类模板不行,类模板如果这样搞,会链接错误的,至于原因呢,我们后面到模板进阶的时候会讲,大家先了解一下。

🆗,那这篇文章就先到这里,欢迎大家指正!!!
在这里插入图片描述

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

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

相关文章

神经网络中的激活函数

文章目录为什么要使用激活函数常用的激活函数如何选择激活函数ReLU激活函数的优点及局限性为什么Sigmoid和Tanh会导致梯度消失的问题为什么Tanh收敛速度⽐Sigmoid快&#xff1f;为什么要使用激活函数 在真实情况中&#xff0c;我们往往会遇到线性不可分问题&#xff0c;需要非…

汇编指令学习(MOV,MOVSX,MOVZX,LEA,XCHG)

一、MOV指令1、将十六进制0x1234数值&#xff0c;赋值给eax寄存器mov eax,0x12342、将十六进制0x123数值&#xff0c;赋值给内存地址为ebxmov dword [ebx],0x1233、将edx的高八位赋值给eax的低八位ax&#xff0c;eax的低16位&#xff0c;al&#xff0c;eax的低8位&#xff0c;a…

RedisTemplate 的基本使用手把手教

下载实例源码 使用步骤 1、引入 spring-boot-starter-data-redis 依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency>2、在 application.yml 配置 R…

一文分析Linux虚拟化KVM-Qemu之virtqueue

说明&#xff1a; KVM版本&#xff1a;5.9.1QEMU版本&#xff1a;5.0.0工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 前边系列将Virtio Device和Virtio Driver都已经讲完&#xff0c;本文将分析virtqueue&#xff1b;virtqueue用于前后端之间的数据交换&…

[4.10]-AutoSAR零基础学习-Secure Debug(SHE+)(一)

目录 1 内部调试保护概述 2 UCB confirmation AURIXTM 设备提供多个安全保护层&#xff0c;以限制调试器访问整个微控制器。 保护层的配置基于用户配置块 UCB&#xff0c;存在于DFlash上&#xff08;DF_UCB&#xff09;。UCB 包含保护设置参数和其他可由用户配置的参数。 DF_…

使用Python对excel中的数据进行处理

一、读取excel中的数据首先引入pandas库&#xff0c;没有的话使用控制台安装 —— pip install pandas 。import pandas as pd #引入pandas库&#xff0c;别名为pd#read_excel用于读取excel中的数据&#xff0c;这里只列举常用的两个参数&#xff08;文件所在路径&#xff…

华为OD机试模拟题 用 C++ 实现 - 连续子串(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明连续子串题目输入输出示例一输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD …

SQL Server开启CDC的完整操作过程

这里写自定义目录标题写在前面SQL Server开启CDC1. 将指定库的实例先开启CDC2. 开启需要开启CDC的表3. 关闭CDC功能更详细信息参照官网写在前面 鉴于老旧数据的结构和项目都在sqlserver上存储&#xff0c;且迁移成本巨大&#xff0c;当下要为sqlserver的存储过程减负。要将一部…

深入理解Spring MVC下

上一篇博客从理论概念上来梳理Spring MVC相关知识&#xff0c;此篇博客将通过spring官网提供showcase代码为例子&#xff0c;详细介绍showcase代码中包含的各个例子是如何实现的。官网的showcase代码包含的主要例子包括&#xff0c;Demo地址&#xff1a;Mapping Requests&#…

2023王道考研数据结构笔记第一章绪论

第一章 绪论 1.1 数据结构的基本概念 1.数据&#xff1a;数据是信息的载体&#xff0c;是描述客观事物属性的数、字符以及所有能输入到计算机中并被程序识别和处理的符号的集合。 2.数据元素&#xff1a;数据元素是数据的基本单位&#xff0c;通常作为一个整体进行考虑和处理…

【LeetCode】2363. 合并相似的物品

2363. 合并相似的物品 题目描述 给你两个二维整数数组 items1 和 items2 &#xff0c;表示两个物品集合。每个数组 items 有以下特质&#xff1a; items[i] [valuei, weighti] 其中 valuei 表示第 i 件物品的 价值 &#xff0c;weighti 表示第 i 件物品的 重量 。items 中每…

cpp之STL

STL原理 STL ⼀共提供六⼤组件&#xff0c;包括容器&#xff0c;算法&#xff0c;迭代器&#xff0c;仿函数&#xff0c;适配器和空间配置器&#xff0c;彼此可以组合套⽤。容器通过配置器取得数据存储空间&#xff0c;算法通过迭代器存取容器内容&#xff0c;仿函数可以协助算…

电子科技大学软件工程期末复习笔记(九):项目管理

目录 前言 重点一览 软件项目管理的四大要素 团队组织方式 虚拟团队 软件范围 项目计划 P-CMM的5个级别 本章小结 前言 本复习笔记基于王玉林老师的课堂PPT与复习大纲&#xff0c;供自己期末复习与学弟学妹参考用。 重点一览 本节是软件工程复习笔记的最后一篇&…

Hive---Zeppelin安装教程

Zeppelin安装教程 安装zeppelin必须基于Hadoop和Hive上 文章目录Zeppelin安装教程简介安装步骤1.上传zeppelin压缩包2.解压并更名3.修改配置文件编辑zeppelin-site.xml---将配置文件的ip地址和端口号进行修改编辑 zeppelin-env.sh---添加JDK和Hadoop环境拷贝hive文件切换目录拷…

数据结构与算法(六):图结构

图是一种比线性表和树更复杂的数据结构&#xff0c;在图中&#xff0c;结点之间的关系是任意的&#xff0c;任意两个数据元素之间都可能相关。图是一种多对多的数据结构。 一、基本概念 图&#xff08;Graph&#xff09;是由顶点的有穷非空集合和顶点之间边的集合组成&#x…

嵌入式小白的进阶之路

学习嵌入式采用的是2021年华清远见嵌入式课程 安装虚拟机 &#xff1a; 懒人版 目标&#xff1a;快速上手 问题一&#xff1a; 在Linux虚拟环境中运行一个c语言程序步骤 1、激活虚拟环境&#xff1a;source venv/bin/activate 2、进入Hello文件夹&#xff1a;cd Hello 3、新…

【Python学习笔记】第二十节 Python 网络编程

Python 网络编程简单来说&#xff0c;网络是用物理链路将各个孤立的工作站或主机相连在一起&#xff0c;组成数据链路&#xff0c;从而达到资源共享和通信的目的。所谓的网络编程就是&#xff0c;让在不同的电脑上的软件能够进行数据传递&#xff0c;即进程之间的通信Python 提…

超级品牌符号怎么设计?大咖有方法

怎么设计超级LOGO图标&#xff1f;有方法&#xff01; LOGO设计大趋势&#xff1a;卡通化、拟人化 抽象符号已经泛滥 但卡通形象也已经泛滥 趣讲大白话&#xff1a;设计容易出名难 【安志强趣讲信息科技89期】 ******************************* 别以为设计一个卡通就牛X闪闪 比…

解析iptables原里及设置规则

文章目录一、前言二、iptables简介三、iptables原理四、iptables设置规则五、 Linux 常用实例六、注意事项1、命令书写规则2、谨慎使用的动作 DROP3、永久生效一、前言 其实在IT行业里了&#xff0c;不仅是专职的运维人员需要了解 iptables&#xff0c;开发、测试等人员在了解…

IIS .Net Core 413错误和Request body too large解决办法

错误描述图片比较大时&#xff0c;在前端上传就报413错误。根本到不了后端。在网上看到这个文章比较有用。https://blog.csdn.net/wstever/article/details/1288707421、修改网站Web.config配置文件加入下面这段配置<?xmlversion"1.0" encoding"utf-8"…