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

适合具备 C 语言基础的 C++ 教程(七)

作者头像
wenzid
发布2021-03-04 14:43:26
4390
发布2021-03-04 14:43:26
举报
文章被收录于专栏:wenzi嵌入式软件wenzi嵌入式软件

前言

在前面的教程中,阐述了继承的相关内容,其中就包括继承之后数据成员的访问控制以及多重继承,虚拟继承等内容,本节的内容即将阐述另外一个面向对象的特性:多态,多态是面向对象编程三大特性之一。

定义

如何通俗的话来解释多态呢?我们印出来这样一个例子:首先,我们说:人类用手吃饭是本能,而英国人是用刀叉吃饭,中国人则是用筷子吃饭,那现在有一个问题了,当我们问这个人是怎么吃饭的,就不能简单地回答说用筷子或者是用刀叉吃饭,应该根据其所在地国别不同而采用不同的吃饭方式,这就是多态。

代码实现

上述用通俗的话解释了一下,那么现在我们编写具体的代码来实现一下,上述中有人类,有英国人,有中国人,那么我们定义一个Human类,然后 EnglishManChinese都继承自Human,代码如下所示:

代码语言:javascript
复制
class Human
{
public:
    void eating(void) {cout << "use hand to eat" << endl;}
};

class EnglishMan : public Human
{
public:
    void eating(void) {cout << "use knife to eat" << endl;}
};

class Chinese : public Human
{
public:
    void eating(void) {cout << "use chopsticks to eat" << endl;}
};

紧接着,我们编写一下test代码,代码如下所示:

代码语言:javascript
复制
void test_eating(Human& h)
{
    h.eating();
}

紧接着,我们来编写main函数,主函数代码如下所示:

代码语言:javascript
复制
int main(int argc,char **argv)
{
    Human h;
    Englishman e;
    Chinese c;

    test_eating(h);
    test_eating(e);
    test_eating(c);

    return 0;
}

按照常规思路,在调用 test_eating()函数的时候,我们传入的实参不同,那么它就会调用不同实参所对应的成员函数,我们看代码的运行结果:

image-20210220103645630

可见代码的运行结果并不是如我们所想的一样,那这是为什么呢,这就要提到前面一则教程中所讲的派生类的空间分布,也正是因为这个原因,导致代码的运行结果如上图所示,那要如何更改呢,让其按照我们的想法来运行。这里就要提到虚函数的概念了。

虚函数

要实现不同的实参调用不同的方法,我们也可以在test_eating()函数里进行判断,然后进行不同方法的调用,当然这是比较笨的方法了,最好的实现方式就是引入虚函数,到底什么是虚函数呢,我们直接看代码:

代码语言:javascript
复制
class Human
{
public:
   virtual void eating(void) {cout << "use hand to eat" << endl;}
};

Human类的实现里,在成员函数的前面加了virtual关键字,则将eating函数就变成了虚函数,Humaneating方法变成了虚函数,那么EnglishMan类和chinese类的eating方法也变成了虚函数。引入了虚函数之后,我们继续执行上述所示的代码,结果如下所示:

image-20210220105143829

可见,我们实现了不同的实参调用了不同的方法,回过头来,我们来看最初提出的多态的概念,也就与之呼应上了,关于多条总结下来也就是:使用相同的方法,调用不同类里面的成员函数

多态的概念阐述清楚之后,我们继续来剖析虚函数,提到虚函数,必须提及如下两个概念:

  • 静态联编:非虚函数,在编译的时候就已经确定好何时调用
  • 动态联编:
  • 1、对象里有指针,指向虚函数表
  • 2、通过指针,找到虚函数表,进而调用虚函数

静态联编和动态联编也存在着区别,静态联编效率高,动态联编支持多态

简而言之,也就是说一个类里有虚函数,那么这个类的实例化对象中必然存在指针,指针指向虚函数表,通过指针指向的虚函数表调用虚函数,下面是这个过程的一个示意图:

image-20210220111846577

为了验证存在虚函数的实例化对象中确实含有一个指针,我们将类的大小打印出来观察。首先打印没有虚函数的类的大小,我们在Human类中加入一个变量:

