【C++ 学习 ⑯】- 继承(上)

目录

一、继承的概念和定义

1.1 - 概念

1.2 - 定义

二、继承时的对象内存模型

三、向上转型和向下转型

四、继承时的名字遮蔽问题

4.1 - 有成员变量遮蔽时的内存分布

4.2 - 重名的基类成员函数和派生类成员函数不构成重载


 


一、继承的概念和定义

1.1 - 概念

C++ 中的继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承类似,例如儿子继承父亲的财产。

继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程,例如类 B 继承于类 A,那么 B 就拥有 A 的成员变量和成员函数。

被继承的类称为父类或基类,继承的类称为子类或派生类。"父类" 和 "子类" 通常放在一起称呼,"基类" 和 "派生类" 通常放在一起称呼。

#include <iostream>
using namespace std;
​
// 基类 Person
class Person
{
public:void SetName(const char* name = "张三") { _name = name; }
​void SetAge(int age = 18) { _age = age; }
​const char* GetName() const { return _name.c_str(); }
​int GetAge() const { return _age; }
protected:string _name;  // 姓名int _age;  // 年龄
};
​
// 派生类 Student
class Student : public Person
{
public:void SetStuId(int stu_id = 0) { _stu_id = stu_id; }
​int GetStuId() const { return _stu_id; }
protected:int _stu_id;  // 学号
};
​
// 派生类 Teacher
class Teacher : public Person
{
public:void SetJobId(int job_id) { _job_id = job_id; }
​int GetJobId() const { return _job_id; }
protected:int _job_id;  // 工号
};
​
int main()
{Student s;s.SetName("李四");s.SetAge(19);s.SetStuId(1);cout << s.GetName() << "的年龄是" << s.GetAge() << ",学号是" << s.GetStuId() << "。" << endl;// 李四的年龄是19,学号是1。
​Teacher t;t.SetName("王五");t.SetAge(30);t.SetJobId(10);cout << t.GetName() << "的年龄是" << t.GetAge() <<",工号是" << t.GetJobId() << "。" << endl;// 王五的年龄是30, 工号是10。return 0;
}

通过以上的例子我们就可以明白:继承机制是面向对象程序中使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。继承呈现了面向对象呈现设计的层次结构,体现了由简单到复杂的认知过程,以前我们接触的复用是模板,继承则是类设计层次的复用

1.2 - 定义

继承的定义格式为

class 派生类名 : [继承方式] 基类名
{派生类新增加的成员
}

不同的继承方式会影响基类成员在派生类中的访问权限

基类成员/继承方式public 继承protected 继承private 继承
基类的 public 成员派生类的 public 成员派生类的 protected 成员派生类的 private 成员
基类的 protected 成员派生类的 protected 成员派生类的 protected 成员派生类的 private 成员
基类的 private 成员在派生类中不可见在派生类中不可见在派生类中不可见

总结

  1. 不管继承方式如何,基类中的 private 成员在派生类中始终不能使用(不能在派生类的成员函数中访问和调用)。注意:我们说的是基类的 private 成员不能在派生类中使用,并没有说基类的 private 成员不能被继承。实际上,基类的 private 成员是能够被继承的,并且成员变量会占用派生类对象的内存,它只是在派生类中无法使用罢了,即不可见

  2. 如果希望基类的成员既不向外暴露,即不能通过对象访问,还能在派生类中使用,那么只能声明为 protected,由此可以看出 protected 访问限定符是因为继承才出现的

  3. 通过上面的表格我们可以发现,基类的 private 成员在派生类中不可见,基类的其他成员在派生类中的访问权限 = min(成员在基类中的访问权限,继承方式),比较规则是:public > protected > private

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

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


二、继承时的对象内存模型

没有继承时,对象内存模型很简单,对象的内存中只包含成员变量,存储在栈区或堆区(使用 new 创建对象),成员函数与对象内存分离,存储在代码区。

当有继承关系时,派生类对象的内存模型可以看成是基类成员变量和新增成员变量的总和,而所有成员函数仍然存储在另外一个区域——代码区,由所有对象共享

