代码改变世界

白话C++系列(33) -- 函数模板

2016-07-05 20:38  Keiven_LY  阅读(812)  评论(0编辑  收藏  举报

思考:为什么要引入模板呢?

对于这个问题,我们通过一个例子让大家切实体会一下,模板给我们带来的好处。

当我们要写一个比较大小的函数时,如果我们要比较的两个数是整数,那么,我们往往会这样来定义:

首先,传入两个int类型的参数a和b,然后去比较a和b的大小,将较大的数通过return返回出来,使它成为max这个函数的返回值。

可是,如果我们又想比较两个float类型的数,那么这两个数也要取其中的最大值,那么,我们可以写成这样:

同样的,如果我们要比较两个char类型的字符呢,那么,我们就可以写成这样:

通过上面的三个函数,大家可以看到,除了数据类型有所不同之外,它们的运算逻辑是完全相同的。那么,在这种情况下,大家会发现,如果我们要写上这三个函数,对于我们程序员来说,简直太痛苦,而且所做的工作重复性较高,没有多大意义。所以,这种情况下,最好有一种方案:将类型作为参数传递进去,通过计算机帮我们把这三个函数做出来,做出来之后就可以分别通过这三个函数来处理int类型,float类型、char类型的数据,并且能够根据传入的数据类型做相应的处理,从而得到相应的返回值,于是,作为程序员的我们就省事多了。

那么,如果想要这么做,我们需要学习三个关键字: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类型作为第二个参数

注意:typenameclass这两个关键字可以混用,它们所起的作用是一样的。如下所示:

我们还可以这样来混用,如下所示:

在这里,用typename哎定义了一个数据类型T,另外一个则是int类型的变量size。我们在使用的时候,就可以制定出size的值以及T的数据类型,如下:

这里调用display的时候,第一个传入的参数是int类型,即用int代替了上面的T,第二参数是5,即用5代替了上面的size,打印的效果就是打印出5个a。

函数模板与重载

其实函数的模板看上去就已经具有重载的意思了,因为通过函数的模板可以拓展出无数个模板函数来,我们可以尽情的去想象它能够拓展出来的数据类型,那么,这些拓展出来的模板函数之间就能形成了一种重载关系。此外,不同的函数模板所拓展出来的模板函数也能够形成重载。我们来看一看:

上面这三个函数的模板骑士都有所不同。第一个函数模板只有一个参数a,第二个函数模板有两个参数a和b,那么第一个与第二个就形成了参数个数不同的函数重载关系。第三个函数模板也只有一个参数,看上去与第一个函数模板一样,但是,第三个函数模板,它的模板参数本身就有两个:一个是T类型,一个是int类型的size变量,那么这个时候当我们去使用的时候:

你会发现,通过三个不同的函数模板可以实例化出三个不同的模板函数,这三个不同的模板函数之间就形成了重载关系。大家请注意:我们在定义出函数模板的时候,函数模板本身并不是互相重载的关系,因为当我们仅仅定义出函数模板,在内存当中并不会产生任何的数据代码,只有当我们去使用它的时候,编译器才会为我们产生出相应的函数代码出来,这些函数代码之间才可以称得上具有重载关系。

函数模板的代码实践

题目描述:

/*   **************************  */

/* 函数模板

       要求:定义函数模板display

*/

/*   **************************  */

程序代码:

#include <iostream>
#include <stdlib.h>
using namespace std;

template <typename T>
void display(T a)
{
    cout << a << endl;
}

template <typename T, class S>
void display(T t, S s)  //C++是区分大小写的
{
    cout << t << "" << s << endl;
}

template <typename T, int KSize>
void display(T a)
{
    for(int i = 0; i < KSize; i++)
    {
        cout << a << endl;
    }
}


int main()
{
    //调用第一个display函数模板
    cout << "第一个函数模板调用示例:" << endl;
    display<int>(10);
    display<double>(10.89); 
    display<char>('A');

    //调用第二个display函数模板
    cout << "第二个函数模板调用示例:" << endl;
    display<int, double>(20,8.8);

    //调用第三个display函数模板
    cout << "第三个函数模板调用示例:" << endl;
    display<int,5>(100);//这里一旦传入5之后,KSize就成为了一个常量,并且作为循环次数

    system("pause");
    return 0;
}

运行结果:

练习:定义一个函数模板,功能是交换两个数的位置

#include <iostream>
using namespace std;
/**
 * 定义模板函数swapNum
 * 实现功能:交换两个数的位置
 */
template<typename T>
void swapNum(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

int main(void)
{
    int x = 10;
    int y = 20;
    // 调用模板函数
    swapNum<int>(x,y);
    cout << "x = " << x << endl;
    cout << "y = " << y << endl;
    system("pause");
    return 0;
}

运行结果: