广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >【C++】面向对象---多态(万字详解)
  • 499
分享到

【C++】面向对象---多态(万字详解)

c++java开发语言 2023-09-01 12:09:34 499人浏览 安东尼
摘要

       🔥🔥 欢迎来到小林的博客!!       🛰️博客主页:✈️小林爱敲代码       🛰️文章专栏:✈️小林的C++之

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️小林爱敲代码
      🛰️文章专栏:✈️小林的C++之路
      🛰️欢迎关注:👍点赞🙌收藏✍️留言
在这里插入图片描述

      今天给大家讲解多态,多态是面向对象的一个重要内容。也非常的抽象,所以今天尽我所能为大家分享自己对c++中多态的一些理解。



         每日一句: 世界上只有想不通的人,没有走不通的路。

大纲:
插入图片

目录

💖1. 多态的概念

    多态的意思就是多种形态,简而言之就是 : 不同的事物做同一种行为,产生了不同的结果。
打个比方,学生和普通人买票,学生优惠7折,而普通人没有优惠。这类现象就符合多态,不同的事物(普通人,学生)做同一种行为(买票)产生了不同的结果(学生七折,普通人全款)。
blog.csdnimg.cn/c6c1467796d347858c71b7ade474d3ec.png)
而普通人和学生之间还有另外一种关系,那就是继承关系。因为学生也是人,所以构成多态的前提是不同的事物之间构成继承关系。


💖2. 多态的定义及实现

🌺2.2 多态的构成条件

    想要知道多态如何定义,那么我们必须知道多态的定义条件。构成多态的两个条件:

必须通过基类的指针或引用调用虚函数。
2.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。


🌺2.3 虚函数

虚函数:被virtual 修饰的函数即为虚函数。

