继承深度剖析

前言

从继承开始就开始C++进阶了,

这一块需要好好学习,这块知识很重要,

坑有点多,所以是面试笔试的常客。

基本概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,

它允许程序员在保持原有类特性的基础上进行扩展,增加功能,

这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,

体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,

继承是类设计层次的复用。

举例:

学校的老师和同学,

他们具有一些相同的属性,

比如:年龄,姓名,性别等等,同时,

也具备一些不同的属性,

如:学生的学号,老师的工号等等

这样我们就可以把相同的属性提取出来,

写到一个类中去,而老师,学生的专属信息则写到自己的类中,

然后将相同的属性继承过来。

师生共同信息:

struct Person
{
	string name;
	string sex;
	int age;
}

学生专属信息:

class Student : public Person
{
protected:
	int _stuid; // 学号
};

老师专属信息:

class Teacher : public Person
{
protected:
	int _jobid; // 工号
};

我们通常把被继承的类叫做基类/父类,

把继承类的类叫做子类/派生类

继承关系和访问限定符

继承方式和访问限定符各有三种:

继承的方式不同,那么子类中继承
到的父类的变量的访问权限就不同

大概有几点:

  1. 父类的private成员在子类是不可见的!
    (继承下来了但不能使用)
  2. 不使用继承,protected与private没有区别
  3. 使用继承,private:类内访问:可以访问;类外访问:不可以访问;子类访问:不可以访问。protected:类内访问:可以访问;类外访问:不可以访问;子类访问:可以访问。public:类内访问:可以访问;类外访问:可以访问;子类访问:可以访问。
  4. 使用时一般使用public
  5. 使用关键字class时默认的继承方式是private
    使用struct时默认的继承方式是public

继承中的作用域

1. 在继承体系中基类和派生类都有独立的作用域。
2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,

这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
4. 注意在实际中在继承体系里面最好不要定义同名的成员

在main函数中定义student对象
后再打印_num默认为子类中的_num
若想打印父类中的_num,需要指定类域


 

但是函数名相同的话
不应该是构成函数重载吗?是的,在同一
作用域下,函数名相同确实构成函数重载
但是父子类是不同作用域,这里是构成隐藏!

父子类赋值兼容规则

子类对象可以赋值给父类对象,基类的对象 / 基类的指针 / 基类的引用(切片)

父类对象不能直接赋值给子类对象

注意这里能够赋值不是隐式类型转换!

子类的默认成员函数

我们知道类的六个默认成员函数,
不显示写系统会自动生成的

子类的默认成员函数有哪些特殊的行为?

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。

如果基类没有默认的构造函数,

则必须在派生类构造函数的初始化列表阶段显示调用。
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。

因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造。
6. 派生类对象析构清理先调用派生类析构再调基类的析构。
7. 因为后续一些场景析构函数需要构成重写,

重写的条件之一是函数名相同那么编译器会对析构函数名进行特殊处理,

处理成destrutor(),所以父类析构函数不加virtual的情况下,

子类析构函数和父类析构函数构成隐藏关系。

继承与友元,继承与静态变量

继承与友元

友元关系不能继承
也就是说基类友元不能访问子类私有和保护成员

继承于静态变量

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

菱形继承和虚拟继承

菱形继承是一个大坑,为了解决这个大坑祖师爷掉了不少头发

先看一下单继承、多继承、菱形继承的形式:

单继承:

多继承:

菱形继承:

类B继承了类A,类C也继承了类A
然而类D继承了类B和C

此时会有一个问题,类D的实例化对象中
有类B和类C,然而B类和C类都有A类
所以说D类对象中的A类成员就重复了!

class A
{
	int _a = 1;
};
class B :public A
{
	int _b = 2;
};
class C :public A
{
	int _c = 3;
};
class D :public B, A
{
	int _d = 4;
};

D对象中有两个_a,一个在B类一个在C类
这就造成了数据冗余,

使用域访问限定符可以勉强解决二义性,但是解决不了数据冗余,

但是可以使用虚拟继承
来解决这一问题:

虚拟继承:在继承前加上virtual关键字

class A
{
	int _a = 1;
};
class B :virtual public A
{
	int _b = 2;
};
class C :virtual public A
{
	int _c = 3;
};
class D :public B, A
{
	int _d = 4;
};

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。虚拟继承不要在其他地方去使用

注意,只用腰部的类加上virtual即可(少用)!

虚拟继承原理

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余

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

可以看到,BC里面多存一个地址,不再是存储a的值了,

这个地址用来指向右边的表,表里面第一个值暂且不管(不是目前的内容)

