[C++ 从入门到精通] 17.基类与派生类关系的详细再探讨

  • 📢博客主页:https://loewen.blog.csdn.net
  • 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
  • 📢本文由 丶布布原创,首发于 CSDN,转载注明出处🙉
  • 📢现在的付出,都会是一种沉淀,只为让你成为更好的人✨

文章预览:

      • 一. 派生类对象模型简述
      • 二. 派生类构造函数
      • 三. 既当父类又当子类(多继承)
      • 四. 不想当基类的类final
      • 五. 静态类型与动态类型
      • 六. 派生类向基类的隐式类型转换
      • 七. 父类子类之间的拷贝与赋值


一. 派生类对象模型简述

若一个类,继承自一个父类(基类),那么该类称之为子类(派生类)。

并且该子类的对象包含两种成分:

  1. 该子对象含有子类自己的对象成分(包括子类自己的成员函数以及成员变量);
  2. 该子对象也含有基类的对象成分(包括基类自己的成员函数以及成员变量);

回顾:基类的指针为什么可以new派生类的对象?

Human* phuman = new Men;

基类指针可以用来new一个子类对象本质上是因为子类对象中含有基类的成分,因此,子类对象也可以当做是一个特殊的父类对象了。实际上,编译器在我们用多态时,帮我们做了隐式的,从派生类到基类的类型转化。而这种转换的好处就是,当需要用到基类引用的地方,你可以用这个派生类对象的引用来代替or当需要用到派生类引用的地方,你可以用这个基类引用来代替。因此我们就可以用多态这种知识来实现更加复杂的代码。


二. 派生类构造函数

派生类实际上是使用基类的构造函数来初始化其基类部分的。即,基类控制基类部分的成员初始化,派生类控制派生类部分的成员初始化。

new myMen;

所以,当我们创建一个派生类的对象时,既会调用派生类的构造函数,也会调用基类的构造函数(调用顺序:先调用基类的构造函数,再调用派生类的构造函数;释放顺序:先调用派生类的析构函数,再调用基类的析构函数)。

那么,当定义派生类对象的时候,如果基类构造函数需要传递参数,该如何完成呢?

class Human
{
public:Human();Human(int);
};

可通过派生类的构造函数初始化列表中为基类构造函数传递参数

如:

class Human {
public:Human (int age):m_Age(age){ cout << "this is Human 的构造函数!" << endl;}vitrual ~Human() {}//为基类析构声明为virtual的!
private:int m_Age;
};
class Men: public Human {
public://在子类的初始化列表中,直接调用父类的构造函数并传参进进去!Men(int age,int a) :Human(age), nums(a) {cout << "this is Son 的构造函数!" << endl;}virtual ~Men() {}//此时子类的析构其实本质上也是virtual的,因为你继承自Human 
private:int nums;
};

这时,定义子类对象时可用:

Men men(10,10);

三. 既当父类又当子类(多继承)

一个类可以既可以作为某一个类的子类,也可以作为另一个类的父类。

class GrandDad{/.../};            
class Dad: public GrandDad{/.../};//GrandDad类为Dad类的直接基类
class Son: public Dad{/.../};     //GrandDad类为Son类的间接基类

继承关系一直继承,构成了一种继承链,最终结果就是派生类Son会包含它的直接基类的成员以及每个间接基类的成员。但是,在实际开发中,尽量少用这种多继承来写代码,不然很容易造成你写的代码难维护,也不易读。


四. 不想当基类的类final

对于不想用于基类的类,C++中给出了 final 关键字,放在不想做基类的类后面(最终类),可以防止我们写代码时误用了不想当基类的类作为基类。

如图:这时Human类不会再被当做基类使用

在这里插入图片描述

注意:若在一个类的成员函数声明后加final关键字,则该类的子类在继承该类时,不可重写该成员函数。

