定义一个类模板并实例化
函数可以定义模板,对于类来说,也可以定义一个类模板,类模板是针对成员数据类型不同的类的抽象,它不是代表一个具体的实际的类,而是一个类型的类,一个类模板可以生成多种具体的类,定义类模板的格式如下所示:
template<typename 形参名,typename 形参名…>
class 类名
{
………
}
类模板中的关键字含义与函数模板相同。需要注意的是,类模板的模板形参不能为空,一旦声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用数据类型的地方都可以使用模板形参名来声明,例如:
template<typename T>
class A
{
public:
T a;
T b;
T func(T a, T b);
};
上述代码中,在类A中声明了两个T类型的成员变量a和b,还声明了一个返回类型为T带两个T类型参数的成员函数func()。
由于类模板包含类型参数,因此也称为参数化类,如果说类是对象的抽象,对象是类的实例,则类模板是类的抽象,类是类模板的实例。
定义了类模板后就要使用类模板创建对象以及实现类中的成员函数,这个过程其实也是类模板实例化的过程,实例化出的具体类称为模板类。如果用类模板创建类的对象,如用上述定义的模板类A创建对象,则在类A后面跟上一个<>,并在里面表明相应的类型,格式如下所示:
A<int> a;
这样类A中凡是用到模板形参的地方都会被int类型所代替。当类模板有两个模板形参时,创建对象时,类型之间要用逗号分隔开,如定义一个有两个模板形参的类B,然后用B创建类对象,示例代码如下所示:
template<typename T1, typename T2>
class B
{
public:
T1 a;
T2 b;
T1 func(T1 a, T2& b);
};
B<int,string> b; //创建模板类B的一个对象
使用类模板时,必须要为模板形参显式指定实参,不存在实参推演过程,也就是说不存在将整型值10推演为int类型传递给模板形参,必须要在<>中指定int类型,这一点与函数模板不同。为了加深读者对类模板的理解与使用,我们可以定义一个类模板来实现两个数比较大小,具体代码如例1所示。
例1
1 #include <iostream>
2 #include <string>
3 using namespace std;
4 template<typename T>
5 class Compare
6 {
7 private:
8 T t1, t2;
9 public:
10 Compare(T a, T b) :t1(a), t2(b){}
11 T max(){ return t1 > t2 ? t1 : t2; }
12 T min(){ return t1 < t2 ? t1 : t2; }
13 };
14 int main()
15 {
16 Compare<int> c1(1, 2); //定义int类型的类对象
17 cout <<"int max: "<< c1.max() << endl;
18 Compare<double> c2(1.2, 3.4); //定义double类型的对象
19 cout <<"double min: "<< c2.min() << endl;
20 Compare<char> c3('a', 'b');// 定义char类型的对象
21 cout <<"char max: "<< c3.max() << endl;
22 system("pause");
23 return 0;
24 }
运行结果如图1所示。
图1 例1运行结果
在例1中创建类对象时,在类名后<>中指定模板形参的类型,编译器先根据类型实例化出一个具体的类,然后再创建这个具体实例的对象。如本例中Compare<int> c1(1, 2);语句,在编译时,先根据<>中的int来创建一个类,如下所示:
class Compare
{
private:
int t1, t2;
public:
Compare(int a, int b) :t1(a), t2(b){}
int max(){ return t1 > t2 ? t1 : t2; }
int min(){ return t1 < t2 ? t1 : t2; }
};
然后再根据这个类创建对象c1,这个过程也和函数模板是一样的,都不会减少最终执行程序的代码。
在学习函数模板时我们知道,对于类型模板参数,如果对函数模板add(T t1, T t2)进行add(1,1.2)调用则编译器会报错,因为指定了两种类型的参数编译器无法确定到底依据哪个参数类型来调用。但对于类模板来说,编译器则不会报错,例如下面类模板的定义:
template<typename T>
class A
{
public:
A(){}
T add(T t1, T t2)
{
return t1 + t2;
}
};
如果定义A<int> a对象,则a.add(1,1.2)调用则不会报错,因为在定义a对象时就已经指定是int类型,当add()函数模板实例化时也会实例化出一个int类型的函数,它会自动将double类型的1.2转换为int类型的1。
多学一招:模板声明或定义的作用域
模板的声明或定义只能在全局、命名空间或类范围内进行,不能在局部范围、函数内进行,比如不能在main()函数中声明或定义一个模板。声明或定义一个模板还有以下几点需要注意:
(1)如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏。
(2)模板参数名不能被当作类模板定义中类成员的名字。
(3)同一个模板参数名在模板参数表中只能出现一次。
(4)在不同的类模板声明或定义中,模板参数名可以被重复使用。