跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ 继承机制详解:从基础到虚拟继承

C++ 继承通过派生类扩展基类实现代码复用。内容涵盖继承定义、访问权限控制、对象切片转换、作用域隐藏、默认成员函数生成顺序、友元不继承特性及静态成员共享机制。重点解析菱形继承导致的数据冗余问题,并介绍虚拟继承解决方案。最后对比继承与组合的优劣,强调优先使用组合以降低耦合度。

云间运维发布于 2026/3/22更新于 2026/5/24 浏览
C++ 继承机制详解:从基础到虚拟继承

概述

继承是 C++ 面向对象编程的核心特性之一,它允许在保持原有类特性的基础上进行扩展,从而构建层次化的类结构。掌握继承机制对于编写高效、可维护的代码至关重要。

一、继承的概念及定义

继承通过派生类(子类)复用基类(父类)的成员。定义格式通常为 class Derived : public Base。其中 public 是继承方式,决定了基类成员在派生类中的访问权限。C++ 支持三种继承方式:public、protected 和 private。

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

![图示:继承关系示意图]

例如,基类的 public 成员在 public 继承下仍是 public,但在 protected 继承下则变为 protected。

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

派生类对象可以赋值给基类对象、指针或引用,这被称为'向上转型'。由于派生类包含基类的所有成员,这种转换是安全的且自动完成。形象地说,这就像把派生类中属于基类的那部分'切'出来进行赋值,称为切片(Slicing)。

Student sobj;
Person pobj = sobj;      // 对象切片
Person* pp = &sobj;       // 指针向上转型
Person& rp = sobj;        // 引用向上转型

反之,基类对象不能直接赋值给派生类对象。若需将基类指针转换为派生类指针(向下转型),必须谨慎处理。如果基类是多态类型,建议使用 dynamic_cast 进行运行时类型检查以确保安全。

向上转型永远安全,是配合虚函数实现多态的基础;向下转型存在风险,仅在确认对象实际类型为派生类时才应使用。

![图示:对象切片示意图]

三、继承中的作用域

在继承体系中,基类和派生类拥有各自独立的作用域。当子类和父类存在同名成员时,子类成员会屏蔽父类对同名成员的直接访问,这种现象称为隐藏(Hiding)。

class Person {
protected:
    int _num = 111;
};

class Student : public Person {
protected:
    int _num = 999;
public:
    void Print() {
        cout << "Person::_num: " << Person::_num << endl;
        cout << "Student::_num: " << _num << endl;
    }
};

在 Student 的 Print 函数中,通过作用域解析运算符 :: 明确访问父类的 _num 成员。实际编程中应尽量避免在继承体系里定义同名成员,以免造成代码理解和维护上的困难。

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

编译器会自动为派生类生成一些默认成员函数,其执行顺序与初始化逻辑如下:

  1. 构造函数:派生类构造函数必须先调用基类构造函数来初始化基类部分。若基类没有默认构造函数,必须在派生类构造函数的初始化列表中显式调用合适的基类构造函数。

    • 编译过程中,先初始化基类,再初始化派生类。派生类只初始化自己的成员。
  2. 拷贝构造函数:派生类的拷贝构造函数需要调用基类的拷贝构造函数,完成基类成员的拷贝初始化。如果基类无拷贝构造函数,默认调用默认构造函数。

  3. 赋值运算符函数:派生类的 operator= 必须调用基类的 operator= 来完成基类成员的复制,避免隐藏基类逻辑。

  4. 析构函数:派生类的析构函数被调用完成后,会自动调用基类的析构函数。清理顺序遵循'先派生类后基类',确保资源正确释放。无需手动调用基类析构函数,否则会导致二次析构错误。

![图示:构造与析构顺序]

五、继承与友元

友元关系在继承体系中是不能自动继承的。基类的友元不能访问子类的私有和保护成员。简单来说,'父亲的朋友不是你的朋友',除非你单独声明其为朋友的友元。

class Student;
class Person {
public:
    friend void Display(const Person& p, const Student& s);
protected:
    string _name;
};

class Student : public Person {
protected:
    int _stunNum;
};

void Display(const Person& p, const Student& s) {
    cout << p._name << endl;
    cout << s._stunNum << endl;
}

这里 Display 函数作为 Person 类的友元,能访问 Person 的保护成员,但对于 Student 类,它并不具备天然访问其保护成员的权限。

六、继承与静态成员

若基类定义了 static 静态成员,在整个继承体系中,无论派生出多少个子类,都只会存在一个该静态成员的实例。所有派生类共享这份数据。