总结C++11中引入的final关键字的用法:

  • 对于不想被子类重写的成员函数,需要用final对基类成员函数进行声明,那么子类就不再有权限对该成员函数进行重写了。
  • 对于不想当做基类的类,用final对类进行声明后,该类就不可以给其他类用作继承时的基类了

五. 静态类型与动态类型

静态类型:变量声明时的类型,编译的时候是已知的。

动态类型: 指针或引用所代表的内存中的对象的类型,在运行的时候才能知道。

只有在基类指针/引用,才存在这种静态类型和动态类型不一致的情况。

Human* pHuman1 = new Men();    //静态类型是Human *,动态类型是Men *
Human& p1 = *pHuman1;          //静态类型是Human &,动态类型是Men &
Human* pHuman2 = new Woman();  //静态类型是Human *,动态类型是Woman *
Human& p2 = *pHuman2;          //静态类型是Human &,动态类型是Woman &

如果不是基类的指针/引用,那么动态类型和静态类型永远都是应该一致的:

Human* pHuman = new Human();   //静态类型是Human *,动态类型也是Human *
Human human;                   //静态类型是Human,  动态类型也是Human 
Man* pman = new Man();         //静态类型是Man*,   动态类型也是Man* 
Man man;                       //静态类型是Man,    动态类型也是Man

六. 派生类向基类的隐式类型转换

Human *phuman = new Men();  //基类指针指向一个派生类对象,编译器隐式地帮我们将Men类对象转换为了pHuman对象
Human &q = *phuman;         //基类引用绑定到派生类对象上

当我们使用多态时,编译器是隐式地帮我们执行了派生类到基类的转化工作的。这种隐式转换只所以能成功,是因为每一个派生类对象中都包含着基类的成分,所以基类的指针或者引用是可以绑定到子类对象的基类部分上的。也就是说,基类对象可以独立存在,也可以作为派生类对象的一部分存在。

但注意:并不存在从基类到派生类的自动类型转换。(因为子类是从基类中继承过来的,因此子类中含有的成分基类中不一定含有)

Men *pmen = new Human ();  //非法!不能将基类转为派生类
Human human;
Men& men = human;          //非法!不能将基类转为派生类(派生类的引用不能绑定到基类对象上去)
Men* pmen = &human;        //非法!不能将基类转为派生类(派生类指针不能指向基类地址)
Men men;
Human* phuman = &men;     //可以,编译器是通过静态类型来推断转换的合法性(派生类Men*可以转换到基类Human*上)
Men* pmen = phuman;       //非法!不能将基类转为派生类(基类Human*不可以转换到派生类Men*上)//但是,如果基类中含有至少一个虚函数的话,就可以通过dynamic_cast<Type*>进行类型转换!
Men* pmen = dynamic_cast<Men* >(phuman);//合法!

七. 父类子类之间的拷贝与赋值

方式一

Men men;
Human human(men);// 用子类对象初始化(拷贝给)基类对象,这个会导致基类的拷贝构造函数的执行

此时调用的是基类的拷贝构造函数,将其形参const Human& thuman中的thuman动态绑定到了子类对象men上。

Human(const Human& thuman) {cout << "拷贝构造函数!" << endl;
}

方式二:用子类对象赋值给基类对象也是合法的

Men men;
Human human;
human = men;  //用子类对象赋值给基类对象,men对象里基类的那部分就被human拿去了

此时调用的是基类的拷贝赋值运算符的重载函数,将其形参const Human& thuman中的thuman动态绑定到了子类对象men上。

Human& operator=(const Human& thuman) {cout << "拷贝赋值运算符函数!" << endl;return *this;
}

结论:用派生类对象为一个基类对象初始化或赋值时,派生类对象只会将自己基类那部分对其进行拷贝或者赋值,派生类部分将被忽略掉

也就是:基类只干基类自己的事,多余的部分不会去操心。


下雨天,最惬意的事莫过于躺在床上静静听雨,雨中入眠,连梦里也长出青苔。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微电网优化(Matlab复现)— 微电网两阶段鲁棒优化经济调度方法_刘一欣

