模板与泛型编程

模板与泛型编程

所谓泛型编程就是以独立于任何特定类型的方式编写代码。使用泛型程序时,我们需要提供具体程序实例所操作的类型或值。标准库的容器、迭代器、算法都是泛型编程的例子。在c++中,模板是泛型编程的基础。模板是创建类或函数的蓝图或公式。

1.      函数模板

函数模板是一个独立于类型的函数,可作为一种方式,产生函数的特定类型版本。

eg

template<typename T>

int compare(const T &v1,const T &v2)

{

          if(v1<v2)     return -1;

    if(v2<v1)     return 1;

    return 0;

}

(1):模板形参:上面定义了一个名为T的类型形参,在compare内部,可以使用名字T引用一个类型,T表示哪个实际类型有编译器根据所用的函数而确定。类型形参跟在关键字classtypename之后定义,如class T是名为T的类型形参,在这里classtypename没有区别

2)使用函数模板

使用函数模板时,编译器会推断哪个模板实参绑定到模板形参。一旦编译器确定了实际的模板实参,就称它实例化了函数模板的一个实例。

Eg

int main()

{

  cout<< compare(1,0)<<endl;

   Strings1=”hi”,s2=”world”;

   cout<<compare<<(s1,s2)<<endl;

return 0;

}

编译器将用int代替T创建第一个版本,并用string代替T创建第二个版本。

3inline函数模板

template<typenameT> inline T min(constT&,const T&);

inline 放在模板形参表之后,返回类型之前。

2.     类模板

template <class  Type>  class  Queue

{

Public:

          Queue();

          Type &front();

          const Type &front() const;

          void push(const Type &);

          void pop();

          ……

};

使用类模板:与调用函数模板形成对比,使用类模板时,必须为目标形参显式指定实参

Queue <int> qi;

Queue< vector<double> >  qc;

Quequ<string>   qs;

3.      编写模板时,代码不可能针对特定类型,对实参类型的要求尽可能少是很有益的。

Compare 模板函数说明了编写泛型代码的两个重要原则:

(1)     模板的形参是const引用2)函数体中的测试只用< 比较(操作符尽可以能少)

通过将形参设为const引用,就可以允许使用不允许复制的类型。大多数类型都可以复制,但也有不可以复制的。将形参设为const引用,保证这种类型可以用于compare函数,而且,如果有比较大的对象调用compare,则这个设计还可以是函数运行得更快。(不需要调用拷贝构造函数)

4.      模板在使用时将进行实例化,类模板在引用实际模板类类型时实例化,类模板的每次实例化都会产生一个独立的类类型,eg :编写Queue <int> qi;时,编译器会自动创建名为Queue<int>的类。函数模板在调用它或用它对函数指针进行初始化或赋值时实例化。

5.      模板实参推断

1.多个类型的实参必须完全匹配

short   si;

compare (si,1024);   //错误,从第一个实参推断出的模板类型是short,从第二个实参推断出来int类型,两个类型不匹配,所以模板实参推断失败。

如果compare的设计者允许实参的常规转换,则函数必须用两个类型形参来定义

template<typename A, typename B>

int compare(const  A& v1,const  B& v2)

{

          if(v1<v2)     return -1;

    if(v2<v1)     return 1;

    return 0;

}

compare (si,1024);   //正确,

6.      类模板成员

(1).Queue 的实现策略

AQueueItem 类表示Queue的链表中的节点,该类有两个数据成员item next

B. Queue类将实现上面描述的接口函数,Queue类也有两个数据成员:headtail,这些成员是QueueItem指针。

(2).QueueItem

Template <class Type> class  QueueItem

{

          QueueItem(const  Type &t): item(t), next(0) {}

          Type  item;

          QueueItem  *next;

};

(3).Queue

template <class  Type> class  Queue

{

public:

          Queue():head(0), tail(0)  {}

          Queue(const  Queue &Q):head(0), tail(0)  {  copy_elem(Q); }

          Queue&  operator=(const  Queue&);

          ~Queue()   { destroy();  }

          Type &front() {  returnhead->item;  }

          const Type &front() const  {  return head->item;  };

          void push(const Type &);

          void pop();

          bool empty () const  {  return head==0;  }

private:

          QueueItem<type>  *head;

          QueueItem<type>  *tail;

          void destroy();

          void copy_elems(const  Queue);

         

};

