在面向对象编程时,有时会遇到这种需求:我们希望同一个方法在基类和派生类中实现不同的功能,即体现出行为上的多态性。一般有两种方法可以实现这种需求,其一是在派生类中重新定义基类中方法,其二是使用虚函数。这里主要记录利用虚函数实现多态性的方法。
类中虚函数的定义方法
虚函数使用关键词virtual进行标识。通过一个例子加深对虚函数的理解,一个银行需要开发两类账户,一类是基本账户BaseAcct,另一类是高级账户PlusAcct,具有透支功能,多显示透支上限和透支额度信息。两个账户的基本特征为:
class BaseAcct{
private:
string fullName;
long balance;
public:
BaseAcct(const stirng &s="Nullbody", long bal=0.0);
void Deposit(double amt); //存款
virtual void Withdraw(double amt); //取款,无透支功能
virtual void ViewAcct() const;
virtual ~BaseAcct() {}; //基类析构函数最好定义为虚函数以保证正确的析构顺序
};
class PlusAcct:public BaseAcct{
private:
double maxLoan; //透支上限和透支额度
double owesBank;
public:
PlusAcct(const stirng &s="Nullbody", long bal=0.0, double ml=500, double ob=0.0);
virtual void Withdraw(double amt); //取款,有透支功能
virtual void ViewAcct() const; //多显示透支上限和透支额度
~PlusAcct() {}; //析构函数是自带默认的,可以不写
};
派生类PlusAcct中对基类BaseAcct中的虚函数Withdraw()和ViewAcct()进行改写,实现了不同的功能。可以在后续进行重写,这是虚函数的特点。但是重写时要注意,重写的函数其函数原型要保持与基类中完全相同,否则编译器将会产生warning。只有返回值可以由基类指针/引用改变为派生类指针/引用,称之为返回值协变。
上述特点与重载不同,重载中要求函数的参数列表必须不同才能实现重载。
虚函数的工作原理
编译器在处理虚函数的方法是为每一个对象增加一个隐藏成员,其中保存了一组指向虚函数数组地址的指针,一般称之为虚函数表。
动态联编与多态性
在管理BaseAcct和PlusAcct帐户时,不能用一个数组去存储BaseAcct和PlusAcct对象,因为两者的类型不同。但是却可以创建指向BaseAcct类的指针数组,然后根据需要让其指向BaseAcct或PlusAcct对象。因为编译器在处理虚函数时是采用动态联编的,即在程序运行的时候再选择正确的虚方法。而这种动态联编的特性,可以让同一个数组中的指针指向不同的基类或者派生类,并让其表现出不同的特性,即为多态性。
通过指针或者引用调用方法时,若为virtual,将根据指针或者引用指向对象选择方法;若非virtual,将根据指针或者引用本身的类型选择方法。这个特性是类对象实现多态性的基础。
祝枫
2018年10月9日