再介绍泛型编程前我们先来看下面这段代码
int min(int a, int b)
{
return a < b ? a : b;
}
char min(char a, char b)
{
return a < b ? a : b;
}
double min(double a, double b)
{
return a < b ? a : b;
}
short min(short a, short b)
{
return a < b ? a : b;
}
void main()
{
cout << min(10, 20) << endl;
cout << min('A', 'B') << endl;
cout << min(10.23, 2.45) << endl;
}
我们可以看到有三个min函数,他们的功能类似,只是参数类型不同,我们在求不同类型的时候就需要重新写对应的函数,这个时候增加了冗余的代码,因此能不能用一个通用的函数来满足不同类型参数,因此c++就引入了模板的概念。模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
template
返回值类型 函数名(参数列表)
{
}
将上面的min用模板实现如下:
//typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
template<typename T1,typename T2>
T1 min(T1& a, T2& b)
{
return a < b ? a : b;
}
模板分为函数模板,类模板。
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模
板就是将本来应该我们做的重复的事情交给了编译器,在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此.
对于普通内置类型:
template<typename T1,typename T2>
T1 min(T1& a, T2& b)
{
return a < b ? a : b;
}
void main()
{
cout << min(10, 20) << endl;
cout << min('A', 'B') << endl;
cout << min(10.23, 2.45) << endl;
}
emplate<typename T1,typename T2>
T1 min(T1& a, T2& b)
{
return a < b ? a : b;
}
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
void main()
{
A aa(10), bb(12);
min(aa, bb);
}
当我们运行时发现并不能运行,这是因为直接调用的话可以看到是运行不了的,是因为在比较大小的时候,编译器并不知道类类型到底要怎么进行 < 的比较,.
为了让编译器能够知道怎样进行比较,这时候我们就需要进行重载。我们对 > 进行重载 重载如下,这个const是因为要进行类型的匹配,定义模板的时候类型是const
template<typename T1,typename T2>
T1 min(T1& a, T2& b)
{
return a < b ? a : b;
}
class A
{
public:
A(int a = 0)
: _a(a)
{
//cout << "A():" << this << endl;
}
//对<进行函数重载
bool operator<(const A& t)const
{
return _a < t._a ? _a : t._a;
}
~A()
{
//cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
/*cout << min(10, 20) << endl;
cout << min('A', 'B') << endl;
cout << min(10.23, 2.45) << endl; */
A aa(10), bb(12);
min(aa, bb);
return 0;
}
再次运行时就没有问题了(如果要显示打印结果,需要对<<输出进行重载)
类模板就如同名字一样将类进行模板化,对类实行模板化后就可以适应不同参数类型的引用,例如原来写一个顺序表只能对单个参数类型进行应用,虽然可以进行更改,但会造成代码冗余,使用模板后就可以定义多种不同类型的顺序表。
// 注意:Vector不是具体的类,是编译器根据被实例化的类型生成具体类的模具,我们给它什么类型,它会具体实例化出对应的
template<class T>
class Vector
{
public:
Vector(size_t capacity = 10)
: _pData(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
// 使用析构函数演示:在类中声明,在类外定义。
~Vector();
T& operator[](size_t pos)
{
assert(pos < _size);
return _pData[pos];
}
private:
T* _pData;
size_t _size;
size_t _capacity;
};
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表,且每个函数都需要单独加模板参数列表,因为它的作用范围只有这个函数里
template <class T>
Vector<T>::~Vector()
{
if (_pData)
delete[] _pData;
_size = _capacity = 0;
}
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>
中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector类名,Vector才是类型
Vector<int> s1;
Vector<double> s2;
下面时指定类型实例化:
template<class T >
//注意必须要加const否则编译器不通过,这里涉及到权限问题,下面传递到该函数会发生隐式类型转换和显示类型转化并产生中间变量,而中间变量具有常性,加const防止权限放大
T ADD(const T& left, const T& right)
{
return left + right;
}
template<typename t>
t* Alloc(int n)
{
return new t[n];
}
int main()
{
int a1 = 12, a2 = 22;
double b1 = 12.0, b2 = 45.6;
//这里传递过去的参数一致编译器可以推导出实例化
cout << ADD(a1, a2) << endl;
cout << ADD(b1, b2) << endl;
//而当时这种两种参数情况时编译器无法推导出,因此我们有两种方法解决
//第一种强制类型转换
cout << ADD (a1, (int)b1) << endl;
cout << ADD ((double)a2, b2) << endl;
//第二种显示实例化,用指定类型实例化
cout << ADD<int>(a1, b1) << endl;
cout << ADD<double>(a2, b2) << endl;
//这种函数无法自动推导的,一般要指定类型实例化
double* bb = Alloc<double>(12);
return 0;
}