继承的详解

目录

1. 继承的概念和定义

1.1 继承的概念

1.2 继承的定义

2. 基类(父类)和派生类对象(子类)的赋值转换

3. 继承中的作用域

3.1 同名的成员变量

3.2 同名的成员函数

4. 派生类的默认构造函数

5. 继承和友元

6. 继承和静态成员

7. 菱形继承

1. 继承的概念和定义

1.1 继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保
持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类,而原有类被称为基类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次上的复用。

1.2 继承的定义

1.2.1 继承的定义格式

 继承的详解_第1张图片

1.2.2 继承关系和访问限定符
继承方式和访问限定符又分为三种:

  • public
  • protected
  • private

1.2.3 继承基类成员访问方式的变化

由于继承关系和访问限定符的各有三种类型,继承的成员访问方式的结果就有九种:

类成员 public继承 protected继承 private继承
基类的public成员 派生类的public成员 派生类的protected成员 派生类private成员
基类的protected成员 派生类的protected成员 派生类的protected成员 派生类private成员
基类的private成员 在派生类中不可见 在派生类中不可见 在派生类中不   可见

总结:

1. 基类中的private修饰的成员,在派生类中是不可见的,但这不代表它没有继承在派生类中,只不过是在类外和类内都不可以访问。

继承的详解_第2张图片

 2. 由于基类中被private修饰过的成员是无法在派生类内访问的,如果想要在派生类中可以访问,但在类外又不被访问,则就可以使用protected修饰基类中的成员。

继承的详解_第3张图片

 3. 可以观察,除了private的访问权限变为不可见以后,其余的基类成员的访问权限均变成了与原本访问权限和继承方式权限的较小值

4.class的默认继承方式是private继承,struct的默认继承方式是public继承

 2. 基类(父类)和派生类对象(子类)的赋值转换

  • 派生类的对象可以赋值基类的对象、指针、引用,有一种说法将这种赋值转换比喻成切片,寓意将从父类中继承的那一部分切出来赋值
  • 派生类可以赋值基类,基类不能赋值派生类

继承的详解_第4张图片

 而将基类赋值给派生类是不被允许的

继承的详解_第5张图片

 

3. 继承中的作用域

  1. 在基类和派生类中都存在独立的作用域
  2. 基类和派生类有同名成员时,派生类成员会自动屏蔽基类中的同名成员,这种情况被称为隐藏
  3. 如果是同名的成员函数,则会将基类中同名的函数都给隐藏,包括重载后的函数
  4. 在实际情况中尽量避免使用同名的成员:

3.1 同名的成员变量

这种情况就属于隐藏掉了Base中的a成员 

继承的详解_第6张图片

那么如何访问Base中的a成员呢?

继承的详解_第7张图片

 我们可以通过作用域来让编译器知道要进行访问的是哪个同名成员

3.2 同名的成员函数

继承的详解_第8张图片

 当派生类和基类存在同名函数时,会隐藏基类中同名的所有函数。

 继承的详解_第9张图片

 4. 派生类的默认构造函数

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
   的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用,相当于基类是派生类的成员

继承的详解_第10张图片

如果没有基类没有构造函数,在初始化列表中可以类比初始化自身成员一样来初始化基类
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化,这里就涉及到了派生类对基类的切片赋值

继承的详解_第11张图片 

当进行赋值时,因为除了赋值派生类本身的成员还需要赋值基类的成员,因此需要调用基类的赋值
3. 派生类的operator=必须要调用基类的operator=完成基类的赋值,注意操作符重载的隐藏

继承的详解_第12张图片
4. 派生类对象初始化先调用基类构造再调派生类构造,派生类对象析构清理先调用派生类析构再调基类的析构,满足栈的特点

继承的详解_第13张图片

总是先调用基类的构造再调用派生类的构造,而释放时则是相反的。
5. 编译器会对析构函数名进行特殊处理,处理成destrutor(),因此在不作处理的前提下,基类和派生类的析构函数会构成隐藏,因此无须显示调用基类的析构函数,编译器会自动调用。 

 继承的详解_第14张图片

 如果显示调用基类的析构函数,编译器无法认识该函数,因为已经被派生类的析构函数完成了因此,如果要调用被因此掉的函数需要添加作用域:

继承的详解_第15张图片

 但是当我们显示去调用基类的析构函数时,我们会发现析构函数被多调用了一次,这是因此为了保证派生类是先析构的,所以当派生类析构结束后会自动调用基类的析构函数,因此无须显示调用基类的析构函数。

 5. 继承和友元

友元关系是无法继承的,就像是你爸的朋友不一定是你的朋友。

继承的详解_第16张图片

 

6. 继承和静态成员

因为静态成员是不存于对象中的,因此当派生类继承基类的所有成员时,除了静态成员是独一份的,其余都是可以无限继承的。

继承的详解_第17张图片

 该图也很好的体现了,所有的对象都是继承了同一个静态成员变量count

7. 菱形继承

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

多继承:一个派生类直接继承了两个或以上的基类时这种关系称为多继承

那么什么是菱形继承呢?

菱形继承其实是属于多继承中的一种特殊情况

继承的详解_第18张图片

这里我们可以看到,我们的类中有两个string,而我们只需要一个,这就是菱形继承的问题:

具有冗余性和二义性

那我们应该如何解决这个问题呢?

我们只需要在继承同一个基类的基类上添加virtual关键字即可

继承的详解_第19张图片 

我们可以发现在student和teacher类对象当中已经不存在string成员了,而是转变成了vbptr的指针,该指针指向了一个名为vbtable的虚基表中,该表中存储的是指针的偏移量,而指针的当前位置加上偏移量刚好等于唯一一个string成员的地址,因此这就解决了冗余性和二义性,整个对象中也只存在一个string



 

你可能感兴趣的:(C++,c++)