目录
1.为什么会有模板(泛型编程)?
比如你想实现一个简单的swap函数 它能够支持很多类型 比如int,double,char...所以你要这么写
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
char temp = left;
left = right;
right = temp;
}
使用函数重载虽然可以实现,但是有一下几个不好的地方:
1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
2. 代码的可维护性比较低,一个出错可能所有的重载均出错
所以C++引入了模板这一概念
2.函数模板
形式:template<typename T1,typename T2.....>
返回值类型 函数名称(参数列表)
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
typename是用来定义模板参数的关键字
template<typename T>
void Swap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 1, b = 2;
double c = 1.1, d = 2.2;
printf("%d %d\n", a, b);
printf("%lf %lf\n", c, d);
Swap(a, b);
Swap(c, d);
printf("%d %d\n", a, b);
printf("%lf %lf", c, d);
return 0;
}
运行结果如下:
我们发现是可以正确交换数据的
那它的原理是什么呢?
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
3.函数模板的实例化
1.隐式实例化
//3.函数模板的实例化
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
cout<<Add(a1, a2)<<endl;
cout<<Add(d1, d2)<<endl;
/*
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,
编译器无法确定此处到底该将T确定为int 或者 double类型而报错
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
Add(a1, d1);
*/
// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化 3.添加合适的typename 保证匹配
cout<<Add(a1, (int)d1)<<endl;
return 0;
}
2.显式实例化
在函数名后的<>中指定模板参数的实际类型
int main(void)
{
int a = 10;
double b = 20.0;
// 显式实例化
cout<<Add<int>(a, b)<<endl;
return 0;
}
那么如果出现同名的普通函数和函数模板呢?
//如果出现同名的 模板和普通函数
//类型匹配优先调用普通函数(如果非要调用函数模板就采用显式实例化的方式) 不完全匹配则优先调用模板
int Add(int left, int right)
{
return left + right;
}
void Test()
{
cout<<Add(1, 2)<<endl; // 与非模板函数匹配,编译器不需要特化
cout<<Add<int>(1, 2)<<endl; // 调用编译器 将模板实例化的Add版本
}
int main() {
Test();
return 0;
}
有人可能会说 不完全匹配不一定是调用模板吗 为什么说优先呢?
因为模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
int main() {
cout<<Add(1, 2)<<endl;
cout<<Add(1, 2.0)<<endl;//自动类型转换 虽然警告但是可以运行
return 0;
}
编译 运行成功但是会有警告
4.类模板
对于同一个类 存放的数据类型可能不完全一样 比如存放double类型的栈 int类型的栈等
所以类也有模板
形式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
注意
1.类必须显式实例化 类模板名字不是真正的类,而实例化的结果才是真正的类
2.类的声明和定义是不一样的 class stack{} 是声明 而stack<int>是类型
3.声明和定义不能分离到两个文件 定义的时候要指明是类模板 template<class T> 类模板名<T>::函数名(参数列表){}