第二个值则表示偏移量,

例:B里面3上面,然后通过地址找到偏移量,例:B,14,然后地址加偏移量找到a

也就是继承的是同一份a

当然,如果只看当前问题,这个位置直接存偏移量或者地址是没有问题的,

但是如果有多个子类呢,明显还是这种方法更佳!

下面是上面的Person关系菱形虚拟继承的原理解释

继承和组合

public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

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

住:优先使用对象组合,而不是类继承 。

// Car和BMW Car和Benz构成is-a的关系
 class Car{
 protected:
 string _colour = "白色"; // 颜色
 string _num = "陕ABIT00"; // 车牌号
 };
 class BMW : public Car{
 public:
 void Drive() {cout << "好开-操控" << endl;}
 };
 class Benz : public Car{
 public:
 void Drive() {cout << "好坐-舒适" << endl;}
 };
 // Tire和Car构成has-a的关系
 class Tire{
 protected:
   string _brand = "Michelin";  // 品牌
   size_t _size = 17;     // 尺寸
 };
 class Car{
 protected:
 string _colour = "白色"; // 颜色
 string _num = "陕ABIT00"; // 车牌号
  Tire _t; // 轮胎
 };

总结

继承是多态的基础,而笔试面试的时候继承和多态是考察的很多的,

同时这里也有很多坑,稍不注意就会掉进去,

这块知识的学习一定要仔细认真。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用MNIST数据集训练手写数字识别模型

一、MNIST数据集介绍 MNIST 数据集&#xff08;手写数字数据集&#xff09;是一个公开的公共数据集&#xff0c;任何人都可以免费获取它。目前&#xff0c;它已经是一个作为机器学习入门的通用性特别强的数据集之一&#xff0c;所以对于想要学习机器学习分类的、深度神经网络分…

抓包工具 Wireshark 的下载、安装、使用、快捷键

目录 一、什么是Wireshark&#xff1f;二、Wireshark下载三、Wireshark安装四、Wireshark使用4.1 基本使用4.2 过滤设置1&#xff09;捕获过滤器2&#xff09;显示过滤器 4.3 过滤规则1&#xff09;捕获过滤器-规则语法2&#xff09;显示过滤器-规则语法 4.4 常用的显示过滤器规…

js实现一个数据结构——栈

栈的概念就不再赘述&#xff0c;无可厚非的先进后出&#xff0c;而JS又是高级语言&#xff0c;数组中的方法十分丰富&#xff0c;已经自带了push pop方法进行入栈出栈的操作。 1.基本实现 class Stack {constructor() {this.items [];}// 入栈push(item) {this.items.push(i…

【C++入门(1)】命名空间

一、C出世 我们先简单认识下C的来历&#xff0c;C是在C语言的基础上发展来的。 当年C的设计者Bjarne Stroustrup&#xff0c;本贾尼斯特劳斯特卢普先生设计C语言之初&#xff0c;是为了对C语言做出一些更改&#xff0c;弥补C语言在一些方面的不足&#xff0c;或者做出其他的设…

二阶段提交(2pc)协议

二阶段提交&#xff08;2pc&#xff09;协议 1、 简介 二阶段提交算法是一个分布式一致性算法&#xff0c;强一致、中心化的原子提交协议&#xff0c;主要用来解决分布式事务问题。在单体spring应用中我们往往通过一个Transactional注解就可以保证方法的事务性&#xff0c;但…

破解发展难题 台山这家合作社以农业社会化服务助推乡村振兴

风吹稻田千层浪&#xff0c;眼下&#xff0c;台山四九镇的早稻长势喜人&#xff0c;沉甸甸的稻穗迎风而动&#xff0c;已进入破口抽穗的关键期&#xff0c;即将在6月底陆续迎来丰收。在台山市明华汇种养专业合作社管理的稻田里&#xff0c;合作社负责人梁明喜正仔细观察着稻苗的…

算法第六天:力扣第977题有序数组的平方

一、977.有序数组的平方的链接与题目描述 977. 有序数组的平方的链接如下所示&#xff1a;https://leetcode.cn/problems/squares-of-a-sorted-array/description/https://leetcode.cn/problems/squares-of-a-sorted-array/description/ 给你一个按 非递减顺序 排序的整数数组…

#慧眼识模每日PK[话题]##用五种语言说爸爸我爱你[话题]#

#慧眼识模每日PK #用五种语言说爸爸我爱你 你觉得哪个模型回答得更好&#xff1f;欢迎留言 A.蓝 B.紫 更多问题&#xff0c;扫码体验吧&#xff5e; by 国家&#xff08;杭州&#xff09;新型交换中心

