C++之模板

模板

前言

本篇介绍一下模板的初步应用

一、模板

我们之前在讲解数据结构的时候,免不了要用到Swap交换函数,而在C语言中,一般都要靠我们自己实现,那不知道大家有没有发现一个问题,如果我们需要交换int类型的变量,那就会定义一个交换函数,形参都是int类型,如:

c 复制代码
void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

那如果我们需要交换double类型,交换char类型,交换类类型呢?我们还能继续用这个函数吗?

那肯定是不能的,那我们又该怎么办呢?

难道要为每种类型都定义一个交换函数吗?

如果像上面图片中这样使用交换函数,未免也有点太挫了,十分麻烦,因为swap函数的逻辑很简单,只是形参的类型不同而已,那么为了应对这种情况,祖师爷本贾尼呢又在C++中提出了一个概念,叫做模板,

模板,顾名思义,就是类似于活字印刷术中的模具,我给你一个例子,一个模具,你照着它去做,用模具刻出来不同的字,使用的逻辑相同,只有部分细节不同,它的使用也非常简单,如下:

template就是模板的意思,后面使用<>来包裹模板参数,图中带有template定义的函数就是函数模板

<typename T>中,T就是模板参数,它的名字可以任意,也可以叫做Ty、x,都可以,它所在的位置就是模板参数,编译器自动识别实参的数据类型,并将模板参数T转为对应的数据类型,而T前面的typename,就是一个关键字,用于指明这是一个类型,是模板参数,同样,typename关键字可以用class替代,我们后续会详细讲解两者的不同

此外,模板参数的使用,其实底层实现和我们之前讲的C语言的方法一样,也是通过几个形参类型不同的Swap函数来实现交换,但是唯一不同的是,这几个不同的Swap函数是编译器帮助我们实现的,也就是说,C++和C语言中对这种情况的底层处理方式相同,但是C语言要靠我们自己去分别实现不同的交换函数,而C++中我们只需要给一个模板,编译器根据这个模板去实例化出各种不同的实际函数(即模板函数),比如形参是int类型的,形参是double类型的,形参是char类型的等等等等

注:模板函数即为实际函数,函数模板则是template关键字对应的无法使用未实例化不知晓形参具体类型的函数

因此,当我们在使用函数模板时,对于不同类型的变量,调用的swap函数实际上是不同的函数,只有同类型的变量调用的swap函数才是相同的模板函数

注:我们库中的交换函数swap的字母均为小写,即:swap()

二、传递不同类型参数时函数模板的使用

而在生活中,我们或许会遇到这种情况:

c 复制代码
template<class T1,class T2>
T1 Func(T1& a, T2& b)
{
	return a + b;
}
int main()
{
	int a = 1, b = 2;
	double c = 1.8, d = 2.2;
	int e = Func(a, c);
	cout << e << endl;
	return 0;
}

对于这种不同类型要相加的情况,我们有三种应对方法

1。设置两个模板参数

如下:

我们可以通过设置两个不同的模板参数来解决,并且可以通过控制返回值类型来控制到底要返回哪种类型

2.强制类型转换传参

如上图,我们可以传参时,对实参进行强制类型转换,通过这种方式来控制结果的类型,并且完成函数的调用,实现两者相加的功能

但是这种情况,我们需要注意一点,函数模板的形参必须要用const修饰,这是为了避免权限的放大,因为我们在传递实参时进行了强制类型转换,我们在前面讲过,强制类型转换的情况,会生成一个临时变量,因此我们传过去的实参中,有一个是临时变量,而临时变量具有常性,那么我们就需要用const修饰的变量去接收,否则处于权限的放大,如下图:

当然,只是其中一个变量是临时变量,因此我们只用一个const也是可以的,但是为了美观与保险,我们最好还是为形参变量都添加const修饰符

3.显式实例化

如图,这种方式实际上是造成了隐式类型转换,但是它与前面两种方法不同的有点是,这种方法属于函数模板的显式实例化,是通过我们指定的类型去实例化出实际函数的,而前面两种方法称为编译器的隐式实例化,是通过我们传递的实参类型去实例化出实际函数的

此外,因为这种方式也属于类型转换,因此也会受到权限放大或是缩小的约束,需要用const修饰函数模板的形参,如:

如果不用const去修饰函数模板的形参,此时编译器就会报错

三、类模板

既然有了函数模板的概念,那么对应的,为了方便,C++中也存在一种类模板的概念

类模板与普通类的一种区别就是,类名和类类型是区分开的

在普通类中,类名就是类型,譬如:

c 复制代码
class A
{};
int main()
{
	A a1;
}

其中的类名A,就是类类型,但是对于类模板来说,如:

c 复制代码
template<class T>
class Stack
{
public:
	Stack(size_t capacity = 3);

	void Push(const T& data);

	// 其他方法...

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

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

它的类名和类型是不一样的,类名为:Stack,而类型则是Stack<T>

如果我们在main函数中实例化对象,那么对象对应的类型就比如为:Stack<int>或者Stack<double>等等

因此,我们如果想要将类模板中的方法声明和定义分离,就需要使用对应的类型来标明,譬如上面的类中的push方法和构造方法:

c 复制代码
template<class T>
Stack<T>::Stack(size_t capacity)
{
	/*_array = (T*)malloc(sizeof(T) * capacity);
	if (NULL == _array)
	{
		perror("malloc申请空间失败!!!");
		return;
	}*/
	_array = new T[capacity];

	_capacity = capacity;
	_size = 0;
}

template<class T>
void Stack<T>::Push(const T& data)
{
	// CheckCapacity();
	_array[_size] = data;
	_size++;
}

template的作用范围仅仅是离它最近的下方的某个函数或者类,可以看成是以{}作为作用范围

而实例化出的各种类称为模板类

类模板解决了类似函数模板的问题,譬如,我们之前在C语言学习数据结构的时候,有一个栈结构,如果我们要求定义一个存储int类型数据的栈,定义一个存储char类型数据的栈,一个存储double类型数据的栈,我们该如何定义呢?

我们之前用的是typedef这个关键字来控制存储什么类型的数据,但是typedef某种意义上来说也是写死的,它只能让整个栈都存储那一种类型的数据,对于我们这种要求,它只能将栈的代码复制成多份,并且每一个栈的代码都typedef为不同的int、double、char等等,这是十分麻烦的,而使用类模板,我们便可以解决这种问题,通过类模板实例化出不同的模板类,毫无疑问,这是十分方便的

总结

本篇为模板的初步介绍,我们后面对在STL的讲解中使用模板,并且后续会进行模板更多进阶内容的详细讲解

相关推荐
阿杰 AJie2 小时前
MyBatis-Plus 的内置方法
java·数据库·mybatis
牧小七2 小时前
java Base64 是什么
java
Da Da 泓2 小时前
多线程(八)【定时器】
java·学习·多线程·定时器
装不满的克莱因瓶2 小时前
Android Studio 的模拟器如何上传本地图片到手机相册
android·智能手机·android studio
2401_841495642 小时前
【数据结构】英文单词词频统计与检索系统
数据结构·c++·算法·排序·词频统计·查找·单词检索
NotStrandedYet2 小时前
《国产系统运维笔记》第2期:在 openEuler 24.03 LTS 上在线部署 Tomcat 9 全记录
java·tomcat·信创·国产化·openeuler·信创运维·国产化运维
月明长歌2 小时前
Selenium Web 自动化测试脚本总结
java·selenium·测试工具
多看书少吃饭2 小时前
文件预览的正确做法:从第三方依赖到企业级自建方案(Vue + Java 实战)
java·前端·vue.js