【C++ | 继承】C++的继承详解 及 例子源码演示

news2024/10/2 3:34:25

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
  • 🎄二、继承的定义
  • 🎄三、派生类(子类)继承了什么
    • ✨3.1 派生类从基类继承了哪些东西
    • ✨3.2 继承的东西怎么使用
  • 🎄四、派生类对象的创建、销毁
  • 🎄五、基类和派生类的类型关系
  • 🎄六、继承中的类作用域



在这里插入图片描述

🎄一、概述

继承的概念:

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

通过继承可以完成的一些工作:

  • 1、可以在己有类的基础上添加功能。 例如, 对于数组类, 可以添加数学运算。
  • 2、可以给类添加数据。 例如, 对于字符串类, 可以派生出一个类, 并添加指定字符串显示颜色的数据成员。
  • 3、可以修改类方法的行为。

按照继承权限区分,有公开继承、保护继承、私有继承三种方式。
按照继承的直接基类个数区分,有单继承、多重继承两种方式。


在这里插入图片描述

🎄二、继承的定义

通过继承 ( inheritance ) 联系在一起的类构成一种层次关系。通常在层次关系的根部有一个基类(base class),其他类则直接或间接地从基类继承而来,这些继承得到的类称为派生类(derived class)。基类负责定义在层次关系中所有类共同拥有的成员, 而每个派生类定义各自特有的成员

派生类必须通过使用 类派生列表(class derivation list) 明确指出它是从哪个(哪些)基类继承而来的。
类派生列表的形式是:首先是一个冒号,后面紧跟以逗号分隔的基类列表,其中每个基类前面可以有访问说明符。
在这里插入图片描述
访问说明符有三种:pulbic(公有继承)、protect(保护继承)、private(私有继承)。
类派生列表也可以有多个基类,如:class CDog : public CAnimal, public CFriend

下面用代码演示一下继承的过程:

class CAnimal{
public:
	CAnimal(){
		sprintf(m_name, "Ainmal");
	}
	void run(){
		cout << "my name is "<< m_name <<", Animal run" << endl;
	}
private:
	char m_name[64];
};


class CDog : public CAnimal{

};

is-a:我们这里使用的是 公有继承,它需要遵循is-a的关系,就是派生类 is a 基类,可以表示成派生类 是 基类,例如“狗 是 动物”。按照这个关系,我们不能在 动物苹果 之间使用公有继承。派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行。


在这里插入图片描述

🎄三、派生类(子类)继承了什么

继承完之后,那么派生类继承了哪些东西?这些东西怎样使用呢?这个小节,我们来学习这两个问题的答案。

✨3.1 派生类从基类继承了哪些东西

派生类继承了基类的 数据成员 和 大部分的函数成员,而基类的 构造函数、析构函数、赋值运算符函数 不会被派生类所继承。

下面修改一下上面的代码,演示派生类继承了父类的数据成员。

// 22_Inheritance.cpp
// g++ 22_Inheritance.cpp 
#include <iostream>
#include <stdio.h>
#include <string.h>

using namespace std;

class CAnimal{
	enum{NAME_LEN=64};
public:
	CAnimal(){
		sprintf(m_name, "Ainmal");
	}
	void run(){
		cout << "my name is "<< m_name <<", Animal run" << endl;
	}
private:
	char m_name[NAME_LEN];
};


class CDog : public CAnimal{
private:
	int m_hairColor;	// 毛发颜色
};

int main ()
{
	CAnimal animal;
	cout << "sizeof(animal)=" << sizeof(animal) << endl;
	
	CDog dog;
	cout << "sizeof(dog)=" << sizeof(dog) << endl;
	return 0;
}

运行结果如下:

从结果来看,派生类CDog 比基类CAnimal 多了 4 个字节。这4个字节刚好是一个int型,也就是派生类多的 m_hairColor 成员。而成员函数也是被继承的,但成员函数并不会放在类对象的内存中,而是放在程序的代码段。打印类对象大小只会打印该对象的数据成员总和。

