跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ 继承机制详解:从基础到多继承与组合

C++ 继承是面向对象设计的核心,允许派生类复用基类特性。本文涵盖单继承语法、访问权限控制、对象切片风险、成员隐藏规则及默认成员函数调用顺序。深入解析静态成员共享、友元不可继承特性,重点剖析菱形继承问题及其虚继承解决方案。最后对比组合模式优势,强调优先使用组合而非继承的设计原则,帮助开发者构建低耦合、高内聚的代码结构。

虚拟内存发布于 2026/3/29更新于 2026/7/2832 浏览
C++ 继承机制详解:从基础到多继承与组合

一、初识继承

继承是面向对象编程中实现代码复用的重要手段。它允许我们在保持原有类特性的基础上进行扩展,从而产生新的类,即派生类。这种设计体现了类层次上的复用。

在术语上,原有类被称为基类(Base)、父类或超类;而在其基础上扩展而来的类则称为派生类(Derived)、子类。

语法格式

C++ 中定义继承关系非常简单,只需在类名后加上冒号和继承方式即可:

class Derived : public Base {
    // 新增成员
};

这里的 public 表示公有继承,还有 protected 和 private 两种形式。

继承方式与访问权限

继承方式决定了基类成员在派生类中的可见性。

  • public 继承:基类的 public 和 protected 成员在派生类中保持原有访问权限。
  • protected 继承:基类的 public 和 protected 成员在派生类中变为 protected。
  • private 继承:基类的所有成员在派生类中均变为 private。

注意:

  1. 基类的 private 成员无论以何种方式继承,在派生类中都是不可见的。它们虽然被继承到了派生类对象的内存布局中,但语法上限制了访问。
  2. 如果基类成员不想在类外直接访问,但需要在派生类中使用,应定义为 protected。这是因继承而特有的访问限定符。
  3. 使用关键字 class 时默认继承方式是 private,使用 struct 时默认为 public。建议显式写出继承方式以提高可读性。
  4. 实际开发中通常使用 public 继承,很少使用 protected 或 private 继承,因为后者会限制成员的可用性,降低扩展维护性。

二、基类和派生类之间的赋值转换

理解对象间的赋值转换对于避免逻辑错误至关重要。

基本特性

  1. 派生类对象可以赋值给基类的对象、指针或引用。这被称为'对象切片'(Slicing),寓意将派生类中属于父类的部分切下来赋值过去。
  2. 基类对象不能赋值给派生类对象。
  3. 基类的指针或引用可以通过强制类型转换赋值给派生类的指针或引用,但这要求基类指针实际指向的是派生类对象,否则不安全。

切片现象演示

子类虽然继承了父类成员,但父类和子类中的同名成员(如 _name, _age)在内存中是独立的。子类包含两部分成员:继承来的父类成员和自身新增的成员。

代码示例:

class Base {
public:
    int age;
    Base(int a) : age(a) {}
};

class Derived : public Base {
public:
    int no;
    Derived(int a, int n) : Base(a), no(n) {}
};

int main() {
    Derived d(18, 1001);
    
    // 1. 派生类对象赋值给基类对象(切片)
    Base b = d; 
    // 此时 b.age 为 18,但 b 中没有 no 成员
    
    // 2. 基类指针指向派生类对象(安全)
    Base* pb = &d;
    Derived* pd = dynamic_cast<Derived*>(pb);
    // 若 pb 确实指向 Derived 对象,pd 可安全访问 no
    
    // 3. 危险操作:基类指针指向基类对象
    Base* p_obj = new Base(20);
    Derived* pd2 = dynamic_cast<Derived*>(p_obj);
    // 此时 pd2 为空,若强行访问会导致未定义行为
}

三、重定义(隐藏)

在继承体系中,基类和派生类拥有独立的作用域。如果子类和父类中存在同名成员,子类成员将屏蔽对父类同名成员的直接访问,这种情况称为'隐藏'或'重定义'。

注:

  1. 成员函数只需要函数名相同就构成隐藏,参数列表不同也不影响隐藏判定(除非使用 using 声明)。
  2. 在实际项目中,尽量避免在继承体系里定义同名的成员,以免混淆。

示例①:成员变量同名

class Base {
public:
    int num;
};

class Derived : public Base {
public:
    int num;
};

int main() {
    Derived d;
    d.num = 10; // 访问的是 Derived::num
    d.Base::num = 20; // 显式访问 Base::num
}

如果不指定作用域,编译器优先查找局部域,然后是子类域,最后是父类域。找到一处即停止查询。

示例②:成员函数同名

class Base {
public:
    void fun() { cout << "Base" << endl; }
    void fun(int x) { cout << "Base int" << endl; }
};

class Derived : public Base {
public:
    void fun() { cout << "Derived" << endl; }
};

int main() {
    Derived d;
    d.fun();      // 调用 Derived::fun,Base::fun 被隐藏
    d.fun(1);     // 报错!Base::fun(int) 也被隐藏了
}

通俗理解,当父子类出现同名成员时,默认先访问子类的同名成员,父类的同名成员仿佛被'藏'起来了。

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

派生类的生命周期管理依赖于基类的配合。

基本特性

派生类的构造函数必须调用基类的构造函数来初始化基类部分。如果基类没有默认构造函数,必须在派生类构造函数的初始化列表中显式调用。拷贝构造函数和赋值运算符同理。析构函数则是先清理派生类成员,再自动调用基类析构函数。

总结:

  1. 派生类继承自基类的那部分,其构造、拷贝构造、赋值重载以及析构都需要通过派生类调用基类的来完成。
  2. 构造顺序:先基类,后派生类。
  3. 析构顺序:先派生类,后基类。

