【C++进阶之路】模板

前言

假如需要你写一个交换函数,交换两个相同类型的值,这时如果交换的是int 类型的值,你可能会写一个Swap函数,其中参数是两个int类型的,假如再让你写一个double类型的呢?你可能又要写一个Swap的函数重载,参数是两个类型double的,假如再让你写一个char类型的呢?你可能……这样会有尽头吗?没有,因为还有自定义类型呢!那我们如何解决这样的问题呢?——模板(主要功能是实现通用)

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

一. 函数模板

基本用法

①定义模板参数类型名

template<typename T1, typename T2,......,typename Tn>
//这里的typename也可以换为class
template<class T1, class T2,......,class Tn>
  • 切记: 不能换为struct

②函数模板的实现

  • 比如我们实现一个交换函数的模板
template<typename T>
void Swap(T& n1, T& n2)
{
	T tmp = n1;
	n1 = n2;
	n2 = tmp;
}

用如下几个例子实验一下:
例1:

class Date
{
public:

	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{

	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	int x = 0,y = 1;
	double x1 = 1.1, y1 = 2.2;
	Date x2(1949, 10, 1), y2(2023, 5, 23);
	Swap(x, y);
	Swap(x1, y1);
	Swap(x2, y2);
	x2.Print();
	y2.Print();
	cout << "x:" << x << " y:" << y << endl;
	cout << "x1:" << x1 << " y1:" << y1 << endl;
	return 0;
}

运行结果:
在这里插入图片描述

  • 可以看到这是交换了。

到这里我们又有一个疑问——调用的是一个函数吗?
继续分析——转到反汇编
在这里插入图片描述

  • 可以看到——调用的Swap的地址是不一样的!
  • 因此:调用的不是一个函数。
  • 如何解释呢?

换汤不换药,换的是类型,不变的交换的思想。
图解:
在这里插入图片描述

其实我们也跟根本不用自己实现交换函数,直接用库里的就行了。在这里插入图片描述

  • 不过库里的是全小写,我们平常实现的是首字母大小。

③模板的实例化


隐式实例化

  • 编译器根据实参推演模板参数的实际类型
template <typename T>
T Func(const T& n1, const T& n2)
{
	return n1 + n2;
}
int main()
{
	int x = 0,y = 1;
	Func(x, y);
	return 0;
}
  • 就好比这里传参并没有明确的说明x的类型,但是编译器会自动推导实参的类型。

显示实例化

  • 在函数名后的<>中指定模板参数的实际类型
template <typename T>
T Func(const T& n1, const T& n2)
{
	return n1 + n2;
}

int main()
{
	int x = 0,double y = 1;
	Func<int>(x, y);
	//这里会将y强制类型转换为一个double类型的常量,因此函数参数的const不可省去。
	return 0;
}
  • 像这样如果,编译器推导不出来类型,这时就需要我们显示声明一下,其中的类型是什么,否则编译器会直接报错。
  • 那除了显示示例化还有什么办法么?——强制类型转换。
template <typename T>
T Func(const T& n1, const T& n2)
{
	return n1 + n2;
}

int main()
{
	int x = 0,double y = 1;
	Func(x, (int)y);
	//这里会将y强制类型转换为一个double类型的常量,因此函数参数的const不可省去。
	return 0;
}
  • 强转的结果必然是使两个参数类型保持一致,如果强转后的参数类型不一致,那么编译器还是会报错。

还有一种情况是我们必须显示示例化的——模板参数做返回值
示例:

template <typename T>
T Func(int x, int y)
{
	return x + y;
}

int main()
{
	int x = 0;
	int y = 1;
	Func<int>(x,y);
	return 0;
}
  • 这就好比你要使用函数模板,得先让编译器知道或者能推导出模板参数的类型,那么返回值写上模板参数的话,编译器是无从下手的,那么只有显式实例化了,编译器才知道返回类型,才会给你生成指定类型的函数。

④函数模板与普通函数的调用规则

1.能用普通函数就用普通函数,用不了再用函数模板。

int add(int x, int y)
{
	return x + y;
}

template <typename T1,typename T2>
int add(T1 x, T2 y)
{
	return x + y;
}

int main()
{
	add(1, 2);//这里调用的非模板
	add(1.0, 1);//这里使用的是模板生成的函数
	return 0;
}

看反汇编:
在这里插入图片描述

2.非模板函数与模板部分重复都可以使用。

int add(int x, int y)
{
	return x + y;
}
template <typename T1,typename T2>
int add(T1 x, T2 y)
{
	return x + y;
}
int main()
{
	add(1, 2);//这里是调用add函数,因为能用普通函数就用普通函数
	add<int>(1, 1);这里是用的add模板函数实例化生成的函数。
	//这样也可以
	add<>(1, 1);//这算是空模板,只是为了调用模板而已。
	return 0;
}

3.普通函数支持类型转换,而模板要严格按照类型。

template<typename T>
int myAdd(T a, T b)
{
	cout << "template function" << endl;
	return a + b;
}

int myAdd(char a, char b)
{
	cout << "normal function" << endl;
	return a + b;
}
void test()
{
	int a = 10;
	int b = 20;
	char c1 = 'a';
	char c2 = 'b';

	myAdd(a, c1);//这里调用的是函数,这里的a会自动转换为char类型的
	myAdd(a, b);//两个都是int类型的调用模板函数
	myAdd(c1, b);//这里会发生类型转换,b的int类型会转换为char类型的
	myAdd(c1, c2);
	myAdd<>(c1, c2);//这里是显示实例化,自然会调用函数模板
}
int main()
{
	test();
	return 0;
}

