【C++】深入理解 C++ 中的继承进阶:多继承、菱形继承及其解决方案

【C++】深入理解 C++ 中的继承进阶:多继承、菱形继承及其解决方案

个人主页:起名字真南的ZEEKLOG博客

个人专栏:


目录

C++继承机制详解与代码示例

继承是面向对象编程(OOP)中的重要概念之一,通过继承,C++允许我们复用现有类的代码,并在此基础上进行扩展,以构建层次化的类结构。本文将结合详细的代码示例,讲解C++中的继承机制,包括单继承、多继承、菱形继承、虚继承、模板类继承等。学习这些概念将有助于我们在实际开发中设计更高效、可复用的代码结构。

📌1. 继承的基本概念

继承(inheritance)是指一个类可以获得另一个类的属性和方法,从而实现代码的复用。通过继承,派生类(也称子类)不仅能继承基类(也称父类)的成员和行为,还可以扩展或修改这些行为。C++中继承形成了“从一般到特殊”的层次结构,并且有以下几种访问控制方式:
  • public继承:基类的publicprotected成员在派生类中分别保持为publicprotected
  • protected继承:基类的public成员变为protectedprotected成员保持不变。
  • private继承:基类的publicprotected成员都变为private

示例代码展示了如何通过继承减少重复代码,提升代码复用率。


📌 2. 继承示例:StudentTeacher继承Person

