目录
一、模板的意义
我们都知道,C语言中有函数重载这一语法,可以达到对于不同类型的数据进行相同操作的目的。但是,如果需要的传参组合类型过多,会导致函数定义显得冗余,因为除了参数类型(或个数)不同,函数体的设计基本相同。一种传参(按能不能调用同一个重载函数进行分类)要定义一个函数,如果数量过多,会导致代码不够简洁,代码复用率低。
C++模板很好的解决了这一问题,那,什么是C++模板,具体是如何定义和使用的呢?
C++模板分为函数模板和类模板,效果可比喻成打印机,可以根据实参自动实例化(调用对应的重载函数或模板类)。
二、函数模板
定义
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被实例化,根据实参类型产生函数的特定类型版本。
格式
template<class T1,class T2,....,typename T3>
//定义模板函数/模板类 //注意:(一个template<...>只对正下方相邻的一个函数模板/类模板有效)
例如:
//例1:
template<class T>
void swap(T& x, T& y)
{
T temp = x;
x = y;
y = temp;
}
//例2:
template<typename T>
T Add(T x, T y)
{
return x + y;
}
注意:如果有多个模板参数,则每个参数前都必须加上class/typename关键字,各参数用逗号分隔开。
原理
函数模板本身不是函数,而是函数的“模具”。在编译器编译阶段,编译器会根据实参类型来推演生成合适的函数(本质就是将我们之前定义函数重载的任务转交给了编译器),然后再调用生成的函数。
例如:
#include <iostream>
using namesoace std;
template<typename T>
T Add(T x, T y)
{
return x + y;
}
int main()
{
int x1 = 0, y1 = 0;
cin >> x1 >> y1;
cout<<Add(x1, y1)<<endl;
double x2 = 0.0, y2 = 0.0;
cin >> x2 >> y2;
cout<<Add(x2, y2)<<endl;
return 0;
}
/*
//1号
int Add(int x, int y)
{
return x + y;
}
//2号
double Add(double x, double y)
{
return x + y;
}
*/
上例中,main()函数第一次调用Add()函数时,编译器自动生成了1号函数供使用;第二次调用Add()函数时,编译器自动生成了2号函数供使用。
注意:1号或2号函数可以和函数模板同时存在,并且,调用Add()函数的时候会调用1号或2号函数,编译器不会再参照函数模板自动生成。
模板实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
隐式实例化
上面举到的例子全是隐式实例化,即直接通过函数模板的函数名来进行模板调用时编译器的实例化方法。当模板参数个数只有一个而实参有两种或两种以上的不同类型(以此类推,当实参类型个数比模板参数个数更多)时,会导致报错。比如在上述例子中的main()函数中添加一次函数调用:Add(x1,y2),编译器报错,原因是x1将T实例化成int,y2将T实例化成double,但是T只能实例化成一种数据类型,会产生歧义,编译器无法做出选择,故会报错。如下图:
解决方法:1.将x1强转为double类型或将y2强转为int类型。
2.显式实例化。
注意:int强转为double不会造成数据上丢失,但double强转为int可能造成部分数据的丢失 (int和double在内存中的存储方式不同,详见:http://t.csdn.cn/kwh3N)
显式实例化
显式实例化的本质是省略了编译器通过实参类型来推演应该生成何种参数类型的函数的这一步,即不需要推演,由代码直接指定好需要的类型,编译器直接按给定的类型生成函数。
注:显示实例化时<>内的数据类型应与模板参数个数相同。
三、类模板 && 模板类
类模板是模板的一种,类模板实例化后得到模板类。
定义格式
template<class T1, class T2, ..., class Tn> //class关键字可替换成typename
class 类模板名
{
// 类内成员定义
};
例如:
template<class T>
class SeqList
{
public:
SeqList(size_t capacity = 10)
:a(new T(capacity))
, _size(0)
, _capacity(capacity)
{}
T& operator[](size_t pos)
{
assert(pos < _size);
return a[pos];
}
void Print()
{
for (size_t i = 0;i < _size;i++)
{
cout << a[i];
}
}
~SeqList();
private:
T* a;
size_t _size;
size_t _capacity;
};
//类外定义类模板的成员函数
template<class T>
SeqList<T>::~SeqList()
{
delete[] a;
_size = _capacity = 0;
}
int main()
{
//SeqList是类名,SeqList<int>、SeqList<double>等是类类型
//模板类的定义必须显式实例化
SeqList<int> s1;
SeqList<char> s2;
return 0;
}
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可。类模板不是真正的类,实例化的结果才是真正的类。
四、总结
函数模板实例化的实现,实参不会进行隐式转换,普通函数的实参会进行隐式转换。原因是,函数模板实例化时,是编译器通过实参的类型来定义合适的函数;而调用普通函数时是将实参隐式转化成满足形参要求的变量类型。即:函数模板实例化,最终调用的函数形参类型是由编译器根据实参类型决定的,必要时要求实参强转或显式类型转换;普通函数调用,实参传给形参,实参会根据形参的类型自动转换。
总结:模板是C++较C增添的一个语法功能,能够提高代码的复用性,简化代码。