4.1 C++多态的概念及前提条件
参考:http://www.weixueyuan.net/view/6370.html
总结:
而多态的功能则是将函数名动态绑定到函数入口地址,这样的动态绑定过程称为运行期绑定。
而在运行期绑定的函数我们称其是多态的。
通过基类类型的指针根据所指向对象的类型来自动决定调用基类还是派生类的display函数
要想形成多态必须具备以下三个条件:
- 必须存在继承关系;
- 继承关系中必须有同名的虚函数;
- 存在基类类型的指针或引用,通过该指针或引用调用虚函数。
在C++程序中,程序的每一个函数在内存中会被分配一段存储空间,而被分配的存储空间的起始地址则为函数的入口地址。例如我们在设计一个程序时都必须为程序设计一个主函数,主函数同样会在内存中被分配一段存储空间,这段存储空间的起始地址即为函数的入口地址。
在前面的所有列举的程序中,函数的入口地址与函数名是在编译时进行绑定的,我们称之为编译期绑定,而多态的功能则是将函数名动态绑定到函数入口地址,这样的动态绑定过程称为运行期绑定,换句话来说就是函数名与函数入口地址在程序编译时无法绑定到一起,只有等运行的时候才确定函数名与哪一个函数入口绑定到一起。
那么多态到底有什么用处呢?我们不妨来看个例子。在windows操作系统中,我们经常会进行一些关闭操作,比如关闭文件夹、关闭文本文件、关闭播放器窗口等,这些关闭动作对应的函数close假设都继承自同一个基类,但是每一个类都需要有自己的一些特殊功能,比如清理背景、清除缓存等工作,如此一来当我们执行close函数时,我们当然希望根据当前所操作的窗口类型来决定该执行哪一个close函数,因此运行期绑定就可以派上用场了。
编译期绑定是指在程序编译时就将函数名与函数入口地址绑定到一起,运行期绑定是指在程序运行时才将函数名与函数入口地址绑定到一起,而在运行期绑定的函数我们称其是多态的。
为了说明虚函数的必要性,我们先来看一个示例程序。
例1:
#include<iostream> using namespace std; class base { public: void display(){cout<<"I'm base class!"<<endl;} }; class derived: public base { public: void display(){cout<<"I'm derived class!"<<endl;} }; int main() { base * p; derived test; p = &test; p->display(); return 0; }
这个例子非常简单,两个类,一个是base类,一个是derived类,二者构成继承关系,同时在这两个类中均含有一个display函数,因为函数同名,故在派生类对象中会出现遮蔽现象,即派生类中的display函数会遮蔽基类中的display函数。
在主函数中,定义了一个基类类型的指针p和派生类对象test,之后p指针指向派生类对象test,然后通过指针调用display函数。此程序最终运行结果如下:
I’m base class!
从结果来看这个程序最终调用的display函数是基类的display函数,而非派生类中的display函数。但此程序的本意是先通过基类类型的指针根据所指向对象的类型来自动决定调用基类还是派生类的display函数。为了实现这样的一种功能,C++提供了多态这一机制。
要想形成多态必须具备以下三个条件:
- 必须存在继承关系;
- 继承关系中必须有同名的虚函数;
- 存在基类类型的指针或引用,通过该指针或引用调用虚函数。
根据这三个条件,我们将例1进行修改,使display函数具有多态特性。修改后程序如例2 所示。
例2:
#include<iostream> using namespace std; class base { public: virtual void display(){cout<<"I'm base class!"<<endl;} }; class derived: public base { public: virtual void display(){cout<<"I'm derived class!"<<endl;} }; int main() { base * p; derived test; p = &test; p->display(); return 0; }
例2所示程序相对于例1只是在display函数前各添加了一个virtual关键字。我们对照三个多态的构成条件来分析一下,多态首先需要继承关系,derived类继承自base类,因此base类和derived类构成继承关系;其次多态需要同名的虚函数,base类和derived类中都有display函数,同名满足,同时通过添加关键字virtual后,display函数成为虚函数;最后多态需要通过基类类型的指针或引用来调用虚函数,在例2中的主函数中,p即为基类类型指针,并且将该指针指向派生类对象,然后调用display函数。这段程序最终运行结果如下:
I’m derived class!
例2这个程序展示出来的就是多态,display函数通过virtual关键字声明为虚函数,具有多态特性。我们将例2中的主函数修改成以下形式再来分析一下函数运行结果。
int main() { base * p = new base; p->display(); delete p; p = new derived; p->display(); delete p; return 0; }
在这个主函数中同样是声明一个基类类型的指针,之后通过new给指针分配一个基类对象,通过p指针调用display函数,此时不用说肯定是输出“I’m base class!”,因为这中间一直没有涉及到派生类的事情。之后销毁之前new分配的base对象,然后通过new分配一个derived类对象,p指向该派生类对象,通过p指针调用display函数,此时的情况和例2是完全相同的,因此输出“I’m derived class!”,之后再delete销毁派生类对象。修改主函数之后程序输出结果如下:
I’m base class!
I’m derived class!
这样的输出结果与我们的分析结果是一致的。