通常,当使用类模板的名字的时候,必须指定模板形参。这一规则有个例外:在类本身的作用域内部,可以使用类模板的非限定名。例如,在默认构造函数和复制构造函数的声明中,名字QueueQueue<Type>的缩写表示。因此复制构造函数定义其实等于:

Queue<Type>  ( const Queue<Type>  &Q ): head(0),tail(0)   {copy_elem(Q);}

但是类中其他模板的模板形参必须指定类型形参:

QueueItem<type>  *head;

QueueItem<type>  *tail;

4. 类模板成员函数

类模板成员函数的定义具有如下形式:

A. 必须以关键字template开头,后接类的模板形参表

B. 必须指出它是哪个类的成员

C. 类名必须包含其模板形参

从这些规则可以看出,在类外定义的Queue类的成员函数的开头应该是:

Template <class  Type> ret-type Queue<Type>::member-name

l  Destroy函数

为了举例说明在类外定义的类模板成员函数,我们来看destroy函数:

template<class  Type>  void   Queue<Type>::destroy()

{

         while(!empty())

                  pop();

}

它的工作是遍历这个Queue的每个分支,调用pop除去每一项。

l  pop函数

template<class  Type>  void   Queue<Type>::pop()

{

           QueueItem<type>  *p=head

       head=head->next;

           delete p;

}

Pop函数假设用户不会在空Queue上调用pop。它的工作是除去Queue的头元素。必须重置head指针以指向Queue中的下一个元素,然后删除head位置的元素。唯一有技巧的部分是记得保持指向该元素的一个单独指针,以便在重置head指针后可以删除元素。

l  push函数

template<class  Type>  void   Queue<Type>::push(const  Type &val)

{

           QueueItem<type>  *pt=newQueueItem<Type>(val)

                  if (empty())

                          head=tail=pt;

                  else

                  {

                          tail->next=pt;//添加一个新元素到尾巴

                          tail=pt;

}

 }

首先分配新的QueueItem对象,用传递值初始化它。如果Queue中已经有元素了,则使当前tail元素指向这个新元素。旧的tail不再是最后一个元素了,这也是通过使tail指向新构造的元素指明的。

l  copy_elems函数

template<class  Type>  void   Queue<Type>::copy_elem(const  Type &orig)

{

         for(QueueItem<Type>*pt==orig.head;  pt;  pt=pt->next)

                  push(pt->item);//拷贝元素

}

(5).类模板成员函数的实例化

与其他函数模板不同的是,在实例化类模板成员函数的时候,编译器不执行模板实参推断,相反,类模板成员函数的模板形参由调用该函数的对象的类型确定。例如,当调用Queue<int>类型对象的push成员时,

实例化的push函数为void  Queue<int>::push(const  int &val)

对象的模板实参能够确定成员函数模板形参,这一事实意味着,调用类模板成员函数比调用函数模板更灵活。用模板形参定义的函数形参的实参允许进行常规转换

Queue<int >  qi;

shorts=42;

inti=42;

qi.push(s);//oks is convert to int

qi.push(i);

7.     类模板中的友元声明

在类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体的友元关系:

(1)   普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。

(2)   类模板或函数模板的友元声明,授予友元所有实例的访问权

(3)   只授予对类模板或函数模板的特定实例的访问权的友元声明。

A.普通友元

非模板类或非模板函数可以是类模板的友元:

template<class  Type>  class Bar

{

         friend class  FooBar;

         friend void  fcn();

//……

};

这个声明是说,FooBar的成员和fcn函数可以访问Bar类的任一实例的private成员和protected成员。

B.一般模板友元关系

友元可以是类模板或函数模板:

template<class  Type>  class Bar

{

         template <class  T>  friendclass  Fool;

         template <class  T>  friendvoid  fcn(const  T&);

//……

};

这些友元声明使用与类本身不同的类型形参,该类型形参指的是Foolfcn的类型形参。Fool的任意实例都可以访问Bar的任意实例的私有元素,类似的,fcn的任意实例可以访问Bar的任意实例。

C.特定的模板友元关系

除了将一个模板的所有实例设为友元,类也可以只授予对特定实例的访问权

template<class  T>  class  Foo2

template<class  T>  class  voidtemp_fcn2(const  T&);

template<class  Type>  class Bar

{

         friend class Foo2<char *>;

         friend void temp_fcn2<char *>(char*  const&);

};

