函数模板的引入
int Max(int a, int b) {return a>b? a : b; }
char Max(char, char b) {return a>b? a : b; }
double Max(double a, double b) {return a>b? a : b; }
以上是C++里函数的重载,C语言不支持函数重载,C语言汇编的时候对于如上三个Max, 都会起名为_Max, 对于C语言就会发生命名冲突,所以C不可以函数重载;而对于C++,C++编译的时候会用名字粉碎技术,对如上三个Max函数名进行改写,
虽然源码里三个函数都是Max但是编译器编译之后,三个函数名就不一样了
注意:虽然经过名字粉碎技术,返回值也作了标记,但是返回值类型仍不能作为区分重载函数的依据,因为调用时编译器无法根据返回值类型识别具体调用的函数,例如对于如下代码:
int Max(char a, char b) { return a>b? a : b;} char Max(char a, char b) { return a>b? a : b;} int main() { Max('x','y'); }
在主函数中调用Max函数,由于两个Max形参列表都一样,所以主函数无法识别Max具体要调用哪一个,似乎两个都可以调动,这样就产生了二义性,所以这样的重载是错误的
对于最开始给出的三个函数,我们发现这三个函数处理的过程都是一样的,只是处理类型不同而已,那能不能把类型与算法分离开呢,这就要用到函数模板
函数模板
函数模板的标准形式:
template<class Type> //template与class是关键字,class也可以为Typename,Type是占位符,可简写为T
返回类型 函数名(Type a)
{
函数体
}
对于如下代码:
template<class Type>
const Type & Max(const Type &a, const Type &b)
{
return a>b? a : b;
}
int main()
{
int a = Max(12,23);
char ch = Max('a','b');
double db = Max(12.23, 23.45);
}
编译器在编译的时候,会根据调用的具体类型对函数模板进行推演到具体的模板函数,这个过程就是模板推演过程
如果不需要编译器用实参和形参去推演,可以在调用的时候显示地给出类型的参数,如下:
int main()
{
int a = Max<int>(12,23);
char ch = Max<char>('a','b');
double db = Max<double>(12.23, 23.45);
}
注意:模板的推演不能理解为宏替换,而是重命名typedef,宏替换和typedef是不一样的,例如对于如下代码:
#define PINT int* typedef int* SINT int main() { PINT x,y; SINT a,b; }
PINT宏替换,所以x是int*类型,y是int类型,因为指针是和变量名进行结合的;而SINT是取别名,则a和b都是int*类型
又如下函数模板代码:#include <iostream> #include <typeinfo> using namespace std; template<class T> void fun(T a) { T x,y; cout<<typeid(x).name()<<endl; cout<<typeid(y).name()<<endl; } int main() { int a = 10; int *p = &a; fun(p); }
p是int*类型,所以x和y都是int*类型, 输出结果如下:
模板把功能代码段的“算法”和算法中“数据的数据类型”区分开来。这样编程者可以先写算法,而在定义算法时不确定实际计算数据的数据类型,然后在使用算法时再根据实际获得的数据类型来确定使用何种数据类型进行数值计算
使用函数模板的注意事项:
- 在定义模板形参表时,每一个数据类型参数的前面必须书写关键字class或typename,以下的定义格式是错误的:
template <typename T1, T2>//错误,T2前也要加关键字 void fun(T1 a, T2 b) {...}
- 调用模板函数时,<模板实参表>在大多数情况下可以缺省,但在某些情况下不可以:函数模板有多个虚拟类型参数,而在生成模板函数时函数实际参数不足以让编译器确认每一个虚拟类型参数,此时必须明确给出<模板实参表>,例:
#include <iostream> using namespace std; template <class T1, class T2> T1 fun(T2 x) { return x; } int main() { int a = 10; float b; b = fun<float,int>(a);//T1是float,T2是int }
函数模板:函数模板的重点是模板。表示的是一个模板,专门用来生产函数。
模板函数:是函数模板的实例化,是一个函数。函数模板与模板函数是抽象与具体的关系
函数模板的重载
函数的重载就是函数名相同,参数个数或类型不同,函数模板的重载与一般函数的重载在概念和方法上是完全一样的,利用函数模板的参数列表的不同,可以为函数模板提供重载。例如如下multiply函数:
template <class T>
T multiply(T var1, T var2) {.......}
template <class T>
T multiply(T var1, int var2) {.......}
template <class T>
T multiply(T var1, T var2, T var3) {.......}
可以看到,上面定义的三个函数模板,其函数名称,返回值的类型完全相同,只是函数参数类型或参数个数不同,这和普通函数的重载一样的,利用函数模板的重载不仅可以解决不同数据类型的参数问题,还可以解决参数个数不同的问题,使得函数模板的应用能力进一步增强
注意:
1、函数返回值不能作为区分重载函数的依据
2、以下两个函数模板也不是重载的关系
template <class T1>
T1 multiply(T1 var1, T1 var2) {.......}
template <class T2>
T2 multiply(T2 var1, T2 var2) {.......}
虚拟类型参数T1和T2所代表的实际数据类型只是在函数被真正调用时才由编译器确定,虚拟类型参数的名称本身没有任何实际意义,即参数名T1或者T2并没有任何差别,因此上面的两个函数模板其实是一模一样的函数模板。如果将上面的两个定义写在同一个程序段中,就会造成函数模板的重复定义,编译器会在编译的过程中发现这个错误并向用户报警。
类模板
如同利用函数模板技术可以解决如何用一个函数定义处理不同数据类型的数据问题,利用类模板也可以解决如何让一个类定义能够处理不同数据类型的问题。
通过一个类模板,可以生成一系列数据类型不同的模板类,类模板与模板类的关系如同函数模板与函数模板的关系,都是抽象与具体的关系
对于类模板,在使用类模板去实例化形成模板类时,要显示地给出类型,它不同于函数模板,它没有办法去推演。
类模板的定义格式如下:
template <class T>//class也可以是typename
class 类名
{
};
在完成类模板的定义后,可以像普通类一样定义类的对象,用类模板定义对象的格式如下:
类名 <模板实参表> 对象名
模板类定义对象的时候,由于不能通过参数传递的方法来确定虚拟类型参数对应的具体数据类型,所以必须使用<模板实参表>来告诉编译器具体的数据类型
类模板示例:
template <class T>
class Stack
{
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const
{
return elems.empty();
}
};