课件

10.pdf

10.1 引言

引言

10.1.1 引言.mp4 (29.44MB)

notes

引子 | 为什么需要继承和派生

类真不错!类描述了群体的共性,通过创建类的不同对象,我们实现了代码重用。但是这种重用是不充分的……

假设我们有汽车类

  1. #include <iostream>
  2. using namespace std;
  3. class Car {
  4. public:
  5. int seats;
  6. void accelerate() { cout << "加速到50迈" << endl; } // 油门
  7. void brake() {
  8. // 刹车
  9. }
  10. };
  11. int main() {
  12. Car c;
  13. return 0;
  14. }

当 Car 类的功能需要进行扩展为变形金刚类 Transformer,增加“变身”的功能,怎么做?

解决方案一:创建一个新类 Transformers,在其中粘贴 Car 类的代码,再添加新的方法 voidbianshen()

  1. #include <iostream>
  2. using namespace std;
  3. class Car {
  4. public:
  5. int seats;
  6. void accelerate() { cout << "加速到50迈" << endl; } // 油门
  7. void brake() {
  8. // 刹车
  9. }
  10. void bianshen() {
  11. // 变身
  12. }
  13. };
  14. int main() {
  15. Car c;
  16. return 0;
  17. }

Car 类增加一个功能:停车 voidstop(),那么我们要同时更改 Car 类和 Transformer 类的代码。这种做法太不优雅啦,会让代码难以维护。

解决方案二:继承与派生

Transofomer 类“使用”了 Car 类的特性,那么不需要复制粘贴,Transformer 类就自动具有 Car 类所有的特性。更好的一点是,任何时候 Car 类进行修改,Transformer类都能应用这种修改。

继承与派生就是本章节要介绍的重点内容。

10.2 派生类的引入与特性

10.2.1 派生类的引入与特性.mp4 (42.68MB) 继承:

  • 一旦指定了某种事物父代的本质特征,那么它的子代将会自动具有那些性质。这就是一种朴素的 可重用的概念
  • 继承就是在一个已经存在的类的基础上建立另一个新的类
  • 已存在的类称为“基类”或“父类”,新建立的类称为“派生类”或“子类”

派生:

  • 子代可以拥有父代没有的特性,这是 可扩充的概念
  • 派生类的功能主要通过以下方式来体现:
    • 吸收基类成员
    • 添加新成员
    • 改造基类成员

从编码的角度来看,派生类从基类中以较低的代价换来了较大的灵活性:

  • 派生类可以对继承的属性进行扩展、限制或改变。
  • 一旦产生了可靠的基类,只需要调试派生类中所作的修改即可。

10.3 单继承

单继承的继承方式

10.3.1 单继承的继承方式.mp4 (119.33MB)

同名成员的访问方式

10.3.2 同名成员的访问方式.mp4 (34.89MB)

赋值兼容原则

10.3.3 赋值兼容原则.mp4 (49.11MB)

单继承方式下的构造与析构函数的调用顺序

10.3.4 单继承方式下的构造与析构函数的调用顺序.mp4 (47.72MB)

notes

单继承、多继承

  • 单继承:派生类只有一个直接基类
  • 多继承:派生类有多个直接基类

10. 第一部分继承与派生 - 图7

单继承派生类的定义语法、访问权限

  1. class 派生类名:<继承方式>基类名{
  2. // …… 派生类修改基类的成员
  3. // …… 派生类新添加的成员
  4. };
  5. // 继承方式:public、private、protected

虽然继承了基类的所有成员,但是派生类并非都能访问基类的所有成员,继承方式 会影响派生类对基类中各种成员的使用。

public、private、protected:

  • 回顾类成员的访问方式也有 public、private、protected 分别是什么意思?
  • 和我们这里继承方式的 public、private、protected 有区别吗?

类成员的 public、private、protected

  • public 类内外都可以访问,是类的对外接口
  • private 类内可以访问,内外不能访问
  • protected 同 private,类内可以访问,内外不能访问

继承方式的 public、private、protected

  • public 公有派生类
  • private 私有派生类
  • protected 保护派生类
基类 公有成员 私有成员 保护成员
公有派生类 公有成员 不可访问成员 保护成员
私有派生类 私有成员 不可访问成员 私有成员
保护派生类 保护成员 不可访问成员 保护成员

虽然派生类继承了基类所有的成员,但是对于派生类来说,基类的私有成员是不可见的,因此不能被派生类所访问。

