继承(CPP)

news2024/11/25 4:04:34

引言

继承是CPP的一个重要语法。在现实生活中存在“子承父业”的说法,在CPP中同样存在这样的语法,而继承就是这种语法。

面向对象的三大特征:封装、继承、多态

本文将通过以下要素,进行继承的深入讲解

1.继承的概念及定义 2.基类和派生类对象赋值转换 3.继承中的作用域 4.派生类的默认成员函数 5.继承与友元 6.继承与静态成员 7.复杂的菱形继承及菱形虚拟继承 8.继承的总结和反思

1.继承的概念及定义

继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

继承的本质是类的复用,为什么会出现类的复用呢?生活中其实有大量类的复用的例子。假如我们的学校教务处想要开发一款教务管理工具,需要管理学生,也需要管理老师。老师和学生都是人,只不过是学号、工号不同,因此可以将“人”这个对象抽象出一个单独的类,去复用这个类,而学生、教工就可以继承“人”这个类的概念。

   这就是继承(子承父业),下面继承上面(公共部分)。上面叫做父类、基类,下面叫子类、派生类

继承的格式

继承的格式体现在派生类。当student类继承person类时,格式为student:person。

Person是父类,也称作基类。Student是子类,也称作派生类。public是继承方式

继承之后可以复用成员函数与成员变量。

class Student : public Person
{
protected:
 int _stuid; // 学号
};

继承关系和访问限定符

访问限定符分为:public、private、protected

其权限大小是public>protected>private

如何理解呢?public是共有设施;protected是家庭私有财产,允许继承之后使用;private则像是爸爸的私房钱,只能爸爸使用。

因此后两个在类外部不能使用,private继承之后也不能直接访问。

protected和private区别(cpp)

在C++中,protectedprivate都是访问修饰符,用于控制类的成员变量和成员函数的访问权限。以下是它们之间的主要区别:

private(私有):

  1. 访问权限:当成员被声明为private时,它们只能被同一个类中的函数访问,不能被类的实例或者派生类访问。
  2. 封装性private成员用于实现类的内部工作,对外部隐藏实现细节,是面向对象编程中封装特性的体现。
  3. 继承:在继承关系中,基类的private成员不会被派生类继承,即在派生类中无法直接访问基类的private成员。

protected(受保护的):

  1. 访问权限protected成员可以被同一个类中的函数访问,也可以被派生类中的函数访问,但是不能被类的实例直接访问。
  2. 继承:当类被继承时,protected成员在派生类中保持protected的访问级别,这意味着派生类的成员函数可以访问基类的protected成员,但类的用户代码不能直接访问。
  3. 封装性protected提供了一种比public更严格的访问级别,但比private更宽松,允许派生类访问基类的特定成员,这在实现某些继承关系时非常有用。

在选择使用protected还是private时,通常遵循最小权限原则,即尽可能使用private,只在需要让派生类访问基类特定成员时使用protected。这样可以保证类的封装性,同时提供必要的灵活性。

总结:

1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。

3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected> private。

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

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

这种继承是最常见的。

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

一般类型的转化

在我们之前的知识可以知道,相近类型的赋值要隐式类型转换,隐式类型转化要产生临时对象!!!也就是说,只要是隐式转化发生,就总是得产生一个常性的临时对象。

表示数据的多少int、double可以转化赋值。r引用的不是d,而是一个临时对象,常性

xxx赋值给string对象,可以看出自定义类型也要产生临时对象

那派生类和基类是如何进行的呢?

最常用的继承就是公有继承,公有继承可以理解成is-a的关系。即父子之间的赋值存在特殊的规则。

student继承person

可以观察到对象s可以直接赋值给对象p。

当然p类型的引用可以直接引用s(p是基类,s是派生类)。

为什么不需要const引用,而可以直接引用呢?原因就是is-a规则。

当子类赋值给父类时,我们可以人为是直接赋值,而不是产生了临时对象。

规则

派生类对象 可以赋值基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片 或者切割。寓意把派生类中父类那部分切来赋值过去。这是赋值兼容规则。

父类将获得子类继承的那一部分。

注意:

基类对象不能赋值给派生类对象

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类 的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。

3.继承中的作用域

首先我们要明确,基类和派生类都有独立的花括号{},因此他们属于独立的作用域。

关于继承中作用域相关现象的解释:

1. 在继承体系中基类派生类都有独立的作用域
2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
4. 注意在实际中在继承体系里面最好不要定义同名的成员
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected :
 string _name = "小李子"; // 姓名
 int _num = 111;   // 身份证号
};
class Student : public Person
{
public:
 void Print()
 {
 cout<<" 姓名:"<<_name<< endl;
 cout<<" 身份证号:"<<Person::_num<< endl;
 cout<<" 学号:"<<_num<<endl;
 }
protected:
 int _num = 999; // 学号
};
void Test()
{
 Student s1;
 s1.Print();
};

打印_num时,打印的时999而不是111,这就是构成了成员变量的隐藏。

当我们想要访问基类的_num时,需要点明作用域

观察下面的代码:

// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
 void fun()
 {
 cout << "func()" << endl;
 }
};
class B : public A
{
public:
 void fun(int i)
 {
 A::fun();
 cout << "func(int i)->" <<i<<endl;
 }
};
void Test()
{
 B b;
 b.fun(10);
};

这是关于两个fun的解释:

// B 中的 fun A 中的 fun 不是构成重载,因为不是在同一作用域
// B 中的 fun A 中的 fun 构成隐藏,成员函数满足函数名相同就构成隐藏。

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

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类
中,这几个成员函数是如何生成的呢?

规则:

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用(构造函数先执行初始化列表,再执行函数体)。(存在默认构造会在初始化列表阶段调用默认构造)
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
3. 派生类的operator=必须要调用基类的operator=完成基类的复制
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
保证派生类对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造
6. 派生类对象析构清理先调用派生类析构再调基类的析构
7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

构造函数

如果想显式定义构造函数,那么C++规定,必须把父类当成一个完整的对象去初始化。如果不写,会去调用父类的默认构造。

这是基类:person



class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}

	Person(const Person& p)
		: _name(p._name)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name; // 姓名
};
派生类:student类的构造实现(公有继承)
class Student : public Person
实现:
 
Student(const char* name, int id)
	:_id(id)
	, Person(name)
{
	cout << "Student(const char* name, int id)" << endl;
}

id用来初始化成员变量_id,name用来初始化基类Person

可以看到,在初始化列表阶段,先调用了基类的构造,然后函数体执行了派生类的构造。

拷贝构造

拷贝构造需要完成基类部分的拷贝

Person(const Person& p)
	: _name(p._name)
{
	cout << "Person(const Person& p)" << endl;
}

在派生类如何传参进行基类的拷贝呢?

其实只需要传入派生类对象就可以。这就是赋值兼容规则。在引用中,会截取父类的部分去完成父类拷贝构造的调用。

Student(const Student& s)
	:Person(s)
	, _id(s._id)
{
	cout << "Student(const Student& s)" << endl;
}

赋值重载

这是父类的赋值重载


	Person& operator=(const Person& p)
	{
		cout << "Person operator=(const Person& p)" << endl;
		if (this != &p)
			_name = p._name;

		return *this;
	}

派生类的赋值重载可以显式调用operator=(s)来完成父类重载的调用。

当然,我们必须指明作用域才可以。其次1.不能自身给自身赋值 2.return *this保证连续赋值


	Student& operator=(const Student& s)
	{
		if (&s != this)
		{
			Person::operator=(s);
			_id = s._id;
		}

		cout << "Student& operator=(const Student& s)" << endl;

		return *this;
	}

析构函数

析构函数不允许额外调用基类的析构。因为派生类析构之后,会自动调用基类的析构。否则会造成资源的重复释放。


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

原因:由于派生类可以访问基类成员,假设基类成员先析构,那么可能存在非法访问的问题。

构造:先父后子

析构:先子后父

基类占头尾

5.继承与友元

规则:友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
怎么理解呢?子承父业,子可以继承父亲的财产,但是父亲的朋友却不是你的朋友。除非进行声明。
class Student;
class Person
{
public:
 friend void Display(const Person& p, const Student& s);
protected:
 string _name; // 姓名
};
class Student : public Person
{
protected:
 int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
 cout << p._name << endl;
 cout << s._stuNum << endl;
}
void main()
{
 Person p;
 Student s;
 Display(p, s);
}

