news 2026/10/1 13:55:22

C++继承

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++继承

一.继承的概念

继承是一种可以让代码复用的机制,它在保持原有类结构的基础上进行拓展,增加方法和变量形成新的类,称为派生类。派生类继承的叫做基类。

继承定义格式

继承按照访问权限符分类
类成员/继承方法public继承protect继承private继承
基类public派生类的public成员派生类的protect成员派生类的private成员
基类protect派生类的protect成员派生类的protect成员派生类的private成员
基类private派生类不可见派生类不可见派生类不可见

这样做的目的是protect可以让子类进行访问的到同时保证类外不会访问到protect的成员,虽然基类private派生类不可见,但派生类仍然继承了基类的成员

  • 基类的private成员在派生类中无论以何种方式继承都是不可见的。这种不可见性意味着基类的私有成员虽然会被继承到派生类对象中,但在语法上禁止派生类对象(无论在类内部还是外部)访问这些成员。

  • 基类的private成员在派生类中不可访问。若希望基类成员不能被类外直接访问,但允许派生类访问,则应将其定义为protected。可见,protected访问限定符正是为继承场景而设计的。

  • 通过总结可以发现:基类的私有成员在派生类中始终不可见。对于基类的其他成员,在派生类中的访问权限等于成员在基类的访问限定符与继承方式中的较小者,遵循public > protected > private的优先级规则。

  • class关键字默认使用private继承方式,struct关键字默认使用public继承方式。但最佳实践是显式声明继承方式。

  • 实际开发中主要采用public继承,极少使用protected/private继承。因为这两种继承方式会导致继承的成员只能在派生类内部使用,不利于代码的扩展和维护。

二.基类与派生类的转化

  • public继承的派生类对象可以赋值给基类的指针/引用。把派生类的基类部分切片给基类指针/引用
  • 基类对象不能赋值给派生类对象
  • 基类的指针/引用可以强制转换类型赋值给派生类的指针/引用。但必须是基类指针指向派生类对象才是安全的。意思就是创建基类类型的指针指向派生类,而不是创建基类类型指针指向基类后又把这个指针转给派生类指针。

三.继承中的作用域

  • 继承体系里基类和派生类有独立的作用域
  • 派生类与基类有同名成员,派生类成员会隐藏基类的同名成员,叫做隐藏,对于隐藏的函数可以指定类域访问
  • 成员函数只要同名就会构成隐藏

四.派生类默认成员函数

  • 派生类构造函数必须调用基类的构造函数用来初始化基类的成员。若基类没有默认的构造函数,则派生类必须在初始化列表初始化。
  • 派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的拷贝构造。
  • 派生类的operator=必须显式调用基类的operator=来完成基类部分的复制。需要注意的是,派生类的operator=会隐藏基类的operator=,因此在调用时需要指定基类作用域。

  • 派生类的析构函数执行完毕后会自动调用基类的析构函数来清理基类成员。这种机制确保了对象销毁时遵循先清理派生类成员、再清理基类成员的正确顺序。

  • 派生类初始化对象先调用基类构造在调用派生类构造

  • 析构先调用派生类析构再调用基类析构

  • 由于多态需要析构函数构成重写,导致析构函数处理成destructor导致父类子类会隐藏父类析构函数(在基类不加virtual下)

实现不可被继承类
  1. 可以将类名后加final这代表此类无法被继承
  2. 也可以将类的默认构造函数用private 让子类无法访问,就无法被继承了
继承和友元

父类的友元不会被子类继承

继承与静态成员

基类定义一个static成员则整个继承关系中只有这一个这样的成员

五.多继承

单继承:当一个派生类仅有一个直接基类时,这种继承关系称为单继承。

多继承:若一个派生类拥有两个或更多直接基类,则称为多继承。在多继承中,对象的内存布局遵循继承顺序:先继承的基类位于内存前部,后继承的基类依次排列,派生类成员则置于最后。

菱形继承:这是多继承中的特殊情形。从对象成员模型分析可见,菱形继承会导致数据冗余和二义性问题(如Assistant对象中包含两份Person成员)。由于多继承必然存在菱形继承问题,部分语言(如Java)选择直接禁用多继承来规避此问题。因此在实际开发中,应当避免设计菱形继承结构。

虚继承

因为多继承导致棱形继承二义性所以就有了虚继承。

虚继承是C++中解决多重继承带来的"菱形继承"问题的一种机制。它通过确保基类在继承体系中只被继承一次来避免数据冗余和歧义。

在多重继承中,当派生类通过不同路径继承同一个基类时,会产生"菱形继承"问题。例如:

class Base { public: int data; }; class Derived1 : public Base { // 继承Base }; class Derived2 : public Base { // 继承Base }; class Final : public Derived1, public Derived2 { // 通过Derived1和Derived2间接继承了两个Base };

这种情况下,Final类中将包含两个Base子对象,导致:

