C++模板从入门到入土

news2024/11/16 22:42:35

1. 泛型编程  

如果我们需要实现一个不同类型的交换函数,如果是学的C语言,你要交换哪些类型,不同的类型就需要重新写一个来实现,所以这是很麻烦的,虽然可以cv一下,有了模板就可以减轻负担。

下面写一个适合所有类型的交换就可以这样写。

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.0, d2 = 2.2;
	swap(a1, a2);
	swap(d1, d2);
	return 0;
}

让我们先从文字上来了解什么是泛型编程,泛型指的是广泛类型的意思。

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

问题:我们其实如果用函数重载也能解决问题,但是为什么我们还是有模板这个东西呢?

1.重载的只是函数类型不同,代码相同的部分很多,代码复用率很高。
2.如果有一行代码是有问题的话,这些重载的代码都是需要修改的
那我们就可以给编译器一个例子,然后让编译器自己去生成,就像古代的磨具一样,我们再磨具上印出东西,然后就拿的这个磨具去印出相同的东西,这不是很方便的东西。

函数模板

1.函数模板的概念

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

2.函数模板的格式

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

我们可以拿Swap这个例子来模拟

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

1.template是关键字

2.typename是修饰后面T的关键字,也有class这个关键字,class这个关键字比较短,所以我们用这个比较多。
3.T1, T2, ..., Tn 表示的是函数名,可以理解为模板的名字,名字你可以自己取。

注意事项:函数模板不是一个函数,而是我们的编译器拿的这个函数模板去实例化出一个一个的函数来的,我们可以理解为函数的模板

函数模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模 板就是将本来应该我们做的重复的事情交给了编译器

 

#include<iostream>

using namespace std;
template<class T>
void Swap(T& x, T& y)
{
	T tmp(x);
	x = y;
	y = tmp;
}
int main()
{
	int x = 1;
	int  y = 2;
	double d1 = 2.2;
	double d2 = 3.3;
	cout << "交换前->" << x << " " << y << endl;
	cout << "交换前->" << d1 << " " << d2 << endl;
	Swap(x, y);
	Swap(d1, d2);
	cout << "交换后->" << x << " " << y << endl;
	cout << "交换后->" << d1 << " " << d2 << endl;
	return 0;
}

我们可以看到的是我们的数据也是成功的进行交换了,那我们来想想他的原理是什么呢,首先编译器是会根据函数模板生成不同的函数,他们的类型是不同的。而且他们的函数栈帧不是同一个。

 编译器是会根据这个函数模板去实例化不同的函数出来,所以在函数栈帧上调用的不是同一个函数栈帧,我们也可以来看汇编代码,看看call的地址是不是同一个地址。

所以可以看出我们不是调用的用一个函数。

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

那我们下面就来探讨编译器是怎么进行实例化的。

 

函数模板的实例化

 其实过程是很简单的,我们在编译阶段的时候,告诉我们的函数模板你要去根据类型进行实例化,然后因为T是函数模板的参数,所以如果我们传int过去的时候他就知道T是int,所以的T改成int去实例化出一个函数出来。

但是函数模板在实例化的过程中也是会出现问题的,比如我们可以来下面的这种情况,我们下一个简单的Add函数模板,然后在main函数里面进行相加计算出结果,我们可以来看看如果不是同一个类型的化会出现怎样的问题。

#include<iostream>

using namespace std;
template<class T>
T Add(const T& x, const T& y)
{
	return x + y;
}

int main()
{
	int x = 1;
	int  y = 2;
	double d1 = 2.2;
	double d2 = 3.3;
	int ret1 = Add(x, y);
	double ret2 = Add(d1, d2);
	cout << ret1 << " " << ret2;
	return 0;
}

首先这样的代码是没有问题的,但是如果我们是x+d1呢,我们来看看他的报错信息。、

 

如果是这样写的化报错信息就是这个样子的,所以我们需要怎么进行修改才行呢。

 显式实例化:在函数名后的<>中指定模板参数的实际类型

没错,我们是需要进行显示实例化的,但是我们应该如何进行显示实例化呢,规则很简单。

上面的Add就可以写成。

Add<int>(x,d1);

 我们的代码是可以运行的,但是会有这样的警告,其实是可以忽略的,因为我们本生就是不同类型的相加,肯定会产生强转的。

对于模板函数的使用,编译器需要根据传入的实参类型来推演,生成对应类型的函数以供调用。但是我们可以显示的去实例化,规则和Add是一样的道理。 

像第一个 Add<int>(a1, a2)  ,a2 是 double,它就要转换成 int 。

