我们在编写不同类型的参数的类时,特别是对于容器类,除了想保存的对象类型不同外,代码都相同,这时候我们希望一种泛型的类,我们可以将具体的类型用参数的形式传递给它,而不用每次都去实现。
c++的类模板为生成通用的类声明提供了一种很好的方法。这也是代码重用的重要的一部分。
首先怎样定义一个模板类:
template <class Type>
关键字template 是告诉编译器,将定义一个模板。尖括号内容相当于是参数列表,关键字class 看做是变量的类型名,Type是变量的名称。
当然了使用class并不意味着只能用类做参数,这只是表明Type是一个通用的类型说明符,在模板的实例化是将使用具体的类型替代它,我们也可以使用typename 来代替class
template <typename Type>
<span style="font-size:18px;">template <class Type>
class Stack{
private:
Type items[10];
int top;
public:
Stack();
bool push(const Type & item);
...
};
template <class Type> //每个函数头都将以模板声明开始
Stack<Type>::Stack(){ //类限定符也要加上模板类型
top = 0;
}
template <class Type>
bool Stack<Type>::push(const Type & item){
...
}</span>
重要的一个概念就是:
模板不是类,也不是成员函数。它们只是c++编译器指令,说明了如何生成类和成员函数。模板的具体实现是由实例化和具体化 来完成的。
我们一般将模板信息放入头文件中,在使用的时候包含头文件。
接下来就是如何使用模板类,我们需要声明一个类型为模板类的对象,方法使用具体的类型作为参数。
比如,Stack<int > st; Stack<double> st2;
使用Int 来替换Type ,double 来替换Type。
注意我们赋给类型参数的只能是类型,不能是数字。必须显示的提供所需的类型。
模板类扩展:
1,模板中非类型参数的使用
template <class T,int n>
关键字class指出T为类型参数,int 指出n的类型为Int ,这种参数称为非类型参数(表达式参数)。
表达式参数有一些限制:表达式参数可以是整型,枚举,引用,指针。模板代码不可以修改参数的值,也不能使用参数的地址。比如n++ ,&n等。
2,模板的多功能性
递归使用模板:对于数组模板Array,可以Array< Array<int ,5> , 10> twodee;
这样我们就得到一个包含10个元素的数组,每个元素都是包含5个Int 元素的数组。
模板可以包含多个类型参数:template <class T1,class T2>;
默认类型参数模板:template <class T1,class T2 = int> class topo;
如果我们实例化的时候省略T2,编译器将使用int。
模板的具体化:
具体化可以分为隐式实例化,显示实例化,显示具体化,部分具体化。
1,隐式实例化
指出所需类型,声明一个或多个对象,主要用途是创建对象。
pt = new Array<double,20>;
编译器生成类定义,然后根据定义创建一个对象。
2,显示实例化
使用关键字template 并指出所需类型来声明类。
template class Array<string,20>;
没有创建对象,只是生成类声明。
3,显示具体化
它是针对特定类型的定义。具有具体定义的模板,不同于泛型定义模板
template <> class Array(const char *) {...};
这是一个专供char * 类型使用的模板。
在实例化时遵循一个原则:当具体化模板和通用模板与实例化匹配时,编译器优先使用具体化模板。
4,部分具体化
给类型参数的其中之一指定具体类型
template <class T1> class Pair<T1,int > {...};
关键字template 后面的<>声明里是没有被具体化的类型参数,如果为空,表示显示具体化、
template <> class Pair<int ,int> {...};
如果有多个模板可供选择,编译器将使用具体化程度最高的模板。
模板的使用场景:
1,模板做为成员
template <class T>
class Bete{
private:
template <class V>
class hold{
private:
V val;
public:
hold(V v = 0):val(v){ }
void show() const {cout << val << endl;}
};
hold <T> q;
hold <int > n;
public:
beta(T t,int i):q(t),n(i){}
...
};
这是beta模板中声明和定义hold方法,q成员是基于类型T的hold对象。
如果在beta模板中声明hold方法,在外面定义它,就会出现模板嵌套。
tempate <class T>
class Bete{
private:
template <class V>
class hold;
hold<T> q;
hold <int> n;
...
};
template <class T>
template <class V>
class Bete<T>::hold{
private:
V val;
public:
...
};
2,模板作为参数
模板可以包含本身就是模板的参数。
template <template <typename T> class Thing>
class Crab{
Thing <int > s1;
Thing <int > s2;
...
};
template <typename T>
class King{
...
};
模板参数是emplate <typename T> class Thing
我们将用KIng<int > 替换Thing<int>
模板和友元:
模板的友元分为三类:非模板友元,约束模板友元,非约束模板友元
1,非模板友元
将模板类中一个常规函数声明为友元,称为模板所有实例化的友元。
为友元函数提供模板类参数,如果要使用,需要显示具体化。
template <class T>
class HasFriend{
pulbic:
friend void reports(HasFriend<T> &);
...
};
void reports(HasFriend <int > & hf){ }
void reprots(HasFriend <double> & hf) {}
report()并不是模板函数,只是使用一个模板类参数,定义的时候,需要传递给它具体的类型。上面定义的两个report()函数,分别是两个特定HasFriend具体化的友元。
2,模板类的约束模板友元函数
友元函数成为模板,友元类型取决于类被实例化的类型。
template <class T> void couts();
template <class T> void reports(T &); // 第一步,声明模板函数
template <class TT>
class HasFriend{
friend void couts<TT> (); //第二步,将模板声明为友元
friend void reports<> (HasFriend<TT> &);
};
template <class T> //第三步,提供模板定义
void couts{
cout << ... << endl;
}
couts()函数没有参数,因此要使用<>来指明具体化类型,reports()函数有参数,可以从参数推断出模板类型参数,所以<>可以为空。
在使用的时候,couts<int >(),couts<double> (),
HasFriend<int> hf1;
HasFrined<double> hf2;
reports(hf1); //根据函数参数类型
reports(hf2);
3,非约束模板友元函数
在类内部声明模板,每个函数具体化都是每个类具体化的友元。
tempalet <class T>
class Many{
template <class C,class D>friend void show(C & ,D &);
};
template <class C,class D> void show(C & c,D & d){
cout << .. << endl;
}
show()函数是所有具体化的友元。
所有这些机制,都是为了让我们能够重用经过测试的代码。