在下面的示例中,我们定义了一个Person类,包含了一些基本的个人信息。然后我们通过继承创建Student类和Teacher类,分别表示学生和老师。这些类除了继承Person类的基本信息外,还包含各自特有的属性和方法。
classPerson{public:voididentity(){ cout <<"身份认证:"<< _name << endl;}protected: string _name ="默认名字"; string _address; string _tel;int _age =18;};classStudent:publicPerson{public:voidstudy(){ cout << _name <<"在学习"<< endl;}protected:int _stuid;// 学号};classTeacher:publicPerson{public:voidteaching(){ cout << _name <<"在授课"<< endl;}protected: string _title;// 职称};

在该示例中:

  • Person类包含了姓名、地址、电话、年龄等个人信息。
  • Student类继承了Person类,同时增加了学号(_stuid)和study()方法,用于学生的学习行为。
  • Teacher类同样继承自Person类,增加了职称(_title)和teaching()方法,用于表示老师的授课行为。

这种设计避免了在StudentTeacher中重复定义姓名、年龄等成员变量,使代码更加简洁。


📌 3. 继承类模板示例

C++支持模板类继承,通过继承标准库的容器类,我们可以轻松地扩展这些容器类的功能。以下示例展示了一个栈(Stack)类模板,分别继承自std::vectorstd::liststd::deque,从而实现栈的基本操作。
template<classT>classStack:public std::vector<T>{public:voidpush(const T& x){ std::vector<T>::push_back(x);}voidpop(){ std::vector<T>::pop_back();}boolempty(){return std::vector<T>::empty();}};

在这个模板类示例中,Stack类继承了std::vector模板类,并添加了push()pop()empty()方法:

  • push:在栈顶添加一个元素;
  • pop:移除栈顶的元素;
  • empty:判断栈是否为空。

模板类在继承时需要特别注意类域的使用。在上例中,vector<T>::push_back(x)明确指出调用vector模板类的成员函数push_back,以确保模板实例化时找到正确的成员函数。这种方式提供了简便的接口,使得Stack类直接具备了向量的功能而无需重写。


📌 4. 基类和派生类间的转换

在C++中,基类指针或引用可以指向派生类对象,从而通过基类指针或引用调用派生类对象的基类成员,这种机制称为“切片”或“切割”。以下代码示例展示了这种转换的用法:
Student sobj; Person* pp =&sobj;// 基类指针指向派生类对象 Person& rp = sobj;// 基类引用指向派生类对象

这种转换称为“向上转换”(upcasting),因为派生类对象可以转换为基类类型。这在多态性设计中非常有用,可以使代码更加通用。相反,基类对象不能直接转换为派生类对象,但可以通过强制类型转换来实现。这种转换称为“向下转换”(downcasting),需要开发者确保安全性,可以使用dynamic_cast来进行运行时类型检查。

注意:向下转换必须确认基类指针实际指向派生类对象,否则会引发运行时错误。

📌 5. 菱形继承与虚继承

C++支持多继承,即一个类可以继承自多个基类。然而,如果多个基类继承自相同的祖先类,就会导致菱形继承问题。如下所示,Assistant类继承了TeacherStudent,而这两个类都继承自Person
classPerson{public: string _name ="莱昂纳多";int _num =111;};classStudent:virtualpublicPerson{};classTeacher:virtualpublicPerson{};classAssistant:publicTeacher,publicStudent{protected: string _major;};

此时,Assistant类将拥有两份Person的成员变量,导致数据冗余。此外,访问_name等成员变量时会引起二义性问题。通过虚继承可以避免这一问题。


📌 6. 虚继承的实现

虚继承(virtual inheritance)是解决菱形继承问题的一种方法。通过虚继承,派生类可以确保祖先类的成员只有一份,从而消除了菱形继承中的数据冗余和访问二义性问题。以下代码展示了虚继承的用法:
classPerson{public: string _name ="莱昂纳多";int _num =111;};classStudent:virtualpublicPerson{};classTeacher:virtualpublicPerson{};classAssistant:publicTeacher,publicStudent{protected: string _major;};intmain(){ Assistant a; a._name ="小李";// 仅有一个 _name,避免二义性 cout <<"Name: "<< a._name << endl;return0;}

通过在StudentTeacher类中使用virtual public继承Person,虚继承确保了Assistant类仅保留一份Person的成员变量,这样既解决了数据冗余问题,又避免了访问时的二义性。


📌 7. 继承与组合的区别

在面向对象设计中,继承组合是两个不同的设计思路:

  • 继承(is-a关系):用于表示派生类是基类的一种特殊类型。继承常用于表示类之间的层次结构,例如Car类和BMW类。
  • 组合(has-a关系):用于表示类之间的包含关系。组合常用于表示类之间的拥有关系,例如Car类包含多个Tire对象(轮胎)。

组合优于继承,通常能降低类与类之间的耦合性,使代码更加灵活。仅当派生类确实是基类的一种“特殊类型”时,才考虑使用继承。

例如,在以下代码中,Tire类表示轮胎,Car类组合了多个Tire对象,因为车和轮胎是拥有关系,而不是层次关系。

classTire{protected: string _brand ="Michelin";// 轮胎品牌};classCar{protected: string _color ="白色";// 车颜色 Tire _tire1, _tire2, _tire3, _tire4;// 4个轮胎};

在该示例中,CarTire是组合关系,Car对象拥有四个`Tire

对象,说明两者间的关系是has-a`,更适合组合关系,而非继承关系。


📌 总结

C++的继承机制提供了代码复用和层次结构的基础,但其灵活性也带来了复杂性。本文介绍了C++继承的多种使用方式和注意事项,如模板类的继承、基类与派生类的转换、菱形继承和虚继承的使用等。菱形继承可能导致数据冗余和访问冲突,虚继承可以解决这一问题,但会增加实现的复杂性。因此,在设计中要谨慎使用继承,尽量优先选择组合关系,以降低耦合性,提高代码的可维护性和复用性。

Read more

ubuntu-24.04安装配置rime(中州韵)输入法

ubuntu-24.04安装配置rime(中州韵)输入法

由于搜狗输入法版本较老,在ubuntu-24.04上总是有些毛病,遂尝试使用社区较好的rime输入法。 笔者前前后后也是踩了不少坑,故做一次记录,也希望对各位有用。 有关rime的一些基础知识 安装前先介绍一下rime的相关知识,这样对于后续的操作就比较易于理解,遇到问题也容易解决。 详细参见rime中州韵小狼毫 保姆级安装配置教程 100种增强功能_小狼毫输入法100+增强功能-ZEEKLOG博客 1、rime与小狼毫、中州韵、鼠须管有什么关系? rime全平台通用,不过在不同平台有着不同的名字;windows叫小狼毫,linux叫中州韵,mac上叫鼠须管。 2、rime和東風破有什么关系? 東風破是中州韻輸入引擎的配置管理工具,更易于管理rime的第三方包(比如笔者使用的雾凇)。東風破安装可选,但是强烈推荐。 3、rime自定义配置好用吗? 包好用的;只要搞明白.custom.yaml以及部署流程(并不难,在配置部分会讲)。 注意:针对windows、macos、linux(ibus、fcitx)有不同的配置文件,不同平台请勿错用。 输入法平台主配置文

By Ne0inhk
【图文】Codex接入Kimi K2/GLM-4.6 环境配置指南 (Windows/macOS/Ubuntu)

【图文】Codex接入Kimi K2/GLM-4.6 环境配置指南 (Windows/macOS/Ubuntu)

Codex接入Kimi K2/GLM-4.6 环境配置指南 (Windows/macOS/Ubuntu) 前言 紧跟DeepSeek的步伐,智谱也在节前发布了GLM-4.6,并称它是智谱"最强的代码Coding模型(较GLM-4.5提升27%)" * 代码能力对齐Claude Sonnet 4,部分榜单对齐Claude Sonnet 4.5。 * 上下文长度增加到200K。 * 推理能力提升,增加图像识别与搜索能力。 * token消耗较GLM-4.5节省30%以上。 GLM Coding Plan订阅自动升级至GLM-4.6(包含已订阅的用户): * 支持Claude Code、Roo Code、Kilo Code、Cline等10+主流编程工具。 * 套餐命名对齐Claude,用量为Claude x3,费用为Claude x1/7,

By Ne0inhk
Flutter 三方库 vy_string_utils 的鸿蒙化适配指南 - 实现高效的字符串模式校检、支持富文本清洗与多维度命名规范转换

Flutter 三方库 vy_string_utils 的鸿蒙化适配指南 - 实现高效的字符串模式校检、支持富文本清洗与多维度命名规范转换

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 vy_string_utils 的鸿蒙化适配指南 - 实现高效的字符串模式校检、支持富文本清洗与多维度命名规范转换 前言 在进行 Flutter for OpenHarmony 开发时,字符串处理几乎无处不在。从校验用户输入的手机号,到将后台返回的 snake_case 字段转化为鸿蒙 UI 需要的文本格式,这类基础工作如果通过硬编码实现,会产生大量的冗余逻辑。vy_string_utils 是一款轻量级却功能强悍的字符串工具包。它通过一系列精心设计的扩展方法,让鸿蒙开发者能以极简的语法管理所有文本流。本文将带大家领略这款“字符串手术刀”的威力。 一、原理解析 / 概念介绍 1.1 基础原理 vy_string_utils 基于 Dart

By Ne0inhk
Flutter 三方库 flutter_test_config 的鸿蒙化适配指南 - 实现具备全局上下文配置与测试桩自动化注入的质量管理中心、支持端侧测试资源预加载与环境归一化实战

Flutter 三方库 flutter_test_config 的鸿蒙化适配指南 - 实现具备全局上下文配置与测试桩自动化注入的质量管理中心、支持端侧测试资源预加载与环境归一化实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 flutter_test_config 的鸿蒙化适配指南 - 实现具备全局上下文配置与测试桩自动化注入的质量管理中心、支持端侧测试资源预加载与环境归一化实战 前言 在进行 Flutter for OpenHarmony 的大规模质量建设时,我们经常需要为整个项目的测试用例配置统一的参数。例如:为所有 UI 测试注入统一的字体包、配置模拟的鸿蒙屏幕尺寸,或者在每个测试开始前重置分布式数据库状态。flutter_test_config 是 Flutter 官方提供的一种特殊的配置机制,用于在测试执行前注入全局逻辑。本文将探讨如何在鸿蒙端构建极致、专业的全局测试治理中心。 一、原直观解析 / 概念介绍 1.1 基础原理 该库通过在 test 目录下搜索名为 flutter_test_config.dart 的特殊入口文件,

By Ne0inhk