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

news2024/11/21 15:54:29

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

个人专栏:

  • 【数据结构初阶】 📘 基础数据结构
  • 【C语言】 💻 C语言编程技巧
  • 【C++】 🚀 进阶C++
  • 【OJ题解】 📝 题解精讲

目录

  • C++继承机制详解与代码示例
  • 📌1. 继承的基本概念
  • 📌 2. 继承示例:`Student`和`Teacher`继承`Person`
  • 📌 3. 继承类模板示例
  • 📌 4. 基类和派生类间的转换
  • 📌 5. 菱形继承与虚继承
  • 📌 6. 虚继承的实现
  • 📌 7. 继承与组合的区别
  • 📌 总结

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

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


📌1. 继承的基本概念

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

  • public继承:基类的publicprotected成员在派生类中分别保持为publicprotected
  • protected继承:基类的public成员变为protectedprotected成员保持不变。
  • private继承:基类的publicprotected成员都变为private

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


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

在下面的示例中,我们定义了一个Person类,包含了一些基本的个人信息。然后我们通过继承创建Student类和Teacher类,分别表示学生和老师。这些类除了继承Person类的基本信息外,还包含各自特有的属性和方法。

class Person {
public:
    void identity() {
        cout << "身份认证:" << _name << endl;
    }
protected:
    string _name = "默认名字";
    string _address;
    string _tel;
    int _age = 18;
};

class Student : public Person {
public:
    void study() {
        cout << _name << "在学习" << endl;
    }
protected:
    int _stuid;  // 学号
};

class Teacher : public Person {
public:
    void teaching() {
        cout << _name << "在授课" << endl;
    }
protected:
    string _title;  // 职称
};

在该示例中:

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

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


📌 3. 继承类模板示例

C++支持模板类继承,通过继承标准库的容器类,我们可以轻松地扩展这些容器类的功能。以下示例展示了一个栈(Stack)类模板,分别继承自std::vectorstd::liststd::deque,从而实现栈的基本操作。

