六、C++基础系列笔记——继承和多态(一)

1.继承的本质和原理

总结:
①外部只能访问对象的public成员,无法访问对象的private和protected成员(可通过对象的set(),get()方法访问非public成员)。
②在继承结构中,派生类可以从基类中继承过来private的成员,但是派生类却无法直接访问该成员。
③protected与private的区别?
如果只有一个类中,没有继承关系时,两者体现不出区别。
当有继承关系时:
在基类中定义的成员,我们想让派生类可以访问,但是外部无法访问,就将其定义为基类的protected成员;(自家人可访问,外人碰不到
在基类中定义的成员,我们想让派生类和外部都无法访问,就将其定义为基类的private成员;(老祖宗自己珍藏的秘方,除了他自己谁也看不到摸不着

问:
派生类默认的继承方式是什么?要看派生类是怎么定义的
class定义的派生类,默认为private(私有继承)
struct定义的派生类,默认为public(公有继承)

访问权限表:
在这里插入图片描述

2.派生类的构造过程(创建/诞生的过程)

问:派生类是怎么初始化从基类继承来的成员变量?
答:调用基类的相应的构造函数(为什么是相应的构造函数呢,答:构造函数可以重载)
基类的成员(变量、方法),派生类都可以继承,除了基类的构造函数和析构函数。
派生类构造时,所继承基类的成员初始化调用基类的构造函数;派生类自己的成员初始化,调用派生类自己的构造函数。

派生类的构造函数和析构函数负责初始化和清理派生类部分;
问:派生类从基类继承来的成员的初始化和清理由谁负责呢?
由基类的构造函数和析构函数负责

派生类对象的构造和析构的过程:
①派生类调用基类的构造函数初始化基类部分成员(从基类继承的成员)
②派生类调用自己的构造函数初始化派生类自己特有的成员。
…派生类对象的作用域到期了
③调用派生类的析构函数,释放派生类成员可能占用的外部资源(堆内存文件)
④调用基类的析构函数,释放从基类继承的成员可能占用的外部资源(堆内存)
总结:先构造的后析构,后构造的先析构。

3.重载、覆盖、隐藏

①重载:一组函数要重载,必须在同一个作用域中,并且满足函数名字相同,参数列表不同的条件。
②覆盖:覆盖即重写,是指在继承关系中,基类有一个虚函数,然后派生类有一个返回值、函数名、参数列表都相同的函数(系统自动声明为虚函数),
那么派生类的同名函数就会覆盖基类的同名虚函数。其本质是:派生类生成的虚函数表中的原先基类的同名虚函数地址被替换成派生类的同名虚函数的地址。
③隐藏的关系(作用域的隐藏,在没有定义虚函数的情况下):在继承的结构中,派生类的同名成员,把基类的同名成员给隐藏掉了。
(外界访问时优先访问派生类中的同名成员,就访问不到基类的同名成员了)
如果非要访问只要声明作用域就行,举例:Derive d; d.show();d.Base::show();
④对象转换
总结:类型从下到上的转换总是成立的,这里的下指的是派生类,上指的是基类。
从存储空间的角度来看:派生类所占据的内存总是比基类大,范围大的可以给范围小的赋值,反之就不行(会造成内存的非法访问)。
指针定义时的类型是什么,指针解引用时访问的地址空间就有多大(指针解引用受指针类型的限制)
所以:如果指针定义时指向的地址空间比实际上指针访问的空间大,就会产生非法访问。(你给指针传的值是什么,实际就是访问什么

4.静态绑定和动态绑定

①静态(编译时)绑定(函数调用):在编译时就确定了要调用函数的地址。(汇编指令call的是具体的被调用函数的地址)
②动态(运行时)绑定(函数调用):动态绑定是针对存在虚函数的情况而言的,编译时系统的call指令是从寄存器中读取地址,而寄存器中的地址是不确定的,在运行时才确定要调用函数的地址。(汇编:call eax)
总结:
在编译时如果发现类里面的函数是普通函数就进行静态绑定;如果发现类里的函数是虚函数,就进行动态绑定。
如:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.虚函数、vfptr和vftable

总结一:

如果一个类定义了虚函数,那么编译阶段编译器给这个类类型产生了一个唯一的Vftable虚函数表,虚函数表中主要存储的内容就是RTTI指针和虚函数的地址。当程序运行时,每一张虚函数表都会加载到内存的.rodata区(只读数据区)
RTTI:运行时数据类型
在这里插入图片描述

总结二:

如果一个类里面定义了虚函数,那么这个类定义的对象,其运行时的内存空间开始部分,多存储一个Vfptr虚函数指针,指向这个类的虚函数表Vftable(该指针四个字节大小)。
一个类型定义的n个对象,它们的Vfptr指向的都是同一张虚函数表。

总结三:

一个类里面的虚函数个数不影响对象的内存大小(都只多了一个Vfptr虚函数指针,因为所有的虚函数地址都存储在同一张表中),影响的是虚函数表的大小。

总结四:

如果派生类中的方法和基类继承而来的方法,返回值、函数名、参数列表(主要指参数类型和个数)都相同,而且基类的方法是Virtual虚函数,那么派生类的这个方法,自动处理成虚函数。(此时这两个类的关系就是覆盖关系)
覆盖关系的意义:派生类中与基类同名的虚函数地址会替代派生类的虚函数表中基类虚函数的地址。
在这里插入图片描述

6.虚析构函数

问题一:哪些函数不能实现虚函数?

1、构造函数:
构造函数中调用的任何函数都是静态绑定。
(只有调用了构造函数,对象才被创建出来)
2、Static静态成员方法(全局):连对象都不需要

问题二:是不是虚函数的调用一定是动态绑定?

答:肯定不是
原因:1.在构造函数中调用虚函数也是静态绑定。
2.如果不是通过指针或引用调用虚函数,也是静态绑定。
(注:不是通过指针或引用调用函数,那就是通过对象调用函数
啦,动态绑定的最终也是绑定到具体对象,现在都已经确定对象
了,肯定是编译时就明确具体调用的函数地址了。)

问题三:什么是虚函数依赖?

1、虚函数会产生地址,并存储在Vftable当中
2、对象必须存在(没有对象我们就无法使用虚函数了)
(Vfptr—>Vftable—>虚函数地址)

问题四:存在虚析构函数吗?

答:存在,析构函数调用时对象是存在的

问题五:什么时候基类的析构函数必须定义成虚函数?

答:基类的指针(引用)指向堆上new出来的派生类对象的时候,
delete pb(基类的指针),它调用析构函数必须发生动态绑定,否
则会导致派生类的析构函数无法调用。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西里小诸葛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值