C++知识文档十_模板

来源:互联网 发布:golang mgo 连接池 编辑:程序博客网 时间:2024/05/04 06:02

模板概念

我们在设计程序时,常常会遇到大量的相似函数。如比较多个数求其最大值,如果对于两个数的比较,我们可以将函数名称命名为Max, 各种数据类型的参数可以构成重载函数,保证函数名称不变在编程中具有很多优势,能让程序调用者确切知道函数的功能,调用简单。如果没有函数重载的概念,则每一个函数都要规定一个不能重复的函数,程序调用者在使用函时会感到困惑。但重载函数在使用中是有条件的,重载函数间必须在函数参数个数或参数类型上有所不同。而且同名的重载函数要分别定义,当遇到有大量的重载函数时不复不定义大量的重载函数,比如本例中仅两个参数计算最大值就可能有很重载函数,如果三个参数,多个参数,重载函数的数目可想而知。

 

有没有更进一步的简化办法呢?C++提出了模板的概念,所谓模板就是虚拟的类型,或者称参数化类型。模板与类型的关系就跟类型和变量的关系一样。

 

将模板应用在函数的参数和返回类型中称为模板函数,将模板应用到类的定义中称为模板类。


函数模板

函数模板是用模板做函数参数和返回,设计出的通用的函数。

其定义形式为:

template<class T1,class T2,,,>

函数返回类型 函数名(函数参数表)

{

   //函数模板定义

}

其中template表示定义的是模板,<>里是模板类型参数列表,其中每一个模板参数用关键字class来定义,模板参数的名称可以由程序定义,模板参数的名称就标识一个模板参数,该参数并不表示某一具体类型,而是表示了一个虚拟的类型,在函数调时可以用一个给定的类型来替换之,模板参数列表可以定义一个或多个模板参数。函数模板的返回值类型可以是普通类型,也可以是模板参数表中指定的类型。函数参数表给出的类型可以是普通类型,也可以是模板参数表中指定的类型。在模板参数表中指明的类型参数不必都用于函数参数表中。例如:

template<class T>

Tmax(T a,T b)

{

   return a>b? a:b;

}

在上例中,如果用一个普通类类型来实例化该函数模板,那么T应该重载”>”运算符。

例:

#include"stdafx.h"

#include<iostream>

usingnamespace std;

 

template<class T> T Max(T a, T b)

{

   return a>b? a:b;

}

intmain(int argc, char* argv[])

{

   int i1=1,i2=9;

   char c1='a', c2='b';

   //调用Max(int , int)

   int iRet=Max(i1,i2);       

   //调用Max(char , char)

   char cRet=Max(c1,c2);      

   cout<<iRet<<endl;

   cout<<cRet<<endl;

   return 0;

}

函数的模板参数

函数模板参数列表中也可以定义也可以出现具体类型参数,例子:

template<class T,inti>

Tmax(T a, T b)

{

   T c = a>b? a:b;

   if (c>i)

      return c;

   else

      return i;

}

上例中参数i是具体类型的参数。

模板函数的调用

一般来讲,模板函数可以直接调用,比如例子中代码int iRet=Max(i1,i2);编译器会自动根据函数实参的类型去替换模板类型,并生成整型版本的函数加以调用。

也可以明确指定其实例化所用的类型参数。比如在上面的例子中加上:

intiRet=Max<int>(c1,c2);

当函数模板参数中有具体类型的参数,函数的调用必须显式指定模板参数实例化所用的类型,如上例中函数的调用必须使用下面的语句调用:

inta = max<int, 5>(12,18);

具体类型的参数在实例化时要用该类型的值来替换。

函数模板之间也可以重载

和普通函数一样,函数模板之间也可以重载。例:

template<classT>

TFunc(T t)

{

   return t;

}

template<classT>

intFunc(int i,T t)

{

   return i*t;

}

而且函数模板也可以与普通函数之间构成重载关系。如上例中可以再写一个普通函数:

intFunc(int  i)

{

   return i;

}

当函数调用时,具体函数可以调用,普通函数也可以调用,实际会调用哪一个呢?实际会调用具体函数。

模板函重载规则

函数模板表示了一组相同的函数,这些函数之间(包括重载的函数模板),以及这些函数与其他同名的普通函数之间是重载的关系。这些重载函数之间的匹配规则如下:

1)  如果发现某一函数的参数正好与调用函数所使用的参数匹配,则调用该函数;否则按照2执行;

2)  如果从相应的模板所生成的某个函数,其参数正好与调用函数的参数匹配,调用时使用从模板实例化生成的函数,否则执行3;

3)  对相应参数进行隐式转换后,调用普通的函数;

4)  调用模板实例化生成的函数时,从不进行隐式的类型转换;

5)  若明确指定了函数模板的类型参数,则调用相应的模板实例化后生成的函数,如果需要,编译器会进行隐式类型转换

例:

#include<iostream>

usingnamespace std;

intMax(int i1,int i2)

{

   cout<<"NormalMax"<<endl;

   return i1>i2? i1:i2;

}

template<classT> Max(T t1, T t2)

{

   cout<<"TemplateMax,sizeof(t1):"<<sizeof(t1)<<endl;

   return t1>t2? t1:t2;

}

intmain(int argc, char* argv[])

