C++——继承

news2024/11/20 20:20:52

作为面向对象的语言,c++开发了名为继承的机制,它是c++中代码复用的重要手段

允许程序员在保持原有特性的基础(基类)上进行扩展,并产生新的类(派生类),这就是继承。

继承的格式

class (派生类名) :  (继承方式) (基类名)

class A {
public:
	int _a;
	

};

class B :public A{
public :
	int _b;
};

int main()
{
	B b;
	return 0;
}

在上面的一段代码中,我让 B 类以 public 方式继承了 A 类;

并且通过断点,我们发现,b 对象中包含了 A 类的成员; 

而这里的继承方式又起着什么作用呢?

继承方式 

public、protected、private;

 我们继承一共有三种方式,而这三种方式和类的访问限定符是一样的;

而这三种方式继承分别有什么不同呢?

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

虽然这个表很复杂难记,但是实际上用一句话概括就是:

派生类继承的基类成员,选继承方式基类成员访问限定符权限小的那个作为派生类中基类成员的权限大小;

其中比较特殊的就是基类的private成员派生类不可见;

之前在了解类和对象的时候就说过三个访问限定符,其中private和protected的作用类似

都是在类外面不可见,但是实际上,protected 的范围稍微大点,可以在派生类中看见

private 则只能在基类里面看见

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

 我们都知道,c++中的赋值都是创建一个中间临时变量,然后将这个临时变量给对应的对象;

比如:

 这里编译器实际上是先创建一个中间变量记录b的值,然后再把中间变量赋给a;

但是中间变量具有常性,因此我们这样写就会出错;

 我们必须加上 const 修饰符才行;

 但是基类和派生类对象之间的赋值转换则没有这样的限制

 

这就是基类和派生类对象的赋值转换;

但是实际上,这里的赋值转换只传递了 b 对象中基类的那一部分给了 a2;

class A {
public:
	int _a;
	A(int a = 10)
		:_a(a)
	{

	}
};

class B : public A {
public:
	int _b;
	B()
		:_b(5), A(5)
	{

	}
};

int main()
{
	A a1;
	B b;
	cout << a1._a << endl;
	a1 = b;
	cout << a1._a << endl;

	return 0;
}

 

这就是继承的特殊之处——基类和派生类的切割

从字面意思上看,就是指基类对象接收派生类对象的时候,只会接收基类成员的那一部分;

规则:

1.派生类对象可以赋给基类对象 / 指针 / 引用 ;

2.基类对象不能赋值给派生类对象;

3.基类对象的指针和引用可以通过强制类型转换赋值给派生类对象的指针和引用;

通过规则我们可以知道,实际上基类对象的指针和引用是可以通过强制类型转换赋给派生类对象的指针和引用的;

但是也有特殊情况:

根据基类指针或者引用指向的地址不同,可能导致越界访问; 

 继承的作用域

 由于派生类会继承基类的一切成员,当基类和派生类中出现同名成员时,就会构成隐藏关系;

1.基类和派生类有独立的作用域

2.基类和父类出现同名成员时,派生类将屏蔽基类中同名成员的访问——隐藏

3.成员函数只需要函数名相同就构成隐藏

4.继承体系中最好不要定义同名成员

虽然会构成隐藏关系,但是我们也能够显式的访问父类的同名成员;

class A {
public:
	int _a;
	A(int a = 10)
		:_a(a)
	{

	}

	void func()
	{
		cout << "A::func()" << endl;
	}
};

class B : public A {
public:
	int _b;
	B()
		:_b(5), A(5)
	{

	}

	void func()
	{
		A::func();
		cout << "B::func()" << endl;
	}

};

int main()
{
	B b;
	b.func();
	return 0;
}

 

 就像上面写的一样,通过  <基类名>::<同名成员> 来访问同名成员;

而若是不显式访问,就只会访问派生类的成员;

派生类的默认成员函数

 在派生类的默认成员函数中,我们一般都是先调用基类构建,再派生类构建,先派生类析构再基类析构;

而不管是拷贝构造还是 运算符=重载,都需要先调用基类函数,再使用派生类的函数;

class A {
public:
	int _a;
	A(int a = 10)
		:_a(a)
	{
		cout << "A::A()" << endl;
	}
	~A()
	{
		cout << "A::~A()" << endl;
	}
	void func()
	{
		cout << "A::func()" << endl;
	}
};

class B : public A {
public:
	int _b;
	B()
		:_b(5), A(5)
	{
		cout << "B::B()" << endl;
	}

	~B()
	{
		cout << "B::~B()" << endl;
	}

	void func()
	{
		A::func();
		cout << "B::func()" << endl;
	}

};

int main()
{
	B b;
	return 0;
}

 

 

继承与友元

友元关系无法继承,因此基类友元无法访问子类私有和保护成员;

 

继承和静态成员

整个继承体系只有一个这样的成员

 

class A {
public:
	static int cout;
	A()
	{
		cout++;
	}
};

int A::cout = 0;

class B :public A {
public :
	B()
	{
		cout++;
	}
};