  1. 数据冗余 - 两份Base成员变量
  2. 访问歧义 - 无法直接访问Base成员,必须通过特定路径
虚继承的解决方案

使用virtual关键字声明继承关系:

class Derived1 : virtual public Base { // 虚继承Base }; class Derived2 : virtual public Base { // 虚继承Base }; class Final : public Derived1, public Derived2 { // 现在只包含一个Base子对象 };
实现原理
  1. 虚基类指针(vbase_ptr):编译器为每个虚继承的类添加一个指针,指向共享的基类子对象
  2. 虚基类表(vbtable):存储虚基类偏移量信息
  3. 共享实例:确保整个继承体系中只有一个基类实例

示例代码

#include <iostream> class Animal { public: Animal() { std::cout << "Animal constructor\n"; } void breathe() { std::cout << "Breathing...\n"; } }; class Mammal : virtual public Animal { public: Mammal() { std::cout << "Mammal constructor\n"; } }; class WingedAnimal : virtual public Animal { public: WingedAnimal() { std::cout << "WingedAnimal constructor\n"; } }; class Bat : public Mammal, public WingedAnimal { public: Bat() { std::cout << "Bat constructor\n"; } }; int main() { Bat bat; bat.breathe(); // 没有歧义,因为只有一个Animal实例 return 0; }

输出结果:

Animal constructor Mammal constructor WingedAnimal constructor Bat constructor Breathing...

六.继承和组合

继承与组合的区别
继承关系
  • public继承体现的是"is-a"关系,即每个派生类对象本质上都是一个基类对象。
  • 继承允许基于基类实现来定义派生类,这种复用方式称为白箱复用(white-box reuse)。"白箱"指基类的内部细节对派生类可见。
  • 继承会破坏基类封装性:基类的修改会显著影响派生类,两者之间存在强依赖关系,耦合度高。
组合关系
  • 组合体现的是"has-a"关系,例如类B组合类A时,每个B对象都包含一个A对象。
  • 组合是继承之外的另一种复用方式,通过组装对象实现更复杂功能,要求被组合对象有良好定义的接口。
  • 这种复用称为黑箱复用(black-box reuse),因为对象内部细节不可见,仅通过接口交互。
  • 组合类之间依赖关系弱,耦合度低,有助于保持类的封装性。
使用建议
  1. 优先使用组合:组合耦合度低,代码更易维护
  2. 继承适用场景:
    • 当类之间确实是"is-a"关系时
    • 需要实现多态功能时
  3. 权衡选择:当关系既适合继承又适合组合时,优先选择组合方式
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 13:21:40

分公司组织架构图在线设计 总部分支管理模板

良功绘图网站 (https://www.lghuitu.com ) 在企业规模化发展的进程中&#xff0c;分公司的设立成为拓展市场、优化资源配置的重要举措。而总部分支之间的高效协同&#xff0c;离不开清晰、科学的组织架构作为支撑。分公司组织架构图作为直观呈现管理层级、部门设置、权责划分的…

作者头像 李华
网站建设 2026/9/30 7:14:10

KD-Tree的查询原理

好的&#xff0c;让我详细解释KD-Tree的查询原理&#xff0c;以及为什么它能将时间复杂度从O(n)降到O(log n)。 KD-Tree的基本结构 KD-Tree&#xff08;k-dimensional tree&#xff09;是一种用于多维空间的数据结构&#xff0c;特别适合范围搜索和最近邻搜索。 构建过程示例…

作者头像 李华
网站建设 2026/10/1 11:44:43

基于Mask R-CNN的道路路面损伤自动检测与分类研究

1. 基于Mask R-CNN的道路路面损伤自动检测与分类研究 1.1. 引言 随着城市化进程的加速&#xff0c;道路基础设施的维护变得越来越重要。传统的人工检测方法效率低下、成本高昂&#xff0c;且存在安全隐患。&#x1f6a7; 近年来&#xff0c;计算机视觉技术的快速发展为道路路…

作者头像 李华
网站建设 2026/9/30 5:11:36

最近在研究高速列车的主动悬挂系统,发现H无穷控制策略在这个领域挺有意思的。今天就来聊聊基于H无穷控制策略的横摆半车9自由度高速列车主动悬挂

基于H无穷控制策略的横摆半车9自由度高速列车主动悬挂首先&#xff0c;我们得明白什么是H无穷控制。简单来说&#xff0c;H无穷控制是一种鲁棒控制方法&#xff0c;能够在系统存在不确定性和外部干扰的情况下&#xff0c;保证系统的稳定性和性能。对于高速列车这种复杂系统&…

作者头像 李华
网站建设 2026/10/1 8:59:15

Ubuntu硬盘空间不够?一文带你理清过程的根分区无损扩容实战指南

复杂分区布局下的 Ubuntu 根目录无损扩容实践&#xff1a;从引导参数调试到扇区移位 摘要 本文详细记录了在一块 1TB NVMe 固态硬盘&#xff08;WD_BLACK SN770&#xff09;上&#xff0c;解决 Ubuntu 根分区&#xff08;/&#xff09;空间不足问题的全过程。本次扩容的特殊性在…

作者头像 李华
网站建设 2026/9/30 12:51:40

想让你的标书不废标应该这样做

1、检查项目编号与名称:投标文件整篇项目编号与名称是否正确。2、检查投标人名称:投标人名称与营业执照、资质证书、安全生产许可证、银行资信证明等证明证书是否一致。3、检查投标文件的排版:文本格式、字体、行数、图片是否模糊或歪斜&#xff0c;这些是否按照招标文件的要求…

作者头像 李华