2.1 函数模板

2.1.1 基本范例

创建一个函数,能将两个int类型的形参相减,将结果值返回,代码如下。

如果是两个float类型的形参相减呢?那么还要再写一个函数版本,代码如下。

可以看到,上面是两个同名函数,属于函数重载。

这两个函数的区别是什么呢?除了参数类型,函数体的代码是一样的,重复写这种代码完全没有必要,如果将来参数类型再发生改变,还得再写一个类似的函数。那么,这种场合就是使用函数模板的最好场合。

这里,不想为每种类型都定义一个不同的函数,所以采取定义一个通用的函数模板的策略。下面看一看怎样利用模板写出一个适合多种参数类型求差值的函数,将刚才的两个重载函数全部注释,增加以下代码,定义一个函数模板。

     template <typename T>
     T Sub(T tv1, T tv2)
     {
        return tv1 - tv2;
     }

在main()主函数中添加代码:

     int subv = Sub(3, 5);
     cout << "subv = " << subv << endl;
     double subv2 = Sub(4.7 , 2.1);
     cout << "subv2 = " << subv2 << endl;

运行程序,查看结果:

     subv = -2
     subv2 = 2.6

上述就是一个函数模板的典型范例,因为函数参数的类型没有确定,所以用T表示,这个T可以称为模板参数,更确切地,是一个类型模板参数,因为它代表的是一个类型

这里有一些模板的基础知识:①模板的定义是以template关键字开头;②类型模板参数T前面用typename修饰(语法就这样规定),所以,遇到typename就应该知道其后跟的是一个类型;同时,这里的typename可以用class取代(但推荐使用typename),但这里的class并没有“类”的意思(有些人习惯于使用typename表明对应的模板实参可以是任意类型,而使用class表明对应的模板实参必须是一个类类型);③类型模板参数T(代表一个类型)以及前面的修饰符typename都是用尖括号< >括起来的;④T可以换成任何其他标识符,对程序没有影响,用T是一种使用习惯而已。

如果读者对这些最基础的模板知识仍感到陌生,从现在开始逐步去掌握,它们其实都很简单。

如果继续在main()主函数中添加代码:

     string a("abc"), b("def");
     string addresult = Sub(a, b);

编译一下,发现报错,这是为什么呢?因为这里的T是string,而string类型是不支持减法操作的,所以Sub()这个函数模板里面的代码行就是不合法的。这说明Sub()函数模板要想成功编译,传入的类型T必须要支持减法操作。所以,同样一个函数模板,可能以某种方式进行调用是合法的,而换一种方式调用就不合法了。尤其值得注意的是,这种合法性,在编译阶段就可以由编译器判断出来,因为这些对Sub()函数模板的调用代码就在这里摆着,编译器有能力在编译时就从这些调用代码中去推断Sub()函数模板中的模板参数T的类型。根据模板参数T的类型,编译器就能够判断出这个类型是否支持减法运算。