{

   int i1=1,i2=9;

   char c1='a',c2='b';

   //调用普通函数int Max(int i1,int i2)

   int iRet=Max(i1,i2);       

   //调用模板实例化生成的char Max(char a,char b)

   char cRet=Max(c1,c2);      

   //调用模板实例化生成的char Max(char a,char b),

   //最后将返回值隐式转换成int型

   int iRet2=Max(c1,c2);

   //调用模板实例化生成的char Max(char a,char b),

   char cRet2=Max<char>(i1,c1);  

   return 0;

}


类模板

在定义一个类时,也可以包含模板参数。类模板的主要用途是实现包容类,如集合、链表等。虽然模板的功能很大程度上可以通过宏实现,但是模板带有类型信息,编译器可以进行类型检查,而且具有更好的通用型。

类模板的定义形式为:

template<class T1,class T2...class Tn>

class类模板名

{

   //类模板定义

}

其中template是C++关键字,表示是对模板进行定义。tempalte后的<>里是模板的参数,参数可以有一个或多个,每个参数用class关键字修饰,并用逗号格开。class关键字也可以用基本数据类型代替。在类模板实例化时,T可以是任意类型。接下来的关键字class 说明模板是类模板,后面紧接的是类模板名。

下面例子定义了一个堆栈模板类,并使用该模板类实现了字符堆栈和整型堆栈:

#include"stdafx.h"

#include<iostream>

usingnamespace std;

template<class T> class TStack

{

private:

   T* pFirst;

   T* pLast;

   int iSize;

public:

   TStack(int i)

   {

      iSize=i;

      pFirst=pLast=new T[iSize];

   }

   ~TStack()

   {

      delete []pFirst;

   }

   void Push(T t)

   {

      *pLast++=t;

   }

   T Pop()

   {

      --pLast;

      return *pLast;

   }

   int GetSize() const

   {

      return pLast-pFirst;

   }

};

 

typedefTStack<char> CStackChar;

typedefTStack<int>  CStackInt;

 

intmain(int argc, char* argv[])

{

   CStackChar charStack(3);

   CStackInt intStack(5);

   charStack.Push('a');

   charStack.Push('b');

   charStack.Push('c');

   cout<<"CharStack:"<<charStack.Pop()

      <<","<<charStack.Pop()

      <<","<<charStack.Pop()<<endl;

   intStack.Push(1);

   intStack.Push(2);

   cout<<"IntStack:"<<intStack.Pop()

      <<","<<intStack.Pop();

   return 0;

}

由于类模板中T是一个类型参数,不是实际的类型,因此,不能由它直接生成实例对象。为类模板中的类型参数指定具体类型的过程叫做类模板的实例化,类模板实例化的结果是类,而不是对象。实例化类模板的一般形式为:

类模板名<具体类型表>

比如上面的:

typedefTStack<char> CStackChar;

typedefTStack<int>  CStackInt;

用类模板实例化了两个类CStackChar,CStackInt.

类模板的成员函数也可以在类模板的外面定义。如

template<classT>

voidTStack<T>::Push(T t)

{

   *pLast++=a;

}

类模板成员函数也可以重载,如:

template<classT> T TStack<T>::Pop(int i)

{

   return *(pLast-i);

}

类模板的继承性

  类模板可以从普通类中派生。

如:

classCBase

{

   //...

};

template<classT> class TDerived:public CBase

{

public:

   T t;

   //...

};

  类模板的基类也可以是类模板。

比如:

template<classT> class CBase

{

public:

   T t;

};

template<classT1,class T2>

classTDerived:public CBase<T2>

{

public:

   T1 t;

};

注意,派生的类模板的基类是类模板时,派生类模板的参数表中应包含基类模板的参数。

如果派生类没有类型参数,或者它的类型参数与基类的参数相同时,派生类模板的参数只需包含基类模板的参数。如:

template<classT> TDerived:public CBase<T>

{

public:

   T d;

   //...

};

  无法从类模板中派生出普通类。

因为从类模板中派生的类总是含有类型参数,不能做为普通类使用。

 

  类模板之间也可以多重继承:

例子:

template<classT> class TBase

{

public:

   T tb;

};

 

classCBase

{

   //...

};

 

template<classT> class TDerived:public TBase<T>,public CBase

{

public:

   T td;

   //...

};

类模板实例化后即是普通的类,所以类模板实例化后也可以当函数的参数类型使用:

voidFunc(TStack<int> &intStack)

{

   intStack.Push(1);

   //...

}

  类模板实例化后生成的类可以做其他类模板的参数

前面说过,可以用任何具体类型作为参数来实例化类模板,类模板实例化以后就可以当普通类来使用,所以也可以用类模板实例化后生成的类来做为其他类模板的参数:

template<classT>

classTVector

{

   //...

}

TStack<TVector<int>> vStack(5);

注意两个”> >”中间应该空一格,否则当右移处理了0。

  标准的数据类型也可以做模板参数

模板参数除了类型参数外,也可以是标准的数据类型,但是在类模板实例化类时,标准数据类型的模板参数必须用实际的值代替。

template<classT,int i> class TBase

{

public:

   T tb;

   int m_i;

   TBase();

};

template<classT,int i>

TBase<T,i>::TBase():m_i(i)

{

  

}

intmain(int argc, char* argv[])

{

   TBase<int,9> obj;

   return 0;

}

  类模板的模板参数也可以没有类型参数

如:

template<inti> class A

{

   int ar[i];

};


0 0