模板初阶(C++)

news2024/11/23 15:07:19

目录

泛型编程

引入

模板

函数模板

函数模板的概念 

函数模板格式

函数模板的原理

函数模板的实例化

隐式实例化

显式实例化

模板参数的匹配原则

类模板

类模板的定义格式

类模板的实例化


泛型编程

引入

我们在实际编写代码中,经常会遇到不同的类型需要实现同一种功能的实例!

比如:同类型的两个变量进行交换:

代码:

#include <iostream>
using namespace std;

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; 
}
 
//......

int main()
{
	return 0;
}

以上代码中的几个函数,都是在实现同一个交换功能,只是它的类型不同,且都是通过函数重载实现的!实现同一功能的函数我们写了很多遍,这样写就很麻烦了!那么有没有一种方法可以实现一个通用的交换函数呢?在C++中为了解决这一问题,就引出了模板的概念!


模板

概念: 

告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码!


看图理解:

我们做一个建筑图标模型,我们可以通过这个模具来弄出各种不同颜色的建筑类型,通过一个同样式模具,可以做出各种同样式不同颜色的建筑图标!而C++中的模板也是如此,告诉编译器一个模板,编译器就可以根据不同的类型利用该模板,生成各类型所对应的代码!


 泛型编程:

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


模板分类:


函数模板

函数模板的概念 

概念:

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

给编译器一个函数模板,编译器根据模板参数的类型自动生成该类型所对应的函数!


函数模板格式

写法格式:

template <typename T1,typename T2,……,typename Tn>

返回值类型 函数名(参数类型) { }


template <class T1,class T2,……,class Tn> 

返回值类型 函数名(参数类型) { }


代码:

#include <iostream>
using namespace std;

//函数模板
//使用typename 来定义模板 
template <typename T>
void Swap(T& a,T& b)
{
	T tmp=a;
	a=b;
	b=tmp;
}

//使用class 来定义函数模板
template <class T>
T Add(T& a,T& b)
{
	//T z=a+b;
	return a+b;	
} 

//注意typename是定义模板的关键字
//也可以使用class来定义
//切记不可用struct来定义模板 
 
int main()
{
	//利用typename来定义模板 
	int a=10;
	int b=20;
	cout<<"交换前:"<< " a = "<<a<<", b = "<< b<<endl;
	Swap(a,b);//利用模板进行交换 
	cout<<"交换后:" << " a = "<<a<<", b = "<< b<<endl;
	
	double d1=1.11;
	double d2=2.22;
	cout<<"交换前:"<< " d1 = "<<d1<<", d2 = "<< d2<<endl;
	Swap(d1,d2);//利用模板进行交换 
	cout<<"交换后:" << " d1 = "<<d1<<", d2 = "<< d2<<endl;
	
	//class定义模板
	cout<<Add(a,b)<<endl;
	
	double c=12.3;
	double d=12.6; 
	cout<<Add(c,d)<<endl;
	
	return 0;
}

注意:

typename是用来定义模板参数的关键字,也可以使用class来定义(切记:不可使用struct)!


函数模板的原理

原理:

模板是一个蓝图,它本身并不是函数,是编译器使用方式产生特定具体类型函数的模具。所以模板就是将本来我们应该重复做的事情交给了编译器!模板的本质实际上还是函数重载,只不过这些活都交给编译器干了!编译器在内部自动实现了,不需要用户自己实现,用户只需要提供模具就行!


代码:

#include <iostream>
using namespace std;

template <typename T>
void Swap(T& a,T& b)
{
	T temp=a;
	a=b;
	b=temp;
}

int main()
{
	double d1=2.0;
	double d2=5.0;
	Swap(d1,d2);
	
	int i1=10;
	int i2=20;
	Swap(i1,i2);
	
	char a='0';
	char b='9';
	Swap(a,b);
	
	return 0;
}

理解:

在编译器编译阶段,对于函数模板的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符,整型……等类型都是如此!


函数模板的实例化

概念:

用不同类型的参数使用函数模板时,称为函数模板的实例化!