class Person{public:virtual void BuyTicket()//被virtual修饰,是虚函数{}};

🌺2.4 虚函数的重写

虚函数的重写: 派生类必须有一个和基类一样(三同)的虚函数,才能构成重写。构成重写的条件:
1.派生类被重写的函数也得是虚函数(虽然不是也可以,因为会直接继承父类的虚函数)。
2.派生类被重写的函数和基类的虚函数一样 (函数名,返回值,参数都相同,协变除外)。

下面是一个虚函数构成重写的案例:

class Person{public:virtual void BuyTicket(){cout << "成年人买票" << endl;}};class Student : public Person{public:virtual void BuyTicket(){cout << "学生买票" << endl;}};

以上代码构成以下关系:
基类 : Person
派生类 : Student
基类的BuyTicket函数被virtual修饰,所以是虚函数。
派生类有一个和基类虚函数一模一样的虚函数。
所以派生类BuyTicket构成重写(覆盖)。

🌺2.5 协变

上面说过,被重写的函数必须与其基类对应的虚函数保持三同(返回值,函数名,参数),而协变是个例外,协变支持返回值是父子类的指针或引用。

代码案例如下:

class A{};class B:public A{};class Person{public:virtual A* BuyTicket(){new A();}};class Student : public Person{public:virtual B* BuyTicket(){new B();}};

返回值是父子类的指针或引用(也就是协变),一样会重写。

🌺2.6析构函数的重写

当我们在通过父类指针接收一个子类对象时,并期望释放掉这个对象。那么我们必须要让子类重写析构函数。也就是让析构函数变成虚函数,析构函数变成虚函数之后。子类会自动重写析构,这是因为在编译时析构函数的函数名会被统一处理为destructor。所以析构函数的函数名看起来不同,但实际上却是相同的。

下面是一个重写析构函数的例子:
不重写析构函数的代码:

class Person{public:virtual void BuyTicket(){cout << "成年人买票" << endl;} ~Person(){cout << "~Person" << endl;}};class Student : public Person{public:virtual void BuyTicket(){cout << "学生买票" << endl;} ~Student(){cout << "~Student" << endl;}};void a(Person* p){delete p;}int main(){Person* p = new Person();Student* s = new Student();a(s);return 0;}

我们这个代码是没有没有重写析构函数的,因为析构函数不是虚函数,我们看看结果。
在这里插入图片描述

我们会发现,问题很严!因为我传过去的是一个Student,也就是基类对象。但是我们用父类指针接收,那么 指针pp的使用范围 就是Person的范围。所以无法调用子类的析构函数,只能调用自己的析构函数。也就是说!释放不彻底,因为传过去的对象是s对象的指针,而delete它时,它却只调用了父类的析构函数,没有调用自己本身的析构函数,如果此时s对象有动态开辟的空间,那么就造成了内存泄露,这是很严重的。这是因为指针是Person类型的,所以只能访问Person的那一部分。想要解决这个问题,我们就需要重写析构函数。以至于传子类对象指针,父类指针接收也能调到子类的析构函数。


正确的写法:

class Person{public:virtual void BuyTicket(){cout << "成年人买票" << endl;}virtual ~Person(){cout << "~Person" << endl;}};class Student : public Person{public:virtual void BuyTicket(){cout << "学生买票" << endl;}virtual ~Student(){cout << "~Student" << endl;} int _a;};void a(Person* pp){delete pp;}int main(){Person* p = new Person();Student* s = new Student();a(s);return 0;}

这时候我们可以看到Student的析构函数也被调用了,这就意味着s对象被真正析构。所以析构函数还是很有必要被重写的。
在这里插入图片描述

🌺2.7 重载,重写(覆盖),重定义(隐藏)之间的区别

一张图概括

在这里插入图片描述


💖3. override 和 final(c++11)

override 和 final 在c++11才被引用,2个关键字的作用也很简单。
final:修饰虚函数,表示虚函数不能被重写。
override:检查派生类是否重写了虚函数,如果没重写,会报错。

final的使用:
在这里插入图片描述

override的使用:
在这里插入图片描述


💖4.抽象类

在虚函数的后面加上一个 = 0,这个函数就是纯虚,这就代表这是一个抽象类,也叫接口类,抽象类不能被实例化,派生类继承后也不能实例化出对象。除非重写其基类的纯虚函数。

代码样例:

class Person{public:virtual void Eat() = 0{}};class Student : public Person{public:};int main(){Person p;Student s;return 0;}

在这里插入图片描述
如果想使用,我们必须重写纯虚函数。
在这里插入图片描述
但是p依然不能实例化,想要p对象,我们可以通过指针或者引用的方式。

class Person{public:virtual void Eat() = 0{}};class Student : public Person{public:virtual void Eat(){cout << "吃饭" << endl;}};int main(){Student s;Person& p = s;p.Eat();return 0;}

这种方法已经构成了多态,因为s通过了基类的指针调用其纯虚函数。


💖 5.多态的原理

那么多态是怎么实现的呢?我们先来监视一下,非多态时,子类对象和父类对象。

class Person{public: void BuyTicket(){ cout << "成年人买票" << endl;} int _p;};class Student : public Person{public: void BuyTicket(){cout << "学生买票" << endl;} int _s;};int main(){Student s;Person p;return 0;}

这是父类对象
在这里插入图片描述


这是子类对象
在这里插入图片描述
接下来我们看看实现多态时的样子。

class Person{public:virtual void BuyTicket(){ cout << "成年人买票" << endl;} int _p;};class Student : public Person{public:virtual void BuyTicket(){cout << "学生买票" << endl;} int _s;};int main(){Student s;Person p;return 0;}

父类对象:
在这里插入图片描述
子类对象:
在这里插入图片描述
我们可以很清楚的发现,构成多态后,对象里面会多一个__vfptr的参数。而这个参数是虚函数表指针(简称虚表指针),它指向一个数组,数组的每个元素是一个函数指针。而这个数组,叫做虚函数表。而虚函数表里面存的就是虚函数的地址。当子类进行重写的时候,就会去虚函数表里面把存储的父类虚函数的地址覆盖成自己的虚函数地址。所以进行切片时,虚函数表里的虚函数地址还是自己的。调用虚函数时,去自己的虚函数表里面找到对应的虚函数。

所以多态的实现原理,简单来说就是以下几个步骤:

  1. 看父类有没有虚函数,如果有虚函数,则会在父类生成一个虚函数表。
    在这里插入图片描述

  2. 子类继承父类时,会把父类的虚函数表也继承下来。
    在这里插入图片描述

