有以下这样3个求加法的函数:
int Add(int x,int y) { return x+y; } double Add(double x,double y) { return x+y; } long Add(long x,long y) { return x+y; }
它们拥有同一个函数名,相同的函数体,却因为参数类型和返回值类型不一样,所以是3个完全不同的函数。即使它们是二元加法的重载函数,但是不得不为每一函数编写一组函数体完全相同的代码。如果从这些函数中提炼出一个通用函数,而它又适用于多种不同类型的数据,这样会使代码的重用率大大提高。那么C++的模板就可解决这样的问题。模板可以实现类型的参数化(把类型定义为参数),从而实现了真正的代码可重用性。C++中的模板可分为函数模板和类模板,而把函数模板的具体化称为模板函数,把类模板的具体化成为模板类。
1.函数模板就是建立一个通用的函数,其参数类型和返回类型不具体指定,用一个虚拟的类型来代表。函数模板的声明格式:
template<typename 类型参数>
返回类型 函数名(模板形参表)
{
函数体
}
或
template<class 类型参数>
返回类型 函数名(模板形参表)
{
函数体
}
template是一个声明模板的关键字,类型参数一般用T这样的标识符来代表一个虚拟的类型,当使用函数模板时,会将类型参数具体化。typename和class关键字作用都是用来表示它们之后的参数是一个类型的参数。只不过class是早期C++版本中所使用的,后来为了不与类产生混淆,所以增加个关键字typename。下面我就对上述3个加法函数进行函数模板化:
#include "stdafx.h" #include <iostream> template <typename T>//加法函数模板 T Add(T x,T y) { return x+y; } int main() { int x=10,y=10; std::cout<<Add(x,y)<<std::endl;//相当于调用函数int Add(int,int) double x1=10.10,y1=10.10; std::cout<<Add(x1,y1)<<std::endl;//相当于调用函数double Add(double,double) long x2=9999,y2=9999; std::cout<<Add(x2,y2)<<std::endl;//相当于调用函数long Add(long,long) return 0; }
结果:
看着上述的代码中,是否觉得函数模板的声明和C#中的泛型使用有点相像呢,当调用函数模板时(如:Add(10,10))就是对函数模板的具体化(如:int Add(int,int)),具体化的函数模板就是模板函数。在函数模板中类型参数也可以指定多个,只不过定义的每个类型参数之前都必须有关键字typename(class)。
#include "stdafx.h" #include <iostream> template <typename T1,typename T2>//多类型参数的函数模板 T1 Add(T1 x,T2 y) { return x+y; } int main() { int x=10; double y=10.10; std::cout<<Add(x,y)<<std::endl;//相当于调用函?数int Add(int,double) std::cout<<Add(y,x)<<std::endl;//相当于调用函数double Add(double,int) return 0; }
在定义函数模板时要注意的一点是在template语句和函数模板定义语句之间是不允许插入其他的语句的。和一般函数一样,函数模板也可以重载:
#include "stdafx.h" #include <iostream> template <typename T>//加法函数模板 T Add(T x,T y)//二?元? { std::cout<<"调用模板函数:"; return x+y; } template <typename T>//重载加法函数模板 T Add(T x,T y,T z)//三?元? { std::cout<<"调用重载模板函数:"; return x+y+z; } int main() { double x1=10.10,y1=10.10; std::cout<<Add(x1,y1)<<std::endl;//调用模板函数 //相当于调用函数double Add(double,double) std::cout<<Add(x1,y1,y1)<<std::endl;//调用重载模板函数 //相当于调用函数double Add(double,double,double) return 0; }
结果:
函数模板与同名非模板函数也可以重载。比如:
#include "stdafx.h" #include <iostream> template <typename T>//加法函数模板 T Add(T x,T y) { std::cout<<"调用模板函数:"; return x+y; } int Add(int x,int y) { std::cout<<"调用非模板函数:"; return x+y; } int main() { int x=10,y=10; std::cout<<Add(x,y)<<std::endl;//调用非模板函数 double x1=10.10,y1=10.10; std::cout<<Add(x1,y1)<<std::endl;//调用模板函数 return 0; }
结果:
就如示例代码运行的结果表明的一样,当模板函数和同名的非模板函数重载时,首先寻找与参数类型完全匹配的非模板函数,找到了,则调用它,如果没找到,则寻找函数模板,找到后具体化函数模板,而后调用该模板函数。
2.和函数模板一样,类模板就是建立一个通用类,其数据成员的类型、成员函数的返回类型和参数类型都不具体指定,用一个虚拟类型来代表。当使用类模板建立对象时,系统会根据实参的类型来取代类模板中的虚拟类型从而实现不同类的功能。其定义格式为:
template <typename 类型参数>
class 类名
{
类成员声明
}
或
template <class 类型参数>
class 类名
{
类成员声明
}
在类成员声明里,成员数据类型、成员函数的返回类型和参数类型前面需加上类型参数。在类模板中成员函数既可以定义在类模板内,也可以定义在类模板外,在定义类模板外时C++有这样的规定:需要在成员函数定义之前进行模板声明,且在成员函数名之前加上“类名<类型参数>::”:
template <typename(class) 类型参数>
返回类型 类名<类型参数>::函数名(形参)
{
函数体
}
而类模板定义对象的形式:
类模板名<实际类型> 对象名;
类模板名<实际类型> 对象名(实参);
示例说明如下:
#include "stdafx.h" #include <iostream> #include <string> template <typename T>//在类模板定义之前,都需要加上模板声明 class BinaryOperation//二目运算类 { private: T x; T y; char op; void add() { std::cout<<x<<op<<y<<"="<<x+y<<std::endl; } void sub() { std::cout<<x<<op<<y<<"="<<x-y<<std::endl; } void mul(); void div(); public: BinaryOperation(T x,T y):x(x),y(y) //调用构造函数时,利用初始化清单x(x),y(y)给对象的私有成员变量赋值, { } void determineOp(char op); }; //在类外定义成员函数: //在成员函数定义之前进行模板声明, //且在成员函数名之前加上"类类名<类型参数>::" template <typename T> void BinaryOperation <typename T>::mul() { std::cout<<x<<op<<y<<"="<<x*y<<std::endl; } template <typename T> void BinaryOperation <typename T>::div() { std::cout<<x<<op<<y<<"="<<x/y<<std::endl; } template <typename T> void BinaryOperation <typename T>::determineOp(char op) { this->op=op; switch(op) { case '+': add(); break; case '-': sub(); break; case '*': mul(); break; case '/': div(); break; default: break; } } int main() { BinaryOperation<int> op(10,10); //调用构造函数生成op时,顺便给对象op的成员变量赋值, op.determineOp('+'); op.determineOp('-'); op.determineOp('*'); op.determineOp('/'); return 0; }
结果:
和函数模板一样,类模板也允许定义多个类型参数,这里不再一一举例了,有兴趣的朋友可以参考上述的示例代码尝试一下。