一.继承
1.继承的概念
C++ 中的继承,就是让一个类“继承”另一个类已有的成员,实现代码复用和功能扩展。
基本形式:


这里:
父类 也叫 基类
子类 也叫 派生类
子类 : public 父类 表示子类以 public 方式继承父类。
C++ 继承的核心作用是:子类可以复用父类已有代码,并在此基础上增加自己的新功能。

此时 Student 对象不仅有自己的 stuId,还拥有从 Person 继承来的 name、age 和 Print()。

可以理解为:
学生是一种人,所以 Student 可以继承 Person。
2.定义格式

继承方式主要有三种:
最常用的是 public 继承。
public 继承:父类成员在子类中基本保持原来的访问权限。
protected 继承:父类 public 和 protected 成员到了子类中都变成 protected。
private 继承:父类 public 和 protected 成员到了子类中都变成 private。
注意:父类的 private 成员也会被继承到子类对象中,但子类不能直接访问。

3.继承类模版
C++ 中“继承类模板”,就是:一个类模板可以继承另一个类模板实例化后的类。
基本格式:

这里:

不是继承 Base,而是继承 Base<T>。
因为 Base 本身只是一个模板,还不是具体类型;Base<int>、Base<double> 才是具体类型。

也就是说,Derived<int> 继承的是 Base<int>。固定类型继承:

这里不管 Derived<T> 的 T 是什么,它都继承 Base<int>。

实际是:

所以父类里的 _data 类型永远是 int。
普通类继承类模版
这是可以的,因为 Base<int> 已经是一个具体类。
但这样不可以:

错误原因:Base 只是模板,不是类型。
总结:
类模板不能直接被继承,必须先实例化成具体类型,比如 Base<T> 或 Base<int>,才能作为父类。
二.父类和子类之间的转换

1. 子类转父类(向上转型)
对象转换

执行后:

id 被丢弃了。
这叫对象切片 。
指针转换

合法。
因为学生一定是人。
所以:
可以转换为:

引用转换

此时:

实际上修改的是:

2. 父类转子类(向下转型)
一般不允许。

错误
因为人不一定是学生。
指针也不允许

错误
因为Person对象中根本没有id成员。
总结:
子类拥有父类的全部内容,所以子类可以当父类用。
父类不拥有子类新增的内容,所以父类不能随便当子类用。
三.继承中的作用域
1. 作用域的概念

a属于类A的作用域,b属于类B的作用域
2. 继承中的作用域
当派生类继承基类后:

Student对象中既有

因此:

都可以访问
3. 同名成员隐藏
如果派生类定义了与基类同名的成员

此时:

访问的是:

而不是:

这称为隐藏派生类成员会隐藏基类同名成员
4. 如何访问基类成员

输出: 学生
父类
5. 函数也会发生隐藏
很多人误以为函数重载会生效。
实际上在继承中:
只要名字相同,就会隐藏。

此时:
正确
但:

错误
因为:

已经被隐藏了
编译器只会在Student作用域中查找:
找不到无参版本。
6. 调用基类同名函数

输出: Person
四.派⽣类的默认成员函数
1.派生类的构造函数

创建对象:

执行对象:

先构造父类,再构造子类。
如果不调用

编译失败。
因为Person没有默认构造函数,编译器不知道如何构造基类部分。
2. 派生类的拷贝构造
基类部分先拷贝


输出:Person拷贝构造
Student拷贝构造
若不写:

编译器自动生成:

本质上:
调用基类拷贝构造 + 拷贝自己的成员。
3. 派生类赋值运算符重载


顺序:

为什么要写 Person::operator=()
因为_name属于基类。
派生类不能直接完成基类部分的赋值工作。
必须调用:

4. 派生类析构函数

生命周期结束:

为什么顺序相反?
构造:

析构:

类似:
盖房子先打地基再盖二楼,拆房子先拆二楼再拆地基
五.继承与友元
1.友元关系不能继承
Display()是Person的的友元,但不是Student的友元。

可以访问_name

会报错
因为Display不是 Student 的友元
无法访问:_id
2. 怎样访问派生类私有成员
必须再次声明友元


才合法
3. 基类友元可以访问继承来的基类成员


这是合法的
因为Student可以向上转换为Person&,而Show访问的是Person::_name,不是Student::_id
六.继承与静态成员
在继承中,静态成员是整个类共享的,不属于某个具体对象。因此继承后,基类和派生类访问的是同一份静态成员。

访问 :

或者:
都可以
因为:

实际上是同一个变量
七. 多继承及其菱形继承问题
单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承
多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。⽀持多继承就⼀定会有菱形继承,像Java就直接不⽀持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。
1.多继承

其中Derive同时拥有Base1中的成员,Base2中的成员,自己的成员。
例子:

输出: 张三
学习
2.菱形继承
这是多继承最大的坑




出现两个: _name
访问:

编译错误
因为:

都有
编译器不知道该选哪个
菱形继承不仅有二义性,还有数据重复

同一个人名存了两份。
浪费空间。
3.虚继承解决菱形继承
C++提供virtual关键字。

结构就变成:

此时Person只有一份。
访问:

合法
因为_name只有一个。
八.继承和组合
1.什么是继承
继承表示:
is-a(是一个)关系

这里Student 是一个 Person
即Student is a Person
2.什么是组合
组合表示:
has-a(拥有)关系

这里Car 拥有 Engine
即Car has a Engine
3. 如何判断用继承还是组合
看关系
1.是一个(is-a)
学生 是一个 人
猫 是一个 动物
圆 是一个 图形
适合继承
2.拥有(has-a)
汽车 有 发动机
电脑 有 CPU
学校 有 学生
适合组合
4. 组合优于继承

后来增加:

如果用继承:

想换发动机,car必须重写
如果用组合:

直接换

扩展性更强。







