C++:继承

一、继承的概念

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

下面是一个继承简单的例子

class Person
{
public:
	string name;
	int age;
};
class student :public Person
{
public:
	int stunum;//学号
};

这里首先创建了一个Person类,再以public方式(public是一种继承方式,除此之外,还有protected、private继承方式,之后再讲)继承Person类,创建出了student派生类。

因为student类继承了Person类,所以student类实例化出来的对象都有name和age成员变量

int main()
{
	student st1;
	st1.name = "zhangsan";
	st1.age = 18;
	st1.age = 2004;
	return 0;
}

 不只成员变量能够被继承,成员函数同样也能够被继承

class Person
{
public:
	void PrintName()//新增打印姓名的函数
	{
		cout << name << endl;
	}
	string name;
	int age;
};
class student :public Person
{
public:
	int stunum;//学号
};
int main()
{
	student st1;
	st1.name = "zhangsan";
	st1.age = 18;
	st1.age = 2004;
	st1.PrintName();//打印名字
	return 0;
}

运行结果

二、继承方式

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


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


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


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


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

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

  1. 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
  2. 基类对象不能赋值给派生类对象。
  3. 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。

下面给出示例代码

int main()
{
	student sobj;
	//子类对象可以赋值给父类对象/指针/引用
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;

	//基类对象不能赋值给派生类对象,这么写会报错
	sobj = pobj;

	//基类的指针可以通过强制类型转换赋值给派生类
	pp = &sobj;
	student* ps1 = (student*)pp;
	
	//这种情况可能会越界
	pp = &pobj;
	student* ps2 = (student*)pp;

	return 0;
}

 四、继承中的作用域

1. 在继承体系中基类和派生类都有独立的作用域。


