[1.x] 切片Slice

直接将子类对象赋给一个父类对象时会发生信息丢失,这就是切片

[2.x] 向上造型upcast

| 假设我们有:
- 类Base的对象b
- 类Derived的对象d
- 且DerivedBase的派生类
如果我们将一个Base类的指针p指向Derived,或者用父类引用子类,就发生了向上造型upcast
- Base *p = &d;
- Base &r = d;
- 一个理解向上造型的方法是:向上造型并不改变对象数据的实际内容,只是改变对这个对象的看法。
- 类似于:我不把一个苹果当成苹果类,而是把它看成水果类


- 子类override了父类的函数可以返回父类的函数的返回值的子类(这个情况只适用于指针和引用)
- 父类的函数返回一个水果,子类override以后可以返回一个苹果(但是你不能直接返回一个苹果对象)
- 本质和上面的向上造型类似


- 更深入的理解可以参考我如何理解C++虚表和动态绑定
| | —- |

[3.x] 虚函数virtual

| 当一个类的成员函数前有virtual关键字时,储存该类对象的第一块地址将存放一个二级指针,该指针指向该类的虚函数表。
虚函数表中将存放若干指针,分别指向该类的若干虚函数。
虚函数的作用将在存在override的动态绑定和静态绑定的区别中体现。```cpp // 不使用虚函数 class Shape{ private: Point center; public: void render(){ cout << “Shape::render()\n”; } };

class Circle:Shape{ private: Point center; public: void render(){ cout << “Circle::render()\n”; } };

void render(Shape* p){ p->render(); // 多态变量 }

int main(){ Shape s; Circle c;

  1. s.render(); // static binding / early binding
  2. c.render(); // static binding / early binding
  3. render(&s); // dynamic binding / late binding
  4. render(&c); // dynamic binding / late binding

}

// 输出结果: // Shape::render() // Circle::render() // Shape::render() // Shape::render() *

  1. ```cpp
  2. // 使用虚函数
  3. class Shape{
  4. private:
  5. Point center;
  6. public:
  7. virtual void render(){ cout << "Shape::render()\n"; }
  8. };
  9. class Circle:Shape{
  10. private:
  11. Point center;
  12. public:
  13. // 由于继承,Circle::render()也是虚函数
  14. void render(){ cout << "Circle::render()\n"; }
  15. };
  16. void render(Shape* p){
  17. p->render(); // 多态变量
  18. }
  19. int main(){
  20. Shape s;
  21. Circle c;
  22. s.render(); // static binding / early binding
  23. c.render(); // static binding / early binding
  24. render(&s); // dynamic binding / late binding
  25. render(&c); // dynamic binding / late binding
  26. }
  27. // 输出结果:
  28. // Shape::render()
  29. // Circle::render()
  30. // Shape::render()
  31. // Circle::render() *

虚表
- image.png
- image.png
- image.png
- 观察发现,子类的结构和父类的结构需要一样(为了适应继承关系)
- 如果继承关系再多一层:
- image.png
- 需要注意:
- 虚表内指针的顺序不能改变
- 这意味着当一个父类指针对其进行Upcast的时候它知道虚表第二项是render(),但不必关心指向的是谁
- 通过vptr我们可以实现运行时刻类型识别


- 构造函数中调用的函数与虚函数与否无关,因为Vptr是在构造函数中被写入/重写的,所以调用的总是该类的函数
- 习惯上,我们不希望出现重新定义父类中的非virtual函数的行为(出于效率考虑,我们更希望静态绑定)


- 更深入的理解可以参考我如何理解C++虚表和动态绑定
| | —- |

[4.x] 动态绑定与静态绑定

| > 例子可以查看[2.3.5.x] 虚函数virtual

  • 静态绑定Static Binding
    - 能够明确运行的是哪个类的方法时会发生静态绑定
    - 发生在编译时刻,所以又叫早绑定
    - 动态绑定Dynamic Binding
    - 出现多态,编译器不能明确到底使用哪个类的方法时发生动态绑定
    - 发生在运行时刻,所以又叫晚绑定
    - 只有存在virtual且通过指针访问时,才会发生动态绑定
    > The compiler cannot make a function call in the traditional sense. The function call generated by a non-OOP compiler causes what is called early binding, a term you may not have heard before because you’ve never thought about it any other way. It means the compiler generates a call to a specific function name, and the linker resolves this call to the absolute address of the code to be executed. In OOP, the program cannot determine the address of the code until runtime, so some other scheme is necessary when a message is sent to a generic object.
    To solve the problem, object-oriented languages use the concept of late binding. When you send a message to an object, the code being called isn’t determined until runtime. The compiler does ensure that the function exists and performs type checking on the arguments and return value (a language in which this isn’t true is called weakly typed), but it doesn’t know the exact code to execute.

  • 本质上要看编译器能否在编译时刻确定要绑定哪个函数,如果能够确定,那即便是virtual函数也会做静态绑定
    [4.x] 多态 - 图5
    点击查看【bilibili】 | | —- |

[5.x] 多态

| 多态的两个技术基础:
- 🔗向上造型Upcast[00:20:00]
- 🔗动态绑定Dynamic Binding
多态变量Polymophic Variable
- 变量类型状态分为:
1. 静态类型 / 声明类型
1. 动态类型 / 实时类型
- 多态变量执行virtual函数,主要依照其动态类型
```cpp // … void render(Shape* p){ p->render(); // 多态变量 }

int main(){ Shape s; Circle c; render(&s); // dynamic binding / late binding render(&c); // dynamic binding / late binding }

`` 如上代码,render()中的p就是个多态变量,编译器在编译时刻无法知道它将指向哪种类型的实例,它可以接收所有Shape`的子类进行向上转型,所以称其为“多态变量”

多态的意义在于,我们可以大大提高代码的重用率,在构成is-a关系的类中能够实现用同一段代码控制多种类型 | | —- |