超详细 - 一文说懂 C++ 继承(上)

目录

0 引言

1. 继承的概念

1.1 继承的本质

1.2 继承的作用 

2. 继承的定义

 2.1 继承的格式

2.2 继承的权限 

2.3  默认继承

 2.5 继承权限的使用

3. 继承的作用域

3.1 隐藏 

4. 基类与派生类对象的赋值转换 

4.1 切片

5. 派生类的默认成员函数 

 5.1 隐式调用

5.2 显示调用 


0 引言

从前面我们知道,继承是面向对象的三大特性之一(封装 继承 多态)。今天我们主要一起学习什么是继承。即如何在父类的基础之上去构建更加丰富的子类。在此举一个不完全恰当的例子,例如柑橘类水果的不同品种,实际上继承了其父类品种的某些特性。

1. 继承的概念

那什么是继承呢?是继承遗产还是某些东西呢?答案都不是。

实际上,官方说 继承(inheritance)机制是面向对象程序设计使代码可以复用的重要的手段,它允许程序员在保持原有基类(父类)特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类(子类)。

因此:被继承的对象:父类/基类

           继承一方:子类/派生类  

1.1 继承的本质

继承的本质实际上就是为了复用代码。

例如:现在需要完成一个学校教务系统代码的编写,单从角色划分上来说,可以简单分为:教职工和学生 这两大类,但如果继续划分的话,还可以分出:校领导、各级院长、辅导员、后勤人员、大一/大二/大三/大四学生等,假设为每个角色都设计一个结构,那么这个工程量也未免太大了,且存在冗余。

所以,为了提高开发效率,我们就可以利用继承的概念。可以从各种角色中选出共同点,组成基类,比如每个人都有姓名、年龄、性别、联系方式等基本信息

而 教职工与学生的区别就在于 管理与被管理,因此可以在基类的基础上加一些特殊信息如教职工号表示教职工,加上学号表示学生,其他细分角色设计也是如此,产生多种子类

因此,我们通过继承的方式,复用基类的代码,进而划分出各种子类。

1.2 继承的作用 

子类基础父类后,可以享有父类中的所有的 公开 / 保护 属性,也就是说,除了 私有 内容外,父类有的,子类全都有 。

举一个例子, 在父类 - 房子 的基础上,派生出 小平层和别墅 这两个子类。 

//父类 - 房子
class house
{
public:
	house(int area = int())
		:_area(area)
	{}
	int getarea()
	{
		return _area;
	}
private:
	int _area;
};

//子类 - 平层
class Flatbed : public house
{
public:
	Flatbed()
		:house(90)
	{
		cout << "小平层,面积是:" << getarea() << "平方米" << endl;
	}
};

//子类 - 别墅
class villa : public house
{
public:
	villa()
		:house(500)
	{
		cout << "别墅,面积是:" << getarea() << "平方米" << endl;
	}
};

int main()
{
	Flatbed f;
	villa v;
	return 0;
}

 

可以看到,两个子类都能具备父类中的 公有和保护 属性,并且互不干扰。

2. 继承的定义

 2.1 继承的格式

继承的格式:子类 :继承方式 父类,比如 class A :public B 就表示 A 继承 B, 且为公有继承

2.2 继承的权限 

继承的权限可分为 公有继承(public)、保护继承(protected)、私有继承(private)。

权限大小:public > protected > private。

public公开的,任何人都可以访问
protected保护的,只有当前类和子类可以访问
private私有的,只允许当前类进行访问

保护 protected 比较特殊,只有在 继承 中才能体现它的价值,否则与 私有 作用一样。