2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,
    也叫重定义。(在子类成员函数中,
可以使用 基类::基类成员 显示访问


3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。


4. 注意在实际中在继承体系里面最好不要定义同名的成员。

class Person
{
public:
	void PrintName()
	{
		cout << name << endl;
	}
	string name;
	int age;
};
class student :public Person
{
public:
	void PrintName()
	{
		cout << name << name << endl;//打印两次
	}
	int stunum;//学号
};
int main()
{
	student st1;
	st1.name = "zhangsan";
	st1.PrintName();
	return 0;
}

运行结果

 下面是容易混淆的情况

class Person
{
public:
	void PrintName()//无参
	{
		cout << name << endl;
	}
	string name;
	int age;
};
class student :public Person
{
public:
	void PrintName(int)//需要传入一个整形参数
	{
		cout << name << name << endl;
	}
	int stunum;//学号
};
int main()
{
	student st1;
	st1.name = "zhangsan";
	st1.PrintName(1);//这里必须传参,不传参就报错
	return 0;
}

student中的PrintName和Person中的PrintName不构成函数重载,即便函数名相同参数不同,因为其不在同一作用域,此处构成隐藏(重定义),只要函数名相同就构成隐藏

五、派生类的默认成员函数 

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


1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。


2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。


3. 派生类的operator=必须要调用基类的operator=完成基类的复制。


4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
保证派生类对象先清理派生类成员再清理基类成员的顺序。


5. 派生类对象初始化先调用基类构造再调派生类构造。


6. 派生类对象析构清理先调用派生类析构再调基类的析构。


7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲
解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加
virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

六、继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

class Person
{
private :
	friend void func(Person& p,student& s)//增加了一个函数,这个函数访问了子类的私有成员
	{
		cout << p.age <<s.stunum<< endl;
	}
public:
	void PrintName()
	{
		cout << name << endl;
	}
	string name;
	int age;
};
class student :public Person
{
public:
	void PrintName(int)
	{
		cout << name << name << endl;
	}
	int stunum;//学号
};
int main()
{
	student st;
	st.age = 18;
	func(st,st);
	return 0;
}

这里会报错

七、菱形继承 

菱形继承是多继承的一种特殊情况

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。
在Assistant的对象中Person成员会有两份。 

class base0
{
	int a;
};
class base1 :public base0
{
	int b;
};
class base2 :public base0
{
	int c;
};
class derive :public base1, public base2
{
	int d;
};
int main()
{
	cout << sizeof(derive) << endl;
	return 0;
}

运行结果

很明显,derive中有两份a成员变量

int main()
{
	derive D;
	D.base1::a = 1;
	D.base2::a = 2;
	D.b = 3;
	D.c = 4;
	D.d = 5;
	cout << sizeof(derive) << endl;
	return 0;
}

进行调试,观察内存

 虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在base1和
base2继承base0时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用。虚拟继承专门针对菱形继承

class base0
{
public:
	int a;
};
class base1 :virtual public base0
{
public:
	int b;
};
class base2 :virtual public base0
{
public:
	int c;
};
class derive :public base1,public base2
{
public:
	int d;
};
int main()
{
	derive D;
	D.a = 1;
	D.b = 3;
	D.c = 4;
	D.d = 5;
	cout << sizeof(derive) << endl;
	return 0;
}

 虚基表存放的是偏移量,即公共成员变量在子类对象中的相对位置,即此处1的位置,我们搜索虚基表地址继续查看内存

很清楚的看到虚基表地址处存放0,后四个字节存放偏移量,这里第一个虚基表存放的是十六进制的14,即十进制的20,第一个虚基表指针往后二十个字节刚好是1的位置。第二个虚基表存放的是0c,即十进制的12 ,第二个虚基表指针往后12个字节也是1的位置

八、继承的总结反思

1. 很多人说C++语法复杂,其实多继承就是一个体现。有了多继承,就存在菱形继承,有了菱
形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设
计出菱形继承。否则在复杂度及性能上都有问题。


2. 多继承可以认为是C++的缺陷之一,很多后来的OO语言都没有多继承,如Java。


3. 继承和组合
 
// 有童鞋会有疑问为什么D中B和C部分要去找属于自己的A?那么大家看看当下面的赋值发生时,d是不是要去找出B/C成员中的A才能赋值过去?

 

D d;
B b = d;
C c = d;
  • public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
  • 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
  • 优先使用对象组合,而不是类继承 。
  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
  • 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
  • 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

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

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

相关文章

网络异常检测

随着社交网络、视频流、点对点技术、云计算和 SaaS 的出现&#xff0c;可以肯定地说&#xff0c;现代企业的好坏取决于他们的网络&#xff0c;尤其是在它们提供的带宽和安全性方面。无论是银行保护其数据免遭盗窃&#xff0c;还是商业组织保护其网络免受安全威胁和攻击&#xf…

Linux 中 .tar 和 tar.gz 的区别

1、前言 有时候你会发现&#xff0c;即便是有些拥有 3 年左右工作经验的运维或开发工程师对 .tar 和 .tar.gz 的区别并不是很清楚。.tar 和 .tar.gz 是在 Linux 系统中用于打包和压缩文件的两种常见格式。它们之间的主要区别在于压缩算法和文件扩展名。 2、区别 .tar .tar 是…

python练习题(markdown中的60道题)

1.Demo01 摄氏温度转化为华氏温度 celsius float(input(输入摄氏温度&#xff1a;)) fahrenheit (9/5)*celsius 32 print(%0.1f 摄氏温度转为华氏温度为 %0.1f % (celsius, fahrenheit))结果&#xff1a; 2.Demo02 计算圆柱体的体积 h, r map(float, input().split())# …

581. 最短无序连续子数组

581. 最短无序连续子数组 题目&#xff1a; 给你一个整数数组 nums &#xff0c;你需要找出一个 连续子数组 &#xff0c;如果对这个子数组进行升序排序&#xff0c;那么整个数组都会变为升序排序。 请你找出符合题意的 最短 子数组&#xff0c;并输出它的长度。 示例&…

【Android】声浪 UI 效果并附上详细代码

声浪效果是基于第三方实现的。 https://github.com/xfans/VoiceWaveView 将三方的 Kotlin 代码转 java 使用&#xff08;按照他的readme 进行依赖&#xff0c;好像少了点东西&#xff0c;至少本项目跑不起来&#xff09; 声浪效果在android 8 以上都是比较好的&#xff0c;不会…

【JavaEE】操作系统与进程

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…

Oracle的控制文件多路复用,控制文件备份,控制文件手工恢复

一.配置控制文件多路复用 1.查询Oracle的控制文件所在位置 SQL> select name from v$controlfile;NAME -------------------------------------------------------------------------------- /u01/app/oracle/oradata/orcl/control01.ctl /u01/app/oracle/fast_recovery_a…

规划类3d全景线上云展馆帮助企业轻松拓展海外市场

科技3D线上云展馆作为一种基于VR虚拟现实和互联网技术的新一代展览平台。可以在线上虚拟空间中模拟真实的展馆&#xff0c;让观众无需亲自到场&#xff0c;即可获得沉浸式的参观体验。通过这个展馆&#xff0c;您可以充分、全面、立体展示您的产品、服务以及各种创意作品&#…

实用篇 | T-SNE可视化工具详情及代码示例

本文主要是为了快速的了解t-sne和如何快速使用&#xff01; 简要了解TSNE TSNE&#xff0c;降维方法之一。降维在机器学习中非常重要。这是因为如果使用高维数据创建模型&#xff0c;则很容易欠拟合。换句话说&#xff0c;有太多无用的数据需要学习。可以通过从各种数据中仅…

Golang版本处理Skywalking Trace上报数据

Tips: 中间记录了解决问题的过程&#xff0c;如不感兴趣可直接跳至结尾 首先去es里查询skywalking trace的元数据 可以拿到一串base64加密后的data_binary(直接解密不能用&#xff0c;会有乱码&#xff0c;可参考https://github.com/apache/skywalking/issues/7423) 对data_b…

第四代智能井盖传感器:智能井盖位移怎么进行监测

井盖是城市基础设施的一个重要组成部分&#xff0c;若井盖出现移位等现象&#xff0c;可能会对路过的车辆和行人造成潜在危险。特别是那些含有甲烷气体的井盖&#xff0c;一旦气体超过阈值且被意外踩踏&#xff0c;可能会导致气体的释放&#xff0c;这便会引发一系列安全事故&a…

Linux wait函数用法

wait 函数是用于等待子进程结束并获取子进程的终止状态的系统调用。它在父进程中使用&#xff0c;用于等待其子进程终止并获得子进程的退出状态。 函数原型&#xff1a; pid_t wait(int *status);status 是一个指向整型的指针&#xff0c;用于存储子进程终止时的退出状态&…

clang+llvm多进程gdb调试

clangllvm多进程gdb调试 前言1. 命令行gdb2. 父进程调试3. 子进程调试4. 返回父进程 前言 在学习新增llvm的优化pass时&#xff0c;需要跟踪clang及llvm的调用栈。然而llvm通过posix_spawn()创建了新进程&#xff0c;这使得gdb调试必须有一定的技巧了。 1. 命令行gdb 以下命…

小红书干货类笔记怎么写?建议收藏

小红书干货类笔记是指在小红书这个社交平台上&#xff0c;用户分享的各种实用、有价值的生活技巧、经验、心得等内容的笔记。这类笔记通常具有以下特点&#xff1a;内容详实、实用性强、独特见解、图文并茂。 比如&#xff1a;某个妆要怎么化、某种技能该怎么学、某个城市该怎…

详细梳理山姆·奥特曼离职闹剧 仍试图重返OpenAI

大家好,我是极智视界,欢迎关注我的公众号,获取我的更多前沿科技分享 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码和资源下载,链接:https://t.zsxq.com/0aiNxERDq OpenAI 与微软围绕 Sam Altman 的离职风波,这场肥皂剧似乎还没到终结的样子。目前 …

跨越行业边界,CodeMeter护航AI领域安全与合规

在人工智能&#xff08;AI&#xff09;技术如ChatGPT的推动下&#xff0c;工业视觉、医疗诊断和智能驾驶等领域正在经历重大变革。这些技术不仅扩大了应用范围&#xff0c;也带来了数据安全、软件授权保护和合规性等新挑战。 AI工业视觉正在推动制造和自动化的快速发展&#x…

YOLOv5结合华为诺亚VanillaNet Block模块

🗝️YOLOv5实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局部注意力、增加检测头部,实测涨点 💡 深入浅出YOLOv5:我的专业笔记与技术总结   -- YOLOv5轻松上手, 适用技术小白,文章代码齐全,仅需 …

出海企业首选的免费开源财务管理系统解决方案

计费与订阅管理 Odoo计费与订阅管理解决方案可帮助您同步从订单、计费到收入确认的复杂流程 Odoo Subscriptions将计费与订阅置于核心业务流程中&#xff0c;将其从普通的后端功能转化为具有决定性意义的战略性业务工具。Odoo统一计费框架支持根据事务、订阅、使用量计费以及…

大数据题目的解题技巧

目录 大数据题目的技巧总括 实例精析 实例一 实例二 实例三 大数据题目的技巧总括 &#xff08;1&#xff09;哈希函数可以把数据按照种类均匀分流&#xff1b; &#xff08;2&#xff09;布隆过滤器用于集合的建立与查询&#xff0c;并可以节省大量空间&#xff1b; &…

前缀和的动态维护——树状数组[C/C++]

文章目录 前言lowbitlowbit的定义lowbit的计算 树状数组的思想树状数组的操作单点修改 update前缀查询 query树状数组的建立 build 前言 树状数组巧妙了利用位运算和树形结构实现了允许单点修改的情况下&#xff0c;动态维护前缀和&#xff0c;并且实现单点修改和前缀和查询的效…