首页 > 编程语言 >【C++】C++中的继承,看这一篇就够了

【C++】C++中的继承,看这一篇就够了

时间:2024-10-19 14:47:07浏览次数:3  
标签:一篇 继承 成员 基类 就够 对象 菱形 C++ 派生类

【C++】C++中的继承,看这一篇就够了

一.继承的概念及定义

继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

继承定义

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类
在这里插入图片描述

继承关系和访问限定符

在这里插入图片描述

继承基类成员访问方式的变化

在这里插入图片描述
总结:

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected>private。
  4. **使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,**不过最好显示的写出继承方式。
  5. 实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强

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

  1. 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去
  2. 基类对象不能赋值给派生类对象
  3. 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的

在这里插入图片描述

三. 继承中的作用域

  1. 在继承体系中基类和派生类都有独立的作用域
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
  4. 注意在实际中在继承体系里面最好不要定义同名的成员
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
	 void fun()
 	{
 		cout << "func()" << endl;
 	}
};
class B : public A
{
public:
	 void fun(int i)
 	{
 		A::fun();
 		cout << "func(int i)->" <<i<<endl;
 	}
};

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

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=必须要调用基类的operator=完成基类的复制
  4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序
  5. 派生类对象初始化先调用基类构造再调派生类构造
  6. 派生类对象析构清理先调用派生类析构再调基类的析构
  7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系

在这里插入图片描述
在这里插入图片描述

五. 继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员*

六. 继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。

七. 复杂的菱形继承及菱形虚拟继承

  1. 单继承:一个子类只有一个直接父类时称这个继承关系为单继承
    在这里插入图片描述

  2. 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
    在这里插入图片描述

  3. 菱形继承:菱形继承是多继承的一种特殊情况。
    在这里插入图片描述

  4. 菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。
    在这里插入图片描述

  5. 下图是菱形继承的内存对象成员模型:这里可以看到数据冗余

在这里插入图片描述

八. 虚继承

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和
Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用

class Person
{
public :
 string _name ; // 姓名
};
class Student : virtual public Person
{
protected :
 int _num ; //学号
};
class Teacher : virtual public Person
{
protected :
 int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
 string _majorCourse ; // 主修课程
};
  1. 虚拟继承解决数据冗余和二义性的原理

下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下
面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指
向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量
可以找到下面的A。

在这里插入图片描述

在这里插入图片描述

  1. 下面是上面的Person关系菱形虚拟继承的原理解释
    在这里插入图片描述

九. 继承和组合

  1. 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称
    为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的
    内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很
    大的影响。派生类和基类间的依赖关系很强,耦合度高。
  2. 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象
    来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复
    用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
    组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被
    封装。
  3. 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有
    些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用
    继承,可以用组合,就用组合。

组合的构成:即在一个类里面定义另外一个类的对象

   // Tire和Car构成has-a的关系
   
   class Tire{
   protected:
       string _brand = "Michelin";  // 品牌
       size_t _size = 17;         // 尺寸
   
   };
   
   class Car{
   protected:
   string _colour = "白色"; // 颜色
   string _num = "陕ABIT00"; // 车牌号
    Tire _t; // 轮胎
   };

十. 笔试面试题

  1. 什么是菱形继承?菱形继承的问题是什么?

‌(1)菱形继承是指在类的继承关系中,存在两个或更多个直接或间接的基类,它们之间形成了一个类似菱形的结构。

(2)菱形继承带来的主要问题是‌数据冗余和二义性

  1. 什么是菱形虚拟继承?如何解决数据冗余和二义性的?

(1)菱形虚拟继承是一种特殊的继承方式,用于解决菱形继承中的数据冗余和二义性问题。‌

(2)菱形继承是指一个类同时从两个直接或间接的基类继承,而这些基类又共同继承自同一个基类,形成了一个菱形结构。这种情况下,可能会导致数据冗余和访问不明确(二义性)的问题。

(3)菱形虚拟继承通过在基类声明中使用virtual关键字来解决这些问题。当在基类中使用虚拟继承时,派生类中只会存储一份基类的数据成员,通过虚基表(Vtable)和虚基表指针来管理这些共享数据,从而避免了数据冗余。虚基表包含了虚基类的偏移量信息,通过这些信息可以正确地访问到共享的数据成员,解决了二义性问题‌

  1. 继承和组合的区别?什么时候用继承?什么时候用组合?
    (1)继承意味着子类是父类的一种特殊类型,继承了父类的所有特性。而组合则是一个类将另一个类作为自己的成员变量使用

‌使用场景方面:
(2)继承适用于以下情况‌:

当两个类具有明显的“is-a”关系时,即一个类是另一个类的具体化或特例时,使用继承。例如,如果有一个“动物”类,而“狗”是动物的一种,那么狗类应该继承自动物类‌。
当一个类需要使用另一个类的所有方法或属性时,继承也是一个好的选择。例如,如果一个类需要使用另一个类中的大量属性和方法,通过继承可以很方便地获取这些功能‌。