第二个 Add<double>(a1, a2),a1 是 int,它就要转换成 double。

这种地方就是类型不匹配的情况,编译器会尝试进行隐式类型转换。

像 double 和 int 这种相近的类型,是完全可以通过隐式类型转换的。

🔺 总结:

  • 函数模板你可以让它自己去推,但是推的时候不能自相矛盾。
  • 你也可以选择去显式实例化,去指定具体的类型。

 模板参数的匹配原则

场景:我们会写一个关于Add的函数模板和实现一个Add的函数,类型是int那他到底会配对那个呢。

#include<iostream>

using namespace std;
template<class T>
T Add(const T& x, const T& y)
{
	return x + y;
}
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int x = 1;
	int  y = 2;
	double d1 = 2.2;
	double d2 = 3.3;
	int ret1 = Add(x, y);
	double ret2 = Add(d1, d2);
	Add<int>(x, d1);
	cout << ret1 << " " << ret2;
	return 0;
}

就是像这样的场景,那我们如果函数是Add(int ,int)的时候是调用哪个呢。

规则:有现成的就用现成的呗,我们函数模板进行实例化是要根据类型去实例化的,但是我们已经有一个关于它的函数了,这个函数是最适合你的,你还要去生成一个,都多余了。

 

所以我们就不会再去麻烦编译器去再生成一个函数来实现了。

总结:

① 一个非模板函数可以和一个同名的模板函数同时存在,

而且该函数模板还可以被实例化为这个非模板函数:

② 对于非模板函数和同名函数模板,如果其他条件都相同,

在调用时会优先调用非模板函数,而不会从该模板生成一个实例。

如果模板可以产生一个具有更好匹配的函数,那么将选择模板。

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

 

第三点解释一下,就是我们再根据模板生成的时候,只会根据你给的类型去生成,而不存在强转这些,除非是隐式类型转换,隐式类型转换是会存在强转的可能性的。

类模板

1 类模板的定义格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{
 // 类内成员定义
};

 规则其实和函数模板是差不多的。

1.template是关键字

2.typename是修饰后面T的关键字,也有class这个关键字,class这个关键字比较短,所以我们用这个比较多。
3.T1, T2, ..., Tn 表示的是函数名,可以理解为模板的名字,名字你可以自己取。

这里要强调一下函数模板和类模板都是不支持分离声明和定义的,你可以再同一个文件里,但是不能在不同的文件进行声明和定义(指的是在一个.h进行声明,在一个.cpp进行定义)这个情况是会我们程序进行链接的时候出现找不到这个地址的现象,因为我们的模板函数是不知我们要实例化的类型是什么,所以就会出现最后链接的时候Call(没地址),所以就会链接错误,后面会深入的讲解。

继续回归我们对类模板的认识,首先是引出问题,我们没有类模板的栈是怎么写的。来看看吧。

class Stack {
public:
	Stack(int capacity = 4) 
		: _top(0) 
		, _capacity(capacity) {
		_arr = new int[capacity];
	}
	~Stack() {
		delete[] _arr;
		_arr = nullptr;
		_capacity = _top = 0;
	}
private:
	int* _arr;
	int _top;
	int _capacity;
};

这个栈是只能来存int,有人就会说,如果我们对int进行typedef不就行了,如果我想要其他类型的时候就只需要改类型就行了,但是这样就有了第二个问题,那就是如果我们需要的是一个存放int的栈,一个存放的是node* 节点的栈,或者一个日期的时候,那问题就很大了,每当我们需要这个类型的时候就需要ctrl c + v然后改一下类型这个操作其实很简单,也很快,但是最终结果就是造成代码相同的还是很多,这样和我们之前的函数模板是一样的问题,所以就有了我们的类模板,那我们来改造一下上面的代码吧。

template<class T>
class Stack {
public:
	Stack(int capacity = 4) 
		: _top(0) 
		, _capacity(capacity) {
		_arr = new T[capacity];
	}
	~Stack() {
		delete[] _arr;
		_arr = nullptr;
		_capacity = _top = 0;
	}
private:
	T* _arr;
	int _top;
	int _capacity;
};
 
int main(void)
{
	Stack<int> st1;   // 存储int
	Stack<double> st2;   // 存储double
 
	return 0;
}

这样就可以解决了我们要存放int和double或者其他类型的问题了。

但是我们发现,类模板他好像不支持自动推出类型,

 它不像函数模板,不指定它也可以根据传入的实参去推出对应的类型的函数以供调用

这就是为什么我们需要在类模板后面根生类型,这里大家就要记住的是类模板必须要显示实例化的方法写,它不能像函数模板一样去推演类型。

