C++模板类详解



一、模板类定义及实例化

1. 定义一个模板类:

template

class 类名{

// 类定义......

};

其中,template 是声明模板类的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是类型参数 ,也可以是非类型参数。类型参数由关键字class或typename及其后面的标识符构成。非类型参数由一个普通参数构成,代表模板定义中的一个常量。

例:

template

//type为类型参数,width为非类型参数

class Graphics;

注意:

(1)如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏掉。

(2)模板参数名不能被当作模板类定义中类成员的名字。

(3)同一个模板参数名在模板参数表中只能出现一次。

(4)在不同的模板类或声明中,模板参数名可以被重复使用。

typedef string type;

template

class Graphics

{

type node;//node不是string类型

typedef double type;//错误:成员名不能与模板参数type同名

};

template//错误:重复使用名为type的参数

class Rect;

template //参数名”type”在不同模板间可以重复使用

class Round;


(5)
在模板类的前向声明和定义中,模板参数的名字可以不同。

// 所有三个 Image 声明都引用同一个类模板的声明

template  class Image;

template  class Image;

// 模板的真正定义

template 

class Image { //模板定义中只能引用名字”Type”,不能引用名字”T”和”U” };


(6)
类模板参数可以有缺省实参,给参数提供缺省实参的顺序是先右后左。

template 

class Image;

template //错误!

class Image;


(7)
模板类名可以被用作一个类型指示符。当一个模板类名被用作另一个模板定义中的类型指示符时,必须指定完整的实参表

template

class Graphics

{

Graphics *next;//在类模板自己的定义中不需指定完整模板参数表

};

template 

void show(Graphics &g)

{

Graphics *pg=&g;//必须指定完整的模板参数表

}


2.模板类实例化

定义:从通用的模板类定义中生成类的过程称为模板实例化。

例:Graphics gi;

模板类什么时候会被实例化呢?

当使用了模板类实例的名字,并且上下文环境要求存在类的定义时。

对象类型是一个模板类实例,当对象被定义时。此点被称作类的实例化点

一个指针或引用指向一个模板类实例,当检查这个指针或引用所指的对象时。

例:

template

class Graphics{};

void f1(Graphics);// 仅是一个函数声明,不需实例化

class Rect 

{

  Graphics& rsd;// 声明一个模板类引用,不需实例化

  Graphics si;// si是一个Graphics类型的对象,需要实例化模板类

}

int main(){

  Graphcis* sc;// 仅声明一个模板类指针,不需实例化

  f1(*sc);//需要实例化,因为传递给函数f1的是一个Graphics对象。

  int iobj=sizeof(Graphics);//需要实例化,因为sizeof会计算Graphics对象的大小,为了计算大小,编译器必须根据模板类定义产生该类型。

}

 
   

3.非类型参数的模板实参

要点

绑定给非类型参数的表达式必须是一个常量表达式。

从模板实参到非类型模板参数的类型之间允许进行一些转换。包括左值转换、限定修饰转换、提升、整值转换。

可以被用于非类型模板参数的模板实参的种类有一些限制。

例:

template 
class Graphics{……};

template
class Rect{……};

const int size=1024;

Graphics<&size> bp1;//错误:从const int*->int*是错误的。

Graphics<0> bp2;//错误不能通过隐式转换把0转换成指针值

const double db=3.1415;

Rect fa1;//错误:不能将const double转换成int.

unsigned int fasize=255;

Rect fa2;//错误:非类型参数的实参必须是常量表达式,将unsigned改为const就正确。

Int arr[10];

Graphics gp;//正确

二、类模板的成员函数

要点:

类模板的成员函数可以在类模板的定义中定义(inline函数),也可以在类模板定义之外定义(此时成员函数定义前面必须加上template及模板参数)。

类模板成员函数本身也是一个模板,类模板被实例化时它并不自动被实例化,只有当它被调用或取地址,才被实例化。


Class Graphics{

  Graphics(){…}//成员函数定义在模板类的定义中

  void out();

};

template//成员函数定义在模板类定义之外

void Graphics::out(){…}


 
  

三、模板类的友元声明

模板类中可以有三种友元声明:

1.非模板友元类或友元函数

class Graphics{void out();};

template

Class Rect{

  friend class Graphics;//类Graphics、函数

  friend void create();// create、 out是类模板

  friend void Graphics::out();// Rect所有实例的友元

};

 
  
 
   

2、绑定的友元模板类或函数模板。

3、非绑定的友元模板类或函数模板。

第二种声明表示模板类的实例和它的友元之间是一种一对一的映射关系。

如图:

第三种声明表示模板类的实例和它的友元之间是一种一对多的映射关系。

如图:

例:绑定的友元模板

template

void create(Graphics);

template

class Graphics{

