模板的初阶--初识函数模板和类模板

在这里插入图片描述

泛型编程

我们首先来看一个问题, 如何实现一个通用的交换函数

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 T3...>
返回值类型 函数名(参数列表){}
注意这里的typename 是用来定义模板参数的关键字, 也可以使用class定义但是不能使用struct


//template<class T, class T> 这也是正确的
template<typename T, typename T>
void Swap(T left, T right)
{
	T temp = left;
	left = right;
	right = temp;
}
函数模板的原理

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

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

函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。
模板参数实例化分为:
隐式实例化
显式实例化

隐式实例化: 让编译器根据实参推演出模板实参的实际类型

![[Pasted image 20260518190037.png|437]]

那我们如果让a和c传参会发生什么呢???
需要注意的是, 在模板中,编译器不会进行类型类型转换, 编译器无法确定T是int还是double类型, 这里就会编译报错.
![[Pasted image 20260518190622.png|551]]

所以,我们只能在模板外进行类型转换
![[Pasted image 20260518191428.png|536]]

还有就是显示实例化我们接下来看看

显示实例化

![![[Pasted image 20260518191822.png|472]]](https://i-blog.csdnimg.cn/direct/7899c551919f43d2bf469d57afb9daa8.png

模板参数的匹配原则

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

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

![[Pasted image 20260518194206.png]]

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

类模板

template <class T1, class T2, class T3...>
class 类名
{类内成员定义};
那么我们来看一下, 数据结构栈怎么来写

template <class T>
class Stack
{
public:
	Stack(int n = 4)
		:_arr(new T[n])
		, _size(0)
		, _capacity(n)
	{}
	void Push(const T& x);
	
	~Stack()
	{
		delete[] _arr;
		_size = 0;
		_capacity = 0;
	}
private:
	T* _arr;
	int _size;
	int _capacity;
};
template<class T>
void Stack<T>::Push(const T& x)
{
	if (_size == _capacity)
	{
		T* temp = new T[_capacity * 2];
		memcpy(temp, _arr,_capacity);
		delete[] _arr;
		_capacity *= 2;
		_arr = temp;
	}
	_arr[_size++] = x;
}

int main()
{
	Stack<int> s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	Stack<double> s2;
	s2.Push(1.1);
	s2.Push(1.2);
	s2.Push(1.3);

	return 0;
}

这里我们就会在想类模板变化也不大呀, 我们之前C语言使用typedef 就可以完成, 他们有什么区别吗??
这里我们可以看一下main 函数里面的两个栈, 一个是int类型的s1 一个是double类型的s2, 这完全是两个类型, 两个类.
如果在C语言这么实现的话, 就需要再复制粘贴一份, 该他们的变量名, 这样就太麻烦了.

但是模板不建议声明和定义分离到两个文件.h和点.cpp会出现链接错误.

类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
![[Pasted image 20260518201733.png]]

结语

上面介绍模板的初阶的知识, 希望能让大家对函数模板, 类模板有一些了解.
有不足的地方也希望大家多多讨论, 谢谢大家!!!

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值