显式调用说明

  1. 父类的普通构造函数:若父类有默认构造函数,子类可不显式调用;若无,则必须显式调用。
  2. 父类的拷贝构造函数:子类的拷贝构造函数不会自动调用父类的拷贝构造函数,而是默认调用父类的默认构造函数。因此,若需正确拷贝父类部分,必须在初始化列表中显式调用父类的拷贝构造函数。
  3. 父类的赋值运算符重载:子类的赋值运算符重载不会自动调用父类的赋值运算符,需在函数体内部显式调用 Parent::operator=(other)。

五、继承与友元

友元关系不能继承。父类的友元不能访问子类的私有和保护成员,即使这些成员是从父类继承来的。因为父类的友元并不是子类的友元。

六、继承与静态成员

1. 基类静态成员变量

基类的静态成员变量在内存中仅存在一份实例,所有派生类(及基类自身的对象)共享这一份数据,不会因继承而产生新的副本。

  • 内存唯一:全局区分配一次。
  • 共享修改:任何对象修改都会反映在所有访问者眼中。
  • 访问方式:推荐用 类名::静态成员 访问。

2. 基类静态成员函数

派生类会继承该函数的实现,且所有派生类共享同一函数实例。

  • 无多态性:静态成员函数无法被声明为 virtual,不支持多态。即使派生类重写了同名静态函数,也只是'隐藏',而非覆盖。

3. 派生类对基类静态成员的'隐藏'

若派生类定义了与基类同名的静态成员,会隐藏基类的静态成员,但不会覆盖。基类成员仍独立存在。

七、多继承

1. 单继承

前面例子多为单继承,此处略过。

2. 多继承

一般不建议设计出多继承,因为它增加了复杂度。

class Base1 {};
class Base2 {};
class Derived : public Base1, public Base2 {};

Derived 同时拥有 Base1 和 Base2 的成员。

3. 菱形继承

警告:一定不要设计出菱形继承,否则在复杂度和性能上可能出问题。 菱形继承会导致派生类中出现数据冗余和访问歧义。例如 FinalDerived 继承 Derived1 和 Derived2,两者都继承 Base,导致 FinalDerived 中有两份 Base 的成员。

4. 菱形继承的解决方法:虚继承

虚继承确保最终派生类中只保留共同基类的一份实例。

class Base { public: int x; };
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class FinalDerived : public Derived1, public Derived2 {};

底层原理: 虚基类实例被移到内存末尾,成为共享实例。派生类增加虚基类指针(vbptr)和虚基类表(vbtable)来记录偏移量。最终派生类负责初始化虚基类。

注意:

  • 仅在菱形继承场景下使用虚继承。
  • 非菱形结构无需使用,徒增开销。

八、对象组合

通过在一个类中声明另一个类的对象作为成员,实现功能组合。这体现了 "has-a" 关系。

对象组合的形式

  1. 严格组合:成员对象完全依赖所属对象存在(如汽车和发动机)。
  2. 聚合:成员对象可独立存在,被所属对象引用(如公司和员工)。
  3. 关联:两个类通过指针/引用建立临时联系(如客户和订单)。

对象组合的优势

  • 低耦合:修改成员对象内部实现不影响组合类。
  • 高灵活性:可替换不同的成员对象。
  • 避免菱形继承问题:多继承导致的歧义可通过组合解决。

总结: 在实际开发中,应遵循'优先使用组合,而非继承'(Favor Composition Over Inheritance)的设计原则,以构建更健壮、易维护的代码。

目录

  1. 一、初识继承
  2. 语法格式
  3. 继承方式与访问权限
  4. 二、基类和派生类之间的赋值转换
  5. 基本特性
  6. 切片现象演示
  7. 三、重定义(隐藏)
  8. 示例①:成员变量同名
  9. 示例②:成员函数同名
  10. 四、派生类的默认成员函数
  11. 基本特性
  12. 显式调用说明
  13. 五、继承与友元
  14. 六、继承与静态成员
  15. 1. 基类静态成员变量
  16. 2. 基类静态成员函数
  17. 3. 派生类对基类静态成员的“隐藏”
  18. 七、多继承
  19. 1. 单继承
  20. 2. 多继承
  21. 3. 菱形继承
  22. 4. 菱形继承的解决方法:虚继承
  23. 八、对象组合
  24. 对象组合的形式
  25. 对象组合的优势
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ 类与对象进阶:构造函数、拷贝构造与操作符重载
  • 本地部署 Flux.1 绘画工具实测:中低显存设备的高质量生成方案
  • OpenClaw 在 Linux 环境下的安装、初始化及 Web UI 配置
  • 转行数据分析师:难度评估与备考指南
  • 50 行代码实现 SLAM+AI Agent 机器人自主导航最小原型
  • 使用 uv 工具发布 Python 包到 PyPI 指南
  • OpenClaw AI 助手框架搭建与配置指南
  • OpenClaw 对接飞书实现多个机器人自动群聊配置
  • Python 文件路径操作指南:遍历、过滤与文件管理
  • Home Assistant 美的设备本地控制配置指南
  • AI 产业双重里程碑:政策筑基与豆包 2.0 技术破局
  • 链表分割:以给定值 x 为基准划分链表
  • OpenCore Legacy Patcher 旧款 Mac 升级 macOS 完整教程
  • Android 11.0 Framework 核心原理与系统启动流程解析
  • Vivado 2019.2安装破解教程:零基础手把手指南
  • 宝兰德 BES 中间件部署与启动实战:从环境搭建到应用上线
  • QUEST 一体机 SideQuest 安装 APK 与 OBB 数据包教程
  • Vivado 烧录 Flash 时出现 ILA 缺失警告 [Labtools 27-3413] 解决方法
  • N46Whisper 云端日语语音转字幕工具使用指南
  • Figma Make 实测:AI 驱动的前端代码生成能力评估

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online