【C++】继承的理解

1.继承的概念和定义

1.1继承的概念

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


class B : public A	// 继承:A叫做基类/父类	B叫派生类/子类
{
public:
	void func()
	{
		std::cout << ma << std::endl;
	}
	int md;
private:
	int me;
protected:
	int mf;
};

int main()
{
	A a;
	B b;
	std::cout << sizeof(a) << std::endl;	// 12
	std::cout << sizeof(b) << std::endl;	// 24
}

1.2继承基类成员访问方式的变化 

 

总结:

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

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

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

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

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

1.3默认的继承方式? 

class定义的默认private,struct定义的默认public的.

1.4派生类的构造函数

派生类可以继承基类的构造函数和析构函数,用来初始化和释放从基类继承来的成员变量
派生类的构造函数和析构函数,负责初始化和清理派生类
派生来从基类继承来的成员的初始化和清理由基类的构造函数和析构函数来负责。

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

  • 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片
  • 或者切割。寓意把派生类中父类那部分切来赋值过去。
  • 基类对象不能赋值给派生类对象。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类
  • 的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run
  • Time Type Information)dynamic_cast 来进行识别后进行安全转换。
#include <iostream>
/*
1.把继承结构,也说成从上(基类)到下(派生类)的结构
2.

	基类对象 -> 派生类对象
	派生类对象 -> 基类对象

	基类指针(引用) -> 派生类对象
	派生类指针(引用) -> 基类对象

	总结:在继承结构中进行上下的类型转换,默认支持从下到上的类型的转换
*/

class Base
{
public:
	Base(int data = 10) :ma(data)
	{}
	void show()
	{
		std::cout << "Base:show()" << std::endl;
	}
	void show(int)
	{
		std::cout << "Base:show(int)" << std::endl;
	}
private:
	int ma;
};

class Derive : public Base
{
public:
	Derive(int data = 20):Base(data), mb(data)
	{}
	void show()
	{
		std::cout << "Derivr::show()" << std::endl;
	}
private:
	int mb;
};

int main()
{
	Base b(10);
	Derive d(20);

	// 基类对象 <- 派生类对象		类型从下到上的转换	Y
	b = d;
	// 派生类对象 <- 基类对象		类型从上到下的转换	N
	// d = b;
	// 基类指针(引用)<- 派生类对象	类型从下到上的转换	Y
	// 只能访问派生类继承的基类的那部分内容
	Base* pb = &d;
	pb->show();
	pb->show(10);
	((Derive*)pb)->show();	// 类型强转为派生类的指针就可以访问派生类的内容
	// 派生类指针(引用)<- 基类对象	类型从上到下的转换	N
	/*Derive* pd = (Derive*) & b;	不安全,涉及了内存的非法访问
	pd->show();*/

	d.show();
	d.Base::show(10);
	// d.show(20);	// 优先找的是派生类自己作用域的show名字成员;没有的话才去基类里面找

	return 0;
}

3.重载、隐藏、覆盖


    1.重载关系    
    一组函数要重载,必须处在同一个作用域中;而且函数名字相同,参数列表不同

    2.隐藏关系
    在继承结构当中,派生类的同名成员把基类的同名成员给隐藏调用了

    3.覆盖关系/相当于重写方法
    虚函数表中虚函数地址的覆盖

4.虚函数、静态绑定、动态绑定

#include <iostream>
#include <typeinfo>

class Base
{
public:
	Base(int data = 10) :ma(data)
	{}
	// 虚函数
	virtual void show()
	{
		std::cout << "Base:show()" << std::endl;
	}
	// 虚函数
	virtual void show(int)
	{
		std::cout << "Base:show(int)" << std::endl;
	}
private:
	int ma;
};

class Derive : public Base
{
public:
	Derive(int data = 20) :Base(data), mb(data)
	{}
	void show()
	{
		std::cout << "Derivr::show()" << std::endl;
	}
private:
	int mb;
};

int main()
{
	Derive d(50);
	Base* pb = &d;

	/*
	pb是基类类型	Base::show如果发现show是普通函数,就进行静态绑定call Base::show()
	如果发现pb是基类类型,编译阶段在Base类中去看show函数,发现是虚函数,就进行动态绑定了
	*/
	pb->show();		// 静态(编译时期)的绑定(函数的调用)
	pb->show(10);

	std::cout << sizeof(Base) << std::endl;
	std::cout << sizeof(Derive) << std::endl;
	/*
	pb的类型:Base -> 有没有虚函数
	如果Base没有虚函数,*pb识别的就是编译时期的类型, *pb -> Base类型
	如果Base有虚函数,*pb识别的就是运行时期的类型 RTTI 类型 "calss Derive"
	*/
	std::cout << typeid(pb).name() << std::endl;
	std::cout << typeid(*pb).name() << std::endl;

	return 0;
}

