(十八)类模板

1、类模板就是模板的一种,同函数模板一样,不同的类型对应不同的函数,实质是重载的发生。

      类模板也一样,也是类型的不一样(当然它也有非类型的情况),不同类型就生成不同的类。


2、特化与实例化的区别

      原由:由多个相同类型的对象,我们提炼出“类”的概念,相同“形式”的类,我们又提炼出“类模板”的概念。

      因此:特化是某个类型不适合通用成员的代码,须要指明这个类型时单独写出一段代码,以便

                   适应各种情况,也就是大家通常意义上所说的特化。

                   注意这两个是有区别的,一个是通用到一般,另一个是通用加以特殊情况。

                  由类根据具体数据产生出的实例,也即对象,称“实例化”

      注意 :偏特化:就是对参数列表部分进行特殊化处理,就是偏特化。

                  泛型:即通用型,比如类模板,它的参数是任意的,相当于适用各种情况,即通用型,所以类模板也称泛型。


3、类模板定义

template<参数列表> Class ClassName{
	//模板定义 
};

template<参数列表> Class ClassName{
	//模板定义 
};

     同函数模板一样,先进行关键字template及参数列表的声明,然后再是类似类的声明与定义。

      1、参数列表中的类型可以是Class,也可以是typename;各参数用逗号分隔。

       2、最后结束也是以;进行结束。

       3、模板内的定义,可以用前面的参数,来替代相关的类型或数值。


4、简单的类模板

       用数组来举例,数组可以是int、long、int*等类型,还可以是对象类型,为了把它们统一起来,于是就用一个类模板来表示一个数组。

       它的参数就是类型,就如前面所说的各种类型。

       通常这样的类模板具有下面成员函数:构造(初始化),拷贝构造(深拷贝),析构(动态分配内存时的释放),拷贝赋值(对象赋值)等

template<typename T>class Array{
	private:
	T*  elemnets;//指针,相当于数组首地址
	size_t size;//数组大小
	public:
	explicit Array<T>(size_t arraySize);
	Array<T>(const Array<T>& theArray);
	~Array<T>();
	T& operator[](size_t index);                //两个下标重载 
	const T& operator[](size_t index)const;
	Array<T>&  operator=(const Array<T>& rhs); 
};  //这里用; 


//上面的相当于下面写法,模板“体内”Array本身就表示Array<T>,故可以简写: 
template<typename T>class Array{
	private:
	T*  elemnets;//指针,相当于数组首地址
	size_t size;//数组大小
	public:
	explicit Array(size_t arraySize);
	Array(const Array<T>& theArray);
	~Array();
	T& operator[](size_t index);                
	const T& operator[](size_t index)const;
	Array&  operator=(const Array& rhs); 
}; 

      一、上面两个定义是一样的,因为Array就表示Array<T>;只有体内时才能省略,体外不能省略。

      二、类头的写法可以写成两行,这样让人阅读列明了:

                  template<typename T>

                  class Array{........................};

       三、explicit显式声明,表示禁止隐式调用,比如:Array<int>  a=3;//是不允许的。

                                                                                只能显式:Array<int>  a(3);//这样才符合构造函数的形式


4、模板ID

      ID就是能识别这个类的,模板ID就是识别这个模板的(表示与其它模板相区别),那怎么才能区别呢?

       很显示:模板中的类名(如上面Array),还有模板中的参数(如上面T)

       因此:模板名和后面紧跟尖括号中的参数名列表,它们就称为“模板ID"

              一般来说,模板某个实例的类名是由模板名跟尖括号中的类型参数组成的。

      注意:在模板体外,那些要标识模板的,必须使用模板ID进行标识。


5、类模板中函数成员的定义。

      它们可以定义在模板体内,同类一样,隐含是内联函数,如果定义在类外,刚必须加上模板ID,因为它本身是模板函数,同时,

      还应用类型进行限定,注意这个类型是根据模板ID而来的。

