前言
模板(Template)
是C++支持参数化多态
的工具,使用模板可以使用户为类或者函数
声明一种一般模式,使得类中的某些数据成员
或者成员函数的参数、返回值
取得任意类型
。
模板是一种对类型进行参数化的工具,通常有两种形式:函数模板和类模板。函数模板
针对仅参数类型不同的函数,类模板
针对仅数据成员和成员函数类型不同的类。
使用模板的目的就是能够让程序员编写与类型无关的代码
。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。下面分别介绍。
注意:模板的声明或定义只能在全局
,命名空间
或类
范围内进行。即不能在局部
范围,函数
内进行,比如不能在main函数中声明或定义一个模板。
一、函数模板
1)函数模板的定义语法
template <class 形参名,class 形参名,...> 返回类型 函数名(参数列表)
{
//函数体
}
其中,template
和class
是关键字,class
可以用typename
关键字代替,在这里,typename
和class
没区别。尖括号<>
中的参数叫模板形参
,模板形参和函数形参很相像,但是,模板形参不能为空。一旦声明了函数模板
就可以用函数模板的形参名
声明类中的成员变量
和成员函数
,即可以在该函数中使用内置类型
的地方都可以使用模板形参名
。模板形参需要调用该函数模板时提供的模板实参
来初始化模板形参,一旦编译器
确定了实际的模板实参
类型就称其实例化了函数模板的一个实例
。如下为交换函数swap()
的函数模板:
template <class T> void swap(T& a, T& b){}
当调用这样的函数模板时,作为模板形参的类型T
就会被调用时所使用的类型
所代替,比如swap(a,b)其中a和b是int 型,这时函数模板swap中的模板形参T就会被内置类型int 所代替,函数模板就变为函数swap(int &a, int &b)。而当swap(c,d)中的c和d是double类型时,函数模板会被替换为函数swap(double &a, double &b),这样就实现了函数的实现与类型无关的代码。
2)注意事项
对于函数模板而言不存在 h(int,int) 这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行h(2,3)
这样的调用,或者int a, b; h(a,b)
。
二、类模板通式
1)类模板的定义语法
template <class 形参名,class 形参名,...> class 类名
{
//...
};
类模板和函数模板都是以template开始后接模板形参列表
组成,模板形参不能为空。一旦声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。如下为类A的类模板:
template<class T> class A
{
public:
T a;
T b;
T hy(T c, T &d);
};
在类A中声明了两个类型为T的成员变量a和b,还声明了一个返回类型为T带两个参数类型为T的函数hy。
2)类模板对象的创建
如对于类模板A,则使用类模板创建对象的方法为A<int> m;
,在类A后面跟上一个尖括号<>
并在里面填上相应的类型。如此,类A中凡是用到模板形参的地方都会被int 所代替。当类模板有两个模板形参时,创建对象的方法为A<int, double> m;
,类型之间用逗号隔开。
需要注意的是,对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;
用这种方法把模板形参设置为int是错误的(编译错误:error C2079: ‘a’ uses undefined class ‘A’),类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A m。
3)在类模板外部定义成员函数
template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表)
{
//函数体
}
比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:
template<class T1,class T2> void A<T1,T2>::h()
{
//函数体
}
需要注意的是,当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。
4)注意事项
模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
三、模板的形参
模板形参的类型有三种:类型形参,非类型形参和模板形参。
1)类型形参
1、类型模板形参:类型形参由关见字class或typename后接说明符构成,如template void h(T a){};其中T就是一个类型形参,类型形参的名字由用户自已确定。模板形参表示的是一个未知的类型。模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同,即可以用于指定返回类型,变量声明等。
2、不能为同一个模板类型形参指定两种不同的类型,比如templatevoid h(T a, T b){},语句调用h(2, 3.2)将出错,因为该语句给同一模板形参T指定了两种类型,第一个实参2把模板形参T指定为int,而第二个实参3.2把模板形参指定为double,两种类型的形参不一致,会出错。(针对函数模板)
3、当声明类对象为:A a,比如templateT g(T a, T b){},语句调用a.g(2, 3.2)在编译时不会出错,但会有警告,因为在声明类对象的时候已经将T转换为int类型,而第二个实参3.2把模板形参指定为double,在运行时,会对3.2进行强制类型转换为3。当我们声明类的对象为:A a,此时就不会有上述的警告,因为从int到double是自动类型转换。
2)非类型形参
1 、非类型模板形参:模板的非类型形参也就是内置类型形参,如template<class T, int a> class B{};其中int a就是非类型的模板形参。
2、 非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
3、 非类型模板的形参只能是整型,指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的。
4、 调用非类型模板形参的实参必须是一个常量表达式,即他必须能在编译时计算出结果。
5 、注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
6、 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
7 、sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
8 、当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如template <class T, int a> class A{};如果有int b,这时A<int, b> m;将出错,因为b不是常量,如果const int b,这时A<int, b> m;就是正确的,因为这时b是常量。
9 、非类型形参一般不应用于函数模板中,比如有函数模板template<class T, int a> void h(T b){},若使用h(2)调用会出现无法为非类型形参a推演出参数的错误,对这种模板函数可以用显示模板实参来解决,如用h<int, 3>(2)这样就把非类型形参a设置为整数3。显示模板实参在后面介绍。
10、 非类型模板形参的形参和实参间所允许的转换
(1)允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A<b> m;
即数组到指针的转换
(2)const修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m;
即从int *
到const int *
的转换。
(3)提升转换。如:template<int a> class A{}; const short b=2; A<b> m;
,即从short
到int
的提升转换
(4)整值转换。如:template<unsigned int a> class A{}; A<3> m;
,即从int
到unsigned int
的转换。
(5)常规转换。
3)类模板的默认模板类型形参
1、可以为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值。函数模板和类模板都可以为模板的非类型形参提供默认值。
2、类模板的类型形参默认值形式为:template<class T1, class T2=int> class A{};
,为第二个模板类型形参T2提供int型的默认值。
3、类模板类型形参默认值和函数的默认参数一样,如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值,比如template<class T1=int, class T2>class A{};
就是错误的,因为T1给出了默认值,而T2没有设定。
4、在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型。比如template<class T1, class T2=int> class A{public: void h();};
定义方法为template<class T1,class T2> void A<T1,T2>::h(){}
。