总结一:
如果一个类里面定义了虚函数,那么编译阶段,编译器给这个类类型产生了一个唯一的vftable虚函数表,
虚函数表中主要存储的内容就是RTTI指针和虚函数的地址。
RTTI:run-time type information 运行时的类型信息    指向类型字符串,例如我的类是Base那么&RTTI指向"Base"
当程序运行时,每一张虚函数表都会加载到内存的.rodata区,是个只读数据区,也叫常量区。

总结二:
一个类里面定义了虚函数,那么这个类定义的对象。其运行时,内存中开始部分,多存储一个vfptr虚函数指针,指向
类型的虚函数表vftable。一个类型定义的n个对象,它们的vfptr指向的都是同一张虚函数表。

总结三:
一个类里面虚函数的个数,不影响对象内存大小,都是多一个虚函数指针(vfptr    4个字节),影响的是虚函数表的大小

总结四:
如果中的方法和基类继承来的某个方法,返回值,函数名,参数列表都相同,而且基类的方法是virtual虚函数,那么派生类的这个方法,自动处理成虚函数。这两个函数的关系是覆盖的关系,相当于重写了这个函数

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

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

 6.继承和友元

友元关系不能继承 ,也就是说基类友元不能访问子类私有和保护成员
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);
}

7.继承与静态成员

基类定义了 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;
}

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

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

相关文章

车圈大厂9月利润率惨淡收尾,“金九”或变“铜九”

撰文&#xff5c;ANGELICA 编辑&#xff5c;ANGELICA 审核&#xff5c;烨 Lydia 声明&#xff5c;图片来源网络。日晞研究所原创文章&#xff0c;如需转载请留言申请开白。 国庆假期第一天&#xff0c;不少车企就迫不及待晒出假期战报。 按照以往的经验&#xff0c;每年9月…

蓝牙是如何诞生,如何工作,如何发展的?【无线通信小百科】

蓝牙为什么叫蓝牙&#xff1f;深入了解关于蓝牙的一切|无线通信小百科 在前两期文章&#xff0c;我们为大家介绍了无限通信技术是如何工作&#xff0c;如何发展&#xff1b;也为大家讲解了目前主流无线通信模块、SoC方案都有哪些。 无线通信工作原理、发展历程介绍https://blo…

Pytorch cuda版本选择(简洁高效版)

简而言之 Pytorch cuda版本选择 只需要低于cuda驱动版本即可&#xff0c;cuda版本查看是nvidia-smi, nvcc -V 是runtimeapi版本可以不用管 1.只要看cuda驱动版本 安装pytorch 选择cuda版本&#xff0c;只要看你电脑cuda驱动版本即可。 2.选择依据 pytorch中cuda版本只要不高于…

告别复杂协作:Adobe XD的简化替代方案

Adobe XD是一款集成UI/UX设计和原型创建功能的设计平台。它允许用户进行网页、移动应用的设计&#xff0c;以及原型的绘制&#xff0c;并且能够将静态设计转化为动态的交互原型。尽管Adobe XD提供了这些功能&#xff0c;但它依赖于第三方插件&#xff0c;且插件库有限&#xff…

【创新技术】探索Nuvoton NSP2.0:打造高品质语音提示新方案

你还在为寻找低成本高品质的语音播放芯片而困扰吗&#xff1f;面对市面上参差不齐的语音播放芯片&#xff0c;我们一直都在追求性价比&#xff0c;真正的性价比并不单独只是考虑价格&#xff0c;更加考虑播放音效的品质&#xff0c;芯片的抗干扰性能、外围电路的简易程度和开发…

【IC每日一题:CDC多bits跨时钟传输+handShake】

IC每日一题&#xff1a;CDC多bits跨时钟传输handShake 1 八股题&#xff1a;CDC多bits跨时钟传输1.1 慢到快&#xff1a;MUX enbale同步器法1.1.1 分析1.1.2 时序图1.1.3 代码 1.2 快到慢&#xff1a;握手协议1.2.1 原理分析1.2.2 时序图1.2.3 代码题 2 手撕题&#xff1a;使用…

如何将MySQL彻底卸载干净

目录 背景&#xff1a; MySQL的卸载 步骤1&#xff1a;停止MySQL服务 步骤2&#xff1a;软件的卸载 步骤3&#xff1a;残余文件的清理 步骤4&#xff1a;清理注册表 步骤五:删除环境变量配置 总结&#xff1a; 背景&#xff1a; MySQL卸载不彻底往往会导致重新安装失败…

完美解决 vscode 多设备通过ssh登录远程服务器免密登录

文章目录 问题解决分支一&#xff1a;只需一个设备连服务器分支二&#xff1a;需要多台设备连服务器&#xff08;即分支一成立的情况下&#xff0c;即已经有一个设备连接服务器了&#xff09;确保权限正确注意 问题 麻烦&#xff0c;每次打开vscode都要输入密码才能连接服务器&…