不可访问成员:

  • 在类外不能被直接访问
  • 在派生类的类内不能被直接访问
  1. class 派生类名: public 基类名 {
  2. // …… 派生类新添加的成员
  3. }
  1. #include <iostream>
  2. using namespace std;
  3. class Base {
  4. int v1; // 没有使用访问修饰符,则默认使用 private 访问修饰符
  5. public:
  6. int v2;
  7. Base(int a = 0, int b = 0) {
  8. v1 = a;
  9. v2 = b;
  10. }
  11. };
  12. class Derived : public Base {
  13. int v3;
  14. public:
  15. int v4;
  16. Derived(int a = 0, int b = 0) {
  17. v3 = a;
  18. v4 = b;
  19. }
  20. void func() {
  21. // cout << v1 << endl; // 编译器报错“ v1 是 Base 的 private 成员,不能访问”,private 权限 + 任何继承 = 不能访问
  22. cout << v2 << endl; // 成功访问到基类成员v2 ,public 权限 + public 继承 = public 权限,此时 v2 的访问权限是 public
  23. cout << v3 << endl; // 成功访问到自身成员 v3,private 权限,允许自身成员函数访问,不允许自身对象访问。
  24. cout << v4 << endl; // 成功访问到自身成员 v4,public 权限,允许自身成员函数,自身对象访问。
  25. }
  26. };
  27. int main() {
  28. Derived obj = Derived(5, 6);
  29. // obj.v1 = 8; // 编译器报错:“Derived 没有成员 v1”
  30. // obj.v3 = 4; // 编译器报错:“v3 是 Derived 的私有成员”
  31. obj.v2 = 8; // 成功,通过对象访问公有变量 public 权限 + public 继承 = public 权限
  32. obj.v4 = 7; // 成功,通过对象访问公有变量。
  33. obj.func();
  34. return 0;
  35. }
  36. /* 运行结果:
  37. 8
  38. 5
  39. 7
  40. */
  1. class 派生类名: private 基类名 {
  2. // …… 派生类新添加的成员
  3. }
  1. #include <iostream>
  2. using namespace std;
  3. class Base {
  4. int v1;
  5. public:
  6. int v2;
  7. Base(int a = 0, int b = 0) {
  8. v1 = a;
  9. v2 = b;
  10. }
  11. };
  12. class Derived : private Base {
  13. int v3;
  14. public:
  15. int v4;
  16. Derived(int a = 0, int b = 0) {
  17. v3 = a;
  18. v4 = b;
  19. }
  20. void func() {
  21. // cout << v1 << endl;
  22. cout << v2 << endl;
  23. cout << v3 << endl;
  24. cout << v4 << endl;
  25. }
  26. };
  27. int main() {
  28. Derived obj = Derived(5, 6);
  29. // obj.v1 = 8;
  30. // obj.v3 = 4;
  31. // obj.v2 = 8; // v2 是派生类的私有成员,不能被类外访问
  32. obj.v4 = 7;
  33. obj.func();
  34. return 0;
  35. }
  36. /* 运行结果:
  37. 0
  38. 5
  39. 7
  40. */
  1. class 派生类名: protected 基类名 {
  2. // …… 派生类新添加的成员
  3. }
  1. #include <iostream>
  2. using namespace std;
  3. class Base {
  4. protected:
  5. int v1;
  6. public:
  7. int v2;
  8. Base(int a = 0, int b = 0) {
  9. v1 = a;
  10. v2 = b;
  11. }
  12. };
  13. class Derived : protected Base {
  14. int v3;
  15. public:
  16. int v4;
  17. Derived(int a = 0, int b = 0) {
  18. v3 = a;
  19. v4 = b;
  20. }
  21. void func() {
  22. cout << v1 << endl;
  23. cout << v2 << endl;
  24. cout << v3 << endl;
  25. cout << v4 << endl;
  26. }
  27. };
  28. int main() {
  29. Derived obj = Derived(5, 6);
  30. // obj.v1 = 8; // error => 'v3' is a private member of 'Derived'
  31. // obj.v3 = 4; // error => 'v3' is a private member of 'Derived'
  32. // obj.v2 = 8; // error => 'v2' is a protected member of 'Base'
  33. obj.v4 = 7;
  34. obj.func();
  35. return 0;
  36. }
  37. /* 运行结果(注意:和课件描述不一致):
  38. 0
  39. 0
  40. 5
  41. 7
  42. */

