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

C++ 核心:继承机制详解

C++ 继承机制允许派生类扩展基类功能,支持代码复用与层次化设计。涉及访问权限控制(public/protected/private)、对象切片转换、作用域隐藏规则及默认成员函数调用顺序。多继承场景下需处理菱形继承带来的数据冗余与二义性,虚拟继承通过虚基表解决此问题。友元关系不可继承,静态成员在继承体系中共享。实际开发应权衡继承与组合的使用,优先组合以降低耦合。

竹影清风发布于 2026/3/24更新于 2026/5/59 浏览
C++ 核心:继承机制详解

前言

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

一、继承的概念及定义

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

文章配图

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

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

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

Student sobj; 
Person pobj = sobj; 
Person* pp = &sobj; 
Person& rp = sobj;

文章配图

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

向上转型(Upcast)指子类指针/引用转换为父类指针/引用,这是永远安全且自动支持的转换,也是多态的基础。向下转型(Downcast)指父类指针/引用转换为子类指针/引用,存在安全风险,必须手动强制转换,且原对象必须是子类实例,否则可能触发未定义行为。

如果 Person&p=s;,实际上是将子类对象作为父类的别名。赋值、指针操作都涉及兼容切割与切片。

文章配图

三、继承中的作用域

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

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. 构造函数:派生类的构造函数必须调用基类的构造函数来初始化基类的那部分成员。若基类没有默认构造函数,就需在派生类构造函数的初始化列表中显式调用合适的基类构造函数。

编译过程中,子类有两组成员,一组是自身的,一组是继承而来。编译过程中会先初始化基类,再初始化派生类。派生类只初始化自己的成员,编译器默认把继承而来的对象交给父类初始化,但是不可以以--子类对象 (初始化变量)--显示调用父类的默认构造,如果没有默认构造-->会报错。

需要以 person(成员名) 的形式调用父类交给他的默认构造。

文章配图

文章配图

  1. 拷贝构造函数:派生类的拷贝构造函数要调用基类的拷贝构造函数,完成基类成员的拷贝初始化。

文章配图

子类拷贝构造时如果基类无拷贝构造,默认调入默认构造,如果默认构造也没有会报错。

文章配图

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

文章配图

  1. 析构函数:派生类的析构函数在被调用完成后,会自动调用基类的析构函数,以清理基类成员,确保对象资源的正确释放,遵循先派生类后基类的清理顺序。

不需要如下这样显示调用,否则会二次析构。

文章配图

文章配图

同时还有一个原因就是,如果析构是先父亲后儿子,子类析构中有调用父类成员函数,就会有坑。

五、继承与友元

友元关系在继承体系中是不能自动继承的。也就是说,基类的友元不能访问子类的私有和保护成员。

父亲的朋友不是你的朋友,你需要和他成为朋友才可以访问。

文章配图

例如:

class Student; 
class Person { 
    public: 
        friend void Display(const Person& p, const Student& s); 
    protected: 
        string _name; 
}; 
class Student : public Person { 
    //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 成员。

文章配图

数据冗余和二义性: Assistant 中就会出现 Person 成员的两份拷贝。

文章配图

(三)菱形虚拟继承

为解决菱形继承的数据冗余和二义性问题,引入了虚拟继承。在 Student 和 Teacher 继承 Person 时使用虚拟继承(如 class Student : virtual public Person),就能确保在 Assistant 对象中只存在一份 Person 成员的拷贝。虚拟继承通过虚基表指针和虚基表来管理基类成员的存储和访问,有效解决了上述问题,但也增加了一定的实现复杂度。

原理图

文章配图

示例代码

文章配图

文章配图

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

文章配图

八、继承的总结和反思

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

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

文章配图

Test

#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++ 的继承机制丰富而复杂,深入理解它的各个方面,从基本概念到复杂应用,能让我们在编程时更得心应手,编写出结构良好、高效且易于维护的代码。

目录

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

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

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

更多推荐文章

查看全部
  • Python 汽车销售大屏数据分析可视化系统设计与实现
  • HarmonyOS6 RcList 组件核心架构与类型系统设计
  • AI 产品经理面试高频 100 题及核心解析
  • C++ 微服务 UserServer 设计与实战落地
  • ROS 基于 v4l2loopback 虚拟摄像头的 YOLO 目标检测与机器人控制
  • Diffusion Transformer (DiT) 架构解析:从图像生成到机器人动作预测
  • 自动化批量挖掘 SRC 漏洞实战指南
  • Python FastAPI 入门实战:从环境搭建到核心功能详解
  • 从三年前端到 CS 硕士:韩国留学复盘与前端回归之路
  • C++ 模板初阶
  • Springboot 整合 Java DL4J 打造自然语言处理之智能写作助手
  • Z-Image-Turbo 云端 AI 绘画一键部署指南
  • Node.js 最新安装教程及环境变量配置
  • Java 类加载机制详解
  • CMake 核心概念与实战指南
  • Copilot 最佳使用方式与深度配置指南
  • n8n 飞书机器人 Webhook 配置与 Crypto 节点签名实现
  • 使用 Bright Data Web Scraper API + Python 抓取 Glassdoor 数据实战
  • Windows 平台 Qwen1.5 大模型部署指南
  • Cursor+Codex 截图调试前端 Bug 实战(React/Chakra UI 案例)

相关免费在线工具

  • 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