(3)组合适用于以下情况‌:

当两个类之间存在“has-a”关系时,即一个类作为另一个类的组件时,使用组合。例如,如果一个“汽车”类需要包含一个“发动机”对象,那么汽车类可以将发动机作为自己的成员变量‌。
当需要保证类的安全性,不破坏封装时,使用组合。组合可以让一个类作为另一个类的组件,而不必暴露其全部接口,从而保持了较高的封装性‌。

学习C++必看

标签:一篇,继承,成员,基类,就够,对象,菱形,C++,派生类
From: https://blog.csdn.net/2303_80737493/article/details/142864227

相关文章

  • 移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——13.map&&set(无习题)
    C++中的set和map容器详细总结1.概述C++标准模板库(STL)提供了多种关联容器,用于管理键值对和集合的数据。其中,set和map是最常用的两种关联容器。set用于存储唯一的元素集合,而map则用于存储键值对,其中每个键都是唯一的。它们都使用红黑树(自平衡二叉搜索树)作为底......
  • YU_C++算法学习笔记 · 枚举
    1.1枚举类问题·枚举是什么?枚举也叫穷举,是计算机解决问题最基本的策略。其方法是一一列举所有的可能性,根据题意要求进行合理的判断或计算,最终得到答案,本质上就是一种搜索算法基础的枚举就是人们常说的“暴力”求解。对于不同的问题,不可过分依赖“暴力”求解,应该根据具体的......
  • 《 C++ 修炼全景指南:十六 》玩转 C++ 特殊类:C++ 六种必备特殊类设计的全面解析
    摘要这篇博客深入探讨了六种C++特殊类的设计及其技术细节。首先,介绍了如何设计只能在堆上或栈上创建对象的类,通过控制构造函数的访问权限来限定对象的内存分配区域。接着,探讨了如何设计一个不能被拷贝的类,避免资源重复释放的问题。随后,介绍了如何防止类被继承以及单例模......
  • 位置式与增量式PID控制器理论与C++实现
    1理论推导1.1PID式中: ——控制器的输出;——控制器的输入(常常是设定值与被控量之差);Kp——控制器的比例放大系数;Ti——控制器的积分时间;Td——控制器的微分时间;1.2位置式PID设为第k次采样时刻控制器的输出值,可得离散的PID算式,又称位置式PID算式:e(k):用户设定的值(目标......
  • 网络安全学习路线+自学笔记(超详细) 自学网络安全看这一篇就够了
    一、什么是网络安全网络安全是一种综合性的概念,涵盖了保护计算机系统、网络基础设施和数据免受未经授权的访问、攻击、损害或盗窃的一系列措施和技术。经常听到的“红队”、“渗透测试”等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。作为一......
  • C++内存模型实践探索
    前言C++对象模型是个常见、且复杂的话题,本文基于ItaniumC++ABI通过程序实践介绍了几种简单C++继承场景下对象模型,尤其是存在虚函数的场景,并通过图的方式直观表达内存布局。本文展示的程序构建环境为Ubuntu,glibc2.24,gcc6.3.0。由于clang和gcc编译器都是基于ItaniumC++ABI......
  • C++ WM\_COPYDATA 实现进程通信
    基于MFC用于存储数据的自定义结构体:structMSG_STRUCT{ wchar_tmsg[256];};发送端,发送按钮响应事件的代码voidCSendWM_COPYDATADlg::OnBnClickedSendmessage(){ MSG_STRUCTsmsg; GetDlgItemText(IDC_MESSAGE,smsg.msg,256); HWNDhTargetWnd=NULL; hTargetWnd=::Fi......
  • C++使用共享内存实现进程间通信
    C++使用共享内存实现进程间通信文件映射是一种实现进程间单向或双向通信的机制。它允许两个或多个本地进程间相互通信。为了共享文件或内存,所有的进程必须使用相同的文件映射的名字或是句柄。为了实现共享文件,第一个进程先调用CreateFile方法。接下来调用CreateFileMappin......
  • 算法笔记 C/C++快速入门 | 全章节整理
    目录零.【C语言中的输入和输出函数】sscanf应用场景1:解析用户输入应用场景2:解析文件内容应用场景3:处理网络协议数据应用场景4:字符串解析和数据转换应用场景5:解析复杂的日志数据其他应用场景:scanf 一【编程语言相关】c和cpp二.【数据结构相关】结构体循环定......
  • C++之类和对象2
    文章目录1.类的默认成员函数有哪些2.构造函数3.析构函数4.拷贝构造函数5.赋值运算符重载函数5.1运算符重载5.2赋值运算符重载函数6.取地址运算符重载函数6.1const成员函数6.2普通取地址重载函数与const取地址运算符重载函数1.类的默认成员函数有哪些默认构造函数是......