mac|安装redis及RedisDesk可视化软件

一、安装 通过Homebrew安装 brew install redis 在安装过程可以得到以下信息&#xff1a; 1、启动redis或重新登陆redis brew services start redis 如果只想在前端运行&#xff0c;而不是在后端&#xff0c;则使用以下命令 /opt/homebrew/opt/redis/bin/redis-server /opt…

大学适合学C语言还是Python?

在大学学习编程时&#xff0c;选择C语言还是Python&#xff0c;这主要取决于你的学习目标、专业需求以及个人兴趣。以下是对两种语言的详细比较&#xff0c;帮助你做出更明智的选择&#xff1a; C语言 优点&#xff1a; 底层编程&#xff1a;C语言是一种底层编程语言&#x…

3大关键点教你用Java和Spring Boot快速构建微服务架构:从零开发到高效服务注册与发现的逆袭之路

你好&#xff0c;我是忆~遂愿&#xff0c;全网3w粉丝&#xff0c;《遂愿盈创》社群主理人。 副业启航① | 遂愿盈创&#xff08;对副业感兴趣免费可入&#xff0c;多种赚钱实战项目等你来&#xff0c;一起探寻副业快速变现的途径&#xff1b;以及对接互联网大厂商务合作&#x…

ubuntu下使用pocketsphinx进行语音识别

文章目录 前言一、pocketsphinx的介绍二、ubuntu下编译三、使用示例1.模型选择2.代码示例3.自定义字典 四、交叉编译总结 前言 由于工作需要语音识别的功能&#xff0c;环境是在linux arm版上&#xff0c;所以想先在ubuntu上跑起来看一看&#xff0c;就找了一下语音识别的开源…

Redis的持久化以及性能管理

目录 一、Redis持久化概述 1.什么是Redis持久化 2.持久化方式 3.RDB持久化 3.1概念 3.2触发条件 3.3执行流程 3.4启动时加载 4. AOF持久化 4.1概念 4.2启动AOF 4.3执行流程 4.4启动时加载 5.RDB和AOF的优缺点 二、Redis性能管理 1.查看Redis内存使用 2…

科研绘图系列:R语言组合连线图和箱线图(linechart+boxplot)

文章目录 介绍加载R包数据数据预处理画图1画图2系统信息介绍 连线图(Line Chart)是一种常用的数据可视化图表,它通过将一系列数据点用直线段连接起来来展示数据随时间或有序类别变化的趋势。以下是连线图可以表示的一些内容: 时间序列数据:展示数据随时间变化的趋势,例如…

Webserver(3.1)线程

目录 创建线程终止线程连接已终止的进程二级指针 线程分离线程取消线程属性线程同步多线程卖票 创建线程 编译时需要加-pthread gcc pthread_create.c -o create -pthread#include<pthread.h> #include<stdio.h> #include<string.h> #include<unistd.h&…

WPS实现手机电脑同步文件

复制链接&#xff0c;发送微信&#xff0c;即可在手机端打开同步编辑文档。 当手机编辑文档后&#xff0c;会有如下提示: 根据提示&#xff0c;可选择文件更新版本。

ES海量数据插入如何优化性能?

2024年10月NJSD技术盛典暨第十届NJSD软件开发者大会、第八届IAS互联网架构大会在南京召开。百度文心快码总经理臧志分享了《AI原生研发新范式的实践与思考》&#xff0c;探讨了大模型赋能下的研发变革及如何在公司和行业中落地&#xff0c;AI原生研发新范式的内涵和推动经验。 …

【linux】HTTPS 协议原理

1. 了解 HTTPS 协议原理 &#xff08;一&#xff09;认识 HTTPS HTTPS 也是一种应用层协议&#xff0c;是在 HTTP 协议的基础上引入了一个加密层 因为 HTTP协议的内容都是按照文本的方式进行传输的&#xff0c;这个过程中&#xff0c;可能会出现一些篡改的情况 &#xff08;…

labview学习总结

labview学习总结 安装labview的特点一、图形化编程范式二、并行执行机制三、硬件集成能力四、应用领域优势五、开发效率六、系统集成能力**labview基本组成示意图****常用程序结构图解**结语 基础知识介绍界面前后面板的概念平铺式和层叠式 帧的概念结构类型顺序结构for循环whi…

PostgreSQL技术内幕17:PG分区表

文章目录 0.简介1.概念介绍2.分区表技术产生的背景3.分区类型及使用方式4.实现原理4.1 分区表创建4.2 分区表查询4.3 分区表写入4.4 分区表删除 0.简介 本文主要介绍PG中分区表的概念&#xff0c;产生分区表技术的原因&#xff0c;使用方式和其内部实现原理&#xff0c;旨在能…