C++继承和多态

目录

继承

继承的意义

访问限定符、继承方式

赋值兼容规则(切片)

子类的默认成员函数

多继承

继承is a和组合has a

多态

什么是多态

形成多态的条件

函数重载,隐藏,重写的区别

override和final

多态原理


继承

继承的意义

继承本质就是类之间代码的复用,通过子类继承父类的方式,让子类对象中具有父类对象的成员。

访问限定符、继承方式

通过访问限定符,可以限制子类可以继承哪些变量,不能继承哪些变量,在实际开发过程中通常使用公有public继承。父类中想要让子类继承的成员就用protected或public修饰,不想让子类看到就用private修饰。

赋值兼容规则(切片)

子类对象,指针,引用分别赋值给父类对象,指针,引用时,会遵循赋值兼容规则(切片)。即把子类对象中父类对象的可见范围给到父类对象、指针、引用

子类的默认成员函数

默认成员函数主要包括构造函数,拷贝构造,赋值运算符重载,析构函数。那么在子类中他们是如何调用的呢?(考虑到要初始化子类对象中的父类对象

子类的初始化包括对子类成员的初始化和对继承的父类成员的初始化

由于父类的默认成员函数在子类内是可见的,所以在初始化父类成员时,只需要调用父类的默认成员函数即可。子类成员再另当初始化。

例如,基类为Person,子类为Student的代码:

class Person
{
public:
	Person(string name,int age,string gender)
		:_name(name),_age(age),_gender(gender)
	{
		cout << "Person()" << endl;
	}
	Person(const Person& p)
	{
		cout << "Person(const Person& p)" << endl;
	}
	Person& operator=(const Person& p)
	{
		cout << "Person.operator=()" << endl;
		return *this;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;//姓名
	int _age;//年龄
	string _gender;//性别
};
class Student:public Person
{
private:
	string _id;//学号
	string _college;//所属学院
public:
	Student(string name, int age, string gender,string id,string college)
		:Person(name,age,gender)//调用父类构造
		,_id(id),_college(college)
	{
		//初始化子类成员
		cout << "Student()" << endl;
	}
	Student(const Student& s)
		:Person(s)//调用父类拷贝构造
	{
		//拷贝子类成员
		cout << "Student(const Student& s)" << endl;
	}
	Student& operator=(const Student& s)
	{
		Person::operator=(s);//调用父类的operator=
		cout << "Student.operator=()" << endl;
		//赋值子类成员
		return *this;
	}
	~Student()
	{
		//默认自动先调用子类析构,再调用父类析构
		cout << "~Student()" << endl;
	}
};
int main()
{
	Student s("cx", 20, "男", "2022112102601", "计算机与信息工程学院");//构造
	Student s2 = s;//拷贝构造
	Student s3("xxx", 18, "女", "2022112102728", "经济管理学院");//构造
	s3 = s2;//赋值
	return 0;
}

代码输出结果:

多继承

多继承:多继承就是一个类可以继承多个父类。

多继承会导致的问题:菱形继承,图示如下

为什么说菱形继承会有问题?

由于继承的本质是代码的复用,而菱形继承会导致A类对象在D类对象中存在两份,导致数据冗余和二义性。

如何解决:虚继承

虚继承就是多个类 在继承重复类时,都使用虚继承。这样在上图中D对象内就只会有一份A对象,消除了对象的冗余。

继承is a和组合has a

继承和组合本质都是代码的复用,使得一个对象存在于另一个对象之中。

继承:

class B
{
protected:
	int _b;
};
class A :public B
{
//A中可以直接使用父类的protected和public成员
protected:
	int _a;
};

组合:

class B
{
protected:
	int _b;
};
class A
{
protected:
	B _b;//B类对象作为成员,只能使用B类对象的public成员
	int _a;
};

由于组合只能使用另一个对象的公有成员,这使得组合而成的类之间耦合度更低,因此在组合和继承都可以达到目的时,组合方案更优。

多态

什么是多态

多态:多态就是一个接口多种实现,多种状态,是面向对象编程语言的一重要特征。当使用父类指针或引用接收子类对象指针或引用,并调用子类对象中重写的虚函数时,就会体现出多态性。即指向父类调父类,指向子类调子类

class Animal //基类
{
public:
	virtual void say()//虚函数
	{
		cout << "animal say " << endl;
	}
};
class Cat:public Animal  //子类,重写基类的虚函数
{
public:
	virtual void say()
	{
		cout << "cat say miao~miao~" << endl;
	}
};
class Dog :public Animal  //子类,重写基类的虚函数
{
public:
	virtual void say()
	{
		cout << "dog say wang~wang~" << endl;
	}
};
void func(Animal* n)//基类指针/引用接收
{
	n->say();//多态调用
}
int main()
{
	Animal a;
	Cat c ;
	Dog d;
	func(&a);
	func(&c);
	func(&d);
	return 0;
}

上面的代码基类为Animal,子类有Cat和Dog,当使用基类指针或引用接收对象的指针或引用时,指向哪个类调用哪个类的虚函数。

形成多态的条件

1.具有继承关系

2.虚函数重写

3.父类指针或引用调用虚函数

函数重载,隐藏,重写的区别

重载:同一作用域内,函数名相同,参数不同。

隐藏(重定义):子类函数和父类函数名字相同。父类函数就被隐藏了。

重写(覆盖):子类函数和父类函数名字相同,参数相同,返回类型相同,且父类函数是虚函数。

override和final

子类重写父类虚函数时,用override修饰子类重写的函数,用于检查是否成功重写(三同或者协变)。如果不构成重写在编译时就会报错。

用final修饰的类,不允许有子类继承

用final修饰的虚成员函数,在子类中不可被重写

多态原理

首先看一下这段代码

class A
{
public:
	void func()
	{
		;
	}
};
int main()
{
    A a;
	cout << sizeof(a) << endl;
	return 0;
}

因为类的成员函数存放在代码段,并不会在每个对象中单独存放一份,所以这里是空类,但是打印的是1,是因为要用至少一个字节来标识不同的对象。

class A
{
public:
	virtual void func()
	{
		;
	}
};
int main()
{
	A a;
	cout << sizeof(a) << endl;
	return 0;
}

这个类的大小是多少呢?

这又是为什么呢?为什么函数由普通函数变为虚函数,对象的大小就变了呢?

这是因为类中的每个虚函数地址要放到一个专门的位置,即虚函数表中。

对象中会多存放一个虚函数表指针,虚函数表中存放着一个类中所有的虚函数指针。

指针的大小在32位计算机上为4字节,64位计算机为8字节,所以类的大小变为8字节。

这和多态有什么关系呢?

子类继承父类时,会把父类对象中的虚函数表也继承下来。


重写虚函数前:

子类虚函数表中的虚函数地址和父类虚函数表中的虚函数地址是相同的。

重写之后:子类虚函数表中的虚函数地址会被重写的虚函数地址"覆盖"。

虚函数指针被覆盖后,此后在多态的条件下,基类指针指向父类对象时,编译器会在父类对象的虚函数表中查找虚函数,指向子类对象时,编译器会在子类对象的虚函数表中查找虚函数。进而体现出了指向谁,调用谁的多态性。

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

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

相关文章

矩阵管理系统实现后台统一管理的解决方案

在当今数字化浪潮中&#xff0c;企业面临着前所未有的挑战与机遇。如何快速响应市场变化、提升运营效率、降低管理成本&#xff0c;成为众多企业关注的焦点。而矩阵管理系统作为一种新兴的管理工具&#xff0c;凭借其强大的后台统一管理能力&#xff0c;正成为越来越多企业的首…

【Redis】复制(Replica)

文章目录 一、复制是什么&#xff1f;二、 基本命令三、 配置&#xff08;分为配置文件和命令配置&#xff09;3.1 配置文件3.2 命令配置3.3 嵌套连接3.4 关闭从属关系 四、 复制原理五、 缺点 以下是本篇文章正文内容 一、复制是什么&#xff1f; 主从复制 master&#xff…

基于javaScript的冒泡排序

目录 一.前言 二.设计思路和原理 三.源代码展示 四. 案例运行结果 一.前言 冒泡排序简而言之&#xff0c;就是一种算法&#xff0c;能够把一系列的数据按照一定的顺序进行排列显示&#xff08;从小到大或从大到小&#xff09;。例如能够将数组[5,4,3,2,1]中的元素按照从小到…

使用数字孪生实现电池管理系统 (BMS) 测试自动化

电池管理系统 (BMS) 监控和控制电动飞机和电动汽车等车辆中的电池。它需要在正常和极端条件下进行严格测试&#xff0c;以证明其质量和完整性。 使用模拟电池进行测试非常有益&#xff0c;因为可以快速、反复地安全地测试各种条件&#xff0c;而不会冒着宝贵硬件的风险。这种硬…

Hash表(C++)

本篇将会开始介绍有关于 unordered_map 和 unordered_set 的底层原理&#xff0c;其中底层实现其实就是我们的 Hash 表&#xff0c;本篇将会讲解两种 Hash 表&#xff0c;其中一种为开放定址法&#xff0c;另一种为 hash 桶&#xff0c;在unordered_map 和 unordered_set 的底层…

6、evil box one

低—>中 目标&#xff1a;获取root权限以及2个flag 主机发现 靶机 192.168.1100.40 或者使用fping -gaq 192.168.100.1/24发现主机使用ping的方式。 端口扫描 发现开放了22和80 可以使用-A参数&#xff0c;-A参数会得到更多的扫描细节 访问80端口就是一个apache的基本的…

Redis 7.x 系列【23】哨兵模式

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2. 工作原理2.1 监控2.2 标记下线2.3 哨兵领袖2.4 新的主节点2.5 通知更新 3. …

JMeter案例分享:通过数据验证的错误,说说CSV数据文件设置中的线程共享模式

前言 用过JMeter参数化的小伙伴&#xff0c;想必对CSV Data Set Config非常熟悉。大家平时更关注变量名称&#xff0c;是否忽略首行等参数&#xff0c;其余的一般都使用默认值。然而我最近遇到一个未按照我的预想读取数据的案例&#xff0c;原因就出在最后一个参数“线程共享模…

服务重启时容器未自动启动

1、容器重启策略 通过设置容器的重启策略&#xff0c;‌可以决定在容器退出时Docker守护进程是否重启该容器。‌常见的重启策略包括&#xff1a;‌ no&#xff1a;‌不重启容器&#xff0c;‌默认策略。‌always&#xff1a;‌无论容器是如何退出的&#xff0c;‌总是重启容器…

keil配置irom偏移地址进行IAP,偏移地址不生效问题解决

如果keil配置了 IROM1 偏移地址&#xff0c;但是生成的hex&#xff0c;程序并没有偏移&#xff0c;问题多半是出现在linker里如下图所示。选择了分散加载&#xff0c;所以keil配置地址偏移不生效。 点开edit 更改分散加载的地址偏移即可。 偏移成功&#xff0c;可以IAP了。

算法学习笔记(8.5)-零钱兑换问题二

目录 Question&#xff1a; 动态规划思路&#xff1a; 代码实现&#xff1a; 空间优化代码 Question&#xff1a; 给定n种硬币&#xff0c;第i种硬币的面值为coins[i-1],目标金额为amt&#xff0c;每种硬币可以重复选取&#xff0c;问凑出目标金额的硬币组合数量。 动态规划思路…

Java 线程池详解

序言 在高并发编程中&#xff0c;线程池是一个非常重要的组件。它不仅能够有效地管理和复用线程资源&#xff0c;还可以提升应用程序的性能和稳定性。本文将详细介绍Java中的线程池机制&#xff0c;以及如何正确地使用线程池。 一、什么是线程池 线程池是一组已经初始化并等…

ftp pool 功能分析及 golang 实现

本文探究一种轻量级的 pool 实现 ftp 连接。 一、背景 简要介绍&#xff1a;业务中一般使用较多的是各种开源组件&#xff0c;设计有点重&#xff0c;因此本文探究一种轻量级的 pool 池的思想实现。 期望&#xff1a;设置连接池最大连接数为 N 时&#xff0c;批量执行 M 个 F…

超时导致SparkContext构造失败的问题探究

文章目录 1.前言2. 基于事故现场对问题进行分析2.1 日志分析2.2 单独测试Topology代码试图重现问题 3. 源码解析3.1 Client模式和Cluster模式下客户端的提交和启动过程客户端提交时在两种模式下的处理逻辑ApplicationMaster启动时在两种模式下的处理逻辑 3.2 两种模式下的下层角…

OSPF.综合实验

1、首先将各个网段基于172.16.0.0 16 进行划分 1.1、划分为4个大区域 172.16.0.0 18 172.16.64.0 18 172.16.128.0 18 172.16.192.0 18 四个网段 划分R4 划分area2 划分area3 划分area1 2、进行IP配置 如图使用配置指令进行配置 ip address x.x.x.x /x 并且将缺省路由…

uniapp编译成h5后接口请求参数变成[object object]

问题&#xff1a;uniapp编译成h5后接口请求参数变成[object object] 但是运行在开发者工具上没有一点问题 排查&#xff1a; 1&#xff1a;请求参数&#xff1a;看是否是在请求前就已经变成了[object object]了 结果&#xff1a; 一切正常 2&#xff1a;请求头&#xff1a;看…

2024辽宁省数学建模C题【改性生物碳对水中洛克沙胂和砷离子的吸附】原创论文分享

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024 年辽宁省大学数学建模竞赛C题改性生物碳对水中洛克沙胂和砷离子的吸附完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃…

OpenGL笔记九之彩色三角形与重心插值算法

OpenGL笔记九之彩色三角形与重心插值算法 —— 2024-07-07 晚上 bilibili赵新政老师的教程看后笔记 code review! 文章目录 OpenGL笔记九之彩色三角形与重心插值算法1.运行3.main.cpp 1.运行 3.main.cpp 代码 #include <iostream>#define DEBUG//注意&#xff1a;glad…

虚拟机centos连接xshell

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 ☁️运维工程师的职责&#xff1a;监…

VsCode远程ssh连接失败:Could not establish connection to XXX

一、问题描述 在VsCode中按下"F1"&#xff0c;选择Remote-SSH:Connect to Host 选择一个已经配置好的SSH主机&#xff0c;比如我选择的是192.168.0.104&#xff1a; 结果提示&#xff1a;Could not establish connection to XXX 二、解决方法 观察VsCode的输出信息…