派生类与基类同名成员的访问方式

  • C++ 允许派生类可以重新定义基类的成员,此时称派生类的成员 覆盖了 基类的同名成员。
  • 如果在派生类中,想使用基类的同名成员,则可以显式地使用 类名 + 限定符 的方式:基类名::成员
  1. #include <iostream>
  2. using namespace std;
  3. class Base {
  4. protected:
  5. int v1;
  6. public:
  7. int v2;
  8. Base(int a = 0, int b = 0) {
  9. v1 = a;
  10. v2 = b;
  11. }
  12. };
  13. class Derived : public Base {
  14. int v2;
  15. public:
  16. int v3;
  17. Derived(int a = 0, int b = 0) {
  18. v2 = a;
  19. v3 = b;
  20. }
  21. void func() {
  22. int sum1 = v1 + v2 + v3;
  23. int sum2 = v1 + Base::v2 + v3;
  24. cout << "v1 = " << v1 << endl;
  25. cout << "v2 = " << v2 << endl;
  26. cout << "v3 = " << v3 << endl;
  27. cout << "Base::v2 = " << Base::v2 << endl;
  28. cout << "sum1 = " << sum1 << endl;
  29. cout << "sum2 = " << sum2 << endl;
  30. }
  31. };
  32. int main() {
  33. Derived obj(5, 6);
  34. // obj.v2 = 8; // 错误 这么写使用的是 Derived 中的 v2,它是私有成员
  35. obj.Base::v2 = 9; // 正确 使用的是 Base 中的 v2
  36. obj.func();
  37. return 0;
  38. }
  39. /* 运行结果:
  40. v1 = 0
  41. v2 = 5
  42. v3 = 6
  43. Base::v2 = 9
  44. sum1 = 11
  45. sum2 = 15
  46. */

赋值兼容规则

必须满足赋值兼容规则才能够正常完成赋值。

  1. #include <iostream>
  2. using namespace std;
  3. class animal {
  4. public:
  5. void eat() { cout << "animal is eating" << endl; }
  6. };
  7. class dog : public animal {
  8. public:
  9. void eat() { cout << "wang wang" << endl; }
  10. };
  11. int main() {
  12. animal a;
  13. dog d;
  14. d.eat(); // => wang wang
  15. a = d; // 成功赋值,因为"所有的动物都是狗"
  16. a.eat(); // => animal is eating
  17. animal& ref_a = d;
  18. ref_a.eat(); // => animal is eating
  19. animal* pointer_a = &d;
  20. pointer_a->eat(); // => animal is eating
  21. return 0;
  22. }
  1. #include <iostream>
  2. using namespace std;
  3. class animal {
  4. public:
  5. void eat() { cout << "animal is eating" << endl; }
  6. };
  7. class dog : public animal {
  8. public:
  9. void eat() { cout << "wang wang" << endl; }
  10. };
  11. int main() {
  12. animal a;
  13. dog d;
  14. d.eat(); // => wang wang
  15. // d = a; // 编译错误,不能将 animal 对象 a 转换为 dog 对象 d,因为“不是所有的动物都是狗”
  16. return 0;
  17. }
  1. #include <iostream>
  2. using namespace std;
  3. class animal {
  4. public:
  5. void eat() { cout << "animal is eating" << endl; }
  6. };
  7. class dog : public animal {
  8. public:
  9. void eat() { cout << "wang wang" << endl; }
  10. };
  11. class lion : public animal {
  12. public:
  13. void eat() { cout << "lion lion" << endl; }
  14. };
  15. int main() {
  16. animal a;
  17. dog d;
  18. lion l;
  19. a = l; // 正确,因为狮子是动物
  20. a.eat(); // => animal is eating
  21. // l = a; // 错误,动物不一定是狮子
  22. l.eat(); // => lion lion
  23. // l = (lion)a; // 错误,动物不一定是狮子
  24. // d = (dog)a; // 错误,动物不一定是狗
  25. l.eat(); // => lion lion
  26. d.eat(); // => wang wang
  27. return 0;
  28. }

公有派生 方式下,派生类对象可以作为基类对象来使用,具体方式如下:

  • 派生类的对象可以直接赋值给基类的对象
  • 基类对象的引用可以引用一个派生类对象
  • 基类对象的指针可以指向一个派生类对象

