「C++」模板详解


模板引言

假如现在需要实现一个交换整数的函数,很简单就能实现,但是如果没有说交换类型,那么可能需要写各种类型的重载,函数重载虽然可以实现,但是这么做很麻烦,函数的复用性很低,且代码的可维护性比较低,一个出错可能让所有重载都错。

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; 
}
//、、、、、、、、、、出现一个新类型就要添加一个

那么怎么解决这个问题呢?这个时候我们就可以引入模板的概念


模板基本概念

了解模板之前先了解一下泛型编程的概念,泛型编程就是指编写与类型无关的通用代码,是代码复用的一种手段,而模板呢就是泛型编程的基础

什么是模板?

那么什么是模板呢?

  • 模板是一种对类型进行参数化的工具。
  • 模板是C++的一种特性,允许函数或类(对象)通过泛型的形式表现或运行。
  • 模板可以使得函数或类在对应不同的型别(types)的时候正常工作,而无需为每一个型别都写一份代码。
  • 模板是泛型编程的基础,复用性很强
  • 模板是通用语言的特性,模板又叫参数化类型

模板就像我们现实中的模具,没有使用这个模具的时候是不会生产出任何东西的,只有在你倒入原料才会生产出东西,你倒入塑料材料就会产出一个塑料模型,倒入金属液就会产出一个金属模型。
再回到引言上的问题,我们使用模板就是为了能够编写与类型无关的代码。如果不用模板,那么解决引言上的问题要写不同类型的函数来完成函数重载,但如果使用模板的话就只用写一个swap模板函数就可以实现不同类型的swap函数了。


模板分类

模板通常有两种形式:函数模板和类模板

函数模板

函数模板概念

什么是函数模板呢?函数模板实际上是代表了一个函数家族,建立了一个通用函数,这个函数里面的所用到的所用类型,包括形参类型、返回值类型、局部变量类型都可以不具体指定,在使用的时候才会被参数化,根据实参类型产生函数的特定版本。
函数模板针对仅参数类型不同的函数

函数模板使用方式

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

typename和Class都是用来定义模板参数的关键字,两者没有区别。
以引言的交换函数为例,使用函数模板写一个swap的模板函数:

template<Class T>
//template<typename T> 也可以
void swap(T& left,T& right)
{
    
    
	T temp = left;
	left = right;
	right = temp;
}

当我们调用这个swap模板函数时,类型T就会被调用时的类型代替,举个例子,swap(a,b),两个参数都是int型的,那么这时T就会被替代为int型,模板函数就被替换为swap(int& a,int& b)。

需要注意的是模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。

函数模板实例化

其实模板函数并不是真正的函数,在编译阶段,编译器调用函数模板时,编译器会根据实参的类型来推演出模板的类型,然后产生特定具体类型的函数。
在这里插入图片描述用不同类型的参数使用函数模板是,称为函数模板的实例化。模板类型参数的实例化分为:隐式实例化,显式实例化
1.隐式实例化:让编译器根据实参推演模板参数的实际类型

template<class T> 
T Add(const T& left, const T& right) 
{
    
        
	return left + right; 
}
 
int main() 
{
    
        
	int a1 = 10, a2 = 20;    
	double d1 = 10.0, d2 = 20.0;    
	Add(a1, a2);    
	Add(d1, d2);

	return 0;
} 

但是假如要编译下面这段代码就会报错,因为在编译阶段的时候,编译器推演实参类型时,通过a1把T推演为int类型,但是通过b1又把T推演为double类型,但是模板参数列表只有一个T,此时编译器就无法确定该使用哪个类型替换T,从而报错。(编译器一般不会进行类型转换的操作)

template<class T> 
T Add(const T& left, const T& right) 
{
    
        
	return left + right; 
}
 
int main() 
{
    
        
	int a1 = 10;    
	double b1 = 10.0;   
	Add(a1, b1);    

	return 0;
} 

那么怎么解决这个问题呢?有两种处理方式
第一种方法用户自己强制转换类型

int main() 
{
    
        
	int a1 = 10;    
	double b1 = 10.0;   
	Add(a1, (int)b1); //强制把b1转为int类型   

	return 0;
} 

第二种方法就是使用显式实例化
2.显式实例化:在<>中指定参数的实际类型
还是以上面代码举例

int main() 
{
    
        
	int a1 = 10;    
	double b1 = 10.0;   
	Add<int>(a1, b1);  //显式实例化  

	return 0;
} 

函数参数的匹配原则

先看这段代码

// 加法模板函数 
template<class T> 
T Add(T left, T right) 
{
    
        
	return left + right; 
}
 
 // 专门处理int的加法函数 
 int Add(int left, int right) 
 {
    
        
 	return left + right; 
 }
 
 void Test() 
{
    
        
		Add(1, 2);// 与非模板函数匹配,编译器不需要特化    	
		Add<int>(1, 2);  //调用编译器特化的Add版本 
}

我们知道Add模板函数在遇到int类型的实参时是会实例化出一份int类型的Add函数代码的,那么上面这段代码里的int类型Add函数和Add函数模板是否可以同时存在呢?
可以同时存在,而且该函数模板还可以被实例化为这个非模板函数。
在Test()里,Add(1,2)调用Add函数时,因为已经有了int类型的Add函数,此时肯定是直接调用处理int类型的Add函数更快,因为模板函数还要推演完实参类型后再生成一份特定处理int类型的代码。但是如果我强制要使用模板函数,那么使用Add(1,2)就可以调用函数模板实例化后Add版本。

类模板

类模板基本概念

C++ 除了支持函数模板,还支持类模板(Class Template)。函数模板中定义的类型参数可以用在函数声明和函数定义中,类模板中定义的类型参数可以用在类声明和类实现中。类模板的目的同样是将数据的类型参数化。
类模板针对仅数据成员和成员函数类型不同的类。

类模板使用方式

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

一旦声明了类模板就可以用形参名(T1,T2…Tn)来声明类中的成员变量和成员函数,举例:

template<Class T>
class A
{
    
    
public:
	T a;
	T b;
	T c(T d,T& e);
};

类模板实例化

类模板实例化和函数模板实例化不同,类模板实例化需要在类模板名称后跟<实例化类型>,需要注意的是,类模板中不存在推演的,例如A<2> s,此时就会报错,编译器没法把2推演为int型,在<>里面一定要明确指定类型。
用上面的代码举例,我们实例化一个A< int > m,那么类A中所有使用模板形参的地方都会被int替代:

class A
{
    
    
public:
	int a;
	int b;
	int c(int d,int& e);
};

猜你喜欢

转载自blog.csdn.net/NanlinW/article/details/109709793