【C++程序员的自我修炼】初识模板

news2024/11/16 8:17:43

云收天彩色

木叶落秋声


目录

函数模板

函数模板的实现 

函数模板的实例化

模板参数的匹配原则

 参数模板推不出来的情况

类模板

类模板的定义格式

类模板的实例化

契子 ✨ 

我们在学 C语言 的时候应该都写过交换两个数的函数 swap

当时我们只是写了 int 类型,那么如果是 char、double类型呢?是不是像这样:写多个 swap 函数

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

以上使用函数重载虽然可以实现,但是有一下几个不好的地方:

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

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

就像以下的杯子一样,只要确定好具体的模型,就可以填充自己想要的颜色

答案是有的,接下来我将带大家了解模板的基本操作:


函数模板

概念:

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

格式:

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

注意:typename 是用来定义模板参数关键字,也可以使用 class

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

函数模板的实现 

举个栗子~ 

#include<iostream>
using namespace std;

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

int main()
{
	int a = 1, b = 2;
	cout << "a = " << a << " " << "b = " << b << endl;
	Swap(a, b);
	cout << "a = " << a << " " << "b = " << b << endl;
	double c = 1.1, d = 2.2;
	cout << "c = " << c << " " << "d = " << d << endl;
	Swap(c, d);
	cout << "c = " << c << " " << "d = " << d << endl;
	return 0;
}

我们发现不管是 int 类型还是 double 类型都可以往里套,不仅如此 指针 类型也可以调用

	int a = 1, b = 2;
	int* pa = &a;
	int* pb = &b;

库里面的 swap

 ⭐我们发现库里面的 swap 函数的底层代码就是用模板!!!

就像我们古代的活字印刷术,名人的手稿诗集就是模板,通过模板我们可以印刷出各种类型

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

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

函数模板的实例化

用不同类型的参数使用函数模板时 ,称为函数模板的 实例化
模板参数实例化分为: 隐式实例化和显式实例
隐式实例化:让编译器根据实参推演模板参数的实际类型
显式实例化:在函数名后的<>中指定模板参数的实际类型

先举个栗子~

#include<iostream>
using namespace std;

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

int main()
{
	int a = 1, b = 2;
	double c = 1.1, d = 2.2;
	Add(a, b);
	Add(c, d);
	return 0;
}

以上的代码很简单~我们传了同类型的两个参数 int类型的a和bdouble类型的c和d

像这种能自动推演出 T 的类型的就是隐式实例化


那么我们能不能传不同类型的两个参数呢比如说:a和c

我们发现是不行的,原因如下:

在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过  实参a将T推演为int  类型,通过  实参c将T推演为double  类型,但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为 int 或者 double 类型而报错

 

那有没有什么解决办法呢?

方法一:用户自己来强制转化

Add(a, (int)c);
Add((double)a, c)

因为 double类型c 强转成 int类型 ,精度会丢~

方法二:显示实例化

Add<int>(a, c)
Add<double>(a, c)

显示实例化就是现将数据进行隐式类型转化成自己指定类型(不让编译器自动推演参数)

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

以上的:

<1> c 就是隐式类型转化成 double

<2>c 就是隐式转换成 int 

方法三:利用 auto 自动推导 

template<typename T1, typename T2>
auto Add(const T1& n, const T2& m)
{
	return n + m;
}

以上代码如果返回值是整数就会自动推导成 int ,是小数则自动推导成 double

这样精度就不会丢失~

模板参数的匹配原则

<1>一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函
<2> 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
#include<iostream>
using namespace std;

int Add(int left, int right)
{
	cout << "Add(int left, int right)" << endl;
	return left + right;
}

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

int main()
{
	Add(1, 2); 
	Add<int>(1, 2); 
	return 0;
}

 

简单来讲就是模板和用模板创造的函数 Add 可以同时存在,如果数据类型刚好和 Add 匹配,那么就可以直接用(不调用模板),如果不匹配可以隐式类型转换就可以将就用没有就用模板,当然如果我们想用模板的话,我们可以显示实例化

