泛型编程
我们应该如何实现一个交换函数呢?
int Swapint(int &x,int&y)
{
int z = y;
y = x;
x = z;
}
这样我们便实现了一个交换函数,但是我们发现了一个问题,就是这个函数在交换的时候只能交换int类型的数据,当我们遇到char、double、等类型的数据的时候我们便不再适用了,我们需要写出来更多的函数来使得可以交换更多类型的变量。当然这里我们可以使用函数的重载,但是函数的重载仅仅只是数据的类型不同,代码的复用率较低,当有新的类型出现的时候,我们就要写新的函数,并且代码维护比较困难,一个出错可能所有的重载均出错。这个时候我们引入了一个函数模板。
函数模板
概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
函数模板的格式
template<typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表){}
template<typename T>
T Swap(T& x, T& y)
{
T z = y;
y = x;
x = z;
}
注意:typename是用来定义模板参数关键字,也可以使用class,但是不能使用struct来代替class。
函数模板的原理
函数模板本身并不是一个函数,是编译器用使用方式产生特定具体类型函数的模具。在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
函数模板的实例化
不同类型的参数使用函数模板时,称为模板的实例化,模板的实例化分为隐式实例化和显示实例化
比如我们要写一个Add函数
template<typename T>
T Add(T& x, T& y)
{
return x + y;
}
隐式实例化
让编译器根据实参推演模板参数的实际类型
int main()
{
int i = 1, j = 2;
double n = 1.1, m = 2.2;
Add(i, j);
Add(n, m);
//Add(i, m);
Add((double)i,m);
Add(i,(int)m);
return 0;
}
在上面的模板实例化的过程中,第一二个肯定是没有问题的,模板实例化出来了两个函数,但是第三个中,i为int类型,m为double类型,我们只有一个T,我们是不能既为int又为double类型的,这里呢需要我们去强转i或m的类型
显式实例化
在函数名后的<>中指定模板参数的实际类型
int main()
{
int i = 1, j = 2;
double n = 1.1, m = 2.2;
Add<int>(i,m);
return 0;
}
这样写的时候,两个参数的类型要是不匹配,编译器会尝试隐式类型转换,如果无法转换成功编译器将会报错。
对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
类模板
类模板的定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
为什么会有类模板呢?
我们之前写过很多的数据结构,我们当时用的时typedef来将一个类型去重命名,但是使得我们的数据结构在存储其他类型的数据的时候我们改一改被重命名的类型就可以了,我们以一个栈为假设,当我们要存储多种数据类型的时候,一个栈不能既存int又存double类型的。我们将代码改一改还要复制多份,这样子代码就比较繁杂。
比如我们写一个简单的栈
template<class T>
class Stack
{
public:
Stack(int capacity = 4)
:_top(0)
, _capacity(capacity)
{
_a = new T[capacity];
}
~Stack()
{
delete[] _a;
_a = nullptr;
_capacity = _top = 0;
}
void Push(const T& x);
private:
T* _a;
int _top;
int _capacity;
};
这样子的话当我们呢在创建一个新的栈的时候我们就没有必要再去拷贝复制代码了,编译器会自己生成一份代码,使得我们存储数据
int main()
{
Stack<int> st1; //存储int类型
Stack<double> st1; //存储double类型
Stack<char> st1; //存储char类型
return 0
}
这样子的话我们一个数据结构就既可以存储多种数据类型了。
这里需要注意的是,当我们声明和定义分离时候,我们应该这样子写
template<class T>
void Stack<T>::Push(const T& x)
{
// ...
}
并且Stack是类名,Stack才是类型