13.C++:继承
目录
一、继承的概念及定义
1.1、继承的概念
1.2、继承的定义
1.2.1、语法形式
1.2.2、继承基类成员访问方式的变化
1.3、继承类模版
二、基类和派生类之间的转换
三、继承中的作用域
3.1、隐藏规则
3.2、练习题
四、派生类的默认成员函数
五、友元与继承
六、静态成员与 继承
七、多继承和菱形继承的问题
7.1、多继承
7.2、菱形继承
7.3、虚继承
八、继承和组合
Note:如何让一个类不能被继承?
在现实中,我们想拥有财富,无非是靠自己奋斗,或从父辈那里继承。后者显然是一条更轻松的捷径。在C++的世界里,面向对象编程为我们提供了类似的“捷径”——继承。它让一个类能够直接从另一个类获取已有的属性和方法,无需一切从零开始,这避免了“重复发明轮子”,真正实现了“站在巨人的肩膀上”进行开发。本篇博客将为你详细解读C++的继承机制。
一、继承的概念及定义
1.1、继承的概念
继承机制是面向对象程序设计使代码可以重复使用的一种手段。继承是类设计层次的复用。
比如:学校管理系统。学校里面有学生,老师,职工,这三类有个共同特点:都是人。所以记录它们信息的时候有一部分是必然重叠的(姓名,年龄,性别等)那么我们可以专门创建一个 person 类,然后再创建student ,teacher,worker 三个类,让这三个类去继承 person 这个类就好了。
对于被继承的类,我们称之为 基类(也叫父类),继承别的类的类,称为 派生类(也叫子类)

1.2、继承的定义
1.2.1、语法形式

在派生类名后加个 : ,然后接着继承方式,基类就可以了。
继承方式有三种:

1.2.2、继承基类成员访问方式的变化
| public继承 | protected继承 | private继承 | |
| 基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
| 基类的private成员 | 派生类不可见 | 派生类不可见 | 派生类不可见 |
Note:基类的私有成员,在派生类中不可见,语法上限制不让用。对于如此庞杂的局面,我们只需要记住 public > protected > private 就可以了。基本上,我们使用的都是公有继承。
Note:使用关键字 class,默认继承方式为 private,使用 struct 默认继承方式为 public,不过最好还是显式写出继承方式
1.3、继承类模版
模版是按需实例化,调用到哪个成员函数,就实例化哪个成员函数,所以必须要指定!
说人话就是,在派生类中调用基类成员时,需要在前面用 :: 指定是哪个类型的
二、基类和派生类之间的转换
大家都知道,在普通对象之间的类型转换时,会产生一个临时的中间变量,而该临时变量具有常性。但是,基类和派生类不是这样的,派生类给基类的引用或指针赋值,不会产生临时变量

如图,派生类给基类的引用或指针赋值,该引用或指针会直接截取本来自己有的那部分,不会产生临时变量。但是,只能是派生类赋值给基类的引用和指针,基类不能给派生类。(子可以给父,父不能给子)
三、继承中的作用域
3.1、隐藏规则
(1)基类和派生类都有独立的作用域
(2)如果基类和派生类中有同名成员,派生类会直接屏蔽掉对基类的成员访问
(3)如果是同名函数,只要函数名字相同,就隐藏(不管参数和返回类型),也就是说,没有重载这个说法了
(4)最好不要定义同名成员
3.2、练习题
(1)下面 A 和 B 类中的两个 func 构成什么关系? B
A.重载 B.隐藏 C.没关系
(2)下面程序的编译运行结果是什么? A
A.b编译报错 B.运行报错 C.正常运行

首先,A 是基类,B是派生类,由于两个成员函数的函数名是相同的,所以构成了隐藏关系,在派生类对象中调用 func 会直接隐藏基类的 func ,所以 版本 b.func() 不传任何参数就会报错。
四、派生类的默认成员函数
派生类的成员变量 = 自己的成员变量 + 基类的成员变量
自己的成员变量就跟普通类的成员变量一样,该咋滴就咋地。对于基类的成员变量,在调用派生类构造时,它会自动调用基类的默认构造(或者你手动调用)
1.派生类的构造函数,必须调用基类的构造函数,初始化基类的那部分成员。如果基类没有默认构造,那就必须在派生类构造函数的初始化时显式调用
2.派生类的拷贝构造函数必须调用基类的拷贝构造
3.派生类的赋值符重载(operator=)必须要调用基类的赋值符重载(operator=)
4.派生类的析构函数会在被调用完后自动调用基类的析构函数,因为这样才能保证派生类对象清理完再清理基类对象的顺序
5.派生类初始化,先调用的是基类的构造函数
6.派生类对象析构,先清理派生类,再清理基类
7.因为多态中一些场景的析构函数需要构成重写,重写的条件之一是函数名先攻,所以编译器会对析构函数的名字进行特殊处理,为 destructor() ,所以基类和派生类的析构函数会构成隐藏关系。
五、友元与继承
友元关系不能继承
六、静态成员与 继承
基类定义了 static 静态成员,则整个继承体系里,都只有一个这样的成员。也就是说,不管派生出都少个派生类,都只有这一个 static 成员
七、多继承和菱形继承的问题
7.1、多继承
单继承:一个派生类只有一个直接基类时,称为单继承
多继承:有两个或两个以上的直接基类

Note:在多继承中,先继承的基类成员放在前面,后继承的基类成员放在后面,最后才是派生类自己的成员
7.2、菱形继承
比如学校管理系统的研究生,他即是学生,又是助教,所以他会继承学生和助教两个基类。但是,学生和助教都是人,学生会继承人,助教也会继承人,这样会导致最后研究生里面有两个人的信息,造成数据的冗余

7.3、虚继承
如何解决菱形继承的问题呢?像 Java 直接不支持多继承,避免了菱形继承,而 C++则是提出了一个办法,虚继承。
虚继承的关键字为: virtual
class Person {
protected:string name;
public:Person(string n) : name(n) {}void introduce() {cout << "我是" << name << endl;}
};// 虚继承
class Student : virtual public Person {
public:Student(string n) : Person(n) {}void study() {cout << name << "在学习" << endl;}
};// 虚继承
class Teacher : virtual public Person {
public:Teacher(string n) : Person(n) {}void teach() {cout << name << "在授课" << endl;}
};// 研究生既是学生又是老师
class GraduateStudent : public Student, public Teacher {
public:GraduateStudent(string n) : Person(n), Student(n), Teacher(n) {}void doResearch() {cout << name << "在做科研" << endl; // 直接访问,没有二义性}
};
Note:谁的数据会造成冗余和二义性就给谁用,这里肯定是给人用,而不是给老师和学生用
八、继承和组合
继承和组合本质上都是对类的复用。
继承是一种 is-a 的关系 ,而组合是 has -a 的关系
Note:建议优先使用组合,而不是继承,组合的代码耦合性低,更好维护
Note:如何让一个类不能被继承?
(1)将构造函数放到私有。
(2)使用 final 修饰
class Base final
{//……
};
恭喜你已经阅读完本篇博客的全部内容,如果有不足和错误,恳请批评和指正,期待我们的下次相会!