  3. 随后子类查找有没有符合重写条件的函数(三同,且是虚函数),符合重写条件则到继承的虚函数表里,覆盖掉父类的虚函数表。

在这里插入图片描述
此时如果构成多态,就会进Student的虚函数表里面找对应的虚函数调用,因为父类虚函数的地址被替换了。


💖 6.单继承和多继承的虚函数表

🌺 6.2 打印虚函数表

以下这段代码可以直接打印虚函数表,其原理取对象的地址,随后强制转换成一个指针。因为指针在32平台是4字节,在64平台是8字节。所以把对象强制转换成指针类型,访问的第一个元素就是一个指针的大小。因为虚表指针就是在对象的最前面4个或8个字节。然后强制转换成函数指针。

class Person{public:virtual void fun1(){cout << "Person::fun1()" << endl;}virtual void fun2(){cout << "Person::fun2()" << endl;} int _p;};class Student : public Person{public:virtual void fun1(){cout << "Student::fun1()" << endl;}virtual void fun2(){cout << "Student::fun2()" << endl;}virtual void fun3(){cout << "Student::fun3()" << endl;}virtual void fun4(){cout << "Student::fun4()" << endl;} int _s;};void a(Person& p){p.fun1();}typedef void(*VFPTR) ();void PrintVTable(VFPTR vTable[]){cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl << endl;}int main(){Person p;Student s;VFPTR * vTableb = (VFPTR*)(*(void**)&p);PrintVTable(vTableb);VFPTR* vTabled = (VFPTR*)(*(void**)&s);PrintVTable(vTabled);return 0;}

我们看看打印结果
在这里插入图片描述
这样我们就可以看到虚函数表的打印结果了。

🌺 6.3 单继承的虚函数表

上面说过的情况就是单继承时的情况,子类继承父类时会继承它的虚表。随后在虚表里面覆盖重写的函数。那么如果此时子类自己的函数也是虚函数,那么也会添加至虚函数表中。

两个类的关系如下所示

class Person{public:virtual void fun1(){cout << "Person::fun1()" << endl;}virtual void fun2(){cout << "Person::fun2()" << endl;} int _p;};class Student : public Person{public:virtual void fun1(){cout << "Student::fun1()" << endl;}virtual void fun2(){cout << "Student::fun2()" << endl;}virtual void fun3(){cout << "Student::fun3()" << endl;}virtual void fun4(){cout << "Student::fun4()" << endl;} int _s;};

我们可以发现,子类的fun1和fun2与父类构成多态。可是fun3和fun4并没有构成多态,但是它们依然会被添加进子类的虚函数表。
在这里插入图片描述
所以虚函数表也会添加自身的虚函数。

🌺 6.4 多继承的虚函数表

那么如果是多继承呢?

以下代码实现了多继承,me继承了Base1和Base2。因此,Base1的虚函数表在m的前4/8个字节的位置。但是Base2的虚函数表可不在后面。所以要想知道Base2的虚函数表位置。我们需要m的地址在原有的基础上加一个Base1大小,这样就到达了Base2对象的首地址,再取前4/8个字节就是Base2的虚函数表。

class Base1{public:virtual void fun1(){cout << "Base::fun1()" << endl;}virtual void fun2(){cout << "Base::fun2()" << endl;} int _p;};class Base2{public:virtual void fun3(){cout << "Base2::fun3()" << endl;}virtual void fun4(){cout << "Base2::fun4()" << endl;}};class me :public Base1,public Base2{virtual void fun1(){cout << "me::fun1()" << endl;}virtual void fun2(){cout << "me::fun2()" << endl;}virtual void fun5(){cout << "me::fun5()" << endl;}virtual void fun6(){cout << "me::fun6()" << endl;}};typedef void(*VFPTR) ();void PrintVTable(VFPTR vTable[]){cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i+1, vTable[i]);VFPTR f = vTable[i];f();}cout << endl << endl;}int main(){me m;VFPTR * vTableb = (VFPTR*)(*(void**)&m);PrintVTable(vTableb);VFPTR* vTabled = (VFPTR*)(*(void**)((char*)&m+sizeof(Base1)));PrintVTable(vTabled);return 0;}

