目录

多态的概念

概念

多态的定义及实现

多态的构成条件

虚函数

虚函数的重写

虚函数重写的两个例外:

C++11 override 和 final

重载、覆盖(重写)、隐藏(重定义)的对比

抽象类

概念

接口继承和实现继承

多态的原理

虚函数表

多态的原理​

动态绑定与静态绑定

单继承和多继承关系的虚函数表

单继承中的虚函数表

多继承中的虚函数表

  1. 多态的概念

    1. 概念

      多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

  2. 多态的定义及实现

    1. 多态的构成条件

      多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
      那么在继承中要构成多态还有两个条件:

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

      虚函数:即被virtual修饰的类成员函数称为虚函数。

    3. 虚函数的重写

      虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

      class Person {
      public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
      };class Student : public Person {
      public:virtual void BuyTicket() { cout << "买票-半价" << endl; }/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用*//*void BuyTicket() { cout << "买票-半价" << endl; }*/
      };void Func(Person& p)
      { p.BuyTicket(); }int main()
      {Person ps;Student st;Func(ps);Func(st);return 0;
      }
      
    4. 虚函数重写的两个例外:

      1. 协变(基类与派生类虚函数返回值类型不同)派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

        class A{};
        class B : public A {};class Person {
        public:virtual A* f() {return new A;}
        };class Student : public Person {
        public:virtual B* f() {return new B;}
        };
        
      2. 析构函数的重写(基类与派生类析构函数的名字不同)
        如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

        class Person {
        public:virtual ~Person() {cout << "~Person()" << endl;}
        };
        class Student : public Person {
        public:virtual ~Student() { cout << "~Student()" << endl; }
        };// 只有派生类Student的析构函数重写了Person的析构函数,下面的
        //delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对
        //象正确的调用析构函数。
        int main()
        {Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
        }
        

    5. C++11 override 和 final

      1. final:修饰虚函数,表示该虚函数不能再被继承

        class Car
        {
        public:virtual void Drive() final {}
        };class Benz :public Car
        {
        public:virtual void Drive() {cout << "Benz-舒适" << endl;}
        };
        
      2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
        class Car{
        public:virtual void Drive(){}
        };class Benz :public Car {
        public:virtual void Drive() override {cout << "Benz-舒适" << endl;}
        };
        
    6. 重载、覆盖(重写)、隐藏(重定义)的对比

  3. 抽象类

    1. 概念

      在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

    2. 接口继承和实现继承

      普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

  4. 多态的原理

    1. 虚函数表

      // 这里常考一道笔试题:sizeof(Base)是多少?
      class Base
      {
      public:virtual void Func1(){cout << "Func1()" << endl;}private:int _b = 1;
      };
      

      通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。

      // 针对上面的代码我们做出以下改造
      // 1.我们增加一个派生类Derive去继承Base
      // 2.Derive中重写Func1
      // 3.Base再增加一个虚函数Func2和一个普通函数Func3
      class Base
      {
      public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}private:int _b = 1;
      };class Derive : public Base
      {
      public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
      private:int _d = 2;
      };int main()
      {Base b;Derive d;return 0;
      }
      

      通过观察和测试,我们发现了以下几点问题:

      1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
              2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
              3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。
              4. 虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr。
              5. 总结一下派生类的虚表生成:
                      a.先将基类中的虚表内容拷贝一份到派生类虚表中
                      b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
                      c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

      典型面试题

      虚函数存在哪的?虚表存在哪的?
      答:虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外
      对象中存的不是虚表,存的是虚表指针

    2. 多态的原理

      class Person {
      public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
      };
      class Student : public Person {
      public:virtual void BuyTicket() { cout << "买票-半价" << endl; }
      };void Func(Person& p)
      {p.BuyTicket();
      }int main()
      {Person Mike;Func(Mike);Student Johnson;Func(Johnson);system("pause");return 0;
      }
      
      1. 观察下图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。
      2. 观察下图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket。
      3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
      4. 反过来思考我们要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数。
      5. 满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中去找的。不满足多态的函数调用时编译时确认好的。
    3. 动态绑定与静态绑定

      1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
      2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。
  5. 单继承和多继承关系的虚函数表

    1. 单继承中的虚函数表

      class Base {
      public :virtual void func1() { cout<<"Base::func1" <<endl;}virtual void func2() {cout<<"Base::func2" <<endl;}
      private :int a;
      };class Derive :public Base {
      public :virtual void func1() {cout<<"Derive::func1" <<endl;}virtual void func3() {cout<<"Derive::func3" <<endl;}virtual void func4() {cout<<"Derive::func4" <<endl;}
      private :int b;
      };
      

      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;
      }int main()
      {Base b;Derive d;// 思路:取出b、d对象的头4bytes,就是虚表的指针,前面我们说了虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr// 1.先取b的地址,强转成一个int*的指针// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针// 3.再强转成VFPTR*,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。// 4.虚表指针传递给PrintVTable进行打印虚表// 5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的-生成-清理解决方案,再编译就好了。VFPTR* vTableb = (VFPTR*)(*(int*)&b);PrintVTable(vTableb);VFPTR* vTabled = (VFPTR*)(*(int*)&d);PrintVTable(vTabled);return 0;
      }
      

    2. 多继承中的虚函数表

      多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

