问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

[C++进阶]继承详解

创作时间:
作者:
@小白创作中心

[C++进阶]继承详解

引用
CSDN
1.
https://blog.csdn.net/m0_74067712/article/details/137060125

在C++编程语言中,继承是面向对象编程的核心特性之一。通过继承,一个类(子类)可以继承另一个类(父类)的属性和方法,从而实现代码的复用和模块化。本文将详细介绍C++继承的概念、实现方式及其相关特性,帮助读者全面理解这一重要机制。

继承的概念

在编写程序的过程中,很多时候我们会碰到多个类中有相同成员的情况,这种时候我们想要将其复用,这个过程就是继承。继承的作用:子类通过继承父类的属性和方法,可以对代码进行复用。
如下图,student类和teacher类有很多相同的属性。
所以,我们可以将这些相同的部分提取到一个类中。

继承的方式

继承的定义

继承的定义 :class +派生类:继承方式 +基类

class Person
{
    string _name;
    string _id;
    string _tel;
};
class Student :public Person
{
public:
    int _stuid;//学号
    int _major;//专业
};
class Teacher :public Person
{
public:
    string _salary;//工资
    char _jnum;//工号
 };

学生和老师都有相同的属性:名字,电话号码,年龄。
学生独有的:学号,专业。
老师独有的:工资,工号。
所以,我们将名字,电话号码,年龄提取出来定义为父类。而学生类和老师类里只声明他们各自独有的属性。
在定义子类的时候,我们可以发现继承方式和访问限定符相同。

子类的访问权限

既然继承方式有所不同,那么访问权限就有所不同。
将这一部分的内容总结:
1.父类的私有成员可以继承下来,但是不可见。
2.在继承体系中,父类的protected成员在子类中也是protected或保护成员。
3.实际中使用继承时一般都用public继承。
4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public。
补充:不可见指类里类外都不可使用。

继承中的作用域

既然子类可以继承父类的成员,那么他们的作用域是不是相同呢?我们首先给出继承中的作用域的结论。
1.父类和子类各自是独立的作用域。
2.子类中可以声明同名变量。
3.当子类和父类有同名变量时,进行访问时,默认访问子类。因为在子类和父类有同名成员是,子类中的同名成员会隐藏父类同名成员。
4.如果要想在子类中访问父类的同名成员,需要进行显示访问基类::基类成员。
5.在实际使用中,不建议使用同名成员。

同名变量

下面我们用代码来直观感受
我们看到有子类和基类有相同的成员_num,进行输出。得到如下结果。
我们发现,两个地方输出的_num都是2003, 因此当子类和父类有同名变量时,进行访问时,默认访问子类。
我们修改一下代码,如下。
我们在输出时,对基类中的_num进行了显示调用,得到如下结果。

同名函数

既然同名成员会有这样的访问变化,那么同名函数呢,上代码。
我们可以看到,在子类B和基类A中有同名函数func(),我们进行调用的结果是什么呢?

我们可以看到,当我们仅仅调用子类B中的func函数时,也是默认调用子类中的函数。
而要调用父类中的同名函数,也需要进行显示调用。
在这里很多同学会有疑问,同名函数不是会构成函数重载吗?
由于子类和父类的作用域是不同的,所以这里无法构成函数重载,而是形成隐藏。

子类中的构造函数

什么是默认构造函数?不需要编写,系统自己生成的函数。
子类中的默认构造函数有哪些特性呢?
我们先给出结论,并依次用代码进行讲解。
1.子类的构造函数必须显示调用父类的构造去初始化父类的那部分成员(拷贝构造也是)
2.子类的operator=中必须调用父类的operator=完成父类成员赋值
3.子类的析构函数不用显示调用父类的析构编译器会自动去调用
4.子类初始化对象时,先初始化父类的成员变量再初始化子类的成员变量
5.子类析构清理时先调用子类的析构函数再调用父类的析构函数(与构造反过来)

在进行讲解前,先解决一个疑惑,为什么在进行析构时,要先析构子类再析构父类?
因为在子类中若需要先访问父类中的成员,如果先析构了父类,那么父类被释放,访问会报错。
而父类不会访问子类所以在析构时,要遵从先析构子类,再析构父类。
上述代码中,在对子类进行列表初始化时,我们首先对父类进行了调用。输出结果如下。
我们去掉对基类构造函数的显式调用,代码如下。
我们发现,得到的结果仍然相同。

如果我们对子类的构造进行赋值,会发现同名变量会由子类来决定,也就是子类会覆盖父类的同名变量。如下。
通过以上分析我们可以知道,在继承中,子类中构造函数会自动调用父类中的构造函数。

并且对同名变量进行赋值时,子类会覆盖父类。

子类中的析构函数

在继承中,要先析构子类再析构父类。代码如下。
输出结果如下
此外,我们的编译器在子类析构结束后会自动调用对父类的析构。

继承与友元 静态变量

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

我们该如何理解这段话呢?基类相当你的父亲,友元相当于他的朋友,你是父亲的儿子,相当于子类,但是父亲的朋友不是你朋友。
继承和静态成员关系是:子类继承的是基类静态变量的使用权。
无论派生出多少个子类 都只有一个static成员实例

菱形继承和菱形虚拟继承

继承的方式有:单继承,多继承还有我们接下来要讲到的菱形继承。
什么是菱形继承呢,通俗来讲就是基类派生出的两个子类,这两个子类作为新的基类派生出同一个子类。
在使用菱形继承的时候会造成两个问题,一个是造成二义性,另一个是会造成数据的冗余。
我们首先来探讨二义性问题。上代码。如图。
可以看到对于name的调用是会出现报错的,我们来看看报错的原因是什么。
可以看到,编译器不知到我们要访问的是谁的_name。该如何解决呢,还是得用上我们的老朋友,显示调用。如图。
我们对Student类和Teacher类中的_name进行了显示调用,这时候便解决了二义性问题,让我们看看输出结果。
但是,这样并没有解决数据的冗余问题。
我们可以看到类D中会有两个a值。我们该如何解决这个问题呢:虚拟继承
我们将上述代码中加入virtual。如下。
可以看到,我们在类B和类C前加入了virtual,这样便可以解决数据冗余的问题。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号