在这里插入图片描述

✨3.2 继承的东西怎么使用

派生类继承的成员并不是可以随意地访问,跟继承时使用的 访问说明符 以及该成员在 基类中的访问权限 有关。

按照访问说明符的不同,可以分为public继承、protected继承、private继承,它们从基类继承到的成员访问权限如下表:

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

从表格可以得出下面几个结论:
1、基类的私有成员,在派生类无法访问,只能通过基类的公有成员函数去访问。
2、基类的公有成员、保护成员,可以在派生类成员函数直接访问。
3、公有继承中,基类的公有成员、保护成员在派生类中也分别是公有、保护。
4、保护继承中,基类的公有成员、保护成员在派生类中都是保护的。
5、私有继承中,基类的公有成员、保护成员在派生类中都是私有的。

综上所述,派生类继承到的东西,并不是可以随意使用,而是按照上面表格和结论去使用。下面用代码演示怎样使用。代码使用了公有继承,基类的公有成员将成为派生类的公有成员;基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问。

// 22_Inheritance1.cpp
// g++ 22_Inheritance1.cpp 
#include <iostream>
#include <stdio.h>
#include <string.h>

using namespace std;

class CAnimal{
	enum{NAME_LEN=64};
public:
	CAnimal(){
		sprintf(m_name, "Ainmal");
	}
	void run(){
		cout << "my name is "<< m_name <<", Animal run" << endl;
	}
	void SetName(const char *name)// 修改私有成员 m_name
	{
		int len = strlen(name) > NAME_LEN ? NAME_LEN : strlen(name);
		memset(m_name, 0, sizeof(m_name));
		strncpy(m_name, name, len);
	}
	const char *GetName()
	{
		return m_name;
	}
private:
	char m_name[NAME_LEN];
};


class CDog : public CAnimal{
public:
	enum{HAIR_BLACK, HAIR_WHITE};
	CDog()
	{
		m_hairColor = HAIR_BLACK;
		//sprintf(m_name, "Dog"); // 报错,基类私有成员在派生类无法访问。
	}
private:
	int m_hairColor;	// 毛发颜色
};

int main ()
{
	CAnimal animal;
	animal.run();
	
	CDog dog;
	dog.run();
	
	CDog dog1;
	dog1.SetName("dog1");
	dog1.run();
	return 0;
}

运行结果如下,代码演示了公有继承,派生类无法访问基类的m_name,但可以通过基类的公有函数SetName去修改m_name:
在这里插入图片描述


在这里插入图片描述

🎄四、派生类对象的创建、销毁

我们定义了一个派生类,大概率是需要用到这个派生类的对象的,那么派生类对象是怎样创建、销毁的呢?

创建派生类对象的几个要点:
1、派生类没有继承基类的构造函数。
2、创建派生类对象时,程序会先创建派生类对象的基类部分,所以,会先调用基类的构造函数,再调用派生类构造函数。
3、如果没有显式地调用基类构造函数,那么程序会自动调用基类的 无参构造函数 来创建派生类对象的基类部分。
4、如果要显式调用基类构造函数,为了保证先创建基类部分,需要在派生类构造函数的 成员初始化列表 去调用。
5、一般情况下,让基类构造函数初始化基类部分,派生类构造函数初始化派生类新增的数据成员。

派生类对象的销毁
派生类对象的销毁时,会先调用派生类的析构函数,再调用基类的析构函数。这个顺序与创建时相反。

🌰举例子:

// 22_Inheritance2.cpp
// g++ 22_Inheritance2.cpp 
#include <iostream>
#include <stdio.h>
#include <string.h>

using namespace std;

