【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.组合的耦合性比继承低,所以一般推荐使用组合。


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

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

😍😁谢谢你的阅读。😸😼

相关推荐
天之涯上上7 分钟前
JAVA开发 在 Spring Boot 中集成 Swagger
java·开发语言·spring boot
2402_857583498 分钟前
“协同过滤技术实战”:网上书城系统的设计与实现
java·开发语言·vue.js·科技·mfc
爱学习的白杨树15 分钟前
MyBatis的一级、二级缓存
java·开发语言·spring
OTWOL20 分钟前
两道数组有关的OJ练习题
c语言·开发语言·数据结构·c++·算法
问道飞鱼24 分钟前
【前端知识】强大的js动画组件anime.js
开发语言·前端·javascript·anime.js
拓端研究室24 分钟前
R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别
android·开发语言·kotlin
Code成立25 分钟前
《Java核心技术I》Swing的网格包布局
java·开发语言·swing
Auc2430 分钟前
使用scrapy框架爬取微博热搜榜
开发语言·python
QQ同步助手37 分钟前
C++ 指针进阶:动态内存与复杂应用
开发语言·c++
凯子坚持 c43 分钟前
仓颉编程语言深入教程:基础概念和数据类型
开发语言·华为