类模板实例化

模板实例化在类模板名字后跟 < >,然后将实例化的类型放在 < > 中即可。

注意事项:

① Stack 不是具体的类,是编译器根据被实例化的类型生成具体类的模具。

② Stack 是类名,Stack<int> 才是类型:

我们上面说过类模板不能在两个文件里声明和定义分离,但是没说不能在同一个文件了,但是在同一个文件里有些讲究,我们得来探究一下。

就继续拿我们栈来说话。

#include<iostream>

using namespace std;
template<class T>
class Stack {
public:
	Stack(int capacity = 4)
		: _top(0)
		, _capacity(capacity) {
		_arr = new T[capacity];
	}
	// 这里我们让析构函数放在类外定义
	void Push(const T& x);
	~Stack();
private:
	T* _arr;
	int _top;
	int _capacity;
};

/* 类外 */

void Stack::Push(const T& x) {
	//::::
}

 如果我们是这样写的化就是会存在一些小的问题,编译器是不认识外面的这个T,那我们要改的话是需要在下面函数上加上模板的参数的,

 

template<class T>
class Stack {
public:
	Stack(T capacity = 4)
		: _top(0)
		, _capacity(capacity) {
		_arr = new T[capacity];
	}
	// 这里我们让析构函数放在类外定义
	void Push(const T& x);
	~Stack();
private:
	T* _arr;
	int _top;
	int _capacity;
};

/* 类外 */
template<class T>
void Stack<T>::Push(const T& x) {
	//::::
}

虽然是能编译通过,但是链接的时候又是会存在问题的,所以我的建议就是大家声明和定义都放在类模板里,多一事不如少一事。

对于这个需要记住的是----------> Stack 是类名,不是类型,Stack<T> 才是类型! 

初阶模板就分享到这里了,我们后面还有进阶的模板,今天的分享就到这里了,下次再见了~

 

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

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

相关文章

Windows下搭建EFK实例

资源下载 elasticSearch &#xff1a;下载最新版本的就行 kibana filebeat&#xff1a;注意选择压缩包下载 更新elasticsearch.yml&#xff0c;默认端口9200&#xff1a; # Elasticsearch Configuration # # NOTE: Elasticsearch comes with reasonable defaults for most …

CSB ---> (XXE)XML基础

本来今天想更一下CSbeacon上线多层的内网机器的&#xff0c;但是刚好今天是年后的第一节课&#xff0c;讲的是XXE的基础&#xff0c;那就来先盘一下基础&#xff01;&#xff01; 1.XXE XXE全称是XML External Entity即xml外部实体注入攻击&#xff01;其后果会导致用户…

UE C++ 设置碰撞前 后事件 碰撞中事件

一.在Actor中声明碰撞BOX组件 UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category "MySceneComponent")class UBoxComponent* MyBox; 在Actor以这样的形式实现代理绑定&#xff0c;在BeginPlay()里。 MyBox->OnComponentBeginOverlap.AddDynamic(); 转到…

《游戏引擎架构》--学习3

内存管理 优化动态内存分配 维持最低限度的堆分配&#xff0c;并且永不在紧凑循环中使用堆分配 容器 迭代器 Unicode

(C++) 详解内存地址空间

详解内存空间 0. 概述 一个C/C 程序&#xff0c;编译之后&#xff0c;形成的程序&#xff0c;在执行期间&#xff0c;内存中不仅存在一块区域用于存放代码&#xff0c;还有一些其他的区域用于使用&#xff0c;本节会详解C/C内部所使用的内存地址空间&#xff0c;关于各内存的…

Java下访问SQLServer 2008(低于2016)数据连接问题

环境&#xff1a;ubuntu20.04&#xff0c;tomcat&#xff0c;java 通过jdbc:sqlserver连接远程的SQLServer 2008R2数据库&#xff0c;2016版本之前仅支持TLS10&#xff0c;因此在连接时会出现如下错误&#xff0c; The driver could not establish a secure connection to SQ…

(二十二)Flask之上下文管理第三篇【收尾—讲一讲g】

目录&#xff1a; 每篇前言&#xff1a;g到底是什么&#xff1f;生命周期在请求周期内保持数据需要注意的是&#xff1a; 拓展—面向对象的私有字段深入讲解一下那句&#xff1a; 每篇前言&#xff1a; &#x1f3c6;&#x1f3c6;作者介绍&#xff1a;【孤寒者】—CSDN全栈领域…

