A preliminary understanding of templates in C++

 

Table of contents

A preliminary understanding of templates in C++

1 What can C++ standard templates do

2 Classification of Templates

2.1 Class Templates

2.2 Function Templates

2.2.1 Examples

2.2.2 Definition and usage of function template

                                               A preliminary understanding of templates in C++

1 What can C++ standard templates do?

        In the process of programming, sometimes we will encounter problems like this: a certain algorithm has nothing to do with the data type, but when programming, in order to adapt to various data types, it is often necessary to write multiple versions or multiple overloaded functions to solve it. Another situation is that the logical organization of a certain data is itself independent of the type, but the same different objects need to write different versions. So we have such a dream: to have a standard component warehouse, and the components in it all have good and easy-to-understand standard interfaces. If you can make full use of the wisdom and experience of smart people instead of doing a lot of repetitive work yourself, how bright the sun will be at that time!
  The day has finally arrived. C++ can help our dreams come true.

2   Classification of Templates

2.1 Class Templates

        Sometimes, there are two or more classes whose functions are the same, only the data types are different, such as the following statement declares a class:

Sample code 1:

class Compare_int

{

public:

    Compare_int(inta,int b)

    {

        x=a;

        y=b;

    }

    int max( )

    {

        return(x>y)?x:y;

    }

    int min( )

    {

        return(x<y)?x:y;

    }

private:

    int x,y;

};

Its function is to compare two integers, and the larger and smaller of the two integers can be obtained by calling the member functions max and min. If you want to compare two floating-point numbers (float type), you need to declare another class:

Sample code 2:

class Compare_float

{

public:

    Compare_float(floata,float b)

    {

        x=a;

        y=b;

    }

    float max( )

    {

        return(x>y)?x:y;

    }

    float min( )

    {

        return(x<y)?x:y;

    }

private:

    float x,y;

};

 

        如上两个类,唯一不同的是数据类型不同,组织结构完全相同。类似于这样的问题在我们的编程中经常遇到,有人可能想到C++不是有函数重载么,利用函数重载可以避免重写两个类,但重复的代码量也是很多的。在C++中还有一种解决方式就是利用模板,利用模板类写出一个如下的类:

template<class numtype> //声明一个模板,虚拟类型名为numtype

class Compare  //类模板名为Compare

{

public:

    Compare( numtypea, numtype b)

    {

        x=a;

        y=b;

    }

    numtype max()

    {

        return (x>y)?x:y;

    }

    numtype min()

    {

        return (x<y)?x:y;

    }

private:

    numtype x,y;

};

从上述代码可以看出:

(1) 声明类模板时要增加一行:

        template <class 类型参数名>

(2) 原有的类型名int全都换成了虚拟类型参数名numtype。当建立类对象时,如将实际类型指定为int型,编译器就会用int取代所有的numtype,如果指定为float型,就用float取代所有的numtype。这样就实现一类多用。

由于类模板包含类型参数,因此又称为参数化的类。如果说类是对象的抽象,对象是类的实例,则类模板是类的抽象,类是类模板的实例。利用类模板可以建立含各种数据类型的类。

那么在声明一个类模板后,怎样使用它?怎样使它变成一个实际的类呢? 现在回顾一下类定义对象的方法:

Compare_int cmp1(4,7);//Compare_int是已声明的类

用类模板定义对象的方法与此相似,但是不能直接写成:

Compare cmp(4,7); // Compare是类模板名

具体的做法是:

Compare <int> cmp(4,7);

即在类模板名之后在尖括号内指定实际的类型名,在进行编译时,编译系统就用int取代类模板中的类型参数numtype,这样就把类模板具体化了,或者说实例化了。这时Compare<int>就相当于前面介绍的Compare_int类。

Compare是类模板名,而不是一个具体的类,类模板体中的类型numtype并不是一个实际的类型,只是一个虚拟的类型,无法用它去定义对象。必须用实际类型名去取代虚拟的类型。

        还有一个问题要说明:上面列出的类模板中的成员函数是在类模板内定义的。如果改为在类模板外定义,不能用一般定义类成员函数的形式:

numtype Compare::max( )

{

     return (x>y)?x:y;

}

而应当写成类模板的形式:

template<class numtype>

numtype Compare<numtype>::max( )

{

     return (x>y)?x:y;}

}

归纳以上的介绍,可以这样声明和使用类模板:

(1)  先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。

(2)  将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。

(3)  在类声明前面加入一行,格式为

    template<class 虚拟类型参数>

如:

    template<class numtype>   //注意本行末尾无分号

    class Compare                         

    {

              …//类体

    };   

(4)  用类模板定义对象时用以下形式:

        类模板名<实际类型名>  对象名;

        类模板名<实际类型名>  对象名(实参表列);

如:

    Compare<int>cmp;

    Compare<int>cmp(3,7);

(5)  如果在类模板外定义成员函数,应写成类模板形式:

    template<class 虚拟类型参数>

    函数类型 类模板名<虚拟类型参数>∷成员函数名(函数形参表列)

    {

      …

    }

 

