为什么要有模版?
首先,我们先来看下面的交换函数。
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
void Swap(double& a, double& b)
{
double tmp = a;
a = b;
b = tmp;
}
void Swap(char& a, char& b)
{
char tmp = a;
a = b;
b = tmp;
}
void main()
{
int a = 10, b = 20;
cout << "a=" << a << " " << "b=" << b << endl;
Swap(a, b);
cout << "a=" << a << " " << "b=" << b << endl;
double a1 = 1.1, b1 = 2.1;
cout << "a1=" << a1<< " " << "b1=" << b1 << endl;
Swap(a1, b1);
cout << "a1=" << a1 << " " << "b1=" << b1 << endl;
char a2 = 'a', b2 = 'b';
cout << "a2=" << a2 << " " << "b2=" << b2 << endl;
Swap(a2, b2);
cout << "a2=" << a2 << " " << "b2=" << b2 << endl;
return;
}
是不是很复杂,虽然我们利用里C++的函数重载功能,使得多种内置类型的变量可以相互交换。但是,我们发现了一个问题,那就是重载的函数内部的代码重复率太高。明明都是相同算法的函数,却因为要区分类型的不同,就要写上多次,这本身就是一种浪费。
那么,有什么办法能避免这种情况的发生呢?模版就可以很好地解决这个因为类型不同就要重复写上多次相同代码的问题。
在了解模版之前,我们先要弄清楚一个概念:泛型编程。
泛型编程–编写与类型无关的通用代码,是代码复用的一种手段。模版是泛型编程的基础。
什么是模版?
我们可以把C++当中的模版想像成生活中的模具,一个模具可以生成许多种材质不同,但是形状相同的产品。
C++中的模版也一样,可以通过识别出的不同类型生成不同的函数,然后再使用生成的各种函数来完成我们的功能。
模版可以分为两类:函数模版和类模版。
函数模版
概念:函数模版代表了一个函数家族,该函数模版与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
格式:
template<typename T1,typenameT2,…typenameTn>
返回值类型 函数名(参数列表){}
template<typename T>
void Swap(T& left,T& right)
{
T temp=left;
left=right;
right=temp;
}
注意:
①typrname是用来定义模版参数关键字,也可以用class。
②fun(),这个小括号内部是放数据的。template<>,这个尖括号内部是用来放类型的。
类模版
我们知道函数模版是通过区分不同的类型来生成不同的模版函数来使用的,那么类模版是怎么使用的呢?其实跟函数模版类似,类模版也是通过区分不同的类型来生成不同的模版类来使用的。
格式:
template<class T1,class T2,...,class Tn>
class 类模版名
{
//类内成员定义
}
注意:类模版里的函数都是函数模版。
如何使用模版?
函数模版的使用
通过一段代码来了解:
template <typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
void main()
{
int a = 10, b = 20;
cout << "a=" << a << " " << "b=" << b << endl;
Swap(a, b);
cout << "a=" << a << " " << "b=" << b << endl;
double a1 = 1.1, b1 = 2.1;
cout << "a1=" << a1<< " " << "b1=" << b1 << endl;
Swap(a1, b1);
cout << "a1=" << a1 << " " << "b1=" << b1 << endl;
char a2 = 'a', b2 = 'b';
cout << "a2=" << a2 << " " << "b2=" << b2 << endl;
Swap(a2, b2);
cout << "a2=" << a2 << " " << "b2=" << b2 << endl;
return;
}
可以看出,函数模版完美地解决了代码复用率低的问题。函数模版通过区分不同的类型来产生不同的模版函数,然后在通过模版函数解决了我们的问题。
上图可以看出,虽然我们没有声明参数的类型,但是编译器依旧给我们生成了对应类型的函数,这个就叫做函数模版的隐式实例化。与隐式实例化相对的还有显式实例化,由用户自己在函数名后的<>中指定模版参数的实际类型。
template <typename T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a = 10;
double b = 1.2;
cout<<Add<int>(a, b)<<endl;
}
这里,程序员指定了模版函数的参数类型为int类型,所以输出为11。那么,如果程序员不指定模版函数的参数类型会发生什么呢?
会出现错误,因为a和b的类型不同,参数类型出现了二义性,T无法确定,编译器不知道参数究竟是int类型的还是double类型的。
注意:
①一个非模版函数可以和一个函数模版同时存在,而且该函数模版还可以实例化为这个非模版函数。
②对于非模版函数和同名函数模版,如果其他条件都相同,在调动时会优先调动非模版函数而不会调用函数模版生出一个实例。如果模版可以产生一个具有更好匹配的函数,那么将选择模版。
③模版函数不允许自动类型转换,但普通函数可以进行自动类型转换。
类模版的使用
类模版和函数模版的定义方式一样,在类前声明模版。
template <class T>
class Seqlist
{
public:
Seqlist(size_t capacity = 10)
:_base(new T[capacity])
,_size(0)
,_capacity(capacity)
{}
~Seqlist();
T& operator[](size_t pos)
{
assert(pos < _size);
return _date[pos];
}
private:
T* _base;
size_t _size;
size_t _capacity;
};
//注意:类模版中函数绽放在类外定义时,需要加模版参数列表
template<class T>
Seqlist<T>::~Seqlist()
{
if (_base)
delete[]_base;
_size = _capacity = 0;
}
类模版实例化与函数模版实例化不同,因为类是自定义类型,所以编译器无法推导出我们想要的类的类型,所以类模版实例化需要在类模版名字后面跟<>,然后将实例化的类型放在<>中即可,类模版名字不是真正的类,而实例化的结构才是真正的类。
类名<类型> 对象名(参数)
Seqlist<int> mylist;
Seqlist<double> youlist;