模板的基本概念

news2025/1/18 4:43:21

模板

  • 函数模板
    • 泛型编程
    • 基本概念
    • 函数模板格式
    • 函数模板的实现
    • 函数模板的实例化
  • 类模板
    • 类模板格式
    • 类模板的实例化
  • 模板参数的匹配原则
  • 复数的相加

函数模板

泛型编程

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

基本概念

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

例如:实现交换函数时,倘若存在多种类型的数据进行交换:

int main()
{
	Swap(1, 2);  //整形数据交换
	Swap(1.2, 3.4);  //浮点型数据交换
	Swap('1', '2');  //字符型数据交换
	return 0;
}

按照通常情况下,我们需要编写多个不同数据类型的交换函数来实现以上交换功能:

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
void Swap(double& a, double& b)
{
	double tmp = a;
	a = b;
	b = tmp;
}
void Swap(char& a, char& b)
{
	char tmp = a;
	a = b;
	b = tmp;
}

倘若有多个数据类型的数据需要进行数据的交换,我们需要编写多份交换函数,且各个交换函数只有参数类型是不同的,其函数内部实现都是一样的(构成函数重载),这种方法实现下来就会比较繁琐,因此我们考虑能否只编写一个交换函数就能够实现多种类型的数据间交换,故而引入了模板的概念。

函数模板格式

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

typename 是用来定义模板参数的关键字,也可以使用 class,但是不能使用struct

在每一个函数模板之前都需要定义模板类型

参数只涉及单个类型时

引入函数模板的概念之后,我们可以对上述交换函数进行改进:

template<typename T>   //只涉及一种数据类型
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

它可以实现多种类型的数据进行交换,测试如下:

在这里插入图片描述

在这里插入图片描述
参数涉及多个类型时

当函数参数涉及多种数据类型时,我们在函数模板定义中也可以设置多种数据类型:

template<typename T,typename S,class U>
void Print(T data1, S data2, U data3)
{
	cout << data1 << " " << data2 << " " << data3 << endl;
}

函数模板的实现

在调用交换函数时,编译器首先需要对调用交换函数的数据类型进行推演,例如上例中在进行交换 a,b 两个数据内容时,编译器首先会推演出 a,b对象的数据类型为 int 类型,然后就会产生专门处理 int 数据类型的交换函数代码,也就是我们在底层看到的 call 调用到了 Swap 函数

函数模板的实例化

使用不同类型的数据来调用函数模板的过程称为函数模板的实例化。

(一)隐式实例化

隐式实例化也就是让编译器来自动推演函数模板的实际类型。

例如:
在下述代码中,在主函数中调用 Add 函数,在传递实参时编译器会自动推演出实参的数据类型,根据实参类型来确定函数模板的类型,并实例化出相应类型的交换函数代码实现实参数据的交换

template<class T>
T Add(T& left, T& right)
{
	return left + right;
}

int main()
{
	int a = 1, b = 2;
	double c = 1.2, d = 3.4;
	
	Add(a, b);        //类型推演 int
	Add(c, d);          //类型推演 double
	return 0;
}

在这里插入图片描述

而对于要进行相加的数据类型不一致时,推演会出错:

在这里插入图片描述

对于这种参数类型不一致的情况下编译器自动类型推演会出错,且编译器不会针对这种情况进行自动的隐式类型转换------------因为会产生歧义:到底是将 a 变量类型转换成与 c 相同的类型,还是将 c 变量类型转换成与 a 相同的类型?

故有两种解决方法,其一是由用户对参数进行类型强转:

	int a = 1, b = 2;
	double c = 1.2, d = 3.4;
	
	Add((double)a, c);    //用户进行强制类型转换
	Add(a, (int)c);

在这里插入图片描述

另一种方法,即显式实例化。

(二)显式实例化

显式实例化也就是在函数名之后显式声明函数模板中参数的实际类型:

	int a = 1, b = 2;
	double c = 1.2, d = 3.4;
	
	Add<double>(a, c);  //显式声明函数模板类型
	Add<int>(a, b);

在这里插入图片描述

经过显式定义之后调用的函数模板,在编译阶段就不会再进行类型的推演了

类模板

类模板格式

