文章来源:
http://blog.csdn.net/shift_wwx/article/details/78677119
在C++ template中已经详细的通过实践说明了c++ template的用法,也在typename 和class在template中的区别中解释了template中typename和class的区别。
这一文结合自己的想法做一个总结。
函数模板是一个抽象画的函数,区别于函数的重载。
如函数的重载,多个函数除了数据类型不同,而函数算法 相同时,可以用函数模板。
定义形式:
template
返回类型 函数名(函数形参表) {
函数体;
}
在调用函数模板时,编译系统会根据实参的类型生成一个对应的函数,这就是模板函数。
模板函数由编译系统在发现具体的函数调用时生成相对应的程序代码,是实际的函数定义。所以,模板函数是函数模板的实例化。
在函数调用的时候,实参的参数类型必须和模板中的数据参数类型完全一致,才能正确的实例化,才会有正确的模板函数。
例如,
templateswap(T a, T b) {
...
...
}
在调用swap的时候,两个实参的类型必须都是T,swap(1, 2); 或者swap(1.2, 2.1);都是可以的,但是不能是swap(1, 2.1); 一个是int类型,一个是float类型。
定义形式:
template
class className {
类体;
};
对于类模板的类型形参后面说明,来看一下类模板中的成员函数的实现形式:
template <类型形参表>
返回类型 className<类型形参名>::functionName(函数形参表) {
函数体;
}
比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:
template void A::h(){}
类模板实例:
className<类型实参表> object;
这里className后面的是类型的实参表了,同样是上面定义的模板类A,形参名为T1和T2,但是在实例的时候应该写为:
A
这样对象a中的类型都是int型替换。
对于类模板,模板的形参必须在类名后用尖括号明确指定,这跟函数模板不一样的地方。
注意:
模板的声明或定义只能在全局、命名空间或者类范围内进行。不能放到函数或者局部范围内进行。
模板的形参分三种:类型形参、非类型形参、模板形参。
类型形参
在这之前讲到的模板形参都是类型形参,用关键字class或者typename声明。
实例化都是根据实参的真正类型。
非类型形参
非类型模板形参的形参和实参间所允许的转换
模板形参
模板形参就是模板的参数是个类模板模板。
形式如下:
template
指定模板中形参类型为默认值,这样在实例的时候可以使用默认参数。
例如:
template
class Test4 {
public:
Test4();
Test4(T1, T2);
~Test4();
void test(T1, T2);
private:
T1 value1;
T2 value2;
};
指定第二个形参为int型,如果默认情况下第二个参数为int,实例obj的时候,可以这样:
Test4 obj;
obj.test(5.0, 6);
编译器会自动将匹配到正确的模板函数,如果与默认参数的类型不匹配的时候会编译报错。例如,默认参数改为int*或者char*。
注意:
template void test(T);
template class A;
这就是模板的声明,后面没有函数体或者类体,注意A后面的分号。
定义
定义跟普通的函数定义、类定义是一样的。
注意类模板的定义方式,其实除了加上了template
例如,普通类A可以定义为:
A::A(){}
A::~A(){}
void A::test(){}
换成类模板应该改为
template
A::A(){}
template
A::~A(){}
template
void A::test(T){}
实例化
实例化是在模板调用的时候,例如A
如果创建了这样的实例,在下次再次条用同样的模板实例的时候,是不会创建新的实例。例如,A
对于指针或者引用,之后在真正指向相关的对象的时候才会实例化。例如A *m; 或 A &n;并不会实例化,但是m = new A():就会实例化。
下面会在实参推演的过程中,说明实例化的其他注意事项。
模板的实例化是在模板调用的时候,例如,
template void swap(T x, T y){}
在调用swap(3, 2); 的时候会根据实参推演出swap( int, int);并且建立实例。
当然,这个实例建立好后再次调用swap(2, 3); 是不会在建立实例,会使用已经有的。
对于模板,实例化会建立实例,但是并不会出现类型转化。例如,
templatevoid h(T x){}
void main() {
int a = 2;
short b = 3;
h(a);
h(b);
}
最开始使用h(a); 会建立一个实例,类型为int。在使用h(b); 的时候会再次建立一个实例,类型为short。并不会像普通函数那样存在类型转换。
编译器允许下面实参到模板形参的转换:
(1)数组到指针的转换
template void h(T *x){}
int a[] = {1, 2, 3};
h(a);
可以看到模板形参为指针类型,实参为数组类型。编译器允许数组到指针的转换,这个时候会实例化一个h(int *);的实例,T会被转换为int,函数体中的T会被int替换。换言之,如果已经存在了一个h(int *)的实例,这个时候的数组调用时不会产生新的实例,会直接使用h(int *);
(2)限制修饰符转换
即把const或volatile限定符加到指针上。比如template
(3)到一个基类的转换(基类为一个模板类)
例如,
templateclass A{};
template class B:public A{};
template void h(A& m){}
在main函数中有B
在这里转换的顺序为,首先把子类对象n转换为基类对象A
隐式实例化
例如有模板函数
template void h(T a){}
h(2)这时h函数的调用就是隐式实例化,既参数T的类型是隐式确定的。
函数模板显示实例化
语法是:
template 函数反回类型 函数名<实例化的类型> (函数形参表);
注意这是声明语句,要以分号结束。例如,
template void h (int a);
这样就创建了一个h函数的int 实例。
再如有模板函数
template T h( T a){}
注意这里h函数的反回类型为T,显示实例化的方法为template int h
注意:
适用于函数模板,即在调用函数时显示指定要调用的时参的类型。
格式:
在调用模板函数的时候在函数名后用<>尖括号括住要显示表示的类型
例如,有模板函数
template void h(T a, T b){}
则h
显示模板实参用于同一个模板形参的类型不一致的情况。
对于上面的模板,h(2, 3.2)的调用会出错,因为两个实参类型不一致,第一个为int 型,第二个为double型。而用h
显示模板实参用于函数模板的返回类型中。
例如有模板函数
template T1 h(T2 a, T3 b){}
则语句int a=h(2,3)或h(2,4)就会出现模板形参T1无法推导的情况。而语句int h(2,3)也会出错。用显示模板实参就参轻松解决这个问题,比如h
显示模板实参应用于模板函数的参数中没有出现模板形参的情况。
例如template
显示模板实参用于函数模板的非类型形参。
例如,
template void h(T b){}
而调用h(3)将出错,因为这个调用无法为非类型形参推演出正确的参数。这时正确调用这个函数模板的方法为h
在使用显示模板实参时,我们只能省略掉尾部的实参。
例如,
template T1 h(T2 a, T3 b){}
在显示实例化时h
显示模板实参最好用在存在二义性或模板实参推演不能进行的情况下。
templatevoid h(T a){}
这个函数体中的功能使用所有类型,但是如果int型比较特殊,不需要这里的函数体。这样就需要对模板进行特殊化。
函数模板特例化格式:
template<> 返回类型 函数名<要特化的类型>(参数列表) {函数体}
对于上面的函数模板,其int 类型的特化版本为template<> void h
当出现int 类型的调用时就会调用这个特化版本,而不会调用通用的模板,比如h(2),就会调用int 类型的特化版本。
如果可以从实参中推演出模板的形参,则可以省略掉显示模板实参的部分。
例如:template<> void h(int a){}。注意函数h后面没有<>符号,即显示模板实参部分。
对于返回类型为模板形参时,调用该函数的特化版本必须要用显示模板实参调用,如果不这样的话就会出现其中一个形参无法推演的情况。例如,
template T1 h(T2 a,T3 b){}
有几种特化情况:
情况一:
template<> int h(int a, in b){}
该情况下把T1,T2,T3的类型推演为int 型。在主函数中的调用方式应为h
情况二:
template<> int h(int a, int b){}
这里把T2,T3推演为int 型,而T1为int 型,但在调用时必须用显示模板实参调用,且在<>尖括号内必须指定为int 型,不然就会调用到通用函数模板,如h
下面几种情况的特化版本是错误的,例如,
template<> T1 h(int a,int b){}
这种情况下T1会成为不能识别的名字,因而出现错误。
template<> int h(int a,int b){}
在这种情况下返回类型为int 型,把T1确定为int 而尖括号内又把T1确定为double型,这样就出现了冲突。
具有相同名字和相同数量返回类型的非模板函数(即普通函数),也是函数模板特化的一种情况,这种情况将在后面参数匹配问题时讲解。
类模板特例化格式:
template<> class 类名<要特化的类型> {类体};
例如,
template class A{};
特例化为:
template<> class A{};
在类特化的外部定义成员的方法
例如
template class A{public: void h();};
类A特化为
template<> class A{public: void h();};
在类外定义特化的类的成员函数h的方法为:
void A::h(){}
在外部定义类特化的成员时应省略掉template<>。
类的特化版本应与类模板版本有相同的成员定义,如果不相同的话那么当类特化的对象访问到类模板的成员时就会出错。
因为当调用类的特化版本创建实例时创建的是特化版本的实例,不会创建类模板的实例,特化版本如果和类的模板版本的成员不一样就有可能出现这种错误。比如:模板类A中有成员函数h()和f(),而特化的类A中没有定义成员函数f(),这时如果有一个特化的类的对象访问到模板类中的函数f()时就会出错,因为在特化类的实例中找不到这个成员。
类模板的部分特例化
比如有类模板
template class A{};
则部分特化的格式为
template class A{};
将模板形参T2特化为int 型,T1保持不变。
部分特化以template开始,在<>中的模板形参是不用特化的模板形参,在类名A后面跟上要特化的类型。
如果要特化第一个模板形参T1,则格式为
template class A{};
部分特化的另一用法是:
template class A{};
将模板形参T2也特化为模板形参T1的类型。
在类部分特化的外面定义类成员的方法
例如有部分特化类
template class A{public: void h();};
则在类外定义的形式为:
template void A::h(){}
注意当在类外面定义类的成员时template 后面的模板形参应与要定义的类的模板形参一样,这里就与部分特化的类A的一样template
其他说明:
(1)可以对模板的特化版本只进行声明,而不定义。比如template<> void h
(2)在调用模板实例之前必须要先对特化的模板进行声明或定义。
一个程序不允许同一模板实参集的同一模板既有显示特化又有实例化。例如,
template void h(T a){}
在h(2)之前没有声明该模板的int 型特化版本,而是在调用该模板后定义该模板的int 型特化版本,这时程序不会调用该模板的特化版本,而是调用该模板产生一个新的实例。这里就有一个问题,到底是调用由h(2)产生的实例版本呢还是调用程序中的特化版本。
(3)因为模板的声明或定义不能在局部范围或函数内进行。所以特化类模板或函数模板都应在全局范围内进行。
(4)在特化版本中模板的类型形参是不可见的。例如,
template<> void h(int a,int b){T1 a;}
就会出现错误,在这里模板的类型形参T1在函数模板的特化版本中是不可见的,所以在这里T1是未知的标识符,是错误的。
子类并不会从通用的模板基类继承而来,只能从模板基类的某一实例继承而来。
继承方式一:
templateclass B:public A
{类体};
继承自A某一实例,这里是A的int型实例
继承方式二:
templateclass B:public A
{类体};
在实例化B的时候会用同样的类型实例化基类A
继承方式三:
class B:public A
{类体};
类B不是模板类