【C++初阶】初识模板

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、 泛型编程
  • 二、函数模板
    • 2.1 函数模板概念
    • 2.2 函数模板格式
    • 2.3 例子演示
    • 2.4 函数模板的原理
    • 2.5 函数模板的实例化
      • 2.5.1 概念
      • 2.5.1 隐式实例化
      • 2.5.2显示实例化
  • 三、类模板
    • 3.1 类模板的定义
    • 3.2 为什么会有类模板(实际作用?)
    • 3.3 例子演示
    • 3.4 类模板的实例化
    • 3.5 类模板的声明和定义分离

一、 泛型编程

假设在一个项目中,要交换intchardouble等类型的数据,就要写出至少3个类型交换函数:

#include <iostream>
using namespace std;

void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

void Swap(double& x, double& y)
{
	double tmp = x;
	x = y;
	y = tmp;
}

void Swap(char& x, char& y)
{
	char tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 1, b = 2;
	Swap(a, b);
	cout << "a = " << a << ' ' << "b = " << b << endl;

	double c = 1.1, d = 2.2;
	Swap(c, d);
	cout << "c = " << c << ' ' << "d = " << d << endl;


	char e = 'a', f = 'b';
	Swap(e, f);
	cout << "e = " << e << ' ' << "f = " << f << endl;

	return 0;
}

【程序结果】

在这里插入图片描述

以上代码就使用 函数重载,虽然可以实现交换逻辑,但是有一下几个不好的地方:

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

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

答案当然是可以的!C++泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。

二、函数模板

2.1 函数模板概念

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

2.2 函数模板格式

 typename -- 类型名(照抄)
 Tn -- 变量名(名字可以随便取)

template<typename T1, typename T2, ......, typename Tn>
// 或者还能用class
template<classT1, classT2, ......, class Tn>

返回值类型 函数名(参数列表) 
{
	// 代码逻辑
}

注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)。并且Tn也能做返回值。

2.3 例子演示

例如用函数模板把以上三个交换函数改成:

#include <iostream>
using namespace std;

template<typename T>
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}


int main()
{
	int a = 1, b = 2;
	Swap(a, b);
	cout << "a = " << a << ' ' << "b = " << b << endl;

	double c = 1.1, d = 2.2;
	Swap(c, d);
	cout << "c = " << c << ' ' << "d = " << d << endl;


	char e = 'a', f = 'b';
	Swap(e, f);
	cout << "e = " << e << ' ' << "f = " << f << endl;

	return 0;
}

【程序结果】

在这里插入图片描述

2.4 函数模板的原理

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

我们可以通过反汇编来查看其底层原理:

在这里插入图片描述

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

2.5 函数模板的实例化

2.5.1 概念

函数模板根据调用,编辑器会自己推导模板参数的类型,实例化出对应的函数,这称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化

2.5.1 隐式实例化

让编译器来推演模板参数的实际类型。

【例子】

#include <iostream>
using namespace std;

template<typename T>

T Add(const T& x, const T& y)
{
	return x + y;
}

int main()
{
	int a = 1, b = 2;
	double d1 = 1.1, d2 = 2.2;

	cout << Add(a, b) << endl;
	cout << Add(d1, d2) << endl;

	return 0;
}

【程序结果】

在这里插入图片描述

在以上代码中以Add(a, b)为例,ab在传参时,编辑器就推段出Tint类型。所以代码在编辑器的眼里是 以下这样的:

int Add(const int& x, const int& y)
{
		return x + y;
}

那现在假设是a + d1结果会是如何呢?

在这里插入图片描述

答案已经很明显了,当然不行!原因是:在编译期间,当编译器看到该函数实例化时,通过实参a推演T的类型为int,通过实参d1推演T的类型为double,然而模板参数列表只有一个T,因此编译器无法确定Tint还是double,故导致参数T不明确。

有两种方法可以解决以上问题

  • 第一种:用户自己强制转化
// 输出的答案是int类型
cout << Add(a, (int)d1) << endl;

// 输出的答案是double类型
cout << Add((double)a, d1) << endl;

【程序结果】

在这里插入图片描述

  • 第二种方法就要涉及显示实例化

2.5.2显示实例化

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

// 输出的答案是int类型
cout << Add<int>(a, d1) << endl;

// 输出的答案是double类型
cout << Add<double>(a, d1) << endl;

【程序结果】

在这里插入图片描述

编译器会尝试进行 隐式类型转换,如果无法转换成功编译器将会报错。

但显示实例化在实现中并不常用,但以下场景是最经典的

假设要写一个函数,要求是返回一块动态开辟的空间

template<typename T>

T* New(int n)
{
	return new T[n];
}

int main()
{
	New(10); // 这是错误的

	return 0;
}

【程序结果】

**加粗样式**

New(10)是错误的。原因是:实参传给形参会推演类型,然而形参并没有T类型接收,就导致了编译器无法推导T的类型。

正确的方法是用显示实例化

int main()
{
	// 显示实例化
	int* p = New<int>(10); 

	delete p;

	return 0;
}

三、类模板

3.1 类模板的定义

// 定义格式

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

