简介
(1)模板是允许以通用类型的方式来编写程序,其中的通用类型可以是int,double等具体类型。通俗的说使用模板就可以让程序猿编写与类型无关的代码。这种编程方式有时被称为通用编程,而由于类型是参数表示的,因此,模板特性有时也被称为参数化类型。
(2)模板通常有两种形式,一种是函数模板,一种是类模板。
(3)模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
函数模板
模板格式
template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表)
{
函数体
}
以swap函数为例:
template<class T>
void swap(T& a,T& b)
{
T temp;
temp = a;
a = b;
b = temp;
}
要建立一个模板,
①关键字template和class是必须的。当然可以使用typename替换class。
②必须使用尖括号。
③类型名T可以任意选择,只要符合C++的命名规则。
模板使用
函数模板的使用很简单,例如对于swap函数的使用,swap(3,5); 此时编译器就会生成如下代码:
void swap(int& a,int& b)
{
int temp;
temp = a;
a = b;
b = temp;
}
注意:①函数模板不能缩减可执行程序。使用模板的好处是他是生成多个函数的定义更简单。更可靠。
②上面swap如果出现swap(1,2.1)则会则会出现错误。通过声明我们知道swap函数的两个类型应该是一致的。
模板重载
我们可以像重载常规函数那样来重载模板函数。
声明:
template<class T>
void swap(T& a,T& b);
template<class T>
void swap(T* a,T* b,int n);
定义:
template<class T>
void swap(T& a,T& b)
{
int temp;
temp = a;
a = b;
b = temp;
}
template<class T>
void swap(T* a,T* b,int n)
{
T temp;
for(int i = 0;i < n;i++)
{
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
注意:①并不是所有的模板参数都必须是模板参数类型,也可以是具体类型。具体类型形参在模板定义的内部是常量值,也就是说具体类型形参在模板的内部是常量。 非类型模板的形参只能是整型,枚举,指针和引用,像int,double, String 这样的类型是不允许的。但是int&,int*,对象的引用或指针是正确的.调用具体类型模板形参的实参必须是一个常量表达式,即它必须能在编译时计算出结果。全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。另外,sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。另外。模板代码不能修改参数的值,也不能使用参数的地址。
②当模板函数和常规函数都存在时,并且在调用是函数名和参数都符合,那么优先调用的是常规函数。
显示具体化
在使用swap函数时,我们会遇到交换两个对象或者结构体的值,也会遇到交换两个对象或者结构体中某些变量的值,也就是说并不是交换所有变量的值。这种情况下,模板可能就不够用,所以我们可以通过提供一个具体化函数定义来实现需要的功能。这种方式就是显示具体化。
(1)对于给定的函数名,可以有模板函数,常规函数和显示具体化函数以及重载函数。
(2)显示具体化的原型和定义应以template<>开头,并明确指定类型。
(3)具体化将覆盖常规模板,而常规函数将覆盖具体化和常规模板。
以下是三种形式的swap函数:
常规函数:
void swap(int&,int&);
模板函数:
template<class T>
void swap(T& a,T& b);
显示具体化:
template<>
void swap<int>(int&,int&)
(4)具体化与实例化
①编译器通过模板为特定类型的生成函数定义时,得到的是模板实例。模板并非函数定义,但使用具体类型的模板实例是函数定义。这是实例化方式被称为隐式实例化。想对应的,显示实例化的句法如下:
template void swap<int>(int,int);
上面语句的含义是使用swap模板生成一哥使用int类型的实例,也就是’使用swap模板生成int类型的函数定义’。
②与显示实例化不同,显示具体化的句法如下:
template<> void swap<int>(int&,int&);
template<> void swap(int&,int&);
上面的两个声明等价,显示具体化与显示实例化的区别在于,显示具体化它的含义是‘不要使用模板来生成函数定义,而应使用独立的,专门的函数定义显示的为特定类型生成函数定义’。显示具体化,必须有自己的函数定义。显示具体化声明在template后有<>,而显示实例化没有。
③隐式实例化,显示实例化和显示具体化统称为具体化,他们的相同处在于。它们的表示都是使用具体类型的函数定义,而不是通用描述。
调用哪个函数
对于函数重载,函数模板和函数模板重载,我们在调用函数时,编译器到底调用的是哪个函数??
编译器的这个过程被称为重载解析。具体流程如下:
(1)创建候选函数列表,函数列表包含于被调用函数的名称相同的函数或者模板函数。
(2)利用候选函数列表创建可行函数列表,这些都是函数参数数目正确的函数。这里有个隐式的转化序列,其中包括实参类型与相应的形参类型完全匹配的情况。例如使用float实参可以将float转换为double,从而与double类型形参匹配,就可以使用double类型的模板为float类型生成一个实例。
(3)确定是否有最佳的可行函数。没有则函数调用错误。
参数匹配最佳到最差的顺序如下:
①完全匹配,常规函数优于模板函数。
②提升转换(char,short->int,float->double)
③标准转换(int->char,long->double)
④用户定义的转换,如类声明中定义的转换。
实参 | 形参 |
---|---|
Type | Type& |
Type& | Type |
Type[] | Type* |
Type | const Type |
Type | volatile Type |
Type* | const Type |
Type* | volatile Type* |
在满足完全匹配时,编译器就能找到最佳的调用函数,除非找到多个函数完全匹配。但有两种情况下,编译器仍能完成重载解析。
①指向非const数据的指针和引用优先于非const指针和引用参数匹配。不过,const于非const的区别只适用于指针和引用指向的数据。
②一种是模板函数,另一种是非模板函数。这种情况下,非模板函数优于模板函数。(包括显示具体化)
如果两个完全匹配的函数是两个模板函数,则较具体的模板函数优先,也就是说编译器推断使用哪种类型时,执行的转换最少。
类模板
模板格式
template<class 形参名,class 形参名,…>
class 类名{...};
示例如下:
template<class T>
class stack
{
private:
enum{MAX=10};
T items[MAX];
int top;
int stacksize;
public:
stack();
bool isempty();
bool isfull();
bool push(const T& item);
bool pop(T & item);
stack& operator=(const stack& st);
}
template<class T>
stack<T>::stack()
{
top = 0;
}
template<class T>
bool stack<T>::isempty()
{
return top == 0;
}
template<class T>
bool stack<T>::isfull()
{
return top == MAX;
}
template<class T>
bool stack<T>::push(const T& item)
{
if(top < MAX)
{
items[top++] = item;
return true;
}
else
return false;
}
template<class T>
bool stack<T>::pop(const T& item)
{
if(top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
template<class T>
stack<T>& stack<T>::operator=(const stack<T>& st)
{
if(this == &st)
return *this;
delete [] items;
stacksize = st.stacksize;
top = st.top;
items = new T[stacksize];
for(int i = 0;i < top;i++)
items[i] = st.items[i];
return *this;
}
模板使用
使用方式如下:
stack<int> test1;
stack<string> test2;
特性
(1)可以为类型提供默认值
template<class T1,class T2=int>
class test{...};
虽然可以为类模板提供默认类型,但是却不可以为函数模板提供默认类型,但是却可以为二者的非参数类型提供默认值。
(2)递归调用模板
stack< stack<int> > test;
(3)模板可以使用多个类型参数
template<class T1,class T2>
class test{...};
模板的具体化
(1)隐式实例化
声明了一个或多个对象,指出所需类型,编译器通过模板生成具体类型的类声明。
(2)显式实例化
当使用template关键字并指出所需类型来声明类时,编译器将生成类声明的显式实例化,实例如下:
template class stack<int>;
这种情况下,虽然没有创建或者提及类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板生成具体化。
(3)显示具体化
显示具体化是特性类型(用于替换模板中的通用类型)的定义,有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同,在这种情况下,可以创建显示实例化。
显示具体化模板格式:
template<> class classname<>{...};
(4)部分具体化
template<class T> class<T,int>{...};
如果有多个模板可供选择,编译器将会选择具体化程度最高的模板。
除了上面的方式,也可以使用指针对模板进行部分具体化。
template<class T>
class test{...};
template<class T*>
class test{...};
如果提供的类型不是指针将会使用上面的模板,如果使用的是指针,将会使用下面的模板。
成员模板
C++中模板可用做结构,类或者模板类的成员。
template <class T>
class test
{
private:
template<class T1>
class test_1
{
private:
T1 val;
public:
test_1(T1 v = 0):val(v){}
void show() const {cout<<val<<endl;}
T1 Value() const {return val;}
};
test_1<T> q;
test_1<int> n;
public:
test(T t,int i):q(t),n(i){}
template<class U>
U test2(U u,T t){return ...}
void show() const {q.show();n.show();}
};
将模板用作参数
template<template <typenameT> class Thing>
class crab
{
private:
Thing<int> s1;
Thing<double> s2;
public:
Crab(){};
bool push(int a,double x){return s1.push(a)&&s2.push(b);}
bool pop(int& a,double& x){return s1.pop(a)&&s2.pop(x);}
}
int main()
{
Crab<stack> test;
test.push(4,3.5);
test.pop(4,3.5);
return 0;
}
模板类与友元
模板的友元分三类:
1.非模板友元
2.约束模板友元,即友元的类型取决于类被实例化的类型。
3.非约束模板友元,即友元的所有具体化都是类的每一个具体化的友元。
模板类的非模板友元函数
template<class T>
class HasFriend
{
friend void counts();
};
上面的声明使counts函数成为模板所有实例化的友元。
如果友元函数的参数是模板本身,该怎么定义??
template<class T>
class HasFriend
{
friend void counts(HasFriend<T> &);
}
上面的声明含义,带HasFriend参数的counts()将成为HasFriend类的友元,同样,带HasFriend参数的counts()函数将是counts的一个重载版本,也是HasFriend类的友元。
模板类的约束模板友元函数
为约束模板友元做准备,要是类的每一个具体化都获得与友元匹配的具体化。具体步骤为三步。
(1)首先,在类定义的前面声明每个模板函数
template void counts();
template void report(T &);
(2)然后在函数中再次将模板声明为友元,这些语句根据类模板的参数的类型声明具体化:
template<class TT>
class HasFriend
{
friend void counts<TT>();
friend void report<>(HasFriend<TT>&);
};
(3)为友元提供模板定义。
模板类的非约束模板友元函数
约束模板友元函数是在类外面声明的模板的具体化。int类具体化获得int函数的具体化,以此类推,通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化都是每个类的具体化友元。
template<class T>
class HasFriend
{
template<class C,class D> friend void show(C&,D&);
};