C++三大特性—继承 “基类与派生类之间的类型转换与赋值转换”

news2024/11/27 22:38:44

理解基类与派生类之间的类型转换是理解C++语言面向对象编程的关键所在


继承

   通过继承联系在一起的类构成一种层次关系,层次关系的根部有一个基类,其他直接或间接从基类继承而来,称为派生类。
   继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

派生类必须通过使用类派生列表明确指出它是从哪个(哪些)基类继承而来的。
类派生列表的形式是:
在这里插入图片描述

基类与派生类之间的类型转换

一个派生类对象包含多个组成部分:一个派生类自己定义的非静态的子对象,一个该派生类继承的基类对应的子对象,如果有多个基类,那么这样的子对象也有多个。

C++没有明确规定派生类对象在内存中如何分布,但是我们可以分析下:

class Person
{
protected:
	string _name; // 姓名
	string _sex;  // 性别
	int _age;     // 年龄
};
class Student : public Person
{
public:
	int _No; // 学号
};

在这里插入图片描述
在一个对象中,继承自基类部分和派生类自定义的部分不一定是连续存储的,上图只是工作机理概念模型,不是物理模型

派生类对象含有其基类对应的组成部分,所以我们能把派生类对象当作基类对象使用,我们能将基类的指针或引用绑定到派生类对象中的基类部分

	Person a;        //基类对象a
	Student b;       //派生类对象b
	Person* c = &a;  //c指向Person对象
	c = &b;			 //c指向b的基类Person部分
	Person& d = b;   //d绑定到b的基类Person部分

这种转换通常称为派生类到基类的类型转换,编译器会隐式的执行派生类到基类的转换

   通常情况下,我们想把引用或指针绑定到一个对象上,则引用或指针的类型应与对象的类型一致,或者对象的类型含有一个可接受的const类型转换规则(后续会写关于C++自己的类型转换的规则)。但是,继承关系的类是一个重要例外,我们可以将基类的指针或引用绑定到派生类对象上。
   这里面有一层重要含义:当使用基类的引用或指针是,实际上我们并不清楚该引用或指针所绑定的真实类型,该对象可能是基类对象也可能是派生类对象。
智能指针类也支持派生类向基类的类型转换,这意味着我们可以将一个派生类对象的指针存储在一个基类的智能指针中


静态类型与动态类型

   我们在使用存在继承关系的类型时,必须将一个变量或其他表达式的静态类型与该表达式表示对象的动态类型区分开。

   表达式的静态类型编译时总是已知的,它是变量声明时的类型或表达式生成的类型

   动态类型则是变量或表达式表示的内存中的对象的类型。动态类型只有在运行时才知道

	Person a;        //基类对象a
	Student b;       //派生类对象b
	Person* c = &b;  //c指向Student对象b的基类部分

   如上述,比如我们知道c的静态类型是 Person* ,它的动态类型依赖&b的类型,动态类型只有在运行时调用该语句才知道。
   如果表达式既不是指针也不是引用,那么它的静态类型与动态类型永远一致。比如Person类型的变量,永远是一个Person对象,我们无论如何都不能改变该变量所对应的类型。

基类的指针或引用的静态类型与动态类型可能不一致

不存在基类向派生类的隐式类型转换

之所以会有派生类向基类的类型转换,是因为每个派生类对象都包含一个基类部分,而使用基类的指针或引用可以绑定到该基类部分。一个基类对象既可以以独立的形式存在,也可以作为派生类的一部分存在。

比如下图:

在这里插入图片描述

	Person a;          //基类对象a
	Student* b = &a;   //错误:不能将基类转成派生类
	Student& c = a;    //错误:不能将基类转成派生类

如果允许这样赋值,那么我们很有可能使用b或c去访问原本a中不存在的成员

	Person a;         //基类对象a
	Student b;        //派生类对象b
	Person* c = &b;   //c指向b的Person基类部分、动态类型是Person
	Student* d = c;	  //错误

   上面这种情况也不允许,一个基类的指针或引用已经绑定了一个派生类对象,又继续基类向派生类的转换。

   编译器在编译时无法确定某个特定的转换在运行时是否安全,这是因为编译器只能通过检查指针或引用的静态类型来推断该转换是否合法。如果在基类中含有一个或多个虚函数,我们可以使用dynamic_cast请求一个类型转换,该转换的安全检查将在运行时执行。同样,如果我们已知某个基类向派生类的转换是安全的,则我们可以使用static_cast来强制覆盖掉编译器的检查工作。


赋值转换

对象之间不存在类型转换

派生类向基类的自动类型转换只针对指针或引用有效,在派生类类型与基类类型之间不存在这样的转换。

   当我们在初始化或者赋值一个类类型的时候,我们通常都是调用某个函数,初始化就调用它的构造函数,赋值就调用赋值运算符(重载),这些成员通常都包含一个参数,该参数类型是类类型的const版本引用。

   这些成员接收引用作为参数,所以派生类向基类的转换允许我们给基类的拷贝或移动操作传递一个派生类对象,这些操作不是虚函数。当我们给基类的构造函数传递一个派生类对象的时候,实际运行的构造函数是基类定义的那个,显然这个构造函数只能初始化基类本身的成员,赋值也是相同的道理。