论文链接&#xff1a;微电网两阶段鲁棒优化经济调度方法 - 中国知网 代码链接&#xff1a;https://m.tb.cn/h.5Mg7fCo?tkhnpmWgZiv2R 复现效果&#xff1a; 运行环境&#xff1a;Matlab 2020bCplexyalmip 1 微电网结构 图 1 所示为典型的微电网结构&#xff0c;由可控分布式…

如何在Portainer部署一个web站点到Nginx容器并结合内网穿透远程访问

文章目录 前言1. 安装Portainer1.1 访问Portainer Web界面 2. 使用Portainer创建Nginx容器3. 将Web静态站点实现公网访问4. 配置Web站点公网访问地址4.1公网访问Web站点 5. 固定Web静态站点公网地址6. 固定公网地址访问Web静态站点 前言 Portainer是一个开源的Docker轻量级可视…

基于Vite+Vue3 给项目引入Axios

基于ViteVue3 给项目引入Axios,方便与后端进行通信。 系列文章指路&#x1f449; 系列文章-基于Vue3创建前端项目并引入、配置常用的库和工具类 文章目录 安装依赖新建src/config/config.js 用于存放常用配置进行简单封装解决跨域问题调用尝试 安装依赖 npm install axios …

单元测试(超详细整理)

前言 为什么我们需要测试&#xff1f; 让产品可以快速迭代&#xff0c;同时还能保持高质量 对于一些相对稳定的系统级别页面&#xff0c;自动化测试在提高测试的效率的方面起到非常重要的作用。前端的自动化测试主要包括&#xff1a;浏览器测试和单元测试。Vue官方脚手架自带…

Python Opencv实践 - 手势音量控制

本文基于前面的手部跟踪功能做一个手势音量控制功能&#xff0c;代码用到了前面手部跟踪封装的HandDetector.这篇文章在这里&#xff1a; Python Opencv实践 - 手部跟踪-CSDN博客文章浏览阅读626次&#xff0c;点赞11次&#xff0c;收藏7次。使用mediapipe库做手部的实时跟踪&…

2024年【广东省安全员A证第四批(主要负责人)】考试内容及广东省安全员A证第四批(主要负责人)复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 广东省安全员A证第四批&#xff08;主要负责人&#xff09;考试内容是安全生产模拟考试一点通总题库中生成的一套广东省安全员A证第四批&#xff08;主要负责人&#xff09;复审考试&#xff0c;安全生产模拟考试一点…

二叉树的中序遍历(三种方法)

题目&#xff1a; 原题链接 简述题目就是&#xff1a;给你一颗二叉树的根结点root返回它的中序遍历 方法一&#xff08;递归&#xff09;&#xff1a; 中序遍历&#xff1a; 简单来说就是按照访问左子树——根节点——右子树的方式遍历这棵树&#xff0c;而在访问左子树或者右…

【基础知识】大数据组件HBase简述

HBase是一个开源的、面向列&#xff08;Column-Oriented&#xff09;、适合存储海量非结构化数据或半结构化数据的、具备高可靠性、高性能、可灵活扩展伸缩的、支持实时数据读写的分布式存储系统。 只是面向列&#xff0c;不是列式存储 mysql vs hbase vs clickhouse HMaster …

如何自定义右键弹框并实现位置自适应?

一、问题 右键显示弹框&#xff0c;但是靠近浏览器边缘的部分会被隐藏&#xff0c;需要实现弹框位置自适应 二、 问题分析 如果想要最终弹框的宽高不超过屏幕视口&#xff0c;就等于屏幕视口的总宽/高减去弹框打开时的起点坐标&#xff0c;剩下的部分大于等于弹框的宽/高&…

【快速开发】使用SvelteKit

自我介绍 做一个简单介绍&#xff0c;酒架年近48 &#xff0c;有20多年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0e;因为工作需要&#xff0c;另外也因为兴趣涉猎比较广&#xff0c;为了自己学习建立了三个博客&#xff0c;分别是【全球IT瞭望】&#xff0c;【…