class Person {
public:
    Person() { ++_count; }
public:
    static int _count;
};
int Person::_count = 0;

class Student : public Person {};

Person 类中的 _count 静态成员,在 Student 类及其他派生类中都是共享的,可通过类名或对象来访问。

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

(一)单继承与多继承

单继承结构简单明了。多继承则允许一个子类有两个或以上直接父类,增加了灵活性但也引入了复杂性。

![图示:多继承关系]

(二)菱形继承

菱形继承是多继承的一种特殊情况。例如 Assistant 同时继承自 Student 和 Teacher,而这两者又都继承自 Person。此时 Assistant 中会出现两份 Person 成员的拷贝,导致数据冗余和二义性问题。访问 Person 成员时,编译器无法确定访问的是哪一个副本。

![图示:菱形继承问题]

(三)菱形虚拟继承

为解决上述问题,引入虚拟继承。在中间层继承时使用 virtual 关键字(如 class Student : virtual public Person),确保最终派生类对象中只存在一份 Person 成员的拷贝。

虚拟继承通过虚基表指针和虚基表来管理基类成员的存储和访问。虽然解决了冗余问题,但也增加了一定的实现复杂度。

![图示:菱形虚拟继承内存模型]

八、总结与反思

继承是一把双刃剑。它极大地促进了代码复用,构建了清晰的类层次结构,但也带来了复杂度和维护难度。这也是许多其他语言(如 Java)不采用多继承的原因。

在实际编程中,我们要谨慎选择继承和组合:

  • 继承是'is-a'关系,适用于体现类之间层次关系或实现多态的场景。
  • 组合是'has-a'关系,优先考虑对象组合,因为它耦合度低,代码维护性好,更符合'黑箱复用'原则。

![图示:组合优于继承]

代码示例:虚拟继承测试

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class A {
public:
    A(const char* s) { cout << s << endl; }
    ~A() {}
};

class B : virtual public A {
public:
    B(const char* sa, const char* sb) : A(sa) { cout << sb << endl; }
};

class C : virtual public A {
public:
    C(const char* sa, const char* sb) : A(sa) { cout << sb << endl; }
};

class D : public B, public C {
public:
    D(const char* sa, const char* sb, const char* sc, const char* sd)
        : B(sa, sb), C(sa, sc), A(sa) { cout << sd << endl; }
};

int main() {
    D* p = new D("class A", "class B", "class C", "class D");
    delete p;
    return 0;
}

此示例展示了虚拟继承中基类构造函数的调用顺序,确保 A 类只被初始化一次。

目录

  1. 概述
  2. 一、继承的概念及定义
  3. 二、基类和派生类对象赋值转换
  4. 三、继承中的作用域
  5. 四、派生类的默认成员函数
  6. 五、继承与友元
  7. 六、继承与静态成员
  8. 七、复杂的菱形继承及菱形虚拟继承
  9. (一)单继承与多继承
  10. (二)菱形继承
  11. (三)菱形虚拟继承
  12. 八、总结与反思
  13. 代码示例:虚拟继承测试
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 销讲型直播内容策划:AIGC 的 5 步策划法
  • 从 DDS 到 FPGA:信号发生器技术演进
  • AM32 无人机电调源码架构与工作原理解析
  • MCP Server 实现 Excel 表格一键生成可视化图表 HTML 报告
  • 国内互联网大厂产品经理面试题及参考答案解析
  • 激光雷达外参标定算法详解
  • 医学影像分类器实践:基于深度学习的肺结节检测
  • Git 多人协作实战:分支协同、冲突解决与跨分支管理
  • 深入理解 Java 中的 byte 类型
  • 开放代理产品功能介绍
  • Ubuntu 22.04 基于 ROS2 Humble 的 PX4 无人机仿真环境搭建
  • 红黑树从概念到手撕实现:平衡树的折中智慧
  • LangChain 智能体中间件如何参与 Agent、Model 和 Tool 交互
  • Java 项目 CI/CD 实战:Jenkins 与 GitLab CI 选型与优化
  • Galacean Effects 核心功能与 Web 动画特效开发指南
  • VSCode Copilot MCP 配置与实战指南
  • 基于 Spring Boot 的 Web 三大核心交互案例精讲
  • Vue3 项目实战:Axios 基础封装与接口调用
  • 服务器环境 VS Code GitHub Copilot 加载超时优化与修复
  • Elasticsearch 中查询 Top10 数据的两种实现方式

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • 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