• C++-多态,纯虚函数,抽象类,工厂模式,虚析构函数(day10)


      一、多态(更多见day9)

    1、多态条件

    1)多态特性除了要在基类中声明虚函数,并在子类中形成有效的覆盖,还必须通过指针或者引用来调用虚函数,才能表现出来,直接通过对象无法进行多态调用。

    2)调用虚函数的指针也可以是this指针,只要它是一个指向子类对象的基类指针,同样可以表现出多态的特性。

    class Base{
    
    public:
    
      virtual int cal(int x,int y){
    
        retunr x+y;
      }
    
      //d.foo()-->foo(&d)
    
      //void foo(Base* this)
    
      //Base* this =&d;
    
      void foo(void){
    
        cout<<cal(100,200)<<endl; 
      }
    
    };
    
    
    
    class Derived:public Base{
    
    public:
    
      int cal(int a,int b){
    
        return a*b;
      }
    
    }
    
    
    
    int main(void){
    
      Derived d;
    
      Base b=d;
    
      cout<<b.cal(100,200)<<endl;//调用的是基类中的版本,不会形成多态调用
    
      d.foo();//子类对象中是没有foo函数的,但是由于传进去的this虽然是Base*,但是指向的是Derived*类型的&d,所以在调用时,相当于this->cal(100,200),故形成多态调用
    
      return 0;
    
    }


    二、纯虚函数、抽象类、纯抽象类

    1、纯虚函数

     

    virtual void draw(void)=0;//纯虚函数

    纯虚函数的一般形式如下:

    virtual 返回类型 函数名(参数列表)[const]=0;

    2、抽象类

      1)如果一个类中包含了纯虚函数,那么它就是抽象类。例如,前述的Shape类就是一个抽象类,类并不包含具体的行为。所以编译器不允许抽象类实例化对象。如果实例化,会出现下面的错误:

      2)另外,如果继承过来的基类具有纯虚函数,并且子类不做覆盖的话,那么子类也将变成抽象类。

      3)如果一个类中的所有成员函数(不包括构造函数,析构函数)都是纯虚函数,那么这个类就叫做纯抽象类。

    3)工厂模式举例

    Team1负责解析
    
     class PDFParse{
    
    public:
    
      void prase(const char* pdffile)//解析图形,文本,图片函数
    
      {
    
        OnCircle();
        OnRect();
        Ontext();
        OnImage();
        //。。。
      }
    
    private:
    
      virtual void Oncircle(void)=0;
    
      virtual void OnRect(void)=0;
    
      virtual void OnText(void)=0;
    
      virtual void OnImage(void)=0;
    
    };
    
    Team2负责绘图实现
    
    class PDFRender:public PDFParse{
    
    private:
    
      void OnCircle(void){
    
        ...
      }
    
      void OnRect(void){
    
        ...
      }
    
         void OnText(void){
    
        ...
      }
    
      void OnImage(void){
    
        ...
      };
    
    };
    
    
    
    int main(void){
    
      PDFRender render;
    
      render.parse("something.pdf");//通过this指针实现多态
    
      return 0;
    
    }

    4)多态实现的原理(了解)

      1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。  

      2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。  

      3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。  

      4:多态用虚函数来实现,结合动态绑定.  

      5:纯虚函数是虚函数再加上 = 0;  

      6:抽象类是指包括至少一个纯虚函数的类。

     实现原理:  

    (1)对于编译器来说,非虚函数的调用地址在在编译的时候就被绑定,这样的绑定称为早期绑定。如果是非虚函数,即使子类的指针或者引用向上造型到基类的指针或引用,调用同名函数时也只是调用基类中的函数,因为这在编译阶段就已经绑定好了,在执行时无法改变。

    (2)在(1)中如果要使用基类的指针调用子类的函数,就要使用带虚函数

    (3)在任何一个类中如果有一个虚函数,那么就会为这个类添加一个虚函数表指针(三级指针,函数指针一般为一级指针,虚表中的后半部分为虚函数指针数组,可知它为二级指针),指向虚函数表(简称虚表),如上图,foo(),虚表指针在对象中占四个字节大小,虚表不属于对象,而是通过虚表指针来取其中的内容。图中,基类中foo()函数为虚函数,子类继承之后也为虚函数,并且都存在虚表,并且覆盖了A类的foo()函数的起始地址,添加上自己类的地址。而bar函数没有被重写,则原封不动的继承该函数,起始地址也不变。

    (4)虚表指针vptr属于晚绑定,会根据实际指针指向的类型或引用的实际类型进行调用。每个对象的虚函数的调用都是通过虚函数来进行索引的,就像数组有起始地址和下标一样。所以虚表指针的正确初始化就显得十分重要。那么虚表指针是何时进行创建和初始化的呢?虚表指针其实是在构造函数中被创建和初始化的,(3)中也说了,虚表指针属于对象的一部分,占4个字节的内存大小,所以在构造函数中初创建和初始化显得理所当然。

    (5)在构造时,先要构造基类的基类子对象,编译器看到父类具有虚函数,就创建和初始化基类的虚表,当构造子类对象时,发现了子类的虚函数,就对基类需要覆盖的虚表进行覆盖,就如foo()函数被覆盖,但是bar()函数没有被覆盖。并且在子类中会有自身的虚表指针(区别于基类的虚表指针),这就是为什么通过指针或者引用调用时可以实现多态的原因。而直接使用对象调用时,不用虚表指针进行索引,直接调成员函数(通过基类的对象调用父类的函数(这个函数实现了虚函数,内部其实是this指针起了作用)除外)。


     三、虚析构函数

    1、引入

      一个指向子类对象的基类指针进行析构的时候,只能调用基类的析构函数,而无法调用子类的析构函数,前面学到的方法是把基类的指针做一个向下造型进行析构。实际中并不这样做,而是使用虚析构函数来解决。

    class Base{

    public:

      Base(void){

        cout<<"Base::Base()"<<endl;

      }

       virtual ~Base(void){

        cout<<"Base::~Base()"<<endl;

      }

    };

    class Derived:public Base{

    public:

      Derived(void){

        cout<<"Derived::Derived()"<<endl;

      }

      ~Derived(void){

        cout<<"Derived::~Derived()"<<endl;

      }

    };

    int main(void){

      Base* pb=new Derived;//如果不声明为虚析构函数,这种方式释放内存将有内存泄漏风险

      delete pb;

      return 0;

    }

      如果基类的析构函数为虚函数,那么子类的析构函数也是虚函数,可以对基类的析构函数进行有效覆盖。这时候再delete指向子类的基类指针,实际调用的是子类的析构函数,而子类的析构函数又会调用基类的析构函数,这样可以避免上述问题。


    四、练习--薪资计算

    员工属性:姓名,工号,职位级别,绩效工资,出勤率

    经理:绩效奖金(元/月)

    技术员:研发津贴(元/小时)

    销售员:提成比率(百分比)

    薪资=基本工资+绩效工资

    基本工资=职位级别额度*出勤率

     绩效工资:因职位不同而异

    普通员工:基本工资一半

    经理:绩效奖金*绩效因数(手动输入)

    技术员:研发津贴*工作小时数*进度因数(手动输入)

    销售员:销售额度(手动输入)*提成比例

    技术主管:(技术员绩效工资+经理的绩效工资)/2

    销售主管:(销售员绩效工资+经理绩效工资)/2

    结果:打印员工数据,输入必须输入的数据,计算薪资

  • 相关阅读:
    树莓派配置Jdk环境并设置开机启动jar
    缓存架构之路(一)缓存概述及应用
    并发编程学习历程(零)JMM内存模型
    并发编程学习历程(一)Synchronized
    深入Disruptor源码分析(一)入门指南
    JAVA Synchronized和Lock实现原理
    Vscode断点调试PHP
    php环境搭建
    github使用总结
    面试笔记
  • 原文地址:https://www.cnblogs.com/ptfe/p/11299869.html
Copyright © 2020-2023  润新知