6. 继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例 。
也就是说,派生类也会继承静态成员,但是只是继承的使用权(和继承的函数一样)
class Person
{
public :
 Person () {++ _count ;}
protected :
 string _name ; // 姓名
public :
 static int _count; // 统计人的个数。
};
int Person :: _count = 0;
class Student : public Person
{
protected :
 int _stuNum ; // 学号
};
class Graduate : public Student
{
protected :
 string _seminarCourse ; // 研究科目
};
void TestPerson()
{
 Student s1 ;
 Student s2 ;
 Student s3 ;
 Graduate s4 ;
 cout <<" 人数 :"<< Person ::_count << endl;
 Student ::_count = 0;
 cout <<" 人数 :"<< Person ::_count << endl;
}

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

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

相关文章

什么是网络准入控制系统?四款网络准入控制系统推荐 干货满满!

在当今的企业网络环境中&#xff0c;随着设备类型的多样化和远程办公的普及&#xff0c;网络安全面临的挑战愈加复杂。网络准入控制系统&#xff08;Network Access Control, NAC&#xff09;应运而生&#xff0c;成为企业保障网络安全的重要工具。本文就带你详细了解这一系统&…

4 款基于Python的鼠标键盘自动化工具,可解决Windows、macOS和Linux下的桌面自动化问题

在日常工作中&#xff0c;自动化工具可以极大地提升我们的工作效率&#xff0c;尤其是当面对重复性任务时。今天&#xff0c;我们将详细介绍四款基于Python的鼠标键盘自动化工具&#xff0c;帮助你在各种平台上轻松实现自动化操作。这些工具分别是 PyAutoGUI、KeymouseGo、Keyl…

《Nature》重磅发布:ChatGPT在学术研究和写作中的最佳应用指南

在论文写作过程中&#xff0c;ChatGPT 已成为许多研究人员的得力助手。知名生物医学教授 Milton Pividori 博士在《Nature》杂志上发表的文章《Chatbots in Science: What Can ChatGPT Do for You?》详细探讨了如何通过精心设计的提示词来有效利用 ChatGPT 提高学术写作的效率…

支付宝线上小程序打开异常

1. 其他手机都可以正常访问线上版本&#xff0c;只有一个安卓手机不行&#xff08;排除支付宝低版本以及手机系统问题&#xff09; 2. 出现访问异常的手机都可以正常访问体验版以及开发版本 3. 尝试去关闭该手机的联调设置以及清除开发版缓存&#xff0c;成功访问线上版本 需要…

如何彻底关闭Chrome浏览器自动更新

1.首先找到桌面 中Google Chrome浏览器的图片,鼠标右键打开文件所在的位置 2.选择Google 目录 选择 Update 目录 右键 选择属性 右键 选择属性 点击确定 修改成功 3.继续 第(2)步 选择 高级 4.点击禁用继承 !!!!!!! 测试 再次点击 Update 文件夹 弹出 你当前无权访问该文件夹…

一文1400字Jmeter实现mqtt协议压力测试

1. 下载jmeter&#xff0c;解压 https://jmeter.apache.org/download_jmeter.cgi 以 5.4.3 为例&#xff0c;下载地址&#xff1a; https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.4.3.zip linux下解压&#xff1a; unzip apache-jmeter-5.4.3.zip 2. 下载m…

如何在 Github 上找到心仪的开源项目?

Github&#xff0c;全球最大的开源社区&#xff0c;集中了目前最多、最优质的各种开源代码。 我们经常说不要重复造轮子&#xff0c;但如果不知道怎么找到已有的轮子&#xff0c;那就没有前提&#xff0c;对自己来说什么轮子都是新的。所以&#xff0c;搞开发&#xff0c;首先要…

Opencv中的直方图(5)计算EMD距离的函数EMD()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算两个加权点配置之间的“最小工作量”距离。 该函数计算地球搬运工距离&#xff08;Earth Mover’s Distance&#xff09;和/或两个加权点配…

微电网管理系统

微电网管理系统 1. 相关概念简介 基本概念及分析意义&#xff1a; 微电网基本概念&#xff1a;微电网&#xff08;MG&#xff09;由分布式电源、用电负荷、能量管理系统等组成&#xff0c;是一个能够基本实现内部电力电量平衡的供用电系统。 通过整合分布式电源、储能、负荷…

libusb在window下,使用vsstudio的初步安装与使用

