【C++】:模板初阶—函数模板|类模板

news2025/1/10 16:55:08



Blog’s 主页: 白乐天_ξ( ✿>◡❛)

🌈 个人Motto:他强任他强,清风拂山岗!

💫 欢迎来到我的学习笔记!

本文参考博客:一同感受C++模版的所带来的魅力

一、泛型编程思想

  • 首先我们来实现一个swap交换函数。如果学过了C++的函数重载和引用的话,就可以写出swap函数不同参数类型的重载函数。(注意: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交换函数仅仅只是实现一个交换功能,却需要根据参数的类型写出几个很相似的函数,重复相同或者相近的代码。如果又增加了其他类型数据需要进行交换呢?继续重复相同的操作再去实现这个函数吗?

这样操作虽然可行,但是它存在几处缺陷:

  1. 重载的函数仅仅只是类型不同,代码复用吕比较低,只要有新的类型出现,就需要自己再次实现所需类型的相近函数。
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错。

那么既然重载函数的重复率比较高,能否只给出一个标准,然后让他们自己根据这个标准去实现所需要的东西呢?

这个标准就像做月饼用的模具一样:我们只需要放入材料,就可以利用模具做出形状相同的月饼。

后来C++就生成一个类似于模具的东西,将其交给编译器,让编译器根据这个模具自行生成所需代码。这就是泛型编程思想:编写与类型无关的通用代码,是代码复用的一种手段。模板,是泛型编程的基础。

二、函数模板–函数的模板

2.1 概念

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

通过函数模板,可以编写一种通用的函数定义,使其能够是用于多种数据类型,从而提高代码的服用行和灵活性。

2.2 格式

  • 声明一个函数模板需要关键字template<>内部是模板参数,可以使用class或者typename来进行类型的声明(不能用struct)。
  • 然后开始使用模板参数Tn,
template<typename T1,typename T2,typename T3,……,typename Tn>
返回值类型 函数名(参数列表)
{ }
  • 注意:这里的函数模板的参数和普通的函数参数不一样。函数模板参数定义的是<u>类型</u>,而普通函数参数定义的是<u>对象</u>
返回值类型 函数名(参数列表){ }
  • 在了解上面的函数模板后,我们就可以为Swap()函数写一个通用的函数模板了。
//模板类型
template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}
  • 运用:
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 1.1, d2 = 2.2;
	cout << "a1:" << a1 << " a2:" << a2 << endl;
	cout << "d1:" << d1 << " d2:" << d2 << endl;
	Swap(a1, a2);
	swap(d1, d2);
	cout << "a1:" << a1 << " a2:" << a2 << endl;
	cout << "d1:" << d1 << " d2:" << d2 << endl;
	return 0;
}

运行结果:

调试就可以发现:Swap函数的函数模板可以自己推导传入参数的类型。

  • 那如果给是Swap函数传入不同的参数类型呢?
//模板类型
template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

template<class T1,class	T2>
void func(const T1& x,const T2& y)//不同的类类型
{ }
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 1.1, d2 = 2.2;

	//传入不同的类型:
	Swap(a1, d1);	 //error:  message : “void Swap(T &,T &)”: 模板 参数“T”不明确
    				 // message : 可能是“double”或    “int”
    				 //message: “void Swap(T&, T&)” : 无法从“double”推导出“T & ”的 模板 参数
	func(a1, d1);	 //right

	return 0;
}

message : “void Swap(T &,T &)”: 无法从“double”推导出“T &”的 模板参数

  • 那为什么func()函数传入不同的类型的参数却没有问题呢?
    • 原因:传入func()函数的参数并没有用来做编译器无法推导参数类型的操作。Swap()函数是因为编译器无法推导出参数类型。(一个T类型,结果参数是两个类型)
  • 其实在C++中早就已经定义好了swap()这个函数,头文件<utility>,我们可以直接使用。

2.3 原理

2.4 实例化

  • 用不同类型的参数使用函数模板,称为函数模板的实例化。现在我们利用下面这个函数模板来理解实例化概念。
