前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >适合具备 C 语言基础的 C++ 入门教程(十一)

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

原创
作者头像
wenzid
修改2021-02-26 17:38:17
2200
修改2021-02-26 17:38:17
举报
文章被收录于专栏:wenzi嵌入式软件

前言

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

函数模板重载

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

代码语言:txt
复制
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,剩余的就是一个普通函数,基于此,我们来编写主函数的代码:

代码语言:txt
复制
int main(int argc, char **argv)
{
	int ia = 1;
	int ib = 2;

	cout<<mymax(ia, ib)<<endl;

	return 0;
}

代码执行的结果如下所示:

image-20210224171715532
image-20210224171715532

通过第一行的打印信息可以看到,当前调用mymax()函数是调用的普通函数,并不是模板函数,那么这是为什么呢,在这里通过传入的参数可知两个模板函数也是调用的,但是为什么就是调用的普通函数而非模板函数呢?这个原则是什么,下面列出了一个详细地分析步骤来分析这个调用过程。

  • 1、第一步首先是列出可能被调用地候选函数,就包括普通函数和参数推导成功地模板函数
    • 针对于这个例子来说,列出的候选函数如下所示:
      • 第一个模板函数:mymax(const int &, const int &);
      • 第二个模板函数:mymax(int&, int&);
      • 第三个普通函数:mymax(int&, int&);
  • 2、根据参数转换,进行排序:
    • 第一个模板函数:int -> const int
    • 第二个模板函数:int -> int
    • 第三个普通函数:int -> int
    • 所以,第二个模板函数和第三个普通函数并列第一,第一个模板函数排第二
  • 3、选择更加匹配的候选函数
    • 如果匹配度相同
      • 优先选择普通函数
      • 对于多个模板函数,选择一个更加特化的(特化后续解释)
      • 否则,代码出现二义性
  • 所以,根据第二得到的结果是第二个模板函数和第三个普通函数并列第一,根据第三条原则,优先选择普通函数,因此,在这里调用的是普通函数。

二义性例子

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

代码语言:txt
复制
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;
}

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

代码语言:txt
复制
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
image-20210224195510811

参数为指针

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

代码语言:txt
复制
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;
}

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

代码语言:txt
复制
int main(int argc, char** argv)
{
    int *p1=&ia;
	int *p2=&ib;

	cout<<mymax(p1, p2)<<endl;

	return 0
}

代码执行结果如下图所示:

image-20210224201939143
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*的例子,首先,依旧是先看模板函数,代码如下所示:

代码语言:txt
复制
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;
}

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

代码语言:txt
复制
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
image-20210224210604846

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

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

代码语言:txt
复制
template<typename T>
class AAA
{
private:
    T t;
public:
    void test_func(const T &t);
    void print(void);
};

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

代码语言:txt
复制
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;
}

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

代码语言:txt
复制
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
image-20210224224823979

类重载

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

代码语言:txt
复制
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;
}

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

代码语言:txt
复制
int main(int argc, char** argv)
{
    A<int> a;
    
    a.test_func_int(1);
	a.print_int();
    
    return 0;
}

代码运行结果如下所示:

image-20210224230451701
image-20210224230451701

注:在上述介绍的函数模板和类模板,虽然在介绍的时候,都是在 .cpp中实现的,但是在实际的项目当中,其实基本都是写在.h文件中的,因为对于模板来说,它只是一些编译指令,一般都是将其放在头文件中供其他代码引用。

小结

上述便是本期分享的内容,本期涉及到的代码可以通过百度云链接的方式获取到:

链接:https://pan.baidu.com/s/1p-GU0_5aa46lvaB2AdHemA 提取码:1sxd

如果您觉得我的文章对您有所帮助,欢迎关注我的个人公众号:wenzi嵌入式软件

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 函数模板重载
      • 二义性例子
      • 参数为指针
      • const int *
    • 类重载
    • 小结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档