C++知识第三篇之继承

C++继承

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

文章目录

  • C++继承
    • 一.介绍
      • 1.继承定义
      • 2.继承方式
      • 3.class与struct
    • 二.作用域
      • 1.成员变量
      • 2.成员函数
    • 三.赋值转换
      • 1.给基类对象赋值
      • 2.给基类对象指针赋值
    • 四.派生类的默认函数
    • 五. 其他
      • 1.友元
      • 2.静态
    • 六.继承
      • 1.单继承
      • 2.多继承
      • 3.菱形继承
      • 4.虚拟继承
    • 七.其他
      • 1.如何定义一个不能被继承的类?
      • 2. 菱形继承中如果合理使用虚拟继承?

一.介绍

1.继承定义

class Parent
{};

class Child :public Parent
{};

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

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

2.继承方式

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

继承方式的作用是:

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

如:
C++知识第三篇之继承_第1张图片
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
C++知识第三篇之继承_第2张图片

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

如果派生类是使用struct关键字,则默认继承(不显示表明)方式为private
C++知识第三篇之继承_第3张图片

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

二.作用域

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

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

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


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

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

1.成员变量

C++知识第三篇之继承_第4张图片

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

2.成员函数

需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
C++知识第三篇之继承_第5张图片


C++知识第三篇之继承_第6张图片

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

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


三.赋值转换

public继承方式下

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

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

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

1.给基类对象赋值

C++知识第三篇之继承_第7张图片

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

2.给基类对象指针赋值

C++知识第三篇之继承_第8张图片

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

引用:底层也是指针。

四.派生类的默认函数

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

默认构造函数:

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

构造函数:

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

拷贝构造函数:

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

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

析构函数:

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

C++知识第三篇之继承_第9张图片

五. 其他

1.友元

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

友元关系不能被继承
C++知识第三篇之继承_第10张图片

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

友元类同理

2.静态

对于基类的静态成员,无论其派生类有多少个,都共用着同一个静态成员
C++知识第三篇之继承_第11张图片

静态函数同理

六.继承

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,多继承

对基类的初始化顺序,是根据继承的先后顺序
C++知识第三篇之继承_第12张图片

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


多继承有可能会出现二义性的问题
C++知识第三篇之继承_第13张图片

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对象
C++知识第三篇之继承_第14张图片

二义性问题
C++知识第三篇之继承_第15张图片

数据冗余问题

对于派生类如果只是需要一份基类的成员即可,那么菱形继承也会造成数据冗余。例如上例的Children类间接的继承了两份Grandma。
C++知识第三篇之继承_第16张图片

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

此时会产生什么变化呢?
C++知识第三篇之继承_第17张图片

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

C++知识第三篇之继承_第18张图片

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

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

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


下面来看Children类的变化
C++知识第三篇之继承_第19张图片

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

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


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

七.其他

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

  • 将构造函数私有化

    通过无法实例化来间接使A类无法被继承
    C++知识第三篇之继承_第20张图片

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

  • 将析构函数私有化

    通过无法实例化来间接使A类无法被继承
    C++知识第三篇之继承_第21张图片

  • final

    c++11,不同于前两种,使用final来修饰类后,如果有继承操作就会报错
    C++知识第三篇之继承_第22张图片

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

在下图的菱形继承中,只对BC类使用虚拟继承,就可以解决数据冗余二义性问题
C++知识第三篇之继承_第23张图片

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

观看~~

你可能感兴趣的:(一块来学C++,c++,开发语言)