C++ 继承和多态

目录

  1. 继承的本质和原理
  2. 派生类的构造过程
  3. 重载,覆盖,隐藏
  4. 静态绑定  动态绑定
  5. 多态的vfptr 和 vftable
  6. 抽象类设计原理
  7. 多重继承以及问题
  8. 虚基类 vbptr 和vbtable
  9. RTTI

 

 一 继承的本质和原理

  1.    基类给派生类提供统一的公共属性(成员方法,成员属性),通过继承达到代码复用
  2.      基类可以给派生类提供统一的虚函数接口,派生类通过函数重写,达到多态调用的目的

 

     继承方法,访问权限  public protected private  结论:

  1.    基类的private,无论哪种继承方式,在派生类都可以继承下来,但是无法访问
  2.    基类的protected,派生类可以访问,外部不能访问 
  3.    默认继承方式: 如果是class 定义的 默认为private  若是struct 则是public

    注意  :

 

class A {
 
    public:
      int ma;
    protected:
      int mb;
    private:
      int mc;
  };
  
  class B : private A {
  
    public:                                                                                                                                                
       int md;
   protected:
      int me;
    private:
      int mf;
  };
  
  class C : public B {
  
    public:
      int mg;
    protected:
      int mh;
    private:
      int mi;
  };

 

此时 类C中 对ma的访问不再是 public 而是要看直接基类的访问权限,因为B 对A 的继承是private 因此 在类C 中ma 访问就是private。

 

 二 派生类的构造过程

    必须调用基类的构造函数构造 并且优先构造基类

    构造与析构 的顺序

        1.先调用基类构造函数,构造从基类继承来的成员
        2.再调用派生类自己的构造函数,构造派生类自己的成员
        3.先调用派生类自己的析构函数,释放派生类自己占用的外部资源
        4.调用基类的析构函数,释放基类部分成员占用的外部资源

 

三 重载 隐藏 覆盖

   重载 : 在同一个作用域,函数名相同,参数列表不同,可构成重载函数

    隐藏 :如果派生类与基类的同名函数,当调用派生类的函数时,默认会调用派生类的函数,发生了派生类函数对基类的函数隐藏,如果调用基类的同名函数,需要加上基类作用域

    覆盖 :指的是基类和派生类的同名函数,不但函数名相同,参数列表也相同,返回值也相同。并且基类是virtual 虚函数,那么派生类的函数会被处理成虚函数,覆盖是指在函数表中的函数地址的覆盖

    

四  静态绑定  动态绑定

  

class A {

public :

     void show() {cout<<"A::show"<<endl;}
     
     virtual void show(int a) { cout<<"A::show(int)<<endl;}

private:
    int ma;

};


class B : public A {