模板的实例化其实就是通过参数类型推演出所需类型的函数这就是模板的实例化!


模板实例化分为:

隐式实例化  和 显式实例化


隐式实例化

隐式实例化:

让编译器根据实参推演模板参数的实际类型


代码:

#include <iostream>
using namespace std;

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


int main()
{
    int a1 = 10, a2 = 20;    
	double d1 = 10.1, d2 = 20.0;    
	cout<<Add(a1, a2)<<endl;   
	cout<<Add(d1, d2)<<endl;
	
	
	//Add(a1,d1);//该语句无法编译通过
	 /*     
	 该语句不能通过编译,因为在编译期间,
	 当编译器看到该实例化时,需要推演其实参类型    
	通过实参a1将T推演为int,
	通过实参d1将T推演为double类型,
	但模板参数列表中只有一个T,     
	编译器无法确定此处到底该将T确定为int 或者 double类型而报错    
	注意:在模板中,编译器一般不会进行类型转换操作,
	     因为一旦转化出问题,编译器就需要背黑锅     
		 Add(a1, d1);    */ 
		 
	//此时有两种处理方式
	// 1.用户自己来强制转换
	// 2.使用显式实例化 
	cout<<endl;
	
	Add(a1,(int)d1); 
	cout<<Add(a1,(int)d1)<<endl;
	double s1 = Add((double)a1,d1);
	cout<<s1<<endl;
	
	return 0;
}

显式实例化

显式实例化:

在函数名后的<>指定模板参数的实际类型

可以用显示实例化解决上述代码中的问题


代码:

#include <iostream>
using namespace std;

template <typename T>
T Add(const T& a,const T& b)
{
	return a+b;
}

int main()
{
	int a=10;
	double c=20.22;
	
	//不同类型可显式实例化
	Add<int>(a,c);
	cout<<Add<int>(a,c)<<endl;
	
	cout<<Add<double>(a,c)<<endl;
	 
	return 0;
}

注意:

如果类型不匹配,编译器会尝试进行隐式类型转换,若有强制类型转换,编译器会按照用户强转的类型去转换,若没有或者无法转换成功编译器将会报错!


模板参数的匹配原则

匹配原则:

1. 一个非模板函数,和一个同名的模板函数可以同时存在,且该模板还可以被实例化为这个非模板函数!


代码:

#include <iostream>
using namespace std;

//非模板函数,只针对int类型求和的函数 
int Add(const int& a,const int& b)
{
	return a+b;
}

//求和模板函数
template <typename T>
T Add(const T& left,const T& right)
{
	return left+right;	
}

int main()
{
	Add(1,2);//该函数会去匹配非模板函数
	
	Add<int>(1,2);//该函数会去调用模板(调用编译器特化的Add函数)
	 
	return 0;
}

2. 编译的角度:对于非模板函数与同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数,而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板!


代码:

#include <iostream>
using namespace std;

//专门用来int求和的函数
int Add(int a,int b)
{
	
	return a+b;
 } 
 
 //通用加法模板
template <typename T1,typename T2>//模板可以有多个模板参数 
T1 Add(const T1& left,const T2& right)
{
	return left+right;
} 

int main()
{
	    Add(1, 2);     // 与非函数模板类型完全匹配,不需要函数模板实例化    
		Add(1, 2.0);   // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数 
	return 0;
} 

3. 函数模板不允许自动类型转换,但普通函数可以进行自动类型转换


类模板

类模板的定义格式


定义一个动态顺序表模板

#include <iostream>
#include <assert.h>
using namespace std;

//动态顺序表
//注意: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;
 } 
 
template <class T>
//尾插 
void Vector<T>::PushBack(const T& data)
{
	_pData[_size]=data;
	++_size;		
}  

 int main()
 {
 	return 0;
 }

类模板的实例化

实例化:

类模板的实例化与函数模板的实例化不同,类模板实例化在定义对象时需要在类名后跟<>,然后将实例化的类型放在尖括号中即可,类模板名字不是真正的类,而实例化的结果才是真正的类!


代码样例:


代码:

#include <iostream>
#include <assert.h>
using namespace std;