template<class T1,class T2,…,class Tn>
class 类名{ //类内成员的定义 };

例如,顺序表的实现:

class SeqList {
public:
	SeqList(size_t cap = 10)   //构造函数
		:_arr(nullptr), _capacity(cap == 0 ? 3 : cap), _size(0)
	{
		_arr new int[10];     //int 类型空间的开辟
	}
	void PushBack(int data)
	{
		_arr[_size] = data;
		++_size;
	}
	bool Empty()const
	{
		_size == 0;
	}
	void PopBack()
	{
		if (Empty())
			return;
		--_size;
	}
	int Front()
	{
		return _arr[0];
	}
	int Back()
	{
		return _arr[_size - 1];
	}
	~SeqList()      //析构函数
	{   
		if (_arr) {
			free(_arr);
			_arr = nullptr;
			_capacity = _size = 0;
		}
	}
private:
	int* _arr;
	size_t _capacity;
	size_t _size;
};

上述代码规定了顺序表的数据类型为 int ,倘若要存储其他数据类型,我们应如何应用模板的概念进行修改?

显然,只需要设置类模板,并将类中数据类型更换为类模板类型即可:

template<class T>
class SeqList {
public:
	SeqList(size_t cap = 10)   //构造函数
		:_arr(nullptr), _capacity(cap == 0 ? 3 : cap), _size(0)
	{
		_arr new T[10];           //动态空间的申请
	}
	void PushBack(T data)
	{
		_arr[_size] = data;
		++_size;
	}
	bool Empty()const
	{
		_size == 0;
	}
	void PopBack()
	{
		if (Empty())
			return;
		--_size;
	}
	T& Front()
	{
		return _arr[0];
	}
	T& Back()
	{
		return _arr[_size - 1];
	}
	T& operator[](size_t index)   //下标运算符,可以对某位置元素进行修改
	{
		return _arr[intdex];
	}
	T& operator[](size_t index)const   //下标运算符,不可以对某位置元素进行修改
	{
		return _arr[intdex];
	}
	~SeqList()      //析构函数
	{   
		if (_arr) {
			free(_arr);
			_arr = nullptr;
			_capacity = _size = 0;
		}
	}
private:
	T* _arr;
	size_t _capacity;
	size_t _size;
};

测试:

	cout << s1[2] << endl;  //下标运算符重载
	s1[2] = 10;     //调用第一个下标运算符重载函数

之前的博客在学习类和对象的时候,曾经说明过类成员函数的定义不仅可以在类内也可以在类外,那么采用类模板时的成员函数如何在类外进行定义?

template<class T>
class SeqList {
public:
	SeqList(size_t cap = 10)
		:_arr(nullptr), _capacity(cap == 0 ? 3 : cap), _sizeo(0)
	{
		_arr = new T[10];
	}
	//类中成员方法不仅可以在类内定义,也可以在类外定义
	T& Front();
	T& Back();

