思考:为什么要引入模板呢?
对于这个问题,我们通过一个例子让大家切实体会一下,模板给我们带来的好处。
当我们要写一个比较大小的函数时,如果我们要比较的两个数是整数,那么,我们往往会这样来定义:
首先,传入两个int类型的参数a和b,然后去比较a和b的大小,将较大的数通过return返回出来,使它成为max这个函数的返回值。
可是,如果我们又想比较两个float类型的数,那么这两个数也要取其中的最大值,那么,我们可以写成这样:
同样的,如果我们要比较两个char类型的字符呢,那么,我们就可以写成这样:
通过上面的三个函数,大家可以看到,除了数据类型有所不同之外,它们的运算逻辑是完全相同的。那么,在这种情况下,大家会发现,如果我们要写上这三个函数,对于我们程序员来说,简直太痛苦,而且所做的工作重复性较高,没有多大意义。所以,这种情况下,最好有一种方案:将类型作为参数传递进去,通过计算机帮我们把这三个函数做出来,做出来之后就可以分别通过这三个函数来处理int类型,float类型、char类型的数据,并且能够根据传入的数据类型做相应的处理,从而得到相应的返回值,于是,作为程序员的我们就省事多了。
![](/qrcode.jpg)
那么,如果想要这么做,我们需要学习三个关键字:template(模板)、typename、class(注意:这里的class不是表示类的,而是表示数据类型的)。我们来看一看具体的使用方法。
我们看到,当我们要定义一个函数模板的时候,我们需要通过关键字template来声明一个函数模板,通过class或者typename这个关键字来声明一个参数T,这个参数T能够表明一种数据类型。如果我们再去写max这个函数的时候,它的返回值就写上T,它的参数类型也用T来作参数类型,并且函数内部逻辑不变。于是,当我们未来要传入的是int类型的时候,计算机就会通过这个函数模板将它实例化一个模板函数,这个时候模板函数中的数据类型T就变成了int,就会去处理int类型的数据了。如果遇到的是float类型,T就变成了float,然后也就会去处理float类型的数据了。我们来看一看具体的使用。
当我们用max去比较两个数的大小的时候,如果我们不指定它的数据类型T,那么,计算机会根据自己的判断去选择一种模板函数,选择之后就会用自己的计算逻辑,比如说这里的第一行代码,传入的100和99都是int类型的,它就会自动实例化一个int类型的模板函数,然后对100和99进行相应处理,并将处理互殴的结果100返回出来作为返回值赋值给ival。如果我们制定了数据类型,比如说这里的第二行代码,同样是调用max函数,但是这里用了一对尖括号指明了数据类型为char类型,那么,这就指定了所传入的参数一定要是char类型的参数才可以,然后将返回值返回出去。
那么,我们上面所说的模板函数和函数模板,我们给大家指出来,如下所示:
函数模板是函数的模具,通过模子就可以生产出一个一个的函数,那么,通过函数模板生产出来的函数就称之为模板函数。在计算机当中,如果我们仅仅写出了函数模板而没有去使用它,那么,计算机是不会产生任何代码数据的,因为它也不知道要产生什么样的代码数据。只有当我们去使用函数模板的时候,计算机才会知道具体要实例化出一个怎样的模板函数来,这个时候才会产生真正的代码数据,从而才会参与逻辑运算。
下面我们来看一看通过关键字typename如何来定义一个函数模板。
以上是一个数据交换的函数,在使用上没有什么不同,我们可以看到:当我们调用swap函数的时候,我们所传入的参数是int类型,这就意味着前面的数据类型T就替换成了int。
变量作为模板参数
变量作为模板参数如何来使用呢??我们直接来看使用的方法:
这里通过关键字template声明了一个函数模板,注意,这个时候传入的不再是类型,而是一个变量,而这个变量在我们真正去使用的时候才会将这个函数模板实例化成一个模板函数,它才是一个确定的值,如果不使用它,仍然是没有任何代码数据产生。使用的时候,我们在这传入的不是类型,而是一个确定的数值,此时这个值就是一个常数,只不过在这体现出来的看上去像是一个变量,但真正编译出来就是一个常数。
多参数函数模板
模板有的时候变得很复杂,因为我们不能确定,在我们的日常应用当中只有一个类型作为模板的参数,如果有多个参数我们该如何去处理呢?我们来看一看:
我们看到,当有多个参数时,需要用逗号(,)隔开,并且,尖括号中的两个typename关键字是不能省略的。当我们写成这样之后,T和C就变成了函数模板的参数,那么,在display函数中,其中一个参数a是T类型的参数,另一个参数b是C类型的参数。使用的时候,我们需要将T类型和C类型都指定出来,如下:
在这我们调用display的时候,就爱那个int类型作为第一个参数,将string类型作为第二个参数
注意:typename和class这两个关键字可以混用,它们所起的作用是一样的。如下所示:
我们还可以这样来混用,如下所示:
在这里,用typename哎定义了一个数据类型T,另外一个则是int类型的变量size。我们在使用的时候,就可以制定出size的值以及T的数据类型,如下:
这里调用display的时候,第一个传入的参数是int类型,即用int代替了上面的T,第二参数是5,即用5代替了上面的size,打印的效果就是打印出5个a。
函数模板与重载
其实函数的模板看上去就已经具有重载的意思了,因为通过函数的模板可以拓展出无数个模板函数来,我们可以尽情的去想象它能够拓展出来的数据类型,那么,这些拓展出来的模板函数之间就能形成了一种重载关系。此外,不同的函数模板所拓展出来的模板函数也能够形成重载。我们来看一看:
上面这三个函数的模板骑士都有所不同。第一个函数模板只有一个参数a,第二个函数模板有两个参数a和b,那么第一个与第二个就形成了参数个数不同的函数重载关系。第三个函数模板也只有一个参数,看上去与第一个函数模板一样,但是,第三个函数模板,它的模板参数本身就有两个:一个是T类型,一个是int类型的size变量,那么这个时候当我们去使用的时候:
你会发现,通过三个不同的函数模板可以实例化出三个不同的模板函数,这三个不同的模板函数之间就形成了重载关系。大家请注意:我们在定义出函数模板的时候,函数模板本身并不是互相重载的关系,因为当我们仅仅定义出函数模板,在内存当中并不会产生任何的数据代码,只有当我们去使用它的时候,编译器才会为我们产生出相应的函数代码出来,这些函数代码之间才可以称得上具有重载关系。
类模板
为什么会有类模板呢?这和函数模板的道理是一样的,是因为在很多使用场合下,一个类会用到很多次,而在用的时候发现很多重复的地方,只有它的数据类型不同,所以这个时候我们就要用到类模板。我们看下面一个例子:
在这里,我们定义了一个类:MyArray,其中,我们用T这种数据类型来定义了它的数据成员的指针,并且还定义了一个成员函数display,请大家注意,这个成员函数的定义时写在类内的。那么在类模板的情况下,在类内定义成员函数的时候并没有什么不同。但是,在类模板的情况下,在类外定义成员函数的时候,则大不相同,如下:
我们看到,在类模板的情况下,当在类外定义成员函数的时候,需要在这个成员函数的上方,先把template <class T>这一行代码写出来(注意,它的写法与在类的上方的写法是一样的),我们每定义一个成员函数,都要在这个定义的成员函数上方加上这一行代码;同时,我们需要在这个成员函数的类名后面用尖括号括上相应的参数T(如果有两个参数,需要用逗号隔开)。在使用的时候,我们如果实例化一个对象,我们就需要在类名的后面用尖括号括上当前这个对象是什么数据类型。
与函数模板一样,类模板并不产生实质性代码,只有当我们去实例化一个对象时,将类的后面写上一个固定的参数,这个时候才会产生数据代码,而这套数据代码,我们就称之为模板类,那么,这样的关系与前面所讲的函数模板与模板函数的道理一样。
下面我们来看一看,类模板当中使用多个参数的情况。当我们有多个参数时,我们举了一种比较复杂的情况(既有类型作为参数,也有变量作为参数)如下:
使用的时候,也分为类内定义和类外定义的成员函数。对于类内定义,我们不必多说了,而对于类外定义,如下:
在使用的时候,我们同样也要给定两个参数,如下:
需要大家特别注意的是:如果我们要定义一个类模板,我们必须将类的声明以及类的定义部分写在同一个.h文件当中,未来在使用的时候把它包含进来,所以在MyArray.cpp文件当中没有写任何代码,而是将所有代码都写在了MyArray.h文件当中了。
另外,定义类模板的时候,还需要注意以下内容:
- 在类的上面一行,要写上template关键字,然后加上模板参数列表;
- 如果我们在类的内部定义函数时(类内定义),那么,我们不需要有什么特别需要注意的地方;
- 如果我们在类的外部定义函数时(类外定义),则需要在每一个函数的上面加上template关键字,然后再加上模板参数列表。另外,在函数定义时,还需要用尖括号括上相应参数。
#ifndef TEMPLATE_H #define TEMPLATE_H template <typename T, int size ,int value>//类模板 class Template { public: Template(); ~Template(); void display(); private: T * m_pArr;//类模板里的参数 }; template <typename T, int size ,int value>//模板类的每个成员函数都要以模板的形式呈现 Template<T,size,value>::Template(){//模板类的每个成员函数都要以模板的形式呈现 m_pArr = new T[size];//创建类型为T的size个数组,赋值给指针 for(int i = 0; i < size; i ++){ m_pArr[i] = value;//所有赋值为value } } template <typename T, int size ,int value> Template<T,size,value>::~Template(){//模板类的每个成员函数都要以模板的形式呈现 delete []m_pArr; m_pArr = NULL; } template <typename T, int size ,int value> void Template<T,size,value>::display(){//模板类的每个成员函数都要以模板的形式呈现 for(int i = 0;i < size; i++){ cout<<"value :"<<m_pArr[i]<<endl; } } #endif // TEMPLATE_H
#include <iostream> #include "template.h" using namespace std; int main() { Template<int,10,5> tmp; tmp.display(); getchar(); return 0; }