class CAnimal{
	enum{NAME_LEN=64};
public:
	CAnimal()						// 无参构造
	{
		sprintf(m_name, "Ainmal");
		cout << "Calling CAnimal(): this=" << this << endl;
	}
	CAnimal(char *name)
	{
		int len = strlen(name) > NAME_LEN ? NAME_LEN : strlen(name);
		memset(m_name, 0, sizeof(m_name));
		strncpy(m_name, name, len);
		cout << "Calling CAnimal(char*): this=" << this << endl;
	}
	~CAnimal()						// 无参构造
	{
		cout << "Calling ~CAnimal(): this=" << this << endl;
	}
	void run(){
		cout << "my name is "<< m_name <<", Animal run" << endl;
	}
private:
	char m_name[NAME_LEN];
};


class CDog : public CAnimal{
public:
	enum{HAIR_BLACK, HAIR_WHITE};
	CDog()
	{
		cout << "Calling CDog(): this=" << this << endl;
		m_hairColor = HAIR_BLACK;
	}
	CDog(int color, char* name) : CAnimal(name)
	{
		cout << "Calling CDog(int, char*): this=" << this << endl;
		m_hairColor = color;
	}
	~CDog()
	{
		cout << "Calling ~CDog(): this=" << this << endl;
	}
private:
	int m_hairColor;	// 毛发颜色
};

int main ()
{
	CDog dog;
	dog.run();
	cout << endl;
	
	CDog dog1(CDog::HAIR_BLACK, (char*)"dog1");
	dog1.run();
	cout << endl;
	return 0;
}

运行结果:
在这里插入图片描述


在这里插入图片描述

🎄五、基类和派生类的类型关系

这个小节介绍基类和派生类的一些特殊关系,主要有以下几点:
1、派生类对象可以直接使用基类的公有成员函数。
2、基类指针可以在不进行显式类型转换的情况下指向派生类对象。
3、基类引用可以在不进行显式类型转换的情况下引用派生类对象。
4、派生类对象,可以直接赋值给基类对象,而不需要强制转换(不建议这样做)。
5、派生类指针需要在显式类型转换的情况下指向基类对象,一般不这样做;
6、派生类引用需要在显式类型转换的情况下引用基类对象,一般不这样做;
7、派生类对象不能用来初始化或赋值给基类对象。

  • 第1点没什么好解释的,基类的公有成员被派生类继承后仍旧是公有成员,所以可以直接通过派生类对象访问。

  • 而第2、3点是跟类型相关的。通常,C++不允许将一种类型的地址赋给另一种类型的指针,也不允许一种类型的引用指向另一种类型,那为什么基类指针指向派生类对象时不需要进行显式转换呢?这个规则是 is-a 关系的一部分,派生类对象是基类对象(狗是动物),狗(CDog)继承了动物(CAnimal)的所有数据成员、成员函数,所以CAnimal的所有操作都使用于CDog对象。派生类中有基类部分,当使用基类的指针、引用去指向派生类对象时,会指向派生类的基类部分,如下图:
    在这里插入图片描述

  • 第4点,C++允许用派生类对象初始化(或赋值给)基类对象。但一般不这么做,因为基类的数据成员一般比派生类少,这样的初始化或赋值会导致派生类对象的一些数据成员被“切割”。可能会产生一些意外的结果。
    当我们用一个派生类对象为一个基类对象初始化或赋值时,只有该派生类对象中的基类部分会被拷贝、移动或赋值,它的派生类部分将被忽略掉
    在这里插入图片描述

  • 第5、6点,让派生类指针或引用指向基类对象,需要使用强制转换。因为派生类CDog的数据成员比基类CAnimal多,所以这样的指向会导致派生类指针或引用只能使用基类部分的成员,如果使用派生类自身的成员可能有问题,因为实际所指向的基类对象没有这些成员。
    在这里插入图片描述

  • 第7点,不允许用基类对象来初始化或赋值给派生类对象,因为基类的成员会被派生类少,用基类对象初始化或赋值给派生类对象将导致派生类的一些成员没有被赋值到,这是不允许的。
    在这里插入图片描述

下面用代码演示一下基类、派生类的关系:

// 22_Inheritance2.cpp
// g++ 22_Inheritance2.cpp 
#include <iostream>
#include <stdio.h>
#include <string.h>

