【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,一经查实,立即删除!

相关文章

【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…

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…

第一讲使用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&…

基于JAVA SpringBoot和UniAPP的宠物服务预约小程序

随着社会的发展和人们生活水平的提高&#xff0c;特别是近年来&#xff0c;宠物快速进入人们的家中&#xff0c;成为人们生活中重要的娱乐内容之一&#xff0c;过去宠物只是贵族的娱乐&#xff0c;至今宠物在中国作为一种生活方式得到了广泛的认可&#xff0c;随着人们精神文明…

Docker拉取并配置Grafana

Linux下安装Docker请参考&#xff1a;Linux安装Docker 安装准备 新建挂载目录 /opt/grafana/data目录&#xff0c;准备用来挂载放置grafana的数据 /opt/grafana/plugins目录&#xff0c;准备用来放置grafana的插件 /opt/grafana/config目录&#xff0c;准备用来挂载放置graf…

正则表达式一小时学完

闯关式学习Regex 正则表达式&#xff0c;我感觉挺不错的&#xff0c;记录一下。 遇到不会的题&#xff0c;可以评论交流。 真的很不错 链接 Regex Learn - Step by step, from zero to advanced.

HTTP原理与实现

一、基本概念 一、基本原理* 1、全称&#xff1a; HyperText Transfer Protocol (超文本传输协议) 2、底层实现协议&#xff1a;建立在 TCP/IP 上的无状态连接。 3、基本作用&#xff1a;用于客户端与服务器之间的通信&#xff0c;规定客户端和服务器之间的通信格式。包括请…

长胜证券:货币政策什么意思?

钱银政策是指国家钱银当局经过调控钱银供给量和利率等手法&#xff0c;以到达操控通货膨胀、坚持经济稳定、促进经济增长等目的的一种宏观经济政策。简而言之&#xff0c;钱银政策便是国家中央银行对钱银供给和利率进行调控的政策。那么具体来说&#xff0c;钱银政策到底有哪些…

自动化部署及监测平台基本架构

声明 本文是学习 政务计算机终端核心配置规范. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 核心配置自动化部署及监测技术要求 自动化部署及监测平台基本架构 对于有一定规模的政务终端核心配置应用&#xff0c;需要配备自动化部署及监测平台&am…