//用函数模板生成对应的函数-->模板的实例化
template<class T>//一个模板参数T
T Add(const T& left, const T& right)
{
	return left + right;
}

2.4.1 隐式实例化

  1. 概念:
  • 隐式实例化(推导实例化):让编译器根据实参推演模板参数的实际类型,再返回不同类型的数据。
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	//推导实例化:实参传递给形参,编译器推导出T的类型
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d1) << endl;
	return 0;
}

运行结果:

  1. 缺陷:传入参数类型不同,编译器左右为难、骑虎难下。
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.1, d2 = 20.2;
	cout << Add(a1, d1) << endl;
    //message : “T Add(const T &,const T &)”: 无法从“double”推导出“const T &”的 模板 参数
	return 0;
}

d1强制类型转换为int类型,解决类型冲突问题。

cout << Add(a1, (int)d1) << endl;

a1强制类型转换为double类型,解决类型冲突问题。

cout << Add((double)a1, d1) << endl;

③ 如果非要传入不同类型的参数,就应该用不同的模板参数重新定义一个函数模板。

template<class T1& left,class T2& right>
T1 Add(const T1& left,const T2& right)//两个模板参数
{
	return left + right;
}

2.4.2 显示实例化

  1. 概念
  • 显示实例化:在函数名后面紧跟<>并在其中指定模板参数的实际类型。
//显示实例化:用指定类型来实例化
cout << Add<int>(a1, d1) << endl;
cout << Add<double>(a1, d1) << endl;
  1. 缺陷:其实在某些场景下面我们只能选择显示实例化。
  • 形参部分不是模板参数,而是普通的自定义类型,返回值才是。此时我们无法通过传参来指定这个T的类型,只有外部在调用这个模板时显示指定。
template<class T>
T* Alloc(int n)
{
	return new T[n];
}
// 有些函数无法自动推,只能显示实例化
int  main()
{
    // 有些函数无法自动推,只能显示实例化
    double* p1 = Alloc<double>(10);
    float* p1 = Alloc<float>(20);
    int* p2 = Alloc<int>(30);
    return 0;
}

2.5 模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
//普通函数
int Add(int left, int right)
{
	return left + right;
}
//函数模板
template<class T>
T Add(T left, T right)
{
	return left + right;
}
int main()
{
	Add(1, 2);//调用了普通传参的函数
	Add<int>(1, 2);//调用了模板函数让其实生成对应的函数
	return 0;
}
  • 根据调试可以发现:普通函数和函数模板是可以共存的。在进行普通传参时调用的是普通函数;显示指定了类型时,就会调用函数模板生成对应的函数。
  1. 对于非模板函数和同名函数模板,如果其他条件相同,在调用时会优先调用非模板函数而不会从该模板产生一个实例。如果模板可以产生一个更好匹配的函数,那么将会选择模板。
// 普通函数
int Add(int left, int right)
{
	return left + right;
}

// 函数模板
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
	return left + right;
}

int main()
{
	Add(1, 2);//调用普通函数,因为参数匹配
	Add(1, 2.2);///调用函数模板,因为参数不匹配,普通函数不接收此参数。
    //函数模板可以根据这个类型自动推导
	return 0;
}
  1. 模板函数不允许自动类型转换,但是普通函数可以进行自动类型转换。
  • 普通函数传入的参数可以发生隐式类型转换,又称【自动类型转换】,下面代码中浮点数自动类型转换为整型数。
// 普通函数,允许自动类型转换
void print(int value) 
{
    std::cout << "Integer: " << value << std::endl;
}

int main() 
{
    print(3);	
    print(3.14); // 在这里发生了隐式类型转换,浮点数自动类型转换为整型数
    return 0;
}
  • 对于函数模板来说是不能进行自动类型转换的。a和b是两个不同的类型,并没有发生自动类型转换,一个模板参数T就使得编译器无法进行自动推导。解决办法就是上面提到的隐式/显示类型转换。第三种方法就是增加模板参数。
template <class T>
void print(T a, T b) {
    cout << a << " " << b << endl;
}
int main()
{
    int a = 1;
    double b = 1.11;

    print(a, b);//error---message : “void print(T,T)”: 模板 参数“T”不明确
    return 0;
}