using namespace std;

class CAnimal{
   enum{NAME_LEN=64};
public:
   CAnimal()						// 无参构造
   {
   	sprintf(m_name, "Ainmal");
   	cout << "Calling CAnimal(): this=" << this << endl;
   }
   CAnimal(char *name)
   {
   	int len = strlen(name) > NAME_LEN ? NAME_LEN : strlen(name);
   	memset(m_name, 0, sizeof(m_name));
   	strncpy(m_name, name, len);
   	cout << "Calling CAnimal(char*): this=" << this << endl;
   }
   ~CAnimal()						// 无参构造
   {
   	cout << "Calling ~CAnimal(): this=" << this << endl;
   }
   void run(){
   	cout << "my name is "<< m_name <<", Animal run" << endl;
   }
private:
   char m_name[NAME_LEN];
};


class CDog : public CAnimal{
public:
   enum{HAIR_BLACK, HAIR_WHITE};
   CDog()
   {
   	cout << "Calling CDog(): this=" << this << endl;
   	m_hairColor = HAIR_BLACK;
   }
   CDog(int color, char* name) : CAnimal(name)
   {
   	cout << "Calling CDog(int, char*): this=" << this << endl;
   	m_hairColor = color;
   }
   ~CDog()
   {
   	cout << "Calling ~CDog(): this=" << this << endl;
   }
   int DogColor()
   {
   	cout << "DogColor is " << (HAIR_BLACK==m_hairColor?"black":"white") << endl;
   	return m_hairColor;
   }
private:
   int m_hairColor;	// 毛发颜色
};

int main ()
{
   CDog dog(CDog::HAIR_BLACK, (char*)"dog1");
   dog.run();
   cout << endl;
   
   // 派生类类型 ==》基类类型
   CAnimal animal = dog;
   animal.run();
   cout << endl;
   
   CAnimal &r_animal = dog;
   r_animal.run();
   cout << endl;
   
   CAnimal *p_animal = &dog;
   p_animal->run();
   cout << endl;
   
   // 基类类型 ==》派生类类型,需要强制转换
   //CDog Dog1 = (CDog)animal; 默认不允许
   
   CDog *pDog = (CDog*)&animal;
   pDog->DogColor();
   
   CDog &rDog = (CDog&)animal;
   rDog.DogColor();
   
   return 0;
}

运行结果:
在这里插入图片描述


在这里插入图片描述

🎄六、继承中的类作用域

每个类定义自己的作用域,在这个作用域内我们定义类的成员。
当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内。

  • 名字查找
    派生类对象的名字查找:当出现下面代码时,编译器是怎样进行名称查找的?
    CDog dog(CDog::HAIR_BLACK, (char*)"dog1");
    dog.run();
    
    编译器会先到派生类中找run成员函数;
    如果找不到,因为CDog是CAnimal的派生类,所以会在CAnimal类去找run函数,最终,run被解析为CAnimal的run。
  • 名字隐藏
    当派生类重用定义在其直接基类或间接基类中的名字,此时派生类的成员将隐藏同名的基类成员。
    class CAnimal{
    public:
    	...
    	void run(){
    		cout << "my name is "<< m_name <<", Animal run" << endl;
    	}
    };
    class CDog : public CAnimal{
    public:
    	...
    	void run(){		// 隐藏了 CAnimal 的 run
    		cout << "Dog run" << endl;
    	}
    };
    
  • 通过作用域运算符来使用隐藏的成员
    如果基类名称被派生类同名成员给隐藏了,可以使用基类作用域运算符来使用基类的隐藏成员。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

参考:
《C++ primer plus》
C++继承(详细)

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

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

相关文章

C++视觉开发 七.模板匹配

模板匹配是一种基于图像处理的技术&#xff0c;用于在目标图像中寻找与给定模板图像最相似的部分。通过设定的模板&#xff0c;将目标图像与模板图像比较&#xff0c;计算其相似度&#xff0c;实现对目标图像的判断。 目录 一.手写数字识别 重要函数&#xff1a; 1.cv::glob…