  friend void create(Graphics);

};

 
   

例:非绑定的友元模板

template

class Graphics{

 template

 friend void create(Graphics);

};


注意:
当把非模板类或函数声明为模板类友元时,它们不必在全局域中被声明或定义,但将一个类的成员声明为模板类友元,该类必须已经被定义,另外在声明绑定的友元模板类或函数模板时,该模板也必须先声明。

例:

template 

class A {

  private:

    friend class B; //错误:类B必须先声明

};

template 

class B{};

 
   

四、类模板的静态数据成员、嵌套类型

1.类模板的静态数据成员

要点:

静态数据成员的模板定义必须出现在模板类定义之外。

模板类静态数据成员本身就是一个模板,它的定义不会引起内存被分配,只有对其实例化才会分配内存。

当程序使用静态数据成员时,它被实例化,每个静态成员实例都与一个模板类实例相对应,静态成员的实例引用要通过一个模板类实例。

例:

template

class Graphics{

  static Graphics *next;

  static const type item;

};

template

Graphics * Graphics::next=0;

template

type Graphics::item=NULL;

//静态成员定义分为两部分:前一部分是类型,比如Graphics*,后一部分是名称和值,比如Graphics::next=0;

2.模板类的嵌套类型

要点:

在模板类中允许再嵌入模板,因此模板类的嵌套类也是一个模板,它可以使用外围类模板的模板参数。

当外围模板类被实例化时,它不会自动被实例化,只有当上下文需要它的完整类类型时,它才会被实例化。

公有嵌套类型可以被用在类定义之外,这时它的名字前必须加上模板类实例的名字。

例:

template

class Graphics{

  public:

    template

    class Rect{void out(type a,T b);};

};

Graphics::Rect node;

//引用公有嵌套类型必须加上模板类实例名字


五、成员模板

定义:成员定义前加上template及模板参数表。

要点:

在一个模板类中定义一个成员模板,意味着该模板类的一个实例包含了可能无限多个嵌套类和无限多个成员函数.

只有当成员模板被使用时,它才被实例化.

成员模板可以定义在其外围类或模板类定义之外.

例:

template

class Graphics{
  public:
    template

