继承与多态

一、继承

继承
1.本质:实现代码复用
2.继承与派生
 基类Base   派生类Derive
 基类Base派生了派生类Derive 派生类Derive继承了基类Base
 Derive is a kind of Base 
 比如学生是人 但是人不一定是学生
 继承方式:public protected private
 3.继承的内容
   基类的成员变量 成员方法 作用域
 4.不被继承的东西
   构造函数 析构函数 友元关系
 5.构造顺序
 Derive d(10); //先调用基类Base的构造函数构造基类部分,再进行派生类的成员对象的构造,最后调用派生类的构造函数
 6.析构顺序
    首先派生类析构,再者派生类的成员对象析构,最后基类析构
 7.赋值兼容规则
   基类对象可以赋值给派生类对象
    原因:由于派生类对象的内存布局中包含基类部分,所以可以赋值且不会越界但是派生类对象赋值给基类对象时,基类对象内存中并没有派生类部分 会导致越界
   基类的指针或者引用可以指向或者引用派生类对象
     原因:对于基类指针指向派生类对象 派生类的内存布局包括基类部分 指针解引用指向派生类内存部分的基类部分; 对于派生类指针指向基类对象 基类对象内存中并没有派生类部分 指针解引用会导致越界
 8.函数重载条件
   同一作用域 同一函数名 不同参数列表
 9.函数隐藏
   不同作用域 有继承关系 同函数名
   主要体现在派生类中的同名函数隐藏了基类中的同名函数
 10.函数覆盖(重写)
   不同作用域 有继承关系  基类中有虚函数
   主要体现在派生类中的同名函数覆盖了基类中同名虚函数
  重写的思路:
   基类中存在虚函数 则就存在虚函数指针vfptr指向虚表vftable
   首先将这些虚函数的入口地址记录到vftable中
      如果派生类有重写与基类同名虚函数  那么将此函数地址写入到虚表中 覆盖原先基类的地址
      如果派生类没有重写基类同名虚函数 那么将不做任何改变
   最后派生类和基类共享虚表,这就是重写的实质。
 
11.虚继承
  引入虚继承之后 最终派生类对象的内存布局只有一份基类内容 避免了二义性
  如下图所示

继承与多态
12.纯虚函数
   只实现函数声明 由具体的派生类实现自己对应的类型

二、多态

1.多态本质
  同一接口 不同实现
 2.分类 主要是两种多态发生的时期不同
  静多态
   在编译期间发生的静态绑定 call eax 拿到函数的入口地址
  动多态
   在运行期间发生的动态绑定
   在运行期间call eax找不到函数入口地址 借助虚函数机制找到函数入口地址
   如何发生动多态呢? 
   指针或引用调用虚函数
   带有virual关键字的虚函数的地址全部都存放在虚表存放在.rodata段
   在运行期间 内存中只加载数据和指令 刚好虚函数表存放于内存中 
   那么就可以通过对象中的vfptr找到虚函数表 进而找到此函数的入口地址
 2.虚函数
   (1)定义:在成员函数前面加virtual 此函数就成为了虚函数
            拥有虚函树的对象的内存布局里多了一个vfptr虚函数指针,指向一个vftable表 
            vftable的布局如下图所示
   (2)成为虚函数的条件
     能被对象调用  能取地址

虚表的内存布局:

继承与多态


   (3)哪些函数可以成为虚函数
      首先只有类的成员函数才有成为虚函数的可能 因为成为虚函数的条件是要依赖于对象
      所以普通函数不能成为虚函数-->不依赖于对象调用
      构造函数不能成为虚函数 
         由于构造函数的功能是构造对象  而虚函数需要对象调用 这时对象还没有生成
      静态成员函数 
      虚函数依靠虚函数指针vfptr和虚函数表vtable来执行;vptr是一个指针,是对象的组成部分,
      在类的构造函数中创建生成,并且只能用this指针来访问它,
      vfptr指向保存虚函数地址的vtable.虚函数的调用关系:this -> vfptr -> vftable ->virtual function,
      其解释为this指针指向对象 然后在对象找到这个对象所拥有的vfptr,从而找到部分vfptr所指向的虚函数表,
      从表里找到虚函数的入口地址。
      对于静态成员函数,它没有this指针,所以无法访问vfptr. 这就是为何static函数不能为virtual.
      内联函数
        不能取地址 内联函数在编译期间就展开 不发生普通函数调用过程 所以没有函数地址