三、类模板–类的模板

3.1 类模板的定义格式

  • 函数模板是加在函数上,类模板是加在类上。
template<class T1, class T2, ..., class Tn>
class 类模板名
{
 // 类内成员定义
};
  • 类模板都是显示实例化。
template<class T>
class Stack
{
public:
	Stack(int n = 4)//写构造
		:_array(new T[n])//不用检查是否失败,失败了直接抛异常
		,_size(0)
		,_capacity(n)
	{
	}
	~Stack()
	{
		delete[] _array;
		_size = _capacity = 0;
		_array = nullptr;
	}
	void Push(const T& x)//传引用传参,避免多余拷贝浪费资源,能用引用就尽量使用引用
	{
		//空间不够用:扩容
		if (_size == _capacity)
		{
			//不能使用realloc,无构造
			//C++无自动扩容的概念,扩容需要手动
			T* temp = new T[_capacity * 2];//扩容两倍,size不变
			memcpy(temp, _array, sizeof(T) * _size);//拷贝数据
			delete[] _array;//delete一次即可:释放旧空间
			
			_array = temp;//指向新空间
			_capacity *= 2;
		}
		_array[_size++] = x;
	}
//其他功能…………
private:
	T* _array;
	size_t _capacity;
	size_t _size;
};
int main()
{
	//类模板都是显示实例化
	Stack<int> st1;//     int
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	Stack<double> st2;//  double
	st2.Push(1.1);
	st2.Push(2.2);
	st2.Push(3.3);

	return 0;
}
  • 我们在C语言中学习栈的时候就有一个typedef的操作,功能类似于这里的模板。既然如此,那类模板的意义是什么呢?
    • C语言中的栈只能实现某一种类型,而C++可以一个栈存入int类型,另一个栈存入double类型。
    • 就像在这里一样,实现的栈的结构基本都是一样的,不一样的只是存入栈的数据类型是不一样的。
  • 如果在这里进行声明和定义的分离?
    • 定义的函数模板只能给当前的函数或者当前的类使用,每个函数模板都需要定义自己的模板参数,按需定义。当进行声明和定义分离时,需要在函数前面单独声明类模板,并指定类域。下面就是类模板中的成员函数在类外面实现所需要变化成的模板函数。
template<class T>
class Stack
{
public:
	Stack(int n = 4)
		:_array(new T[n])
		, _size(0)
		, _capacity(n)
	{
	}
	~Stack()
	{
		delete[] _array;
		_size = _capacity = 0;
		_array = nullptr;
	}
	void Push(const T& x);
	//其他功能……
private:
	T* _array;
	size_t _capacity;
	size_t _size;
};
//指定类域不行,还需要声明一下类模板
template<class T>
void Stack<T>::Push(const T& x)//传引用传参,避免多余拷贝浪费资源,能用引用就尽量使用引用
{
	//空间不够用:扩容
	if (_size == _capacity)
	{
		T* temp = new T[_capacity * 2];
		memcpy(temp, _array, sizeof(T) * _size);
		delete[] _array;

		_array = temp;
		_capacity *= 2;
	}
	_array[_size++] = x;
}
  • 如果我们将上面分离后新声明的T改为X,能否编译?
    • 能的。T只是一个符号而已,我们实际上在调用这个函数时,并没有T或者X的概念。
  • 注意:模板不支持声明和定义分离到两个不同文件中去。(即使可以,也相当繁琐。)

3.2 类模板的实例化

  • 类模板实例化和函数模板实例化不同。类模板实例化需要在类模板名称后面跟<>。然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
int main()
{
	//类模板都是显示实例化
	Stack<int> st1;//     int
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);

	Stack<double> st2;//  double
	st2.Push(1.1);
	st2.Push(2.2);
	st2.Push(3.3);

	return 0;
}

以上就是本文所要介绍的所有类容,感谢您的阅读!🌹

在这里插入图片描述

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

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

相关文章

