C++继承与派生

205 阅读9分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天(掘金日新计划 | 12月更文挑战来袭,开启掘金成长之旅 - 掘金 (juejin.cn))

第七章 继承与派生

派生新类的过程一般包括:吸收已有类的成员、调整已有类成员和添加新的成员(实现代码的重用和扩充)

7.1 类的继承与派生

7.1.1 继承关系举例

1. 类的继承,是新的类从已有类那里得到已有的特性

2. 类的派生,从已有类产生新的类的过程

7.1.2 派生类的定义

1. 派生类的一般语法

2. 多继承:一个派生类同时含有多个基类单继承:一个基类直接基类间接基类

7.1.3 派生类生成过程

1. 吸收基类成员(除去基类的构造函数和析构函数)

2. 改造基类成员

1. 基类成员的访问控制问题

2. 对基类数据成员或函数成员的覆盖或隐藏

3. 添加新的成员(构造函数和析构函数)

7.2 访问控制(默认私有继承)

注意:对派生类对象的可见性和在派生类中的可见性

7.2.1 公有继承

7.2.2 私有继承

1. 如果进一步派生的话,基类的全部成员就无法在新的派生类中被直接访问

2. 为了保证基类的一部分外部接口能在派生类中存在,就必须在派生类中重新声明同名的成员

7.2.3 保护继承********

  1. 和私有继承的区别

  2. 当派生类作为新的基类时,二者的区别得以显现

7.3 类型兼容规则(多态性的重要基础之一)****

  1. 类型兼容:在需要基类对象的任何地方,都可以使用公有派生类的对象来替代

1. 派生类的对象可以隐含转换为基类对象。  //隐含的意思就是直接用,不用特意在语句中写上一些转换的标记

2. 派生类的对象可以初始化基类的引用。

3. 派生类的指针可以隐含转换为基类的指针。

4. 注意:在替代之后,派生类对象可以作为基类对象使用,但是只能使用从基类继承的成员

7.4 派生类的构造和析构函数************

7.4.1 构造函数********

1. 派生类的构造函数只负责对派生类新增的成员进行初始化(对基类的初始化要调用基类的构造函数)

2. 构造派生类的对象时,就要对基类的成员对象和新增成员对象进行初始化

3. 派生类构造函数的一般语法形式

4. 对于使用默认构造函数的基类,可以不给出类名。同样,对于成员对象,如果是使用默认构造函数,也不需要写出对象名和参数表

5. 对于基类初始化时,如果需要调用基类的带有形参表的构造函数或者新增成员对象的带参数的构造函数时,派生类就必须声明构造函数。

6. 派生类构造函数执行的一般次序

1. 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)

2. 对派生类新增的成员对象初始化,调用顺序按照它们在类中声明的顺序。

3. 执行派生类的构造函数体中的内容

7. 构造函数初始化列表中的顺序可以随意安排

7.4.2 复制构造函数********

  1. 派生类编写复制构造函数的形式

7.4.3 析构函数********

  1. 与构造函数的调用顺序完全相反

7.5 派生类成员的标识与访问************

  1. 派生类中成员属性

1. 不可访问的成员:基类私有成员

2. 私有成员

3. 保护成员

4. 公有成员

7.5.1 作用域分辨符( :: ********

1. 如果派生类中声明了与基类成员函数同名的新函数,即使函数的参数表不同,从基类继承的同名函数的所有重载形式也都会被隐藏(只有在相同作用域中定义的函数才可以重载)

2. 使用作用域分辨符的两种情况

1. 多继承同名隐藏:多个基类和派生类中有同名的成员,用派生类直接访问时,访问的是派生类的成员。加基类名和作用域分辨符可以访问基类的成员

using关键字的使用(这时,如果派生类中定义了同名但是参数列表不同的函数,基类的函数不会被隐藏,相当于重载)

2. 菱形继承

1. 产生二义性问题:当派生类直接调用间接基类的成员时,无法判断成员到底是从哪个直接基类继承而来

2. 解决方法:使用直接基类名称来限定(此时派生对象中对同一成员拥有多份同名副本)

7.5.2 虚基类********

1. 语法形式

2. 声明虚基类后,对于菱形继承,派生类就可以直接通过成员名称来访问间接基类中的成员

7.5.3 虚基类及其派生类构造函数********

1. 如果虚基类中声明有参构造,这时,在整个继承关系中,直接或者间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中列出对虚基类的初始化(区分普通的菱形继承, 远派生类中不用给出间接继承的初始化)

2. 只有 远派生类的构造函数会调用虚基类的构造函数,该派生类的其他基类对虚基类的调用都自动被忽略(写虚基类时,虚基类的构造函数被调用1次,不写虚基类时,虚基类的构造函数被调用不止一次)

3. 构造一个类对象的一般顺序

  1. 如果这个类有直接或者间接的虚基类,则先执行虚基类的构造函数****

2. 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)(不再执行它们的虚基类的构造函数)

