1.类模板的定义
类模板允许用户为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数或返回值能取任意数据类型。
定义格式:
template <class 标识符〉//如果有多个参数,用逗号隔开。
class 类名
{
…
};
比如
template <class T>
class Array
{ int low;
int high;
T *storage;
public:
Array(int lh = 0, int rh = 0): low(lh), high(rh)
{ storage = new T [high - low + 1]; }
Array(const Array &arr);
Array &operator=(const Array & a);
T & operator[](int index);
~Array() {delete [] storage; }
};
**成员函数的定义**
形式:
template<模板形参>
返回类型 类模板名<形式参数>::成员函数名(函数的形参表)
{
函数体
}
Array类的成员函数的格式
template <class T>
返回类型 Array<T>::函数名(形式参数表)
{函数体}
template <class T>
Array<T> &Array<T>::operator=(const Array<T> & a)
{ if (this == &a) return *this; //防止自己复制自己
delete [] storage; // 归还空间
low = a.low;
high = a.high;
storage = new T [high - low + 1];
for (int i=0; i <= high - low; ++i) storage[i] = a.storage[i];
return *this;
}
template <class T>
Array<T>::Array(const Array<T> &arr)
{ low = arr.low;
high = arr.high;
storage = new T [high - low + 1];
for (int i = 0; i < high -low + 1; ++i) storage[i] = arr.storage[i];
}
2.类模板的实例化
编译器从模板生成一个特定的类或函数的过程称为模板的实例化。 类模板实例化后形成一个模板类
类模板的实例化格式如下:
类模板名<模板的实际参数> 对象名; //需要显式实例化
比 如:
Array<int> array1(20,30);
Array<double> array2(10, 20);
3.模板的编译
编译模板时,编译器进行三个阶段的检查
- 第一阶段是编译模板定义本身。这个阶段编译器只是检查一些诸如漏掉分号、变量名拼写错误之类的语法错误。
- 第二阶段是编译器看到模板使用时。对于函数模板,检查实际参数的数目和类型是否恰当。对于类模板可以检测出提供的模板的实际参数的数目是否正确。
- 第三阶段是实例化。编译器彻底编译模板。
所以,调试包含类模板的程序时,必须在定义了类模板的对象并且对对象调用了所有的成员函数后,才能说明类模板的语法是正确的。
4.非类型模板参数
模板的形式参数不一定都是类型(int ,double等),也可以是非类型的参数。比如是具体数值1、2等。
在模板实例化时,类型参数用一个系统内置类型的名字或一个用户已定义类的名字作为实际参数,而非类型参数将用一个值作为实际参数。非类型的模板实参的值必须是编译时的常量。
比如:定义了一个安全的、可指定下标范围的、且下标范围必须是编译时的常量的类模板Array。(相当于C++中的普通数组)
template <class T, int low, int high>
class Array{
T storage[high - low + 1];
public: T & operator[](int index) ;
};
template <class T, int low, int high>
T & Array<T, low, high>::operator[](int index)
{ if (index < low || index > high)
{cout << "下标越界"; exit(-1); }
return storage[index - low];
}
定义一个下标范围为10到20的整型数组,可用下列语句:
Array< int,10,20> array;
参数的默认值
模板参数和普通的函数参数一样,也可以指定默认值。如果前例中的类模板Array经常被实例化为整型数组,则可在类模板定义时指定缺省值:
template <class T = int> class Array
{ … };
当要定义整型数组array时,就可以不指定模板的实参:
Array<> array;//如果明确指定参数类型,就使用int类型
5.类模板的友元
类模板可以声明两种友元:
- 声明普通的类或全局函数为所定义的类模板的友元。
- 声明某个类模板或函数模板的实例是所定义类模板的友元。
定义普通类或全局函数为所定义类模板的友元的声明格式如下所示:
template <class type>
class A {
friend class B;
friend void f();
…
};
上边这个定义声明了类B和全局函数f是类模板A的友元。B的所有的成员函数和全局函数f( )可以访问类模板A的所有实例的私有成员。
template <class T> friend class B; //类模板声明
template <class T> friend void f(const T &); //f 的声明
template <class type>
class A {
friend class B <int>;
friend void f (const int &);
…
};
上述代码将类模板B的一个实例,即模板参数为int时的那个实例作为类模板A的所有实例的友元。将函数模板f对应于模板参数为int的实例作为类模板A所有实例的友元。
template <class T> class B; //先声明 class B的存在
template <class T> void f(const T &); //先声明 f函数的存在
template <class type>
class A {
friend class B <type>; //声明class B是类A的友元类
friend void f (const type &); //声明函数f是类A的友元函数
…
};
上述代码声明说明了使用某一模板实参的类模板B和函数模板f的实例是使用同一个模板参数的类模板A的特定实例的友元。
当声明类模板B和函数模板f为类模板A的友元时,编译器必须知道有这样一个类模板和函数模板存在,并且知道类模板B和函数模板f的原理。因此必须在友元声明之前先声明B和f的存在。
例子:给前文定义的模板类Array添加一个输出运算符重载函数,可以直接输出数组的所有元素
分析:由于Array是一个模板类,可以用于不同类型的数据,因此对于的输出运算符重载函数也应该是函数模板。定义如下:
template<class type>
ostream &operator<<(ostream &os, const Array<type> &obj)
{ os << endl;
for (int i=0; i < obj.high - obj.low + 1; ++i)
os << obj.storage[i] << '\t';
return os;
}
增加了输出运算符重载函数的模板类Array的定义
template <class T> class Array; //类模板Array的声明
template<class T> ostream &operator<<(ostream &os,const Array<T>&obj);//输出重载声明
template <class T>
class Array
{
friend ostream &operator<<(ostream &, const Array<T> &);
private:
int low;
int high;
T *storage;
public:
……
}