C++中的继承方式与菱形继承解析

 更新时间:2023年08月08日 11:35:23   作者:烟华  
这篇文章主要介绍了C++中的继承方式与菱形继承解析,继承是类和类之间的关系,是代码复用的重要手段,允许在保持原有类结构的基础上进行扩展,创建的新类与原有的类类似,只是多了几个成员变量和成员函数,需要的朋友可以参考下

一.什么是继承?

继承是类和类之间的关系,是代码复用的重要手段,允许在保持原有类结构的基础上进行扩展,创建的新类与原有的类类似,只是多了几个成员变量和成员函数。

二.继承的方式

class 派生类名 :继承方式 基类名 {};

继承的方式有以下三种:

在这里插入图片描述

需要注意的是:

1.若不表明是以何种方式继承,使用关键字class时默认是私有继承,使用struct关键字时默认公有继承

2.上面的不可访问是指派生类对象不管是在类里还是类外都不能不访问

三.基类和派生类对象赋值转换

  1. 在public继承的情况下,派生类对象可以赋值给基类对象或基类指针或基类的引用,因为这里它是把一个派生类的对象看成是一个基类的对象,把派生类多的那一部分进行切片操作。 因此当一个基类的指针指向一个派生类的对象时,该指针只能访问基类定义的函数。(多态与此相反。如果基类的此函数声明为virtual类型,那么基类指针指向子类对象,该指针在调用该函数时会调用子类的函数)
  2. 当父类的对象赋值给派生类的指针时,这是十分危险的,虽然也能通过强制类型转换来进行赋值,但有可能出现越界的问题。

四.派生类的默认成员函数

构造函数

(1)派生类的构造函数需要调用基类的构造函数来初始化基类的那一部分成员。

(2)如果基类没有默认的构造函数而是自己写的构造函数,则必须在派生类的构造函数初始化列表阶段显 示调用,如果基类用的是默认构造函数,编译器会自动调用。

拷贝构造

(1)派生类的拷贝构造函数需要调用基类的拷贝构造函数完成基类的拷贝初始化

(2)默认拷贝构造会自动调用父类的拷贝构造。显式定义子类的拷贝构造,编译器默认调用的父类构造函数

赋值运算符重载

(1)派生类的operator=需要调用基类的operator=完成基类的赋值

(2)默认生成的赋值运算符会自动调用父类的赋值运算符显式定义子类的赋值运算符, 编译器不会自动调用父类的赋值运算符

析构函数

(1)派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。派生类对象析构清理先调用派生类的析构再调用基类的析构

(2)父类与子类析构构成同名隐藏:编译器底层修改的析构函数的名字,为了支持多态

(3)父类析构在任何情况下编译器都会自动调用,不需要显式调用

五.同名隐藏(重定义)的问题

"同名隐藏“:在基类和派生类中,如果派生类的函数和基类的函数同名,就会有同名隐藏。

如果派生类和基类中有同名成员,子类成员将屏蔽父类对同名函数的直接访问。(注意不要与函数重载混淆)

在这里插入图片描述

如果发生同名隐藏,那么通过对象来调用同名函数时,到底是调用子类还是父类的函数,这个是由指针的类型决定,而不是指针执行的对象的类型决定的。

解决同名隐藏问题:通过作用域限定符来访问 (基类::基类成员)

六.继承性

1.友元关系不能继承,基类友元不能访问子类私有和保护成员

2.父类定义static静态成员,则整个继承体系里面只有一个这样的成员

七.菱形继承

在谈菱形继承之前,需要先了解单继承和多继承:

  • 单继承是一个子类只有一个直接父类时称这个继承关系是单继承
  • 一个子类有两个或两个以上的直接父类时称这个继承关系为多继承 继承中子类中成员的排列次序与继承次序有关 (也就是说先继承的类,类中成员的地址越靠下)

菱形继承:两个子类同时继承一个父类,而又有子类同时继承这两个子类

缺点:导致数据冗余和二义性的问题

解决方法:

  1. 对造成二义性的属性使用域访问限定符,这样从本质上并没有解决二义性
  2. 想要解决二义性和数据冗余,这就需要用到虚拟继承。属性只会生成一份,要调用它需要使用虚基表指针,虚基表指针指向虚基表,虚基表中存在偏移量,通过偏移量就可以找到共有的那个属性。

虚拟继承

一般不会实现虚拟继承,一般是为了解决菱形继承中的二义性

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++中的继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++中set/multiset与map/multimap的使用详解

    C++中set/multiset与map/multimap的使用详解

    这篇文章主要为大家详细介绍了C++中set/multiset与map/multimap的使用,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以参考一下
    2023-02-02
  • 详解C++ 指针与二维数组名

    详解C++ 指针与二维数组名

    和一维数组类似,C++ 将二维数组名解释为其第一个元素的地址,而二维数组的第一个元素为一维数组,下面详细总结下二维数组名的性质,需要的朋友可以参考下
    2022-09-09
  • 详解C语言之文件操作下)

    详解C语言之文件操作下)

    这篇文章主要介绍了关于C语言文件操作方法的相关资料,小编觉得这篇文章写的还不错,需要的朋友可以参考下,希望能够给你带来帮助
    2021-11-11
  • C/C++ 控制台等待指令解析

    C/C++ 控制台等待指令解析

    这篇文章主要介绍了C/C++ 控制台等待指令解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • 《C++ Primer》隐式类类型转换学习整理

    《C++ Primer》隐式类类型转换学习整理

    在本篇文章里小编给大家整理的是关于《C++ Primer》隐式类类型转换学习笔记内容,需要的朋友们参考下。
    2020-02-02
  • C++判断矩形相交的方法

    C++判断矩形相交的方法

    这篇文章主要介绍了C++判断矩形相交的方法,涉及C++针对平面坐标数学运算的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • C++实现简易通讯录功能

    C++实现简易通讯录功能

    这篇文章主要为大家详细介绍了C++实现简易通讯录功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • VC小技巧汇总之对话框技巧

    VC小技巧汇总之对话框技巧

    这篇文章主要介绍了VC小技巧汇总之对话框技巧,非常实用!对于进行VC开发有一定的参考借鉴价值,需要的朋友可以参考下
    2014-07-07
  • C++中的对象指针总结

    C++中的对象指针总结

    以下是对C++中的对象指针进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-10-10
  • 探讨C++中数组名与指针的用法比较分析

    探讨C++中数组名与指针的用法比较分析

    本篇文章是对C++中数组名与指针用法的比较进行了详细的分析介绍,需要的朋友参考下
    2013-05-05

最新评论