3. 对派生类新增的成员对象初始化,调用顺序按照它们在类中声明的顺序。

4. 执行派生类的构造函数体中的内容 5.  第八章 多态性********

8.1 多态性概述************

8.1.1 多态的类型 ******** 见4.1.4

8.1.2 多态的实现********

1. 一些概念

  1. 绑定:计算机程序自身彼此关联的过程,把一个标识符名和一个存储地址联系起来,或者把一条消息和一个对象的方法相结合的过程

2. 编译时的多态(静态绑定):在编译,连接过程中,系统就可以确定某一同名标识符到底要调用哪一段程序代码,例:重载,强制和参数多态(静态多态性)

3. 运行时的多态(动态绑定):等程序开始运行后再来确定绑定问题,例:包含多态(动态多态性)

8.3 虚函数************

8.3.1 一般虚函数成员********

  1. 一般虚成员函数的声明语法

总结:特殊函数声明和实现********

1. 静态成员函数:声明时加static,实现时不加

2. 常成员函数:声明和实现的时候都要加const,且都是加在函数后面

3. 一般虚成员函数:声明时加virtual,实现时不加,派生类中可以省略virtual

4. 运行过程中的多态需要满足3个条件

1. 类之间满足赋值兼容规则

2. 要声明虚函数

3. 由成员函数来调用或者通过指针、引用来访问虚函数(如果是使用对象名来访问虚函数,则绑定在编译过程中就可以进行,而无须在运行过程中进行)

虚函数的注意事项

1. 虚函数必须是非静态成员函数

2. 虚函数一般不声明为内联函数(对内联函数的处理是静态的)

3. 当基类构造函数调用虚函数时,不会调用派生类的虚函数(因为当基类被构造的时候,对象还不是派生类的对象)

4. 当基类析构函数调用虚函数时,调用基类的虚函数(同上)

5. 如果派生类需要修改基类的行为,就应该在基类中将相应的函数声明为虚函数

6. 如果虚函数有默认形参值,不要重新定义不同的值(原因:默认形参值是静态绑定的,默认形参值只能来自基类的定义)

7. 基类的对象不能表示派生类的对象,派生类对象复制构造基类对象的行为叫做对象切片

  1. 实例

 

运行结果:

   Base1::display()

   Base2::display()

   Derived::display()

8.4 纯虚函数与抽象类************

8.4.1 纯虚函数********

1. 声明格式

2. 注意事项

1. 声明为纯虚函数之后,基类就可以不再给出函数的实现部分

2. 如果将析构函数声明为纯虚函数,必须给出它的实现,因为派生类的析构函数体执行完后需要调用基类的纯虚函数

3. 在基类中对纯虚函数定义的函数体的调用必须通过“基类名::函数名(参数表)”的形式

4. 纯虚函数所在的类为抽象类

5. 纯虚函数不同于函数体为空的虚函数,纯虚函数没有函数体

8.4.2 抽象类********

  1. 注意事项

1. 如果派生类给出所有纯虚函数的函数实现,这个派生类就可以实例化对象;如果没有给出全部纯虚函数的实现,则这个派生类依然是一个抽象类

2. 抽象类不能实例化,但是可以定义抽象类的指针和引用,通过指针和引用就可以指向并访问派生类的对象实例