1、面向对象程序设计语言C+,电子科技大学计算机学院,1,第七章 虚函数和多态性,C+使用多态性实现同一个消息,不同接收者采取不同的响应方式的这种现象。多态性是一个事务有多种形态。在面向对象语言中,一般这样描述多态:向不同对象发送同一个消息,不同的对象在接收时会产生不同的行为。也就是说,每个对象可以用自己的方式去响应共同的消息。,2,第七章 虚函数和多态性,C+语言的多态性有两种类型:静态多态性和动态多态性。函数重载和运算符重载都是静态的多态性。在程序编译时系统就能够决定调用哪个函数,因此静态多态性又称为编译时的多态性。动态多态性时程序运行过程中才动态的确定操作所针对的对象。它又称为运行时的多态性
2、。动态多态性是通过虚函数实现的。,3,第七章 虚函数和多态性,7.1 虚函数 7.1.1静态多态性 对于普通成员函数的重载,可表达为下面的方式: (1)在同一个类中重载; (2)在不同类中重载; (3)基类的成员函数在派生类中重载; 因此,重载函数的访问是在编译时区分的,这种程序运行之前就能够在多个函数中确定当前访问的函数的方法称为静态多态性。,4,第七章 虚函数和多态性,7.1 虚函数 7.1.1静态多态性 有以下三种区分方法: 据参数的特征加以区分,例如: Show(int, char) 与 Show(char * , float) 使用”:”加以区分,例如: Circle : Show
3、有别于 Point : Show 根据类对象加以区分 ACircle.Show() 调用 Circle : Show(),5,第七章 虚函数和多态性,7.1 虚函数 7.1.1静态多态性 子类可以重载父类的成员: class A public:void fun() cout“In A“endl; ; class B : public A public:void fun() cout“In B“endl; ;,6,第七章 虚函数和多态性,7.1 虚函数 7.1.1静态多态性C Cobj;Cobj.fun(); /调用C:fun()Cobj.B:fun(); /调用B:fun()Cobj.A:fu
4、n(); /调用A:fun()A /调用A:fun(),7,第七章 虚函数和多态性,7.1 虚函数 7.1.2基类和派生类的指针与对象的关系 (1)可以用指向基类的指针指向其公有派生类的对象基类指针访问的是派生对象的拥有的基类部分, 派生类自身的部分不能被基类指针访问。指向派生类的指针指向基类的对象是不正确的(2)希望用基类指针访问其公有派生类的特定成员,必须将基类指针用显示类型转换为派生类指针。,8,第七章 虚函数和多态性,7.1 虚函数 7.1.3 虚函数与多态性 1虚函数的概念 一个指向基类的指针可用来指向从基类公有派生的任何对象。是 C+ 实现运行时多态性的关键途径。如果有多个或者多层
5、派生类,通过一个基类指针可以访问所有派生类对象的成员函数,这样就可以实现一个接口,多个实现的访问了。,9,第七章 虚函数和多态性,7.1 虚函数 7.1.3 虚函数与多态性 class Base public:Base(int a) x=a;void who() cout “base “x“n“; protected:int x; ;,10,class First_d: public Base public:First_d (int a ):Base(a) void who() cout “First derivation “ x“n“; ; class Sec_d :public Base
6、public: Sec_d (int a):Base(a) void who() cout “Second derivation “ x“n“; ;,第七章 虚函数和多态性,7.1 虚函数 7.1.3 虚函数与多态性Base * p;Base base_obj(1);First_d first_obj(2);Sec_d second_obj(3);p= 输出?,11,第七章 虚函数和多态性,7.1 虚函数 7.1.3 虚函数与多态性 程序的输出是:base 1base 2base 3调用的都是父类的函数版本,12,第七章 虚函数和多态性,7.1 虚函数 7.1.3 虚函数与多态性 通过父类指针
7、来看,该指针所指向的是父类对象。,13,第七章 虚函数和多态性,7.1 虚函数 7.1.3 虚函数与多态性如果随着p所指向的对象的不同,p-who()能调用不同类中who()的版本,这样就可以用一个界面访问多个实现版本。实际上,这表达了一种动态的性质,函数调用依赖于运行时p所指向的对象。虚函数提供的就是这种解释机制。虚函数是在基类中被冠以virtual的成员函数,它提供了一种接口界面。,14,第七章 虚函数和多态性,7.1 虚函数 7.1.3 虚函数与多态性虚函数可以在一个或多个派生类中被重新定义,但要求在派生类中重新定义时,虚函数的函数原型(包括返回类型,函数名,参数个数,参数类型的顺序)必
8、须完全相同。重写上例。,15,第七章 虚函数和多态性,7.1 虚函数 7.1.3 虚函数与多态性 class Base public:Base(int a) x=a;virtual void who() cout “base “x“n“; protected:int x; ;,16,class First_d: public Base public:First_d (int a ):Base(a) void who() cout “First derivation “ x“n“; ; class Sec_d :public Base public: Sec_d (int a):Base(a)
9、void who() cout “Second derivation “ x“n“; ;,第七章 虚函数和多态性,7.1 虚函数 2运行时的多态性与虚特性 (1)运行时的多态性在带有虚函数的类中,编译器设置一个指针,称为虚指针vpointer(缩写为VPTR)。编译器对每个包含虚函数的类创建一个虚表(称为VTABLE),存放类的虚函数地址。VPTR指向这个对象的VTABLE。通过基类指针做虚函数调用时,编译器取得这个VPTR,找到该类的VTABLE,并在其中查找相应虚函数地址,完成动态匹配。,17,第七章 虚函数和多态性,7.1 虚函数,18,单界面、多实现,class figure prot
10、ected: float x,y; public: void set_dim(float i, float j=0) x=i; y=j; virtual void show_area() cout “No area n”; ;,class triangle : public figure public: void show_area() cout x* 0.5* y “n”; ;,class square : public figure public: void show_area() cout x * y “n”; ;,class circle : public figure public:
11、 void show_area() cout 3.14 * x * x; ;,void main() figure * p; triangle t;square s;circle c; p=&t; pset_dim(10.0, 5.0); pshow_area();p=&s; pset_dim(10.0, 5.0); pshow_area(); p=&c; pset_dim(9.0); pshow_area(); ,各个派生类共享基类函数set_dim(float i, float j=0); 而对于计算面积,各个实际图形有不同的算法square、triangle和circle具有相同的界面s
12、how_area()但它们对应不同的实现方法所以,函数show_area( )定义为虚函数。,第七章 虚函数和多态性,7.1 虚函数一个成员函数什么时候需要声明为虚函数呢?首先考虑成员函数所在的类是否会做为基类。然后看成员函数在类的继承后有无功能被修改?如果希望修改其功能,一般将它声明为虚函数。还应当考虑对成员函数的调用是通过对象名还是基类指针或引用去访问。如果通过基类指针或引用去访问,则声明为虚函数。,25,第七章 虚函数和多态性,7.1 虚函数 (2)虚特性 用虚函数实现运行时多态性的关键之处是,必须用指向基类的指针(或者引用)访问虚函数。尽管可以像调用其他成员函数那样显式地用对象名来调用
13、一个虚函数,但只有在一个指向基类的指针(或者引用)访问虚函数时,运行时多态性才能实现。这时,称为函数具有虚特性。,27,第七章 虚函数和多态性,7.1 虚函数 基类函数具有虚特性的条件是: 在基类中,将该函数说明为虚(virtual)函数。这样可以在派生类中重新定义此函数,为它赋予新的功能,并能够方便调用。在类外定义虚函数时,不必再加virtual关键字。 定义基类的公有派生类; 在基类的公有派生类中原型一致地重载该虚函数; 定义指向基类的指针变量,它指向基类的公有派生类的对象(或定义基类的引用,它引用基类的公有派生类的对象)。,28,第七章 虚函数和多态性,7.1 虚函数 重载一个虚函数时,
14、要求函数名、返回类型、参量个数、参数类型和顺序是完全相同的。如果不同,会产生什么情况呢? (1)仅仅返回类型不同,其余相同。C+认为这是错误的,因为仅仅返回类型不同的函数本质上是含糊的。 (2)函数原型不同,仅函数名相同,C+认为这是一般的函数重载,此时虚特性丢失。(例7-5),29,第七章 虚函数和多态性,7.1 虚函数 定义虚函数时需要注意: (1) 虚函数必须是类的成员函数。不能将虚函数说明为全局(非成员的)函数,也不能说明为静态成员函数。不能将友元说明为虚函数,但虚函数可以是另一个类的友元。 (2) 析构函数可以是虚函数,但构造函数不能为虚函数。 (3)一旦一个函数被说明为虚函数,不管
15、经历了多少派生类层,都将保持其虚特性。,30,第七章 虚函数和多态性,7.1 虚函数 3虚析构函数 请看这个例子: class base public:base()cout“base()“endl;base()cout“base()“endl; ; class derived:public base public:derived()cout“derived()“endl;derived()cout“derived()“endl; ;,31,int main() base *pb=new derived;delete pb;return 0; ,第七章 虚函数和多态性,7.1 虚函数 请问程序的
16、输出是什么?,32,程序的输出如下: base() derived(); base(); 构造函数base()和derived()都被调用了,但是析构函数只有base()调用了。,第七章 虚函数和多态性,7.1 虚函数 原因很简单:基类指针只调用基类成员函数,不能够调用派生类成员函数,即使是析构函数也是如此。如果希望能够执行派生类的析构函数,则需要将基类的析构函数声明为虚析构函数: virtual base() cout“base()“endl; 当基类的析构函数声明为虚函数时,无论指针指向的是同一类族中的哪一个对象,当对象撤销时,系统会采用动态关联,调用相应的析构函数,对该对象进行清理工作。
17、,33,如果希望通过基类指针或者引用访问派生类成员函数,但基类功能比较抽象或者不能确定功能,可以将基类定义为抽象类,即只定义函数名字,没有函数体,具体功能由派生类添加,第七章 虚函数和多态性,7.2 纯虚函数和抽象类基类往往表示一些抽象的概念。例如,shape是一个基类,它表示具有形状的东西,从shape可以派生出封闭图形和非封闭图形两个派生类。封闭图形又可以派生出椭圆形、多边形, 这个类等级的基类shape体现了一个抽象的概念在shape中定义一个求面积的函数显然是无意义的但可以将其说明为虚函数,提供各派生类一个公共的界面,并由各派生类提供求面积函数的各自版本,35,第七章 虚函数和多态性,
18、7.2 纯虚函数和抽象类基类的有些虚函数没有定义是很正常的,但是要求派生类必须重定义这些虚函数,以使派生类有意义。为此,C+ 引入了纯虚函数的概念。纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义,要求任何派生类都必须定义自己的版本。为说明一纯虚函数,使用下列一般形式: virtual type func_name(参数表) = 0;,36,第七章 虚函数和多态性,7.2 纯虚函数和抽象类将一虚函数说明成纯虚函数,就要求任何派生类都应该定义自己的实现。在构造函数和析构函数中调用虚函数时,采用静态联编,因此,在构造函数和析构函数中不能够调用纯虚函数。但其他的成员函数可以调用纯虚函数。如果
19、一个类至少有一个纯虚函数,那么就称该类为抽象类。抽象类机制支持一般概念的表示。抽象类只能用作其他类的基类,抽象类不能建立对象。抽象类不能用作参数类型、函数返回类型或显式转换的类型。但可以声明抽象类的指针和引用。,37,第七章 虚函数和多态性,7.2 纯虚函数和抽象类 纯虚函数和抽象类的例子: class shape public: virtual void rotate( int )=0; virtual void draw( )=0; ; ,38,用面向对象方法实现一个异质链表。异质是指链表中各表项内容的类型不要求相同,(1)以大学环境为例,这里包括学生、职员和教师。希望对这些人的信息进行管
20、理(所有的人员信息记录在一个链表中)。,学生:姓名、年龄、身份证号码、平均成绩。 职员:姓名、年龄、身份证号码、小时工资。 教师:姓名、年龄、身份证号码、年工资。,要求能实现三个操作: 插入。向异质链表中增加一个学生、职员或教师的信息。 删除。从链表中删除一个学生、职员和教师的信息。 打印。显示链表中所有的信息。,(2)对于一些既是学生又是教师双重身份的人,应能单独记录之。 (3)当以上系统设计完成后,如果希望增加其他人员的派生类(如院长、校长或系主任等),望能给出相应的派生类。,class Person char * name;int age;char * id; public: void
21、out_base_inf ()coutnameageid; virtual out_self_inf()=0; ;,class Stu:virtual public Personint grade;float ave;public: void out_self_inf() cout gradeave; ;,class Tea:virtual public Personfloat year_salary;public: void out_self_inf() cout year_salary; ;,class Worker : public Person float hour_salary;public: void out_self_inf() cout hour_salary; ;,class Stu_Tea: Stu,Tea public:void out_self_inf()Stu:out_self_inf();Tea:out_self_inf(); ;,class list ; class node Person * p;node * next;int person_id;friend list; ;,class list int size;node * head; Public:insert(int id) ;,