Whisper语音识别 -- 自回归解码分析

前言 Whisper 是由 OpenAI 开发的一种先进语音识别系统。它采用深度学习技术&#xff0c;能够高效、准确地将语音转换为文本。Whisper 支持多种语言和口音&#xff0c;并且在处理背景噪音和语音变异方面表现出色。其广泛应用于语音助手、翻译服务、字幕生成等领域&#xff0c;为…

鸿蒙轻内核A核源码分析系列七 进程管理 (3)

本文记录下进程相关的初始化函数&#xff0c;如OsSystemProcessCreate、OsProcessInit、OsProcessCreateInit、OsUserInitProcess、OsDeInitPCB、OsUserInitProcessStart等。 1、LiteOS-A内核进程创建初始化通用函数 先看看一些内部函数&#xff0c;不管是初始化用户态进程还…

收银系统小程序商城商品详情页再升级!

本期导读 1.新增&#xff1a;商品详情页新增商品参数模块&#xff1b; 2.新增&#xff1a;商品详情页新增保障服务模块&#xff1b; 3.新增&#xff1a;线上商城商品新增划线价&#xff1b; 4.新增&#xff1a;线上商城分销商品新增“赚”字标签及预收收益&#xff1b; 5.…

Linux-笔记 全志平台OTG虚拟 串口、网口、U盘笔记

前言&#xff1a; 此文章方法适用于全志通用平台&#xff0c;并且三种虚拟功能同一时间只能使用一个&#xff0c;原因是此3种功能都是内核USB Gadget precomposed configurations的其中一个选项&#xff0c;只能单选&#xff0c;不能多选&#xff0c;而且不能通过修改配置文件去…

我的考研经历

当我写下这篇文章时&#xff0c;我已经从考研 的失败中走出来了&#xff0c;考研的整个过程都写在博客日志里面了&#xff0c;在整理并阅读考研的日志时&#xff0c;想写下一篇总结&#xff0c;也算是为了更好的吸取教训。 前期日志模板&#xff1a;时间安排的还算紧凑&#x…

安鸾学院靶场——安全基础

文章目录 1、Burp抓包2、指纹识别3、压缩包解密4、Nginx整数溢出漏洞5、PHP代码基础6、linux基础命令7、Mysql数据库基础8、目录扫描9、端口扫描10、docker容器基础11、文件类型 1、Burp抓包 抓取http://47.100.220.113:8007/的返回包&#xff0c;可以拿到包含flag的txt文件。…

DDei在线设计器-配置主题风格

DDeiCore-主题 DDei-Core插件提供了默认主题和黑色主题。 如需了解详细的API教程以及参数说明&#xff0c;请参考DDei文档 默认主题 黑色主题 使用指南 引入 import { DDeiCoreThemeBlack } from "ddei-editor";使用并修改设置 extensions: [......//通过配置&am…

【FreeRTOS】内存管理

目录 1 为什么要自己实现内存管理2 FreeRTOS的5中内存管理方法2.1 Heap_12.2 Heap_22.3 Heap_32.4 Heap_4 2.5 Heap_53 Heap相关的函数3.1 pvPortMalloc/vPortFree3.2 xPortGetFreeHeapSize 3.3 xPortGetMinimumEverFreeHeapSize3.4 malloc失败的钩子函数 参考《FreeRTOS入门与…

Python私教张大鹏 Vue3整合AntDesignVue之DatePicker 日期选择框

案例&#xff1a;选择日期 <script setup> import {ref} from "vue";const date ref(null) </script> <template><div class"p-8 bg-indigo-50 text-center"><a-date-picker v-model:value"date"/><a-divide…

原子阿波罗STM32F429程序的控制器改为STM32F407驱动LCD屏

原子大神的阿波罗开发板使用STM32F429IGT6控制器&#xff0c;编程风格也与探索者F407系列有了很大的不同&#xff0c;使用BSP功能模块编程了&#xff0c;也有点类似于安富莱的编程风格了。这种模块式程序风格的优点是更加方便移植&#xff0c;更方便泡系统。 但无奈手里只有F40…

模拟笔试 - 卡码网周赛第二十一期(23年美团笔试真题)

第一题&#xff1a;小美的排列询问 解题思路: 简单题&#xff0c;一次遍历数组&#xff0c;判断 是否有和x、y相等并且相连 即可。 可优化逻辑&#xff1a;因为x和y是后输入的&#xff0c;必须存储整个数组&#xff0c;但是上面说了 **排列是指一个长度为n的数组&#xff0…