c++之多态性,什么是多态?如何实现?相关推荐

  1. tcga癌症亚型获取_将亚型多态性与通用多态性相关联的危险

    tcga癌症亚型获取 Java 5已将通用多态性引入Java生态系统. 即使我们都知道由于泛型类型擦除及其后果而引起的无数警告,这还是对Java语言的重要补充. 通用多态性(也称为参数多态性 )通常与 ...

  2. 将亚型多态性与通用多态性相关联的危险

    Java 5已将通用多态性引入Java生态系统. 即使我们都知道由于泛型类型擦除及其后果而引起的无数警告,这对Java语言还是一个很大的补充. 通用多态性(也称为参数多态性 )通常与可能预先存在的亚型 ...

  3. 解耦 多态性 java_Java的多态浅谈,Java多态浅谈网站安全分享!

    Java的多态浅谈概述Java的四大基本特性:抽象,封装,继承和多态.其中,抽象,封装,继承可以说多态的基础,而多态是封装,继承的具体表现.如果非要用专业术语来描述什么是多态的话 多态是指程序中定义的 ...

  4. 设计代码说明什么是多态性?如何实现多态?(代码中要写注释解释)_狗屎一样的代码!快,重构我...

    关注后你就是我的人了 重构不止是代码整理,它提供了一种高效且受控的代码整理技术. (一)重构原则 1.何谓重构 对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修 ...

  5. 设计代码说明什么是多态性?如何实现多态?(代码中要写注释解释)_狗屎一样的代码!快,重构我!...

    狗屎一样的代码如何重构? 重构不止是代码整理,它提供了一种高效且受控的代码整理技术. (一)重构原则 1.何谓重构 对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低 ...

  6. Python类的多态和多态性

    很多人喜欢将多态与多态性二者混为一谈,然后百思不得其解,其实只要分开看,就会很明朗. 一.多态 多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承) 序列数据类型有多种形态 ...

  7. python中对多态和多态性的理解

    一.多态 多态是指一类事物有多种形态,比如动物类,可以有猫,狗,猪等等.(一个抽象类有多个子类,因而多态的概念依赖于继承) import abc class Animal(metaclass=abc. ...

  8. C++多态的实现原理

    1. 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数. 2. 存在虚函数的类都有一个一维的虚函数表叫做虚表.类的对象有一个指向虚表开始的虚指针.虚表是和类对应的,虚表指针是和对象 ...

  9. python全栈脱产第25天------组合、多态、封装、property装饰器

    一.组合 定义:组合指的是一个对象属性的值是另一个类的对象 用途:通过某一对象添加属性(属性的值时另一个类的对象)的方式,可以间接地将两个类关联/整合/组合到一起,从而减少代码冗余 用法: class ...

  10. [.net 面向对象编程基础] (13) 面向对象三大特性——多态

    [.net 面向对象编程基础] (13) 面向对象三大特性--多态 前面两节,我们了解了面向对象的的封装和继承特性,面向对象还有一大特性就是多态.比起前面的封装和继承,多态这个概念不是那么好理解.我们 ...

最新文章

  1. [转帖]Linux中的15个基本‘ls’命令示例
  2. python学习笔记1.1
  3. 高中数学对计算机,高中数学在计算机中的运用及思考.doc
  4. 2019牛客暑期多校训练营(第三场)F - Planting Trees (枚举 + 单调队列)
  5. Hadoop的TextInputFormat的作用,如何自定义实现的
  6. LSTM原理解读汇总
  7. Java集合面试问题
  8. [蓝桥杯]试题 基础练习 芯片测试
  9. python写的代码怎么发给别人_用python 代码 怎么给别人发邮件的两种写法
  10. c语言实现16位定点数乘法,c语言 fixed-point 定点数 运算
  11. WPF中调用Process打开网页或本地文件夹
  12. Adobe flash cs4的一个序列号
  13. java编程规范(chinasoft内部考试),答案并非全部正确。仅供参考
  14. 微信小程序开发调用接口
  15. sp3485调试:sp3485-TTL转max485模块-TTL转usb模块电脑pc
  16. CRM各行业解决方案
  17. 跑跑卡丁车rush服务器维护,跑跑卡丁车RUSH游戏官方-跑跑卡丁车RUSH+手游官网预约v1.0.8 - 逗游网...
  18. android 网格格式,android绘图网格线
  19. 美丽诗文背诵-未完待续
  20. 基于支持向量机的谐波分析研究与实现

热门文章

  1. 数字电路之组合逻辑电路
  2. 再获认可!人大金仓金融信创优秀解决方案推进我国数字金融高质量发展
  3. 10、刷牛客网SQL题(四)
  4. idea前进和撤销快捷键
  5. 【密码学探秘】EVM链和并行执行交易
  6. 全国首批城市级5G车联网应用项目落地!
  7. java自定义数组_Java自定义数组
  8. 服务器系统盘划分,服务器硬盘分区的划分c区系统区.doc
  9. 计算机网络:BGP路由协议
  10. HtmlUnit使用体会