template<typename T> //必须写上模板ID,表示与类模板是联系在一起的。 
Array<T>::Array(size_t arraySize):size(arraySize){
	elements=new T[size]; 
}//当其实例化时,若T为double,则为Array<double>::Array(size_t arraySize)
template<typename T>
Array<T>::~Array(){
	delete[] elements;
} 
template<typename T>//此行可与下行合并一行。现在写法更易阅读 
Array<T>::Array(const Array& theArray){
	size=theArray.size;//类内,本类对象直接引用私有
	elements=new T[size];
	for(int i=0;i<size;i++) elements[i]=theArray.elemnets[i]; 
}
template<typename T>
T& Array<T>::operator[](size_t index){//可作左值 
	if(index<0||index>=size) throw out_of_range("index wrong!");//判断数组上下限 
	return elements[index]; 
}
template<typename T>
Array<T>&  Array<T>::operator=(const Array& rhs){
	if(&rhs==this) return *this;
	if(elements) delete[] elements;
	size=rhs.size;
	elemnets=new T[rhs.size];
	for(int i=0;i<size;i++) elemnets[i]=rhs.elemnets[i];
} 

        即使把成员在类模板外进行定义,但是,它们一般都与类模板声明一起放在头文件中。

        有时为了让编译器把它们看作是内联的,在前面应加上inline,如

template<typename T>
inline const T& Array<T>::operator[](size_t index)const{
	if(index<0||index>=size) throw out_of_range("index wrong!");
	return elements[index]; 
}



6、创建类模板的实例

      当应用类模板的实例时,编译器将从各种类型中,选择最适当与之匹配。如果匹配有二义性,程序将出错中断

          Array<int> data(40); //定义int型的数组,共40个。

      一、当编译器看到int参数时,就去匹配,只有T=int时最适当,于是就是这个类:

                        class Array<int>{ //注意位置

                                  。。。。。。。

                        };

       二、成员模板匹配情况。

               当匹配到int的类时,只有当调用这个成员时,这个成员函数模板才进行实例化,没有调用的,不会实例化。

               上例中,只是一个构造,因此它只调用了构造函数模板,其它的如析构、拷贝、赋值、重载[]等成员函数模板是不会实例化的。

               感觉是不是C++很扣门?是的,这样更大地节约内存。

       三、在”声明中“实例化类模板,称为模板的隐式实例化(特化),因为类型实例化是主体,声明的对象是一个付带的一个副产品。

               它与模板 的显式实例化区分开来。

        注意:在创建对象类型时,类模板只能隐式实例化。但是,声明对象类型的指针时,并不会创建模板的实例。

                        Array<string>*  pObject; //只说明类型,不会创建Array<string>类,也不会创建其对象。可以对比上面为什么称为“副产品”。

                       Array<string*>   pObject(10); //注意与上面不同,这个会创建类和对象。

 


6、导出模板
   为什么有导出模板?
   导出之意:就是分离之意。因为类模板的声明及成员函数的定义都放在一起在一个头文件中。这样就造成一个问题,当一个大项目的时候,
   有很多个头文件,但头文件中又包含了这些成员函数模板的定义,必定大大增加了处理的系统开销,于是就提出一个“导出模板”即从头
   文件中把函数定义分离出来的概念,以减轻系统开销。
    
    和类的定义一样,在CPP文件中的每个函数模板前加export,表示这是分离出来的(导出的)。


   注意:一、因是导出的,所以它是定义了的,故,你不能在程序的其它地方再定义它,否则会出现重复定义错误;
         二、同类的定义一样,这个导出的源文件中一样也应包含原头文件;
         三、只有不是内联的函数模板(类模板体内定义或加了inline),才能进行导出(export)。
             如果你对内联函数模板进行导出,则关键字会被忽略。
         四、当然,只有导出时才加export,没有导出,共同在一个头文件中时,不能用export;
         五、export也可以用于类模板,即将类模板中所有非内联的函数及函数模板导出到另一个源文件中。同时
             它也会导出类模板中的静态数据成员、成员类、成员类模板。(复杂啊。。没法,这章本身就有人写成了一本书)




7、类模板的静态成员
   同类一样也有静态成员。它们没有this指针,同类的规则一样。
   同理,它因为也依赖于模板参数,故也应该用模板ID进行限定和指明。

template<typename T>class Array{
	private:
	static T  value;//静态成员
	T*  elemnets;
	size_t size;
	public:
	explicit Array(size_t arraySize);
	Array(const Array<T>& theArray);
	~Array();
	T& operator[](size_t index);                
	const T& operator[](size_t index)const;
	Array&  operator=(const Array& rhs); 
}; 
template<typename T>T Array<T>::value;//静态成员的初始化(注意无赋值) 

       注意:这里的静态没有初始化,为什么呢?因为T类型没定,如果是对象就无法赋值。因此注意两点:

                  一、如果静态成员类型是明确的int或long等类型,就必须有初始;

                  二、如果静态成员类型是参数的T类型,那么就要小心了,不能给值,因为定没定,同时还要注意

                          这个T如果是对象,则必须是一个支持无参构造,否则就报错。因为无参构造相当于赋值。



