适合具备 C 语言基础的 C++ 入门教程(十一)

前言

在上一则教程中,着重叙述了抽象类界面以及函数模板的相关内容,在本节教程中,笔者将详细阐述函数模板重载的概念,将从具体的实例中阐述函数模板重载要注意的内容。

函数模板重载

函数重载是在教程最开始就阐述过的概念,那么函数模板重载又该如何理解呢,我们以上一节教程中比大小的函数模板为背景,具体阐述函数模板重载的概念,代码如下所示:

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、选择更加匹配的候选函数
    • 如果匹配度相同
      • 优先选择普通函数
      • 对于多个模板函数,选择一个更加特化的(特化后续解释)
      • 否则,代码出现二义性
  • 所以,根据第二得到的结果是第二个模板函数和第三个普通函数并列第一,根据第三条原则,优先选择普通函数,因此,在这里调用的是普通函数。

二义性例子

接下来,我们来看一个二义性的例子,首先给出模板函数的代码,代码如下所示:

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;
}

有了模板函数,我们再来编写主函数,主函数代码如下所示:

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

参数为指针

接下来,叙述一个实参为指针的一个例子,首先先看模板函数和普通函数,代码如下所示:

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;
}

基于这样一个模板函数,我们现在来编写主函数,主函数代码如下所示:

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;
}

我们在基于上述两个模板函数的基础上,来编写我们的主函数,主函数代码如下所示:

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

虽然调用的是第一个模板函数,编译没有出错,但是实际上这里函数运行结果并非我们想要,它是比较的两个传进去的实参的地址的大小,返回的也是地址的大小,并非值的大小。

类模板

在首次介绍模板的时候,我们也指出,除了有函数模板以外还具有类模板,接下来,笔者将对类模板的相关概念进行阐述。当我们碰到相似的函数的时候,会想到使用函数模板来解决问题;自然,如果我们碰到有相似的类的时候,也可以使用类模板来解决问题,下面的代码就是一个类模板:

template<typename T>
class AAA
{
private:
    T t;
public:
    void test_func(const T &t);
    void print(void);
};

上述就是一个类模板的写法,上述只是类的一个声明,如果要实现模板类里面的成员函数,则需要使用如下所示的代码:

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;
}

基于上述模板类,我们就可以编写主函数了,代码如下所示:

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

类重载

我们知道函数是可以重载的,那么其实类也是可以进行重载的,类重载也可以称之为是定做,在上述代码的基础上,我们来定做类,代码如下所示:

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;
}

在定做了类之后,我们来看主函数的代码:

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

如果您觉得我的文章对您有所帮助,欢迎关注我地个人公众号:wenzi嵌入式软件
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值