代码随想录算法训练营第五十一天 | 99.岛屿数量-深搜 ,99.岛屿数量-广搜 ,100.岛屿的最大面积

目录 99.岛屿数量-深搜 思路 方法一&#xff1a; 深度优先搜索-先判断 方法二&#xff1a;深度优先搜索-终止条件 心得收获 99.岛屿数量-广搜 思路 广度优先搜索 方法一&#xff1a;广度优先搜索 100.岛屿的最大面积 思路 深度优先搜索 广度优先搜索 方法一&am…

c语言——用一维数组输出杨辉三角形

一.代码 #include <stdio.h> int Num[100]; int Hang; int Lie; int a; int Flag; int main() {Lie 1;Hang 1;a 0;while (1) {//列1为1if (Lie 1) {Num[1] 1;Lie;}//数据存到数组里面while (Hang > Lie && Hang ! 2) { if (Hang!Lie) {Flag Num[Lie] …

解锁Web3.0——Scaffold-eth打造以太坊DApp的终极指南

&#x1f680;本系列文章为个人学习笔记&#xff0c;目的是巩固知识并记录我的学习过程及理解。文笔和排版可能拙劣&#xff0c;望见谅。 目录 前言 一、快速部署 1、前期准备&#xff1a; 2、安装项目&#xff1a; ​ 二、配置部署运行环境 1、初始化本地链&#xff1a;…

Qt-QWidget的windowOpacity属性(16)

目录 描述 相关API 使用 设置槽函数 两个问题 第一个问题&#xff1a;浮点数精确度问题 第二个问题&#xff1a;防御性编程 描述 这个属性就是用来设置窗口的不透明度的 相关API 使用 我们创建一个新的项目来进行测试 如下&#xff0c;我们再把这两个按钮设置一下名…

改进YOLOv8系列:加入多尺度卷积注意力MSCA注意力: ,即插即用,助力小目标检测

改进YOLOv8系列:加入非对称卷积块ACNet,加强CNN 的内核骨架 论文研究概括MSCA模块介绍需要修改的代码MSCA代码创建yaml文件测试是否创建成功本文提供了改进 YOLOv8注意力系列包含不同的注意力机制以及多种加入方式,在本文中具有完整的代码和包含多种更有效加入YOLOv8中的ya…

模型 跃迁(泛化)

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。跨越式变革与发展。 1 跃迁的应用 1.1 个人成长中的跃迁现象&#xff1a;职业转型与技能升级 背景描述&#xff1a; 在个人职业发展的过程中&#xff0c;跃迁现象指的是个体在技能、知识和职业地位上…

Python教程(二十一) : 从零开始制作计算器应用【PyQt6】

文章目录 专栏列表环境准备代码解析主要组件初始化界面布局设置事件处理计算逻辑 运行应用完整代码示例截图总结注意 专栏列表 Python教程&#xff08;十&#xff09;&#xff1a;面向对象编程&#xff08;OOP&#xff09;Python教程&#xff08;十一&#xff09;&#xff1a;…

国产化软件内容及要求

国产化软件是指在中国自主研发的软件产品&#xff0c;旨在减少对外部技术的依赖&#xff0c;提升国家信息安全和软件产业的自主可控能力。国产化软件涵盖了从操作系统、数据库、办公软件、各类应用软件、中间件等多个层面。 操作系统&#xff1a;国产操作系统如银河麒麟、UOS、…

博客建站8 - 选择hexo博客网站的主题

1. 环境说明2. 体验过的hexo站点主题 2.1. Acorn2.2. hexo-theme-cafe2.3. volantis2.4. NexT 3. 参考文档 1. 环境说明 博客框架&#xff1a; Hexo网站主题&#xff1a; Volantis评论系统&#xff1a; Disqus服务器: 阿里云ECS服务器系统&#xff1a; Ubuntu 24.04 LTS 2. …

仕考网:结构化面试流程介绍

(一)结构化面试 结构化面试&#xff0c;也叫做标准化面试&#xff0c;考官按照预先设定好的一套试题以问答方式与应试者当面交谈&#xff0c;根据应试者的言语、行为表现&#xff0c;对其相关能力和个性特征作出相应评价。 &#xff08;二&#xff09;考试流程 抵达考场——…