我们看看代码结果:
在这里插入图片描述
我们可以看到,当有一个类继承了多个类时,那么会产生多张虚函数表。而自己的虚函数(非重写) 将会被默认放在第一张函数表中。

💖 7.多态面试问答题

  1. 什么是多态?
    2答:不同的事物做同一行为产生不同的结果。

  2. 什么是重载、重写(覆盖)、重定义(隐藏)?
    答:重载要在同一作用域下,且函数名相同,但参数的顺序,个数,类型不同。
    重写是当基类和派生类有一模一样的虚函数时,子类虚函数表中的父类虚函数地址会被覆盖。
    重定义,从父类继承下来,且没有重写的就是重定义,重定义函数名,参数相同。

  3. 多态的实现原理?
    答:父类的所有虚函数都会存在虚函数表中,而虚函数表存储在常量区。当子类继承了父类时,也会继承父类的虚函数表,如果此时子类又符合重写要求的函数。则会去自己的虚函数表中替换掉父类的虚函数地址,换成自己的虚函数地址。

  4. inline函数可以是虚函数吗?
    答:可以,不过编译器就忽略inline属性,这个函数就不再是
    inline,因为虚函数要放到虚表中去。

  5. 静态成员可以是虚函数吗?
    答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

  6. 构造函数可以是虚函数吗?
    答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。

  7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
    可以,并且最好把基类的析构函数定义成虚函数,否则当子类和父类构成多态时。delete释放对象可能会导致内存泄漏,具体上面有讲解。

  8. 对象访问普通函数快还是虚函数更快?
    答:构成多态,普通函数快。不构成多态,一样快。

  9. 虚函数表是在什么阶段生成的,存在哪的?
    答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

  10. 什么是抽象类?抽象类的作用?
    答:抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系。

总结🥳:

💦💦如果有写的有什么不好的地方,希望大家指证出来,我会不断的改正自己的错误。💯💯如果感觉写的还可以,可以点赞三连一波哦~🍸🍸后续会持续为大家更新C/C++数据结构linux相关的知识

🔥🔥你们的支持是我最大的动力,希望在往后的日子里,我们大家一起进步!!!
🔥🔥

来源地址:https://blog.csdn.net/Lin5200000/article/details/128694663

--结束END--

本文标题: 【C++】面向对象---多态(万字详解)

