前言
在上一则教程中,着重叙述了抽象类界面以及函数模板的相关内容,在本节教程中,笔者将详细阐述函数模板重载的概念,将从具体的实例中阐述函数模板重载要注意的内容。
函数模板重载
函数重载是在教程最开始就阐述过的概念,那么函数模板重载又该如何理解呢,我们以上一节教程中比大小的函数模板为背景,具体阐述函数模板重载的概念,代码如下所示:
代码语言:javascript复制template<typename T>
const T& mymax(const T& a, const T& b)
{
cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
return (a < b)? b : a;
}
template<typename T>
const T& mymax(T& a, T& b)
{
cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
return (a < b)? b : a;
}
const int& mymax(int& a, int& b)
{
cout<<"3: "<<__PRETTY_FUNCTION__<<endl;
return (a < b)? b : a;
}
上述代码中展示了两个函数模板和一个普通函数,两个函数模板的区别就在第一个函数模板形参中具有 const
,但是第二个函数模板不具有const
,剩余的就是一个普通函数,基于此,我们来编写主函数的代码:
int main(int argc, char **argv)
{
int ia = 1;
int ib = 2;
cout<<mymax(ia, ib)<<endl;
return 0;
}
代码执行的结果如下所示:
image-20210224171715532
通过第一行的打印信息可以看到,当前调用mymax()
函数是调用的普通函数,并不是模板函数,那么这是为什么呢,在这里通过传入的参数可知两个模板函数也是调用的,但是为什么就是调用的普通函数而非模板函数呢?这个原则是什么,下面列出了一个详细地分析步骤来分析这个调用过程。
- 1、第一步首先是列出可能被调用的候选函数,就包括普通函数和参数推导成功的模板函数
- 针对于这个例子来说,列出的候选函数如下所示:
- 第一个模板函数:mymax(const int &, const int &);
- 第二个模板函数:mymax(int&, int&);
- 第三个普通函数:mymax(int&, int&);
- 2、根据参数转换,进行排序:
- 第一个模板函数:
int ->const int
- 第二个模板函数:
int ->int
- 第三个普通函数:
int ->int
- 所以,第二个模板函数和第三个普通函数并列第一,第一个模板函数排第二
- 3、选择更加匹配的候选函数
- 如果匹配度相同
- 优先选择普通函数
- 对于多个模板函数,选择一个更加特化的(特化后续解释)
- 否则,代码出现二义性
- 所以,根据第二得到的结果是第二个模板函数和第三个普通函数并列第一,根据第三条原则,优先选择普通函数,因此,在这里调用的是普通函数。
二义性例子
接下来,我们来看一个二义性的例子,首先给出模板函数的代码,代码如下所示:
代码语言:javascript复制template<typename T>
const T& mymax(const T& a, const T& b)
{
cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
return (a < b)? b : a;
}
template<typename T>
const T& mymax(T& a, T& b)
{
cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
return (a < b)? b : a;
}
template<typename T>
const T mymax(T a, T b)
{
cout<<"4: "<<__PRETTY_FUNCTION__<<endl;
return (a < b)? b : a;
}
有了模板函数,我们再来编写主函数,主函数代码如下所示:
代码语言:javascript复制int main(int argc, char **argv)
{
int ia = 1;
int ib = 2;
cout<<mymax(ia, ib)<<endl;
return 0;
}
同样的,按照上述的分析方法,一步一步进行分析:
- 1、第一步,列出候选函数:
- 第一个模板函数:
mymax(const int &, const int &);
- 第二个模板函数:
mymax(int&, int&);
- 第三个模板函数:
mymax(int, int)
- 2、根据参数转换,进行排序
- 第一个模板函数:
int ->const int
- 第二个模板函数:
int ->int
- 第三个模板函数:
int ->int
- 3、选择更加匹配的候选函数
- 由
2、
可知,第二个和第三个匹配度一样,所以当前这个程序也就造成了二义性的错误
下面是代码编译的结果:
image-20210224195510811
参数为指针
接下来,叙述一个实参为指针的一个例子,首先先看模板函数和普通函数,代码如下所示:
代码语言:javascript复制template<typename T>
const T& mymax(const T& a, const T& b)
{
cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
return (a < b)? b : a;
}
template<typename T>
const T& mymax(T& a, T& b)
{
cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
return (a < b)? b : a;
}
const int& mymax(int& a, int& b)
{
cout<<"3: "<<__PRETTY_FUNCTION__<<endl;
return (a < b)? b : a;
}
template<typename T>
const T mymax(T * a, T* b)
{
cout<<"4: "<<__PRETTY_FUNCTION__<<endl;
return (*a < *b)? *b : *a;
}
基于这样一个模板函数,我们现在来编写主函数,主函数代码如下所示:
代码语言:javascript复制int main(int argc, char** argv)
{
int *p1=&ia;
int *p2=&ib;
cout<<mymax(p1, p2)<<endl;
return 0
}
代码执行结果如下图所示:
image-20210224201939143
由上述打印出来的信息可以知道,上述是调用了标号为4
的模板函数,我们按照之前的方法来分析一下,首先调用的函数是mymax(int *, int *)
:
- 1、列出候选函数:
- 第一个模板函数:
mymax(const int*&, const int*&)
; - 第二个模板函数:
mymax(int *&, int *&)
; - 第三个普通函数:不满足
- 第四个模板函数:
mymax(int *,int *)
; - 2、根据参数,进行排序:
- 第一个:
int* -> const int*
- 第二个:
int* -> int*
- 第四个:
int* -> int*
- 3、根据参数,进行排序:
- 最匹配的是:第二个和第四个
- 4、它们都是模板函数,选出“更特化”的,更特化的意思也就是说参数匹配更加特殊,更加具体,更加细化
我们这个时候,回过头来看第二个模板函数,mymax(T& a, T& b)
,对于这个模板函数来说,它存在两种情况:
- 当
T = int
的时候,那么也就是mymax(int &,int &)
; - 当
T= int *
的时候,那么也就是mymax(int *&, int *&)
我们再来看第四个模板函数,mymax(T*, T*)
,参数只能是指针,也就是说当T = int
的时候,也就是 mymax(int*, int*)
,通过这里的分析,我们可以看出对于第二个模板函数和第四个模板函数来讲,第四个模板函数更加具体,也就是更加特化,所以上述是调用的第四个模板函数。
const int *
接下来,我们看一个由int* -> const int*
的例子,首先,依旧是先看模板函数,代码如下所示:
template<typename T>
const T& mymax(const T& a, const T& b)
{
cout<<"1: "<<__PRETTY_FUNCTION__<<endl;
return (a < b)? b : a;
}
template<typename T>
const T mymax(const T * a, const T* b)
{
cout<<"2: "<<__PRETTY_FUNCTION__<<endl;
return (*a < *b)? *b : *a;
}
我们在基于上述两个模板函数的基础上,来编写我们的主函数,主函数代码如下所示:
代码语言:javascript复制int main(int argc, char **argv)
{
int ia = 1;
int ib = 2;
int *p1=&ia;
int *p2=&ib;
cout<<mymax(p1, p2)<<endl;
return 0;
}
同样的方法,为了分析出它会调用的是哪一个模板函数,我们按照之前所述的步骤一步一步地进行分解,首先,明确调用的函数是:mymax(int*, int*)
。
- 1、列出候选函数:
- 第一个模板函数:
mymax(const int*&, const int*&)
- 第二个模板函数:
mymax(const int*, const int*)
- 2、根据参数,进行比较:
- 第一个模板函数:
int* -> const int *;
- 第二个模板函数:
int* ->const int*
- 3、根据参数,进行匹配:
- 由
2、
的结果可知,两个都是匹配的
既然两个都是匹配的,那要如何进行选取呢?首先额外补充一个知识点:
const int *p
,遇到指针的时候,都是从右往左读,遇见p
就读成p is a
,遇见*
就读成pointer to
,那么这条语句也就翻译成这样:p is a pointer to const int
,也就是说p
指向的对象是不可修改的
我们这个时候,来看第二个模板函数,对照其推导出来的模板函数,mymax(const int*, const int*)
,也就是说传进去的实参所指向的内容是不可变的。
此时,我们再来看第一个模板函数,其模板函数是这样的:const T& mymax(const T& a, const T& b)
,而推导的模板函数实际上也就是说T = int *
,所以它的形参实际上应该是这样的:const (int*)&
,那这么说来,p
是常量引用,p
无法修改,但是p
指向的对象是可修改的。
回到我们的代码上去,我们传到函数里面的两个实参是可以修改的,所以这里应该选择第一个模板函数进行调用,下面是代码执行的结果:
image-20210224210604846
虽然调用的是第一个模板函数,编译没有出错,但是实际上这里函数运行结果并非我们想要,它是比较的两个传进去的实参的地址的大小,返回的也是地址的大小,并非值的大小。
类模板
在首次介绍模板的时候,我们也指出,除了有函数模板以外还具有类模板,接下来,笔者将对类模板的相关概念进行阐述。当我们碰到相似的函数的时候,会想到使用函数模板来解决问题;自然,如果我们碰到有相似的类的时候,也可以使用类模板来解决问题,下面的代码就是一个类模板:
代码语言:javascript复制template<typename T>
class AAA
{
private:
T t;
public:
void test_func(const T &t);
void print(void);
};
上述就是一个类模板的写法,上述只是类的一个声明,如果要实现模板类里面的成员函数,则需要使用如下所示的代码:
代码语言:javascript复制template<typename T>
void AAA<T>::test_func(const T &t)
{
this->t = t;
}
template<typename T>
void AAA<T>::print(void)
{
cout << t << endl;
}
基于上述模板类,我们就可以编写主函数了,代码如下所示:
代码语言:javascript复制int main(int argc, char **argv)
{
AAA<int> a;
a.test_func(1);
a.print();
AAA<double> b;
b.test_func(1.23);
b.print();
return 0;
}
代码运行结果如下所示:
image-20210224224823979
类重载
我们知道函数是可以重载的,那么其实类也是可以进行重载的,类重载也可以称之为是定做,在上述代码的基础上,我们来定做类,代码如下所示:
代码语言:javascript复制template<>
class AAA<int>
{
public:
void test_func_int(const int &t)
{
cout << t << endl;
}
void print_int(void);
};
void AAA<int>::print_int(void)
{
cout << "for test" << endl;
}
在定做了类之后,我们来看主函数的代码:
代码语言:javascript复制int main(int argc, char** argv)
{
A<int> a;
a.test_func_int(1);
a.print_int();
return 0;
}
代码运行结果如下所示:
image-20210224230451701
注:在上述介绍的函数模板和类模板,虽然在介绍的时候,都是在
.cpp
中实现的,但是在实际的项目当中,其实基本都是写在.h
文件中的,因为对于模板来说,它只是一些编译指令,一般都是将其放在头文件中供其他代码引用。
小结
上述便是本期分享的内容,本期涉及到的代码可以通过百度云链接的方式获取到:
链接:https://pan.baidu.com/s/1p-GU0_5aa46lvaB2AdHemA 提取码:1sxd