模板初识

模板是实现泛型编程的基础,泛型编程即指编写与类型无关的逻辑代码,是实现复用的一种手段。

模板可分为:函数模板,类模板

函数模板

概念

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

函数模板格式

template<typename T1,typename T2,……,typename Tn>
返回值类型 函数名(形参列表){
     函数体}

如:

template<typename T>
void Swap(T& left,T& right){
     
	T tmp = left;
	left = right;
	right = tmp; 
}//一个可以实现交换功能的函数模板

【Note】: typename也可以用class代替

函数模板的原理

函数模板是编译器产生特定具体类型函数的模具,本身并不是函数

在编译器编译阶段,当需要使用函数模板产生特定类型的函数时,编译器会通过对实参类型的推演,确定传入类型T,然后产生一份专门处理T类型的函数。

如:

double a = 3.14, b = 6.28;
int  x = 1,y = 2;
char r = 'a', t = 'v';
Swap(a,b);
Swap(x,y);
Swap(r,t);

因为代码里存在Swap的函数模板,三次调用传入不同类型的数据,编译器在阶段推断出每次传入数据的类型,分别产生各自对应的函数

上述代码在编译阶段会多出3个函数:

void Swap(double& left,double& right){
     
	T tmp = left;
	left = right;
	right = tmp; 
}
void Swap(int& left,int& right){
     
	int tmp = left;
	left = right;
	right = tmp; 
}
void Swap(char& left,char& right){
     
	char tmp = left;
	left = right;
	right = tmp; 
}

这三个函数分别对应三次Swap函数的调用,我们只写了一个函数模板,但最终代码中会不同类型实参传入,导致多出很多份函数。也就是说,使用模板的代码,存在代码膨胀的问题。

但在下认为不用函数模板,实现功能相同针对不同类型数据的函数,我们似乎只能通过函数重载实现,还是要写很多函数。而模板的使用使得开发者免于去不断重复编写功能相同,类型不同的函数,一定程度上减少了对于开发者来说意义不大但不可避免的低效工作内容,提高了工作效率。即便使用模板的代码存在膨胀问题,似乎也只会膨胀到不用模板的地步。这也不是不可接受的问题。

函数模板的实例化

编译器通过推断传入实参类型,针对不同类型产生对应函数的过程,称为函数模板的实例化,模板实例化可分为:隐式实例化与显示实例化

隐式实例化

隐式实例化即指开发者不告诉编译器传入实参的类型,让编译器自己根据传入实参的类型推演自己该产生哪种函数。如下,我们并不告诉编译器传入实参是什么类型。

template<typename T>
void Swap(T& left,T& right){
     
	T tmp = left;
	left = right;
	right = tmp; 
}//一个可以实现交换功能的函数模板

int main(){
     
	double a = 3.14, b = 6.28;
	Swap(a,b);//隐式实例化
	return 0;
}

显式实例化

显示实例化,我们只需在函数名的<>填入实参类型
如:

	Swap<double>(a,b);//显式实例化

个人觉得我们在使用函数模板时应当使用显式实例化,方便自己把握自己的代码,也方便他人阅读。

模板参数的匹配原则

一般来说,当可以完成一个功能的非模板函数和模板函数均存在,存在一个策略帮助编译器决定采用哪种方式定义的函数。

大白话说就是,现在有很多种函数都可以完成指定功能,这一大堆函数里有的函数编译器什么也不需要做,它与被调函数参数列表完全匹配;有的需要隐式类型转换;有的存在开发者指定的类型转换。这时想完成一个功能就有了很多种选择,为了结果尽量不出错,避免编译器左右横跳,存在一个选择策略,帮助编译器选择被调函数。

调选择策略如下(优先级从高到底向下排列):
1.完全匹配:函数模板与非模板函数同时存在且均可以完成指定功能,类型不冲突(优先使用非模板函数
2.提升转换(charshort自动转换int,flaot自动转换为double
3.标准转换(int转char,longdouble
4.用户定义的转换,如类声明中定义的转换

模板的局限性

假设有如下模板函数:

template<typename T>
void f(T a,T b){
     
	//函数体
}

一般来说,函数内中会存在一些操作。
如:

a = b

T是内置数据类型,这个赋值语句会符合我们的期望。
T若要是一个数组/字符串/结构体等等非内置类型,那么结果极大可能不符合我们的期望。

又如:

if(a>b)

T是数组,a>b将比较数组ab的地址高低,这很可能不是我们所希望的。

总之,模板函数很可能无法处理某些类型。
我们需要牢记,事物都有两面性!没有绝对好的东西,也没有绝对坏的东西。

类模板

类模板格式

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

【Note】 类模板中的类内成员在类外定义时,需要加模板参数列表

类模板的实例化

与函数模板实例化不同,类模板实例化需要在类模板名字之后加<>,然后将实例化的类型放入<>。也就是必须显式实例化。

同样说明适用于类模板:类模板不是类!类模板的实例化结果才是类!

你可能感兴趣的:(C++,c++)