高级程序设计

17 - 消息的多态和动态绑定

2020-04-02 14:00 CST
2020-04-02 12:04 CST
CC BY-NC 4.0

什么是多态

多态是指某一论域中的一个元素存在多种解释。

多态通常体现为:

  • 一名多用:
    • 函数名重载
    • 操作符重载
  • 类属性(范型、generic programming):
    • 类属函数:一个函数能对多种类型的数据进行操作;
    • 类属类型:一个类型可以描述多种类型的数据。

子类型

对用类型$T$表达的所有程序$P$,当用类型$S$去替换程序$P$中的所有的类型$T$时,程序$P$的功能不变,则称类型$S$是类型$T$的子类型。

  • 类型$T$的操作也适合于类型$S$;
  • 在需要$T$类型数据的地方可以用$S$类型的数据去替代(类型$S$的值可以赋值或作为函数参数传递给$T$类型变量)。

在C++种,把类看作类型,把以public方式继承的派生类看作是基类的子类型。

  • 对基类对象实施的操作也能作用于派生类对象;
  • 在需要基类对象的地方可以用派生类对象去替代(派生类对象可以赋值或作为函数参数传给基类变量)。

面向对象程序设计的多态性

对于具有public继承关系的两个类,在C++种存在下面的多态:

  • 派生类对象的类型既可以是派生类,也可以是基类(对象类型的多态)。
  • 基类的指针或引用可以指向或引用基类对象,也可以指向或引用派生类对象(对象标识的多态)。
  • 一个可以发送到基类对象的消息,也可以发送到派生类对象,从而可能会得到不同的解释(消息的多态)。

多态性带来了消息的绑定问题:

  • 向基类的指针或引用所指向或引用的对象发送消息,将调用(基类还是派生类的)什么成员函数来处理这个消息?

消息的静态绑定

void func1(A &x) {
    x.foo();  // 始终调用A::foo
}
void func2(A *p) {
    p->foo(); // 始终调用A::foo
}

消息的动态绑定

一般情况下,需要根据标识实际引用(指向)的对象来决定调用基类还是派生类的函数,即采用动态绑定。

在C++种用虚函数来实现动态绑定。

class A {
  public:
    virtual void foo();
}

虚函数与重定义

虚函数是指加了关键词virtual的成员函数。虚函数具有两个作用:

  • 实现消息的动态绑定;
  • 指出基类种可以被派生类重定义的成员函数。

对于基类中的一个虚函数,在派生类中定义的、与之具有相同型构的成员函数是对基类该成员函数的重定义(或称覆盖,override)。

相同的型构是指;

  • 派生类中定义的成员函数的名称、参数个数和类型与基类相应成员函数相同;
  • 其返回值类型与基类成员函数返回值类型要么相同,要么是基类成员函数返回值类型的public派生类。

动态绑定的发生:

  • 只有通过基类的指针或引用访问基类的虚函数时才进行动态绑定。
  • 基类的构造函数、析构函数对虚函数的调用不进行动态绑定。

对虚函数的其他说明:

  • 只有类的成员函数才可以是虚函数,但静态成员函数不能是虚函数。
  • 构造函数不能是虚函数,析构函数可以且往往是虚函数。
  • 只要在基类中说明了虚函数,在派生类和派生类的派生类等等中,同型构的成员函数都是虚函数(virtual可以不写)。