目录
一、什么是模板?
二、函数模板的格式
三、函数模板的原理
四、函数模板实例化
1、什么是函数模板实例化?
2、隐式实例化
3、显式实例化
五、类模板
1、类模板的格式
2、类名和类型名
先让我举下面的例子:
void Swap(int& a, int& b)
{
int t = a;
a = b;
b = t;
}
void Swap(char& a, char& b)
{
char t = a;
a = b;
b = t;
}
void Swap(double& a, double& b)
{
double t = a;
a = b;
b = t;
}
//...
在碰到模板之前,如果我们遇到交换函数,那么就不可避免地要对每种类型都要写一遍,那这样就会显得代码冗余。而对于这种只是参数类型不一样,但函数的逻辑一样的情况,我们现在就可以用“模板”这一概念解决了,那样我们就只用写一遍这个函数,并且这个函数还适用于任何类型。
因此,模板的概念很简单。就是函数的通式,调用时只要把类型代进去就行了。
template /* :模板参数列表 */
void Swap(T& a, T& b)
{
T t = a;
a = b;
b = t;
}
//调用:
int main()
{
int a = 1;
int b = 10;
Swap(a, b);
return 0;
}
先举个例子:
template /* :模板参数列表 */
void Swap(T& a, T& b)
{
T t = a;
a = b;
b = t;
}
int main()
{
int a = 1;
int b = 10;
Swap(a, b);// B
double c = 0.0f;
double d = 4.5f;
Swap(c, d);// A
return 0;
}
所以请问函数A和函数B是同一个函数吗?很遗憾地告诉你——不是。
虽然我们只写了一个函数模板,但当我们调用这个函数模板时,编译器就会自动识别实参的类型,然后自动把模板的类型名改成一个具体的类型(如int、float、char等等)。因此,虽然函数名相同,但由于参数类型不同,构成函数重载,因此其实是两个不同的函数。
简单来说,其实就是把函数形参的模板类型名变成一个具体的类型,让编译器知道应该把函数模板转换成哪种类型的函数。
template
T Add(const T& a, const T& b)
{
return a + b;
}
int main()
{
int a = 1;
char b = '\n';
Add(a, b);
/*error:没有与参数列表匹配的Add函数模板实例 参数类型为:(int, char)*/
return 0;
}
由于只有一种类型的模板,因此参数列表只能是一种类型,所以编译器就不知道应该转化成哪种类型了。而隐式类型转换就是通过强制类型转化来实现:
template
T Add(const T& a, const T& b)
{
return a + b;
}
int main()
{
int a = 1;
char b = '\n';
Add(a, (int)b);
/*注:这里由于隐式类型转化产生的临时变量具有常性,而引用赋值要考虑权限,因此形参列表只能用 const T& !*/
return 0;
}
如果说隐式实例化是通过强制类型转换来让编译器识别参数类型,那么显式类型转换则是直接人为地把参数模板类型改成你想要的类型,即可以直接跳过编译器的识别。
template
T* Alloc(int num)
{
return new T[num];
}
int main()
{
int* pi = Alloc(5); /* 显式实例化的语法就是直接在函数名和圆括号之间加上 <类型名> 就行了 */
return 0;
}
以上面的例子为例,由于函数的参数列表中没有模板类型的参数,所以编译器无法识别具体的类型是什么,但对于一个函数模板来说它总是要实例化的,因此我们可以用显式实例化来人为地把模板类型改成自己想要的类型。
但要注意的是,无论是隐式实例化还是显式实例化,实参的数据类型都有可能被改变,因此一定要注意临时变量的产生。如果函数的参数列表有引用和指针变量的话那就要注意权限问题了。
template
class 类模板名
{
// 类内成员定义
};
在普通类中,类名和类型名是一个东西。为什么呢?其实看对象实例化就可以看出:
但是在模板中,类名和类型名是两个不同的东西。
原因其实也很简单。因为我们可以看到编译器是无法自动识别具体的数据类型,因此在对象实例化时我们要用显式实例化来实例化对象。因为只有用显式实例化才可以直接跳过编译器识别,人为地把模板参数改成具体的类型。