Java项目:21 基于SSM实现的图书借阅管理系统

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 基于SSM实现的图书借阅管理系统设计了两个角色&#xff0c;分别是管理员、用户&#xff0c;在数据表user中以ident字段区分&#xff0c;为1表示管理员…

Vue中如何使用dayjs

Day.js中文网Day.js是一个极简的JavaScript库&#xff0c;可以为现代浏览器解析、验证、操作和显示日期和时间。https://dayjs.fenxianglu.cn/ 单位不区别大小写&#xff0c;支持复数和缩写形式 单位缩写描述 date D日期 [1,31]dayd星期 [0,6]&#xff08;星期日0&#xff0c…

【开源】JAVA+Vue.js实现超市账单管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统设计3.1 总体设计3.2 前端设计3.3 后端设计在这里插入图片描述 四、系统展示五、核心代码5.1 查询供应商5.2 查询商品5.3 新增超市账单5.4 编辑超市账单5.5 查询超市账单 六、免责说明 一、摘要 1.1 项目介绍 基于…

尾矿库排洪系统结构仿真软件WKStruc(可试用)

1、背景介绍 尾矿库作为重大危险源之一&#xff0c;在国际灾害事故排名中位列第18位&#xff0c;根据中国钼业2019年8月刊《中国尾矿库溃坝与泄漏事故统计及成因分析》的统计&#xff0c;在46起尾矿库泄漏事故中&#xff0c;由于排洪设施导致的尾矿泄漏事故占比高达1/3&#x…

【SpringCloud】使用 Spring Cloud Alibaba 之 Sentinel 实现微服务的限流、降级、熔断

目录 一、Sentinel 介绍1.1 什么是 Sentinel1.2 Sentinel 特性1.3 限流、降级与熔断的区别 二、实战演示2.1 下载启动 Sentinel 控制台2.2 后端微服务接入 Sentinel 控制台2.2.1 引入 Sentinel 依赖2.2.2 添加 Sentinel 连接配置 2.3 使用 Sentinel 进行流控&#xff08;含限流…

【LeetCode: 105. 从前序与中序遍历序列构造二叉树 + DFS】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

【精选】Java面向对象进阶——内部类

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

设计师常常从哪些网站获取灵感?

1、Pinterest Pinterest是一个基于图片共享的社交网站。用户可以在平台上浏览、收集和分享各种想法、设计灵感和项目。Pinterest用户可以在其网站或应用程序上创建虚拟画板&#xff08;boards&#xff09;&#xff0c;根据主题或兴趣收集和整理你最喜欢的图片&#xff08;包括…

RxJS 核心原理-操作符(源码实现)

本文将深入探讨RXJS操作符的核心原理&#xff0c;并介绍一些常见的操作符、应用场景以及相应的代码示例。通过理解RXJS的响应式编程思想&#xff0c;您将能够更好地应用它来处理异步数据流和事件流。 建议读者在阅读本文之前&#xff0c;先参考我的另一篇文章《深入浅出 RxJS …

IDM(Internet Download Manager)2024免激活绿色版下载

IDM&#xff08;Internet Download Manager&#xff09;在安全保护方面提供了多种功能和策略&#xff0c;以确保用户的下载体验和数据安全。以下是一些IDM的安全保护功能和策略&#xff1a; IDM绿色下载如下: https://wm.makeding.com/iclk/?zoneid34275 病毒扫描功能&#…

Spring之AOP源码解析(中)

前言 在上一篇文章中,我们讲解了Spring中那些注解可能会产生AOP动态代理,我们通过源码发现,完成AOP相关操作都和ProxyFactory这个类有密切关系,这一篇我们将围绕这个类继续解析 演示 作用 ProxyFactory采用策略模式生成动态代理对象,具体生成cglib动态代理还是jdk动态代理,…

FairyGUI × Cocos Creator 3.x 使用方式

前言 上一篇文章 FariyGUI Cocos Creator 入门 简单介绍了FairyGUI&#xff0c;并且按照官方demo成功在Cocos Creator2.4.0上运行起来了。 当我今天使用Creator 3.x 再引入2.x的Lib时&#xff0c;发现出现了报错。 这篇文章将介绍如何在Creator 3.x上使用fgui。 引入 首先&…

想把握现货黄金价格走势 这两点能助你掌握优势

现货黄金价格走势很是狡猾&#xff0c;想要抓准它的波动是很难的。但我们作为投资者&#xff0c;偏要与它较劲。想要像捕捉猎物一样捕捉到现黄金价格走势&#xff0c;以下这两点投资者要注意。 了解清楚自己的风格适合什么市场&#xff1f;投资者有比较固定的投资风格&#xff…