本文链接: https://www.lsjlt.com/news/388476.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 【C++】面向对象---多态(万字详解)
           🔥🔥 欢迎来到小林的博客!!       🛰️博客主页:✈️小林爱敲代码       🛰️文章专栏:✈️小林的C++之...
    99+
    2023-09-01
    c++ java 开发语言
  • 详解Java面向对象编程之多态
    目录Java面向对象编程之多态一.对于多态的理解:二.多态的实现方法总结Java面向对象编程之多态 一.对于多态的理解: 通俗点理解,多态其实就是一词多义,就是一种方法的多种状态,即...
    99+
    2022-11-12
  • 详解Java面向对象中的继承与多态
    详解Java面向对象中的继承与多态?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Java 继承与多态的深入理解1、  什么是继承,继承的特点?子类继承父类的特征和...
    99+
    2023-05-31
    java 面向对象 ava
  • java面向对象之多态
    多态的简单理解:student类继承了Person类,则student类的对象既是student又是person类多态性具有两种展现的形式:1.方法的多态性:(1)方法的重载:同一个方法可以根据传入的参数的类型或者个数的不同实现不同的功能(...
    99+
    2019-03-28
    java入门 java 面向对象 多态
  • python 面向对象之多态
    多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。那么,多态的...
    99+
    2023-01-30
    面向对象 多态 python
  • java面向对象——多态的详细介绍
    一、概述多态是继封装、继承之后,面向对象的第三大特性。生活中,比如跑的动作,小猫、小狗和大象,跑起来是不一样的。再比如飞的动作,昆虫、鸟类和飞机,飞起来也是不一样的。可见,同一行为,通过不同的事物,可以体现出来的不同的形态。多态,描述的就是...
    99+
    2020-05-09
    java入门 java 面向对象 多态
  • PHP面向对象之封装,继承与多态详解
    在普通的编程中,没有涉及架构或者良好的设计,绝大多数都是使用的面向过程的方式。 当编程逐步深入后,就需要合理使用面向对象的知识来设计程序,而不是简单地脑海里有了思路就去写代码来实现,...
    99+
    2022-11-13
  • C++类和对象之多态详解
    目录多态基本概念和原理剖析多态案例1 计算器类纯虚函数和抽象类多态案例2 制作饮品虚析构和纯虚析构多态案例3 电脑组装多态基本概念和原理剖析 多态:多态是C++面向对象的三大特性之一...
    99+
    2022-11-12
  • Python 面向对象 组合-多态与多态
    面向对象-组合 1.什么是组合   组合指的是某一个对象拥有一个属性,该属性的值是另外一个类的对象 1 class Foo: 2 xxx = 111 3 4 class Bar: 5 yyy = 222 6...
    99+
    2023-01-30
    多态 组合 面向对象
  • 详细理解JAVA面向对象的封装,继承,多态,抽象
    目录类和对象的使用(面向对象思想落地的实现):子类对象实例化的全过程1.从结果上看:(继承性)2.从过程上来看:1.封装性2.继承性继承性的好处:3.多态性虚拟方法调用4.抽象性1....
    99+
    2022-11-12
  • C++编程面向对象入门全面详解
    目录类1. struct和class的区别2. explicit构造3. const和mutable4. 自引用5. static复数的实现6.成员函数重载7.运算符重载8.new9...
    99+
    2022-11-12
  • 详解Java面向对象之多态的原理与实现
    目录何为多态代码实现多态理解何为多态 定义: 多态是指不同的子类在继承父类后分别都重写覆盖了父类的方法,即父类同一个方法,在继承的子类中表现出不同的形式。系统在运行时(而非编译时),...
    99+
    2022-11-13
  • C++面向对象编程之析构详解
    目录1. 概述2. 详论2.1. 对象生命周期2.2. 不一定需要显式析构2.3. 析构的必要性3. 总结1. 概述 类的析构函数执行与构造函数相反的操作,当对象结束其生命周期,程序...
    99+
    2022-11-13
  • 面向对象封装、继承、多态
    一、面向对象封装   01. 封装 封装 是面向对象编程的一大特点 面向对象编程的 第一步 —— 将 属性 和 方法 封装 到一个抽象的 类 中 外界 使用 类 创建 对象,然后 让对象调用方法 对象方法的细节 都被 封装...
    99+
    2023-01-31
    面向对象 多态
  • Java全面分析面向对象之多态
    目录多态的理解向上转型向上转型的三种形式动态绑定和静态绑定方法的重写进一步认识和理解多态多态的优点多态的理解 什么是多态呢??从字面理解就是多种形态,也就是不同类实例化出来的对象调用...
    99+
    2022-11-13
  • C++面向对象实现万年历的示例代码
    目录引入Controller.hController.cppViewDate.hViewDate.cppModelDate.hModelDate.cppmain.cpp各功能测试结果...
    99+
    2022-11-13
  • python对象及面向对象技术详解
    本文实例讲述了python对象及面向对象技术。分享给大家供大家参考,具体如下: 1 先看一个例子. 本章将讲解这个例子程序: 文件: fileinfo.py: """Framework for gett...
    99+
    2022-06-04
    面向对象 详解 对象
  • Java 面向对象 之 多态实例2
    转载于 : http://www.verejava.com/id=16992846385655 public class Polymorphism3 {public static&...
    99+
    2023-06-02
  • python3:面向对象(多态和继承、方
    1、多态 同一个方法在不同的类中最终呈现出不同的效果,即为多态。 class Triangle: def __init__(self,width,height): self.width = width ...
    99+
    2023-01-31
    面向对象 多态
  • C++面向对象中构造函数使用详解
    目录构造函数作用构造函数特征构造函数种类默认构造函数编译器合成的默认构造函数手动定义的默认构造函数自定义带参数的构造函数拷贝构造函数合成拷贝构造函数自定义拷贝构造函数拷贝构造函数的调...
    99+
    2022-11-13
    C++构造函数的作用 C++构造函数的写法
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作