#include <iostream>
using namespace std;
​
class A
{
public:int _i;int _j;
};
​
class B : public A
{
public:int _k;
};
​
class C : public B
{
public:int _l;
};
​
int main()
{A a;cout << &a << endl;cout << &a._i << " " << &a._j << endl;cout << sizeof(A) << endl; 
​B b;cout << &b << endl;cout << &b._i << " " << &b._j << " " << &b._k << endl;cout << sizeof(B) << endl;  
​C c;cout << &c << endl;cout << &c._i << " " << &c._j << " " << &c._k << " " << &c._l << endl;cout << sizeof(C) << endl;return 0;
}

即:

可以发现,基类的成员变量排在前面,新增的成员变量排在后面


三、向上转型和向下转型

可以用 "派生类对象/派生类指针/派生类引用" 给 "基类对象/基类指针/基类引用" 初始化或赋值,这在 C++ 中称为向上转型(Upcasting),还有一个形象的说法叫切片或切割。向上转型非常安全,可以由编译器自动完成

相应地,用基类给派生类初始化或赋值称为向下转型(Downcasting)。向下转型则有风险,需要程序员手动干预

  1. 不能用 "基类对象" 给 "派生类对象" 初始化或赋值,因为基类不包含派生类的成员变量,所以无法对派生类的成员变量赋值

  2. "基类指针或者引用" 可以通过强制类型转换的方式给 "派生类指针或引用" 初始化或赋值,但是必须是 "基类指针" 指向 "派生类对象" 时才是安全的。这里基类如果是多态类型,可以使用 RTTI(Run-Time Type Information)的 dynamic_cast 识别后进行安全转换(后面再详细讲解)。

#include <iostream>
using namespace std;
​
class Person
{
protected:string _name;int _age;
};
​
class Student : public Person
{
public:int _stu_id;
};
​
int main()
{Student s;Person p = s;Person* pp = &s;Person& rp = s;  // 由此可以发现,向上转型的过程中没有发生隐式类型转换
​// s = p;  // error
​Student* ps = (Student*)pp;  ps->_stu_id = 1; // 基类指针 pp 指向的是派生类对象 s,所以转换是安全的
​// pp = &p;// ps = (Student*)pp;// ps->_stu_id = 0;  // 越界访问// 这种情况下,强制类型转换是可行的,但不安全return 0;
}


四、继承时的名字遮蔽问题

如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。所谓遮蔽,就是在派生类中使用,以及通过派生对象访问该成员时,实际上使用的是派生类新增的成员,而不是从基类继承来的

#include <iostream>
using namespace std;
​
class A
{
public:void Print() { cout << _i << " " << _j << endl; }
public:int _i = 1;int _j = 2;
};
​
class B : public A
{
public:// 遮蔽基类的成员函数void Print() { cout << _i << " " << _j << " " << _k << endl; }
public:int _j = 3;  // 遮蔽基类的成员变量int _k = 4; 
};
​
int main()
{B b;// 使用的是派生类新增的成员,而不是从基类继承来的cout << b._j << endl;  // 3b.Print();  // 1 3 4
​// 使用的是基类继承来的成员cout << b.A::_j << endl;  // 2b.A::Print();  // 1 2return 0;
}

4.1 - 有成员变量遮蔽时的内存分布

cout << &b._i << " " << &b.A::_j << " " << &b._j << " " << &b._k << endl;
cout << sizeof(B) << endl;

即:

当基类 A 的成员变量被遮蔽时,仍然会留在派生类对象 b 的内存中,B 类新增的成员变量也始终排在基类 A 的成员变量的后面

4.2 - 重名的基类成员函数和派生类成员函数不构成重载

类其实是一种作用域,每个类都会定义它自己的作用域,在这个作用域类我们再定义类的成员。当存在继承关系时,派生类的作用域嵌套在基类的作用域之内,如果一个名字在派生类的作用域内无法找到,编译器会继续到外层的基类作用域中查找该名字的定义