🌤️举个栗子~你想吃蛋糕了,如果有你喜欢吃的蛋糕肯定是有现成的吃现成的,如果没有可以将就一下吃别的,若是一个蛋糕都没有,就要求店员有模板做

 参数模板推不出来的情况

 举个栗子~我们来看看以下代码,Func(1)是否推演T成功

#include<iostream>
using namespace std;

template<class T>
T* Func(int a)
{
	T* p = (T*)operator new(sizeof(T));
	new(p)T(a);
	return p;
}

int main()
{
	int* ret = Func(1);
	return 0;
}

因为我们之前 T 的推演是编译器通过对实参类型的推演的,而这里我们的参数并不是 T 

那怎么办呢?

这里我们的显示实例化起到很大作用

int* ret = Func<int>(1);

编译器推导不出来,我们就自己指定类型

类模板

类模板的定义格式

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

举个栗子~我们之前写过的栈

#include<iostream>
#include<assert.h>
#include<cstdlib>
using std::cout;
using StackDataUsing = int;
class Stack
{
public:
	void Init(size_t newcapacity = 4)
	{
		StackDataUsing* newNode = new StackDataUsing[newcapacity];
		if (newNode == nullptr)
		{
			perror("new");
			exit(-1);
		}
		data = newNode;
		capacity = newcapacity;
		top = 0;
	}
	void Destory()
	{
		delete data;
		data = nullptr;
		top = capacity = 0;
	}
	void push(StackDataUsing x)
	{
		if (capacity == top)
		{
			StackDataUsing* newNode = new StackDataUsing[2 * capacity];
			if (newNode == NULL)
			{
				perror("new");
				exit(-1);
			}
			data = newNode;
			capacity *= 2;
		}
		data[top++] = x;
	}
	StackDataUsing Top()
	{
		return data[top - 1];
	}
	void Pop()
	{
		assert(top > 0);
		top--;
	}
	bool Empty()
	{
		return top == 0;
	}

private:
	StackDataUsing* data;
	size_t capacity;
	size_t top;
};

int main()
{
	Stack st1;  //int
	Stack st2; //double
	return 0;
}

 

我们之前想创建两个 double 、int  类型的栈还需要写两个类来进行,但是现在不必了

因为我们升级了~

#include<iostream>
#include<assert.h>
#include<cstdlib>
using std::cout;
template<class T>
class Stack
{
public:
	void Init(size_t newcapacity = 4)
	{
		T* newNode = new T[newcapacity];
		if (newNode == nullptr)
		{
			perror("new");
			exit(-1);
		}
		data = newNode;
		capacity = newcapacity;
		top = 0;
	}
	void Destory()
	{
		delete data;
		data = nullptr;
		top = capacity = 0;
	}
	void push(const T& x)
	{
		if (capacity == top)
		{
			T* newNode = new T[2 * capacity];
			if (newNode == NULL)
			{
				perror("new");
				exit(-1);
			}
			data = newNode;
			capacity *= 2;
		}
		data[top++] = x;
	}
	T Top()
	{
		return data[top - 1];
	}
	void Pop()
	{
		assert(top > 0);
		top--;
	}
	bool Empty()
	{
		return top == 0;
	}

private:
	T* data;
	size_t capacity;
	size_t top;
};

我们只要将原来 StackDataUsing 的位置置为 T 即可,T 就是对应的数据类型

类模板的实例化

类模板实例化与函数模板实例化不同, 类模板实例化需要在类模板名字后跟 <> ,然后将实例化的类型放在 <> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类
Stack<int> st1;    //int
Stack<double> st2; //double

我们的模板是写给编译器的,编译器通过实例化帮我们创建对应的类

注意 Stack<int> Stack<double>是两个不同的类 

总结:

我们在函数模板通常是通过参数进行 T 类型的推导,而类模板这是直接显示实例化

还有一个小点需要注意~如果我们要做声明与定义怎么办?

template<class T>
class Stack
{
public:
	void push(const T& x);
	// ... 
private:
	T* data;
	size_t capacity;
	size_t top;
};

template<class T>
void Stack<T>::push(const T& x)
{
	if (capacity == top)
	{
		T* newNode = new T[2 * capacity];
		if (newNode == NULL)
		{
			perror("new");
			exit(-1);
		}
		data = newNode;
		capacity *= 2;
	}
	data[top++] = x;
}

