浅谈模板----函数模板

来源:互联网 发布:登录前映射网络驱动器 编辑:程序博客网 时间:2024/04/20 09:42

    泛型编程是C++当中用于编写与类型无关的逻辑代码,是代码复用的一种手段,而模板则是泛型编程的基础。

    在C++当中,模板分为两种:函数模板与类模板。这里我们先来分析模板函数:

1. 模板函数的一般格式:


对于模板函数的格式,有以下几点注意:

①模板形参列表不能为空;

②typename是用来定义模板参数关键字,也可以使用class,建议使用typename,但是这里用不能struct;

 ③模板函数也可以被定义成inline函数,对于inline关键字要放在模板形参列表后面,返回值前面,不能放在template关键字之前

 ④对于模板函数,它不是函数,它仅作为模板

2.模板函数的实例化

之前说模板函数不是函数,仅仅作为模板,因此编译器在编译的时候会根据调用的参数类型(或者根据实参推演的结果)以及提供的模板产生特定版本的函数,以用于调用。

测试代码:

template<typename T>T Max(T left,T right){return left>right?left:right;}int main(){Max(1,2);Max('a','b');return 0;}
查看上述代码的反汇编:

由此我们发现上述代码,编译器根据模板函数实例化出了参数类型为int以及参数类型为char的两个不同的函数用于调用。

对于模板函数的实例化,需要注意一点:编译器对于模板函数进行两次编译,第一次是在实例化之前,检查模板函数是否有语法错误;第二次是在实例化的时候,检查模板代码,查看是否所有调用均有效。

3.类型参数的转换

模板函数在实例化的过程中一般不会进行实参类型的转换以适应已有的实例化版本,而是实例化出对应的新版本,但是有两个例外:

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

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

4.模板参数

①.模板形参的名字遵循名字屏蔽原则。

测试代码:

typedef char T;template<typename T>void fun(T a){cout<<typeid(a).name()<<endl;}T c;int main(){int i=0;fun(i);cout<<typeid(c).name()<<endl;return 0;}

测试结果:


由此可以得出,模板形参的名字仅能在模板形参列表之后,到模板声明定义末尾之前使用,而且在这个范围内只要是用到这个名字,不管在外部是否有同名的定义,都只能用模板形参列表中给定的形参作为这个名字的有效意义,而在这个范围之外,就只能看外部的定义了。

②.在模板形参列表中相同的名字只能用一次,且不同的模板形参之间都要用逗号隔开,每个模板形参之前都要加class或typename进行修饰。

③.模板形参可分为两种:类型形参与非类型形参

对于类型形参前面所给的测试用例均是类型形参的例子,顾名思义,模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换;而对于非类型形参:

测试代码:

template<typename T,int N>void funtest(T(&arr)[N]){cout<<"T:"<<typeid(T).name()<<endl;cout<<"N:"<<N<<endl;}int main(){int arr[5];funtest(arr);return 0;}
测试结果:


要注意的一点是:对于给定数组不同(即给定arr[5]和brr[6]时,传入的参数N不同),调用的函数也不同,即生成的实例也不同。


5.模板参数的重载

①. 函数模板可以与它的其中一个实例化函数相同的非模板函数同时存在,并且在条件相同的情况下,优先调用非模板函数,而不会进行函数模板的实例化;但如果能够实例化出更符合条件的模板函数,则进行实例化。

测试代码:

int Max(const int& left,const int& right){return left>right?left:right;}template<typename T>T Max(const T& left,const T& right){return left>right?left:right;}template<typename T>T Max(const T& left,const T& mid,const T &right){return Max(left,mid)>right?Max(left,mid):right;}int main(){Max(1,2);Max(1,2,3);return 0;}
测试结果:

查看Max(1,2);与Max(1,2,3);这两段语句的反汇编:

   

由此,我们可以看出Max(1,2);由于存在符合条件的非模板函数,因此这里就直接调用了非模板函数;而对于Max(1,2,3);由于不存在符合条件的非模板函数,因此这里就根据函数模板合成对应的模板函数。

②.根据上面所说,但如果在存在合适的非模板函数,但一定要调用函数模板实例化的模板函数,则可以显式指定空的模板实参列表,这里就可以告诉编译器这个函数调用必须使用函数模板实例化出的模板函数。

测试代码:

int Max(const int& left,const int& right){return left>right?left:right;}template<typename T>T Max(const T& left,const T& right){return left>right?left:right;}int main(){Max(1,2);Max<>(2,3);return 0;}
测试结果:

查看Max(1,2);与Max(2,3);这两段语句的反汇编:


6.模板函数的特化

以上面的实现比较两个对象的大小这项功能为例,对于比较整型的大小和与比较两个字符串的大小,由于两者比较大小的方法不同,很明显当用一个模板的时候显然无法满足两者,这里就要用到模板函数的特化:

模板函数特化形式如下:
①、关键字template后面接一对空的尖括号<>
②、函数名后接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
③、函数形参表
④、函数体

测试代码:

template<typename T>T Max(const T& left=0,const T& right=0){cout<<typeid(left).name()<<endl;return left>right?left:right;}template<>const char* Max<const char*>(const char* const &left, const char* const &right){if(strcmp(left,right)>0){return left;}else{return right;}}int main(){int ret=0;const char* p1="edf";const char* p2="abc";ret=Max(5,6);cout<<Max(p1,p2)<<endl;return 0;}
这里在执行cout<<Max(p1,p2)<<endl;这条语句的时候,会调用被特化的模板函数。

这里需要注意几点:

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

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










0 0