//动态顺序表
//注意: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;
}

template <class T>
//尾插 
void Vector<T>::PushBack(const T& data)
{
	_pData[_size] = data;
	++_size;
}

int main()
{
	Vector<int> d1;
	Vector<double> d2;
	Vector<char> d3;

	return 0;
}

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

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

相关文章

使用纯C#语言实现4K图像平移显示性能的潜能测试

在介绍“熊猫视图.Net图形控件”系列文章中&#xff0c; 【“熊猫视图.Net图形控件”介绍链接】https://blog.csdn.net/mosangbike/article/details/126026801有对显示图像文件的测试结果&#xff0c;当时测试的不太严谨。今天抽时间详细测试了一下。 从网上找了一张Jpg图像作…

windows下安装配置 elasticsearch | kibana | analysis-ik

简介 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;支持Restful风格&#xff0c;可以帮助我们从海量的数据中快速找到用户所需要的内容。是当前最流的开源企业级搜索引擎&#xff0c;能够达到近实时搜索、稳定、可靠、快速、安装使用方便。 elasticsearch结合kibana、…

wireshark使用-(2)运行时自动分包

现如今的网络通信分析基本都逃不开wireshark这个工具&#xff0c;而运行长时间的网络包往往过大&#xff0c;我们只想关注指定时间段的网络包&#xff0c;所以分包就显得尤为重要&#xff0c;好在wireshark工具自带这个功能&#xff0c;方便运行时能自动按时间&#xff0c;按大…

05.JavaWeb-Servlet(上)

目录 1.Servlet基础 1.1 Servlet概述 1.2 Servlet开发入门 1.2.1 Servlet接口及实现类 1.2.2实现Servlet程序 2.Servlet的请求与响应 2.1HttpServletRequest 2.1.1获取请求参数&#xff08;常用&#xff09; 2.1.2 获取请求方法 2.1.3获取与请求关联的会话对象 2.1.…

Vue总结

这里写自定义目录标题 一、Vue基本结介绍1、Vue 项目示例2、Vue 开发工具3、HTML 基本结构4、HTML 常用标签二、vue常用指令1、v-model 双向绑定a、v-model 修饰符2、插值3、条件渲染1、v-if 和 v-show 区别4、v-on 简介5、属性绑定6、 v-for 简介三、vue环境安装1、Vue 脚手架…

node基础与fs模块学习笔记

了解Node.js与内置模块 什么是Node.js? Node.js is an open-source, cross-platform JavaScript runtime environment. node.js是一个开源跨平台的js运行环境。 前端的运行环境就是浏览器。 注意&#xff1a;Node.js中无法调用DOM和BOM等浏览器内置API。 Node.js中的顶级对象…

【大数据工具】Zookeeper 分布式集群和伪分布式安装

Zookeeper 安装 zookeeper 安装包下载地址&#xff1a;https://archive.apache.org/dist/zookeeper/ 1. 伪分布式部署 说明&#xff1a;伪分布即在一台服务器上通过不同端口模拟出分布式集群的效果&#xff0c;分布式一般 3 台起&#xff0c;一主两从。 说明&#xff1a;伪…

华为OD机试真题 Java 实现【斗地主之顺子】【2023 B卷 100分】,附详细解题思路

一、题目描述 在斗地主扑克牌游戏中&#xff0c;扑克牌由小到大的顺序为: 3.4.5.6.7.8.9.10.J.Q.K A.2&#xff0c;玩家可以出的扑克牌阵型有: 单张、对子、顺子、飞机、炸弹等。 其中顺子的出牌规则为: 由至少 5 张由小到大连续递增的扑克牌组成&#xff0c;且不能包含 2。 …

RK1126 NPU yolov5 6.2

基于 rk npu &#xff0c; 实现 yolov5 6.2 模型推理 实现过程 ⚡️​ 编译 opencv 需根据自己路径修改. cmake -D CMAKE_BUILD_TYPERELEASE \-D CMAKE_C_COMPILER./gcc-arm-8.3-2019.02-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc \-D CMAKE_CXX_COMPILER./gc…