声明与定义分离必须模板化


 

冉冉星河远

          共与乘舟还

先介绍到这里啦~

有不对的地方请指出💞

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

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

相关文章

sherpa + ncnn 离线语音识别

目录结构 前言音视频格式转为wavsherpa-ncnn编译LinuxWindowswindows编译中遇到的问题问题“nmake -? failed with: no such file or directory”编译失败原因 成功编译截图 可执行程序说明模型下载语言识别测试LinuxWindows 参考文献 前言 小编需要实现离线音视频语言部分识…

#STM32F407VET6(天空星)标准库和HAL驱动ILI9341

一、驱动方式&#xff1a;软件SPI&#xff0c;屏幕像素320*240 二、标准库含触摸&#xff0c;HAL库不含触摸 三、立创参考的文档 【立创天空星ST32F407VET6】模块移植手册 - 飞书云文档 (feishu.cn)https://lceda001.feishu.cn/wiki/MFNpw4STVi5ImikkcH1clWrlnqb 四、引脚分…

ElasticSearch中使用向量和关键词联合检索

注&#xff1a;案例测试数据及其索引构建详见&#xff1a;ElasticSearch中使用bge-large-zh-v1.5进行向量检索&#xff08;一&#xff09;-CSDN博客 中的第三部分。 假设任务场景为&#xff1a;用“新疆”向量检索相关的数据&#xff0c;同时需要匹配关键词“巴州”。 首先获取…

C#到底属于编译型语言还是解释型语言?

C#是一种编译型语言&#xff0c;也称为静态类型语言&#xff0c;这意味着C#代码在运行之前需要经过编译器的编译处理&#xff0c;并生成一个可执行的本地代码文件&#xff08;通常是.exe或.dll文件&#xff09;。相反&#xff0c;解释型语言将代码转换为低级代码后直接执行&…

【结构型模式】装饰器模式

​一、装饰器模式概述 装饰器模式&#xff08;装饰者模式&#xff09;定义&#xff1a;装饰器模式动态地将责任附加到对象上。若要拓展功能&#xff0c;装饰者提供了比继承更有弹性地替代方案。&#xff08;对象结构型模型&#xff09;通俗点来说&#xff1a;动态的给一个对象增…

12.事件参数

