C++:继承

一、继承的概念

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

下面是一个继承简单的例子

class Person
{
public:string name;int age;
};
class student :public Person
{
public:int stunum;//学号
};

这里首先创建了一个Person类,再以public方式(public是一种继承方式,除此之外,还有protected、private继承方式,之后再讲)继承Person类,创建出了student派生类。

因为student类继承了Person类,所以student类实例化出来的对象都有name和age成员变量

int main()
{student st1;st1.name = "zhangsan";st1.age = 18;st1.age = 2004;return 0;
}

 不只成员变量能够被继承,成员函数同样也能够被继承

class Person
{
public:void PrintName()//新增打印姓名的函数{cout << name << endl;}string name;int age;
};
class student :public Person
{
public:int stunum;//学号
};
int main()
{student st1;st1.name = "zhangsan";st1.age = 18;st1.age = 2004;st1.PrintName();//打印名字return 0;
}

运行结果

二、继承方式

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私
有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面
都不能去访问它。


2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在
派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。


3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他
成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected
> private。


4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过
最好显示的写出继承方式。


5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡
使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里
面使用,实际中扩展维护性不强。 

三、基类和派生类对象赋值转换

  1. 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
  2. 基类对象不能赋值给派生类对象。
  3. 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。

下面给出示例代码

int main()
{student sobj;//子类对象可以赋值给父类对象/指针/引用Person pobj = sobj;Person* pp = &sobj;Person& rp = sobj;//基类对象不能赋值给派生类对象,这么写会报错sobj = pobj;//基类的指针可以通过强制类型转换赋值给派生类pp = &sobj;student* ps1 = (student*)pp;//这种情况可能会越界pp = &pobj;student* ps2 = (student*)pp;return 0;
}

 四、继承中的作用域

1. 在继承体系中基类和派生类都有独立的作用域。