class 类模板名
{
	// 类内成员定义
};

3.2 为什么会有类模板(实际作用?)

假设要求每次实例化出的对象所存储的是不同类型的数据,那么有的人就会写n次不同类型的类,这无疑是增加了代码量(如下所示)。因此就有了类模板,编译器同样是根据实参推演出T的类型。

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

typedef int DataType;
class StackInt
{
public:
	StackInt(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
			return;

		_capacity = capacity;
		_size = 0;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

	~StackInt()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};

typedef double DataType;
class StackDouble
{
public:
	StackDouble(size_t capacity = 3)
	{
		_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++;
	}

	// 其他方法...

	~StackDouble()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};

int main()
{
	StackInt s1; // int
	StackDouble s2; // double

	return 0;
}

3.3 例子演示

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

template<class T>

class Stack
{
public:
	Stack(int capacity = 3)
	{
		a = (T*)malloc(sizeof(T) * capacity);
		if (NULL == a)
		{
			return;
		}

		capacity = capacity;
		size = 0;
	}

	void Push(T data)
	{
		a[size] = data;
		size++;
	}
private:
	T* a;
	int capacity;
	int size;
};

3.4 类模板的实例化

类模板实例化与函数模板实例化不同, 类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,注意:类模板名字不是真正的类,而实例化的结果才是真正的类

int main()
{
	// 类模板的实例化
	Stack<int> s1;    // int
	Stack<double> s2; // double
	Stack<char> s3;   // char
}

3.5 类模板的声明和定义分离

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

template<class T>

class Stack
{
public:
	Stack(int capacity = 3);

	void Push(T data);

private:
	T* a;
	int capacity;
	int size;
};

template<class T>
Stack<T>::Stack(int capacity)
{
	a = (T*)malloc(sizeof(T) * capacity);
	if (NULL == a)
	{
		return;
	}

	capacity = capacity;
	size = 0;
}

template<class T>

void Stack<T>::Push(T data)
{
	a[size] = data;
	size++;
}

需要注意的是:

  • 普通类,类名和类型是一样
  • 类模板,类名和类型不一样

因此以上代码中,类名:Stack; 类型:Stack<T>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

地理空间数据云管理-四维轻云1.3.2-版本更新内容

很多用户想要在不上传数据的情况下查看案例数据&#xff0c;我们在四维轻云1.3.2版本中为新用户新增了示例项目。此外&#xff0c;此次更新还新增了标绘数据模式和场景定时保存提醒功能&#xff0c;优化了插件启动流程以及移动和旋转变化轴。 1、新增示例项目 示例项目中包含…

Ubuntu 登录提示信息`Message of The Day`(MOTD)定制与开关

一、效果 登录Ubuntu的时候&#xff0c;在控制台可能会弹出一系列提示消息&#xff0c;有欢迎消息、系统信息、更新信息等等&#xff1a; 这些提示消息被称为Message of The Day&#xff0c;简称MOTD。 Ubuntu与其它Linux版本不太一样&#xff0c;它引入了MOTD 的概念。 这些…

健康管理系统开发笔记

健康管理系统 项目介绍功能架构项目结构maven项目搭建 项目介绍 健康管理系统是一款应用于健康管理机构的业务系统&#xff0c;实现健康管理机构工作内容 可视化、会员管理专业化、健康评估数字化、健康干预流程化、知识库集成化&#xff0c;从而提 高健康管理师的工作效率&a…

Intradeco通过适用于Excel的Liquid UI自动执行SAP MM并节省80%的处理时间

背景 Intradeco为服装制造提供整体方法&#xff0c;涵盖所有阶段&#xff1a;从构思阶段到最终产品分销。它已发展成为一家全球垂直制造公司&#xff0c;客户遍布美国、墨西哥和加拿大。 挑战 提高运营效率 原因&#xff1a;人员必须浏览多个 SAP 事务才能为新材料创建采购订单…

面向对象——权限修饰符、匿名内部类

package关键字 为什么要有包&#xff1f; 将字节码&#xff08;.class&#xff09;进行分类存放 包其实就是文件夹 包的定义及注意事项 定义包的格式 package 包名 多级包用.分割&#xff0c;如package com.heima里面的.就是分隔符 定义包的注意事项 package语句必须是程序的第…

Dockerfile详解

Dockerfile是什么 Dockerfile就是一个纯文本&#xff0c;里面记录了一系列的构建指令&#xff0c;如选择基础镜像、拷贝文件、运行脚本等等&#xff0c;RUN, COPY, ADD指令都会生成一个 Layer&#xff0c;而 Docker 顺序执行这个文件里的所有步骤&#xff0c;最后就会创建出一…

分布式系统概念和设计——分布式事务

分布式系统概念和设计 分布式事务 访问多个服务器管理的对象的事务称为分布式事务。 当一个分布式事务结束时&#xff0c;事务的原子特性要求所有参与事务的服务器必须全部提交或全部放弃。 实现&#xff1a; 其中一个服务器承担了协调者的角色&#xff0c;保证在所有的服务器…

“微商城”项目(5登录和注册)

1.我的信息 在pages\User.vue文件中编写HTML结构代码&#xff0c;示例代码如下。 <template><div class"member"><div class"header-con"><router-link :to"{ name: login }" class"mui-navigate-right">&l…

Spring Cloud Alibaba - Sentinel(一)

目录 一、Sentinel介绍 1、什么是Sentinel 2、Sentinel好处 3、Sentinel下载和安装 二、搭建Sentinel项目 1、创建项目cloudalibaba-sentinel-service8401 三、Sentinel流控规则 1、流控规则基本介绍 2、新增流控 2.1、QPS直接失败案例 2.2、线程数直接失败案例 3、…

容器(第一篇)docker安装、基础操作命令

docker是什么&#xff1f; docker是一个go语言开发的应用容器引擎。 docker的作用&#xff1f; ①运行容器里的应用&#xff1b; ②docker是用来管理容器和镜像的一种工具。 容器 与 虚拟机 的区别&#xff1f; 容器 虚拟机所有容器共享宿主机…

【论文阅读】Neuralangelo:高保真神经表面重建

【论文阅读】Neuralangelo&#xff1a;高保真神经表面重建 Abstract1. Introduction2. Related work3. Approach3.1.预备工作3.2.数值梯度计算3.3.渐进细节层次3.4.优化 4. Experiments4.1. DTU Benchmark4.2. Tanks and Temples4.3.细节水平4.4.消融 5. Conclusion paper proj…

深度学习基础知识-tf.keras实例:衣物图像多分类分类器

参考书籍&#xff1a;《Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow, 2nd Edition (Aurelien Geron [Gron, Aurlien])》 本次使用的数据集是tf.keras.datasets.fashion_mnist&#xff0c;里面包含6w张图&#xff0c;涵盖10个分类。 import tensorflo…

Jmeter性能测试 (入门)

Jmeter是一款优秀的开源测试工具&#xff0c; 是每个资深测试工程师&#xff0c;必须掌握的测试工具&#xff0c;熟练使用Jmeter能大大提高工作效率。 熟练使用Jmeter后&#xff0c; 能用Jmeter搞定的事情&#xff0c;你就不会使用LoadRunner了。 本文将通过一个实际的测试例…

IPV6地址基础

IPv6是英文“Internet Protocol Version 6”&#xff08;互联网协议第6版&#xff09;的缩写&#xff0c;是互联网工程任务组&#xff08;IETF&#xff09;设计的用于替代IPv4的下一代IP协议。其地址数量号称可以为全世界的每一粒沙子编上一个地址 1. ipv6地址表示方法 IPv6的…

MQTT与EMQ

文章目录 1 MQTT协议与EMQ中间件1.1 物联网消息协议MQTT1.1.1 什么是MQTT1.1.2 MQTT相关概念1.1.3 消息服务质量QoS——信息的可靠投递1.1.3.1 QoS0——消息服务质量为0&#xff0c;消息发送至多一次1.1.3.2 QoS1——消息发送至少一次1.1.3.3 QoS2——消息发送仅一次1.1.3.4 不…

Oracle中的数据导出(4)

目录 法一&#xff1a;使用SQL plus命令脚本 法二&#xff1a;使用PLSQL Developer工具 前几篇文章描述了如何将Oracle中的数据导出到库外&#xff0c;但是导出的数据结果都是文本文档&#xff0c;这样页面查看不和谐&#xff0c;编辑又略显麻烦。因此这篇文章将描述如何将Or…

Pb协议的接口测试

【摘要】 Protocol Buffers 是谷歌开源的序列化与反序列化框架。它与语言无关、平台无关、具有可扩展的机制。用于序列化结构化数据&#xff0c;此工具对标 XML &#xff0c;支持自动编码&#xff0c;解码。比 XML 性能好&#xff0c;且数据易于解析。更多有关工具的介绍可参考…

氟化物选择吸附树脂Tulsimer ®CH-87 ,锂电行业废水行业矿井水除氟专用树脂

氟化物选择吸附树脂 Tulsimer CH-87 是一款去除水溶液中氟离子的专用的凝胶型选择性离子交换树脂。它是具有氟化物选择性官能团的交联聚苯乙烯共聚物架构的树脂。 去除氟离子的能力可以达到 1ppm 以下的水平。中性至碱性的PH范围内有较好的工作效率&#xff0c;并且很容易再生…

Vue.js 中的过渡动画是什么?如何使用过渡动画?

Vue.js 中的过渡动画是什么&#xff1f;如何使用过渡动画&#xff1f; 在 Vue.js 中&#xff0c;过渡动画是一种在元素插入、更新或删除时自动应用的动画效果&#xff0c;可以为应用程序增加一些动态和生动的效果。本文将介绍 Vue.js 中过渡动画的概念、优势以及如何使用过渡动…

Nginx正则表达式、location、rewrite

目录 一、常用的Nginx正则表达式 二&#xff1a;localtion 1、location 分类 2、 location 常用的匹配规则 3、location 优先级 4、 location 示例 5、优先级总结 6、实际网站使用中&#xff0c;至少有三个匹配规则定义 &#xff08;1&#xff09;第一个必选规则 &…