    class Rect{
      void out(type a,T b);
    };
};

template template

void Graphics::Rect::out(Gtype a,TT b){}//成员模板被定义在模板类定义之外(要跟上完整模板实参)

Graphics的实例可能包括下列嵌套类型:

Graphics::Rect

Graphics::Rect


 
   

注意:模板类参数不一定与模板类定义中指定的名字相同。

 

六、模板类的编译模式

1.包含编译模式

这种编译模式下,模板类的成员函数和静态成员的定义必须被包含在“要将它们实例化”的所有文件中,如果一个成员函数被定义在模板类定义之外,那么这些定义应该被放在含有该模板类定义的头文件中。

2.分离编译模式

这种模式下,模板类定义和其inline成员函数定义被放在头文件中,而非inline成员函数和静态数据成员被放在程序文本文件中。

例:

//------Graphics.h---------

export template

Class Graphics{
  void Setup(const type &);
};

//-------Graphics.c------------

#include “Graphics.h”

template 

void Graphics::Setup(const type &){…}

//------user.c-----

#include “Graphics.h”

void main()

{
  Graphics *pg=new Graphics;

  int ival=1;

  //Graphics::Setup(const int &)的实例(下有注解)

  Pg->Setup(ival);

}


 
   

Setup的成员定义在User.c中不可见,但在这个文件中仍可调用模板实例Graphics::Setup(const int &)。为实现这一点,须将模板类声明为可导出的:当它的成员函数实例或静态数据成员实例被使用时,编译器只要求模板的定义,它的声明方式是在关键字template前加关键字export

3.显式实例声明

当使用包含编译模式时,模板类成员的定义被包含在使用其实例的所有程序文本文件中,何时何地编译器实例化模板类成员的定义,我们并不能精确地知晓,为解决这个问题,标准C++提供了显式实例声明:关键字template后面跟着关键字class以及模板类实例的名字。

例:

#include “Graphics.h”

template class Graphics;//显式实例声明


显式实例化模板类时,它的所有成员也被显式实例化。

 

七、模板类的特化及部分特化

1.模板类的特化

先看下面的例子:

 
template

Class Graphics{

  public:
    void out(type figure){…}
};

Class Rect{…};


如果模板实参是Rect类型,我们不希望使用模板类Graphics的通用成员函数定义,来实例化成员函数out(),我们希望专门定义Graphics::out()实例,让它使用Rect里面的成员函数。

为此,我们可以通过一个显示特化定义,为模板类实例的一个成员提供一个特化定义。

格式:template<> 成员函数特化定义

下面为模板类实例Graphics的成员函数out()定义了显式特化:

Template<> void Graphics::out(Rect figure){…}

注意:

只有当通用模板类被声明后,它的显式特化才可以被定义。

若定义了一个模板类特化,则必须定义与这个特化相关的所有成员函数或静态数据成员,此时模板类特化的成员定义不能以符号template<>作为打头。(template<>被省略)

模板类不能够在某些文件中根据通用模板定义被实例化,而在其他文件中却针对同一组模板实参被特化。

2.模板类部分特化

如果模板有一个以上的模板参数,则有些人就可能希望为一个特定的模板实参或者一组模板实参特化类模板,而不是为所有的模板参数特化该类模板。即,希望提供这样一个模板:它仍然是一个通用的模板,只不过某些模板参数已经被实际的类型或值取代。通过使用模板类部分特化,可以实现这一点。

例:

 
template

Class Graphics{…};

template//模板类的部分特化

Class Graphics{…};


格式:template<模板参数表>

注意:

部分特化的模板参数表只列出模板实参仍然未知的那些参数。

模板类部分特化是被隐式实例化的。编译器选择“针对该实例而言最为特化的模板定义”进行实例化,当没有特化可被使用时,才使用通用模板定义。

例:Graphics<24,90> figure;

它即能从通用模板类定义被实例化,也能从部分特化的定义被实例化,但编译器选择的是部分特化来实例化模板。

模板类部分特化必须有它自己对成员函数、静态数据成员和嵌套类的定义。

 

八、名字空间和模板类

模板类定义也可以被放在名字空间中。例如:

 
 
    
Namespace cplusplus_primer{
 
 template
 
 Class Graphics{…};
 
 template

 type create()
 
 {…}
 
}

 
   

当模板类名字Graphics被用在名字空间之外时,它必须被名字空间名cplusplus_primer限定修饰,或者通过一个using声明或指示符被引入。例如:

 
Void main()

{

using cplusplus_primer::Graphics;

Graphics *pg=new Graphics;

}

注意:在名字空间中声明类模板也会影响该模板类及其成员的特化和部分特化声明的方式,模板类或模板类成员的特化声明必须被声明在定义通用模板的名字空间中(可以在名字空间之外定义模板特化)。

一个关于队列的例子,下面将其代码整理如下:

#include "iostream.h"

template  class QueueItem;

template 

class Queue {

public:

friend ostream& operator<<(ostream &os,const Queue &q);

Queue() : front( 0 ), back ( 0 ) { }

~Queue(){}

void add( const Type & );

bool is_empty() const

{

return front == 0;

}

Type remove();

private:

QueueItem *front;

QueueItem *back;

};

template 

class QueueItem

{

public:

QueueItem(Type val){item=val;next=0;}

friend class Queue;

friend ostream& operator<<(ostream &os,const Queue &q);

friend ostream& operator<<(ostream &os,const QueueItem &qi);

 

private:

Type item;

QueueItem *next;

};

template 

void Queue::add(const Type &val)

{

QueueItem *pt =new QueueItem(val);

if ( is_empty() )

front = back = pt;

else

{

back->next = pt;

back = pt;

}

}

template 

Type Queue::remove()

{

if ( is_empty() )

{

cerr << "remove() on empty queue \n";

exit(-1);

}

QueueItem *pt = front;

front = front->next;

Type retval = pt->item;

delete pt;

return retval;

}

template 

ostream& operator<<(ostream &os, const Queue &q) //输出队列成员

{

os << "< ";

QueueItem *p;

for ( p = q.front; p; p = p->next )

os << *p << “ ;//用到了Queue和QueueItem的私有成员,因此需将此运算符重

//载函数声明为Queue和QueueItem的友元,书上没有将此函数声明为QueueItem

os << “ >”;//的友元。

return os;

}

template 

ostream& operator<< ( ostream &os, const QueueItem &qi )

{

os << qi.item;//用到了QueueItem的私有成员,因此需将此运算符重载函数声明

//为QueueItem的友元

return os;

}

void main()

{

Queue qi;

cout << qi << endl;

int ival;

for ( ival = 0; ival < 10; ++ival )

qi.add( ival );

cout << qi << endl;

int err_cnt = 0;

for ( ival = 0; ival < 10; ++ival ) {

int qval = qi.remove();

if ( ival != qval ) err_cnt++;

}

cout << qi << endl;

if ( !err_cnt )

cout << "!! queue executed ok\n";

else cout << “?? queue errors: " << err_cnt << endl;

}

 
   


运行结果


作者:Out Man
出处:http://www.cnblogs.com/assemble8086/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留作者信息,且在文章页面明显位置给出原文连接。




你可能感兴趣的:(专业,c++,模板类)