这个规则可以简单的总结为:所有的子类对象都是基类的对象。
例:所有的狗都是动物,但不是所有的动物都是狗。

单继承的构造与析构

问:派生类继承了基类成员,对象中既包含派生类的成员又包含基类的成员。当我们生成派生类对象的时候,是怎么进行初始化的呢?
答:通过构造函数!

就像我们自己要建造变形金刚汽车,我们首先要抽象汽车的共性,包括发动机,轮胎,车门等信息,然后在派生类当中加上我们自己的特色,比如我们的汽车具有变形功能等。

所以派生类在构造对象的时候,先要生成自己基类部分,然后生成自己的成员对象,最后才是自己独有的部分。

  1. 派生类构造函数(参数表):基类构造函数(参数表),对象成员1(参数表), …… 对象成员n(参数表), {
  2. // …… 初始化自定义数据成员
  3. }
  • 如果没有对象成员,那么在初始化列表中可以省略 对象成员(参数表) 这一项。
  • 如果基类使用的是缺省的构造函数或不带参数的构造函数,那么在初始化列表中可以省略 基类构造函数(参数表) 这一项。
  1. #include <iostream>
  2. using namespace std;
  3. class Point {
  4. protected:
  5. float x, y;
  6. public:
  7. Point(float xx = 0, float yy = 0) : x(xx), y(yy) {}
  8. float GetX() { return x; }
  9. float GetY() { return y; }
  10. void moveto(float xx, float yy) {
  11. x = xx;
  12. y = yy;
  13. }
  14. };
  15. class Circle {
  16. Point center;
  17. float radius;
  18. public:
  19. Circle(float x, float y, float r) : center(x, y) { radius = r; }
  20. Point GetCenter() const { return center; }
  21. int GetRadius() { return radius; }
  22. };
  23. class ColorCircle : public Circle {
  24. int color;
  25. public:
  26. ColorCircle(float x, float y, float r, int color) : Circle(x, y, r) {
  27. this->color = color;
  28. }
  29. int GetColor() { return color; }
  30. };
  31. int main() {
  32. ColorCircle cc(1, 2, 3, 4);
  33. Point center = cc.GetCenter();
  34. int r = cc.GetRadius();
  35. cout << "圆心: (" << center.GetX() << ", " << center.GetY() << ")" << endl;
  36. cout << "半径: " << r << endl;
  37. cout << "颜色: " << cc.GetColor() << endl;
  38. return 0;
  39. }
  40. /* 运行结果:
  41. 圆心: (1, 2)
  42. 半径: 3
  43. 颜色: 4 */
  • Circle(x,y,r) 初始化基类数据
  • this->color=color; 初始化自定义的基本数据类型数据
  1. #include <iostream>
  2. using namespace std;
  3. class Base {
  4. public:
  5. Base() { cout << "Base obj created" << endl; }
  6. ~Base() { cout << "Base obj deleted" << endl; }
  7. };
  8. class Derived : public Base {
  9. public:
  10. Derived() { cout << "Derived obj created" << endl; }
  11. ~Derived() { cout << "Derived obj deleted" << endl; }
  12. };
  13. int main() {
  14. Derived d;
  15. return 0;
  16. }
  17. /* 运行结果:
  18. Base obj created
  19. Derived obj created
  20. Derived obj deleted
  21. Base obj deleted
  22. */

构造函数和析构函数的调用顺序:

  • 构造函数的调用顺序如下:
    • 先调用基类构造函数;
    • 再调用对象成员所属类的构造函数;
    • 最后调用派生类构造函数;
  • 析构函数的调用顺序如下:
    • 再调用对象成员所属类的析构函数;
    • 先调用派生类的析构函数;
    • 最后调用基类的析构函数;

10.4 多继承

10.4.1 多继承.mp4 (17.22MB) 10.4.2 多继承.mp4 (114.13MB)

10.5 编程实战

10.5 源码.zip

多形状时钟的理论分析

10.5.1 多形状时钟的理论分析.mp4 (201.65MB)

多形状时钟的编程实战

10.5.2 多形状时钟的编程实战.mp4 (417.93MB)

继承与派生的精灵游戏

10.5.3 继承与派生的精灵游戏.mp4 (196MB)

10.6 精灵游戏

10.6 源码.zip

精灵类的继承与派生

10.6.1 精灵类的继承与派生.mp4 (234.19MB)

10.7 小结

10.7.1 小结.mp4 (73.13MB)