多态
多态性(polymorphism)可以简单地概括为“一个接口,多种方法”,多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。
C++支持两种多态性:编译时多态性,运行时多态性。
- 编译时多态性(静态多态):通过重载函数实现:先期联编 early binding
- 运行时多态性(动态多态):通过虚函数实现 :滞后联编 late binding
C++运行时多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(Override),或者称为重写。
多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。
多态的目的:封装可以使代码模块化,继承可以扩展已存在的代码,其目的都是为了代码重用。而多态的目的则是为了“接口重用”。即,不论传递过来的究竟是类的哪个对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
多态最常见的用法就是声明基类类型的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是固定的,因此将始终调用到同一个函数,这就无法实现“一个接口,多种方法”的目的了。
注意
- 只有类的成员函数才能声明为虚函数,虚函数仅适用于有继承关系的类对象。普通函数不能声明为虚函数。
- 静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。
- 内联函数(inline)不能是虚函数,因为内联函数不能在运行中动态确定位置。
- 构造函数不能是虚函数。
- 析构函数可以是虚函数,而且建议声明为虚函数。
重写(覆盖)、重载
重写(覆盖)可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性。
重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。
隐藏
除了重载与覆盖(重写),C++还有隐藏。隐藏是指派生类的函数屏蔽了与其同名的基类函数。隐藏规则如下:
- 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆,重载是在同一个类中发生)。
- 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆,覆盖有virtual关键字)。
#include<iostream>
using namespace std;
class Base
{
public:
virtual void f(float x)
{
cout<<"Base::f(float)"<< x <<endl;
}
void g(float x)
{
cout<<"Base::g(float)"<< x <<endl;
}
void h(float x)
{
cout<<"Base::h(float)"<< x <<endl;
}
};
class Derived : public Base
{
public:
//子类与基类函数同名,有virtual关键字,运行时多态
virtual void f(float x)
{
cout<<"Derived::f(float)"<< x <<endl; //多态、覆盖
}
//子类与基类函数同名,且无virtual关键字,隐藏
//参数不同的隐藏
void g(int x)
{
cout<<"Derived::g(int)"<< x <<endl; //隐藏
}
//参数相同的隐藏
void h(float x)
{
cout<<"Derived::h(float)"<< x <<endl; //隐藏
}
};
int main(void)
{
Derived d; //子类对象
Base *pb = &d; //基类类型指针,指向子类对象
Derived *pd = &d; //子类类型指针,指向子类对象
pb->f(3.14f); // Derived::f(float) 3.14 调用子类方法,多态
pd->f(3.14f); // Derived::f(float) 3.14 调用自己方法
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3
pb->h(3.14f); // Base::h(float) 3.14
pd->h(3.14f); // Derived::h(float) 3.14
return 0;
}
有virtual才可能发生多态现象,不发生多态(无virtual)调用就按原类型调用 。
上面的程序中:
(1)函数Derived::f(float)覆盖了Base::f(float),发生多态重写(覆盖)。
(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。
总结为:在基类与子类函数名相同的前提下,根据参数是否相同、是否具有vritual关键字,可分为4种情况:
- 参数相同、有virtual关键字:多态重写。
- 参数相同、无virtual关键字:隐藏;与重写区分。
- 参数不同、有virtual关键字:隐藏;与重载区分。
- 参数不同、无virtual关键字:隐藏;与重载区分