VMware虚拟机Ubuntu磁盘空间扩充详细教程

文章目录 一、写在前面二、具体步骤三、最后总结 一、写在前面 最近在做Linux内核相关实验的时候&#xff0c;发现有时候我们编译出来的内核太大&#xff0c;如果VMware虚拟机空间分配不足会导致编译Linux内核失败&#xff0c;经过摸索&#xff0c;发现可以扩充Ubuntu的磁盘空间…

【Rust日报】2023-06-05 Effective Rust: 35种提升 Rust 代码的方法

Effective Rust: 35种提升Rust代码的方法 这是一本关于 Rust 的最佳实践指南的电子书&#xff0c;其中包含了很多 Rust 编程的技巧和建议。 这篇指南分为多个章节&#xff0c;涵盖了 Rust 编程的各个方面, 他分别从 类型, 概念, 依赖, 工具 等几大类上阐述了 35 种提高 Rust 代…

记录--你真的能区分JavaScript的各种导入导出方式吗?

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 &#x1f6f0;&#x1f6f0; 我们在无论是在查阅别人的代码&#xff0c;还是在实际项目开发的过程中&#xff0c;肯定都会使用导入导出的功能&#xff0c;有时候我们会搞混这几种方式到底有什么区…

有哪些比较好的游戏图标推荐

游戏图标设计在游戏UI中占有非常重要的地位。例如&#xff0c;当我们看到一个游戏的启动图标时&#xff0c;很容易区分它是哪个游戏。设计游戏图标不仅是一个图形&#xff0c;也是一个标志。 本文将通过各种游戏图标设计素材分享游戏图标的类别和设计游戏图标的思考。 1. 游戏…

基于SSM+JSP的大学生校园兼职系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

wsl连接usb设备,以及挂载u盘

目录 wsl连接usb设备 安装usbinpd-win 配置 在kali中做写的配置 在wsl中的配置 在kali中查看附加结果 报错 挂载u盘&#xff0c;查看u盘的内容 wsl连接usb设备 安装usbinpd-win winget install --interactive --exact dorssel.usbipd-win 配置 在kali中做写的配置 s…

Java 21 将不再有public static void main !

之前的Java是这样&#xff1a; Java 21会是这样&#xff1a; 看到这个消息&#xff0c;我最大的感受是&#xff1a; 卧槽&#xff0c;怎么就Java 21了&#xff1f;&#xff01;我还在用Java 8 呢&#xff01; 实际上&#xff0c;从Java 8 到Java 21&#xff0c;中间也就是Java…

“Pull”和“Push”支付的区别

下图展示了“Pull”和“Push”支付的工作原理。 当我们在商家处刷信用卡/借记卡时&#xff0c;这是一种“Pull”支付&#xff0c;资金从持卡人的账户发送到商家。商家从持卡人的账户中取款&#xff0c;而持卡人批准交易。 通过Visa Direct或Mastercard Send&#xff0c;推送支付…

WebRTC实战 | 视频云

前言 WebRTC是当前实时通信领域的重要技术之一&#xff0c;具有广泛的应用前景。可以实现音频、视频和数据的实时传输。支持点对点通信、多方会议、屏幕共享等多种应用场景&#xff0c;同时具有高质量、低延迟、强安全性等特点&#xff0c;是开发实时通信应用的理想选择。可以应…

从“嘿,Siri”到元宇宙,苹果还有多远距离要走?

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 在当前的科技浪潮中&#xff0c;元宇宙的概念凭借其无限的想象力和可能性&#xff0c;像一颗耀眼的新星吸引着世界的目光。巨大的发展潜力&#xff0c;吸引着一波又一波的企业&#xff0c;前赴后继地向元宇宙发起冲锋。 在这…

27.打包项目

目录 1 打包成dist 2 flask配合dist 3 以文件形式打开 1 打包成dist 项目路径下输入 npm run build 在项目路径下会出现一个dist 打包后直接双击打开index.html是没有用的 因为你双击打开是file协议&#xff0c;你需要一个http协议 2 flask配合dist 我们可以搞一个fla…