首先,我们先由模板的类型说起。
一 。泛型编程
第一个问题,我们如何写一给通用的交换函数?
#include<iostream>
using namespace std;
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;
}
int main()
{
/*int i =3;
int j = 2;*/
double i = 3.45;
double j = 2.34;
Swap(i, j);
cout << i << endl;
cout << j << endl;
return 0;
}
就这样,我们可以实现一个交换函数,但是有没有发现,很麻烦,我们实现一种,就要调用一种类型的交换函数,很麻烦,那有没有一种简单的类型来实现我们现在的要求。
总结一下:
用函数重载可以解决现在问题,但是:
1. 重载的函数仅仅只是类型不同,代码的复用率比较低,只要有新类型出现时,就需要增加对应的函数
2. 代码的可维护性比较低,一个出错可能所有的重载均出错
那么能不能告诉编译器,使用一个模板,不管是传什么类型,都可以直接调用;
template<typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int g =3;
int h = 2;
Swap(g, h);
cout << g << endl;
cout << h << endl;
double i = 3.45;
double j = 2.34;
Swap(i, j);
cout << i << endl;
cout << j << endl;
return 0;
}
这个就实现了函数的模板类型,可以使用任意的类型来调用。
这里要说一下模板类的关键字《 “ typename ”》,class也可以;
2.函数模板的原理
模板是一个蓝图,它并不是一个函数,是编译器产生特定具体类型的模具,所以其实模板就是把我们重复的事情,交给了编译器;
在编译阶段:对于模板的使用,编译器需要根据传入的实参类型来推演生成的对应的函数以提供调用。比如,当使用double类型的时候,编译器先对传入实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码。对于字符串也是如此。
3.函数模板的实例化
用不同类型的参数使用模板的时候,成为模板的实例化。模板参数的实例化分为:隐式实例化和显示实例化
(1)隐式实例化
隐式实例化:让编译器通过实参来推演参数的实际类型。
比如下面一段代码:
template<class T>
T Add(const T& a , const T& b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
Add(a, b);
cout << Add(a, b) << endl;
double a1 = 10.34;
double b2 = 20.45;
Add(a1, b2);
cout << Add(a1, b2) << endl;
return 0;
}
这样就可以实现一个隐式实例化的代码,通过编译器,来实现自己的方法,下面我们看一下执行程序;
(2)显示实例化
显式实例化:在函数名后的<>中指定模板参数的实际类型
比如下面的一段代码:
int main()
{
int a = 10;
double b = 20.34;
Add(a, b);
cout << Add(a, b) << endl;
return 0;
}
首先先看一下这样一段代码,这个编译器是肯定不会过去的,因为我一个int 一个double,这是不符合c++的标准的,所以我们在看下面的代码:
int main()
{
int a = 10;
double b = 20.34;
Add<int>(a, b);
cout << Add<int>(a, b) << endl;
return 0;
}
这样的一段代码,就可以明白了,我们显示实例化的的定义了吧。下面我们再看一下执行程序。
4.模板参数的匹配原则
(1)一个非模板函数可以和一个同名的模板函数同时存在,而且这个模板函数还可以被实例化成这个非模板函数。
看下面的一段代码:
int Add(int x, int y)
{
return x + y;
}
template<class T>
T Add(const T& a, const T& b)
{
return a + b;
}
int main()
{
Add(1, 2);
Add<int>(3, 4);
cout << Add<int>(3, 4) << endl;
cout << Add(1, 2) << endl;
return 0;
}
由这段代码,就可以看出上面的定义,是正确的,可以同时存在,还可以转化成非模板函数,再看一下执行程序。
(2)对于非模板函数和同名模板函数,如果其他条件都相同,那么系统会优先使用非模板函数而不会从该模板产生出一个实例,如果模板可以产生一个更好匹配的函数,那么将选择模板。
下面我们看下面的这样一段代码;
int Add(int x, int y) //专门实现Add函数
{
return x + y;
}
template<class T1 , class T2> //任何Add类型都适用
T1 Add( T1 a, T2 b)
{
return a + b;
}
int main()
{
Add(1, 2);
Add(3.45, 4.34);
cout << Add(3.45, 4.34) << endl;
cout << Add(1, 2) << endl;
return 0;
}
// 与非函数模板类型完全匹配,不需要函数模板实例化
// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
下面我们看一下执行程序:
(3)模板函数不允许自动类型转化,但普通函数可以进行自动类型转化。
三. 类模板
(1)类模板的定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
再看下面的一段代码;
template<class T>
class Vector
{
public:
Vector(size_t capacity = 10)
:_pDate(new T[capacity])
, _size(0)
, _capacity(capacity)
{}
~Vector(); // 在类的里面声明,在类的外面实现;
void PushBack(const T& data)
{
_pDate[_size++] = data;
}
void PopBack()
{
--_size;
}
void Print()
{
for (int i = 0; i < _size; i++)
{
cout << _pDate[i] << endl;
}
}
size_t size()
{
return _size;
}
T& operator[](size_t pos)
{
assert(pos < _size);
return _pDate[pos];
}
private:
T* _pDate;
size_t _size;
size_t _capacity;
};
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template<class T>
Vector<T>::~Vector()
{
if (_pDate)
{
delete[] _pDate;
}
}
(2) 类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
比如下面的代码:
注意:// Vector类名,Vector才是类型
int main()
{
Vector<int> v1; // 就像这里一样,理解上面的定义
v1.PushBack(1);
v1.PushBack(2);
v1.PushBack(3);
v1.PushBack(4);
v1.PushBack(5);
v1.Print();
v1.PopBack();
v1.Print();
v1.size();
v1.Print();
return 0;
}
看一下最后的执行程序:
当然还可以这样定义:
Vector<int> s1; //可以分别定义整形
s1.PushBack(1);
s1.PushBack(2);
s1.PushBack(3);
Vector<double> s2; //或者定义浮点型
s2.PushBack(1.0);
s2.PushBack(2.0);
s2.PushBack(3.0);
for(size_t i = 0; i < s1.Size(); ++i) //分下面两次打印,就可以看到程序执行结果了。
{
cout<<s1[i]<<" ";
}
cout<<endl;
for(size_t i = 0; i < s2.Size(); ++i)
{
cout<<s2[i]<<" ";
}
cout<<endl;
总结一下:类模板名字不是真正的类,而实例化的结果才是真正的类: