【C++进阶1--继承】面向对象三大特性之一(附菱形继承讲解

news2024/9/22 17:29:22

继承是面向对象中很重要的特性,今天就来讲讲C++中的继承。

文中不足错漏之处望请斧正!


什么是继承?

是一种类的复用,可以让B类继承,从而使B类获得A类的所有成员。

A类叫做父类或基类,B类叫做子类或派生类。

而继承分为单继承和多继承。


单继承

是什么

子类只继承一个父类。

怎么用

class 父类
{};

class 子类 : 继承方式 父类
{};

子类可通过派生类列表明确从哪个类而来。

class Base
{
    int _b;
};

class Derive : public Base
{
    int _d;
};

int main()
{
    Base b;
    Derive d;
    return 0;
}

Derive类后跟冒号,冒号后是类派生列表,public是继承方式,Base是要继承的类。

Derive就是子类,Base就是父类。
在这里插入图片描述

通过继承我们能看见,Derive对象确实成功继承了Base类的成员。

但父类的不同成员,按照继承方式在子类中应该会得到不同的访问限定符吧?那限定符继承后到底是什么样的,有什么规律吗?

有的。

第一点:继承中的访问权限

在这里插入图片描述

子类中父类成员的访问权限限定符规则,可看作一个min函数的调用结果:

min(父类成员在父类中的访问权限, 继承方式)

*private < protected < public

*父类的private成员同样会继承到子类,只是不可见

从这里其实我们也可以看出,C++想覆盖尽可能多的继承场景,但是实际上,除了public继承外,其他都不怎么用:

  1. protected继承:子类外不能访问,扩展性变差
  2. private继承:子类都不能访问了,丢了继承的初衷

这里我们就可以谈谈protected有什么用了。

protected

其实就是能被子类访问的private成员。

第二点:父类成员的访问权限

父类的

  • public成员:父子类内外都能访问
  • protected成员:父子类内可以访问
  • private成员:只有父类内可以访问

相关概念

#切片(切割)

子类对象可单向赋值给父类对象,父类对象会拿到子类对象中父类的一部分,没有类型转换。

“父类对象会拿到子类对象中父类的一部分”,就像把子类对象中的父类部分切出来给父类一样,所以这种行为叫做切片。

注意:是子类单向可赋值给父类,父类并不能赋给子类,不然子类多出的一部分哪里来。

场景:

  • 父类对象 = 子类对象
  • 父类对象指针 = &子类对象(指向子类对象中父类的一部分)
  • 父类对象引用 = 子类对象(指向子类对象中父类的一部分)

#隐藏(重定义)

隐藏是一种子类对父类同名成员的屏蔽。

*若想访问父类同名成员,指定父类类域。

这里容易和重载弄混:

  • 隐藏:父子类中只要同名就隐藏
  • 重载:同一作用域中的函数同名,参数列表不同才重载

子类的默认成员函数

一句话:父子类部分分开处理。

构造和析构是先处理父类还是子类?

  • 构造:先父后子
  • 析构:先子后父
  • 父类构造 → 子类构造 → 子类析构 → 父类析构

还有一点很特殊的,这是为了兼容多态而添加的一个特性。

继承中的Destructor

父子类的析构,函数名都会被处理成destructor,是因为多态需要父子类析构同名,才有机会构成重写(不重写析构可能内存泄漏)。

因为父子类析构构成隐藏,所以调用父类析构是要指定类域的,不过一般不用调,因为子类析构调用完后会自动调用父类析构。

继承中的友元

友元关系不会被继承,父类的友元不是子类的友元(你爹的朋友不一定是你的朋友)。

继承中的static成员

和以前的概念吻合:static成员在整个继承体系中只有一个。

类指针的意义

这里强调一下类指针的意义,在继承中容易搞混。

  • 类指针访问属性:->的意义是解引用找属性
  • 类指针访问方法:->的意义是传递this调用代码段的方法
  • 类指针访问static成员:->的意义是访问数据段的静态成员
struct A {
    int _a;

    void func() { cout << "func called..." << endl;}
    static int _s;
};

int A::_s = 999;

int main() {
    A *ptr = nullptr;

    cout << ptr->_a << endl; //err
    ptr->func();
    cout << ptr->_s << endl;

    return 0;
}

主要看有没有解引用找属性。


多继承

是什么

一个子类继承于多个父类多继承。

为什么

有场景:我既需要A类的属性,也需要B的属性

#菱形继承

是什么

在这里插入图片描述

class A {
public:
    int _a;
};

class B: public A {
public:
    int _b;
};

class C: public A { 
public:
    int _c;
};

class D: public B, public C {
public:
    int _d;
};

设计类的时候尽量不要设计这种类,非常复杂,难以玩转。

数据冗余和访问二义性

int main() {
    D d;
    d._a = 10;
    return 0;
}

err:
non-static member '_a' found in multiple base-class subobjects of type 'A':
    class D -> class B -> class A
    class D -> class C -> class A
    d._a = 10;
      ^
  • 菱形继承中最开始的基类属性会被最后的派生类继承两份
  • 访问_a的时候,不确定是B::_a还是C::_a

那么如何解决这两个问题呢?

虚拟继承

虚拟继承的核心思想是共享公共基类的成员。

通过virtual继承方式派生出的派生类,实际上并不拥有虚基类成员,而只能通过偏移量访问。

*被派生类使用虚拟继承来继承的基类称为虚基类。

怎么用

class A {
public:
    int _a;
};

class B: virtual public A { //通过偏移量访问_a
public:
    int _b;
};

class C: virtual public A { //通过偏移量访问_a
public:
    int _c;
};

class D: public B, public C {
public:
    int _d;
};

int main() {
    D d;
    d._a = 10;
    return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

本来冗余的_a,不再被派生类拥有,而是通过偏移量访问了。

虚拟继承原理

通过偏移量访问,它是怎么个访问法?

派生类虚拟继承自基类,派生类实际上会得到一个指针,这个指针指向一个关于A类的偏移量表。

偏移量表中有偏移量,还有A类部分的地址,因此通过偏移量表就能访问A类成员。这个偏移量表就叫做虚基表

int main() {
    D d;

    d._b = 1;
    d._c = 2;
    d._d = 3;
    d._a = 4;

    return 0;
}

在这里插入图片描述

*32位机

&d,在前4个字节,我们首先看到的就是一个指针。查看指针指向的内容,发现了前四个字节是零值(应该是空指针),而后就是一个0x14,即20,这就是d对象要访问A类成员_a需要的偏移量。你数数,从d的第一个字节开始,往后20个字节,就是_a。


继承和组合

继承是一种“is-a”的感觉,比如Student是Person的复用。这种复用称为白箱复用,会暴露底层细节。
组合是一种“has-a”的感觉,比如B类中有A类的对象。这种复用称为黑箱复用,不暴露底层细节。

其中,继承耦合度 > 组合耦合度。


小练

class B {public: int b;};

class C1: public B {public: int c1;};

class C2: public B {public: int c2;};

class D : public C1, public C2 {public: int d;};

A.D总共占了20个字节

B.B中的内容总共在D对象中存储了两份

C.D对象可以直接访问从基类继承的b成员

D.菱形继承存在二义性问题,尽量避免设计菱形继承

解:
A.C1中b和c1共8个字节,C2中c2和b共8个字节,D自身成员d 4个字节,一共20字节

B.由于菱形继承,最终的父类B在D中有两份

C.子类对象不能直接访问最顶层基类B中继承下来的b成员,因为在D对象中,b有两份,一份是从C1中继承的,一份是从C2中继承的,直接通过D的对象访问b会存在二义性问题,在访问时候,可以加类名::b,来告诉编译器想要访问C1还是C2中继承下来的b。

D.菱形继承存在二义性问题,尽量避免设计菱形继承,如果真有需要,一般采用虚拟继承减少数据冗余


今天的分享就到这里了,感谢您能看到这里。

这里是培根的blog,期待与你共同进步!

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

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

相关文章

C++ 构造函数-2

构造函数-2 构造函数体赋值 在对象创建的时候&#xff0c;编译器会调用构造函数&#xff0c;给对象当中的成员赋一个合适的初始值。 class Date { public: Date(int year, int month, int day) { _year year; _month month; _day day; } private: int _year; int _month; i…

Android framework工程师一定要知道的高级技巧

作为一名android framework工程师&#xff0c;你是否对Android framework的一些高阶使用技巧感到陌生&#xff1f;想了解更多的细节&#xff1f;如果是这样&#xff0c;那么就请你读下去。本篇文章我将为大家相信介绍Android framework的高阶技巧&#xff0c;帮助你成为一名高级…

【Nginx】【SSL】Nginx上配置ssl证书

配置需要有自己的域名和云主机&#xff1b;域名已经解析到主机&#xff1b;安装好Nginx 一、申请免费版的SSL证书 1、阿里云可以申请免费版的SSL证书 阿里云搜索 ssl 找到 数字证书管理服务/SSL 证书>免费证书&#xff1b;申请一个免费的 2、下载SSL证书到本地&#xf…

贝叶斯公式与全概率公式的理解。

1.贝叶斯与全概率公式解释 1.全概率公式定义 即若在某个场景下&#xff0c;可找到一个完备事件组 Ai ( i 1,2,3…n)。 则对任一与该场景有关的事件 B&#xff0c;都可以分割成无数个小事件&#xff08;由不同因素引起的事件&#xff09; 有&#xff1a;   B B ∩ A1 ∪ A2…

给你的项目启动提提速:Lazy Initialization

前言 在一个名为种花家的小镇上&#xff0c;生活着一群热爱编程的人。他们致力于构建出高效、可维护的软件系统&#xff0c;而 Spring Boot 框架成为了他们的不二之选。这个小镇上的人们每天都在用 Spring Boot 框架创造着令人瞩目的应用程序。 然而&#xff0c;随着时间的推移…

Java 学习之线程

1、引入线程的优点&#xff1a; 1&#xff09;充分利用cup资源 2&#xff09;简化编程模型 3&#xff09;简化异步事件处理 4&#xff09;使GUI更有效率 5&#xff09;节约成本 2、线程使用&#xff1a;在Java中创建线程有几种方法&#xff0c;每个Java程序至少包含一个线…

软件工程开发文档写作教程(05)—可行性研究报告写作规范

本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl本文参考资料&#xff1a;电子工业出版社《软件文档写作教程》 马平&#xff0c;黄冬梅编著 软件工程开发文档现状 一个软件项目从立项到结尾共有几个阶段&#xff1a;立项&#xff0c;…

动画图解常见串行通讯协议:SPI、I²C、UART、红外分析

一、SPI传输 图1&#xff1a;SPI 数据传输 图1.2&#xff1a;SPI数据传输&#xff08;2&#xff09; ​ 图1.3&#xff1a; SPI时序信号 二、IC传输 图1.2.1&#xff1a; I2C总线以及寻址方式 三、UART传输 图1.3.1&#xff1a;PC 上通过UART来调试MCU 图1.3.2&#xff1a;R…

深入探究语音识别技术:原理、应用与实现

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Qt-AES加密库

Qt-AES加密库 AES在线加解密工具[1] Qt-AES加密库[2] Qt AES/DES加密算法库 软件/文件/任意长度字符串加密 试用期许可使用方法软件试用期算法对称加密和非对称加密非对称加密&#xff08;Asymmetric Cryptography&#xff09;实例总结加密算法 [3] Qt笔记-AES加密[4] AES 加密…

【Betternet怎么用呢?】Betternet下载使用完整教程

Betternet是一款非常历史悠久的访问世界互联网行业的工具了。知道Betternet的用户&#xff0c;也应该是比较久的互联网用户了。早在2015年左右&#xff0c;那时候的betternet就是很多外贸行业的朋友上gmail以及Facebook上开发客户必备的工具了。 因为那时候betternet使用简单&…

Pandas + ChatGPT 超强组合,pandas-ai :交互式数据分析和处理新方法

Python Pandas是一个为Python编程提供数据操作和分析功能的开源工具包。这个库已经成为数据科学家和分析师的必备工具。它提供了一种有效的方法来管理结构化数据(Series和DataFrame)。 在人工智能领域&#xff0c;Pandas经常用于机器学习和深度学习过程的预处理步骤。Pandas通过…

第7章链接:静态链接、符号表、符号解析

文章目录 7.2 静态连接7.3 目标文件7.4 可重定位目标文件7.5 符号和符号表7.6 符号解析7.6.1 链接器如何解析多处定义的全局符号7.6.2 与静态库链接7.6.3 链接器如何使用静态库来解析引用 7.2 静态连接 像 Unix ld 程序这样的静态链接器&#xff08;static linker&#xff09;…

C语言函数大全-- u 开头的函数

C语言函数大全 本篇介绍C语言函数大全-- u 开头的函数 1. ultoa 1.1 函数说明 函数声明函数功能char *ultoa(unsigned long value, char *str, int base);用于将无符号长整型数转换成指定基数下的字符串表示 参数&#xff1a; value &#xff1a; 要转换的无符号长整型数st…

docker容器无法执行vim【已解决】

docker容器无法执行vim【已解决】 docker容器中执行vim失败安装文件没更换之前&#xff0c;速度非常的慢【失败】这里我更换了163的但是报错【失败】这里我更换了阿里的第一种报错【成功】&#xff1a;&#xff1a;&#xff1a;&#xff1a;这里我更换了阿里的第二种成功 完整步…

struct模块进行数据打包

原理&#xff1a; 将一组简单数据进行打包&#xff0c;转换为bytes格式发送。或者将一组bytes格式数据&#xff0c;进行解析。 接口使用 Struct(fmt) 功能: 生成结构化对象 参数&#xff1a;fmt 定制的数据结构 st.pack(v1,v2,v3…) 功能: 将一组数据按照指定格式打包转换为by…

分子动力学基础知识

分子动力学基础知识 目前主要存在两种基本模型&#xff1a;其一为量子统计力学, 其二为经典统计力学。 量子统计力学 基于量子力学原理, 适用 于微观的, 小尺度, 短时 间的模拟&#xff0c;可以描述电子 的结构分布&#xff0c;原子间的成 键断键等化学性质。 经典纭计力学…

MySQL原理(七):内存管理和磁盘管理

前言 上一篇介绍了 MySQL 的日志&#xff0c;这一篇将介绍内存管理和磁盘管理相关的内容。 内存管理 MySQL 的数据都是存在磁盘中的&#xff0c;我们要更新一条记录的时候&#xff0c;得先要从磁盘读取该记录&#xff0c;然后在内存中修改这条记录。修改完这条记录后会缓存起…

15 KVM虚拟机配置-体系架构相关配置

文章目录 15 KVM虚拟机配置-体系架构相关配置15.1 概述15.2 元素介绍15.3 AArch64架构配置示例15.4 x86_64架构配置示例 15 KVM虚拟机配置-体系架构相关配置 15.1 概述 XML中还有一部分体系架构相关的配置&#xff0c;这部分配置包括主板&#xff0c;CPU&#xff0c;一些与体…

【2023/05/10】Mitchel Resnick

Hello&#xff01;大家好&#xff0c;我是霜淮子&#xff0c;2023倒计时第5天。 Share Her wistful face haunts my dreams like the rain at night. 译文&#xff1a; 她的热切的脸&#xff0c;如夜雨似的&#xff0c;搅扰着我的梦魂。 Once we dreamt that we were stra…