C++知识第三篇之继承

C++的继承是面向对象编程的基础,允许类之间复用和扩展。文章介绍了继承的定义、作用域、赋值转换、派生类的默认函数、友元和静态成员。此外,详细讨论了单继承、多继承、菱形继承及其问题,以及如何使用虚拟继承解决数据冗余和二义性。
摘要由CSDN通过智能技术生成

C++继承

继承是面向对象编程的重要特征,是对类设计层次的复用

一.介绍

1.继承定义

class Parent
{};

class Child :public Parent
{};

Parent:是父类,也称作基类(base class);Student:是子类,也称作派生类(derived class)

public:表示继承方式为公共继承

2.继承方式

子类继承父类,有3种继承方式

继承方式的作用是:

对于父类非private的成员,使其在子类中的访问权限为MIN(继承方式,父类成员访问限制符)

如:
在这里插入图片描述
private成员pc在子类中是不可见的,不能在子类中被访问。

  • public继承

    最常用的继承方式,不改变父类成员在子类的访问权限

  • protected继承

    可将父类的public成员,在子类中为protected

  • private继承

    可将父类的public、protected成员,在子类中为private

这个时候可以让我们回顾一下,类中访问限制符的作用

  1. public(公有):其修饰的成员可以在类外直接访问
  2. protected(保护)与private(私有):其修饰的成员不能在类外直接访问

此时就可以发现protected与private的异同了:

如果成员不想在类外被直接访问,则可以用protected或private修饰。但如果需要在子类中被访问,则需要设置为protected。因为private修饰的成员在子类中是不可见(虽然被子类继承了,但是子类不能访问)。

3.class与struct

  • 用class作为类声明的关键字

如果派生类是使用class关键字,则默认继承(不显示表明)方式为private
在这里插入图片描述

  • 用struct作为类声明的关键字

如果派生类是使用struct关键字,则默认继承(不显示表明)方式为private
在这里插入图片描述

无论使用那种,最好显示的写出继承方式

二.作用域

不同类都有其自己的类域,因此基类和派生类都有独立的作用域

例如,上例中的Children类,其继承了Parent类,并继承得到Parent类中的成员,但是这些成员却不在Children的作用域里。

这很好理解,毕竟有两个{},基类成员都在基类的类体中声明(定义)。其次,我们可以在派生类中,声明(定义)与基类同名的成员。(要知道在相同作用域中,定义同名的变量是会引起命名冲突的)


如果派生类和基类中有同名成员,派生类成员将屏蔽对基类同名成员的直接访问,即会优先访问派生类的成员,也称隐藏重定义

此时访问基类成员需要显示指定基类的作用域

1.成员变量

在这里插入图片描述

c.pa:访问的是Children中的成员pa c.Parent::pa:访问到Parent中的成员pa

2.成员函数

需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
在这里插入图片描述


在这里插入图片描述

Parent中的show()和Children中的show(),有不同的参数列表,但是并不构成函数重载,因为函数重载的条件是需要在相同作用域中。这两个函数构成隐藏关系

tips:实际中最好不要在继承体系里定义同名成员


三.赋值转换

public继承方式下

派生类的对象可以赋值给基类的对象、基类的指针、基类的引用

这种赋值操作又被叫做切片或者切割,比喻将派生类对象中基类的那部分切给基类进行赋值。

但是基类对象不能赋值给派生类对象

1.给基类对象赋值

在这里插入图片描述

将对象c中Parent部分切片赋值给对象p

2.给基类对象指针赋值

在这里插入图片描述

pp指向对象c中Parent部分的切片

引用:底层也是指针。

四.派生类的默认函数

对于一个空类,经由编译器处理过后,会为它声明一个默认构造函数、一个拷贝构造函数、一个赋值运算符重载、一个析构函数,且这些函数都是public的。称之为默认成员函数。

默认构造函数:

    会先(在初始化列表的位置)调用基类的默认构造函数,完成基类的创建。

构造函数:

    如果基类没有默认构造函数,则需要在初始化列表显示调用基类的构造函数

拷贝构造函数:

    如果基类和派生类的拷贝构造函数都是编译器生成的,会先(在初始化列表的位置)调用基类的拷贝构造函数。

    如果基类显示定义了,则需要在派生类的拷贝构造函数中显示调用,否则不会完成对基类部分的值拷贝(如果不是在参数列表位置调用的,则会先自动调用基类的默认构造函数,如果基类无默认构造,则会报错)

