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

C++ 继承核心机制详解

综述由AI生成C++ 继承是面向对象编程实现代码复用的关键机制。阐述了继承的基本定义、访问权限控制及派生类默认成员函数的生成规则。重点解析了基类与派生类对象的赋值转换(切片现象)、作用域隐藏问题、友元关系的非继承性以及静态成员的共享特性。针对多继承引发的菱形继承数据冗余和二义性问题,详细介绍了虚拟继承的解决方案及其底层原理。最后对比了继承与组合的使用场景,强调优先使用组合以降低耦合度。

墨染流年发布于 2026/3/28更新于 2026/6/416 浏览
C++ 继承核心机制详解

继承的概念及定义

继承是面向对象程序设计实现代码复用的重要手段。它允许我们在保持原有类特性的基础上进行扩展,产生新的类,即派生类。这体现了面向对象程序设计的层次结构,从简单到复杂逐步构建。

定义格式上,以 class Student : public Person 为例,Person 是基类(父类),Student 是派生类(子类),public 是继承方式。继承方式有 public、protected 和 private 三种,不同继承方式会改变基类成员在派生类中的访问权限。

比如,基类的 public 成员在 public 继承下,在派生类中仍是 public 成员;但在 protected 继承下,就变为派生类的 protected 成员。

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

派生类对象和基类对象之间存在特殊的赋值转换关系。派生类对象可以赋值给基类的对象、指针或引用,这就像把派生类中属于基类的那部分'切'出来进行赋值,形象地称为切片(Slicing)。

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

然而,基类对象不能直接赋值给派生类对象。不过,基类的指针或引用可以通过强制类型转换赋值给派生类的指针或引用,但这种转换需要谨慎。若基类是多态类型,可使用 RTTI(运行时类型信息)的 dynamic_cast 来确保安全转换。

这里涉及两个核心概念:向上转型和向下转型。默认约定基类在上,派生类在下。

  1. 向上转型(Upcast):子类指针/引用 → 父类指针/引用。
    • 适用条件:仅需满足公有继承(public inheritance)。
    • 特点:自动转换,绝对安全,是多态的基础。
  2. 向下转型(Downcast):父类指针/引用 → 子类指针/引用。
    • 语法要求:需手动强转,存在安全风险。
    • 安全方案:多态场景下使用 dynamic_cast 进行运行时检测,防止因指向非子类对象而触发未定义行为。

如果 Person& p = sobj,实际上是将子类对象作为父类的别名。无论是赋值、指针还是引用,都遵循兼容切割的原则。

继承中的作用域

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

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 函数中,通过 Person::_num 明确访问父类的 _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 {
//public:
//    friend void Display(const Person& p, const Student& s);
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 类及其他派生类中都是共享的,可通过类名或对象来访问。

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

单继承与多继承

单继承是指一个子类只有一个直接父类,关系简单明了。而多继承则是一个子类有两个或以上直接父类,这种情况虽然增加了代码复用的灵活性,但也引入了一些复杂问题。

菱形继承

菱形继承是多继承的一种特殊情况。以 class Assistant : public Student, public Teacher 为例,Student 和 Teacher 都继承自 Person,这样 Assistant 中就会出现 Person 成员的两份拷贝,导致数据冗余和二义性问题。比如在访问 Assistant 对象中来自 Person 的成员时,编译器无法明确确定访问的是哪一个 Person 成员。

菱形虚拟继承

为解决菱形继承的数据冗余和二义性问题,引入了虚拟继承。在 Student 和 Teacher 继承 Person 时使用虚拟继承(如 class Student : virtual public Person),就能确保在 Assistant 对象中只存在一份 Person 成员的拷贝。

虚拟继承通过虚基表指针和虚基表来管理基类成员的存储和访问,有效解决了上述问题,但也增加了一定的实现复杂度。D 对象中将 A 放到了对象组成的最下面,这个 A 同时属于 B 和 C,那么 B 和 C 如何去找到公共的 A 呢?这里是通过了 B 和 C 的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的 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;
}

总结与反思

继承在 C++ 中是把双刃剑。它极大地促进了代码复用,构建了清晰的类层次结构,但也带来了一些问题。多继承衍生出的菱形继承和复杂的底层实现,让代码复杂度和维护难度上升,这也是许多其他面向对象语言(如 Java)不采用多继承的原因。

在实际编程中,我们要谨慎选择继承和组合。继承是"is-a"关系,每个派生类对象都是一个基类对象;组合是"has-a"关系,一个类包含另一个类的对象。优先考虑对象组合,因为它耦合度低,代码维护性好,更符合'黑箱复用'原则;而继承适用于需要体现类之间层次关系,或实现多态的场景。

目录

  1. 继承的概念及定义
  2. 基类和派生类对象赋值转换
  3. 继承中的作用域
  4. 派生类的默认成员函数
  5. 1. 构造函数
  6. 2. 拷贝构造函数
  7. 3. 赋值运算符函数
  8. 4. 析构函数
  9. 继承与友元
  10. 继承与静态成员
  11. 复杂的菱形继承及菱形虚拟继承
  12. 单继承与多继承
  13. 菱形继承
  14. 菱形虚拟继承
  15. 示例代码
  16. 总结与反思
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 命令行大模型交互工具 MCPHost 配置与实战指南
  • 算法基础:分治法核心思想与经典例题解析
  • 基于 DeepFace 与 OpenCV 的实时情绪分析系统
  • 人工智能:自然语言处理在教育领域的应用与实战
  • TortoiseGit 下载与 SSH 密钥配置指南
  • 使用 AI 辅助重构老旧 Java 项目实战:告别嵌套 If-Else 与 Map 传参
  • 链表的应用:内存管理与缓存淘汰算法
  • Hunyuan-MT-7B-WEBUI 图形化界面详解
  • 企业级数据库融合架构实践:金仓 KingbaseES 深度解析
  • Seedance 2.0 提示词完全指南:从入门到精通
  • 大数据开发进阶:HDFS 分布式文件系统原理与实战
  • 算法:位运算(三)
  • 蜜罐技术原理、分类及 Hfish 部署实战
  • CoPaw Windows 安装及应用指南
  • Ollama + OpenClaw 本地 AI 部署指南
  • Lostlife2.0 角色对话系统升级:基于 LLama-Factory 微调剧情模型
  • HTTP 请求方式详解:GET、POST 与常用方法实战
  • 二分算法实战:查找有序数组中元素的首尾位置
  • 大型语言模型(LLM)微调技术全面总结
  • OpenClaw 飞书机器人权限管理与安全配置

相关免费在线工具

  • 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