函数模板
作用:
建立一个通用函数,其函数返回值和形参类型可以不具体定制,用一个虚拟的类型来代表
语法:
1.template
2.函数声明或定义
解释:
template:声明创建模板
typename:表明其后面的符号是一种数据类型,可以用class代替
T:通用数据类型,名字可以替换,通常为大写字母
还是不太清楚我们就直接上案例:
写一个交换两个数的函数:
1.交换两个整形:
void swap_int(int &a,int &b)
{
int temp=a;
a=b;
b=temp;
}
2.交换两个浮点型:
void swap_double(double &a,double &b)
{
double temp=a;
a=b;
b=temp;
}
很明显重复了吧,而且还有很多类型:字符型,string,自定义类型…
这个时候我们的函数模板就是可以用了:
既然只是数据类型不同那么我们就用一个T来代表通用:
可以理解为将我们的类型参数化!
//申明一个模板,告诉编译器 ,后面代码中的T不要报错,是一个通用类型
template <typename T>
void swap(T &a,T &b)
{
T temp=a;
a=b;
b=temp;
}
这就是函数模板,什么时候使用T呢?使用的时候来指定这个T的具体数据类型。
两种方式使用模板:
1.自动类型推导:
swap1(a,b);
(有想法的朋友已经在想要是我们的a和b类型不一样怎么办,后面我们会在易错的部分讲到)
2.显示指定类型:
swap1(a,b);
编译器就把这个T当成int类型,比上面就多了一个尖括号然后中间放入需要指定的类型
#include <iostream>
using namespace std;
template <typename T>
void swap1(T &a,T &b)
{
T temp=a;
a=b;
b=temp;
}
int main()
{
int a;
int b;
cin>>a>>b;
swap1<int>(a,b);
cout<<"a:"<<a<<" "<<"b:"<<b;
}
易错部分:
1.函数在隐式类型转化中,我们的函数的参数的类型必须相同才能够使用,否则会产生而二义性
例子:
#include <iostream>
using namespace std;
template <typename T>
T add(T a,T b)
{
return a + b;
}
int main()
{
int a;
double b;
cin>>a>>b;
cout << "a+b:" << add(a, b);
}
使用上面这个函数就会报错,因为我们的a和b类型不一样,发生隐式类型转化的时候会产生二义性。
如何解决:
add(a, b),都强制转化成int就可以了
2.函数模板必须指定T的数据类型才能使用:
什么意思呢?
我们可以举一个例子:
#include <iostream>
using namespace std;
template <typename T>
void fun()
{
cout<<"fun函数的调用"<<endl;
}
int main ()
{
fun();
}
程序会报错,为什么呢?
因为我们编译器默认将我们的函数fun()当作是一个函数模板,因为在这个地方没有隐式转化的方法(没有参数),所以我们如果直接只用fun()的话会被认为是调用函数模板。
如何解决呢?
fun();我们只需要显示指定类型,就可以了,虽然这个int没什么用。打个比喻,你抄袭一个东西你不能完全照搬啊,你至少要把名字改了吧。
普通函数和函数模板的区别:
1.普通函数调用时可以发生自动类型转(隐式类型转换)
我们使用普通函数的时候,普通函数已经帮我们指定了形参的类型,所以会自动帮我们强制转化,这个不举例,太简单。
2.函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
这个就是易错点1的内容
3.如果利用显示指定类型的方式,可以发生隐式类型转换
易错点1的解决办法
其实上面的区别基本都是围绕着隐式类型转化来讨论的。
是不是感觉很简单,那么我再举一个陷阱例子:
#include <iostream>
using namespace std;
template <typename T>
void swap1(T &a,T &b)
{
T temp=a;
a=b;
b=temp;
}
int main()
{
int a;
double b;
cin>>a>>b;
swap1<int >(a,b);
cout<<"a:"<<a<<" "<<"b:"<<b;
}
可以放在编译器中跑一下,为什么这里我们显示指定了类型是int为什么还是会报错?
普通函数和函数模板的调用规则(函数名都一样)
先贴上代码:
#include <iostream>
using namespace std;
void myprint(int a, int b)
{
cout << "普通函数的调用" << endl;
}
template <typename T>
void myprint(T a, T b)
{
cout << "函数模板的调用" << endl;
}
1.如果函数模板和普通函数都可以实现,优先调用普通函数。(通用的和专属的选择专属的)
没错吧,首先调用的是普通函数。
那么来思考这个情况。
#include <iostream>
using namespace std;
void myprint(int a, int b);
template <typename T>
void myprint(T a, T b)
{
cout << "函数模板的调用" << endl;
}
int main()
{
int a = 10;
int b = 10;
myprint(a, b);
}
大部分一样,只是普通函数的实现删掉了,并且改成了一个函数的声明。
那么这种情况会因为没有函数的实现体而去调用函数模板吗?
答案:不会,还是会调用普通函数,并且会出现报错:
一个无法解析的外部命令,这个报错是出现在链接阶段,意思就是我要调用你,但是我却找不到你的实现体。所以就算只是一个声明,没有实现体当满足调用普通函数的时候还是会调用普通函数。
2.可以通过空模板参数列表来强制调用函数模板
什么意思呢?
加个尖括号就ok,空模板就是尖括号里面什么都没有
3.函数模板可以发生重载
注意哦这里重载的时候还是要写一次:
template
4.如果函数模板可以产生更好的匹配,优先调用函数模板
什么叫做更好的匹配?
会调用函数模板。为什么?
如果编译器由高字节的int到低字节的char进行隐式转换,精度会丢失,某些编译器会报错,但编译还是能通过。编译器会尽量避免这种情况的产生。
函数模板的局限
1.如果传入的是一个数组就无法实现
2.如果传入的是自定义数据类型也无法正常运行
c++为了解决这些问题,提供了模板的重载,可以为这些特定的类型提供具体化的模板
解决方法一:
重载运算符
解决方法二:
利用具体化实现代码,具体化会优先调用。
具体化语法:template<> 返回值类型 函数名字(形参具体类型 &a, 形参具体类型 &b);