C++中的继承方式与菱形继承解析
作者:烟华
一.什么是继承?
继承是类和类之间的关系,是代码复用的重要手段,允许在保持原有类结构的基础上进行扩展,创建的新类与原有的类类似,只是多了几个成员变量和成员函数。
二.继承的方式
class 派生类名 :继承方式 基类名 {};
继承的方式有以下三种:
需要注意的是:
1.若不表明是以何种方式继承,使用关键字class时默认是私有继承,使用struct关键字时默认公有继承
2.上面的不可访问是指派生类对象不管是在类里还是类外都不能不访问
三.基类和派生类对象赋值转换
- 在public继承的情况下,派生类对象可以赋值给基类对象或基类指针或基类的引用,因为这里它是把一个派生类的对象看成是一个基类的对象,把派生类多的那一部分进行切片操作。 因此当一个基类的指针指向一个派生类的对象时,该指针只能访问基类定义的函数。(多态与此相反。如果基类的此函数声明为virtual类型,那么基类指针指向子类对象,该指针在调用该函数时会调用子类的函数)
- 当父类的对象赋值给派生类的指针时,这是十分危险的,虽然也能通过强制类型转换来进行赋值,但有可能出现越界的问题。
四.派生类的默认成员函数
构造函数
(1)派生类的构造函数需要调用基类的构造函数来初始化基类的那一部分成员。
(2)如果基类没有默认的构造函数而是自己写的构造函数,则必须在派生类的构造函数初始化列表阶段显 示调用,如果基类用的是默认构造函数,编译器会自动调用。
拷贝构造
(1)派生类的拷贝构造函数需要调用基类的拷贝构造函数完成基类的拷贝初始化
(2)默认拷贝构造会自动调用父类的拷贝构造。显式定义子类的拷贝构造,编译器默认调用的父类构造函数
赋值运算符重载
(1)派生类的operator=需要调用基类的operator=完成基类的赋值
(2)默认生成的赋值运算符会自动调用父类的赋值运算符显式定义子类的赋值运算符, 编译器不会自动调用父类的赋值运算符
析构函数
(1)派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。派生类对象析构清理先调用派生类的析构再调用基类的析构
(2)父类与子类析构构成同名隐藏:编译器底层修改的析构函数的名字,为了支持多态
(3)父类析构在任何情况下编译器都会自动调用,不需要显式调用
五.同名隐藏(重定义)的问题
"同名隐藏“:在基类和派生类中,如果派生类的函数和基类的函数同名,就会有同名隐藏。
如果派生类和基类中有同名成员,子类成员将屏蔽父类对同名函数的直接访问。(注意不要与函数重载混淆)
如果发生同名隐藏,那么通过对象来调用同名函数时,到底是调用子类还是父类的函数,这个是由指针的类型决定,而不是指针执行的对象的类型决定的。
解决同名隐藏问题:通过作用域限定符来访问 (基类::基类成员)
六.继承性
1.友元关系不能继承,基类友元不能访问子类私有和保护成员
2.父类定义static静态成员,则整个继承体系里面只有一个这样的成员
七.菱形继承
在谈菱形继承之前,需要先了解单继承和多继承:
- 单继承是一个子类只有一个直接父类时称这个继承关系是单继承
- 一个子类有两个或两个以上的直接父类时称这个继承关系为多继承 继承中子类中成员的排列次序与继承次序有关 (也就是说先继承的类,类中成员的地址越靠下)
菱形继承:两个子类同时继承一个父类,而又有子类同时继承这两个子类
缺点:导致数据冗余和二义性的问题
解决方法:
- 对造成二义性的属性使用域访问限定符,这样从本质上并没有解决二义性
- 想要解决二义性和数据冗余,这就需要用到虚拟继承。属性只会生成一份,要调用它需要使用虚基表指针,虚基表指针指向虚基表,虚基表中存在偏移量,通过偏移量就可以找到共有的那个属性。
虚拟继承
一般不会实现虚拟继承,一般是为了解决菱形继承中的二义性
class B { public: int _b; }; class D : virtual public B//虚拟继承 { public: int _d; }; int main() { cout << sizeof(D) << endl;//12 D d; d._b = 1; d._d = 2; return 0; }
在执行这段代码时,首先需要调用构造函数。从上图中看到,在构造对象期间,编译器给对象的前4个字节填充了数据。
而这一个过程只能在构造函数期间执行。
因此可以推断应该是编译器默认的构造函数所进行的操作。
cout << sizeof(D) << endl;改语句输出为12,比想象中的多了4个字节。
从反汇编中可以看对象时怎样赋值的。以给_b赋值为例
- 第一个mov语句:把对象的前4个字节的内容获取
- 第二个mov语句:我们可以把第一个mov语句中获取得d对象前4个字节的内容向下偏移4个字节里面的内容存到ecx中,具体过成如下图
- 第三个mov语句:把1赋值到d对象偏移量为8的位置
而个子类自己的_d赋值时一条赋值语句就解决了,而在给基类的成员赋值时通过三条语句来进行的。
看完虚拟继承,接下来我们把虚拟继承放到菱形虚拟继承中来看
class B { public: int _b; }; class C :virtual public B { public: int _c; }; class D : virtual public B { public: int _d; }; class A :public C, public D { public: int _a; }; int main() { cout << sizeof(A) << endl;//输出结果为24 system("pause"); return 0; }
为什么它的大小是24个字节呢?下面给出它的对象模型如下就明白了
到此这篇关于C++中的继承方式与菱形继承解析的文章就介绍到这了,更多相关C++中的继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!