说明:

(1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:

    template<classT1,class T2>

    class someclass

    {…};

      在定义对象时分别代入实际的类型名,如:

    someclass<int,double>obj;

(2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。

(3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。

 

2.2 函数模板

2.2.1 举例

        我们先来以一个求一个数的绝对值的例子开始。通常情况下一个数有各种不同的类型,所以在C++中可以使用函数重载的方法如下:

重载方法一:

#include<iostream.h>

#include<math.h>

int f(int i){return i>0? i: -i; }             //函数重载

long int f(long inti){  return i>0? i: -i; }  //函数重载

double f(float i){  return i>0? i: -i; }       //函数重载

 

void main()

{

    int a;  long int b;    float c;           //声明变量

    cin>>a>>b>>c;

    cout<<"整型:"<<f(a)<<" 长整型:"<<f(b)<<" 浮点型:"<<f(c)<<endl;

}

重载方法二:

#include<iostream.h>

#include<math.h>

class ABS     //求绝对值

{

public:

    int f(int i){ return abs(i); }                    //函数重载

    long int f(long int i){ return labs(i);}         //函数重载

    double f(float i){ return fabs(i); }              //函数重载

};

void main()

{  

    ABS n;                          //声明对象

    int a;  long int b;    float c; //声明变量

    cin>>a>>b>>c;

    cout<<"整型:"<<n.f(a)<<" 长整型:"<<n.f(b)<<" 浮点型:"<<n.f(c);

}

        如下我们使用函数模板来完成求一个数的绝对值

方法三:

template <typenameT> T abs(T a)

{

     returna>0?a:-a;

}

int main()

{

     int x=-1;

     inty=-0.99;

 

     cout<<"abs(x)="<<abs(x)<<endl<<"abs(y)="<<abs(y)<<endl;

     Sleep(5000);

 

     return 0;

}

结果为:

        

        第一次使用函数模板abs()时,实参为整数,由此可以推导出函数模板中的参数类型T为整型,函数的返回值也是整型。

        第二次调用abs()时实参为长整型,由此推导出函数模板中的参数类型T为长整型,函数的返回值为长整型。

 

2.2.2 函数模板的定义与使用

        利用函数模板,可以建立一个具有通用功能的函数,支持不同的函数参数和返回值,达到减少代码书写量的目的。

函数模板的定义形式如下:   

       template < class(typename)  T >

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

       {  函数体 }

其中:

        Template,class,typename为关键字;

        class或typename与T一起说明用户定义的数据类型,当函数不以类而存在时,使用关键字typename, 当函数要在某个类中声明时使用class;当然C++并没有规定必须得这样,而两个关键字都可以,这样做为了区分方便。

        类型形参T代表在函数模板中要使用的通用类型,在该函数的调用过程中,T被实参的类型具体化。

        函数模板实际上是定义了一类函数,对有些参数或数据成员的数据类型并不具体指定,而是作为模板的参数。等到使用模板时再根据实参的数据类型来确定模板参数的类型,得到模板的实例,称为模板的实例化。

例1:

template <typename T>

T abs(T value)

{    

    returnvalue>0? value: -value;  

}

若使用模板时:

abs(5);      //将模板参数的类型实例化为整型

abs(-3.8);    //将模板参数的类型实例化为实型

函数模板实例化后生成的函数称为模板函数。在程序中可用不同类型的实参来调用同一个函数,减少了代码的书写。

例2  编写选择排序的函数模板并实现排序

#include "stdafx.h"

#include <iostream>

#include <windows.h>

 

using namespace std;

//函数模板(两数交换)

template<typenameT> void Swap(T &x,T &y)

{

      T temp=x; x=y; y=temp; 

}

 

//函数模板(选择排序)

template<typenameT>  voidSort(T *v, int n)   //参数中有参数化类型的参数,也有普通类型的参数

{

    for(int i=0;i<n;i++)

     {

       for(int j=i;j<n;j++)

        {

          if(v[i]>v[j])

          {

               Swap(v[j],v[j+1]); //调用函数模板实例化后生成的模板函数

          }

        }

     }

}

 

//print函数模板(显示输出)

template<typenameT>  voidPrint(T *v,int n)

{

    for(int i=0;i<n;i++)

        cout<<""<<v[i];

    cout<<endl;

}

 

void main()

{

   shorta[10]={22,11,44,66,88,99,0,33,55,77};

   cout<<"排序前:";   

   Print(a,10);

   Sort(a,10);           //对整型数据排序

   cout<<"排序后:";   

   Print(a,10);

   char s[]="zcpkvrehaq";

   cout<<"排序前:";   

   Print(s,10);

   Sort(s,10);           //对字符型数据排序

   cout<<"排序后:";   

   Print(s,10);

   Sleep(5000);

}

运行结果为:

        

 

Guess you like

Origin blog.csdn.net/besidemyself/article/details/7201183