C++ note (11)
多态 Ⅰ
虚函数及其意义
概念
虚函数是一个类的成员函数,前面有关键字 virtual
- 作用:在公有继承层次中的一个或多个派生类中对虚函数进行重定义。当派生类的对象使用它基类的指针(或引用)调用虚函数时,将调用该对象的成员函数。
“一旦为虚,永远为虚”
- 含义1:在公有继承层次中,某个类的成员函数申明为 virtual。则其直接或间接派生类中的相同签名的函数,都是虚函数。不论是否用关键字 virtual 再次申明。
- 含义2:任何虚函数,都有可能被其派生类再次重新定义,重新定义后依然是虚函数。因此,虚函数既可以被继承,也可以被重新定义。
意义
Override and Overwrite
覆盖(override)- 修改基类函数定义
- 基类或非直接基类,至少有一个成员函数被 virtual 修饰
- 派生类虚函数必须与基类虚函数有同样签名,即函数名,参数类型,顺序和数量都必须相同。
隐藏(overwrite)- 屏蔽基类的函数定义
- 派生类的函数与基类的函数同名,但是参数列表有所差异。
- 派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有virtual关键字。
对象指针(引用)被向上转型到基类指针(引用)
- 覆盖:调用该对象的虚函数
- 隐藏:调用基类的函数
派生类对象赋值到基类对象 - 按基类行为调用该函数
作用域 | 是否虚函数 | 函数名 | 函数签名 | |
---|---|---|---|---|
重载 | 相同 | - | 相同 | 不同 |
隐藏/屏蔽 | 不同 | 否 | 相同 | 不同/相同 |
覆盖 | 不同 | 是 | 相同 | 相同 |
多态的概念
概念
程序语言中,多态特指一个标识符指代不同类型的数据。或者说,由具体的数据类型决定该标识符的语义。
满足多态定义的标识符
函数重载(Function Overload)
- 函数名,它可为不同类型函数(注:函数类型声明为 f(t1,t2…))
方法覆盖(Method Override)
- 有虚函数基类的 指针或引用,它可指代派生类的对象
泛型(Generics)/模板(Template),即“参数化类型”
- 模板名,例如 vector 可泛化指代各种类型数据的数组
多态标识符必须指派具体的函数或方法以实现规定的语义。
静态绑定:如果在运行前由编译完成这个指派,称为静态绑定
动态绑定:如果在运行期间完成这个指派,称为动态绑定
多态-函数重载
多态-动态模型
静态类型与动态类型
静态类型
- 对程序进行编译时分析所得到的表达式的类型被称为表达式的静态类型。程序执行时静态类型不会更改。
动态类型
- 若某个泛左值表达式指代某个多态对象,则其最终派生对象的类型被称为其动态类型。
1
2
3
4
5
6
7
8struct B {virtual ~B(){}};
// 多态类型,至少包含一个虚函数
struct D:B{} // 多态类型
D d; // 最终派生对象
B* ptr = &d;
// (*ptr)的静态类型为B
// (*ptr)的动态类型为Dcppreference: type class
静态类型——静态绑定
重写一个相同函数名、相同参数的函数,会覆盖或隐藏之前继承而来的函数。
基类指针指向派生类对象,这对于实现多态性的行为是至关重要的。
动态类型——动态绑定
仅需要在基类的成员函数前面加上virtual关键字,就能把一个函数声明为虚函数。该类及其子类都是多态类型。
当多态类型指针(或引用)调用虚函数时,则产生多态现象,即调用指针所指向的对象的成员函数
在面向对象编程中,多态与继承层次结构相关,使开发人员可以“用通用的方法编程”而不是局限于“用特殊的方法编程”。
在现实中会经常用到多态,比如“移动move”这一行为在大象和猴子中有不同表现形式,“发薪水”这一行为在教授、副教授、讲师之中可能有不同的计算方式,“绘制draw”这一行为对于三角形、正方形也是不同的。
特别是我们设计程序库时,根本不可能知道用户行为的具体实现方式。多态为适应这种变化和扩展提供了便利