2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
    也叫重定义。(在子类成员函数中,
可以使用 基类::基类成员 显示访问


3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。


4. 注意在实际中在继承体系里面最好不要定义同名的成员。

class Person
{
public:void PrintName(){cout << name << endl;}string name;int age;
};
class student :public Person
{
public:void PrintName(){cout << name << name << endl;//打印两次}int stunum;//学号
};
int main()
{student st1;st1.name = "zhangsan";st1.PrintName();return 0;
}

运行结果

 下面是容易混淆的情况

class Person
{
public:void PrintName()//无参{cout << name << endl;}string name;int age;
};
class student :public Person
{
public:void PrintName(int)//需要传入一个整形参数{cout << name << name << endl;}int stunum;//学号
};
int main()
{student st1;st1.name = "zhangsan";st1.PrintName(1);//这里必须传参,不传参就报错return 0;
}

student中的PrintName和Person中的PrintName不构成函数重载,即便函数名相同参数不同,因为其不在同一作用域,此处构成隐藏(重定义),只要函数名相同就构成隐藏

五、派生类的默认成员函数 

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类
中,这几个成员函数是如何生成的呢?


1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。


2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。


3. 派生类的operator=必须要调用基类的operator=完成基类的复制。


4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
保证派生类对象先清理派生类成员再清理基类成员的顺序。


5. 派生类对象初始化先调用基类构造再调派生类构造。


6. 派生类对象析构清理先调用派生类析构再调基类的析构。


7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲
解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加
virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

六、继承与友元

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

class Person
{
private :friend void func(Person& p,student& s)//增加了一个函数,这个函数访问了子类的私有成员{cout << p.age <<s.stunum<< endl;}
public:void PrintName(){cout << name << endl;}string name;int age;
};
class student :public Person
{
public:void PrintName(int){cout << name << name << endl;}int stunum;//学号
};
int main()
{student st;st.age = 18;func(st,st);return 0;
}

这里会报错

七、菱形继承 

菱形继承是多继承的一种特殊情况

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
在Assistant的对象中Person成员会有两份。 

class base0
{int a;
};
class base1 :public base0
{int b;
};
class base2 :public base0
{int c;
};
class derive :public base1, public base2
{int d;
};
int main()
{cout << sizeof(derive) << endl;return 0;
}

运行结果

很明显,derive中有两份a成员变量

int main()
{derive D;D.base1::a = 1;D.base2::a = 2;D.b = 3;D.c = 4;D.d = 5;cout << sizeof(derive) << endl;return 0;
}

进行调试,观察内存

 虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在base1和
base2继承base0时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用。虚拟继承专门针对菱形继承

class base0
{
public:int a;
};
class base1 :virtual public base0
{
public:int b;
};
class base2 :virtual public base0
{
public:int c;
};
class derive :public base1,public base2
{
public:int d;
};
int main()
{derive D;D.a = 1;D.b = 3;D.c = 4;D.d = 5;cout << sizeof(derive) << endl;return 0;
}

 虚基表存放的是偏移量,即公共成员变量在子类对象中的相对位置,即此处1的位置,我们搜索虚基表地址继续查看内存

很清楚的看到虚基表地址处存放0,后四个字节存放偏移量,这里第一个虚基表存放的是十六进制的14,即十进制的20,第一个虚基表指针往后二十个字节刚好是1的位置。第二个虚基表存放的是0c,即十进制的12 ,第二个虚基表指针往后12个字节也是1的位置

八、继承的总结反思

1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱
形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设
计出菱形继承。否则在复杂度及性能上都有问题。


2. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。


3. 继承和组合
 
// 有童鞋会有疑问为什么D中B和C部分要去找属于自己的A?那么大家看看当下面的赋值发生时,d是不是要去找出B/C成员中的A才能赋值过去?

 

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

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

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

相关文章

万宾科技智能井盖传感器,预防城市道路安全

随着城市交通的不断发展和城市化进程的加速推进&#xff0c;城市道路安全问题日益凸显。市政井盖作为城市道路的一部分&#xff0c;承担着重要的交通安全保障职责。然而传统的市政井盖管理方式存在许多不足。针对这些问题政府需要采取适当的措施&#xff0c;补足传统管理方式的…

oracle “ORA-25153:临时表空间为空”

从生产上面备份出来了一个数据库&#xff0c;应用在使用时显示ORA-25153临时表空间为空的报错&#xff0c;原因一般是数据库迁移时&#xff0c;没有迁移完整造成的 解决方法 1.创建新的临时表空间temp2 create temporary tablespace temp2 tempfile DATA size 100M autoexten…

杭电oj 2064 汉诺塔III

#include <stdio.h>void main() {int n, i;long long sum[35] { 2,8,26 };for (i 3; i < 35; i)sum[i] 3 * sum[i - 1] 2;while (~scanf_s("%d", &n))printf("%lld\n", sum[n - 1]); }

python实现调和反距离空间插值法AIDW

1 简介 AIDW 主要是针对 IDW 的缺点进行了改进&#xff0c;考虑了样本点与预测点的位置&#xff0c;即方向和距离&#xff0c;具体见下图&#xff1a; 2 改进 IDW 公式&#xff1a; 从IDW算法可看出&#xff0c;插值点的估算值仅与插值样本距插值点的远近相关&#xff0c;并未…

@PropertySource适配通配符加载到Environment的一种方案

PropertySource可将配置文件加载到内存&#xff0c;时间有限说干的&#xff0c;PropertySource注解有4个参数&#xff0c;其中value表示要加载文件的路径&#xff0c;这个参数不支持通配符。还有一个参数PropertySourceFactory是加载配置文件的工厂&#xff0c;这两个参数配合使…

Sqli-lab教程-史上最全详解(1-22通关)

目录 Less-1 联合注入 Less-2 Less-3 Less-4 Less-5 报错注入/布尔盲注/时间盲注 Less-6 报错注入/布尔盲注/时间盲注 Less-7 文件导出 Less-8 布尔盲注/时间盲注 Less-9 时间盲注 Less-10 时间盲注 Less-11 post注入 Less-12 post注入 Less-13 post盲注 Less-14 post盲注 Less…

部署jekins遇到的问题

jdk问题 我用的jdk版本是21的结果版本太新了&#xff0c;启动jekins服务的时候总是报错最后在jekins的安装目录下面的jekinsErr.log查看日志发现是jdk问题最后换了一个17版本的就解决了。 unity和jekins jekins和Git源码管理 jekins和Git联动使用 我想让jekins每次打包的时…

【Java 进阶篇】Redis:打开缓存之门

介绍 Redis&#xff08;Remote Dictionary Server&#xff09;是一个高性能的键值对存储系统&#xff0c;被广泛用作缓存、消息中间件和数据库。它以其快速的读写能力、支持多种数据结构和丰富的功能而闻名。在这篇博客中&#xff0c;我们将深入了解Redis的概念、安装以及基本…

八、ffmpeg录制视频为yuv文件

前言 测试环境&#xff1a; ffmpeg的4.3.2自行编译版本windows环境qt5.12 图片的一些重要知识&#xff1a; RGB图片 位深度&#xff1a;每一个像素都会使用n个二进制位来存储颜色信息。每一个像素的颜色都是由红&#xff08;Red&#xff09;、绿&#xff08;Green&#xff0…

Linux学习笔记-Ubuntu下使用Crontab设置定时任务

文章目录 一、概述二、基于crontab的设置2.1 基本命令说明2.2 使用-e指令编辑命令2.2.1 进入编辑模式2.2.2 指令信息格式2.2.4 开启日志1) 修改rsyslog配置文件2) 重启rsyslog3) 查看日志 2.2.3 设置后之后重启服务 三、示例3.1 每隔一分钟往文件中日期3.2 使用-l查看任务列表3…

