下面我来分析几段继承的类跟大家总结一下前面所说的一些理论知识,这里解释了前面 提供默认构造函数的第二点哦,仔细看




仔细观察地址401090,这里先调用了子类的构造函数,但是原来子类里面并没有构造函数,这就是编译器给出的默认构造函数,需要通过这个构造函数,像一个踏板一样,跳到父类的构造函数去,这里就是前面所讲过的 提供默认构造函数的第二点知识。(情况是:父类有构造函数,但是子类没有)
(这里我一直在想,不是得先调用父类的构造函数吗,为什么这里先子类呢?因为你要发现是否有父类,你得先进入子类构造函数去看,先进子类构造函数去看,如果有,则跳到父类构造函数,没有则向下执行,因为这里是提供的默认构造函数,即空函数,所以的话,只有进门操作,执行操作的话直接没了,所以直接返回)
总结:
当CDervie类没有构造函数时,为了能够在CDervie类对象 产生时调用 成员对象 的构造函数,编译器同样会提供默认构造函数,以实现成员构造函数的调用。
但是,如果子类含有构造函数,而父类不存在构造函数,则编译器不会为父类提供默认的构造函数。在构造子类时,由于父类中没有虚表指针,也不存在构造祖先类的问题,因此添加默认构造函数没有任何意义。
汇编角度来看待构造顺序



根据上面汇编代码分析:
构造的顺序为:先构造父类(地址4010D5处),然后按声明的顺序构造成员对象(地址4010E7处)和初始化列表中的指定的成员(地址4010EF处),最后才是构造函数(地址4010F6处)。

有了这样的内存结构,不但可以使用指向子类对象的子类指针间接寻址到父类定义的成员,而且可以使用指向子类对象的父类指针间接寻址到父类定义的成员中。
在使用父类成员函数中,传递的this指针也可以是子类对象的首地址,因此,在父类中,可以根据以上内存结构将子类对象的首地址视为父类对象的首地址来进行数据操作,而且还不会出错。
由于父类对象的长度不超过子类对象,而子类对象只要派生新的数据,其长度即可超过父类,因此子类指针的寻址范围不小于父类指针,在使用子类指针访问父类时,如果访问的成员数据是父类对象所定义的,那么不会出错,如果访问的是子类派生的成员数据,则会造成访问越界。



父类中成员函数SetNumber 在子类中并没有被定义,但根据派生关系,子类中可以使用父类的共有函数。编译器是如何实现正确匹配呢?
如使用对象或对象的指针调用成员函数,编译器可根据对象所属作用域来使用“名称粉碎法”,以实现正确匹配。在成员函数中调用其他成员函数时,可匹配当前作用域。

父类数据成员被排列在最前端的目的是为了在添加派生类后方便子类使用父类中的成员类型,并且可以将子类指针当父类指针来使用。
这样一来,不管是操作子类对象还是父类对象,只要确认了对象的首地址,对父类成员数据的偏移量而言都是一样的。对子类的对象而言,使用父类指针或者子类指针都可以正确访问其父类数据。反之,如果使用一个子类对象的指针去访问父类对象,则存在越界访问的危险。
子类对象的指针访问父类对象存在的危险:

本文分析继承类相关知识。介绍编译器为子类提供默认构造函数的情况,如父类有构造函数而子类没有时。从汇编角度说明构造顺序,先父类,再成员对象和指定成员,最后构造函数。还阐述了子类、父类指针的寻址范围及成员函数匹配方式,以及子类指针访问父类对象的危险。
5648

被折叠的 条评论
为什么被折叠?