下载&#xff1a; 首先&#xff0c;访问github官网 https://github.com/libusb/libusb/tree/master 然后&#xff0c;在版本选择里面&#xff0c;选择标签&#xff0c;点击最新的标签&#xff0c;&#xff08;这种一般稳定性最高&#xff09; 选择完后使用自己的方式下载下来…

双轨直销模式:团队互助与业绩倍增的商业策略

双轨直销模式因其操作简单、业绩压力较小、管理方便以及初期爆发力强等特点&#xff0c;受到许多直销公司的喜爱&#xff0c;并促进了多家大型企业的成长。 一、双轨直销模式简介 双轨直销是一种独特的组织架构&#xff0c;其核心在于每个销售代表仅需构建两个独立的销售线&a…

MiniMax 首个文生视频模型发布,可生成 6s,限时免费;阿里 Qwen2-VL 第二代视觉语言模型开源丨RTE 开发者日报

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real-Time Engagement&#xff09; 领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、…

第七在线携手美国男士内衣品牌Tommy John,共启智能订货新篇章

【喜讯速递】在数字化转型的浪潮中&#xff0c;7thonline第七在线再次以卓越的技术实力和前瞻性的市场洞察&#xff0c;成功签约国际知名男士内衣品牌Tommy John&#xff0c;双方将携手推进智能订货会计划&#xff0c;共同开启零售供应链管理的新纪元。 男士内衣品牌Tommy John…

智慧水利解决方案应该部署哪些设备(近距离走进智慧水利解决方案)

作为一名水利工程技术人员&#xff0c;我深知智慧水利解决方案对于现代水资源管理的重要性。在我多年的工作经验中&#xff0c;我见证了传统水利系统向智能化转变的过程&#xff0c;也深刻理解了部署正确的设备对于实现智慧水利的关键作用。今天&#xff0c;我想分享一下在智慧…

android AccessibilityService合法合规增加小红书曝光阅读量(2024-09-02)

免责任声明: 任何可操作性的内容与本人无关,文章内容仅供参考学习&#xff0c;如有侵权损害贵公司利益&#xff0c;请联系作者&#xff0c;会立刻马上进行删除。 一、分析 目前可增加曝光阅读流量渠道入口&#xff08;完成&#xff09; 1. 发现页 打开小红书app选择顶部发现页&…

Python和MATLAB(Java)及Arduino和Raspberry Pi(树莓派)点扩展函数导图

&#x1f3af;要点 反卷积显微镜图像算法微珠图像获取显微镜分辨率基于像素、小形状、高斯混合等全视野建模基于探测器像素经验建模荧光成像算法模型傅里叶方法计算矢量点扩展函数模型天文空间成像重建二维高斯拟合天体图像伽马射线能量和视场中心偏移角标量矢量模型盲解卷积和…

三、数组————相关概念详解

数组 前言一、数据理论基础二、数组常用操作2.1 初始化数组2.2 访问数组中的元素2.3 插入元素2.4 删除元素 三、数组扩展3.1 遍历数组3.2 数组扩容 总结1、数组的优点2、数组的不足 前言 在数据结构中&#xff0c;数组可以算得上最基本的数据结构。数组可以用于实现栈、队列、…

中资优配:炒股最笨十句口诀?

在出资股票时&#xff0c;出资者假设掌握一些方法技巧等&#xff0c;可以协助出资者更好地在股市进行生意&#xff0c;下面为我们介绍炒股十大口诀。 1、不跳水不买&#xff0c;不冲高不卖&#xff0c;横盘不生意 不要在股价跳水时急速买入&#xff0c;也不要在股价一开始冲高…

薛定谔的空气墙?一文带你了解其背后的技术原理

封面图 悟空来了都得撞墙&#xff1f; 目前&#xff0c;被称作“村里第一个大学生”的国产3A游戏《黑神话&#xff1a;悟空》发售已经有一段时间了&#xff0c;游戏采用虚幻引擎4技术&#xff0c;仿佛将传统与现代的界限模糊&#xff0c;玩家游玩时沉浸感极强。然而&#xff…

C# 窗口页面布局

1.Groupbox 单机鼠标右键&#xff0c;置于底层 2.Label 在右方属性中修改名称 3.ComboBox 点击属性中的集合&#xff0c;可以添加选择项 4.CheckBox 在属性中修改名称 5.RichTextBox 富文本 在属性中修改名称与区域 6.StatusStrip 状态栏 将AutoSize改成false就可以修改…