  • 总结:

1.普通函数优先调用
2.普通函数支持类型转换
3.模板必须严格的类型匹配
4.如果函数模板可以产生一个更好的匹配,那么选择模板。
5.可以用空模板或者模板实例化让编译器只调用模板

二. 类模板

  • 简而言之就是类是通用的,比如一个栈既要存int类型的,又要存double类型等等。
  • 说明:类模板不是具体的类,是用来生成类对象的模具。

  • 补充:template定义的模板参数只在最近的一个大括号内有效。
#include<iostream>
using namespace::std;
template <typename T>
class Stack
{
public:
	Stack(int capacity = 4)
	{
		cout << "Stack()" << endl;
		T* tmp = new T[capacity];
		_arr = tmp;
		_top = 0;
		_capacity = capacity;
	}
	void PushBack(T x);

private:
	T* _arr;
	int _top;
	int _capacity;
};
//这里放在类外进行定义。
template <typename T>
//这里要说明的是必须写Stack<T>这是具体的类,而不能只写Stack。
void Stack<T>::PushBack(T x)
{
	if (_top == _capacity)
	{
		T* tmp = (T*)realloc(_arr, sizeof(T) * _capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		else
		{
			_arr = tmp;
			_capacity *= 2;
		}
	}
	_arr[_top++] = x;
}
int main()
{
	Stack<int> stack1;//这要说明使用的数据类型,也就是类模板示例化。
	return 0;
}

// 注意:类模板
//1.类名——Stack
//2.类型 ——Stack<T>
  • 类模板实例化得到的类叫模板类

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

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

相关文章

GD32F4x 加密(开启读保护功能)

参考链接1&#xff1a;&#xff08;设置读保护&#xff09; GD32F4x 如何开启读保护功能&#xff08;芯片加密&#xff09;&#xff1f;_EmbeddedOsprey的博客-CSDN博客 参考链接2&#xff1a;读取芯片ID进行加密 《嵌入式 – GD32开发实战指南》第19章 程序加密_gd32大小端…

训练/测试、过拟合问题

在机器学习中&#xff0c;我们创建模型来预测某些事件的结果&#xff0c;比如之前使用重量和发动机排量&#xff0c;预测了汽车的二氧化碳排放量 要衡量模型是否足够好&#xff0c;我们可以使用一种称为训练/测试的方法 训练/测试是一种测量模型准确性的方法 之所以称为训练…

黑客最常用的10款黑客工具

以下所有这些工具都是捆绑在一起的Linux发行版&#xff0c;如Kali Linux或BackBox&#xff0c;所以我们一定会建议您安装一个合适的Linux黑客系统&#xff0c;使您的生活更轻松 - 尤其是因为这些黑客工具可以&#xff08;自动&#xff09;更新。 1、Nikto&#xff08;网站漏洞…

lwIP更新记01:全局互斥锁替代消息机制

从 lwIP-2.0.0 开始&#xff0c;在 opt.h 中多了一个宏开关 LWIP_TCPIP_CORE_LOCKING&#xff0c;默认使能。这个宏用于启用 内核锁定 功能&#xff0c;使用 全局互斥锁 实现。在之前&#xff0c;lwIP 使用 消息机制 解决 lwIP 内核线程安全问题。消息机制易于实现&#xff0c;…

winpcap 发包工具

本工具主要用来进行网络协议的调试&#xff0c;主要方法是&#xff0c;对现场数据抓包&#xff0c;然后将数据包带回交给开发人员&#xff0c;开发人员将该数据包重新发送和处理&#xff0c;模拟现场环境以便于调试和分析。 &#xff08;一&#xff09;使用方法 命令行下输入s…

Visual Studio插件DevExpress CodeRush v22.1- 支持C# 10

DevExpress CodeRush是一个强大的Visual Studio .NET 插件&#xff0c;它利用整合技术&#xff0c;通过促进开发者和团队效率来提升开发者体验。为Visual Studio IDE增压、消除重复的代码并提高代码质量&#xff0c;可以快速思考、自动化测试、可视化调试和重构。 CodeRush v2…

有了 IP 地址,为什么还要用 MAC 地址?

MAC地址等价于快递包裹上的收件人姓名。 MAC地址更多是用于确认对方信息而存在的。就如同快递跨越几个城市来到你面前&#xff0c;快递员需要和你确认一下收件人是否正确&#xff0c;才会把包裹交给你一样。 IP66在线查IP地址位置&#xff1a;https://www.ip66.net/?utm-sour…

软件设计师 数据库刷题项并包含知识点总结

**两级映像 有概念模式和内模式跟物理独立性相关&#xff0c;有外模式和概念模式跟逻辑独立性相关 ** 属性列就是RS共同拥有的ABC&#xff0c;一般去除后面的&#xff0c;所以就只有前面三个ABC&#xff0c;元组就是有没有自然连接成功的&#xff0c;就是R.AS.A R.BS.B… 选项里…

希望所有计算机专业同学都知道这些老师

C语言教程——翁凯老师、赫斌 翁恺老师是土生土长的浙大码农&#xff0c;从本科到博士都毕业于浙大计算机系&#xff0c;后来留校教书&#xff0c;一教就是20多年。 翁恺老师的c语言课程非常好&#xff0c;讲解特别有趣&#xff0c;很适合初学者学习。 郝斌老师的思路是以初学…

Linux-初学者系列6_kvm虚拟机

速通配置kvm虚拟机 通过虚拟化技术将一台计算机虚拟为多台逻辑计算机。 在一台计算机上同时运行多个逻辑计算机&#xff0c;每个逻辑计算机可以运行不同的操作系统&#xff0c;并且应用程序都可以在相互独立的空间内运行互不影响&#xff0c;提高计算机的工作效率。 一、 配置…

【STM32】STM32使用RFID读卡器

STM32使用RFID读卡器 RFID卡片 ID卡&#xff08;身份标识&#xff09;&#xff1a;作用就是比如你要输入学号&#xff0c;你刷卡直接就相当于输入学号&#xff0c;省去了输入的过程 IC卡&#xff1a;集成电路卡&#xff0c;是将一种微电子芯片嵌入卡片之中 RFID的操作 1、…

设计模式之代理模式

文章目录 1、代理模式基本介绍2、Jdk中的动态代理2.1、场景推导2.2、Jdk动态代理 3、静态代理4、代理模式的关键点5、代理模式和适配器模式的比较6、代理模式UML图 1、代理模式基本介绍 代理模式的定义: 为其他对象提供一种代理以控制对这个对象的访问。在某些情况下&#xf…

I2C通信协议原理和MPU6050

一、串口通讯 只能在两个设备之间进行 若要三台设备两两通信&#xff0c;则每个设备得需要两组窗口&#xff0c;为3组相互独立的窗口通讯 为解决这个问题&#xff1a;设计了总线通讯&#xff0c;有多种&#xff0c;I2C为其中一种 二、I2C通信 &#xff08;1&#…

VMware虚拟机三种网络模式详解之NAT(地址转换模式)

VMware虚拟机三种网络模式详解 NAT&#xff08;地址转换模式&#xff09; 二、NAT&#xff08;地址转换模式&#xff09; 刚刚我们说到&#xff0c;如果你的网络ip资源紧缺&#xff0c;但是你又希望你的虚拟机能够联网&#xff0c;这时候NAT模式是最好的选择。NAT模式借助虚拟…

进攻即是最好的防御!19个练习黑客技术的在线网站

前言 进攻即是最好的防御&#xff0c;这句话同样适用于信息安全的世界。这里罗列了19个合法的来练习黑客技术的网站&#xff0c;不管你是一名开发人员、安全工程师、代码审计师、渗透测试人员&#xff0c;通过不断的练习才能让你成为一个优秀安全研究人员。以下网站希望能给各…

Hadoop基础学习---5、MapReduce概述和WordCount实操(本地运行和集群运行)、Hadoop序列化

1、MapReduce概述 1.1 MapReduce定义 MapReduce是一个分布式运算程序的编程框架&#xff0c;是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在一个Had…

【计算机系统基础bomb lab】CSAPP实验:Bomb Lab

【计算机系统基础bomb lab】CSAPP实验&#xff1a;Bomb Lab CSAPP 实验&#xff1a;Bomb Lab实验内容简述实验环境实验过程&#xff1a;phase 1phase 1 调试过程 实验过程&#xff1a;phase 2phase 2 调试过程 实验过程&#xff1a;phase 3phase 3 调试过程 实验过程&#xff1…

Java字符串知多少:String、StringBuffer、StringBuilder

一、String 1、简介 String 是 Java 中使用得最频繁的一个类了&#xff0c;不管是作为开发者的业务使用&#xff0c;还是一些系统级别的字符使用&#xff0c; String 都发挥着重要的作用。String 是不可变的、final的&#xff0c;不能被继承&#xff0c;且 Java 在运行时也保…

C++的cout详解

2023年5月20日&#xff0c;周六早上&#xff1a; 我发现我找不到非常详细的cout类的成员函数&#xff0c;只好自己写了。 不定期更新。 cout的继承关系 cout类继承自ostream类&#xff0c;ostream类继承自ios类&#xff0c;ios类继承自ios_base类 cout类拥有的所有成员函数 …

pg事务:事务的处理

事务的处理 事务块 从事务形态划分可分为隐式事务和显示事务。隐式事务是一个独立的SQL语句&#xff0c;执行完成后默认提交。显示事务需要显示声明一个事务&#xff0c;多个sql语句组合到一起称为一个事务块。 事务块通过begin&#xff0c;begin transaction&#xff0c;st…