	void PushBack(const T& data)
	{
		_arr[_size] = data;
		_size++;
	}
	bool Empty()const
	{
		_size == 0;
	}
	void PopBack()
	{
		if (Empty())
			return;
		--_size;
	}
	~SeqList()
	{
		if (_arr) {
			delete[] _arr;
			_arr = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	T* _arr;
	size_t _capacity;
	size_t _size;
};
//
/
//类外定义
template<class T>      
//每个函数模板实现之前都需要进行模板的定义
T& SeqList<T>::Front()   
//返回值类型为模板类型,类外定义成员函数时需要限定作用域
{
	return _arr[0];
}

//每个函数模板实现之前都需要进行模板的定义
template<class T>
T& SeqList<T>::Back()
{
	return _arr[_size - 1];
}

类模板的实例化

类模板的实例化与函数模板的实例化不同,类模板实例化需要在类模板名字之后跟<>说明实例化的类型,即显式实例化

类模板名字不是真正的类,实例化之后的结果才是真正的类

在这里插入图片描述
在这里插入图片描述

模板参数的匹配原则

一个函数模板可以与同名的非函数模板 同时存在,并且该函数模板可以被实例化为非函数模板

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

int Add(int a, int b)
{
	return a + b;
}

在这里插入图片描述

对于非模板函数和同名的函数模板,若其他条件相同,在调用时会优先调用非模板函数,如果模板可以产生一个具有更好匹配的函数,则会选择模板

template<class T,class U>
T Add(const T& left, const U& right)
{
	return left + right;
}

int Add(int a, int b)
{
	return a + b;
}

int main()
{
	Add(1, 2);   //调用普通函数
	Add(1, 2.0);   //调用模板--------因为两个参数类型不一致,使用模板匹配度更高
	return 0;
}

复数的相加

复数具有实部与虚部,因此在进行相加时候需要分别将实部相加,虚部相加,并返回相加后的结果

//首先定义实现加法的函数模板
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}

定义复数类:

class Complex {
public:
	Complex(double real, double image)  //构造函数
		:_real(real), _image(image)
	{}
	Complex operator+(const Complex& c)const
	{
		//由于复数具有实部和虚部两部分,因此在进行相加时候需要进行运算符重载
		
//方法一:
		/*Complex ret(_real + c._real, _image + c._image);
		return ret;*/   //存在一次值拷贝

//方法二:调用构造函数创建了一个无名对象,对象类型为 Complex,用来保存复数相加的结果,减少一次值拷贝的过程
		return Complex(_real + c._real, _image + c._image);
	}
private:
	double _real;
	double _image;
};

在这里插入图片描述

由因此此时加法函数实现的是复数类型的数据加法,故而在 Add 内部又会调用复数加法运算符重载函数实现复数相加

ps:
欢迎个人学者评论交流丫~

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

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

相关文章

【自学C++】C++命名空间引入

C命名空间引入 C命名空间引入教程 C 中的命名空间的引入&#xff0c;我们通常使用 using 语句后面加上命名空间名即可。 C命名空间引入详解 语法 using namespace namespaceName参数 参数描述using引入命名空间使用的关键字。namespace引入命名空间使用的关键字。namespa…

数影周报:2亿Twitter用户资料仅售2美元,微盟集团将筹约15.9亿港元

本周看点&#xff1a;黑客以2美元出售2亿Twitter用户个人资料&#xff1b;Twitter 第三轮裁员&#xff1b;京东科技成立京东云事业部&#xff1b;TikTok Shop越南收入赶超Lazada&#xff1b;微盟集团将筹资约15.9亿港元......数据安全那些事黑客以2美元出售2亿Twitter用户资料近…

2023年山东最新建筑施工架子工(建筑特种作业)模拟题库及答案

百分百题库提供特种工&#xff08;架子工&#xff09;考试试题、特种工&#xff08;架子工&#xff09;考试预测题、特种工&#xff08;架子工&#xff09;考试真题、特种工&#xff08;架子工&#xff09;证考试题库等,提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助…

mosquitto使用与openssl证书配置

开发环境&#xff1a;ubuntu18.04 64bitmqtt客户端测试工具&#xff1a;mqtt.fx 1.7.11.安装# 引入库 sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa sudo apt-get update # 安装 sudo apt-get install mosquitto #安装客户端 sudo apt-get install mosquitto-clie…

《NDT-LOAM: A Real-Time Lidar Odometry andMapping With Weighted NDT and LFA》深大学生

Abstract激光雷达同时定位和建图&#xff08;Lidar-SLAM&#xff09;从激光雷达上处理点云&#xff0c;并完成定位和建图。激光激光通常分为前端里程计和后端优化&#xff0c;可以并行运行以提高计算效率。前端里程计通过处理点云来估计激光雷达的运动&#xff0c;在点云配准中…

python自学之《21天学通Python》(6)

第9章 迭代器、生成器与装饰器 迭代器、生成器与装饰器是Python语言中常用的语法形式。 迭代器的使用简化了循环程序的代码并可以节约内存&#xff0c;生成器的使用也可以节约大量的内存&#xff0c;特别是需要生成大量序列的对象时。迭代器是一种可以从其中连续迭代的一个容器…

Shell 数组

数组中可以存放多个值。Bash Shell 只支持一维数组&#xff08;不支持多维数组&#xff09;&#xff0c;初始化时不需要定义数组大小&#xff08;与 PHP 类似&#xff09;。与大部分编程语言类似&#xff0c;数组元素的下标由 0 开始。Shell 数组用括号来表示&#xff0c;元素用…

教你如何巧妙化解SSRF漏洞攻击

SSRF是一种由攻击者构造请求&#xff0c;由服务端发起请求的安全漏洞。一般情况下&#xff0c;ssrf攻击的目标是外网无法访问的内部系统。简单来说就是利用服务器漏洞以服务器的身份发送一条构造好的请求给服务器所在内网进行攻击。 SSRF漏洞&#xff08; 服务器端请求伪造 &a…

【pandas】教程:9-如何轻松处理时间序列数据

Pandas 如何轻松处理时间序列数据 数据 本节使用的数据为 data/air_quality_no2_long.csv&#xff0c;链接为 pandas案例和教程所使用的数据-机器学习文档类资源-CSDN文库 import pandas as pd import matplotlib.pyplot as pltair_quality pd.read_csv("data/air_qua…

实战字节码-01-基础知识

开篇字节码是什么、做什么这类问题不在这里赘述&#xff0c;《实战字节码》系列旨在帮助没接触过字节码的人能够快速上手做应用开发&#xff0c;并构建字节码技术的知识骨架&#xff0c;所以不会系统地介绍字节码技术的方方面面&#xff0c;也尽量避免叙述理论和概念相关的东西…

【笔记:模拟CMOS集成电路】噪声——基本电路噪声性能(2)

【笔记&#xff1a;模拟CMOS集成电路】噪声——基本电路噪声性能&#xff08;2&#xff09;前言1 噪声——分析基础2 噪声——基本电路噪声性能2.1 MOS管噪声模型(1)电阻RG热噪声和沟道热噪声(2)衬底电阻热噪声(3)源极寄生电阻RS热噪声2.2常见组态的单级放大器噪声分析2.2.1 CS…

Python电影观众数量回归分析 随机森林 可视化 实验报告

实验报告&#xff1a;Python电影观众数量回归分析随机森林可视化-数据挖掘文档类资源-CSDN文库 前言 随着经济的发展和人民日益增长的美好生活需要的显著提升&#xff0c;看电影成为了人民群众在闲暇时光娱乐的重要途径。面对百花齐放的电影产业&#xff0c;哪些电影更能带动市…

OpenGL期末大作业——模拟太阳系(免费开源)

目录 一、项目介绍 二、配置与运行 三、项目地址 一、项目介绍 这是一个综合的openGL场景&#xff0c;模拟太阳系。场景中有光照&#xff0c;纹理等&#xff0c;并有丰富的视角控制&#xff0c;UI交互&#xff0c;比如WASD/IJKL键控制视角的移动等等。一个太阳系的场景&#…

大数据基础平台搭建-(五)Hive搭建

大数据基础平台搭建-&#xff08;五&#xff09;Hive搭建 大数据平台系列文章&#xff1a; 1、大数据基础平台搭建-&#xff08;一&#xff09;基础环境准备 2、大数据基础平台搭建-&#xff08;二&#xff09;Hadoop集群搭建 3、大数据基础平台搭建-&#xff08;三&#xff09…

Android今日头条平台隐私合规整改

头条应用管理平台开发者合规指引&#xff1a;https://open.oceanengine.com/labels/7/docs/1730079845340164头条审核合规的app&#xff0c;需要具备以下条件&#xff1a;用户协议弹窗抖音隐私政策&#xff08;模板示例&#xff09;&#xff1a;https://sf3-cdn-tos.douyinstat…

别告诉我你只知道waitnotify,不知道parkunpark???

目录 park&unpark wait,notify 和 park,unpark的区别 park unpark 原理 先调用park的情况 先调用park,在调用unpark的情况 先调用unpark,在调用park的情况 park&unpark park和unpark都是LockSupport的方法,park用于暂停当前线程的运行,而unpark用于恢复该线程的…

服务机器人“众生相”

在多种因素的共同作用下&#xff0c;早年间经常出现在科幻片中的机器人已然穿越荧屏来到了现实世界&#xff0c;为人们的日常生活增添了几分便利。比如&#xff0c;在家庭场景中&#xff0c;扫地机器人帮助人们解放双手&#xff1b;在餐饮场景中&#xff0c;送餐机器人为顾客提…

C语言--探索函数栈帧的创建与销毁

目录 为main函数开辟栈帧 创建变量 传参 为自定义函数开辟栈帧 返回 局部变量是怎么创建的&#xff1f;为什么局部变量的值是随机值&#xff1f;函数是怎么传参的&#xff1f;形参与实参的关系&#xff1f;函数怎么调用与返回&#xff1f; 我们用VS2013的环境进行探索…

Https为什么比Http安全?

Https是在Http之上做了一层加密和认证&#xff1b; 主要的区别是Https在TLS层对常规的Http请求和响应进行加密&#xff0c;同时对这些请求和响应进行数字签名。 Http请求的样式&#xff1a; 明文传输&#xff0c;通过抓包工具可以抓到 GET /hello.txt HTTP/1.1 User-Agent: c…

【三】Netty 解决粘包和拆包问题

netty 解决粘包和拆包问题TCP 粘包/拆包的基础知识粘包和拆包的问题说明TCP粘包/拆包 原因粘包和拆包的解决策略tcp 粘包/拆包 的问题案例大致流程如图:代码展示(jdk1.7)TimeServer 服务端启动类TimeServerHandler 服务端业务处理类TimeClient 客户端启动类TimeClientHandler 客…