第6章多态性和虚函数课件.ppt
第第6章多态性和虚函数章多态性和虚函数第1页,此课件共27页哦多态性多态性 不同的对象接收到相同的消息时产生多种完全不同不同的对象接收到相同的消息时产生多种完全不同的行为的现象称为多态性。的行为的现象称为多态性。C+C+支持两种多态性支持两种多态性:编译时的多态性和运行时多态性。编译时的多态性和运行时多态性。编译时的多态性使用重载来获得,运行时的多态性通过编译时的多态性使用重载来获得,运行时的多态性通过使用继承和虚函数获得。使用继承和虚函数获得。C+C+语言中,重载包括函数重载和运算符重载。语言中,重载包括函数重载和运算符重载。第2页,此课件共27页哦6.1 6.1 运算符重载运算符重载第3页,此课件共27页哦1.1.运算符重载定义运算符重载定义 C+C+中预定义的运算符的操作对象只能是基本数据类型,运中预定义的运算符的操作对象只能是基本数据类型,运算符重载是对运算符进行重新定义,赋予已有符号新功能的要求。算符重载是对运算符进行重新定义,赋予已有符号新功能的要求。不能重载的运算符不能重载的运算符是:是:(1)(1)成员访问运算符成员访问运算符.(2)(2)作用域运算符作用域运算符(3)(3)条件运算符条件运算符?:?:(4)(4)成员指针运算符成员指针运算符*(5)(5)编译预处理命令的开始符号编译预处理命令的开始符号#第4页,此课件共27页哦 C+C+中的运算符除了少数几个以外,几乎全部可以重载,中的运算符除了少数几个以外,几乎全部可以重载,程序员不能定义新的运算符,只能重载已有的这些运算符。程序员不能定义新的运算符,只能重载已有的这些运算符。重载之后运算符的优先级和结合性都不能改变。重载之后运算符的优先级和结合性都不能改变。运算符重载是针对新类型数据的实际需要,对原有运算符运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。进行适当的改造。运算符重载可以使用成员函数和友元函数两种形式。运算符重载可以使用成员函数和友元函数两种形式。运算符重载的实质就是函数重载。运算符重载的实质就是函数重载。第5页,此课件共27页哦 在类定义体中声明运算符函数的形式为在类定义体中声明运算符函数的形式为:type operator type operator(参数表参数表)其中其中为为运算符符号运算符符号 若运算符是一元的,则参数表为空,此时当前对象作为此运算符若运算符是一元的,则参数表为空,此时当前对象作为此运算符的单操作数;若运算符是二元的,则参数表中有一个操作数,此时当的单操作数;若运算符是二元的,则参数表中有一个操作数,此时当前对象作为此运算符的左操作数,参数表中的操作数作为此运算符的前对象作为此运算符的左操作数,参数表中的操作数作为此运算符的右操作数。右操作数。运算符函数的定义如下:运算符函数的定义如下:type type 类名类名:operator operator(参数表)参数表)/运算符处理程序代码运算符处理程序代码 重载运算符的使用方法同原运算符一样,只是它的操作数一定要是定义重载运算符的使用方法同原运算符一样,只是它的操作数一定要是定义它的特定类的对象。它的特定类的对象。第6页,此课件共27页哦l【例【例6.16.1】用成员函数重载运算符】用成员函数重载运算符+l#includelclass pointl private:l float x,y;l public:l point(float xx=0,float yy=0)x=xx;y=yy;l float get_x()return x;l float get_y()return y;l point operator+(point q);/重载运算符重载运算符“+”l;lpoint point:operator+(point q)l return point(x+q.x,y+q.y);lvoid main()l point p1(3,3),p2(2,2),p3;/声明声明point类的对象类的对象l p3=p1+p2;或或p1.operator+(p2)/两点相加两点相加l coutp1+p2:x=p3.get_x(),y=p3.get_y()endl;l第7页,此课件共27页哦l【例【例6.6.2 2】用成员函数重载运算符用成员函数重载运算符+l#include point point:operator+()lclass point l +x;l private:+y;lfloat x,y;return*this;l public:lpoint(float xx=0,float yy=0)x=xx;y=yy;lfloat get_x()return x;lfloat get_y()return y;l point operator+();/重载前置运算符重载前置运算符“+”l;lvoid main()l point p1(10,10);l +p1;或或l coutp1:x=p1.get_x(),y=p1.get_y()endl;l第8页,此课件共27页哦6.1.3 6.1.3 用友元函数重载运算符用友元函数重载运算符friend type operator friend type operator(参数表参数表););注意友元函数不属于任何类,它没有注意友元函数不属于任何类,它没有thisthis指针指针,这与,这与成员函数完全不同。若运算符是一元的,则参数表中有一成员函数完全不同。若运算符是一元的,则参数表中有一个操作数;若运算符是二元的,则参数表中有两个操作数。个操作数;若运算符是二元的,则参数表中有两个操作数。友元运算符函数与成员运算符函数的主要区别在其参数个友元运算符函数与成员运算符函数的主要区别在其参数个数不同数不同。友元运算符函数的定义如下:友元运算符函数的定义如下:type type operator operator (参数表参数表)/运算符处理程序代码运算符处理程序代码 第9页,此课件共27页哦l【例【例6.6.3 3】用友员函数重载运算符用友员函数重载运算符+l#includelclass pointl private:l float x,y;l public:l point(float xx=0,float yy=0)x=xx;y=yy;l float get_x()return x;l float get_y()return y;l friend point operator+(point p1,point p2);/重载运算符重载运算符“+”l;lpoint operator+(point p1,point p2)l return point(p1.x+p2.x,p1.y+p2.y);lvoid main()l point p1(3,3),p2(2,2),p3;/声明声明point类的对象类的对象l p3=p1+p2;或或operator+(p1,p2)/两点相加两点相加l coutp1+p2:x=p3.get_x(),y=p3.get_y()endl;l第10页,此课件共27页哦2前自增和后自增运算符前自增和后自增运算符+的重载的重载前自增运算符前自增运算符+和后自增运算符和后自增运算符+重载的语法重载的语法 operator+();/operator+();/前前 operator+(operator+(intint);/);/后后课本课本P147P147【例【例6.76.7】用成员函数重载前自增和后自增运算符。】用成员函数重载前自增和后自增运算符。【例【例6.86.8】用友元重载前自增和后自增用友元重载前自增和后自增 第11页,此课件共27页哦6.2 6.2 虚函数虚函数第12页,此课件共27页哦 指向基类对象的指针都可以指向它的公有派生类对象,但不能指向基类对象的指针都可以指向它的公有派生类对象,但不能指向它的私有派生类对象。指向它的私有派生类对象。不能将一个声明为指向派生类对象的指针指向不能将一个声明为指向派生类对象的指针指向其基类的一个对象。其基类的一个对象。声明为指向基类对象的指针,当它指向公有派生类对象时,只能利用它声明为指向基类对象的指针,当它指向公有派生类对象时,只能利用它来直接访问派生类中从基类继承来的成员,不能直接访问公有派生类中特定来直接访问派生类中从基类继承来的成员,不能直接访问公有派生类中特定的成员。的成员。若想访问其公有派生类的特定成员,可以将基类指针显式类型若想访问其公有派生类的特定成员,可以将基类指针显式类型转换为派生类指针来实现。转换为派生类指针来实现。【例【例6.146.14】引入虚函数举例引入虚函数举例第13页,此课件共27页哦l【例【例6.6.1414】引入虚函数举例引入虚函数举例l#include lclass basel public:lvoid who()lcoutthis is the class of base!endl;lclass derive1:public basel public:lvoid who()lcoutthis is the class of derive1!endl;lclass derive2:public basel public:lvoid who()lcoutthis is the class of derive2!who();l derive1 obj2;p=&obj3;l derive2 obj3;p-who();l p=&obj1;(derive2*)p)-who();l p-who();obj2.who();l p=&obj2;obj3.who();l p-who();第14页,此课件共27页哦6.2.2 6.2.2 虚函数的定义与使用虚函数的定义与使用 1 1虚函数的定义虚函数的定义 虚函数定义是在基类中进行的(虚函数定义是在基类中进行的(virtualvirtual),虚函数提供虚函数提供了一种接口界面。了一种接口界面。在基类中的某个成员函数被声明为虚函数在基类中的某个成员函数被声明为虚函数后,在派生类中重新定义虚函数时,都必须与基类中的后,在派生类中重新定义虚函数时,都必须与基类中的原型原型完全相同完全相同。虚函数是一种非静态的成员函数,说明虚函数的方虚函数是一种非静态的成员函数,说明虚函数的方法如下:法如下:virtual virtual 类型函数名(参数表)类型函数名(参数表)第15页,此课件共27页哦2.2.虚函数与重载函数的关系虚函数与重载函数的关系(1)(1)重载函数要求函数有相同的函数名称,但是形参的个重载函数要求函数有相同的函数名称,但是形参的个数或者类型不应相同;而虚函数则要求函数名、返回值类数或者类型不应相同;而虚函数则要求函数名、返回值类型和参数完全相同;型和参数完全相同;(2)(2)重载函数可以是成员函数或友员函数,而虚函数只能重载函数可以是成员函数或友员函数,而虚函数只能是成员函数;是成员函数;(3)(3)重载函数的调用是以所传递参数序列的差别作为调用不重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据;虚函数是根据对象的不同去调用不同类的虚同函数的依据;虚函数是根据对象的不同去调用不同类的虚函数;函数;(4)(4)虚函数在运行时表现出多态功能,这是虚函数在运行时表现出多态功能,这是C+C+的精髓;而的精髓;而重载函数则在编译时表现出多态性。重载函数则在编译时表现出多态性。第16页,此课件共27页哦l【例【例6.6.1515】l#include void main()lclass base l base obj1,*ptr;lpublic:derive obj2;lvirtual void f1()ptr=&obj1;lcoutf1 function of basef1();lvirtual void f2()ptr-f2();lcoutf2 function of basef3();lvirtual void f3()ptr=&obj2;lcoutf3 function of basef1();lvoid f4()ptr-f2();lcoutf4 function of basef4();l;lclass derive:public basel void f1()/仍为虚函数仍为虚函数lcoutf1 function of derive!endl;lvoid f2(int x)/丢失虚特性丢失虚特性lcoutf2 function of deriveendl;l/int f3()错误,只有返回类型不同错误,只有返回类型不同l/coutf3 function of deriveendl;lvoid f4()lcoutf4 function of deriveendl;l;第17页,此课件共27页哦3 3虚函数的使用虚函数的使用 定义一个基类的对象指针或引用便可使其在需要时指向定义一个基类的对象指针或引用便可使其在需要时指向相应的派生类对象,可以调用该对象所对应的类中已被相应的派生类对象,可以调用该对象所对应的类中已被“虚虚拟化拟化”的函数,从而实现真正的运行时的多态性。的函数,从而实现真正的运行时的多态性。【例【例6.166.16】虚函数的使用举例。虚函数的使用举例。注意:在构造函数中出现虚函数,虚函数的调用采用静注意:在构造函数中出现虚函数,虚函数的调用采用静态联编,即它们所调用的虚函数是基类中定义的函数而态联编,即它们所调用的虚函数是基类中定义的函数而不是在任何派生类中重定义的函数。不是在任何派生类中重定义的函数。第18页,此课件共27页哦l【例【例6.6.1616】虚函数的使用举例虚函数的使用举例l#include void main()lclass A l A a;l public:B b;l A()A*p=&b;l coutthe constructor of class Af();l f();p-g();l virtual void f()p-h();l coutA:f()endl;a.f();l void g()a.g();l coutA:g()endl;a.h();l void h()b.f();l coutA:h()endl;b.g();l f();g();b.h();l;lclass B:public All public:l void f()l coutB:f()endl;l void g()l coutB:g()endl;l;第19页,此课件共27页哦4.4.在构造函数和析构函数中调用虚函数在构造函数和析构函数中调用虚函数 编译系统对构造函数和析构函数中调用虚函编译系统对构造函数和析构函数中调用虚函数采用静态联编,即它们所调用的虚函数是自己数采用静态联编,即它们所调用的虚函数是自己的类或基类中定义的函数而不是在任何派生类中的类或基类中定义的函数而不是在任何派生类中重定义的函数。重定义的函数。使用对象调用虚函数也采用静态联编。使用对象调用虚函数也采用静态联编。第20页,此课件共27页哦l【例【例6.6.1818】在构造函数中调用虚函数。在构造函数中调用虚函数。l#include lclass basel public:lbase()coutconstruct baseendl;lvirtual void vf()l coutbase:vf()calledendl;l;lclass son:public basel public:lson()vf();lvoid g()vf();l;lclass grandson:public sonl public:l grandson()coutconstruct grandsonendl;l void vf()l coutgrandson:vf()calledn;l;lvoid main()l grandson gs;l gs.g();第21页,此课件共27页哦 6.3 6.3 纯虚函数和抽象类纯虚函数和抽象类6.3.1 6.3.1 纯虚函数的概念纯虚函数的概念 纯虚函数是一种没有具体实现的特殊的虚函数。纯虚函纯虚函数是一种没有具体实现的特殊的虚函数。纯虚函数的定义格式如下:数的定义格式如下:virtual virtual 类型类型(函数名函数名)()(参数表参数表)=0)=0;第22页,此课件共27页哦6.3.2 6.3.2 抽象类的概念抽象类的概念 1 1抽象类和具体类的概念抽象类和具体类的概念 如果一个类至少有一个纯虚函数,那么就称该类为如果一个类至少有一个纯虚函数,那么就称该类为抽象类抽象类。抽象类的主要作用是为其所组织的继承层次结构抽象类的主要作用是为其所组织的继承层次结构提供一个公共的基类,其它类可以从它这里继承和实提供一个公共的基类,其它类可以从它这里继承和实现接口,纯虚函数的实现由其具体的派生类来提供。现接口,纯虚函数的实现由其具体的派生类来提供。第23页,此课件共27页哦2 2对抽象类的规定对抽象类的规定(1)(1)抽象类只能作为基类来派生新类,不能建立抽象抽象类只能作为基类来派生新类,不能建立抽象类的对象。类的对象。(2)2)可以声明指向抽象类的指针和引用,此指针可以指可以声明指向抽象类的指针和引用,此指针可以指向它的公有派生类,进而实现多态性。向它的公有派生类,进而实现多态性。第24页,此课件共27页哦l【例【例6.6.2020】计算各类形状的总面积计算各类形状的总面积l#include float total(shape*s,int n)lclass shape /抽象类的定义抽象类的定义 l public:float sum=0;lvirtual float area()=0;for(int i=0;iarea();l protected:return sum;l float h,w;l public:void main()ltriangle(float hh,float ww)lh=hh;w=ww;shape*s4;lfloat area()s0=new triangle(3,4);lreturn h*w*0.5;s1=new rectangle(2,4);lclass rectangle:public triangle /矩形类矩形类 s2=new circle(5);l public:s3=new circle(8);lrectangle(float h,float w):triangle(h,w)float sum=total(s,4);lfloat area()coutsumendl;lreturn h*w;lclass circle:public shape /圆类圆类l private:l float radius;l public:lcircle(float r)radius=r;lfloat area()l return radius*radius*3.14;第25页,此课件共27页哦6.4 6.4 虚析构函数虚析构函数 虚析构函数的声明语法如下:虚析构函数的声明语法如下:virtual virtual 类名类名【例【例6.6.2121】虚析构函数的使用虚析构函数的使用第26页,此课件共27页哦l#include void main()l#include lclass base base*px=new derive(5,7,base,derive);l char*p;delete px;lpublic:l base(int sz,char*bptr)l p=new char sz;lstrcpy(p,bptr);lcoutconstructor baseendl;l virtual base()l delete p;l cout destructor basen;l;lclass derive:public basel char*pp;lpublic:l derive(int sz1,int sz2,char*bp,char*dptr):base(sz1,bp)l pp=new char sz2;lstrcpy(pp,dptr);lcoutconstructor deriveendl;lderive()l delete pp;l cout destructor deriven;第27页,此课件共27页哦