析构函数:

    析构函数基本上都是当对象的生命周期结束后,由编译器自动调用的。在继承体系中,当一个派生类对象需要释放,会先调用派生类的析构函数,再调用其基类的析构函数。

在这里插入图片描述

五. 其他

1.友元

友元的目的是打破封装,使protected或private的类成员也可以被类外访问

友元关系不能被继承
在这里插入图片描述

友元函数show()并非Parent类的成员函数,只是通过friend关键字同类外产生联系

友元类同理

2.静态

对于基类的静态成员,无论其派生类有多少个,都共用着同一个静态成员
在这里插入图片描述

静态函数同理

六.继承

1.单继承

一个派生类只有一个直接基类的关系称为单继承

class Grandparent
{};
class Parent :public Grandparent
{};
class Children :public Parent
{};

Greadparent–>Parent–>Children,单继承

2.多继承

一个派生类有两个或以上直接基类的关系称为多继承

class Father
{};
class Mother
{};
class Children :public Father, public Mother
{};

Children<—Father & Mother,多继承

对基类的初始化顺序,是根据继承的先后顺序
在这里插入图片描述

Children类的默认构造函数中,在其初始列表位置,即使先调用Mother(),后Father(),结果依旧是按继承的顺序先构造Father,后Mother。(初始化列表的错误使用示例,最好按照声明的次序条列)


多继承有可能会出现二义性的问题
在这里插入图片描述

3.菱形继承

菱形继承是多继承的一种特例

class Grandma
{};
class Father :public Grandma
{};
class Mother :public Grandma
{};
class Children :public Father, public Mother
{};

Father是继承于其Grandma,Mother是继承于其Grandma,

当Children继承Father和Mother后,就间接的继承了两个Grandma对象
在这里插入图片描述

二义性问题
在这里插入图片描述

数据冗余问题

对于派生类如果只是需要一份基类的成员即可,那么菱形继承也会造成数据冗余。例如上例的Children类间接的继承了两份Grandma。
在这里插入图片描述

4.虚拟继承

意义:为解决由菱形继承而导致的数据冗余和二义性问题


示例:

class Grandma
{
public:
    int i;
};
class Father :virtual public Grandma
{};
class Mother :virtual public Grandma
{};
class Children :public Father, public Mother
{};

让Father和Mother,虚拟(virtual)继承Grandma

此时会产生什么变化呢?
在这里插入图片描述

可以看见当Father、Mother虚拟继承后,其首位多出来4个字节的空间,存放着某种数据

在这里插入图片描述

首位置是一个指针,指针内存放着地址,该地址指向一张表。指针是虚基表指针,表是虚基表

虚基表中存放的是偏移量,通过第二个偏移量可以找到基类成员变量的地址

可以看出Father、Mother在虚拟(virtual)继承后,就隐式的增加了一个虚基表指针的成员(属于派生类Father、Mother),且所占空间额外增加了4个字节。


下面来看Children类的变化
在这里插入图片描述

由于Father、Mother都是虚拟继承于Grandma,其各自的虚基表中第二个偏移量都是指向来着于Grandma的成员变量。因此在Children类c中唯有一份Grandma成员变量

解决了数据冗余和二义性问题


在实际中建议避免定义出菱形继承。

七.其他

1.如何定义一个不能被继承的类?

  • 将构造函数私有化

    通过无法实例化来间接使A类无法被继承
    在这里插入图片描述

     因此需要提供一个接口GetA,来返回A类型对象

  • 将析构函数私有化

    通过无法实例化来间接使A类无法被继承
    在这里插入图片描述

  • final

    c++11,不同于前两种,使用final来修饰类后,如果有继承操作就会报错
    在这里插入图片描述

2. 菱形继承中如果合理使用虚拟继承?

在下图的菱形继承中,只对BC类使用虚拟继承,就可以解决数据冗余二义性问题
在这里插入图片描述

如果在菱形继承中,每个继承关系都使用虚拟继承,也可以解决问题,但是没有必要,而且还会使空间增大(虚基表指针),并且性能和复杂度都有问题。

🦀🦀观看~~

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值