【C++ 进阶】继承

一.继承的定义格式

基类又叫父类,派生类又叫子类;


二.继承方式

继承方式分为三种:

1.public继承

2.protected继承

3.private继承

基类成员与继承方式的关系共有9种,见下表:

虽然说是有9种,但其实最常用的还是红框里的,其它的很少用。

总结

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的;

不可见指:基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不 管在类里面还是类外面都不能去访问它;

2.如果想要在派生类访问基类中的成员变量,可以在基类中定义为 protected 成员;

3.基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式);

4.使用关键字class时默认的继承方式是private;

使用struct时默认的继承方式是public;


三.派生类和基类之间的赋值转换

1.派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用,这个过程又被形象的称为 切片/切割;

2.基类对象不能赋值给派生类对象;

3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的;


四.隐藏(重定义)

1.子类和父类中,只要函数名相同就构成隐藏;

2.成员名相同也构成隐藏;

3.在子类成员函数中,可以使用 基类::基类成员 显示访问隐藏的函数或成员;

4.最好不要定义同名的成员变量。

隐藏(重定义)与重载

重载:在同一作用域;

隐藏:在不同的作用域,一个在父类,一个在子类。


五.派生类中的默认成员函数

1.构造函数

派生类必须先自动调用基类的默认构造(初始化基类的那一部分成员),如果基类没有默 认构造,就要在派生类的初始化列表阶段显式调用基类的构造函数,然后派生类调用自己 的构造函数。

2.拷贝构造

派生类的拷贝构造必须先调用基类的拷贝构造完成基类的拷贝初始化。

3.赋值重载

派生类赋值重载必须先调用基类的赋值重载完成基类的复制。

4.析构函数

销毁对象时,会先调用派生类的析构函数,然后再自动调用基类的析构函数,这样就保证 了析构的顺序(即先子后父);

如果不是这个顺序,一个成员可能会析构两次,就会导致程序崩溃,比如以下的代码:

cpp 复制代码
class A
{
public:
	~A()
	{
		delete _a;
	}

	int* _a = new int;
};

class B :public A
{
public:
	~B()
	{
		delete _b;  
		A::~A();   //显式调用父类的析构函数,在调用完后,编译器又会自动调用一次父类的析构函数, 
                   //导致父类成员_a析构了两次,从而程序崩溃
	}

	int* _b = new int;
};

int main()
{
	A  a;
	B b;
	return 0;
}

六.继承与友元,静态成员

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

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子

类,都只有一个static成员实例 ,静态成员不属于任何一个具体的实例对象,而是属于类本身,子类可以继承父类的静态成员,而不需要重新定义。


七.多继承

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

下面就是一个多继承:

cpp 复制代码
class A
{};

class B
{};

class C:public A,public B   //多个父类之间用逗号隔开
{};

多继承的一个坑:菱形继承

上图就构成了一个菱形继承,它会导致两个问题:

1.数据冗余,因为会存两个Person类;

2.二义性

cpp 复制代码
class A
{
public:
	int _a;
};

class B :public A
{
public:
	int _b;
};

class C :public A
{
public:
	int _c;
};

class D :public B, public C
{
public:
	int _d;
};

int main()
{
	D d;
	d._a;   //二义性问题,不知道访问的是哪个_a;
    d.A::_a;   //显式访问,解决了二义性问题,但无法解决数据冗余问题

	return 0;
}

虚拟继承:解决菱形继承问题

在菱形继承的腰部加上 virtual 关键字构成虚拟继承;

如将上面代码class B 和 class C 改成:

cpp 复制代码
class B: virtual public A   //虚拟继承
{};

class C: virtual public A
{};

原理

通过观察菱形继承和虚拟继承的内存窗口我们发现:


八.总结和反思

1.public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

2.组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

例如:

cpp 复制代码
class A
{};

class B
{
    A a;   //组合
};

3.组合的耦合性比继承低,所以一般推荐使用组合。


🐬🤖本篇文章到此就结束了, 若有错误或是建议的话,欢迎小伙伴们指出;🕊️👻

😄😆希望小伙伴们能支持支持博主啊,你们的支持对我很重要哦;🥰🤩

😍😁谢谢你的阅读。😸😼

相关推荐
C#Thread15 分钟前
C#上位机--流程控制(IF语句)
开发语言·javascript·ecmascript
牵牛老人1 小时前
Qt开发中出现中文乱码问题深度解析与解决方案
开发语言·qt
大脑经常闹风暴@小猿1 小时前
1.1 go环境搭建及基本使用
开发语言·后端·golang
奔跑吧邓邓子1 小时前
【Python爬虫(45)】Python爬虫新境界:分布式与大数据框架的融合之旅
开发语言·分布式·爬虫·python·大数据框架
Evaporator Core1 小时前
MATLAB学习之旅:数据建模与仿真应用
开发语言·学习·matlab
Zfox_1 小时前
【QT】信号与槽 & 窗口坐标
开发语言·c++·qt·qt5
张鱼小丸子1 小时前
【无标题】云原生作业六
开发语言·php
项目申报小狂人2 小时前
改进收敛因子和比例权重的灰狼优化算法【期刊论文完美复现】(Matlab代码实现)
开发语言·算法·matlab
JD技术委员会3 小时前
Rust 语法噪音这么多,是否适合复杂项目?
开发语言·人工智能·rust
tekin3 小时前
Go、Java、Python、C/C++、PHP、Rust 语言全方位对比分析
java·c++·golang·编程语言对比·python 语言·php 语言·编程适用场景