比如:

	Student b;   //派生类对象b
	Person a(b); //调用Person::Person(const Person&)构造函数
	a=b;         //调用Person::operator=(const Person&)赋值重载

构造a的时候,运行Person的拷贝构造函数,只能构造基类部分成员,忽略派生类部分。b赋值给a,也是一样,只赋值b的基类部分给a,派生类多的部分忽略。

在上述过程我们忽略派生类部分的行为,可以说是派生类比基类多的部分切掉
在这里插入图片描述
当我们用一个派生类对象为一个基类对象初始化或赋值时,只有该派生类的基类部分会被拷贝、移动或赋值,它的派生类会被忽略

总结:

  • 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去
  • 基类对象不能赋值给派生类对象
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换
	Student stu_obj;
	// 1.子类对象可以赋值给父类对象/指针/引用
	Person per_obj = stu_obj;
	Person* pp = &stu_obj;
	Person& rp = stu_obj;

	//2.基类对象不能赋值给派生类对象
	//stu_obj = per_obj;

	// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
	pp = &stu_obj;
	Student * ps1 = (Student*)pp; // 这种情况转换时可以的。

	pp = &per_obj;
	Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
存在继承关系的类型之间转换规则: - 从派生类向基类的类型转换只对指针和引用有效 - 基类向派生类不存在隐式类型转换 - 和任何其他成员一样,派生类向基类的类型转换也可能由于访问权限的受限而变得不行,具体访问性可看这篇:    尽管自动类型转换只对指针或引用类型有效,但是继承体系中的大多数类仍然(显式或隐式地)定义了拷贝控制成员。因此,我们通常能够将一个派生类对象拷贝、移动或赋值给一个基类对象。不过需要注意的是,这种操作只处理派生类对象的基类部分。

如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀

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

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

相关文章

Spring RabbitMQ 实现消息队列延迟

1.概述 要实现RabbitMQ的消息队列延迟功能,一般采用官方提供的 rabbitmq_delayed_message_exchange插件。但RabbitMQ版本必须是3.5.8以上才支持该插件,否则得用其死信队列功能。 2.安装RabbitMQ延迟插件 检查插件 使用rabbitmq-plugins list命令用于查看…

C++引用进阶篇:让你的程序更加高效、安全、简洁

文章目录 前言1. 引用和临时数据🍑 什么样的临时数据会放到寄存器中🍑 关于常量表达式🍑 引用也不能指代临时数据🍑 引用作为函数参数 2. 为const引用创建临时变量3. const引用与转换类型🍑 引用类型的函数形参请尽可能…

SPFA 算法:实现原理及其应用

文章目录 一、前言二、SPFA 算法1、SPFA算法的基本流程2、代码详解 三、SPFA 算法已死 ? 一、前言 SPFA算法,全称为Shortest Path Faster Algorithm,是求解单源最短路径问题的一种常用算法,它可以处理有向图或者无向图&#xff0…

PySide2 QWebEngine与Web js交互

文章目录 单向交互双向传值案例 单向交互 QWebEngineView加载web页面&#xff0c;web页面中点击按钮&#xff0c;执行js代码&#xff0c;js的返回值传给QWebEnginePage&#xff0c;使用python进行保存结果。 单向&#xff0c;js向python(PySide2)端传输数据。 前端实现 <…

前端web3入门脚本三:一键完成与dex的交互,羊毛党必备

前言 该脚本用途&#xff1a;一键可以完成与dex的所有交互&#xff0c;包括2次swap&#xff0c;添加/移除流动性&#xff0c;以及farm和提取LP。一次运行可以有6条交易记录。 无论是个人单刷还是羊毛党批量地址刷交互都完美适配。当然反女巫方案不在这次文章的讨论范围内。 一、…

javascript中find(), filter(), some(), every(), map()等方法介绍

1、find() find() 用于找出第一个符合条件的数组成员。它的参数是一个回调函数&#xff0c;所有数组成员依次执行该回调函数&#xff0c;直到找出第一个返回值为true的成员&#xff0c;然后返回该成员。如果没有符合条件的成员&#xff0c;则返回undefined。 find()方法的回调…

利用Matab进行覆盖计算----战术计算