在上面的例子中,B 继承自 A,它们作用域嵌套的嵌套关系如下:

函数重载指的是在同一个作用域内有多个名称相同但参数列表不同的函数,所以重名的基类成员函数和派生类成员函数不会构成重载,即便参数一样,也不会报错

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

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

相关文章

(五)、Redisson锁机制源码分析

1、了解分布式锁的特性 1、锁的互斥性 也就是说,在任意时刻,只能有一个客户端能获取到锁,不能同时有两个或多个客户端获取到锁。简单来说,就比如上厕所,一个厕所只有一个坑位,只能一个人上,不能同时两个人或多个人上。2、锁的同一性 也就是说,锁只能被持有该锁的客户端…

MMEdu实现摄像头图像分类(Python版)

先安装MMEdu库&#xff01; MMEdu安装&#xff1a;https://blog.csdn.net/zyl_coder/article/details/132483865 下面的代码请在Jupyter上运行&#xff0c;并自己准备数据集。若模型还未训练&#xff0c;请先在本地训练完模型后再进行模型推理。 import cv2 capture cv2.Vi…

【AWS】创建IAM用户;无法登录IAM用户怎么办?错误提示:您的身份验证信息错误,请重试(已解决)

目录 0.背景问题分析 1.解决步骤 0.背景问题分析 windows 11 &#xff0c;64位 我的问题情景&#xff1a; 首先我创建了aws的账户&#xff0c;并且可以用ROOT用户登录&#xff0c;但是在登录时选择IAM用户&#xff0c;输入ROOT的名字和密码&#xff0c;就会提示【您的身份验证…

【分布式技术专题】「OSS中间件系列」从0到1的介绍一下开源对象存储MinIO技术架构

MinIO背景介绍 MinIO创始者是Anand Babu Periasamy, Harshavardhana&#xff08;戒日王&#xff09;等人&#xff0c; Anand是GlusterFS的初始开发者、Gluster公司的创始人与CTO&#xff0c;Harshavardhana曾经是GlusterFS的开发人员&#xff0c;直到2011年红帽收购了Gluster公…

基于VUE3+Layui从头搭建通用后台管理系统(前端篇)十:实体配置功能实现

一、本章内容 本章实现实体配置功能,包括识别实体属性、设置各属性的展示方式、相关类型、要和展示、编辑的内容等。 1. 详细课程地址: 待发布 2. 源码下载地址: 待发布 二、界面预览 三、开发视频 3.1 B站视频地址:

MISRA 2012学习笔记(3)-Rules 8.4-8.7

文章目录 Rules8.4 字符集和词汇约定(Character sets and lexical conventions)Rule 4.1 八进制和十六进制转译序列应有明确的终止识别标识Rule 4.2 禁止使用三字母词(trigraphs) 8.5 标识符(Identifiers)Rule 5.1 外部标识符不得重名Rule 5.2 同范围和命名空间内的标识符不得重…

673. 最长递增子序列的个数

673. 最长递增子序列的个数 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;方法一&#xff1a;动态规划方法二&#xff1a;贪心 前缀和 二分查找 参考代码&#xff1a;__673最长递增子序列的个数__动态规划__673最长递增子序列的个数__贪心_前缀和_二分查找…

书单背景图片哪里找?如何制作成视频?

有没有小伙伴们发现&#xff0c;如今很多热门的短视频平台有很多使用书单文案制作的视频&#xff0c;很多情感博主会配上一些精致的图片&#xff0c;唯美的背景承载着一些美好的文案内容。这种类型的视频让不少的小伙伴都想制作专属于自己的视频来投稿&#xff0c;那么小伙伴们…

linux系统(centos、ubuntu、银河麒麟服务、uos、deepin)判断程序是否已安装,通用判断方法:使用所有应用和命令的判断

前言 项目中需要判断linux服务器中是否已经安装了某个服务 方法有很多种&#xff0c;但是很多都不通用&#xff0c; 脚本代码就不容易做成统一的 解决方案 用下面的脚本代码去进行判断 用jdk测试 脚本意思如下&#xff1a; 输入java -version命令&#xff0c;将返回的字…