事件参数 事件参数可以获取event对象和通过事件传递数据 获取event对象 <template><button click"addCount">Add</button><p>Count is: {{ count }}</p> </template> <script> export default {data() {return {count:0…

13.Hexo Plugins插件及将网站上传到互联网

Plugins 有些想要实现的操作Hexo实现不了&#xff0c;这时就可以使用插件 插件一般都是可以下载的代码片段&#xff0c;可以附加到Hexo上 添加了一些功能或一些额外的东西 Plugins | Hexo 在官方页面&#xff0c;有471个插件&#xff0c;并附加了一些描述 点击一个插件时&…

【研发管理】产品经理知识体系-文化、团队与领导力

导读&#xff1a;文化、团队与领导力是产品经理知识体系中的重要组成部分。产品经理需要深入理解并应用这些要素&#xff0c;以推动产品的成功开发和运营。通过塑造积极的文化氛围、建立高效的团队和发挥领导力&#xff0c;产品经理能够为公司创造更大的价值。 目录 概述 1、…

(1)认识人工智能

第一章 认识人工智能 引言 本人目前大三&#xff0c;双非一本的人工智能专业&#xff0c;代码能力不算太差&#xff0c;做过项目&#xff0c;也打了比赛&#xff0c;获了奖&#xff0c;但是走技术路线总会有否定自己的感觉&#xff0c;可能是感觉自己的才能没有在搞技术方面实…

#QT获取ONENET云平台数据(草稿)

1.基本目标 &#xff08;1&#xff09;查询ONENT云平台的数据 &#xff08;2&#xff09;查询网络时间 &#xff08;3&#xff09;网络音乐拉取&#xff08;作为背景音乐&#xff09;&#xff0c;音量可调 2.制作UI界面 &#xff08;1&#xff09;串口图标的制作方法 &…

RIME-SVM,基于RIME寒冰优化算法优化SVM支持向量机回归预测 (多输入单输出)-附代码

支持向量机&#xff08;SVM&#xff09; 支持向量机&#xff08;SVM&#xff09;是一种广泛用于分类和回归的强大监督学习算法。在回归任务中&#xff0c;特别是在SVM被用作支持向量回归&#xff08;SVR&#xff09;时&#xff0c;目标是找到一个函数&#xff0c;这个函数在给…

PMP证书难考吗?

PMP证书难不难考要看你学的咋样的&#xff0c;我的PMP认证就是一个多月拿下的&#xff0c;同一个考次的同学在考试前的讲师直播过程中一直在说冲刺题难怎么怎么滴&#xff0c;最后还是通过率98%&#xff0c;绝大多数都通过了&#xff0c;并且还有47%的同学考了3A&#xff0c;这…

Vue2slot插槽(理解与应用)

1、插槽的概念 插槽&#xff08;Slot)是vue为组件的封装者提供的能力。允许开发者在封装组件时&#xff0c;把不确定的、希望由用户指定的部分定义为插槽。 举个例子&#xff1a;组件好比小霸王游戏机&#xff0c;插槽就是游戏机的插口&#xff0c;看用户插什么卡&#xff0c;就…

笔记 | 嵌入式系统概论

1 嵌入式系统简介 1.1 嵌入式系统的定义 根据美国电气与电子工程师学会&#xff08;IEEE&#xff1a;Institute of Electrical and Electronics Engineers )的定义&#xff0c;嵌入式系统是用于控制、监视或辅助操作机器和设备的装置(原文: devices used to control, monitor…

【Java基础】23.接口

文章目录 一、接口的概念1.接口介绍2.接口与类相似点3.接口与类的区别4.接口特性5.抽象类和接口的区别 二、接口的声明三、接口的实现四、接口的继承五、接口的多继承六、标记接口 一、接口的概念 1.接口介绍 接口&#xff08;英文&#xff1a;Interface&#xff09;&#xf…

Virtualenv:Python项目管理的救星

在Python的世界里&#xff0c;依赖包冲突是开发者的噩梦&#xff0c;但也是成长的催化剂。最近在写Python项目中就碰到了这样的问题&#xff0c;明明代码在自己的电脑上表现都是正常的&#xff0c;在另外一台电脑上却始终有些小问题&#xff0c;两台电脑安装的Python版本都是一…

云原生Docker容器中的OpenCV:轻松构建可移植的计算机视觉环境

前言 构建可移植的计算机视觉环境 文章目录 前言引言简介&#xff1a;目的和重要性&#xff1a; 深入理解Docker和OpenCVDocker的基本概念和优势&#xff1a;OpenCV简介和应用领域&#xff1a; 构建Docker镜像部署分享Docker容器1. 打包Docker镜像:2. 上传到Docker镜像仓库:3. …

【python】Paste Mask

学习来自【OpenCv】利用roi 掩模 将一张图片添加到另一张上 任务描述&#xff1a;提取图片A的 mask 区域&#xff0c;并粘贴到图片B上 文章目录 1 代码实现2 结果展示3 涉及到的库cv2.bitwise_notcv2.bitwise_andcv2.add 附录——获取 mask 的边界框 1 代码实现 A 图 A 图的 …

Pytest精通指南(23)钩子函数-执行顺序(pytest-ordering)

文章目录 前言应用场景插件安装参数分析装饰方法装饰类装饰模块 前言 pytest-ordering 是一个pytest插件&#xff0c;它允许用户自定义测试用例的执行顺序。 默认情况下&#xff0c;pytest会按照模块、类、函数定义的顺序以及它们的名称的字母顺序来执行测试用例。 但通过使用 …

Kafka集群搭建可视化指南

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 Kafka集群搭建可视化指南 前言准备工作硬件要求环境准备 kafka集群的部署与配置3.1 单节点部署与多节点集群搭建单节点部署&#xff1a;多节点集群搭建&#xff1a; 3.2 Broker配置与优化3.3 Topic的创…