【C/C++】积累和派生类的转换

基类与派生类对象之间有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子类对象代替。具体表现在以下几个方面。


一、派生类对象可以向基类对象赋值

可以用子类(即公用派生类)对象对其基类对象赋值。如

A a1;		// 定义基类A对象a1
B b1;		// 定义类A的公用派生类B的对象b1
a1 = b1;	// 用派生类B对象b1,对基类对象a1赋值。

在赋值时舍弃派生类自己新增的成员。也就是“大材小用”,如图所示。

在这里插入图片描述

实际上,所谓赋值只是对数据成员赋值,对成员函数不存在赋值问题。


请注意,赋值后不能企图通过对象a1去访问派生类对象b1的成员,因为b1的成员与a1的成员是不同的。假设age是派生类B中增加的公用数据成员,分析下面的用法:

a1.age=23;		// 错误,a1中不包含派生类中增加的成员
b1.age=21;		// 正确,b1中包含派生类中增加的成员

应当注意,子类型关系是单向的、不可逆的。B是A的子类型,不能说A是B的子类型。只能用子类对象对其基类对象赋值,而不能用基类对象对其子类对象赋值,理由是显然的,因为基类对象不包含派生类的成员,无法对派生类的成员赋值。同理,同一基类的不同派生类对象之间也不能赋值。



二、派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化

如已定义了基类A对象a1,可以定义a1的引用变量:

A a1; 		// 定义基类A对象a1
B b1; 		// 定义公用派生类B对象b1
A& r= a1; 	// 定义基类A对象的引用变量r,并用a1对其初始化。

这时,引用变量r是a1的别名,r和a1共享同一段存储单元。


也可以用子类对象初始化引用变量r,将上面最后一行改为

A& r= b1;  	// 定义基类A对象的引用变量r,并用派生类B对象b1对其初始化

或者保留上面第3行“A& r=a1;”,而对r重新赋值:

r=b1;  		// 用派生类B对象b1,对a1的引用变量r赋值。

注意,此时r并不是b1的别名,也不与b1共享同一段存储单元。它只是b1中基类部分的别名,r与b1中基类部分共享同一段存储单元,r与b1具有相同的起始地址。



三、如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。

如有一函数fun:

void fun(A& r)  //形参是类A的对象的引用变量
{cout<<r.num<<endl;
}  //输出该引用变量的数据成员num

函数的形参是类A的对象的引用变量,本来实参应该为A类的对象。由于子类对象与派生类对象赋值兼容,派生类对象能自动转换类型,在调用fun函数时可以用派生类B的对象b1作实参:

fun(b1);

输出b1中的基类数据成员num的值

与前相同,在fun函数中只能输出派生类中基类成员的值。



四、派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以指向派生类对象。

定义一个基类Student(学生),再定义Student类的公用派生类Graduate(研究生), 用指向基类对象的指针输出数据。本例主要是说明用指向基类对象的指针指向派生类对象,为了减少程序长度,在每个类中只设很少成员。学生类只设num(学号),name(名字)和score(成绩)3个数据成员,Graduate类只增加一个数据成员pay(工资)。程序如下:

#include <iostream>
#include <string>
using namespace std;class Student//声明Student类
{
public:Student(int, string,float);  //声明构造函数void display( );  //声明输出函数
private:int num;string name;float score;
};Student::Student(int n, string nam,float s)  //定义构造函数
{num=n;name=nam;score=s;
}void Student::display( )  //定义输出函数
{cout<<endl<<"num:"<<num<<endl;cout<<"name:"<<name<<endl;cout<<"score:"<<score<<endl;
}class Graduate:public Student  //声明公用派生类Graduate
{
public:Graduate(int, string ,float,float);  //声明构造函数void display( );  //声明输出函数
private:float pay;  //工资
};//定义构造函数
Graduate::Graduate(int n, string nam,float s,float p):Student(n,nam,s),pay(p){ }
void Graduate::display()  //定义输出函数
{Student::display();  //调用Student类的display函数cout<<"pay="<<pay<<endl;
}int main()
{Student stud1(1001,"Li",87.5);  //定义Student类对象stud1Graduate grad1(2001,"Wang",98.5,563.5);  //定义Graduate类对象grad1Student *pt=&stud1;  //定义指向Student类对象的指针并指向stud1pt->display( );  	//调用stud1.display函数pt=&grad1;  		//指针指向grad1pt->display( );  //调用grad1.display函数
}

下面对程序的分析很重要,请大家仔细阅读和思考。

很多读者会认为,在派生类中有两个同名的display成员函数,根据同名覆盖的规则,被调用的应当是派生类Graduate对象的display函数,在执行Graduate::display函数过程中调用Student::display函数,输出num,name,score,然后再输出pay的值。


事实上这种推论是错误的,先看看程序的输出结果:

num:1001
name:Li
score:87.5num:2001
name:wang
score:98.5

前3行是学生stud1的数据,后3行是研究生grad1的数据,并没有输出pay的值。

问题在于pt是指向Student类对象的指针变量,即使让它指向了grad1,但实际上pt指向的是grad1中从基类继承的部分

通过指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。所以pt->display()调用的不是派生类Graduate对象所增加的display函数,而是基类的display函数,所以只输出研究生grad1的num,name,score3个数据。

如果想通过指针输出研究生grad1的pay,可以另设一个指向派生类对象的指针变量ptr,使它指向grad1,然后用ptr->display()调用派生类对象的display函数。但这不大方便。


通过本例可以看到,用指向基类对象的指针变量指向子类对象是合法的、安全的,不会出现编译上的错误。但在应用上却不能完全满足人们的希望,人们有时希望通过使用基类指针能够调用基类和子类对象的成员。如果能做到这点,程序人员会感到方便。后续章节将会解决这个问题。办法是使用虚函数和多态性。



