类模板浅析

 
一、为什么使用类模板
如果一个类,它对自己要使用到的数据类型不确定,也可以理解成为了提高类的通用性,碰到这种情况时,我们就可以考虑使用类模板。例如C++的类List、Vector,它们实际上就是一种类模板。
以队列类为例进行说明:
队列是一种专门用于对象集合的数据结构,它的行为是先进先出。
Class Queue
{
       public:
              Queue( );
              ~Queue( );
              int& remove( );
              void add(const int&);
              bool is_empty( );
              bool is_full( );
       private:
              //………..
}
这样我们就定义了一个 整形变量的队列类,该队列的节点是整形变量。当然该队列也能操作double型变量,因为在使用成员方法add时,double变量只需进行一个隐式类型转换double―― >int 即可。但是,该类就不能操作 String 类型变量,因为不存在一个 String ―― >int 的隐式类型转换。即:
Queue qobj;
String str(“abcd”);
qobj.add(1.25);// 正确
qobj.add(str);// 错误
即使该类操作 double 型变量没有错误,但是在使用 add 方法无疑会破坏 doubld 型变量的精度,同时隐式类型转换也会增加系统负担,所以说上面的 Queue 类在操作 double 型变量时,也是不太理想的。
那么怎么解决这些问题呢,一个最容易想到的办法就是针对每一种数据类型,我们都定义一个队列类,而 C++ 类名是不能重载的,因此我们必须取不同的名字,比如 IntQueue DoubleQueue StringQueue 等,然后将这些类的定义做一下拷贝,然后稍做修改就可以了。可以感觉到这样做起来重复性工作比较多,也比较繁琐。
C++ 为我们提供的类模板机制就是为了解决这种类型的问题的,它为定义通用类的类型提供了条件。
二、类模板的定义格式:
上述情况,用类模板进行声明时,格式如下:
template<class Type>
class Queue
{
       Queue( );
       ~Queue( );
       Type& remove( );
       void add(const Type&);
       bool is_empty( );
       bool is_full( );
}
这样,就可以定义 int double String 型队列:
Queue<int> qi;
Queue<complex<double>> qd;
Queue<String> qs;
通过使用类模板,在不同类型的队列定义上,就会变得比较统一了,也比原来简洁很多。
三、怎样声明一个类模板:
最简单的格式如下:
template<class T1 class T2>
class A;
说明:类模板的声明都是以关键字 template 开头,然后是一个用逗号分隔模板参数列表,用尖括号( <> )括起来,这个列表不能为空。其中,模板参数可以是类型参数,也可以是非类型参数。如果是非类型参数,则代表一个常量表达式。
 
模板的类型参数 :由关键字 class typename 其后的标志符构成,这两个关键字表明后面的参数名代表一个内置的或用户定义的类型。任何内置的或用户定义的类型,如 int double char* complex String 都可以是上面 A 模板的参数 T1 T2 的实参。
 
一个类模板可以有多个类型参数 template<class T1,class T2,class T3>
但是,每个类型参数前,必须有关键字 class typename
例如,下面的模板声明就是错误的:
template<class T,U> // 错误:参数 U 之前没有关键字 class typename
class collection;
 
模板的非类型参数 :由一个普通参数声明构成。一个非类型参数指示该参数代表了一个潜在的值,而这个值又代表类模板定义中的一个常量。
例如缓冲区类 Buffer
template<class Type,int size>
class Buffer;
其中非类型参数 size 表示缓冲区大小的常量。
 
一个类声明或定义紧跟在类模板参数后面,除了模板参数外,类模板的定义看起来和普通类的定义相似。
 
类模板参数可以有缺省实参。这对类型参数和非类型参数都一样,缺省实参可以是一个类型或值。
例如:
template<class Type=String,int size=1024>
class Buffer;
四、怎样定义一个类模板:
在类模板定义中,类模板的名字(比如前面的 Buffer,Collection,Queue 等,注意不带实参),可以被用作一个类型指示符,凡是可以使用普通类名的地方,都可以使用它。
例如:
template<class Type>
class QueueItem
{
       public:
              QueueItem(const Type&);
       private:
              Type item;
              QueueItem* next;
}
但是,上面的模板名 QueueItem 只能用在自己的定义和类模板之外出现的成员函数定义中。
当然,为了统一起见,我们将上面的QueueItem用QueueItem<Type>代替,也是正确的,只不过会显得稍微复杂点而已。
而当 QueueItem 被用在其他模板中,用作一个类型指示符时,我们必须指定完整的参数列表。例如:
template<class Type>
void display(QueueItem<Type>& qi)
{
       QueueItem<Type>* pqi=&qi;   // 带模板参数列表的完整的
       //…..
}
 
代码示例: QueueItem Queue 类模板的定义如下:
 