即使Foo2本身是类模板,友元关系也只扩展到Foo2的形参类型为char*的特定实例。类似的,temp_fcn2的友元声明是说,只用形参类型为char* 的函数实例是Bar类的友元,形参类型为char* Foo2temp_fcn2的特定实例可以访问Bar的每个实例。

下面形式的友元声明更为常见:

template<class  T>  class  Foo3

template<class  T>  class  voidtemp_fcn3(const  T&);

template<class  Type>  class Bar

{

         friend class Foo3<Type >;

         friend void temp_fcn3<char *>( cons Type&);

};

这些友元定义了Bar的特定实例与使用同一模板实参的Foo3temp_fcn3的实例之间的友元关系。每个Bar实例有一个相关的Foo3temp_fcn3友元:

Bar<int>  bi;///Foo3<int> temp_fcn3<int> 是友元

Bar<string>  bs;/// Foo3<string > temp_fcn3<string> 是友元.因此,Foo3<int>可以访问

Bar<int>的私有部分,但不能访问Bar<string>或者任意其他Bar实例的私有部分。

D.声明依赖性

要想限制对特定实例化的友元关系时,必须在可以用于友元声明之前声明类或函数:

template <class T> class A;

template<class T> class B

{

         friendclass A <T>;      //ok A is knownto be a template

         friendclass C;         //ok, C must be anordinary ,nontemplate class

         template<class S>friend class D;   //OK, Dis a template

         friendclass E<T>;    //error ,E wasn’tdeclared as a template

};

8.     QueueQueueItem的友元声明

QueueItem类不打算为一般程序所用:它的所有成员都是私有的。为了让Queue类使用QueueItem类,QueueItem类必须将Queue类设为友元。

template <class Type>class Queue;

template<class Type>class QueueItem

{

       friend class Queue <Type>;     //只将与QueueItem类用同样类型实例化的Queue类设为友元

};

A——Queue类接口中可能增加的一个有用的操作,是输出Queue对象的内容的能力。提供输出操作符重载实例,可以做到这一点。这个操作符将遍历Queue中的元素链表并输出每个元素的值,将在一对<>内输出元素。因为希望能够输出任意类型的Queue内容,所以需要将输出操作符也设为模板:

template<class Type>

ostream&operator<< (ostream &os, const Queue<Type> &q )

{

         os << “<”;

         queueItem<Type>  *p;

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

                  Os<< p->item<<“ ”;

os << “>”;

return os;

}

如果int类型的Queue包含值35813,这个Queue的输出显示如下<35 8 13>.

B——将函数模板设为友元

输出操作符需要成为Queue类和QueueItem类的友元。它使用Queue类的head成员和QueueItem类的next item成员。我们的类将友元关系授予同样类型实例化的输出操作符的特定实例:

Template<class  T >

Std::ostream&  operator<<(std::ostream&, constQueue<T>&);

template<class Type>class QueueItem

{

       friend class Queue <Type>;     //

         friend Std::ostream&  operator<<  <Type>  (std::ostream&, constQueue<T>&);

};

template <class Type>class Queue

{

         friend Std::ostream&  operator<<  <Type>  (std::ostream&, constQueue<T>&);

 

};

每个友元声明授予对应operator<<实例的访问权,及输出Queue<int>的输出操作符是Queue <int> 类的友元,它不是任意其他Queue类型的友元。

9.     模板特化

模板特化(templatespecialization)是这样的一个定义,该定义中一个或多个模板形参的实际类型或实际值是指定的。特化的形式如下:

A. 关键字template后面接一对空的尖括号

B. 再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参

C. 函数形参表

D. 函数体

下面程序定义了当模板形参类型绑定到constchar*时,compare函数的特化:

template <>

int  compare <constchar*>(const char*  const&v1,

const char*  const& v2)

{       return strcmp(v1,v2);      }

在这个例子中,模板有一个类型形参和两个函数形参,函数形参是类型形参的const引用,在这里,将类型形参固定为const char*,因此,函数形参是const char*const引用。当调用compare函数的时候,传给它两个字符指针,编译器将调用特化版本。

constchar  *cp1=”world”,*cp2=”hi”;

int  i1,i2;

compare(cp1,cp2);  //calls the specialization

compare(i1,i2);     //calls the generic versioninstantiated with int

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值