在 contour函数中添加如下代码 %------- 计算畅通区面积和占比例 --------% Spi*maxrange*maxrange/1e6; S0 nnz(isInRange)*reslons*reslats/1e6;isnn ~isnan(cdata); cdata0 cdata(isnn); S1numel(cdata0)*reslons*reslats/1e6;AS1/S0; % 畅通区所占比例; fprintf("…

CLion开发工具 | 06 - 使用CLion开发STM32(无需Cmake)

专栏介绍 文章目录 专栏介绍一、准备工作1. 工具准备2. 裸机工程准备二、使用CLion打开工程三、基于CLion写代码1. LED blink代码2. printf重定位代码四、编译工程1. 编译配置2. 选择编译目标3. 编译五、烧录1. OpenOCD基础知识(了解)2. 设置CLion路径3. 新建CLion配置文件4.…

面试总结,4年经验

小伙伴你好&#xff0c;我是田哥。 本文内容是一位星球朋友昨天面试遇到的问题&#xff0c;我把核心的问题整理出来了。 1&#xff1a;Java 层面的锁有用过吗&#xff1f;除了分布式锁以外 是的&#xff0c;Java中提供了多种锁机制来保证并发访问数据的安全性和一致性。常见的J…

分析GC日志解读

目录 GC分类 GC日志分类 GC日志结构剖析 透过日志看垃圾收集器 透过日志看GC原因 GC日志分析工具 GC分类 针对HotSpot VM的实现&#xff0c;它里面的GC按照回收区域又分为两大种类型&#xff1a;一种是部分收集&#xff08;Partial GC&#xff09;&#xff0c;一种是整堆…

VPN 虚拟专用网络隧道

1 什么是VPN VPN(全称&#xff1a;Virtual Private Network)虚拟专用网络&#xff0c;是依靠ISP和其他的NSP&#xff0c;在公共网络中建立专用的数据通信的网络技术&#xff0c;可以为企业之间或者个人与企业之间提供安全的数据传输隧道服务。在VPN中任意两点之间的链接并没有…

从零开始学习Linux运维,成为IT领域翘楚(二)

文章目录 &#x1f525;Linux系统目录结构&#x1f525;Linux用户和用户组&#x1f525;Linux用户管理 &#x1f525;Linux系统目录结构 文件系统组织结构 ⭐ /lib 系统开机所需要最基本的动态链接共享库&#xff0c;其作用类似于Windows里的DLL文件。 几乎所有的应用程序都需…

PACS系统源码,大型医院PACS源码集成三维重建

PACS系统为医院提供包括放射、超声、核医学、病理、内窥镜、心电图室在内的所有影像检查数字化的一体化解决方案。 它涵盖了传统PACS和RIS系统的所有功能&#xff0c;以构建全数字化影像科为目标&#xff0c;致力于实现对医院所有影像数据的统一管理、影像检查工作流的自动化&a…

POJ3704 括号匹配问题 递归方法

目录 题目 算法 完整代码 题目 参考 递归: https://blog.csdn.net/qq_45272251/article/details/103257953 利用了递归, 但思路稍复杂了 循环: https://blog.csdn.net/weixin_50340097/article/details/114579805 (看起来是递归其实是循环. 每次递归其实是循环内一次迭…

牛客网Python入门103题练习|【07--循环语句(2)】

⭐NP55 2的次方数 描述 在Python中&#xff0c; * 代表乘法运算&#xff0c; ** 代表次方运算。 请创建一个空列表my_list&#xff0c;使用for循环、range()函数和append()函数令列表my_list包含底数2的 [1, 10] 次方&#xff0c;再使用一个 for 循环将这些次方数都打印出来…

【Linux问题合集001】Linux中如何将用户添加到sudo组中的步骤

看教程的前提我的linux当前用户是zhou&#xff0c;看以下步骤时将zhou看做你的liunx当前用户就行了&#xff1a; 一、 以root用户登录到系统。 在Linux系统中&#xff0c;root用户是具有完全系统管理权限的超级用户。要以root用户身份登录到系统&#xff0c;您可>以使用以下…

继续打脸水货教程:关于可变对象与不可变对象

入门教程、案例源码、学习资料、读者群 请访问&#xff1a; python666.cn 大家好&#xff0c;欢迎来到 Crossin的编程教室 &#xff01; 今天这篇我要继续来打脸互联网上各种以讹传讹的水货教程。 前阵子我们聊了下Python中有关函数参数传递以及变量赋值的一些内容&#xff1a;…

LeetCode0014.最长公共前缀 Go语言AC笔记

时间复杂度&#xff1a;O(n) 解题思路 纵向扫描法。先扫描所有字符串的第一个字符&#xff0c;如果都相同就再次扫描所有字符串的第二个字符&#xff0c;直到某一字符串被扫描完或者出现了不相同的字符&#xff0c;此时就返回该字符串该字符的前缀。 为了确定所有字符是否相同…

【flask】三种路由和各自的比较配置文件所有的字母必须大写if __name__的作用核心对象循环引用的几种解决方式--难

三种路由 方法1&#xff1a;装饰器 python C#, java 都可以用这种方式 from flask import Flask app Flask(__name__)app.route(/hello) def hello():return Hello world!app.run(debugTrue)方法2: 注册路由 php python from flask import Flask app Flask(__name__)//app…

Java IO流第一章

Java IO流第一章 &#xff08;一&#xff09;简介 本文主要是从最基础的BIO式通信开始介绍到NIO , AIO&#xff0c;读者可以清晰的了解到阻塞、同步、异步的现象、概念和特征以及优缺点。 通信技术整体解决的问题 局域网内的通信要求。多系统间的底层消息传递机制。高并发下…