模板
- [1. 概念](#1. 概念)
- [2. 函数模板的实例化](#2. 函数模板的实例化)
- [3. 函数调用原则](#3. 函数调用原则)
- [4. 类模板](#4. 类模板)
1. 概念
先看一个交换函数。
cpp
void swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
相信大家肯定可以信手拈来,但是这个函数只能用来交换两个int类型的数,如果这里有两个字符需要交换,就需要重新写一个交换函数。如下
cpp
void swap(char& x, char& y)
{
char tmp = x;
x = y;
y = tmp;
}
可以看到这两个函数除了参数类型,其它部分完全一致。现在只是交换两种不同类型的数据,如果有很多不同类型的数据需要交换,那么需要依次实现,这太不可取了。由此,C++出现了模板。
模板分为函数模板 和类模板。先介绍函数模板。
概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型的版本。
函数模板格式为
template< typename T1,typename T2,typename T3...>
返回值类型 函数名(参数列表)
typename 是定义模板参数的关键字,可以用class替代,不能用struct
T1,T2,T3等是模板参数,名称随意,通常习惯用T(template首字母)。
参数列表的参数类型需要用模板参数替代。
比如上面swap函数的模板如下
cpp
template<typename T>
void Swap(T& x,T& y)
{
T tmp = x;
x = y;
y = tmp;
}
这样就可以完成任意类型的数据交换了。调用函数时,并不是调用这个模板,而是调用通过模板实例化出来的函数
cpp
template<typename T>
void Swap(T& x,T& y)
{
T tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 1, b = 2;
char c = 'a', d = 'b';
Swap(a, b);
Swap(c, d);
return 0;
}
调试这段代码,通过反汇编可以观察到调用的函数地址。
可以看到,两次调用的Swap函数的地址不同,说明调用的不是同一个函数。
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型 来推演生成对应类型的函数 以供调用。比如:当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码,对于字符类型也是如此。所以函数的数量并没有减少,只是生成多份函数的工作交给了编译器。
2. 函数模板的实例化
实例化分为隐式实例化和显式实例化。
隐式实例化:让编译器通过实参类型去推导模板参数的实际类型。上面的Swap函数调用就是隐式实例化。
显式实例化:使用模板时,显式的传入模板参数的类型。
这里写一个add的函数模板进行讲解
cpp
template<class T>
T add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 1, b = 2;
double c = 1.1, d = 2.2;
cout << add(a, b) << endl;
cout << add(c, d) << endl;
return 0;
}
当写下这样的代码
add(a,d);//编译器会找不到匹配的函数模板,从而报错,注意使用模板时,编译器不会强制类型转换
解决方法为:
1.我们进行强制类型转换 -----> add( (double)a , b)或add ( a , ( int ) b );
2.显式实例化 ---->add< int >(a,b)
方法一我们自己去强制类型转换,这样就可以找到匹配的函数模板了。
方法二显式实例化,直接告诉编译器模板参数的类型。
运行结果如下。
3. 函数调用原则
- 对于普通函数和同名函数模板,如果其他条件都相同,在调动时会优先调用普通函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
举个例子,还是add函数。
cpp
//专门计算int类型数据
int add(int x, int y)
{
cout << "int add(int x, int y)" << endl;
return x + y;
}
//模板
template<class T1,class T2>
auto add(const T1& x, const T2& y)
{
cout << "auto add(const T1& x, const T2& y)" << endl;
return x + y;
}
int main()
{
int a = 1, b = 2;
double c = 1.1, d = 2.2;
add(a, b);
add(c, d);
add(a, d);
return 0;
}
第一个add会调用专门计算int类型数据的add,更匹配;
第二个add会用模板生成一个更匹配的计算double数据的add函数 。
第三个用模板生成的更匹配。
总结:调用时,一定会选择最匹配的那个,没有最匹配的在选择其他的。
4. 类模板
之前实现的数据结构,比如栈,我们为了便于修改存储的数据类型,通常使用typedef来重定义数据类型,但是这样有一个缺陷,会导致我们定义的栈存储的都是一种数据,如果同时需要两个栈,一个存储int,一个存储自定义类型,那么typedef就不管用了。因此就有了类模板。
类模板和函数模板很相似,用法如下
template<class T1, class T2, ...>
class 类模板名
{
// 类内成员定义
};
不同于函数模板的是在实例化时,类模板只能显式实例化 。
实例化方式为
类模板名 <类型>
这里用C++库里的栈做一个演示。
关于模板的初步介绍就到这了,现在只需要简单了解语法即可,后续会应用起来,就会有更深的理解。