因此存在多种 父类成员权限 与 继承权限 的搭配方案。(所谓的外部其实就是子类对象

public 继承protected 继承private 继承
父类:public 成员外部可见,子类中可见外部不可见,子类中可见外部不可见,子类中可见
父类:protected 成员外部不可见,子类中可见外部不可见,子类中可见外部不可见,子类中可见
父类:private 成员都不可见都不可见都不可见

总结:无论是哪种继承方式,父类中的 private 成员始终不可被 [子类 / 外部] 访问;当外部试图访问父类成员,依据 min(父类成员权限,子类继承权限),只有最终权限为 public  时,外部才能访问。

下面我们来看示例:

    

2.3  默认继承

如果不注明继承权限, class 默认为 private , struct 默认 public ,因此最好是注明继承权限。

那么如何访问父类的私有成员?我们只需在父类中设计相应的函数,间接访问私有成员

 2.5 继承权限的使用

 那么我们如何优雅的使用好权限呢?我们只需要记住以下的点:

对于只想自己类中查看的成员,设为 private ,对于想共享给子类使用的成员,设为 protected, 其他成员都可以设为 public 。

例如在 小明 家中,房子面积可以设置为公开,家庭存款只让家庭成员知道,而隐私则可以设置为私有。

class Home
{
public:
	int area = 120;	//房屋面积
};

class Father : public Home
{
protected:
	int money = 100000;	//存款
private:
	int privateMoney = 100;	//私房钱
};

class xiaoming : public Father
{
public:
	xiaoming()
	{
		cout << "我是小明" << endl;
		cout << "我知道我家房子有 " << area << " 平方米" << endl;
		cout << "我也知道我家存款有 " << money << endl;
		cout << "但我不知道我爸爸的私房钱有多少" << endl;
	}
};

class zhangsan
{
public:
	zhangsan()
	{
		cout << "我是张三" << endl;
		cout << "我只知道张三家房子有 " << Home().area << " 平方米" << endl;
		cout << "其他情况不知道" << endl;
	}
};

int main()
{
	xiaoming x;
	cout << "================" << endl;
	zhangsan z;
	return 0;
}

 因此我们可以看到,权限可以很好的保护成员。

那么我们如何设计一个不能被继承的类?

我们只需要将父类的构造和析构函数设为私有,这样子类就无法创建父类对象,同时也就无法继承

3. 继承的作用域

子类虽然继承自父类,但两者的作用域是不相同的,假设出现同名函数时,默认会将父类的同名函数隐藏调,进而执行子类的同名函数。

隐藏 也叫 重定义,与它类似的概念还有:重写(覆盖)、重载。

3.1 隐藏 

子类中出现父类的 同名 方法或者成员。

//父类
class Base
{
public:
	void func() { cout << "Base val: " << val << endl; }
protected:
	int val = 111;
};

//子类
class Derived : public Base
{
public:
	int func()
	{
		cout << "Derived val: " << val << endl;
		return 0;
	}
private:
	int val = 222;
};

int main()
{
	Derived d;
	d.func();
	return 0;
}

 我们发现,父子类中的方法和成员均被隐藏,执行的是 子类方法,输出的是子类成员。

现在我们将子类中函数修改为 funA

int funA() 
{ 
	cout << "Derived val: " << val << endl;
	return 0;
}

 发现此时 隐藏 消失,并且结果的是 父类方法 + 父类成员。

接着,我们修改子类标识符 val 

int num = 222;

此时 隐藏 也消失,执行结果 子类方法 + 父类成员。

因此, 当子类中的方法出现 隐藏 行为时,优先执行 子类 中的方法;当子类中的成员出现 隐藏 行为时,优先选择当前作用域中的成员(局部优先)。

那么,我们如何显示的调用父类的方法或者成员?

 我们利用域作用限定符 : : 即可。 

总结: 

  • 只要是命名相同,都构成 隐藏 ,与 返回值、参数 无关
  • 隐藏会干扰调用者的意图,因此在继承中,要尽量避免同名函数的出现

4. 基类与派生类对象的赋值转换 

首先提出的是,在继承中,允许将子类对象直接赋值给父类,但不允许父类对象赋值给子类。 

 并且这种 赋值 是非常自然的,编译器直接处理,不需要调用 赋值重载 等函数。

//父类
class Base
{
protected:
	int val = 111;
};

//子类
class Derived : public Base
{
private:
	int num = 222;
};

int main()
{
	Base b;
	Derived d;
	b = d;
	//d = b;	//非法,只允许 子->父
	return 0;
}

 子类对象 在 赋值 给 父类对象 时,触发 切片 机制,丝滑的完成 赋值。

4.1 切片

下图展示了柠檬切片:

在基类与派生类的赋值转换中,切片将父类对象看作一个结构体,子类对象 看作 结构体Plu 版,将子类对象中多余的部分去除,留下父类对象可接收的成员,最后再将对象的指向进行改变就完成了切片。

 

因为整个切片过程是由编译器自己完成的,所以效率很高,并且不会发生借助临时对象构造再赋值的情况。由于父类无法满足子类的需求,切片只在 子类->父类 时发生。

5. 派生类的默认成员函数 

派生类(子类)也是 ,同样会生成 六个默认成员函数(用户未定义的情况下)

不同于单一的 子类 是在 父类 的基础之上创建的,因此它在进行相关操作时,需要为父类进行考虑。

 5.1 隐式调用

子类在继承父类后,构建子类对象时 会自动调用父类的 默认构造函数,子类对象销毁前,还会自动调用父类的 析构函数。

class Person
{
public:
	Person() { cout << "Person()" << endl; }
	~Person() { cout << "~Person()" << endl; }
};

class Student : public Person
{
public:
	Student() { cout << "Student()" << endl; }
	~Student() { cout << "~Student()" << endl; }
};

int main()
{
	Student s;
	return 0;
}

此时,自动调用是由编译器完成的,前提是父类存在对应的默认成员函数;如果不存在,会报错 

5.2 显示调用 

因为存在 隐藏 的现象,当父子类中的函数重名时,子类无法再自动调用父类的默认成员函数,此时会引发 浅拷贝 相关问题。

class Person
{
public:
	Person() { cout << "Person()" << endl; }
	void operator=(const Person& P) { cout << "Person::operator=()" << endl; }
	~Person() { cout << "~Person()" << endl; }
};

class Student : public Person
{
public:
	Student() { cout << "Student()" << endl; }
	void operator=(const Student&) { cout << "Student::operator=()" << endl; }
	~Student() { cout << "~Student()" << endl; }
};

int main()
{
	Student s1;
	cout << "================" << endl;
	Student s2;
	s1 = s2;
	return 0;
}

此时可用通过 域作用限定符 :: 显式调用父类中的函数。 

总的来说,子类中的默认成员函数调用规则可以概况为以下几点: 

  1. 子类的构造函数必须调用父类的构造函数,初始化属于父类的那一部分内容;如果没有默认构造函数,则需要显式调用
  2. 子类的拷贝构造、赋值重载函数必须要显式调用父类的,否则会造成重复析构问题
  3. 父类的析构函数在子类对象销毁后,会自动调用,然后销毁父类的那一部分

 注意:

  • 子类对象初始化前,必须先初始化父类那一部分
  • *子类对象销毁后,必须销毁父类那一部分
  • 不能显式的调用父类的析构函数(因为这不符合栈区的规则),父子类析构函数为同名函数destruct ,构成隐藏,如果想要满足我们的析构需求,就需要将其变为虚函数,构成重写。

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【软考---系统架构设计师】计算机网络章节

目录 一、TCP/IP协议族 &#xff08;1&#xff09;基本介绍 &#xff08;2&#xff09;TCP和UDP的区别 &#xff08;3&#xff09;DNS协议 &#xff08;4&#xff09;DHCP协议 二、网络规划与设计 &#xff08;1&#xff09;需求分析 &#xff08;2&#xff09;通信规范…

照片转漫画的软件有吗?分享4款热门的软件!

在数字化时代&#xff0c;我们总是追求新鲜、有趣、创意十足的方式来展现自我。其中&#xff0c;将普通照片转化为漫画风格的图像已成为许多年轻人的新宠。这种既能保留原照片中的人物特征&#xff0c;又能赋予其独特艺术气息的方式&#xff0c;让许多人趋之若鹜。那么&#xf…

头歌-机器学习 第16次实验 EM算法

第1关:极大似然估计 任务描述 本关任务:根据本节课所学知识完成本关所设置的选择题。 相关知识 为了完成本关任务,你需要掌握: 什么是极大似然估计; 极大似然估计的原理; 极大似然估计的计算方法。 什么是极大似然估计 没有接触过或者没有听过”极大似然估计“的同学…

[蓝桥杯 2018 国 C] 迷宫与陷阱

题目&#xff1a; 思路&#xff1a; 代码&#xff1a; #include <bits/stdc.h> using namespace std; const int N1e310; char g[N][N];//输入&#xff1a;图的数组 int vis[N][N]; /* 剪枝&#xff1a;记录magic的个数&#xff08;一个点经过两次&#xff0c;magic越大…

4.9号驱动

1. ARM裸机开发和Linux系统开发的异同 相同点&#xff1a;都是对硬件进行操作 不同点&#xff1a; 有无操作系统 是否具备多进程多线程开发 是否可以调用库函数 操作地址是否相同&#xff0c;arm操作物理地址&#xff0c;驱动操作虚拟地址 2. Linux操作系统的层次 应用层…

深度学习500问——Chapter07:生成对抗网络(GAN)(1)

文章目录 7.1 GAN基本概念 7.1.1 如何通俗理解GAN 7.1.2 GAN的形式化表示 7.1.3 GAN的目标函数是什么 7.1.4 GAN的目标函数和交叉熵有什么区别 7.1.5 GAN的Loss为什么降不下去 7.1.6 生成式模型、判别式模型的区别 7.1.7 什么是mode collapsing 7.1.8 如何解决mode collapsing …

【计算机毕业设计】就业信息管理系统——后附源码

&#x1f389;**欢迎来到琛哥的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 琛哥&#xff0c;一名来自世界500强的资深程序猿&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 琛哥在深度学习任务中展现出卓越的能力&a…

Java快速入门系列-9(Spring框架与Spring Boot —— 深度探索及实践指南)

第九章:Spring框架与Spring Boot —— 深度探索及实践指南 9.1 Spring框架概述9.2 Spring IoC容器9.3 Spring AOP9.4 Spring MVC9.5 Spring Data JPA/Hibernate9.6 Spring Boot快速入门与核心特性9.7 Spring Boot的自动配置与启动流程详解9.8 创建RESTful服务与数据库交互实践…

如何在Ubuntu系统使用docker部署DbGate容器并发布至公网可访问

文章目录 1. 安装Docker2. 使用Docker拉取DbGate镜像3. 创建并启动DbGate容器4. 本地连接测试5. 公网远程访问本地DbGate容器5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 本文主要介绍如何在Linux Ubuntu系统中使用Docker部署DbGate数据库管理工…

大数据基本名词

目录[-] 1.1. 1. Hadoop1.2. 2. Hive1.3. 3. Impala1.4. 4. Hbase1.5. 5.hadoop hive impala hbase关系1.6. 6. Spark1.7. 7. Flink1.8. 8. Spark 和 Flink 的应用场景 1. Hadoop 开源官网&#xff1a;https://hadoop.apache.org/ Hadoop是一个由Apache基金会所开发的分…

arXiv苹果公司新论文“Self-Play”方法训练车辆道路merge的策略

arXiv苹果公司新论文“Self-Play”方法训练车辆道路merge的策略 附赠自动驾驶学习资料和量产经验&#xff1a;链接 苹果于2020年1月28日上传arXiv新论文“Towards Learning Multi-agent Negotiations via Self-Play“。 摘要&#xff1a; 做出复杂、鲁棒和安全的串行决策是智能…

0169. 多数元素

Problem: 169. 多数元素 文章目录 思路解题方法复杂度Code 思路 利用哈希表计数&#xff0c;遍历一遍数组此时时间复杂度为 O ( n ) O(n) O(n)&#xff0c;空间复杂度为 O ( n ) O(n) O(n)。 参考K神学会摩尔投票法 这个方法思想很简单&#xff0c;就是模拟投票&#xff0c;且…

嵌入式热门发展方向有哪些?

嵌入式热门发展方向有哪些? 现在越来越多的计算机、电子、通信、自动化等相关专业跨行学习嵌入式&#xff0c;嵌入式开发作为未来职业发展的方向&#xff0c;不论从薪资待遇还是发展前景来看&#xff0c;都非常不错。 在嵌入式领域&#xff0c;有多个热门发展方向&#xff0…

JVM常用参数一

jvm启动参数 JVM&#xff08;Java虚拟机&#xff09;的启动参数是在启动JVM时可以设置的一些命令行参数。这些参数用于指定JVM的运行环境、内存分配、垃圾回收器以及其他选项。以下是一些常见的JVM启动参数&#xff1a; -Xms&#xff1a;设置JVM的初始堆大小。 -Xmx&#xff1…

VueRouter使用,界面切换

一、安装 vue-router3&#xff0c;4分别对应vue2&#xff0c;3.。我现在用的是vue2&#xff0c; npm install vue-router3二、使用 ①首先在component路径下提前写好需要渲染的组件。 ②在App.vue中使用router声明路由。其中router-link的to指明渲染哪一个组件。router-view…

企业鸿蒙原生应用元服务备案实操基本材料要求

一、要提前准备的主要材料包括 域名&#xff0c;服务器&#xff0c;包名&#xff0c;公钥&#xff0c;MD5值&#xff0c;法人身份证正反两面&#xff0c;邮箱&#xff0c;手机号2个。 域名是备案过的&#xff0c;应为要求域名能打开&#xff0c;还要悬挂备案号。 操作时要提前沟…

【从浅学到熟知Linux】进程状态与进程优先级(含进程R/S/T/t/D/X/Z状态介绍、僵尸进程、孤儿进程、使用top及renice调整进程优先级)

&#x1f3e0;关于专栏&#xff1a;Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程及数据库等内容。 &#x1f3af;每天努力一点点&#xff0c;技术变化看得见 文章目录 进程状态进程状态查看R运行状态&#xff08;running&#xff09;S睡眠状态&#xff08;sleeping&a…

CentOS安装MeterSphere并实现无公网IP远程访问本地测试平台

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…

电脑出现正在清理已完成100%,出现正在清理已完成0%,正在准备windows请不要关闭你的计算机,电脑出现dell图标,下方没有小的旋转圆圈

1.开机发现停留在dell图标。在正上方。和平时不一样。下方没有小的旋转圆圈 2.长按电源键5秒重启 3.电脑重启后。显示正在准备windows请不要关闭你的计算机 4.电脑自动重启 5.电脑出现正在清理已完成0%&#xff0c;等了大概十几分钟 6.电脑出现正在清理已完成100%&#xff0c;等…

图片尺寸在线怎么修改大小?利用图片在线处理工具解决

在社交媒体平台上分享照片是我们日常生活中常见的活动之一。有时&#xff0c;我们需要调整照片的尺寸以适应社交媒体平台的要求。在线修改图片尺寸的工具可以帮助我们快速调整照片的大小&#xff0c;确保其在社交媒体上显示完整且美观。 压缩图网站&#xff0c;点击“图片改大…