int main()
{
	A a1;
	cout << A::cout<<endl;

	B b1;
	cout << B::cout << endl;

	return 0;
}

 

 

可以发现,cout 的值是始终都是根据 A 类 和 B 类的构造函数来变化的;

复杂的菱形继承和菱形虚拟继承

继承这个机制是允许多继承的,也就是一个子类可以有多个父类;

但是这就造成了菱形继承;

 

 

 

 一般的单继承和多继承没有什么问题,但是菱形继承就会导致数据冗余和二义性的问题;

在上图中,D 类就含有两个 A 类的成员,并且当 D 类使用 A 类的成员时,编译器无法区分是用 C类的 A 还是 B 类的 A ,就导致了二义性的问题; 

而解决方法就是让 B 类 和 C 类虚拟继承 A 类,这样就解决了数据冗余和二义性的问题;

那么虚拟继承是如何做到解决这些问题的呢?

class A {
public: int _a = 1;
};

class B : public A {
public: int _b = 2;
};

class C : public A {
public:int _c = 3;
};

class D :public B, public C {
public: int _d = 4;
};

int main()
{
	D d;
	return 0;
}

 我们简单的写一段菱形继承的代码;

然后我们通过内存来看看 d 如何存储的;

 我们可以看到,普通的菱形继承中有两个 A 的成员。然后最底下是它自身;

而虚拟继承又如何呢?

class A {
public: int _a = 1;
};

class B : virtual public A {
public: int _b = 2;
};

class C :virtual public A {
public:int _c = 3;
};

class D :public B, public C {
public: int _d = 4;
};

int main()
{
	D d;
	return 0;
}

 

我们写下菱形虚拟继承的代码;

 当我们虚拟继承的时候, D 里面只有一个 A,因此 为了找到这个 A,B和C原来存储 A 的位置改为了一个地址,这个地址指向了一个表,用来记录 B 地址 和 C 地址分别 距离 A 的偏移量,用来访问那一个 A ;

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

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

相关文章

剑指 Offer 64. 求1+2+…+n

剑指 Offer 64. 求12…nhttps://leetcode.cn/problems/qiu-12n-lcof/ 求 12...n &#xff0c;要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句&#xff08;A?B:C&#xff09;。 示例 1&#xff1a; 输入: n 3 输出: 6 示例 2&#xff1a; 输入…

Linux——安装JDK和OpenJDK【多种方法】

目录 一、Linux安装OpenJDK 1、查看系统是否有自带的版本 2、删除OpenJDK 3、本地下载OpenJDK 4、无法本地下载&#xff0c;也可以使用命令下载&#xff08;方法1&#xff09; 4、无法本地下载&#xff0c;也可以使用命令下载&#xff08;方法2&#xff09; 5、拓展 三…

3-3-多线程-TheadLocal内存泄漏

Java TheadLocal内存泄漏 1、引言 组内来了一个实习生&#xff0c;看这小伙子春光满面、精神抖擞、头发微少&#xff0c;我心头一喜&#xff1a;绝对是个潜力股。为了帮助小伙子快速成长&#xff0c;我给他分了一个需求&#xff0c;这不需求刚上线几天就出网上问题了&#x1…

C++ 树进阶系列之线段树和它的延迟更新

1. 前言 线段树和树状数组有相似之处&#xff0c;可以用于解决区间类型的问题。 但两者又各个千秋&#xff0c;树状数组本质是数组&#xff0c;有着树的形&#xff0c;可以借用树的一些概念。线段树是典型的二叉树结构&#xff0c;无论神和形都是树&#xff0c;可以应用树的所…

用 Python 的 tkinter 模块编写一个好看又强大的中国象棋

继上次我的第一版的《中国象棋》程序之后&#xff0c;我又编写了第二版的《中国象棋》程序&#xff0c;关注我的粉丝知道&#xff0c;我在第一篇《中国象棋》的文章末尾说了&#xff0c;我会出第二版的&#xff0c;对第一版感兴趣的朋友们&#xff0c;可以去看看&#xff0c;也…

VueJS 之样式冲突与样式穿透

文章目录参考描述样式冲突现象scoped原理样式穿透深度选择器使用原理顶层元素局限性参考 项目描述搜索引擎Bing哔哩哔哩黑马程序员 描述 项目描述Edge109.0.1518.70 (正式版本) (64 位)操作系统Windows 10 专业版vue/cli5.0.8npm8.19.3VueJS2.6.14 样式冲突 在使用 Vue 进行…

大文件上传/下载

一、前言 大文件上传下载一直以来是前端常用且常考的热门话题。本文将分别介绍大文件上传/下载的思路和前端实现代码。 二、分片上传 整体流程 对文件做切片&#xff0c;选择文件后&#xff0c;对获取到的file对象使用slice方法可以将其按照制定的大小进行切片&#xff0c;…

使用matplotlib,pylab进行python绘图

