1、了解函数模板
可以说模板是产生代码的代码;
函数模板:
Template <typename T,……>
T 函数名(T a,T B……)
{
模板体;
}
2、函数模板的编译过程
编译定义点,模板头部;
调用点编译——模板函数;
3、模板的实例化过程
模板的实例化过程就是类型重定义的过程;替换后生成的代码为模板函数;也就是类型和值的替换过程称为模板实例化。
4、类型参数化(类型参数化和非类型参数化)
关键字template总是放在模板的定义与声明的最前边。关键字后面是用逗号分隔的模板参数表,他用尖括号括起来。该列表是模板参数列表 ,不能为空。模板参数可以是一个模板类型参数,他代表了一种类型;也可是一个模板非类型参数,他代表了一个常量表达式;
5、模板的实参推演
在进行模板的实参推演时,注意一下几点:
一不能让编译器产生二义性;
二必须有实参,才可以进行推演;
//函数模板
template<typename T>
T Sum(T a,T b)
{
return a+b;
}
int main()
{
Sum(10,20);//正确。推演为int类型
Sum(10.1,10.2);//正确。推演为double类型
Sum(10,23.1);//错误;编译器无法选择最终推演的类型,所以只能报错;也就是产生了二义性;
Sum();//错误,没有实参,无法进行类型推演。
return 0;
}
模
板实参推演的通用算法如下:
①依次检查每个函数实参,以确定在每个函数实参的类型中出现的模板参数;
②如果找到模板参数,则通过检查函数实参的类型,推演出相应的模板实参;
③函数参数类型和函数实参类型不必完全匹配。下列类型转换可以被应用在函数实参上,以便将其转换成相应的函数参数的类型:
左值转换
限定修饰转换
从派生类到基类类型的转换。假定函数参数具有形式T<args>、T<args>&或T<args>*,则这里的参数表args至少包含一个模板参数。
④如果在多个函数参数中找到同一个模板参数,则从每个相应函数实参推演出的模板实参必须相同。
6、模板的特例化
模板的特例化又可以划分为完全特例化和部分特例化;完全特例化指的是具体到某一个类型;而部分特例化指的是具体到部分类型;
模板特例化的结果是一个模板函数;
注意:
特例化调用优先级高于模板实例化的版本;
模板不能满足特殊类型的需求时,作模板特例化;
特例化应该符合模板实例化逻辑;
7、接收不明确类型的返回值
Auto rt = Add(10,20);
用auto接收,自适应返回值的类型,调节rt的类型。
8、模板重载
关于函数重载,首先通过以下代码和结果进行简单了解:
template<typename T>
T test(T a, T b)
{
cout << "模板函数"<<"template<typename T> T test(T,T)"<<endl;
return a+b;
}
int test(int a,int b)
{
cout << "普通函数"<<"int test(int,int)"<<endl;
return a + b;
}
template<>
int test<int>(int a, int b)
{
cout << "特例化模板函数"<<"template<typename T> double test(double,double)"<<endl;
return a+b;
}
int main()
{
test(10,20);
return 0;
}
第一次运行结果:
当将普通函数屏蔽,再来运行一次的结果:
在进行模板调用的时候,我们要做到的是精确匹配,这也就是出现上述运行结果的原因。
考虑优先级:普通函数版本>模板特例化版本> 模板版本
9、模板的显示实例化和隐式实例化
显示实例化:template int test<int>(int ,int);
显式实例化只需声明,不需要重新定义。编译器根据模板实现实例声明和实例定义。
隐式实例化:test<int>(10,10);
编译器会根据实参进行推演,生成test<int>(int a,int b);这样的函数定义;
总结:
对于函数模板,也是用来解决泛型编程的一个方法。模板会在编译时期对他的头部以及定义点进行编译,在调用点编译有他生成的模板函数。一般是在头文件中实现。至于模板在头文件实现的原因,那是因为模板可以说是函数的声明,在编译时期对模板体不进行编译,而是通过调用点以及模板头部生成一个新的函数就是我们的模板函数,编译模板函数,并最终调用执行的也是模板函数。