爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      CPP中的继承语法

      首页 知识中心 软件开发 文章详情页

      CPP中的继承语法

      2025-03-10 09:53:00 阅读次数:9

      CPP,代码,函数,子类,成员,父类,继承

      我们知道, 对于面向对象的语言往往有三大特性, 即封装, 继承和多态. 那对于CPP来说, CPP也是一种面向对象的语言, 自然也具有这三大特性. 虽然说这三大特性是所有面向语言所公有的, 但是往往具体实现并不相同, 今天来简单谈一下CPP中关于继承语法的这个话题.

      参考思维导图:
      CPP中的继承语法
      (提示: 清晰的思维导图在对应资源绑定中).

      什么是继承呢?
      CPP中的继承语法

      1. 继承的概念

      概念: 继承是一种类设计层次的代码复用.

      可能有些同学还是不是很理解上面我所说的定义, 但是没事, 我们下面来简单解释一下这个概念中相关的含义.
        这里面的"类设计层次"如何理解呢? 我们举一个简单的例子, 假设我们现在要写一个关于各种职业的类, 很显然在这其中有老师类, 有学生类, 有警察类, 有公务员类等等, 如果是这样的话, 我想写出来工作量是巨大的. 但是CPP提供了继承这个语法, 我们可以通过写一个共同的人类, 把姓名,性别,年龄…这些共有的属性放入人类中, 让其他来来复用这个人类的代码, 这就是"类设计层次" 的一种代码复用. 所以, 我们只需要写子类中各自独有的类属性即可.
        那什么是"代码复用"呢? 这怎么理解呢? 实际上, 我们C语言中的函数, 就是把一段代码用函数封装起来, 然后可以随时调用, 这就是一种函数层次的代码复用, 然后我们CPP中的模板, 是一种在代码逻辑基本一致, 代码类型存在差异的一种代码复用, 再比如我们的适配器模式, 也算是一种代码复用, 我们通过把底层的类结构抽象成简单的接口对外暴露, 使外部在使用上感觉是一样的, 而不用关心底层的具体实现细节. 在这里, 我们所说的继承是一种类设计层次上的代码复用. CPP中的继承语法

      下面是一个简单的类继承代码示例: CPP中的继承语法

      #include <iostream>
      using namespace std;
       
      class Animal {
      public:
          void eat() {
              cout << "Eating" << endl;
          }
      };
       
      class Dog : public Animal {
      public:
          void bark() {
              cout << "Barking" << endl;
          }
      };
       
      int main() {
          Dog myDog;
          myDog.eat();
          myDog.bark();
          return 0;
      }
      

      OK, 对于上面的代码来说, Dog是子类, 而Animal是父类. 而对于子类因为继承了父类的代码, 所以子类Dog里面也有Animal的类成员(在这其中包括eat函数). CPP中的继承语法

      好的, 上面我们就简单介绍了一下CPP继承的一个基本概念, 但是有个问题, 父类中成员有权限修饰符, 子类中也有, 那么这些从父类中继承到子类里的成员的权限是怎么确定的呢?

      2. 继承权限的规则

      2.1访问限定符的回忆

      CPP中的继承语法

      对于这个问题, 我们先来简单介绍一下在一个类中决定类成员权限的权限修饰符有哪些?
      public: 被该修饰符修饰的成员类外类内均可自由访问.
      private: 被该修饰符修饰的成员仅类内可访问.
      protected: 被改修饰符修饰的成员可以在类内访问, 不能再类外访问. 与private的区别在于, 当两个类构成继承关系的时候, 是protected的父类成员被子类继承后, 对于父类来说与private是一样的, 对于子类来说该成员只能在子类类内访问, 在类外不能访问.

      2.2继承的三种方式

      实际上, 继承可以私有继承, 可以公有继承, 也可以保护继承… 分别对应下面三种情况:
      CPP中的继承语法
      而不同的继承方式影响的是继承后成员的权限.

      2.3 继承权限规则

      我们知道,继承有公共继承、保护继承和私有继承三种方式,每个父类里面又有公共成员、保护成员和私有成员也是三种,那么组合起来,那么子类对于父类的成员访问共有九种访问权限。
      CPP中的继承语法
      但是有几个细节需要注意一下:

      1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。虽然我们不能直接去使用基类的 private 成员, 但是也可以通过定义函数的一些方式去间接使用基类中的 private 成员.
      2. protected: 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
      3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
      4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
      5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

      说到这里, 我们简单结合一下之前的CPP学习来回顾一下class和struct的不同之处吧!

      calss 与 struct 辨析
      相同点: 两者都可以在CPP中做类关键字.
      不同点:

      • 一般用法上: class习惯用作类, struct习惯用作结构体(C语言).
      • 成员的默认修饰限定符: class类的成员默认访问限定符是private, 而struct是public.
      • 继承方式上: 对于继承可以省略继承方式, class是默认是私有继承, struct默认是公有继承

      3. 父类和子类赋值转换.

      首先我们需要明确一点, 就是对于父子类的赋值转换, 说的不严谨一点, 实际上有三种赋值转换:

      • 赋值: 父类对象 = 子类对象
      • 指针: 父类指针 = 子类对象
      • 引用: 父类引用 = 子类对象
      void test2()
      {
      	Son s;
      	Father f = s;//代码为 0。
      	Father* pf = &s;//代码为 0。
      	Father& qf = s;//代码为 0。
      }
      

      好的, 我们下面依次解释上面三种赋值转换.

      3.1 赋值转换

      在父类和子类体系中,支持把子类对象赋值给父类。CPP中的继承语法

      理解: 对于上面子类来说是有一个单独的成员_no的, 但是因为要赋值给父类对象, 因此必须要把子类中独有的成员给扔掉, 因此CPP中有一个形象的术语来描述这个过程 “切片”.

      3.2 指针转换 和 引用转换

      对于指针和引用也是同理,
      CPP中的继承语法
      只不过需要特别注意一点, 严格意义上来说指针转换和引用转换都不能说是"切片", 因为把一个子类对象的地址交给父类对象的地址是不会扔掉子类中任何东西的, 因为我们地址说白了就是一串地址编号而已, 只不过指针类型不一样, 指针类型不一样影响的是解引用, 指针+1指针-1的一个结果!!! 而引用呢虽然在语法层面是给一个变量/对象起别名, 但是底层也是用的指针, 因此说两者是没啥本质区别的.

      但是有个问题, 那父类对象可以赋值给子类对象吗? 如果是指针/引用呢?
      通常来说是不可以的, 但是有一些情况是可以的, 当然我们不用了解那么深入, 有兴趣可以参考: 为什么子类对象可以赋值给父类,而父类对象不可以赋值给子类呢?.

      3.3 简单总结: 父类和子类对象的赋值转换.

        这个地方我们简单总结一下,之前的时候,C语言,允许一些相关类型的转换,有些是隐式类型转换,也有一些是强转,但是不相关类型不能进行转换.
        CPP出来了之后呢,为了兼容C语言,所以一些相关类型也跟C语言一样.但是继承这一块父子类之间就不太一样了.一个很大的区别是父类可以直接被赋值/引用/
      指针指向子类,并且中间并不产生临时变量(至于为什么不会产生临时变量,可以简单理解为编译器做了特殊处理).
        父对象能不能给子类对象呢?在当前阶段认为不可以,其实有些情况是可以的.

      好的, 从上文来看, 我们讨论了父子类的概念(继承的概念), 父子类之间的权限问题, 父子类之间的类型转换问题, 那么我想问父子类之间的作用域是一个什么情况呢? 好的, 我们下面开始介绍: 父子类作用域相互独立!

      4. 继承的作用域

      我们CPP中有四种域, 即局部域, 全局域, 命名空间域 和 类域. 因为继承的引入, 所以类域又可以分为父类域和子类域, 父子域没啥关系. 不过有个知识点叫隐藏.

      4.1 隐藏

      1.在继承体系中基类和派生类都有独立的作用域。
      2.隐藏: 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访向,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用基类:基类成员显示访问)
      需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
      建议:注意在实际中在继承体系里面最好不要定义同名的成员。

      强调: 我这里在说一下隐藏的概念哈, 这个隐藏啊, 不是说子类和父类有函数名相同的情况下直接不继承父类的同名函数了, 而意思是呢? 就是父子类是继承关系, 并且父子类两个类域中有两个同名函数, 此时编译器在访问的时候, 会优先去访问离得近的那个函数, 对于子类对象来说就先访问自己的那个函数, 如果想通过子类对象去访问继承来的同名函数也可以, 指定类域访问就行. 这里本质上说的是一种编译器优先访问规则! 而不是说的是继不继承的规则!

      class Father2
      {
      public:
      	int a = 1;
      	void func()
      	{
      		cout << " father " << endl;
      	}
      };
      
      class Son2 : public Father2
      {
      public:
      	int a = 2;
      	void func()
      	{
      		cout << " son " << endl;
      	}
      };
      
      void test3()
      {
      	Son2 son2;
      	cout << son2.a << endl;//访问的是son中的变量
      	son2.func();//访问的是son中的函数
      	cout << son2.Father2::a << endl;//访问的是father中的变量
      	son2.Father2::func();//访问的是father中的函数
      }
      

      为了更好的去理解这个点, 我们下面来看一道题:
      CPP中的继承语法
      在main函数中, 第4行和第5行调用的代码都会构成隐藏, 并且都会优先去匹配子类中的函数. 但是第4行的参数对应起来了, 因此可以调用成功, 第5行去匹配子类中的函数参数不对应, 此时会报错!

      4.2 重定义(隐藏) 与 函数重载

      我们下面来辨析一下这两个概念, 因为老有人喜欢搞错~

      首先, 重定义和函数重载没一点关系!

      重定义, 又叫隐藏, 说的是两个有继承关系的两个类, 因为函数同名了, 编译器必须优先去匹配一个(类似于就近原则), 说的是编译器优先匹配函数的规则. 并且需要强调的是在子类对象中两个同名函数都同时存在, 只不过在不指定类域的情况下编译器会优先去匹配子类对象自己的!!!
      函数重载, 在同一个类域下, 两个同名不同参(不包含返回值类型)的函数构成重载. 说白了就是两个函数可以同时存在, 在C语言中两个同名函数在同一个域下是不允许的!

      下面我们再拓展一个知识: 域

      域(作用域)
      CPP中存在四种域
      局部域: 一个"{}"就是一个局部域
      全局域: "{}"外就是全局
      类域: class struct里面就是类域
      命名空间域: "namesapce"所定义的域


      那? 有什么区别?
      两个点, 第一个点: 四个域都影响变量/函数/成员的使用范围.
      第二个点, 局部域和全局域会影响变量/对象的生命周期.

      5.子类中的默认成员函数. (重要程度: ⭐⭐⭐⭐⭐)

      我们先来回忆一下默认成员函数有哪些?

      • 构造函数
      • 拷贝构造
      • 赋值运算符重载函数
      • 析构函数
      • 取地址重载函数 包括const和非const两个函数.

      好的, 下面我们来说一下两个父子类之间的默认成员函数的问题.

      5.1 构造函数

        对于子类,子类的默认构造函数对于自己的独有的那一部分会根据类型区分,如果是内置类型不做处理,如果是自定义类型会调用对应类型的默认构造.而对于继承父类的那一部分,会去调用父类的默认构造,如果父类的默认构造不存在,那么会报错.
      如果有下面场景该如何处理呢?
        现在父类写了一个带参的构造函数,没有其他构造子类中如果不写构造函数,那么就会报错,因为子类会走编译器自己生成的默认构造函数,在其中会走初始化列表,初始化列表里会调用父类的构造函数.那如果现在要显示写子类的构造函数,那么要在子类初始化列表里指明调用父类的带参构造,这样编译器才不会报错.子类在初始化自己独有的类型之前,会先把从父类继承过来的变量通过调用父类构造函数的方式初始化

      5.2 拷贝构造函数

        拷贝构造函数,假设是浅拷贝情况下,子类中你不写拷贝构造函数会自动生成拷贝构造函数,对于这个拷贝构造函数他会先调用父类的拷贝构造函数把从父类继承来的那一部分成员初始化,之后再把自己的成员分成自定义类型和内置类型,对于内置类型他会直接进行值拷贝,对于自定义类型他会调用对应的拷贝构造函数.
      CPP中的继承语法

      5.3 赋值运算符重载函数

        对于CPP继承中的子类赋值函数,如果我们在子类中没有写,他也会有,这是CPP的基本语法,之后对于这个编译器写的赋值函数,他会先调用父类的赋值函数,完成父类部分的成员赋值,然后对于自己独有的成员他会区分为内置类型和自定义类型,对于内置类型他会直接值拷贝,对于自定义类型,他会调用对应的赋值函数,

      CPP中的继承语法

      5.4 析构函数

        如果子类的析构函数不用自己写,编译器会自动生成一个,对于这个自动生成的,他对于我们类中的内置类型忽略,对于我们类中的自定义类型会去调用对应的析构函数.最后再调用父类的析构函数.
      CPP中的继承语法

      6. 继承与友元

      继承对于友元函数的态度是相互独立的,也就是说一个函数是父类的友元,也不能访问子类,是子类的友元函数可以访问被继承下来的父类成员,因为父类的成员被子类继承下来了,是属于子类的。

      这个地方没有什么好说的, 下面是代码示例:

      class Student;//类的声明
      class Person
      {
      public:
      	friend void Display(const Person& p, const Student& s);
      
      protected:
      	string _name = "1"; // 姓名
      };
      
      class Student : public Person
      {
      protected:
      	string _s = "2";
      };
      
      void Display(const Person& p, const Student& s)
      {
      	cout << p._name << endl;//友元函数仅可以访问父类的东西
      	//cout << s._stuNum << endl;//报错
      }
      
      void test81()
      {
      	Person p;
      	Student s;
      
      	Display(p, s);
      }
      
      class Student;//类的声明
      class Person
      {
      public:
      protected:
      	string _name = "1"; // 姓名
      };
      
      class Student : public Person
      {
      	friend void Display(const Person& p, const Student& s);
      
      protected:
      	string _s = "2";
      };
      
      void Display(const Person& p, const Student& s)
      {
      	cout << s._name << endl; //友元函数可以访问子类继承父类的东西
      	cout << s._s << endl; //友元函数仅可以访问子类的东西
      	//cout << p._name << endl;//此时去访问父类的东西会报错
      }
      
      void test81()
      {
      	Person p;
      	Student s;
      
      	Display(p, s);
      }
      

      7. 继承与静态成员

      对于一般的变量,父类对象有一份,继承他的子类对象也有一份(前提是父类变量不是私有的哈)。
      对于静态变量比较特殊,CPP规定只有一份,既属于父类,也属于子类。请注意,整个父类无论有多少对象,都只有一个static变量!

      基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。

      应用:统计下生成了多少个父类+子类对象

      class Person
      {
      public:
      	Person() { ++_count; }
      protected:
      	string _name; // 姓名
      public:
      	static int _count; // 统计人的个数。
      };
      int Person::_count = 0;
      class Student : public Person
      {
      protected:
      	int _stuNum; // 学号
      };
      class Graduate : public Student
      {
      protected:
      string _seminarCourse; // 研究科目
      };
      void TestPerson()
      {
      	Student s1;
      	Student s2;
      	Student s3;
      	Graduate s4;
      	cout << " 人数 :" << Person::_count << endl;
      	Student::_count = 0;
      	cout << " 人数 :" << Person::_count << endl;
      }
      

      8. 多继承(重要程度: ⭐⭐⭐⭐⭐)

      8.1 多继承

      实际上, 除了单继承之外, CPP还提供了强大的多继承语法!
      CPP中的继承语法
        前面讲的都是单继承,CPP中也有多继承机制,在多继承机制下,CPP为多种场景提供了更好的支持,但是,多继承中的菱形继承存在一定的小问题!
        多继承实际上是一种合理的设计, 因为依照我们的生活经验来看, 有许多东西都同时属于多个方面的, 比如说, 我们经常吃的西红柿, 就既属于水果, 也属于一种蔬菜, 因此多继承的设计也是可以理解的.
        但是因为多继承语法的设计, 必然会出现"菱形继承"问题.

      8.2 多继承 -> 问题: 菱形继承

      CPP中的继承语法
      菱形继承再最下面的那个类当中, 会存放两份A的内容.

      我们不难发现, 对于菱形继承存在两个很大的问题:

      1. 数据冗余: 我们看菱形继承的空间图可以发现, person 的数据继承了两份, 这显然是冗余的.
      2. 二义性: 我们在使用的时候, 编译器并不清楚我们要访问两份数据的哪一个, 因此具有二义性问题.
      class Person
      {
      public:
      	string _name; // 姓名
      };
      class Student : public Person
      {
      protected:
      	int _num; //学号
      };
      class Teacher : public Person
      {
      protected:
      	int _id; // 职工编号
      };
      class Assistant : public Student, public Teacher
      {
      protected:
      	string _majorCourse; // 主修课程
      };
      void test10()
      {
      	// 这样会有二义性无法明确知道访问的是哪一个
      	Assistant a;
      	//a._name = "peter";
      	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
      	a.Student::_name = "xxx";
      	a.Teacher::_name = "yyy";
      }
      

      为了解决上面问题: 我们CPP提出了解决方案 – 菱形虚拟继承.

      8.3 菱形继承问题的解决: 菱形虚拟继承

      虚拟继承: 可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。

      CPP中的继承语法
      下面是菱形虚拟继承代码:

      class Person
      {
      public:
      	string _name; // 姓名
      };
      class Student : virtual public Person
      {
      protected:
      	int _num; //学号
      };
      class Teacher : virtual public Person
      {
      protected:
      	int _id; // 职工编号
      };
      class Assistant : public Student, public Teacher
      {
      protected:
      	string _majorCourse; // 主修课程
      };
      void test10()
      {
      	// 这样会有二义性无法明确知道访问的是哪一个
      	Assistant a;
      	a._name = "peter";
      	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
      	a.Student::_name = "xxx";
      	a.Teacher::_name = "yyy";
      
      	cout << a._name << endl;//yyy
      	cout << a.Student::_name << endl;//yyy
      	cout << a.Teacher::_name << endl;//yyy
      
      	cout << &a._name << endl;//000000860A0FFAA8
      	cout << &a.Student::_name << endl;//000000860A0FFAA8
      	cout << &a.Teacher::_name << endl;//000000860A0FFAA8
      }
      

      语法虽然是感觉比较简单, 但是他的原理是什么呢? 我们从内存的角度来看一看这个问题:
      首先, 如果是虚拟继承的类, 会影响继承子类的对象模型:
      CPP中的继承语法
      然后, 我们来看虚拟菱形继承中的 D 类, 我们利用下面代码进行验证:

      class A
      {
      public:
      	int _a;
      };
      
      class B : public A
      //class B : virtual public A
      {
      public:
      	int _b;
      };
      
      class C : public A
      //class C : virtual public A
      {
      public:
      	int _c;
      };
      class D : public B, public C
      {
      public:
      	int _d;
      };
      void test11()
      {
      	//这是在没有使用虚拟继承情况下的菱形继承,看d的内存空间
      	D d;
      	d.B::_a = 1;
      	d.C::_a = 2;
      	d._b = 3;
      	d._c = 4;
      	d._d = 5;
      }
      

      CPP中的继承语法
      CPP中的继承语法
      实际上, 上面的菱形虚拟继承我们也可以通过测验发现, CPP 中通过对对象模型做修正从而实现了解决这种问题. 我们称存放偏移量的表格叫做虚基表, 而对象中指向虚基表的指针我们称为虚基表指针.

      8.4 关于"切片"的内存级理解

      同时, 我们也可以通过切片的概念去进一步理解上面概念, 我们的切片分为赋值, 指针和引用.

      • 赋值CPP中的继承语法

      • 指针CPP中的继承语法

      • 引用CPP中的继承语法

      菱形继承这么复杂, 真的有人用菱形继承去写代码吗? 库中就有一个例子:

      8.5 菱形虚拟继承的实例

      CPP中的继承语法
      上面在CPP标准库当中, 就有一个菱形虚拟继承的例子, 是IO类.
      有兴趣自己观摩一下: 链接

      9. 继承 与 组合

      什么是组合呢?

      class A
      {
      public:
      	int _a;
      };
      
      class B
      {
      public:
      	A _aa;//组合
      	int _b;
      };
      
      void test13()
      {
      	B b;
      }
      

      public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
      建议: 优先使用对象组合,而不是类继承 。
      继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
      对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
      写代码时, 是选择组合还是继承?
      实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

      CPP中的继承语法

      知识拓展: 良好代码结构, 低耦合, 高内聚
      什么是耦合呢? 就是说一个软件的不同模块之间的关联程度, 如果两个功能模块之间联系度很高, 我们就成为高耦合, 而反之我们称为低耦合.
      我们建议写代码要实现低耦合, 因为低耦合的好处就是极大的方便代码维护. 一份高耦合的软件代码会"牵一发而动全身", 造成代码维护很难的情况, 这时候可能会需要去重构代码.
      什么是内聚呢? 就是说一个软件的同一个功能下的代码之间的关联程度.
      我们建议写代码要实现高内聚. 说白了就是同一个类中, 同一个功能下代码之间的关联程度要尽可能高, 而不是东拼西凑式的, 好处就是同一个功能下的代码关联度高, 高了就可以动一个地方而影响很多地方, 不至于一个一个地方修改, 从而降低维护成本.

      下面来自文心一言补充:
      您说得非常对,关于代码结构中的低耦合和高内聚的理解非常准确。让我稍微补充和细化一下这两个概念:
      低耦合(Low Coupling)
      耦合指的是模块或组件之间相互依赖的程度。低耦合意味着模块之间的连接尽可能松散,每个模块可以独立地变化而不影响其他模块。实现低耦合的好处包括:

      1. 提高代码的可维护性:由于模块之间关联度低,修改一个模块时不需要担心会破坏其他模块的功能。
      2. 提高代码的可重用性:独立的模块更容易在不同的项目中重用。
      3. 提高代码的灵活性:系统可以更容易地适应需求的变化,因为变化通常只影响单个模块。
        实现低耦合的一些方法包括:
        ● 接口隔离:通过定义明确的接口来限制模块之间的通信。
        ● 依赖注入:将依赖关系从模块中抽离出来,通过注入的方式提供给模块。
        ● 事件驱动架构:模块通过发布和订阅事件来进行通信,而不是直接调用对方的方法。
        高内聚(High Cohesion)
        内聚指的是模块内部元素之间的关联程度。高内聚意味着模块内的代码紧密相关,共同实现一个明确的功能。实现高内聚的好处包括:
      4. 提高代码的可读性:由于代码紧密相关,更容易理解模块的功能和目的。
      5. 提高代码的可靠性:模块内部的代码逻辑清晰,更容易发现和修复错误。
      6. 降低维护成本:由于功能集中,修改或扩展功能时更容易定位到相关的代码。
        实现高内聚的一些方法包括:
        ● 单一职责原则:确保每个模块只负责一个功能,避免将多个功能混合在一个模块中。
        ● 重构:通过提取方法、类或模块来将相关代码聚集在一起。
        ● 领域驱动设计:根据业务领域的知识来组织代码,使模块与业务概念对应。
        综上所述,低耦合和高内聚是良好代码结构的重要特征,它们有助于提高代码的可维护性、可重用性和灵活性。在编写代码时,我们应该努力追求这两个目标。

      10. 继承相关的面试题

      1. CPP有多继承,为啥Java没有。
        这个主要是历史问题,CPP我们知道是出现比较早的(相对于Java),作为一个高级语言的先驱者,刚开始设计者并没有预料到多继承的坑(复杂性问题->比如菱形多继承问题),后来CPP多继承语法支持之后,经过实践才发现多继承中菱形继承是个坑。之后设计者为了更好的兼容之前的语法(这里如果直接把多继承删掉就违背了语言的向前兼容性),所以设计者为了弥补这个坑就提出了虚拟菱形继承的弥补方法。
        Java作为后来新生的高级语言,参考了CPP的前车之鉴,认为多继承没有必要(或者说过于复杂),直接选择不支持CPP多继承的语法。
      2. 多继承问题是什么?多继承有问题吗?
        多继承本身是没有问题的,但是多继承语法支持就意味着就一定会有菱形继承,菱形继承是有问题的。
      3. 菱形继承的问题是什么?
        数据冗余:多存储了一份数据(这里的数据是指父类的父类可以继承的那一部分数据)。
        二义性:因为数据继承了两份,所以要指定。
      4. 如何解决菱形继承问题?
        虚拟菱形继承。在中间父类的时候虚拟继承即可。
        此时解决了二义性问题,数据冗余就随之解决了。

      好的, 本期内容分享到这里.

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.csdn.net/2302_79031646/article/details/143991017,作者:睡觉待开机,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:【分治——归并排序】排序数组的归并方法

      下一篇:手把手教你使用JavaScript打造一款扫雷游戏

      相关文章

      2025-05-19 09:04:44

      js小题2:构造函数介绍与普通函数对比

      js小题2:构造函数介绍与普通函数对比

      2025-05-19 09:04:44
      new , 关键字 , 函数 , 对象 , 构造函数
      2025-05-19 09:04:30

      【Canvas技法】辐射式多道光影的实现

      【Canvas技法】辐射式多道光影的实现

      2025-05-19 09:04:30
      代码 , 函数 , 实现
      2025-05-19 09:04:22

      外设驱动库开发笔记54:外设库驱动设计改进的思考

      外设驱动库开发笔记54:外设库驱动设计改进的思考

      2025-05-19 09:04:22
      使用 , 函数 , 初始化 , 定义 , 对象
      2025-05-19 09:04:14

      C语言字符函数和字符串函数--(超全超详细)

      C语言字符函数和字符串函数--(超全超详细)

      2025-05-19 09:04:14
      函数 , 字符 , 字符串
      2025-05-19 09:04:14

      复杂度的OJ练习

      复杂度的OJ练习

      2025-05-19 09:04:14
      代码 , 复杂度 , 思路 , 数组 , 算法
      2025-05-16 09:15:24

      如何将一串数字用函数的方法倒过来(C语言)

      如何将一串数字用函数的方法倒过来(C语言)

      2025-05-16 09:15:24
      函数 , 数字 , 数组
      2025-05-14 10:33:31

      【数据结构】第一章——绪论(2)

      【数据结构】第一章——绪论(2)

      2025-05-14 10:33:31
      函数 , 实现 , 打印 , 理解 , 算法 , 输入 , 输出
      2025-05-14 10:33:31

      计算机小白的成长历程——习题演练(函数篇)

      计算机小白的成长历程——习题演练(函数篇)

      2025-05-14 10:33:31
      函数 , 字符串 , 数组 , 知识点 , 编写 , 迭代 , 递归
      2025-05-14 10:33:25

      webpack5基础--13_生产模式介绍

      生产模式是开发完成代码后,我们需要得到代码将来部署上线。

      2025-05-14 10:33:25
      npm , 代码 , 优化 , 指令 , 模式 , 运行
      2025-05-14 10:33:25

      30天拿下Rust之高级类型

      Rust作为一门系统编程语言,以其独特的内存管理方式和强大的类型系统著称。其中,高级类型的应用,为Rust的开发者提供了丰富的编程工具和手段,使得开发者可以更加灵活和高效地进行编程。

      2025-05-14 10:33:25
      Rust , type , 代码 , 函数 , 类型 , 返回
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5252044

      查看更多

      最新文章

      【Canvas技法】辐射式多道光影的实现

      2025-05-19 09:04:30

      外设驱动库开发笔记54:外设库驱动设计改进的思考

      2025-05-19 09:04:22

      C语言字符函数和字符串函数--(超全超详细)

      2025-05-19 09:04:14

      复杂度的OJ练习

      2025-05-19 09:04:14

      如何将一串数字用函数的方法倒过来(C语言)

      2025-05-16 09:15:24

      webpack5基础--13_生产模式介绍

      2025-05-14 10:33:25

      查看更多

      热门文章

      Python 函数调用父类详解

      2023-04-23 09:44:31

      C#8.0新语法

      2023-02-07 10:34:04

      游戏编程之六 游戏编程的特点

      2024-09-25 10:13:46

      实现远程线程DLL注入

      2023-05-04 08:57:15

      (10)Qt对象模型

      2023-02-13 07:55:59

      Java 程序设计 第6章 异常与断言 笔记

      2023-02-24 09:42:48

      查看更多

      热门标签

      java Java python 编程开发 代码 开发语言 算法 线程 Python html 数组 C++ 元素 javascript c++
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      深入解析 Java 最新技术:模块化、虚拟线程与功能增强

      \'yii\\base\\InvalidConfigException\' with message \'Invalid CAPTCHA action ID:

      Java 8 Lambda表达式详解

      初始函数模板和类模板

      Python算法学习[11]—图像问题&问题描述与实现

      java.super详解

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号