关于“Python”的核心知识点整理大全37

目录 13.6.2 响应外星人和飞船碰撞 game_stats.py settings.py alien_invasion.py game_functions.py ship.py 注意 13.6.3 有外星人到达屏幕底端 game_functions.py 13.6.4 游戏结束 game_stats.py game_functions.py 13.7 确定应运行游戏的哪些部分 alien_inva…

12.18构建哈夫曼树(优先队列),图的存储方式,一些细节(auto,pair用法,结构体指针)

为结构体自身时&#xff0c;用.调用成员变量&#xff1b;为结构体指针时&#xff0c;用->调用成员变量 所以存在结构体数组时&#xff0c;调用数组元素里的成员变量&#xff0c;就是要用. 结构体自身只有在new时才会创建出来&#xff0c;而其指针可以随意创建 在用new时&…

【音视频】remb twcc原理

目录 twcc简介 WebRTC REMB 参考文档 twcc简介 TWCC全称是Transport wide Congestion Control&#xff0c;是webrtc的最新的拥塞控制算法。其原理是在接收端保存数据包状态&#xff0c;然后构造RTCP包反馈给发送端&#xff0c;反馈信息包括包到达时间、丢包状态等&#xff…

JavaWeb笔记之前端开发CSS

一 、引言 1.1 CSS概念 层叠样式表(英文全称&#xff1a;Cascading Style Sheets)是一种用来表现HTML&#xff08;标准通用标记语言的一个应用&#xff09;或XML&#xff08;标准通用标记语言的一个子集&#xff09;等文件样式的计算机语言。CSS不仅可以静态地修饰网页&…

ffmpeg 硬件解码零拷贝unity 播放

ffmpeg硬件解码问题 ffmpeg 在硬件解码&#xff0c;一般来说&#xff0c;我们解码使用cuda方式&#xff0c;当然&#xff0c;最好的方式是不要确定一定是cuda&#xff0c;客户的显卡不一定有cuda&#xff0c;windows 下&#xff0c;和linux 下要做一些适配工作&#xff0c;最麻…

吴恩达RLHF课程笔记

1.创建偏好数据集 一个prompt输入到LLM后可以有多个回答&#xff0c;对每个回答选择偏好 比如{prompt,answer1,answer2,prefer1} 2.根据这个数据集&#xff08;偏好数据集&#xff09;&#xff0c;创建reward model&#xff0c;这个model也是一个LLM,并且它是回归模型&#…

MySQL数据库 触发器

目录 触发器概述 语法 案例 触发器概述 触发器是与表有关的数据库对象&#xff0c;指在insert/update/delete之前(BEFORE)或之后(AFTER)&#xff0c;触发并执行触发器中定义的soL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性&#xff0c;日志记录&am…

Linux系统LVS+Keepalived群集

目录 一、概述 &#xff08;一&#xff09;群集特性 1.负载均衡 2.健康检查&#xff08;探针&#xff09; 3.故障转移 &#xff08;二&#xff09;Keepalived 1.作用 &#xff08;1&#xff09;支持故障自动转移 &#xff08;2&#xff09;支持节点健康状态检…

听GPT 讲Rust源代码--src/tools(21)

File: rust/src/tools/miri/src/shims/x86/mod.rs 在Rust的源代码中&#xff0c;rust/src/tools/miri/src/shims/x86/mod.rs文件的作用是为对x86平台的处理提供支持。它包含一些用于模拟硬件操作的shim函数和相关的类型定义。 具体来说&#xff0c;该文件中的函数是通过使用一组…

linux系统和网络(二):进程和系统时间

本文主要探讨linux系统进程和系统相关知识&#xff0c;本博客其他博文对该文章的部分内容有详细介绍 main函数 int main(int argc,char *argv[],char *envp[]); 操作系统下main执行前先执行引导代码,编译连接引导代码和程序连接在一起构成可执行程序,加载器将程序加载到内存中…