【C++】模板简述(二):函数模板

时间:2022-08-23 19:20:13

  我们上文讲了,模板的引入,我们发现在某种特殊的情况下,必须得通过模板才能完美的解决问题.

  本文就来简述一下函数模板的基本使用.

  一、函数模板格式

template<typename Param1, typename Param2,...,class Paramn>
返回值类型 函数名(参数列表){
...
}

  二、函数模板的实例

//T表示类型,具体是什么不知道,实例化的时候才知道
//typename可以用class代替,但推荐使用typename
//注意:typename不能用struct代替!!!
template<typename T>
T Add(const T l,const T r){
return l+r;
} //模板函数也可以定义为inline
//注意inline的位置,必须在template之后,返回值之前
template<typename T>
inline T Add(const T l,const T r){
return l+r;
}

  注意:模板本身并不是函数或类,它只是一个泛型的代码.

  编译器在通过模板来生成特定类型的代码这一过程称之为:模板的实例化.

  因此,模板被编译了两次:

  第一次编译在实例化之前,编译时完成的工作主要是:检查模板代码本身是否有语法问题(比如少个分号什么的)

  第二次编译时在实例化期间,再次检查模板代码,在特定类型下,所有的调用是否都有效.

  三、函数模板的形参

  首先,模板有两种参数,类型形参与非类型形参.

//T为类型形参,N为非类型形参
template<typename T,int N>
T Add(const T l,const T r){
return l+r;
}

  模板形参小结:

  1.模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则.

  2.模板形参的名字在同一模板形参列表中只能使用一次.

  3.所有类型形参前面必须加上class或者typename关键字修饰.

  4.函数模板的内部不能指定缺省参数.

  四、模板函数的重载

template<typename T>
int Add(const int a,const int b){
return a+b;
}
T Add(const T l,const T r){
return l+r;
} template<typename T>
T Add(const T a,const T b,const T c){
return a+b+c;
} template<typename T1,typename T2>
T1 Add(const T1 l,const T2 r){
return l+r;
} template<typename T,int N>
void Print(const T (&arr)[N]){
for(int i=0; i<N; ++i){
cout<<arr[i]<<" ";
}
cout<<endl;
}
//...
cout<<Add(1,2)<<endl; //int Add(const int,const int)
cout<<Add<>(1,2)<<endl; //T Add(const T l,const T r)
cout<<Add(1,2,3)<<endl; //T Add(const T a,const T b,const T c)
cout<<Add(1,2.2)<<endl; //T1 Add(const T1 l,const T2 r)
cout<<Add(1.1,2)<<endl; //T1 Add(const T1 l,const T2 r)

  模板函数重载说明:

  1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

  2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例.如果模板可以产生一个具有更好匹配的函数,那么将选择模板.

  3.显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来.

  4.模板函数不允许自动类型转换,但普通函数可以进行自动类型转换.

  五、模板函数的特化

  在某些情况下,通用模板定义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事情:

template<typename T>
T Max(const T l,const T r){
return l>r?l:r;
}
//....
char *str1="123456789";
char *str2="abcdefhij";
cout<<Max(1,2)<<endl; //正确
cout<<Max(str1,str2)<<endl; //错误,它比较的是指针的大小,而不是ASCII码

  因此,我们需要用到模板的特化:

template<>
const char* Max<const char*>(const char* l,const char* r){
int ret = strcmp(l,r);
return ret>0?l:r;
}

  注意:

  1.在特化时如果少了形参列表,那么只是定义了一个普通的函数,该函数含有返回类型与模板实例化相匹配的形参表.

  2.在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配,编译器将为实参模板定义中实例化一个实例.

  3.特化不能出现在模板实例的调用之后,应该在头文件中包含模板特化的声明,然后使用该特化版本的每个源文件包含该头文件.

 五、模板函数的参数推演

  从函数实参确定模板形参类型和值的过程称为模板实参推断.

  类型形参转换时,一般不会转换实参以匹配已有的实例化,相反会产生新的实例.

  而编译器只会执行两种转换:

  1、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用

  2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换.数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针.