//QueueItem 的声明
template<class T> class QueueItem;
template<class Type>
class Queue
{
public:
       Queue( ):front(0),back(0){ };
       ~Queue( );
       Type& remove( );
       Void add(const Type&)
       Bool is_empty( )
{
return front==0;
}
private:
       QueueItem<Type>* front; // 若这样声明 QueueItem* front; 便是错误的。
       QueueItem<Type>* back;
}
通过上面代码可以看到:在类模板定义中,使用名字 Queue 时,可以省略参数表 <Type> ,但是当 Queue 的定义引用到类模板 QueueItem 时,不能省略参数表。
五、怎样实例化一个类模板:
从通用的类模板定义中生成类的过程,被称为模板实例化,每一个针对 int 型对象的 Queue 类被实例化时,类模板 Queue<class Type> 定义中的 Type 均被 int 取代,也就是用实参 int 取代形参 Type ,这样类模板实例就可以向普通类一样使用了。
关键是模板非类型参数的实例化时,赋给非类型参数的实参必须是一个常量表达式,即它必须在编译时便能计算出结果。例如,下面的类模板实例化就是错误的:
template<int* ptr> class BufPtr{…….};
BufPtr<new int[24]> bp; // 错误,该非类型参数的实参只有在运行时刻才知道结果
 
Sizeof 表达式的结果是一个常量,所以它可以被用作非类型参数的实参。
六、如何具体定义类模板的成员函数
与普通类一样,类模板的成员函数也可以在类模板定义中定义具体实现,这时,该函数是内联的( inline )。
然而,当在类模板定义之外具体定义成员函数时,必须使用特殊语法,来指明它是一个类模板的成员。成员函数定义的前面必须加上关键字 template 及模板参数 。例如:
template<class Type>
class Queue<Type>::Queue( )
{
front=back=0;
}
在这个成员函数定义中,凡是可以使用普通类型的地方,我们均可以使用模板参数 Type 。也就是说类模板函数本身也是一个模板。
用来实例化成员函数的类型,就是其成员函数要调用的那个类对象的类型。例:
Queue<String> qs;
这时,用来实例化构造函数的模板实参就是 String
 
 
七、常犯错误:
1、 进行模板声明时,比如 template<class Type> 这里的“ <> ”,往往一不小心会写成普通函数声明中用的“ ( ) ”。另外在一个类模板定义中用到另一个类模板时,常常只写那个模板的模板名,却忘记了写上模板参数,就像前面的 Queue 模板中使用到 QueueItem 模板时,用到 QueueItem 的地方,应该使用 QueueItem<Type> 而不是使用 QueueItem ,或者是将这里的“ <> ”写成了“ ( ) ”,这都是错误的。
2、 看一个关于模板声明的题目:
指明下面拿一个 template class 的声明或定义是错误的:
a template<class Type>
        class Container1;
        template<class Type,int size>
        class Container1;
答:错误,因为两个声明的模板参数不相同。
b template<class T,U,typename V>
        class Container2;
答:错误,因为模板参数 U 的前面没有用 class typename 修饰。
c template<class T,typename T>
        class Container3{};
答:错误,因为一个模板参数名称在模板参数列表中只能出现一次,即模板参数列表中不能有重复的参数名。
d template<class Type,int* ptr>
        class Container4;
        template<class T,int* pi>
        class Container4;
答:正确。模板参数名不同(注意不是模板参数类型不同),这种情况,即使模板名相同也是可以的。
e template<class Type,int value=0>
        class Container5;
        template<class Type=complex<double>,int value>
        class Container5;
答:正确。类模板的后继声明可以为模板参数提供额外的默认值,就像函数参数的默认值一样,一旦你指定某个参数的默认值,其右边参数也都必须具有默认值。
3、 请找出下面哪一个模板实例化的使用,会引起模板确实被实例化出来?
Template<class Type>
Class Stack{};
Void f1(Stack<char>);//a 、仅是一个函数声明,不需实例化类模板
Class Exercise
{
stack<double>& rsd;//b 、声明一个类模板引用,不需实例化
stack<int> si;//c si 是一个 stack 类型的对象,需要实例化类模板
}
int main()
{
       stack<char>* sc;//d 、仅声明一个类模板指针,不需实例化
       f1(*sc);//e 、需要实例化,因为传递给函数 f1 的是一个 stack<int> 对象。
       int iobj=sizeof(stack<String>);// 需要实例化,因为 sizeof 会计算 stack<String> 对象的 // 大小,为了计算大小,编译器必须根据类模板定义产生该类型。
}
答:首先分析题意,是模板实例化,也就是产生具体的类型,因为上面定义的 stack 只不过是一个通用模板,它不是一个确切的类型 。不要理解成产生某种类型的对象,这是关键。
而只有当程序使用了一个模板实例化名称,而当时的环境需要一个类定义,这时候类模板才会被实例化出来。如果只是声明了一个类的指针或引用,不需要实现知道该类的定义。
4、 说明为什么下面的模板实例化是非法的?
Template<int* ptr> class Ptr{…….};
Template<class Type,int size> class Fixed_Array{……..};
Template<int hi,int wid> class Screen{……};
A、 const int size=1024;
Ptr<&size> bp1;
错误:因为 int* >const int* 是正确的,但反之错误。
B、 Ptr<0> bp2;
错误: 0 属于一个 int 型,它需要透过一个隐式转换编程 null 指针,才能被认可为 int*
C、 const double db=3.1415;
Fixed_Array<double,db> fa1;
错误:编译器无法将 const double 转换成 int
D、 unsigned int fasize=255;
Fixed_Array<String fasize> fa2;
错误:非类型参数的实参必须是常量表达式,如果将 unsigned 改为 const 就正确。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值