函数模板
所谓模板,是C++中实现多态的又一方法。我们已经知道函数重载可以实现多态,让一个函数名多用。这样可以简化函数的调用形式,但是又必须定义每一个函数。
C++提供模板正是为了简化这一个过程。
探索函数重载的缺点
首先,让 我们写一个Add函数
int Add(int x,int y)
{
return x - y;
}
ok,现在我们要对Add函数进行重载
char Add(char x, char y)
{
return x - y;
}
我们新定义了一个日期类型Date,我们又得对Date进行重载
Date Add(Date& d1, Date &d2)
{
return d1 - d2;
}
额,我们为什么如此的粗心。。。把“+”写成了“-”
这样的话,我们需要修改所有重载函数的逻辑。这就是函数重载的缺点。
虽然只需要把所有的加号换过来就好了,可是由于我们写的是非常简单的函数(只有一条返回语句)。当我们写了很长很长的语句时,这就会有很大的工作量了
函数重载的缺点:
(1)每当需要新的类型出现后,需要重载对应添加的函数
(2)函数重载的各个函数,大体函数逻辑一样,只是改变了类型。导致代码的利用率不高。
(3)当逻辑出现问题时,需要修改所有重载的函数。不方便维护
(4)函数重载无法解决返回值的问题
泛型编程
由于函数重载的诸多缺点,我们引入了【泛型编程】的概念。所谓的【泛型编程】,就是编写与类型无关的代码,是提高代码复用的一种手段。
而泛型编程,又包括【函数模板】和【类模板】。模板是泛型编程的基础!
函数模板
关键字 template
函数模板定义格式:
template<typename T>
T Add(T x,T y)
{
return x + y;
}
注意:
(1)函数模板不是类或者函数,它只是一个蓝图。只有在模板实例化后编译器才会为之生成对应的类或者函数。
(2)模板会被编译两次,第一次检查模板里的语法是否有错,如缺少分号;第二次则是在实例化的时候,会查看所有的调用是否都是有效的。
参数转换
编译器只能执行两种转换
1、const转换
接收const对象指针或者const对象引用的参数,可以用非const对象的指针或引用进行调用
template<typename T>
T Add(const T& x,const T& y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int &ra = a;
int &rb = b;
cout << "a + b =" << Add(ra, rb) << endl;
system("pause");
return 0;
}
2、函数或者数组到指针的转换
如果模板不是引用类型的话,那么它对函数或者数组类型的实参应转为指针
template <typename T>
T Add(const T* x, const T* y)
{
return (*x + *y);
}
int main()
{
int a[5] = {0};
int b[5] = {1,2,3,4,5};
cout << "a + b = " << Add(a, b) << endl;
return 0;
}
模板参数
包括两种类型,分别是类型形参和非类型形参
模板形参的名字只能在模板形参之后,到模板的声明和定义的结束使用,遵循就近原则。
template<typename T>//FunTest函数模板
void FunTest(T t)
{
cout << "t's type:"typeid(t).name() << endl;
cout << "global's type:"typeid(global).name() << endl;
}
T global;
int main()
{
FunTest<int>(5);
return 0;
}
注意:
1、模板形参的名字在同一模板参数列表内只能使用一次
template<typename T,typename T>
void FunTest(T a,T b)
{
return;
}
2、所有模板前必须加上class或者typename关键字进行修饰
template<typename T,U>
void FunTest(T a,U b)
{
return;
}
函数模板的重载
1、函数模板和函数模板进行重载
当然,函数模板也是可以重载的。
template<typename T>
T Max(const T& left, const T& right)
{
return left>right ? left : right;
}
template<typename T>
T Max(const T& a, const T& b, const T& c)
{
return Max(Max(a, b), c);
};
int main()
{
cout << Max(1, 2) << endl;
cout << Max(1.2, 3.4, 5.6) << endl;
system("pause");
return 0;
}
上面我们重载了两个求最大值的函数模板,他们只有模板的参数不同而已2、函数模板和非模板的函数进行重载
int Max(const int& left, const int & right)//普通函数
{
return left>right ? left : right;
}
template<typename T>
T Max(const T& left, const T& right)
{
return left>right ? left : right;
}
template<typename T>
T Max(const T& a, const T& b, const T& c)
{
return Max(Max(a, b), c);
};
int main()
{
cout << Max(10, 20, 30) << endl;
cout << Max<>(10, 20) << endl;
cout << Max(10, 20) << endl;
cout << Max(10, 20.12) << endl;
cout << Max<int>(10.0, 20.0) << endl;
cout << Max(10.0, 20.0) << endl;
system("pause");
return 0;
}
注意:在此种情况下,编译器会先在非模板的函数中搜寻与之参数类型,个数完全匹配的函数;如果找到了,编译器便会调用它,不会让函数模板进行实例化;
如果没有找到,则通过函数模板进行实例化;如果想让编译器不调用非模板函数,则需要在调用的时候进行显示实例化,这样的话,函数模板实例化生成的代码也和原来定义的
非模板函数不是同一块代码
模板函数的特化
有时候,不能写出对所有可能实例化出来的类型都最适合的模板,在某些情况下,通过模板定义的函数有可能对于某个类型是错误的,或者编译失败,或逻辑错误
template <typename T>
int Compare(T s1, T s2)
{
if(s1<s2)
return -1;
else if(s1>s2)
return 1;
else
return 0;
}
int main()
{
char* str1 = "abcd";
char* str2 = "ghf";
cout<<Compare(str1,str2)<<endl;
system("pause");
return 0;
}
如果这样写我们的代码,那么,你就会发现比较的结果一直是不变的
对此,我们只需要特化一下~
template<>
int Compare<const char*>(const char* str1,const char *str2)
{
return strcmp(str1, str2);
}
就可以避免错误的发生
注意:特化一定要和原模板函数版本的形参类型完全一致