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


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

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

😍😁谢谢你的阅读。😸😼

相关推荐
唐僧洗头爱飘柔95279 分钟前
(Go语言)初上手Go?本篇文章帮拿捏Go的数据类型!
开发语言·golang·go语言·go数据类型·go开发·go初上手
唐僧洗头爱飘柔952716 分钟前
(Go基础)变量与常量?字面量与变量的较量!
开发语言·后端·golang·go·go语言初上手
Duck Bro19 分钟前
数据结构:顺序表(动态顺序表)
c语言·数据结构·c++·学习·算法
·云扬·19 分钟前
Lambda 表达式详解
java·开发语言·笔记·学习·1024程序员节
linhhanpy26 分钟前
自制操作系统(九、操作系统完整实现)
c语言·开发语言·汇编·c++·操作系统·自制操作系统
ACALJJ3227 分钟前
STL整理
开发语言·c++
豆本-豆豆奶29 分钟前
最全面的Python重点知识汇总,建议收藏!
开发语言·数据库·python·oracle
Bosenya1233 分钟前
【信号处理】绘制IQ信号时域图、星座图、功率谱
开发语言·python·信号处理
monkey_meng33 分钟前
【Rust Crate之Actix Web(一)】
开发语言·后端·rust
AI原吾1 小时前
探索PyAV:Python中的多媒体处理利器
开发语言·python·ai·pyav