五、精简例子:

class Animal
{
public:void speak(){cout << "动物在说话" << endl;}
};class Cat :public Animal
{
public:void speak(){cout << "小猫在说话" << endl;}
};void DoSpeak(Animal & animal)
{animal.speak();
}void test01()
{Cat cat;DoSpeak(cat);
}int main() {test01();system("pause");return 0;
}

程序输出:

动物在说话

而不是小猫在说话。

派生类对象在对父类对象赋值时,其新增的成员会被舍弃。赋值之后的父类对象,也只能调用基类的数据成员。




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

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

相关文章

javaEE -15( 13000字 JavaScript入门 - 2)

一&#xff1a;JavaScript(WebAPI) JS 分成三个大的部分 ECMAScript: 基础语法部分DOM API: 操作页面结构BOM API: 操作浏览器 WebAPI 就包含了 DOM BOM&#xff0c;这个是 W3C 组织规定的. (和制定 ECMAScript 标准的大佬们不是一伙人). 前面学的 JS 基础语法主要学的是 …

3.网络之UDP

UDP协议 文章目录 UDP协议1. UDP概述2. UDP报文格式3. UDP传输限制4. UDP校验和4.1 CRC 循环冗余校验算法4.2 md5 校验算法 1. UDP概述 UDP&#xff08;UserDatagramProtocol&#xff09;是一个简单的面向消息的传输层协议&#xff0c;尽管UDP提供标头和有效负载的完整性验证&a…

【实战Flask API项目指南】之一 概述

实战Flask API项目指南之 概述 本系列文章将带你深入探索实战Flask API项目指南&#xff0c;通过跟随小菜的学习之旅&#xff0c;你将逐步掌握Flask在实际项目中的应用。让我们一起踏上这个精彩的学习之旅吧&#xff01; 前言 小菜是一个Python编程爱好者&#xff0c;他目前…

SolidWorks2022安装教程(正版)

网盘资源附文末 一.简介 SolidWorks软件是世界上第一个基于Windows开发的三维CAD系统&#xff0c;由于技术创新符合CAD技术的发展潮流和趋势&#xff0c;SolidWorks公司于两年间成为CAD/CAM产业中获利最高的公司。良好的财务状况和用户支持使得SolidWorks每年都有数十乃至数百…

小程序day03

目标 页面导航 声明式导航 1.导航到tabBar页面 2.导航到非tabbar页面 3.后退导航 编程式导航 1.导航到tabBar页面 2.导航到非tabBar页面 3.后退导航 导航传参 1.声明式导航传参 2.编程式导航传参 3.在onLoad中接收导航参数 页面事件 下拉刷新 这个可以获取完数据之后再停止…

Docker学习——①

文章目录 1、什么是虚拟化、容器化&#xff1f;2、为什么要虚拟化、容器化&#xff1f;3、虚拟化实现方式3.1 应用程序执行环境分层3.2 虚拟化常见类别3.3 常见虚拟化实现3.3.1 主机虚拟化(虚拟机)实现3.3.2 容器虚拟化实现3.3.3 空间隔离实战--基础知识3.3.4 PID 隔离3.3.5 Mo…

二叉树OJ题(用前序和中序遍历构建二叉树,用中序和后续遍历构建二叉树)

文章目录 二叉树OJ题一、用前序和中序遍历构建二叉树1.思路2.代码 二、用中序和后续遍历构建二叉树1.思路2.代码 二叉树OJ题 一、用前序和中序遍历构建二叉树 1.思路 1.根据前序遍历找到根结点root 2.在中序遍历中&#xff08;inBegin0和inEndelem.length-1范围之间&#xff09…

Kafka、RabbitMQ、RocketMQ中间件的对比

消息中间件现在有不少&#xff0c;网上很多文章都对其做过对比&#xff0c;在这我对其做进一步总结与整理。 RocketMQ 淘宝内部的交易系统使用了淘宝自主研发的Notify消息中间件&#xff0c;使用Mysql作为消息存储媒介&#xff0c;可完全水平扩容&#xff0c;为了进一步降低成…

三门问题 最通俗解释+拓展

三门问题是概率论比较经典的一个问题&#xff0c;答案有点反直觉&#xff0c;所以值得学习&#xff0c;理性第一&#xff01;但是&#xff0c;很多网上解释都让人云里雾里&#xff0c;或者干脆解释就是错了&#xff0c;或一上来就贝叶斯公式开始搞数学&#xff0c;其实很简单可…

Wonder3D安装完美教程

话不多说,先附上地址: https://github.com/xxlong0/Wonder3D#wonder3dhttps://github.com/xxlong0/Wonder3D#wonder3d 目录 一、预览 二、环境配置

2023年四川省网络与信息安全技能大赛 决赛个人赛Writeup

文章目录 Web前端验证PHP_Try MiscHelloWorld密码在这easy_log Cryptobaser 线下“断网”CTF个人赛&#xff0c;题都很简单(新手级难度)&#xff0c;总共10道题目&#xff0c;解了6题。 赛题附件请自取&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1lgNEBO7a1L4KLE2t…

PerfectPixel 插件,前端页面显示优化工具

1.简介 PerfectPixel 插件是一款适用于 Chrome 浏览器的网页前端页面显示优化工具&#xff0c;该插件能够帮助开发人员和标记设计人员在开发时将设计图直接加载至网页中&#xff0c;与已成型的网页进行重叠对比&#xff0c;以规范网页像素精度 作为一款可以优化前端页面显示的…