【数据结构练习】单链表OJ题(一)

目录 一、移除链表元素思路1&#xff1a;思路2&#xff1a; 二、反转链表三、链表的中间节点四、链表中倒数第k个节点五、回文结构六、合并两个有序链表 一、移除链表元素 题目&#xff1a; 思路1&#xff1a; 在原来的链表上进行修改&#xff0c;节点的数据是val的删除&am…

微信小程序--data的赋值与取值的几种方式

通过小程序官方文档可知&#xff1a; 赋值一定需要注意。需要setData的使用&#xff0c;这样页面才刷新&#xff0c;数据才会改变&#xff0c;并且分清that和this的使用 Page() 函数用来注册一个页面。接受一个 object 参数&#xff0c;其指定页面的初始数据、生命周期函数、事…

聊聊springboot的启动事件

序 本文主要研究一下springboot的启动事件 SpringApplicationEvent org/springframework/boot/context/event/SpringApplicationEvent.java public abstract class SpringApplicationEvent extends ApplicationEvent {private final String[] args;public SpringApplicatio…

Cesium 使用 Entity 绘制点线面

文章目录 一、绘制点1. 第一种2. 第二种 二、绘制面三、绘制线四、移除 Entity <!--* Author: HuKang* Date: 2023-08-18 11:06:43* LastEditTime: 2023-08-25 09:16:59* LastEditors: HuKang* Description: program-c* FilePath: \global-data-display\src\views\program-c…

机器学习笔记之优化算法(十七)梯度下降法在强凸函数的收敛性分析

机器学习笔记之优化算法——梯度下降法在强凸函数的收敛性分析 引言回顾&#xff1a;梯度下降法在强凸函数的收敛性二阶可微——梯度下降法在强凸函数的收敛性推论 引言 上一节介绍并证明了&#xff1a;梯度下降法在强凸函数上的收敛速度满足 Q \mathcal Q Q-线性收敛。 本节将…

Postgresql12基于时间点恢复

1、环境 centos 7系统 postgresql 12 docker 20.10.6 2、整体思路 1&#xff09;进行一个pgdata目录的全量备份 2&#xff09;通过wal日志恢复到故障发生之前某个时间点 3、操作步骤 配置postgresql.conf文件 #日志级别 wal_level replica #归档开关 archive_mode on …

第一讲使用IDEA创建Java工程——HelloWorld

一、前言导读 为了能够让初学者更快上手Java,不会像其他书籍或者视频一样,介绍一大堆历史背景,默认大家已经知道Java这么编程语言了。本专栏只会讲解干货,直接从HelloWord入手,慢慢由浅入深,讲个各个知识点,这些知识点也是目前工作中项目使用的,而不是讲一些老的知识点…

16、Flink 的table api与sql之连接外部系统: 读写外部系统的连接器和格式以及FileSystem示例(1)

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

nvm安装使用教程

文章目录 下载配置安装最新稳定版 node安装指定版本查看版本切换版本删除版本 常见问题安装node后 显示拒绝访问的问题使用cnpm会报错的问题降低cnpm版本npm镜像 下载 NVM for Windows 下载地址&#xff1a;https://link.juejin.cn/?targethttps%3A%2F%2Fgithub.com%2Fcoreyb…

《深度学习计算机视觉 》书籍分享(包邮送书三本)

深度学习计算机视觉介绍 随着计算机技术的发展和进步&#xff0c;计算机视觉领域得到了广泛的关注和研究。而深度学习作为一种强大的机器学习方法&#xff0c;已经成为计算机视觉领域的重要工具之一。本文将介绍深度学习在计算机视觉中的应用和取得的成果。 深度学习是一种模…

Sim/circuit10

通过观察可知&#xff0c;在a、b同时为0或1时&#xff0c;state的值改变 state的值可以改变q的输出&#xff0c;1为ab的同或&#xff0c;0为异或 利用assign q进行输出 module top_module (input clk,input a,input b,output q,output state );always(posedge clk)if(a&…