C++ 之 【类模板详解】
发布日期:2021-11-03 21:19:22 浏览次数:2 分类:技术文章

本文共 10082 字,大约阅读时间需要 33 分钟。

 

使用注意1:需要将类模版中的成员函数定义也写入.h文件中

 

使用注意2:模板的声明或定义只能在全局,命名空间或类范围内进行。不能在局部范围,函数内进行。

 

使用注意3:类何时实例化?

3.1 在我们使用类模板时,只有当代码中使用了类模板的一个实例的名字,而且上下文环境要求必须存在类的定义时,这个类模板才被实例化。

{

  (a) 声明一个类模板的指针和引用,不会引起类模板的实例化,因为没有必要知道该类的定义。

  (b) 定义一个类类型的对象时需要该类的定义,因此类模板会被实例化。

  (c) 在使用sizeof()时,它是计算对象的大小,编译器必须根据类型将其实例化出来,所以类模板被实例化.

  (d) new表达式要求类模板被实例化。

  (e) 引用类模板的成员会导致类模板被编译器实例化。

  (f) 需要注意的是,类模板的成员函数本身也是一个模板。标准C++要求这样的成员函数只有在被调用或者取地址的时候,才被实例化。用来实例化成员函数的类型,就是其成员函数要调用的那个类对象的类型

}

 

非类型模板形参:模板的非类型形参也就是内置类型形参,如template<class T, int a> class B{};其中int a就是非类型的模板形参

详细介绍了非类型形参的使用:

非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。

调用非类型模板形参的实参必须是一个常量表达式,必须能在编译时计算出结果。

 

以下转自

 

一、类模板定义及实例化

. 定义一个类模板:

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<int> 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及模板参数)。

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

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<int>::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<Rect>::out()实例,让它使用Rect里面的成员函数。 

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

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

下面为类模板实例Graphics<Rect>的成员函数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 Queu.Item (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;  }

 运行结果:

 

 

转载地址:https://blog.csdn.net/wjydym/article/details/94458318 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:多线程编程——条件变量(Condition Variable)
下一篇:正则表达式格式化编辑目录

发表评论

最新留言

初次前来,多多关照!
[***.172.111.71]2022年05月22日 08时30分06秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

最新文章