CAD 多个页面在一个任务栏图标设置

命令行输入快捷键op或&#xff1a; 下图打对号&#xff0c;确定即可。

vsCode 自动发布文件到服务器文件

1.新建 publish.cmd文件 xcopy D:\_____\*.* \\_____\jyou /s /e /y源文件夹和目标文件夹按照自己替换&#xff0c;/s /e /y会复制空白文件夹&#xff0c;且遇到相同文件直接覆盖 2.将这个文件复制到nodemodules/bin目录下 3.在package.json中配置发布命令

翻译器大分享,这5款你选哪款?

作为一个经常需要阅读和翻译各种学术论文和专业文档的研究生&#xff0c;我深知找到一款好用的翻译工具是多么重要。今天&#xff0c;我就来跟大家聊聊我用过的四款翻译PDF文档的工具它们的表现如何呢&#xff1f;一起来看看吧&#xff01; 一、福昕在线翻译 网址&#xff1a;…

[米联客-XILINX-H3_CZ08_7100] FPGA程序设计基础实验连载-38 LVDS Select IO高速Serdes

软件版本&#xff1a;VIVADO2021.1 操作系统&#xff1a;WIN10 64bit 硬件平台&#xff1a;适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA 实验平台&#xff1a;米联客-MLK-H3-CZ08-7100开发板 板卡获取平台&#xff1a;https://milianke.tmall.com/ 登录“米联客”FPGA社区 http…

使用 VisionTransformer(VIT) FineTune 训练驾驶员行为状态识别模型

一、VisionTransformer(VIT) 介绍 大模型已经成为人工智能领域的热门话题。在这股热潮中&#xff0c;大模型的核心结构 Transformer 也再次脱颖而出证明了其强大的能力和广泛的应用前景。Transformer 自 2017年由Google提出以来&#xff0c;便在NLP领域掀起了一场革命。相较于…

Typora 画图技巧(思维利器,含文本及图示~!)

Typora 画图技巧&#xff08;思维利器&#xff0c;含文本及图示~&#xff01;&#xff09; 设置图表示例流程图横向流程图竖向流程图标准流程图标准流程图&#xff08;横向&#xff09; UML时序图UML时序图一UML时序图二UML标准时序图一UML标准时序图二 甘特图类图状态图饼图 &…

找高清视频素材,上这8个网站

分享8个提供高清视频素材的优秀网站&#xff0c;无论你是在制作宣传片、社交媒体内容还是影视作品&#xff0c;这些资源都能帮助你找到理想的素材&#xff0c;让你的作品更加生动和引人注目。 1、菜鸟图库 视频素材下载_mp4视频大全 - 菜鸟图库 菜鸟图库免费视频素材下载。网站…

UWB定位室外基站

定位基站&#xff0c;型号SW&#xff0c;是一款基于无线脉冲技术开发的UWB定位基站&#xff0c;基站可用于人员、车辆、物资的精确定位&#xff0c; 该基站专为恶劣环境使用而设计&#xff0c;防尘、防水等级IP67&#xff0c;工业级标准支持365天连续运行&#xff0c;本安防爆可…

Java并发编程实战 05 | 什么是线程组?

1.线程组介绍 在 Java 中&#xff0c;ThreadGroup 用于表示一组线程。通过 ThreadGroup&#xff0c;我们可以批量控制和管理多个线程&#xff0c;使得线程管理更加方便。 ThreadGroup 和 Thread 的关系就像它们的字面意思一样简单&#xff1a;每个线程 (Thread) 必定属于一个线…

7.统一网关-Gateway

文章目录 1.统一网关介绍2.网关开发3.predicate4.Route Predicate Factories(路由断言工厂)4.1Path 路由断言工厂4.2.Method 路由断言工厂4.3 Header 路由断言工厂4.4 Query 路由断言工厂4.5 Host 路由断言工厂4.6 After 路由断言工厂4.7 Before 路由断言工厂4.8 Between 路由断…