多态性的概念
多态性(polymorphism)是面向对象程序设计的一个重要特征,利用多态性可以设计和实现一个易于扩展的系统.
在c++程序设计中,多态性是指具有不同功能的函数可以用一个函数名,这样就可以用一个函数名调用不同内容的函数.
在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息.
从系统实现的角度看,多态性分为两类:静态多态性和动态多态性。
以前学过的函数重载和运算符重载实现的多态性属性静态多态性,在程序编译系统就能决定调用的是哪个函数,因此静态多态性又称为编译时的多态性。静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)。
动态多态性是程序运行过程中才动态的确定操作所针对的对象。它又称运行时多态性,动态多态性是通过虚函数(virtual function)实现的。
虚函数
虚汗的作用
在类的继承层次结构中,在不同的层次中可以出现名字相同,参数个数和类型都相同而功能不同的函数。系统根据同名覆盖的原则决定调用的对象。
虚函数的作用是允许在派生类中重新定义域基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。
虚函数的使用方法
1)在基类用virtual声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便的被调用,在类外定义虚函数时,不必再加virtual
2)在派生类中重新定义此函数,要求函数名,函数类型,函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数,因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但是习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。
如果在派生类中没有对基类的虚函数重新定义,则派生类简单的继承其直接基类的虚函数。
3)定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
4)通过该指针变量调用此虚函数,此时调用的就是指针变量执行的对象的同名函数。
通过虚函数于指向基类对象的指针变量的配合使用,就能方便的调用同一类族中不同类的同名函数,只要先用基类指针指向即可。如果指针不断的指向同一类族中不同类的对象,就能不断的调用这些对象中的同名函数。
需要说明的是;有时在基类定义的非虚函数会在派生类中被重新定义,如果用基类指针调用该成员函数,则系统会调用对象中基类部分的成员函数;如果用派生类指针调用该成员函数,则系统会调用派生类中对象的成员函数,这并不是多态性行为(使用了不同类型的指针),没有用到虚函数的功能。
以前介绍的函数重载是同一层次上同名函数问题,而虚函数处理的是不同派生层次上的同名函数问题,前者是横向重载,后者可以理解为纵向重载,但与重载不同的是:同一类族的虚函数的首部是相同的,而函数重载时函数的首部是不同的(参数个数和类型不同)
在声明情况下应当声明虚函数
使用虚函数时,有两点要注意:
1)只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数,因为虚函数的作用是允许在派生类中对基类的虚函数重新定义,显然,它只能用于类的继承层次结构中。
2)一个成员函数被声明为虚函数后,在同一类族中的类就不用再定义一个非virtual的但与该虚函数具有相同参数(包括个数和类型)和函数返回值类型的同名函数。
需要说明的是:使用虚函数,系统要有一定的空间开销,当一个类带有虚函数时,编译系统就会为该类构造一个虚函数表(virtual function table,简称vtable),它是一个指针数组,存放每个虚函数的入口地址,系统在进行多态关联时的时间开销很少的,因此,多态是高效的。
虚析构函数
析构函数的作用是在对象撤销之前做必要的"清理现场"的工作,当派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数。
但是如果用new运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量,在程序用带指针参数的delete运算符撤销对象时,会发生一个情况,系统会只执行基类的析构函数,而不会指向派生类的析构函数。
当基类的析构函数为虚函数时,无论指针指向的是同一类族中的哪一个对象,系统会采用动态关联,调用相应的析构函数,对该对象进行清理工作。
如果将基类的析构函数声明为虚函数,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类析构函数名字不相同。
最好把基类的析构函数声明为虚函数,这样使所有派生类的析构函数都自动成为虚函数,这样,如果程序中显示的用delete运算符准备删除一个对象,而delete运算符操作的对象用了指向派生类对象的基类指针,则系统会调用相应类的析构函数
虚析构函数的概念和用法很简单,但它在面向对象程序设计中却是很重要的技巧。专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显示声明一个函数体为空的虚析构函数。以保证在撤销动态分配空间时能得到正确的处理
纯虚函数和抽象类
纯虚函数
有时在基类中将某一成员函数声明为虚函数,并不是基类本身的需求,而是考虑到派生类的需要,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义。
纯虚函数是在声明虚函数时被初始化为0的函数,声明纯虚函数的一般形式为
virtual 函数类型 函数名(参数列表) = 0;
注意:1)纯虚函数没有函数体
2)最后的"=0"并不代表函数的返回值为0,它只起形式上的作用,告诉编译系统这是纯虚函数
3)这是一个声明语句,最后应该有分号
纯虚函数只有函数名而不具备函数的功能,不能被调用,它只是通知编译系统,在这里声明一个虚函数,留待派生类中定义。
在派生类中提供定义后,它才能具备函数的功能,可被调用
纯虚函数的作用是在基类中为派生类保留一个函数的名字,以便派生类根据需要对它进行定义。
如果在基类中没有保留函数名字,则无法实现多态性。
如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,在该虚函数在派生类中仍然为纯虚函数。
抽象类
如果声明了一个类,一般可以用它定义对象,但是在面向对象程序设计中,往往有一些类,他们不用来生成对象,定义这些类的唯一目的是用它作为基类去建立派生类。
这种不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstract class),由于它常用作基类。通常成为抽象基类。
凡是包含纯虚函数的类都是抽象类,因为纯虚函数是不能被调用的,保护纯虚函数的类是无法建立对象的。抽象类的作用是作为一个类族的公共基类,或者说为一个类族提供一个公共接口.
虽然抽象类不能定义对象(或者说抽象类不能实例化),但是可以可以指向抽象类数据的指针变量。
当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后如果该指针调用虚函数,实现多态的操作。
如果在基类声明了虚函数,则在派生类中凡是与该函数有相同的函数名,函数类型,参数个数和类型的函数,均为虚函数(不论在派生类中是否用virtual声明)