模板
C++的另一种编程思想是泛型编程,其主要利用的技术就是模板。模板树妖分为两类: 函数模板 和 类模板 。
1.函数模板
所谓函数模板,就是一个返回值和形参类型不用具体指定的通用函数,是一个虚拟的类型。
其目的是提高代码的复用性,将类型参数化。
1.1 语法
template<typename T>
函数声明或者定义
其中,
- template——声明创建模板
- typename——表明其后面的符号是一种数据类型,也可以用class代替
- T——通用数据类型,任意规范的名称均可,一般为大写字母
1.2 范例
这里写一个简单的实例:
#include <iostream>
#include <string>
using namespace std;
template<typename T>//模板声明,表明后面紧跟着的T是一个通用数据类型,编译器不应该报错
void myswap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
cout << a << "\t" << b << endl;
}
int main()
{
int a = 1, b = 2;
myswap(a, b);
double c = 1.3, d = 3.2;
myswap(c, d);
return 0;
}
1.3 自动类型推导与显式指定类型
在上面的例子中,我们定义完 a 和 b 后直接将其输入myswap() 的形参中去,事实上是编译器自动判断T的类型的,因此这种方式称为自动类型推导。
与自动类型推导相对的,是我们显式的去告诉编译器我们输入的实参的类型,其语法是:
//自动类型推导
myswap(a,b)
//显式指定类型
myswap<int>(a,b)
1.4 注意事项
- 自动类型推导,其数据类型必须一致;
- 模板必须确定T的数据类型才可以使用。
对于第一点,其实不完全对,或者说,仅在上面那种写法下是正确的。
template<typename T>
void myswap(T &a, T &b)
{
/*******/
}
int main()
{
int a = 10;
char b = 'b';
myswap(a,b);//这个时候编译器会报错,因为无法判断形参的类型
}
但是在下面的情况下是可以不一致的。
template<class T,class V>
void aaa(T &a, V &b)
{
cout << a - b << endl;
}
int main()
{
int a = 10;
char b = 'a';
aaa(a, b);//这样编译器就可以判断T是int类型,V是char类型了
aaa<int,char>(a,b);//这样的写法也是被编译器认可的
}
对于第二点,它的含义是,在模板函数的实现中必须出现通用类型T。
template<class T>
void aaa()
{
cout << "aaa()实现" << endl;
}
int main()
{
aaa();//这个调用是会报错的,应为aaa的定义中没有出现T
//这个所谓的出现不一定是在函数体内,只出现在形参列表也是可以的,反正得出现一次至少
}
1.5 与普通函数的区别
- 普通函数可以发生自动类型转换;
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 但是通过显式指定类型的方式,则可以发生隐式类型转换
另外说一句,在引用传递的时候不会发生隐式类型转换。
1.6 函数模板与普通函数的调用规则
- 在都能调用的情况下,优先普通函数
- 通过空模板参数列表强制调用函数模板
- 函数模板可以重载
- 如果函数模板可以产生更好的匹配,使用函数模板
在都能调用的情况下,优先普通函数
void myprint(int a)
{
cout << "普通函数" << endl;
}
template<typename T>
void myprint(T a)
{
cout << "模板函数" << endl;
}
/******主函数*******/
int a =10;
myprint(a);
在这种情况下两个都能调用,编译器选择优先调用普通函数。
通过空模板参数列表强制调用函数模板
/******主函数*******/
int a =10;
myprint<>(a);
这里利用空模板参数列表强制调用了模板函数。
函数模板可以重载
template<typename T>
void myprint(T a)
{
cout << "模板函数" << endl;
}
template<typename T>
void myprint(T a,T b)
{
cout << "重载的模板函数" << endl;
}
/******主函数*******/
int a =10;
myprint(a);
myprint(1,2);
在这里,第一个函数调用的是普通函数;第二个调用的是重载的模板函数。
如果函数模板可以产生更好的匹配,使用函数模板
/******主函数*******/
char a ='a';
myprint(a);
在这里,事实上依然是普通函数与模板函数都能调用,但是由于调用普通函数需要发生隐式类型转换,而模板函数不需要,只要自动推导T的类型即可,因此编译器选择调用函数模板。
1.7 局限性
虽然模板具有很高的泛用性,但是对于自定义数据类型而言可能略有不足。