类模板
从上一篇的博客里面我们可以清楚的看到模板在编程中的广泛应用。那么既然函数有模板,我们的类当然也有模板。
下面的内容,主要翻译自 http://www.cplusplus.com/doc/oldtutorial/templates/
我们还可以编写类模板,以便一个类可以使用模板参数作为类型的成员。例如我们熟知的map类:
template <class T>
class mypair {
T values [2];
public:
/*构造函数*/
mypair (T first, T second)
{
values[0]=first; values[1]=second;
}
};
我们刚刚定义的类用于存储任何有效类型的两个元素。例如,如果我们想声明一个这个类的对象来存储两个值为115和36的int类型的整数值,我们将会写:
mypair<int> myInt (115, 36);
这个同样的类也可以用来创建一个存储任何其他类型的对象:
mypair<double> myDouble (3.14, 2.56);
上一个类模板中唯一的成员函数已经在类声明本身内定义, 如果我们在类模板的声明之外定义一个函数成员,我们必须始终在该定义之前使用模板< >前缀:
// class templates
#include <iostream>
using namespace std;
template <class T>
class mypair {
T a, b;
public:
//构造函数
mypair (T first, T second)
{a=first; b=second;}
T getmax (); //这个函数我们是定义在类里面
};
/*
*我们在类外定义,要加上<T>,指定我们的类型
*这个函数我们比较pair中的较大者
*/
template <class T>
T mypair<T>::getmax ()
{
T retval;
retval = a>b? a : b;
return retval;
}
int main () {
mypair <int> myobject (100, 75);
cout << myobject.getmax();
return 0;
}
定义成员函数的语法:
template <class T>
T mypair<T>::getmax () ;
被这么多T迷惑了? 在这个声明中有三个T:第一个是模板参数。 第二个T指的是函数返回的类型。 而第三个T(尖括号之间)也是一个要求不能省略:它指定该函数的template参数也是类模板参数。
模板特殊化
如果我们要在将特定类型作为模板参数传递时为模板定义不同的实现,那么我们可以声明该模板的专业化。
例如,假设我们有一个非常简单的类名为mycontainer的类,它可以存储任何类型的一个元素,并且它只有一个成员函数叫做增量(increase),它增加了它的值。 但是我们发现,当它存储一个类型为char的元素时,使用函数成员大写一个完全不同的实现会更方便,所以我们决定为该类型声明一个类模板专门化:
// template specialization
#include <iostream>
using namespace std;
// class template:
template <class T>
class mycontainer {
T element;
public:
mycontainer (T arg) {element=arg;}
T increase () {return ++element;}
};
/*
*自定义特殊的类型的方法
*/
template <>
class mycontainer <char> { //指定特殊处理的类型为char型
char element;
public:
mycontainer (char arg) {element=arg;}
char uppercase ()
{
if ((element>='a')&&(element<='z'))
element+='A'-'a';
return element;
}
};
int main () {
mycontainer<int> myint (7);
mycontainer<char> mychar ('j');
cout << myint.increase() << endl;
cout << mychar.uppercase() << endl;
return 0;
}
类里面的模板特殊化的模板为:
template <> class mycontainer <char> { ... };
首先,请注意,我们在类模板名称之前有一个空模板<>参数列表。 这是为了明确地将其声明为模板专门(特殊)化。
但是比这个前缀更重要的是类模板名称后面的 specialization参数。 这个专业化参数本身标识了我们要声明一个模板类特殊(char)的类型。 注意通用类模板和专业化之间的区别:
template <class T> class mycontainer { ... };//通用类
template <> class mycontainer <char> { ... };//特殊类
当我们为模板类声明特殊类时,我们还必须定义所有的成员,甚至是与通用模板类完全相同的成员,因为从通用模板到特殊化的成员中并没有涉及“继承”。
没有参数类型的模板
除了代表类型的类或typename关键字之前的模板参数,模板还可以具有与函数中类似的常规类型参数。 作为示例,请查看此类模板,该模板用于包含元素序列:
// sequence template
#include <iostream>
using namespace std;
/*
*这个模板含一个默认类型的参数,我们在使用的时候不用指定
*setmember将 value的值赋值给 member中的对应的值
*getmember获取当前的值对应的value值,相当于map中的key
*/
template <class T, int N>
class mysequence {
T memblock [N];
public:
void setmember (int x, T value);
T getmember (int x);
};
template <class T, int N>
void mysequence<T,N>::setmember (int x, T value) {
memblock[x]=value; //将value的值赋值给,member[x]对应的值
}
template <class T, int N>
T mysequence<T,N>::getmember (int x) {
return memblock[x];
}
int main () {
mysequence <int,5> myints;
mysequence <double,5> myfloats;
//下面的操作类似与map中的操作
myints.setmember (0,100);
myfloats.setmember (3,3.1416);
cout << myints.getmember(0) << '\n';
cout << myfloats.getmember(3) << '\n';
return 0;
}
也可以为类模板参数设置默认值或类型。 例如,如果以前的类模板定义是:
template <class T=char, int N=10> class mysequence {..};
注意,为模板设置默认类是为T直接赋值。我们可以使用默认模板参数创建对象,方法是声明:
mysequence<> myseq;
等价于
mysequence<char,10> myseq;
模板与头文件
从编译器的角度来看,模板不是正常的函数或类。它们是按需编译的,这意味着在需要具有特定模板参数的实例化之前,不会编译模板函数的代码。在那一刻,当需要实例化时,编译器会从模板中为这些参数专门生成一个函数。
当项目增长时,通常会在不同的源代码文件中分割程序的代码。在这些情况下,界面和实现通常是分开的。以函数库为例,界面通常由可以调用的所有函数的原型声明组成。这些通常在扩展名为.h的“头文件”中声明,并且实现(这些函数的定义)在具有c ++代码的独立文件中。
因为模板在需要时被编译,所以这迫使对多文件项目的限制:模板类或函数的实现(定义)必须在与其声明相同的文件中。这意味着我们不能在单独的头文件中分离接口,并且我们必须将接口和实现都包含在使用模板的任何文件中。(我们可以用include命令来包含代码,就像我们之前的XXXpriv.h文件)
由于在需要时模板被实例化之前没有生成代码,因此编译器准备允许在项目中包含不止一次具有项目声明和定义的相同模板文件,而不会产生链接错误。