目录
一、继承
1、继承的本质
代码复用
2、继承和派生
派生:从一个或多个以前定义的类(基类)产生新类的过程称为派生,这个新类称为派生类。派生的新类同时可以增加或者重新定义数据和操作,这就产生了类的层次性。
继承:继承是面向对象语言的一个重要机制,通过继承可以在一个一般类的基础上建立新类,被继承的类称为基类,在基类上建立的新类称为派生类。
继承和派生其实都是一回事,只是说法不同罢了,如:子类继承了父类,父类派生了子类。
3、特点:
1)派生类继承了基类的什么?
派生类继承了除基类构造、析构以外的所有成员。
2)继承方式:public、protect、private
3)访问限定符:
Public:在任意位置可见;
Protect:在本类类中和子类类中;
Private:在本类类中访问。
基类中不同访问限定符下的成员以不同的继承方式继承后在派生类中访问
基类 继承 | Public | Protect | Private |
Public | Public | Protect | 不可访问(在派生类中) |
Protect | Protect | Protect | 不可访问(在派生类中) |
Private | Private | Private | 不可访问(在派生类中) |
4、派生类的构造和析构顺序
构造时:基类的构造优先级大于派生类
析构时:先调用派生类的析构,再调用基类的析构
5、派生类对象
- 开辟内存:基类加上派生自己的内存
- 对内存空间初始化:
- 系统调用基类的构造;
- 系统调用派生类自己的构造。
6、类和类的关系
- 组合:has_a a part of (以成员变量的方式实现)
- 继承:is_a a kind of (私有继承不是is_a的关系,是has_a的关系)
- 代理
7、同名函数的关系
- 函数重载(重定义)overload
- 同名;
- 不同参数;
- 同作用域。
- 函数隐藏 overhide
派生类中隐藏了基类中所有同名函数
- 继承 不同作用域
- 同名
3.函数覆盖 override
派生类中同名同参的函数覆盖了基类中同名同参的函数
- 继承 不同作用域
- 虚函数
- 同名同参
7、基类的指针或者引用的相互指向或者引用
- 允许基类的指针指向或引用派生类对象;
- 不允许派生类指针指向或者引用基类对象。
Base *pb = &d;//基类指针可以指向派生类对象
Base &rb = d;//基类的引用能引用派生类对象
Derive *pd = b;//派生类的指针不能指向基类对象
Derive &rb = b;//派生类的引用不能引用基类对象
二、多态
1、多态的本质
接口复用 (同一接口,不同形态)
2、形式
①静多态(在编译期间确定函数的调用) 静态绑定 属于早绑定
优点:调用速度快、效率高
缺点:缺乏灵活性
函数重载和模板
②动多态(在运行期间确定函数的调用) 动态绑定 属于晚绑定
虚函数(指针调用动态)
早绑定和晚绑定的区别?
早绑定:早绑定也称静态绑定,是程序在编译时就确定调用的是哪个函数。
汇编指令是 call Base::func()
晚绑定:晚绑定也称动态绑定,是编译的时候才确定调用的是哪个函数。晚绑定基于继承实现,基类的指针(或引用)指向派生类的对象,通过指针(或听引用)访问虚函数时,会调用指针所指向的派生类的方法。
汇编指令如下:
mov ecx,dword ptr[p] 访问虚表指针,将虚表指针放在ecx寄存器中
mov eax,dword ptr[ecx] 将ecx(虚表指针)的值(虚函数表)放在eax寄存器中
call eax 调用函数
在运行过程中,确定了eax寄存器里的值,才能确定调用哪个函数。
3、虚函数
1)概念:如果派生类继承了有被vritual关键字修饰的函数的基类,被vritual修饰的函数称为虚函数。派生类可以重写该虚函数。如果派生类重写了该虚函数,那么派生类对象调用该方法时调用的就是派生类自己实现的方法。如果派生类没有重写该方法,则调用基类的方法。
2)虚函数表有什么?
Vftable(虚函数表)
-
- 编译阶段生成;
- 运行时放在.rodata段;(只能读不能写,和常量字符串放在同一段,生命期是整个应用程序的生命周期)
- 每个类拥有一张虚表。
Vfptr(虚函数指针)
优先级最高
虚表的写入时机:
-
- 构造函数第一次代码执行之前;
- 虚表的二次写入
3)成为虚函数的条件:
①可以取地址;
②可以依赖对象调用。
4)哪些能成为虚函数
- 普通全局函数 (不能)
- 普通的类成员方法 (可以)
- 静态的类成员方法 (不能,原因:不依赖对象调用)
- Inline函数 (不能,原因:不生成符号,没有地址,不能取地址)
- 构造函数 (不能,原因:不依赖对象调用)
- 析构函数 (可以)
5)虚函数的内存布局
博客:https://blog.youkuaiyun.com/like_that/article/details/89761772
4、纯虚函数
1)概念:纯虚函数是特殊的虚函数,基类中不能给出这个虚函数的实现方法,派生类必须给出该函数的实现。这种特殊的函数称为纯虚函数,有纯虚函数的类称为抽象类,抽象类不能实例化对象,但是可以定义抽象类的指针或引用,派生类必须重写方法后才能实例化对象。
2)例子:
- 抽象类(拥有纯虚函数的类称为抽象类)
- 不能实例化对象;
- 用引用或者指针。
接博客:https://blog.youkuaiyun.com/like_that/article/details/89739004
5、虚析构
基类指针指向派生类对象
作用:避免通过基类指针释放派生类对象,派生类中自己的资源没有办法释放。(可以解决资源泄露的问题)
问题:
- 析构函数能不能写成虚函数?
答:析构函数是可以写成虚函数的。
2.析构函数什么时候必须写成虚函数?
答:当使用基类的指针指向堆上的派生类时对象时,
如:Base *p = new Derive(); delete p;当通过delete来释放派生类对象的内存的时候,会导致派生类对象的析构函数无法调用,只调用了基类部分的析构函数,如果此时派生类的析构函数有释放额外的系统资源的代码,会直接造成资源泄露。
Delete p 编译器在编译的时候是静态绑定的,只调用基类的析构。写成虚析构函数,就会变为动态绑定,派生类提供了自定义的析构函数,那么虚函数表写的就是派生类析构函数的地址,此时派生类和基类的析构函数就都可以调用到,解决了资源泄露的问题。
6、多态的发生时机(什么时候发生动多态的调用)
1)指针调用虚函数;
2)对象要完整。