template<class T>
class Stack : public std::vector<T> {
public:
    void push(const T& x) {
        std::vector<T>::push_back(x);
    }
    void pop() {
        std::vector<T>::pop_back();
    }
    bool empty() {
        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

class Person {
public:
    string _name = "莱昂纳多";
    int _num = 111;
};

class Student : virtual public Person {};
class Teacher : virtual public Person {};

class Assistant : public Teacher, public Student {
protected:
    string _major;
};

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


📌 6. 虚继承的实现

虚继承(virtual inheritance)是解决菱形继承问题的一种方法。通过虚继承,派生类可以确保祖先类的成员只有一份,从而消除了菱形继承中的数据冗余和访问二义性问题。以下代码展示了虚继承的用法:

class Person {
public:
    string _name = "莱昂纳多";
    int _num = 111;
};

class Student : virtual public Person {};
class Teacher : virtual public Person {};

class Assistant : public Teacher, public Student {
protected:
    string _major;
};

int main() {
    Assistant a;
    a._name = "小李";  // 仅有一个 _name,避免二义性
    cout << "Name: " << a._name << endl;
    return 0;
}

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


📌 7. 继承与组合的区别

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

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

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

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

class Tire {
protected:
    string _brand = "Michelin";  // 轮胎品牌
};

class Car {
protected:
    string _color = "白色";  // 车颜色
    Tire _tire1, _tire2, _tire3, _tire4;  // 4个轮胎
};

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

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


📌 总结

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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2244756.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

根据条件 控制layui的table的toolbar的按钮 显示和不显示

部分代码&#xff1a; <!-----查询条件-----> <input type"date" id"StartDate" onchange"PageList()" /> <input type"date" id"EndDate" onchange"PageList()" /><!-----表格Table-----&…

net某高校社交学习平台的设计与实现

摘 要 高校社交学习平台是一个融合了社交网络特性的在线学习交流系统&#xff0c;旨在促进高校学生之间的信息共享与学习互动。该平台通过提供学习资料、学习视频和学习交流等功能&#xff0c;支持发布学习动态、参与知识问答、并实时追踪学习进度。为学生提供一个全面且便捷的…

5-对象的访问权限

对象的访问权限知识点 对象的分类 在数据库中&#xff0c;数据库的表、索引、视图、缺省值、规则、触发器等等、都可以被称为数据库对象&#xff0c;其中对象主要分为两类 1、模式(schema)对象&#xff1a;模式对象可以理解为一个存储目录、包含视图、索引、数据类型、函数和…

如何在vscode 中打开新文件不覆盖上一个窗口

在 VSCode 中&#xff0c;如果你单击文件时出现了覆盖Tab的情况&#xff0c;这通常是因为VSCode默认开启了预览模式。在预览模式下&#xff0c;单击新文件会覆盖当前预览的文件Tab。为了解决这个问题&#xff0c;你可以按照以下步骤进行操作 1.打开VSCode&#xff1a;启动你的…

【网络系统管理】Centos7——配置主从mariadb服务器案例(下半部分)

【网络系统管理】Centos7——配置主从mariadb服务器案例-CSDN博客 接上个文档&#xff0c;我们已经完成了主服务器创建数据库备服务器可以看到 一、在DBMS2查看信息 File&#xff0c;Position这两个字段的数据要记好&#xff0c;等一下需要用到 show master status; 二、在…

C#编写的日志记录组件 - 开源研究系列文章

以前编写过一个日志记录组件的博文&#xff0c;这次发布一个修改过的完善版本。 1、 项目目录&#xff1b; 2、 源码介绍&#xff1b; 1) 实现&#xff1b; 2) 使用&#xff1b; 后面的参数为级别设置&#xff0c;只有大于这个级别的才进行日志记录&#xff0c;限制了日志记录的…

react 如何修改弹出的modal的标题

原来标题的样子&#xff1a; 修改为&#xff1a; 实现方式&#xff1a; <Modal title<span>股价趋势/{this.state.pccode}</span> visible{this.state.isPriceModalOpen} style{{ top: 20 }} width{1320} height{400} footer{null} onCancel{()>this.hideMo…

学习threejs,对模型多个动画切换展示

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.AnimationMixer 动画…

笔记01----Transformer高效语义分割解码器模块DEPICT(即插即用)

学习笔记01----即插即用的解码器模块DEPICT 前言源码下载DEPICT实现实验 前言 文 章 标 题&#xff1a;《Rethinking Decoders for Transformer-based Semantic Segmentation: Compression is All You Need》 当前的 Transformer-based 方法&#xff08;如 DETR 和其变体&…

layui合并table相同内的行

<table border"1" id"table1" class"layui-table"><thead><tr><th><b>姓名</b></th><th><b>项目</b></th><th><b>任务</b></th><th><b>…

【大模型】大模型RAG检索增强生成技术使用详解

目录 一、前言 二、RAG技术介绍 2.1 RAG是什么 2.2 RAG工作原理 2.3 RAG优势 2.4 RAG应用场景 三、在线大模型平台RAG技术使用 3.1 阿里百炼平台 3.1.1 创建知识库 3.1.2 导入文档数据 3.1.3 文档数据解析 3.1.4 查看数据 3.2 百度文心智能体 3.2.1 创建知识库 3…

人工智能与SEO优化中的关键词策略解析

内容概要 在当今数字化快速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;与搜索引擎优化&#xff08;SEO&#xff09;的结合正变得愈发重要。关键词策略是SEO优化的一项基础工作&#xff0c;它直接影响到网站的可见性和流量。通过运用智能算法&#xff0c;企业能…

【WRF-Urban】WRF 4.3版本中城市模块更新总结

【WRF-Urban】WRF 4.3版本中城市模块更新总结 WRF 4.3 版本中城市模块更新1. 局地气候区&#xff08;LCZ&#xff09;的引入WRF 查找表的修改&#xff1a;如何启用 11 类 LCZ 分类&#xff1a; 2. 屋顶缓解策略与建筑材料渗透性3. 新的建筑物阻力系数处理 使用LCZ的WRF-Urban模…

【Apache Paimon】-- 6 -- 清理过期数据

目录 1、简要介绍 2、操作方式和步骤 2.1、调整快照文件过期时间 2.2、设置分区过期时间 2.2.1、举例1 2.2.2、举例2 2.3、清理废弃文件 3、参考 1、简要介绍 清理 paimon &#xff08;表&#xff09;过期数据可以释放存储空间&#xff0c;优化资源利用并提升系统运行效…

第二十周:机器学习

目录 摘要 ABSTRACT 一、吴恩达机器学习exp2——逻辑回归 1、logistic函数 2、数据预处理 3、损失函数 4、梯度下降 5、设定评价指标 6、决策边界 7、正则化 二、动手深度学习pytorch——数据预处理 1、数据集读取 2、缺失值处理 3、转换为张量格式 总结 摘要…

反转链表、链表内指定区间反转

反转链表 给定一个单链表的头结点pHead&#xff08;该头节点是有值的&#xff0c;比如在下图&#xff0c;它的val是1&#xff09;&#xff0c;长度为n&#xff0c;反转该链表后&#xff0c;返回新链表的表头。 如当输入链表{1,2,3}时&#xff0c;经反转后&#xff0c;原链表变…

VScode学习前端-01

小问题合集&#xff1a; vscode按&#xff01;有时候没反应&#xff0c;有时候出来&#xff0c;是因为------>必须在英文状态下输入&#xff01; 把鼠标放在函数、变量等上面&#xff0c;会自动弹出提示&#xff0c;但挡住视线&#xff0c;有点不习惯。 打开file->pre…

【AI图像生成网站Golang】JWT认证与令牌桶算法

AI图像生成网站 目录 一、项目介绍 二、雪花算法 三、JWT认证与令牌桶算法 四、项目架构 五、图床上传与图像生成API搭建 六、项目测试与调试(等待更新) 三、JWT认证与令牌桶算法 在现代后端开发中&#xff0c;用户认证和接口限流是确保系统安全性和性能的两大关键要素…

基于Kafka2.1解读Consumer原理

文章目录 概要整体架构流程技术名词解释技术细节coordinatorfetcherclientconsumer#poll的主要流程 全局总览小结 概要 继上一篇讲Producer原理的文章过去已经一个多月了&#xff0c;今天来讲讲Consumer的原理。 其实源码早就读了部分了&#xff0c;但是最近工作比较忙&#x…

测试使用vite搭建的uni-app打包app区分开发环境和生产环境

用脚手架搭建的uniapp项目&#xff0c;打包H5和小程序可以和web端一样&#xff0c;能够通过env.dev和env.prod区分开发环境和生产环境&#xff0c;但是不知道打包成app时如何区分开发环境和生产环境&#xff0c;在此做一个测试记录。 打开package.json文件&#xff0c;在scrip…