C++模版分为函数模版和类模版。其实模版就是一种对类型进行参数化的工具。
一、函数模板
1. 函数模板的声明定义
Template<typename/class T1,typename/class T2,….>
返回类型 函数名(函数参数){
函数体
}
例如:
template<typename T>
T add(T t,T t1)
{
return t+t1;
}
2. 模板函数的两种调用方式
(1)隐式调用
int i=1,j=2;
int k=add(i,j);
默认是add函数的两个参数是同一类型,系统根据实际调用的时候的实参的类型来确定形参的具体类型。注意这里的两个参数类型必须相同。
不可以:
int i=1,j=2;
int k=add(1,2.2);//error
(2)显示调用
int i=1,j=2;
int k=add<int>(i,j);
首先指定函数模板的具体类型,再去调用。这样就可以:
int k=add<int>(1,2.3);//OK
这里和隐式调用的区别:显示调用就是先指定函数模板的具体类型,则函数就变成了具体类型的函数了,intadd(int t,int t1)则,再去调用add<int>(1,2.3);显然就没有问题了。只是系统进行了一下类型转换而已。
二、类模板
1. 类模板的声明定义
Template<typename/class T1, typename/class T2,….>
Class 类名{
类的成员
}
例如:
template<typename T>
class A
{
public:A(T b):a(b){}
T swap(T& i,T& j){
T temp;
temp=i;
i=j;
j=temp;
}
void foo(Tt);
private: T a;
};
template<typename T>
void A<T>::foo(T t)
{cout<<t<<endl;}
(1) 注意类模板的成员函数,如果在类外实现,必须在实现的前面加上template<…>。而且函数名前加”类名<T1[,T2,…]>”。
(2)当在类中,想要使用自身类的对象时(例如:copy构造、operator=)时,直接使用类名就可以了。例如:
class A
{
public:A(T b):a(b){}
A(const A&c){
a=c.a;
}
private: T a;
};
(3)当在类外,想要使用这个类名的时候,必须要“类名+<T,….>”。例如成员函数的类外实现的时候,函数名前加的东西。
class A
{
public:A(T b):a(b){}
void foo(Ac);
private: T a;
};
template<typename T>
void A<T>::foo(A<T>t)
{cout<<t<<endl;}
(4)有时在类中,想使用同一模板的其他类型的类的对象。例如:
class A
{
public:A(T b):a(b){}
T getm(){returna;}
template<typename Y>
voidfoo(A<Y>& c){
cout<<c.getm()<<endl;
}
private: T a;
};
int _tmain(int argc, _TCHAR* argv[])
{
A<int> a(2);
A<double> b(3);
a.foo(b);
system("pause");
return 0;
}
在函数的前面加template<typenameY>,注意这里的Y不能和类模板时的T相同。
这种情况常常在重写copy和operator=时,使用子类的对象给父类的对象copy构造、赋值时,会经常用到这个。具体可见auto_ptr的源码。
2. 类模板的调用
A<int>a(2);
定义类对象时,先声明类的具体类型。
(1) 和函数模板隐式调用不一样的地方,可以:
template<typename T>
class A
{
public:A(T b):a(b){}
T add(T t1, T t2)
{returnt1+t2;}
private: T a;
};
int _tmain(int argc, _TCHAR* argv[])
{
A<int> a(2);
a.add(2,3.4);//OK
system("pause");
return 0;
}
这里注意类模板的原理:由于类模板在定义的时候,已经确定了T的类型,所以在定义了A<int> a(2)之后,a中的T都变成了int,所以这里add函数就变成了int add(int t1,int t2).这显然就可以add(2,3.4)调用了,符合系统的默认类型转换。
三、非类型形参
当template<typenameT,int In>。出现了int这样的具体类型的时候,In就是非类型形参。例如:
template<typename T,int In>
class A
{
public:A(){a=new T[In];a[0]=3;}
void foo()
{cout<<a[0]<<endl;}
~A(){delete[]a;}
private: T* a;
};
int _tmain(int argc, _TCHAR* argv[])
{
A<int,4> a;
a.foo();
system("pause");
return 0;
}
1. 注意非类型形参In在内部是常量值,在上面代码中也可以看出。
2. 非类型形参只能是整型(包括int,short,unsigned int),指针和引用。就是说不可以是float/double/string,但可以是double*/double&/对象的指针、引用也是可以的。
class B{};
template<typename T,doubleIn>class A{};//ERROR
template<typename T,floatIn>class A{};//ERROR
template<typename T,B In>classA{};//ERROR
template<typename T,double&In>class A{};//OK
template<typename T,double*In>class A{};//OK
template<typename T,B* In>classA{};//OK
3. 调用非类型形参的实参必须是一个常量表达式。编译时能计算出结果。(注意sizeof的返回值也是常量,可以使用)
注意:何为常量表达式?(值不会改变,且在编译阶段就能知道确切值的)
const int 、常数、枚举。全局变量的引用/地址、全局对象的引用/地址。
不为常量表达式:
局部变量,局部对象,局部对象的地址/引用,全局变量,全局对象都不是常量表达式。
总结:首先非类型的形参只能是整型,指针,引用。这也决定了实参也必须只能是这些,除此之外,实参还必须是一个常量表达式。所以这样一汇总:实参只能是const int、常数、枚举、全局变量的引用/地址、全局对象的引用/地址。
也可以:
template<int* T>
class A{};
int d[]={2};
int _tmain(int argc, _TCHAR* argv[])
{
A<d>a;//----------OK
system("pause");
return 0;
}
4. 有非类型形参的模板在类外实现成员函数时:
其实对于所有类型的类模板,在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。
template<int* a>
class A{
public:void foo();
};
template<int* a> -----在类外不要随便修改a的名称。保持和定义时一致
void A<a>::foo(){}
四、可以为类模板提供类型的默认值
template<typename T=int>
class A{};
int _tmain(int argc, _TCHAR* argv[])
{
A<>a;//OK
A<double> b;//OK
system("pause");
return 0;
}
1. 注意可以为类模板提供类型默认值,不可以为函数模板提供类型默认值。
template<typename T=int>//ERROR
void foo(){}
2. 注意当有多个类型形参,则从第一个提供默认值的形参后面的形参,都要提供默认值。
template<typename T=int,typename T1>//ERROR
class A{};
五、模板的特化
1. 模板的特化就是给一个已存在的模板进行特殊类型时,执行特别的操作。
template<typename T>
class A{
public:void foo(){cout<<"T"<<endl;}
};
template<>//模板的特化,当T为int时,执行的是这个,当其他类型时,执行的是上面
class A<int>{
public:void foo(){cout<<"int"<<endl;}
};
int _tmain(int argc, _TCHAR* argv[])
{
A<int> a;//执行的是下面那个特化模板
a.foo();
A<double> b;//执行的是上面那个普通模板
b.foo();
system("pause");
return 0;
}
2. 模板的偏特化,也叫部分特化。当模板有多个形参时,对其中一部分进行特化。与上面的全部特化想对应。
//1. 标准模板类。
template<typename T1, typenameT2>
class MyClass {
... ...
};
//2. 两个模板参数具有相同类型的部分特化类。
template<typename T>
class MyClass<T,T> {
... ...
};
//3. 第二个类型参数是int
template<typename T>
class MyClass<T,int> {
... ...
};
//4. 两个模板参数都是指针。
template<typename T1,typenameT2>
classMyClass<T1*,T2*> {
... ...
};
//5. 两个模板参数都是相同类型的指针。
template<typename T>
class MyClass<T*,T*>{
... ...
};
//6. 模板的全部特化,上面2~5为部分特化
template<>
class MyClass<int,double>{
......
};
int _tmain(int argc, _TCHAR* argv[])
{
MyClass<int,float>c1; //调用MyClass<T1,T2>
MyClass<float,float> c2; //调用MyClass<T,T>
MyClass<float,int> c3; //调用MyClass<T,int>
MyClass<int*,float*>c4; //调用MyClass<T1*,T2*>
MyClass<int*,int*>c5; //调用MyClass<T*,T*>
MyClass<int,double>c6; //调用MyClass<int,double>
system("pause");
return 0;
}
全部特化一般都是template<>。中括号里没有东西,把所有的形参全部特化。
部分特化一般都是template<T,…>。中括号里有东西,没有把所有的形参都特化。
函数模板同理也可以特化。
六、函数模板的重载
类模板是不可以重载的,而函数模板可以重载。
例子:
template<typename T>
void foo(T)
{cout<<"template"<<endl;}
void foo(int)
{cout<<"foo"<<endl;}
int _tmain(int argc, _TCHAR* argv[])
{
int i=2;
double j=2.0;
foo(i);//调用的是foo(int)------(1)
foo(j);//调用的是template<>foo(T)----------(2)
system("pause");
return 0;
}
和特化类似,只是类模板不可以重载。(废话,类能重载吗)
重载选择的顺序:“越特殊越优先高”
显然如果(1)这么写的话:”foo<int>(i);”就是显式的调用函数模板,这样就调用的tempalte<>foo(T);了。