[C++]面向对象语言三大特性--多态

Posted 一个正直的男孩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C++]面向对象语言三大特性--多态相关的知识,希望对你有一定的参考价值。

大家这篇文章描述的是我在学习多态时的一些学习笔记,和问题,还有一些面试题,希望这篇文章对你会有帮助


文章目录


什么是多态?

下定义之前先讲个例子:

加入你是一个铲屎官,且家里有三个祖宗 🐱 、🐶 、🐰,那么在要干饭的时候,你为他们准备的食物分别是 猫粮,狗粮,胡萝卜。那么这里为不同的宠物准备不一样的食物就可以看做多态


下定义:

在对待同一件事(喂食)的时候会有不同的结果(食物)

z

如何才可以看到多态这一属性呢?(概念)

多态是在继承后的基础上拓展出来的,那么需要看这一个特性必须要满足以下的条件

  1. 必须是父子类(等于白说)
  2. 必须是父类的指针或者引用
  3. 必须是虚函数

上面三个条件缺一不可,少一个你都看不到多态这美丽的属性


示例代码:

class Shoveler

public:
    virtual void foot()//条件3
    
        cout<<"杂食,蔬菜,肉"<<endl;
    

;
class Cat:public Shoveler

   public:
    virtual void foot()//条件3
    
        cout<<"猫粮,鱼"<<endl;
    

;
class Dog:public Shoveler

    public:
   virtual void foot()//条件3
    
        cout<<"狗粮,骨头"<<endl;
    
;
void WhatEat(Shoveler &sl)//条件2

  sl.foot();

int main()

    Shoveler d1;
    Cat d2;
    Dog d3;
     
   //调用
    WhatEat(d1);
    WhatEat(d2);
    WhatEat(d3);

结果:




什么虚函数?

在上一篇文章继承中,有一个概念叫菱形继承,可以用虚继承来解决。那你还记得那个关键字吗?virtual

大胆猜想

给继承时加virtual 就是虚继承,那么在函数前加virtual那不就是虚函数,我真聪明,emmm似乎确实如此




如何使用多态?

上述的代码大致你已经知道他的基本使用了,但是那个还不够,毕竟只是一个示范罢了 !!!!

条件2

虽然这里时条件,但是我还是好想知道为什么,指针和引用可以但是类型不可以呢

这里涉及到了底层,本文后面回、会讲解,就带着这个疑问继续阅读

那他是如何调用的虚函数达到多态的呢?

好家伙你这问到点上了,但是这还是涉及到底层。现在可以说这种达到多态的场景叫做覆盖,重写


条件3

多态条件补充

如果要构成多态,那么他的虚函数一定要 函数名,返回值,参数 都相同,当然返回值前加virtual




C++奇葩的设计(多态篇)

由上面刚刚补充的条件知道要构成多态虚函数需要满足函数名,返回值,参数 都相同,但是这里emmm出岔子了

岔子1:

返回值不同也可以构成多态,但是必须返回值是父子类(虽然我也不知道为啥有这个操作),且有一个好听的专有名词 协变

如图所示:

岔子2:

子类的虚函数可以不写。这里的话其实还比较好理解,或许是C++协会的大大害怕你会写忘了(有点贴心),其实重要的是继承了父类的虚拟函数了,因为虚函数继承是接口继承就是直接覆盖你的函数声明(但是建议还是写上为好,高质量c++编程🤪)

岔子3:

析构函数的虚函数可以名称不同,其实在上篇文章继承中说过,构造函数会被转换成destruct,所以其实还是同名

为啥析构函数还要是虚函数呀,好奇???


看下面这个例子

这种情况如果析构不构成重写的话,那么就会如图所示




一些看起来挺实用,但是感觉不太会用的操作

final

  1. 这个关键修饰的类不可继承
  2. 关键字修饰的虚函数不可以多态(重写,覆盖)

用法

override

关键修饰的虚函数如果没有实现多态就会报错,类似assert

用法

抽象类

虚函数声明后面 ➕ =0 就是抽象类,当是抽象类的时候是不可以实例化对象的,继承后如果子类不重写虚函数,那么子类也是抽象类

用法

抽象类就是一个媒介,或者他就是一个工具人,且只有被继承才可以使用,这里提个醒,他不可以实例化对象,但是可以创建指针引用,且类中也是可以有变量,好家伙真的工具类




多态的底层是如何实现的?

先看一些这道面试或许会问到的题(输出的是啥??)

运行结果

解释:

这里为啥是16呢?其实是虚函数在搞鬼,有了虚函数后这个类就会在开始多一个指针(虚表指针)博主编译器是在64平台下的,如图所示内存模型

虚表指针好熟悉,哦继承中不是也有一个虚基表指针,虚表指针是不是就是存虚函数的指针?

不得不说你还是很聪明,没错他确实就是存虚函数的指针(_vfptr),但是你要吧虚表和虚基表区分开来,这是菱形继承解决方案中的虚继承,虚基表存的父类元素的是偏移量




虚函数表

概念:

每个类都有属于自己的一个虚表(毕竟有各自的作用域),且这个这个类实例化的对象共用一张虚表,(虚函表存的这个函数的地址,而这个函数是存在公共代码段中的,所以一个类实例化的对象公用一张虚表


上述留下的坑

  1. 为啥只有指针和引用才支持多态
  2. 多态的实现

为啥只有指针和引用才支持多态?

因为如果用对象去接收的换,那么就只子类拷贝构造父类,但是指针和引用还是志向子类的内存空间的,如图所示的内存模型视图:

对象接收的情况

这里你或许会疑问为啥不把虚表拷贝构造呢?

每个类有属于自己唯一的虚表,且本类实例化对象共用一个虚表,如果拷贝(实例化对象就会有问题,它的虚表改变了,自己本类的虚函数访问不到)


父类指针或者引用的情况(其实也就是底层)




精准区分重载、重写,隐藏

重载

必须是同一作用域下函数名相同,但是参数列表不同,这个就是重载,有人会称他为静态的多态(在编译时就确定好函数的位置了)

重写

大前提条件就是在继承的前提下,父子类都有同名,同返回值,同参,虚函数,调用的时候一定是父类的指针或者引用

隐藏

也是在继承的前提下,只要父子类中有同名的函数不管参数和返回值是否一样,直接构成隐藏




面试或许会考察的题

请问下面程序的打印结果是啥?

答案

classA classB classC classD

解释

这里初始化看的不是构造函数列表,而是继承的顺序,编译器会优化A只初始化一次,至于为啥那就要看底层了


请问代码中的指针的偏移量(其实就是指针指向的位置)

A p1==p2==p3    B p1==p2!=p3   C p1==p3!=p2  D p1!=p2!=p3

答案

C

解释

如图所示:


请问下面打印的结果是啥?

答案

b->1

解释如图所示

如果这几道题对了恭喜,你这就是腾讯之前考过的笔试选择题




唠唠家常

这个就是面向对象的三大特性,其实理解起来也还好,也是全部语言通用的哦,那么就到这里吧!!!!

以上是关于[C++]面向对象语言三大特性--多态的主要内容,如果未能解决你的问题,请参考以下文章

[C++]面向对象语言三大特性--多态

[C++]面向对象语言三大特性--多态

C++ 面向对象程序三大特性之 多态

面向对象三大特性-多态的思考

面向对象语言三大特性之 “多态”

Golang-面向对象编程三大特性-多态