Mac平台虚拟机 Parallels Desktop v19.4.1,支持M1/M2/M3芯片组

Parallels Desktop for Mac是功能强大灵活度高的虚拟化方案&#xff0c;无需重启即可在同一台电脑上随时访问Windows和Mac两个系统上的众多应用程序。从仅限于PC的游戏到生产力软件&#xff0c;Parallels Desktop都能帮您实现便捷使用。Parallels Desktop 是一款专业的Mac虚拟机…

虚拟机因断电进入./#状态解决办法

现象&#xff1a; 解决&#xff1a;先查看错误日志&#xff1a;journalctl -p err -b查看自己虚拟机中标黄部分的名字 之后运行&#xff1a;xfs_repair -v -L /dev/sda #这里sda用你自己标黄的 最后重启 reboot 即可。

ArcGIS的智慧与情怀

初识ArcGIS 在这个信息化的时代&#xff0c;ArcGIS如同一位智者&#xff0c;静静地伫立在地理信息系统的巅峰。初识它时&#xff0c;我仿佛走进了一片未知的领域&#xff0c;心中充满了好奇与期待。ArcGIS&#xff0c;这款专业的地理信息系统软件&#xff0c;凭借其强大的功能…

【k8s中安装rabbitmq】k8s中安装rabbitmq并搭建镜像集群-hostpath版

文章目录 简介一.条件及环境说明二.需求说明三.实现原理及说明四.详细步骤4.1.规划节点标签4.2.创建configmap配置4.3.创建三个statefulset和service headless配置4.4.创建service配置 五.安装完后的配置六.安装说明 简介 k8s集群中搭建rabbitmq集群服务一般都会用到pvc&#x…

传知代码-图神经网络长对话理解(论文复现)

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 概述 情感识别是人类对话理解的关键任务。随着多模态数据的概念&#xff0c;如语言、声音和面部表情&#xff0c;任务变得更加具有挑战性。作为典型解决方案&#xff0c;利用全局和局部上下文信息来预测对话中每…

2024世界人工智能大会:AI产品技术与未来趋势的深度解析

随着2024年世界人工智能大会&#xff08;WAIC 2024&#xff09;在上海的圆满落幕&#xff0c;我们见证了人工智能技术的又一次飞跃。本次大会以“以共商促共享&#xff0c;以善治促善智”为主题&#xff0c;汇聚了全球顶尖的智慧&#xff0c;共同探讨了AI技术的未来趋势和应用前…

妙笔生词智能写歌词软件:创新助力还是艺术之殇?

在音乐创作日益普及和多样化的当下&#xff0c;各种辅助工具层出不穷&#xff0c;妙笔生词智能写歌词软件便是其中之一。那么&#xff0c;它到底表现如何呢&#xff1f; 妙笔生词智能写歌词软件&#xff08;veve522&#xff09;的突出优点在于其便捷性和高效性。对于那些灵感稍…

JVM内存泄露的ThreadLocal详解

目录 一、为什么要有ThreadLocal 二、ThreadLocal的使用 三、实现解析 实现分析 具体实现 Hash冲突的解决 开放定址法 链地址法 再哈希法 建立公共溢出区 四、引发的内存泄漏分析 内存泄漏的现象 分析 总结 错误使用ThreadLocal导致线程不安全 一、为什么要有Thr…

Test-Time Adaptation via Conjugate Pseudo-labels--论文笔记

论文笔记 资料 1.代码地址 https://github.com/locuslab/tta_conjugate 2.论文地址 https://arxiv.org/abs/2207.09640 3.数据集地址 论文摘要的翻译 测试时间适应(TTA)指的是使神经网络适应分布变化&#xff0c;在测试时间仅访问来自新领域的未标记测试样本。以前的TT…

【pytorch24】Visdom可视化