8、非类型参数
    类模板的参数列表可以是类型,也可是非类型,还可以是两者的混合。
    特别注意的是:同类型参数一样,不同类型参数就是不同的类,而非类型参数不同的值也对应不同的特化(类)
    
    非类型参数只能是整型,如int,long,枚举,对象的指针或引用(string*,Box&),函数的指针或引用,类成员的指针等 
    它不能是浮点型或类类型。而且它是一个常量。
    为什么呢?
    因为非类型参数通常在类模板中表示的是指定容器(数组、堆栈)等的大小和上下限。一旦定了,那这个类型就定了。如果
    不是常数,是变化的,一是说明大小在变,代码就很难实现也不符合初衷,二是非类型参数在变化,那类模板生成的类也是
    变化的,数值1和数值2的非类型参数生成的类,是不同的类。因此浮点是不行了,因为在计算机中,浮点的后面数是变动的,
    变动一下就是一个类型,一个数组里面每个元素都是不同的类型而且是你不能确定的类型,那太混乱了。如下:

template<typename T,size_t size)class ClassName{
	//类模板代码 
};
//当特化时,下面两个是不同的类
ClassName<int,3> a;
ClassName<int,4> b;
// 类型不同当然不能相互赋值。 

    非类型参数不同会生成复杂的结果,如上面,这种称为“代码膨胀”,解决办法,是把这个非类型参数纳入到类模板中作为
    一个数据成员。


    特别要注意的是:
    非类型参数的类型必须严格符合类型。
    一是非类型参数不是引用或指针,则参数必须是在编译期间编译的常量表达式
       long start=-10;
       Array<double,start> values(21);//错误,上一句中表明start不是一个常量。
       ------------------------------
       const long start=-10;
       Array<double,start> values(21);//正确,上一句表明了是常量。

      二,如果非类型参数是指针,则它必须是一个地址:对象的地址或带有外部链接的函数地址

#include <iostream>
using namespace std;
template<long *p>class MyClass{
//code
};
long a[3]={2,4,7};//必须是全局的,可在链接的 
int main(int argc, char *argv[]){
	MyClass<a> n;
	return 0;
}

      特别注意:由于非类型参数可以是表达式形式,于是如果带有右尖括号的符号如(>,->,>>)等会造成歧义,如:

                            Array<Box,start>5?start:5>  boxes;//错误,因为大于符号将被看作是右尖括号。

                            Array<Box,(start>5?start:5)>  boxes;//正确,用括号来避免错误发生


9、默认的模板参数值

     同函数一样,类模板也有默认值,且规则相同,如,默认值都按从右到左进行定义,不能跳跃。

     注意的是,如果都有默认值,实例化时尽管不写参数,但尖括号必须写。

      template<typename T=int,long startIndex=0>class Array{.....}

      实例化可以是: Array<>  number(101);//尖括号必须写上


10、模板的显式实例化

        前面有模板的隐式实例化(对象的声明时),显式实例化,就是指明参数,同时生成类和“所有的函数模板”实例化。

       这和隐式是有区别的,隐式是调用则实例化类和相关的函数,不相关的函数不会实例化。

         template   class  Array<double,3>;//类模板情况

         template void func<double>(double const&);//函数模板情况


11、类模板的友元

        类模板的友元可以是类,函数,或者模板。

        一、如果类是模板的友元,其所有成员函数将是模板每个实例的友元;一对多

        二、如果函数是模板的友元,函数就是模板每个实例的友元。一对多

        三、如果模板是类模板的友元,有两种情况:

                 (1)通常模板中的参数与类模板的参数相关,因此具体的类模板参数对应一个唯一的模板,形成一对一。

                            即,模板的实例化对应一个类模板的实例化,通常就是这种情况。

                   (2)模板中的参数与类模板的参数一点都不相关,于是任意实例化的一个模板都是类模板每个实例化的友元。多对多。

          注意 :因为友元不是类模板的成员,只有调用到友元时,才对友元进行实例化


