引例
xxxx我们想要了解模板语法,我们要先举一个小“栗子”。那就是:请写出short、int、double三种类型的相加函数(Add)。
short Add(short a, short b)
{
return a+b;
}
int Add(int a, int b)
{
return a+b;
}
double Add(double a, double b)
{
return a+b;
}
我们发现,对于不同的类型,我们就要写不同的函数,有几种类型就需要几种函数。多亏了C++还有函数重载,得以让我们函数名可以相同。否则我们连函数名都要不同AddInt、AddDouble……而这样就会出现一个问题。Add函数,不管类型如何,操作的原理都是一样的,逻辑完全相同。我们并没有因为数据类型的不同而对代码加以调整,如图这么写代码,势必会造成代码的冗余,明明逻辑完全相同,我们却要重复多次,开发效率很低。这时候,我们就要引入模板
何为模板
xxxx模板是啥,现实中有没有什么模板呢?我想到一个例子,就是如图
如何理解它呢?这个是一个模具,你只需要往里面放入面团,模具就可以按照它既有的形状将面团变成模具的形状。想要不同颜色的,你只需要放入不同的面团子。就会出现不同的但是形状相同的糕点。
不同的颜色 | 不同的数据类型 |
---|---|
相同的形状 | 相同的代码逻辑 |
xxxx模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。模板分为:1、函数模板。2、类模板
格式:
template <class T>
template <typename T>
1、函数模板
语法示例:
template<class T>
T Add(T& a, T& b)
{
return a+b;
}
第一行是固定格式,其中class/typename都可以T是任意的(不过要大写)。在这里T有type(类型)的意思,所以常用T来做模板的名字。在后面,编译器会自动识别传参传过来的数据是什么类型,这时候编译器就会拿着这个函数模板去实例化出一个具体的函数。
当然,以上就要要求,返回值和两个参数的数据类型应该保持一致,如果一个参数的类型时int,一个参数的类型是double,那么编译器就不知道该如何赋予相应的数据类型,就会导致错误。
但是这并不代表无法解决这个问题,这个时候我们需要了解一下,函数模板的隐式/显示调用
函数模板的隐式/显示调用
按照上面的函数模板的书写,这是一种隐式调用。
隐式调用:不指明数据类型,编译器通过对参数的数据类型进行分析,从而确定模板中T应该被实例化为那种数据类型。
xxxx都能看出,隐式调用有一个隐藏的问题,就是,当代码书写者,没有注意,在传参的时候参数的类型不一样,就会导致报错。这是我将提供两种解决方法。
方法一:设置多个函数模板。详见代码:
方法二:模板的显示实例化:
隐式实例化,是通过编译器自动识别参数的类型而确定模板的类型从而实例化出对应的函数,显示实例化就是指定模板应该实例化成什么类型,从而生成函数。例如:
通过指定数据类型,就可以解决上面的问题。
注:通过函数模板实例化生成的不同的函数,是不一样的,他们的函数地址是不同的
2、类模板
类为什么需要模板呢?我们来看一个库中的容器
这一张图就很清晰的表现了,vector需要模板。why?
Reason:因为类中需要数据,一个类可能需要不同的数据。就像vector,vector类似于C语言中的数组。数组中存储的数据各种各样,内置类型,自定义类型都可以,这就需要,实例化出多种多样的类。如果没中类型的vector都写一遍,对于无穷无尽的自定义类型,那就要写无穷无尽个vector。但是每个vector除了存储的数据的类型不同,逻辑结构几乎完全一致。因此,有符合泛型编程的使用场景,于是就出现了类模板。
同时,我们来想一下C语言当初定义自定义类型(如同C++的类)是,如何控制变量,这里我拿出以前学习C语言实现数据结构的图来阐述
对于不同的数据类型就要改变typedef的内容。这样有什么问题呢?就是,代码在一次执行中,该结构体只能用于存储一种数据,不可能说创建一个数据存储是int的结构体,同时有创建一个存储double的结构体。所以,C语言这种还是十分不方便。而如果有了模板,那么需要一个新数据类型的,我们就可以实例化来一个,需要一个就可以实例化一个,多种类型可以同时存在。就解决了这个问题。下面我们通过模拟实现一下vector来具体阐述:
多种数据类型的vector都可以同时定义啦!!
注:在类模板中,如果在类内声明函数,要在类外去实现函数,那么类外实现之前,也要声明一下函数模板