一、为什么使用函数模板
假设我们在程序中需要比较两个变量的大小,但变量的类型可能是int、float或者double,此时为了满足程序的要求我们可能会在程序中编写多个函数,如:
1 //比较两个int型变量的大小 2 int compare(const int &a,const int &b){ 3 return a>b?a:b; 4 } 5 //比较两个float型变量的大小 6 float compare(const float &a,const float &b){ 7 return a>b?a:b; 8 } 9 //比较两个double型变量的大小 10 double compare(const double &a,const double &b){ 11 return a>b?a:b; 12 }
在上面的代码中,我们发现编写的函数除了函数参数类型和返回值类型不同之外其余完全相同,这样导致的结果就是该程序代码的冗余性较高。为了解决这个问题,我们可以使用函数模板来编写与类型无关的函数以降低程序的冗余性。
二、什么是函数模板
一个函数模板就是一个公式,可用来生成针对特定类型的函数体
语法:
template <typename 形参名,typename 形参名,......>
返回值类型 函数名(参数列表){
/*..........
函数体
...........*/
}
实例演示:
1 //compare函数的函数模板 2 template <typename type> 3 type compare(const type &a,const type &b){ 4 return a>b?a:b; 5 }
特别注意:
1.在函数模板的定义中,模板参数列表不能为空
2.在进行模板定义的时候可以用关键字class代替关键字typename,二者在这里是等价的。甚至可以在定义函数模板时同时使用这两个关键字,但还是推荐使用typename,因为这样会使程序的可读性更好
1 template <typename type1,class type2> 2 void func(type1 a,type2 b){ 3 cout<<a<<endl; 4 cout<<b<<endl; 5 }
3.模板参数(<>包裹的参数)表示在函数定义中使用到的类型或值。当使用模板时,我们(隐式地或显式地)指定模板实参,将其绑定到模板参数上
4.inline和constexpr的函数模板:将关键字inline或constexpr放在模板参数之后,返回值类型之前即可
1 //inline函数的函数模板 2 template <typename type> 3 inline type func(type a){ 4 a+=1; 5 return a; 6 }
三、函数模板的实例化
当我们调用一个函数模板时,编译器(通常)用函数实参来为我们推断模板实参,例如上面的compare函数,编译器会使用函数实参的类型来确定模板参数type的类型。
特别注意:
1.编译器会用推断出的模板参数来为我们实例化一个特定版本的函数,换句话说就是编译器会使用实际的模板实参来替代对应的模板参数来创建出一个模板的新“实例”
1 template <typename type> 2 type compare(const type &a,const type &b){ 3 return a>b?a:b; 4 } 5 6 int main(){ 7 //实例化为 int compare(const int&,const int&) 8 cout<<compare(1,2)<<endl; 9 //实例化为float compare(const float&,const float&) 10 cout<<compare(1.2f,2.3f)<<end; 11 //实例化为double compare(const double&,const double&) 12 cout<<compare(1.2,2.3)<<endl; 13 return 0; 14 }
2.当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。
3.为了生成一个实例化的版本,编译器需要掌握函数模板的定义,因此与非模板代码不同,模板的头文件通常既包括声明也包括定义
四、函数模板的形参
A、类型形参:类型形参由关见字class或typename后接说明符构成,如:
1 template <typename type> //类型形参 2 void func(type a){ 3 ...... 4 }
特别注意:
1.我们可以将类型参数看做类型说明符,就像内置类型或类类型说明符一样使用。
2.不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,也就是说不能以func(int)的形式调用上面的函数模板func,只能以func(1)的形式调用。
B、非类型形参:非类型形参的参数表示一个值而非一个类型,通常情况下,我们通过一个特定的类型名而非关键字typename或class来指定非类型形参。
特别注意:
1.当一个模板被实例化时,非类型形参会被一个用户提供的或编译器推断出的值所替代,这些值必须是常量。
1 template <unsigned N,unsigned M> 2 int compare(const char (&p1)[N],const char (&p2)[M]){ //p1和p2是对数组的引用,而N和M都表示数组的长度 3 return strcpy(p1,p2); 4 }
当我们调用这个版本的compare函数时“
compare("Tomwenxing","Jack");
编译器会用字面值常量的大小来代替N和M,从而将模板实例化。另外编译器会在字符串字面常量的末尾插入一个空字符作为终结符,因而编译器最终实例化出的版本如下:
int compare(const char (&p1)[11],const char (&p2)[5])
2.一个非类型形参可以是一个整型,也可以是一个指向对象或函数类型的指针或引用。其中绑定到非类型整型参数的实参必须是一个常量表达式;而绑定到指针或引用非类型参数的实参必须具有静态的生存期。
3.函数模板中需要常量表达式的地方(比如说数组的大小),可以使用非类型形参