  public :
    void show() {cout<< " B:show()"<<endl}
    void show(int a) { cout<<"B::show(int)<<endl;}
private: int mb; }; int main() { B b; A * p = &b; p ->show(); // 静态绑定 p->show(10); // 动态绑定  }

 

 

      那么 当调用show()时,此时p 是A* 类型 当调用方法show 时  就会去类A中 找相应的函数,在汇编层面就是call  0x1234 函数地址,并且在编译时期就确定了函数地址,因此发生了静态的绑定  

     当调用 show(10)时,先去类A 中找show(int )函数  但是A中的show(int) 是虚函数,那么就访问p指向的对象前4字节 vfptr,然后访问到vfptr指向的vftable,然后在vftable中找到虚函数地址在调用,并且只有在运行的时候才能获取到虚函数地址,因此是发生的是动态绑定

p 是A类型  *p 识别的是RTTI 类型,因为A 中有虚函数,因此获取vftable 获取RTTI类型,如果A 中没有虚函数,那么*p 识别的是编译期时期的类型

 

五  虚函数,vfptr,vftable

    如果基类中Base有虚函数,那么编译期在编译时期会为该类型生成一张虚函数表,虚函数表中放着虚函数的地址,还有RTTI 指针信息,这张表在运行时被加载到.rodata,并且只能读 不能写。

      那么用这个Base类实例化的对象,前4 字节会放着vfprt 虚函数指针,指向虚函表的地址

总结

1.类中出现虚函数,编译阶段会给该类型产生虚函数表,里面存放了虚函数的地址和RTTI指针。
2.有虚函数的类实例化的对象,内存都多了一个vfptr虚函数指针,指向该对象类型的虚函数表,同类型对象都有自己的vfptr,但是它们共享一个vftable。
3.派生类如果提供了同名覆盖函数,那么在虚函数表中,需要把基类继承来的虚函数地址给覆盖掉。
4.一个类里面出现多个虚函数,对象的内存只增长4个字节(vfptr),但是虚函数表的大小会逐渐增大。

 

多态:

静态时期的多态 : 模版,函数重载

运行时期的多态: 指基类指针指向派生类,调用派生类同名覆盖函数,基类指针指向哪个派生类,就会调用该派生类的同名覆盖函数,因为基类指针调用派生类的方法时,发生动态绑定,访问了该对象指向的虚函数表,并且取出对应的虚函数地址,因此指向谁调用谁的方法

 

   

六 ,抽象类   

class Animal
{
public:
    Animal(string name) :_name(name) {}
    virtual void bark() = 0; // 纯虚函数
protected:
    string _name;
};

理论上类中拥有纯虚函数的类就是抽象类,抽象类不能定义对象,但是可以定义指针和引用,

1.基类给所有派生类提供公共的属性(成员变量)和方法(成员函数),通过继承达到代码复用的目的。
2.基类可以给所有派生类提供统一的纯虚函数接口,派生类通过函数重写,达到多态调用的目的。

虚析构函数

class Base // 基类定义
{
public:
    Base(int data=10):_ptrb(new int(data))
    { cout << "Base()" << endl; }
    ~Base() { delete _ptrb; cout << "~Base()" << endl; }
protected:
    int *_ptrb;
};
class Derive : public Base // 派生类定义
{
public:
    Derive(int data=20):Base(data), _ptrd(new int(20))
    { cout << "Derive()" << endl; }
    ~Derive() { delete _ptrd; cout << "Derive()" << endl; }
private:
    int *_ptrd;
};
int main()
{
    Base *p = new Derive();
    delete p; // 只调用了Base的析构函数,没有调用Derive派生类的析构函数
    return 0;
}

在delete p 的时候,因为基类的析构函数时普通函数,因此只是发生了静态绑定,只调用了Base的析构函数,没有调用Derive 的析构函数 尤其这种在堆上的对象,因此将基类析构函数修改成虚析构函数,这样就是动态绑定,因此堆上的内存就能释放

   

七,多重继承

      一  虚基类

   

class A {

public:
private:
   int ma;

};


class B : virtual public A {

public:
private:
   int mb;

};

 

被虚继承的类就是虚基类  A为虚基类

B内存布局 

 

 

 

 

如果不被虚继承  那么B内存就是  A::ma , mb  那么被虚继承之后 将基类中成员搬到最后的位置,并且在原位置添加vbptr 指向虚基类表,在虚基类表(vbtable 记录了虚基类的偏移量)

当基类指针指向派生类时,指针指向的永远是基类成员的内存地址,因此存在虚基类 ,并且指向派生类对象在堆上,那么不同编译期析构可能出现内存泄漏

 

C++ 多重继承

    菱形继承 

 

 

 

这样类D 具有了两份类A 中 成员变量 存在问题  

解决办法  :

  所以从A继承的都要虚继承 virtual 这样 D中只具有A 中的一份数据 这样A的构造函数 也是在D中调用,不会再是B,C调用

 

posted @ 2020-08-28 15:11  睡觉lc  阅读(279)  评论(0编辑  收藏  举报