教你如何将Web项目部署到Linux中

文章目录 前言0. 什么是部署1. 调整代码达成一致2. 数据库建表3. 构建项目并打包4. 拷贝到 Tomcat 中5. 效果总结 前言 在我们完成了一个Web项目后, 我们该怎样将项目部署到 Linux 系统中呢? 本文就来简单讲解一下. 文章已部署本人的博客系统代码展开讲解. 关注收藏, 开始学…

【Spring】SpringBoot的扩展点之ApplicationContextInitializer

简介 其实spring启动步骤中最早可以进行扩展的是实现ApplicationContextInitializer接口。来看看这个接口的注释。 package org.springframework.context;/*** Callback interface for initializing a Spring {link ConfigurableApplicationContext}* prior to being {linkpl…

ChatGPT规模化服务的经验与教训

2022年11月30日&#xff0c;OpenAI发布ChatGPT&#xff0c;以很多人未曾预料的速度迅速走红。与此同时&#xff0c;由于短时间内用户量的暴涨&#xff0c;导致服务器过载&#xff0c;迫使OpenAI停止新用户的注册。 ChatGPT发布这一年&#xff0c;同样的情景发生了好几次。在最近…

暖阳脚本_ 将Agent技术的灵活性引入RPA,清华等发布自动化智能体ProAgent

RPA暖阳脚本 近日&#xff0c;来自清华大学的研究人员联合面壁智能、中国人民大学、MIT、CMU 等机构共同发布了新一代流程自动化范式 “智能体流程自动化” Agentic Process Automation&#xff08;APA&#xff09;&#xff0c;结合大模型智能体帮助人类进行工作流构建&#x…

微信小程序会议OA-登录获取手机号流程登录-小程序导入微信小程序SDK(从微信小程序和会议OA登录获取手机号到登录小程序导入微信小程序SDK)

目录 获取用户昵称头像和昵称 wx.getUserProfile bindgetuserinfo 登录过程 登录-小程序 wx.checkSession wx.login wx.request 后台 准备数据表 反向生成工具生成 准备封装前端传过来的数据 小程序服器配置 导入微信小程序SDK application.yml WxProperties …

ABAP调用Https接口 Ssl证书导入

ABAP调用Https接口 Ssl证书导入 一、证书导入 谷歌浏览器打开对方系统URL地址&#xff0c;下载SSL Server certificate,步骤如下&#xff1a; 浏览器打开要导出certificate(证书)的网站&#xff0c;点击这个小锁的图标&#xff1a; 点击连接是安全的后面小播放按钮 点击证…

解决ElementUI时间选择器回显出现Wed..2013..中国标准时间.

使用饿了么组件 时间日期选择框回显到页面为啥是这样的&#xff1f; 为什么再时间框中选择日期&#xff0c;回显页面出现了这种英文格式呢&#xff1f;&#xff1f;&#xff1f;&#xff1f; 其实这个问题直接使用elementui的内置属性就能解决 DateTimePicker 日期时间选择…

汇编-PROTO声明过程

64位汇编 64 模式中&#xff0c;PROTO 伪指令指定程序的外部过程&#xff0c;示例如下&#xff1a; ExitProcess PROTO ;指定外部过程&#xff0c;不需要参数.code main PROCmov ebx, 0FFFFFFFFh mov ecx,0 ;结束程序call ExitProcess ;调用外部过程main ENDP END 32位…

【华为OD题库-032】数字游戏-java

题目 小明玩一个游戏。系统发1n张牌&#xff0c;每张牌上有一个整数。第一张给小明&#xff0c;后n张按照发牌顺序排成连续的一行。需要小明判断&#xff0c;后n张牌中&#xff0c;是否存在连续的若干张牌&#xff0c;其和可以整除小明手中牌上的数字. 输入描述: 输入数据有多组…

redis---非关系型数据库

关系数据库与非关系型数据库 redis非关系型数据库&#xff0c;又名缓存型数据库。数据库类型&#xff1a;关系型数据库和非关系型数据库关系型数据库是一 个机构化的数据库,行和列。 列&#xff1a;声明对象。 行&#xff1a;记录对象属性。 表与表之间的的关联。 sql语句&…