TensorboardX pytorch有一个工具借鉴了tensorboard pip install tensorboardX 有查看变量的数值、监听曲线等功能 如何使用 新建SummaryWriter()实例 要把监听的数据&#xff0c;比如说要监听dummy_s1[0]&#xff08;y 坐标&#xff09;存放到data/scalar1中&#xff0c;…

普中51单片机:中断系统与寄存器解析(六)

文章目录 引言中断流程图中断优先级下降沿中断结构图中断相关寄存器IE中断允许寄存器&#xff08;可位寻址&#xff09;XICON辅助中断控制寄存器&#xff08;可位寻址&#xff09;TCON标志控制寄存器SCON串行口控制寄存器 中断号中断响应条件中断函数代码模板电路图开发板IO连接…

洁净车间的压缩空气质量如何检测(露点、水油、粒子、浮游菌)

通常一个空压机站的设备即为一个狭义的压缩空气系统&#xff0c;下图为一个典型的压缩空气系统流程图&#xff1a; 气源设备&#xff08;空气压缩机&#xff09;吸入大气&#xff0c;将自然状态下的空气压缩成为具有较高压力的压缩空气&#xff0c;经过净化设备除去压缩空气中的…

新手如何正确学习Python?分享我是如何2个月熟练掌握Python的!学习大纲+学习方式+学习资料 汇总!

前言 一直以来都有很多想学习Python的朋友们问我&#xff0c;学Python怎么学&#xff1f;爬虫和数据分析怎么学&#xff1f;web开发的学习路线能教教我吗&#xff1f; 我先告诉大家一个点&#xff0c;不管你是报了什么培训班&#xff0c;还是自己在通过各种渠道自学&#xff…

[C++][ProtoBuf][Proto3语法][三]详细讲解

目录 1.默认值2.更新消息1.更新规则2.保留字段reserved 3.未知字段1.是什么&#xff1f;2.未知字段从哪获取 4.前后兼容性5.选项option1.选项分类2.常用选项列举3.设置自定义选项 1.默认值 反序列化消息时&#xff0c;如果被反序列化的⼆进制序列中不包含某个字段&#xff0c;…

elasticsearch集群模式部署

系统版本&#xff1a;CentOS Linux release 7.9.2009 (Core) es版本&#xff1a; elasticsearch-7.6.2 本次搭建es集群为三个节点 添加启动用户 确保elasticsearch的启动用户为普通用户&#xff0c;这里我创建了es用户用于启动elasticsearch 执行命令为es用户添加sudo权限 v…

数学建模及国赛

认识数学建模及国赛 认识数学建模 环境类&#xff1a;预测一下明天的气温 实证类&#xff1a; 评价一下政策的优缺点 农业类&#xff1a; 预测一下小麦的产量 财经类&#xff1a; 分析一下理财产品的最优组合 规划类&#xff1a; 土地利用情况进行 合理的划分 力学类&#xf…

如何在 CentOS 中配置 Linux 命名空间(ip netns)

引言 Linux 命名空间是一项强大的技术&#xff0c;允许在同一系统上创建多个独立的虚拟化实例&#xff0c;每个实例可以拥有自己的网络栈、路由表、IP 地址等网络资源&#xff0c;实现资源的隔离和管理。本文将深入探讨如何在 CentOS 中配置和使用 ip netns 命名空间&#xff0…

网络安全合规建设

网络安全合规建设 一、法律安全需求基本合规&#xff08;1&#xff09;《网络安全法》重要节点等级保护政策核心变化 二、安全需求 业务刚需&#xff08;1&#xff09;内忧&#xff08;2&#xff09;外患 三、解决方法&#xff08;1&#xff09;总安全战略目标图&#xff08;2&…

PaddleVideo:Squeeze Time算法移植

参考PaddleVideo/docs/zh-CN/contribute/add_new_algorithm.md at develop PaddlePaddle/PaddleVideo GitHubAwesome video understanding toolkits based on PaddlePaddle. It supports video data annotation tools, lightweight RGB and skeleton based action recognitio…