一提到python绘图&#xff0c;matplotlib是不得不提的python最著名的绘图库&#xff0c;它里面包含了类似matlab的一整套绘图的API。因此&#xff0c;作为想要学习python绘图的童鞋们就得在自己的python环境中安装matplotlib库了&#xff0c;安装方式这里就不多讲&#xff0c;方…

openmmlab学习打卡1

openmmlab学习打卡1通用视觉框架 OpenMMLab通过 conda 安装通用视觉框架 OpenMMLab 基于pytorch实现 其中&#xff1a; 分类算法在 mmclassification 模块下 目标检测在 mmdetection 模块下 分割模型在 mmsegmentation 模块下&#xff08;openmmlab 2.0 版本中加入&#xff09…

洛谷P1885 Moo —— 搜索

This way 题意&#xff1a; 奶牛 Bessie 最近在学习字符串操作&#xff0c;它用如下的规则逐一的构造出新的字符串&#xff1a; S(0)S(0) S(0) moo S(1)S(0)S(1) S(0) S(1)S(0) m ooo S(0) S(0) S(0) moo m ooo moo moomooomoo S(2)S(1)S(2) S(1) S(2)S(1) m oooo S(…

无js实现拖拽边框改变大小的笔记

前言 最近刷抖音看到一款游戏"拣爱",看到这个人手动拖动的很有意思,就想着能不能前端实现,来学习学习,虽然说最终的效果没有gif图片那么好,但是也算实现了,吧… 具体原理 利用resize属性所出现的小拖拽条 再配合::-webkit-scrollbar设置拖拽区域宽度,高度,结合opac…

手动签发证书配置nginx

openssl和ssh基本用法 通过OpenSSL工具生成证书 创建私钥 openssl genrsa -des3 -out server.key 2048 注意&#xff0c;centos版本如果是CentOS Linux release 8.0.1905 (Core)版本&#xff0c;私钥长度不能设置成1024位&#xff0c;必须2048位。不然再最后启动nginx时会出…

java之数组模块

数组定义格式1.1数组概述一次性声明大量的用于存储数据的变量要存储的数据通常都是同类型数据&#xff0c;例如&#xff1a;考试成绩1.2什么是数组数组(array)是一种用于存储多个相同类型数据的存储模型1.3数组的定义格式格式一&#xff1a;数据类型[] 变量名范例&#xff1a; …

h5实现相机

什么是取景器 取景器是什么&#xff1f;取景器是相机的一个专业术语&#xff0c;在前端就是扫描拍照 取景器的实现原理 请求手机的一个媒体类型的视频轨道&#xff0c;利用一个div或者图片作为上层蒙层&#xff0c;然后在利用canvas绘制视频中某一帧的画面绘制为图片。 前期…

HTML基础知识

一个网站由两部分组成&#xff1a;前端和后端。前端主流语言目前是HTML、CSS、JS等。HTML只是描述了页面的内容&#xff08;骨架&#xff09;&#xff0c;CSS才是描述了页面的样式。HTML结构HTML标签HTML代码是由“标签”构成的&#xff0c;HTML描述了页面上有什么东西&#xf…

数字化转型导师坚鹏:银行数字化转型为什么需要融合王阳明心学

在BLM银行数字化转型方法论中&#xff0c;我之所以融合BLM模型与王阳明心学&#xff0c;作为一个工科背景并拥有多年软硬件产品研发经验的人来说&#xff0c;深刻地知道很多人利用了科技的力量做了大量的恶事&#xff0c;而不是善事&#xff0c;如黑客大量盗取、泄漏、贩卖客户…

ESLint 的一些理解

ESLint ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具&#xff0c;它的目标是保证代码的一致性和避免错误。 为什么要使用ESLint 有的可以帮我们避免错误&#xff1b;有的可以帮我们写出最佳实践的代码&#xff1b;有的可以帮我们规范变量的使用方式&a…

Docker入门之使用Dockerfile 构建镜像(七)

文章目录1. 前言2. Docker file 核心要点2.1 注意事项2.2 Docker file 执行流程2.3 Docker Image、Docker file、Docker Container区别2.4 Dockerfile常用保留字指令2.4.1 FROM2.4.2 MAINTAINER2.4.3 RUN2.4.4 EXPOSE2.4.5 WORKDIR2.4.6 USER2.4.7 ENV2.4.8 ADD2.4.9 COPY2.4.1…

ansible 简单使用

运行过程 1.加载自己的配置文件&#xff0c;默认/etc/ansible/ansible.cfg&#xff1b; 2.查找对应的主机配置文件&#xff0c;找到要执行的主机或者组&#xff1b; 3.加载自己对应的模块文件&#xff0c;如 command&#xff1b; 4.通过ansible将模块或命令生成对应的临时py文…

OpenMMLab 实战营打卡 - 第 一 课

OpenMMLab 实战营打卡 - 第 一 课 复习下总忘的基础知识 卷积的通道数变化 前一层特征纬度&#xff08;通道数&#xff09;决定核的通道数 当前层输出的特征纬度&#xff0c;由核的数量决定 图像尺寸变化 padding 公式&#xff1a;H′H−K12pH^{\prime}H-K12 pH′H−K12p…