代码语言:javascript
复制
class Human
{
private:
    int a;
public:
    void eating(void) {cout << "use hand to eat" << endl;}
};

加入了一个数据成员之后,其他的继承方式不改变,我们能编写主函数打印出实例化对象的大小,代码如下所示:

代码语言:javascript
复制
int main(int argc,char **argv)
{
    Human h;
    Englishman e;
    Chinese c;

    cout<<"sizeof(Human) = "<<sizeof(h)<<endl;
    cout<<"sizeof(Englishman) = "<<sizeof(e)<<endl;
    cout<<"sizeof(Chinese) = "<<sizeof(c)<<endl;
}

结果也是显而易见的,下面是代码运行结果:

image-20210220161605477

实例化对象的大小等于4,也就是类的一个数据成员的大小。

在上述的代码中,Human类引入虚函数,将代码更改如下所示:

代码语言:javascript
复制
class Human
{
private:
    int a;
public:
    virtual void eating(void) {cout << "use hand to eat" << endl;}
}

主函数代码不变,代码执行结果如下所示:

image-20210220161931616

可见这个时候,实例化对象的大小就发生了改变,比之前增加了12,而这个增加的12的大小就是指向虚函数表的指针的大小。虚函数的内容就说到这,我们接下来继续叙述一下多态的特性,要使用多态这个特性,那自然也要了解一下多态有哪些限制。

多态的限制

形参必须为指针或者引用才有多态,如果形参是传值调用,则没有多态

我们使用代码来验证一下上面这句话,相对于上面来说,代码更改的比较少,只需要将test_eating函数的形参进行更改就可以,代码如下所示:

这个时候,继续执行上述所示的主函数,主函数代码如下所示:

执行结果如下所示:

可见,采用传值调用的时候,即便使用了虚函数,多态也无法体现了,那这是为什么呢,正如下面这张图所示:

如上图所示,e是从h继承而来,而 test_eating()函数的形参又是 Human类的,那么在进行传值调用的时候,e要进行类型转换为h,那么 e本身的指针所占的空间就没有,那指针所张的空间没了,也就找不到虚函数表了,因此,也就有了上述的执行结果。

只有类的成员函数才能做为虚函数;

静态成员函数不能是虚函数;

内联函数不能是虚函数;

构造函数不能是虚函数;

析构函数一般都声明为虚函数;

作为析构函数一般都声明为虚函数,我们在以代码详细阐述一下,首先,我们将上述内容所涉及到的类都加入析构函数:

紧接着,我们来编写主函数,主函数代码如下所示:

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

通过运行结果可知,在执行析构函数的时候,都是执行的Human类的析构函数,这样看来并不是正确的,因此也就证实了那句话析构函数一般声明为虚函数,更改之后的代码如下所示:

在将析构函数改为虚函数之后,我们继续运行主函数的内容,运行结果如下所示:

通过上述可以看到,在执行析构函数时也根据不同的实例化对象,而执行了不同的析构函数,上面仍然调用了三次Human类的析构函数是因为派生类在执行析构函数时,首先执行自己的析构函数,然后执行父类的析构函数,因此,~Human()执行了三次。

重载函数不可设置为虚函数,重载函数的形参不同;

覆写可以设置为虚函数,函数参数、返回值均相同;

如果函数的参数相同,但是返回值是当前对象的指针或者引用时,也可以设置为虚函数;

上述这句话的重点,必须返回值是当前对象的指针或者引用,否则不可以设置为虚函数,我们使用代码来具体阐述,同样的,我们先编写各个类的代码实现:

上述代码中,可见每个类中的 test()函数的返回值分别是各个类的指针,形参为void,这也满足了返回值不同,形参相同可以作为虚函数的条件,紧接着,我们来编写一个测试函数:

我们看主函数:

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

可以看到运行结果是符合我们的推测的,而且返回值为各个类的指针,形参相同的函数也确实可以作为虚函数。

小结

本节关于多态要阐述的内容就到这里了,本节所涉及到的代码可以通过下面的百度云链接进行获取:

链接:https://pan.baidu.com/s/1xCwpCtqRgfdoeyRICf8WkA 提取码:ewun

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 wenzi嵌入式软件 微信公众号,前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 定义
  • 代码实现
  • 虚函数
  • 多态的限制
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档