12、类模板说明:类模板特殊情况下的说明。中文一般翻译为“特化”

               对于template<typename T>class  Array{......};

        类模板说明:提供的类定义是专门针对类模板参数的给定的参数集,就是类模板特殊情况(某一特定参数时).它就是一个类。

        为什么要有“类模板说明”呢?

                因为假定上面类模板中有成员是专门进行比较的,但是如果T类型是string对象时,比较时就会发现“非空字符串”是不能进行比较的。

        那么这个成员就不适合特定的string情况。如果模板使用比较去处符比较对象,则该比较运算符可用于string类型,但又不能用于char*

        类型。要比较char*类型的对象,需要使用<cstring>头文件中声明的比较函数。

                 对于类模板它是处理“通用”情况的,不能处理某一个特定的情况。于是就产生了一个特殊的版本:就是类模板说明,它本质上就是

        一个类(类模板本质上不是类),只是这个类与类模板相关,它配合、补充类模板来处理各种“通用”情况。

       产生的效果:

                 类模板说明一旦定义后,它就配合类模板,当参数为特定的参数时,就会调用类模板说明(类),而不是由类模板实例化出一个类。

        定义:“类模板说明”必须放于原类模板的前面,这样编译器才“先入为主”地以它为主,特定参数时就不会去要求类模板来生成类,它会直

                   接使用“类模板说明”。

                         template<>class Array<char*>{........};

                   空〈〉表示的是模板的完整说明,它指定了所有参数,就不能再指定模板参数,它的具体说明全部就在模板名Array的后面。

         类模板中可能有一两个成员函数需要为特定类型编码(比如前面所说的比较的情况)。如果成员函数由独立的函数模板定义,而不是在

         类模板体中定义,就只能为该函数模板提供说明。


        偏特化:偏特化是针对特化而言的,如上面例子中是特化,因为是一个参数,相当于是全部参数了,所以不是偏特化。

    特殊化处理多个参数时,应注意:

    一、只能对类型参数进行特化,不能对非类型参数进行特化。

    二、特化的具体数据应在模板名(如Array)后面具体说明,模板名的参数个数应与原类模板个数相同,同时这个已经具体说明了的

      参数,在关键字template中应省略不再写(固定它已经具体化了,已经绑定到模板名上了)。

       template<typename T,long start> class Array{....};

                        偏特化T为int时:

       template<long start>class Array<char*,start>{...};//省略第一参数,后面char*具体给出,模板名后仍为2个参数。

      但是如果是指针时:

       template<typename T,long start>class Array<T*,start>{...};//这里为什么不省略呢?

       因为这里的T*只是说明是指针,但不是具体的(是int*还是long*等),编译器无法确认,前面必须指出这个T仍是一个类型。

                三、编译器选择最适合的特化类型。

      比如上面二中特化成char*和T*,它们都是指针型,遇到一个指针型比如Box*怎么处理呢?

      第一,按最适合的类型处理,比如特化成 Box*的情况(template<long start>class Array<Box*,start>{..};),

                                    但是,这里没有。

      第二,按最特殊到一般的情况进行处理(或者说最具体到一般的)

         上面二中只有char*和T*两种特化,显然,char*更特殊,更清楚。T*只是一般意义上的指针。

                                    因此,编译器会按char*进行处理。

         

13、带有嵌套类的类模板

  类模板中可以包含嵌套的类或嵌套的类模板。

  嵌套的类模板进行独立的参数化(实例化),这样就可以生成二维的类(相当于二维数组),内部生成N种,外问生成M各,共NXM种情况。

  这个太复杂,先看一下简单的,内部的类的参数只与外部的参数相关或无关,如:

template<typename T>class Stack{
	private:
		class Node{ //私有,只限类内使用。
			public:
			T* pItem;
			Node* pNext;
			Node(T& rItem):pItem(&rItem),pNext(0){}
			Node():pItem(0),pNext(){} 
		};
	
	//rest of the Stack definition...
};

       

14、高级的类模板(简介)

  关于模板的东西可以写一本书如:C++ template

  这里简介一些情况:

  类模板可以有基类,这些基类可以是一般的类,也可以是类模板。如:

template<typename T>class SpecialStack:public Stack<T>{
	public:
	SpecialStack();
	~SpecialStack();
	SpecialStack(const SpecialStack& aStack);
	int ObjectCount();
};

  另外,类模板中的类型参数也可以是一个类模板,如:

template<typename T1,template<typename T2> Array>class{
	//code
};

 

天啊,模板这章真难学,特别是初学者。看了一周才明白。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值