《C备课讲义教程.ppt》由会员分享,可在线阅读,更多相关《C备课讲义教程.ppt(37页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、2虚函数虚函数多态性:多态性:调用同一个函数名,可以根据需要调用同一个函数名,可以根据需要但实现不同的功能。但实现不同的功能。多态性是面向对象的程序设计的关键技术。多态性是面向对象的程序设计的关键技术。编译时的多态性(函数重载)编译时的多态性(函数重载)运行时的多态性运行时的多态性(虚函数)(虚函数)多态性多态性运行时的多态性是指在程序执行之前,根据函数名和参数运行时的多态性是指在程序执行之前,根据函数名和参数无法确定应该调用哪一个函数,必须在程序的执行过程中,无法确定应该调用哪一个函数,必须在程序的执行过程中,根据具体的执行情况来动态地确定根据具体的执行情况来动态地确定3可以将一个派生类对象
2、的地址赋给基类的指针变可以将一个派生类对象的地址赋给基类的指针变量量。基类对象基类对象派生类对象派生类对象Base b;Derive d;Base *basep;basepbasep=&b;basepbasep = &d;basep只能引用从基只能引用从基类继承来的成员。类继承来的成员。xShow()xShow()yShow()basep -Show();basep-Show()基类指针基类指针派生类对象派生类对象基类对象基类对象4class Pointfloat x,y;public: Point()Point(float i,float j)x=i;y=j;float area(void)
3、 return 0.0;const float Pi=3.14159;class Circle:public Point/类类Point的派生类的派生类float radius;public: Circle(float r)radius=r;float area(void) return Pi*radius*radius;void main(void) Point *pp; /基类指针,可以将派生类对象的地址赋给基类指针基类指针,可以将派生类对象的地址赋给基类指针 Circle c(5.4321); pp=&c; coutarea ()Show()Base *basep;basep=&b;ba
4、sep = &d;basep -Show();即指向派生类新增的成员函数即指向派生类新增的成员函数需要将基类中的需要将基类中的Show()说明为虚函数说明为虚函数6若要访问派生类中相同名字的函数,必须将若要访问派生类中相同名字的函数,必须将基类中的基类中的同名函数定义为虚函数同名函数定义为虚函数,这样,将,这样,将不同的派生类对象的地址赋给基类的指针变不同的派生类对象的地址赋给基类的指针变量后,就可以量后,就可以动态地根据这种赋值语句调用动态地根据这种赋值语句调用不同类中的函数不同类中的函数。7class Point float x,y;public: Point()Point(float i
5、,float j)x=i;y=j;virtual float area(void) return 0.0; ;const float Pi=3.14159;class Circle:public Point/类类Point的派生类的派生类float radius;public: Circle(float r)radius=r;float area(void) return Pi*radius*radius;void main(void) Point *pp; /基类指针,可以将派生类对象的地址赋给基类指针基类指针,可以将派生类对象的地址赋给基类指针 Circle c(5.4321); pp=&
6、c; coutarea ()endl; /调用虚函数调用虚函数将将area()声明为虚函数,编译器对其进行动态聚束,按照实际对象声明为虚函数,编译器对其进行动态聚束,按照实际对象c调用了调用了Circle中的函数中的函数area()。使。使Point类中的类中的area()与与Circle类中的类中的area()有一个统一有一个统一的接口。的接口。输出:输出:92.7011声明为虚函数声明为虚函数调用虚函数调用虚函数虚函数再定义虚函数再定义8虚函数的定义和使用虚函数的定义和使用 可以在程序运行时通过调用相同的函数名而实可以在程序运行时通过调用相同的函数名而实现不同功能的函数称为虚函数。现不同功
7、能的函数称为虚函数。定义格式为:定义格式为:virtual FuncName();一旦把基类的成员函数定义为虚函数,由基类所一旦把基类的成员函数定义为虚函数,由基类所派生出来的所有派生类中,该函数均保持虚函数派生出来的所有派生类中,该函数均保持虚函数的特性。的特性。 在派生类中重新定义基类中的虚函数时,可以不在派生类中重新定义基类中的虚函数时,可以不用关键字用关键字virtual来修饰这个成员函数来修饰这个成员函数 。9虚函数是用关键字虚函数是用关键字virtual修饰的某基类中的修饰的某基类中的protected或或public成员函数。它可以在派生成员函数。它可以在派生类中重新定义,以形成
8、不同版本。类中重新定义,以形成不同版本。只有在程只有在程序的执行过程中,依据指针具体指向哪个类序的执行过程中,依据指针具体指向哪个类对象,或依据引用哪个类对象,才能确定激对象,或依据引用哪个类对象,才能确定激活哪一个版本,实现动态聚束活哪一个版本,实现动态聚束。10class Aprotected:int x;public: A()x =1000; virtual void print()cout “x=”xt;/虚函数虚函数;class B:public Aint y;public: B() y=2000;void print()cout “y=”yt;/派生虚函数派生虚函数;class C
9、:public Aint z;public: C()z=3000;void print()cout “z=”zprint();/调用类调用类A的虚函数的虚函数 pa=&b; pa-print();/调用类调用类B的虚函数的虚函数 pa=&c; pa-print();/调用类调用类C的虚函数的虚函数x=1000y=2000z=3000 x=1000y=2000z=300011class Base public : virtual int Set(int a, int b) . .;class Derive:public Basepublic : int Set(int x, int y) . .
10、;class Base public : virtual int Set(int a, int b) . .;class Derive:public Basepublic : int Set(int x, int y=0) . .;int Set(int ,int )是虚函数是虚函数两个两个Set()函数参数函数参数不一致,是重载,不一致,是重载,不是虚函数不是虚函数12关于虚函数,说明以下几点:关于虚函数,说明以下几点:1、当在基类中把成员函数定义为虚函数后,当在基类中把成员函数定义为虚函数后,在其派生类中定义的虚函数必须与基类中的在其派生类中定义的虚函数必须与基类中的虚函数同名,参数的类型
11、、顺序、参数的个虚函数同名,参数的类型、顺序、参数的个数必须一一对应,函数的返回的类型也相同数必须一一对应,函数的返回的类型也相同。若函数名相同,但参数的个数不同或者参数若函数名相同,但参数的个数不同或者参数的类型不同时,则属于函数的重载,而不是的类型不同时,则属于函数的重载,而不是虚函数。若函数名不同,显然这是不同的成虚函数。若函数名不同,显然这是不同的成员函数。员函数。132、实现这种动态的多态性时,必须使用实现这种动态的多态性时,必须使用基类类型基类类型的指针变量或基类引用的指针变量或基类引用,并使该指针或引用,并使该指针或引用指向指向不同的派生类对象不同的派生类对象,并通过调用指针或引
12、用所指,并通过调用指针或引用所指向的虚函数才能实现动态的多态性。向的虚函数才能实现动态的多态性。xShow()xShow()yShow()xShow()zShow()类类A类类B类类CShow()定义为虚函数定义为虚函数类类B与类与类C均为类均为类A的公有派生。的公有派生。A *p; B b;C c; p=&b ; p-Show();p=&c ; p-Show();即在程序运行时,即在程序运行时,通过赋值语句实通过赋值语句实现多态性现多态性143、虚函数必须是类的一个成员函数,不能是虚函数必须是类的一个成员函数,不能是友元函数,也不能是静态的成员函数。友元函数,也不能是静态的成员函数。4、在派
13、生类中、在派生类中没有重新定义虚函数没有重新定义虚函数时,与一时,与一般的成员函数一样,当调用这种派生类对象般的成员函数一样,当调用这种派生类对象的虚函数时,的虚函数时,则调用其基类中的虚函数则调用其基类中的虚函数。5、可把析构函数定义为虚函数,但是,不能可把析构函数定义为虚函数,但是,不能将构造函数定义为虚函数将构造函数定义为虚函数。156、虚函数与一般的成员函数相比较,、虚函数与一般的成员函数相比较,调用时的执调用时的执行速度要慢一些行速度要慢一些。为了实现多态性,在每一个派。为了实现多态性,在每一个派生类中均要保存相应虚函数的入口地址表,函数生类中均要保存相应虚函数的入口地址表,函数的调
14、用机制也是间接实现的。因此,除了要编写的调用机制也是间接实现的。因此,除了要编写一些通用的程序,并一定要使用虚函数才能完成一些通用的程序,并一定要使用虚函数才能完成其功能要求外,通常不必使用虚函数。其功能要求外,通常不必使用虚函数。7、一个函数如果被定义成虚函数,则不管经历多、一个函数如果被定义成虚函数,则不管经历多少次派生,仍将保持其虚特性,以实现少次派生,仍将保持其虚特性,以实现“一个接一个接口,多个形态口,多个形态”。16虚函数的访问虚函数的访问用基类指针或基类引用访问与用派生类对象用基类指针或基类引用访问与用派生类对象名访问名访问用基类指针或基类引用访问虚函数时,指向用基类指针或基类引
15、用访问虚函数时,指向其实际派生类对象重新定义的函数。实现动其实际派生类对象重新定义的函数。实现动态聚束。态聚束。通过一个通过一个派生类对象名派生类对象名访问时,只能静态聚访问时,只能静态聚束。即由编译器在编译的时候决定调用哪个束。即由编译器在编译的时候决定调用哪个函数。函数。17class Point float x,y;public: Point()Point(float i,float j)x=i;y=j;virtual float area(void) return 0.0; /声明为虚函数声明为虚函数;const float Pi=3.14159;class Circle:public
16、 Point/类类Point的派生类的派生类float radius;public: Circle(float r)radius=r;float area(void) return Pi*radius*radius;/虚函数再定义虚函数再定义;void main(void) Point *pp; /基类指针,可以将派生类对象的地址赋给基类指针基类指针,可以将派生类对象的地址赋给基类指针 Circle c(5.4321); coutc.area()endl; coutc.Point:area()endl; coutc.Circle:area ()endl;输出:输出:92.7011092.701
17、1可见,利用对象名进行调用与一般非虚函数没有区别。可见,利用对象名进行调用与一般非虚函数没有区别。用对象名调用用对象名调用area( )18class base0public: void v(void)coutbase0n;class base1:public base0public: virtual void v(void) coutbase1n; ;class A1:public base1public: void v()coutA1n; ;class A2:public A1public: void v(void)coutA2n; ;class B1:private base1publi
18、c: void v(void)coutB1n; ;class B2:public B1public: void v(void)coutv(); A2 a2; (pb=&a2)-v(); B1 b1; (pb=&b1)-v(); B2 b2; (pb=&b2)-v();base0base0私有派生,在类外私有派生,在类外不能调用基类函数不能调用基类函数19class base0public: void v(void)coutbase0n;class base1:public base0public: virtual void v(void) coutbase1n; ;class A1:publi
19、c base1public: void v()coutA1n; ;class A2:public A1public: void v(void)coutA2n; ;class B1:private base1public: void v(void)coutB1n; ;class B2:public B1public: void v(void)coutv(); A2 a2; (pb=&a2)-v();A1A220下面程序的输出是下面程序的输出是 。class Aprotected:int x;public:A()x =1000;virtual void p()cout x=xn; p2(); vi
20、rtual void p2()coutA:p2()endl; ;class C:public Aint z;public:C()z=3000; void p()cout z=zn; p2();virtual void p2()coutC:p2()p();pa=&c;pa-p();x=1000z=3000C:p2()A:p2()21class B0class B0public:public: virtual void display()cout B0:display()n; virtual void display()cout B0:display()n; ;class B1:public B0
21、class B1:public B0public:public: void display()cout B1:display()n; void display()cout B1:display()n; ;class D:public B1class D:public B1public:public: void display()cout D:display()n; void display()cout display(); p-display(); void main()void main() B0 b0, B0 b0, * *p; p; p = &b0; fun(p); p = &b0; f
22、un(p); B1 b1; p = &b1; B1 b1; p = &b1; fun(p); fun(p); D d; p = &d; D d; p = &d; fun(p); fun(p); B0:display()B0:display()B1:display()B1:display()D:display()D:display()22class B0class B0public:public: virtual void display()cout B0:display()n; virtual void display()cout B0:display()n; ;class B1:public
23、 B0class B1:public B0public:public: void display()cout B1:display()n; void display()cout B1:display()n; ;class D:public B1class D:public B1public:public: void display()cout D:display()n; void display()cout D:display()n; ;void fun(void fun(B0 &bbB0 &bb) ) bb.display(); bb.display(); void main()void m
24、ain() B0 b0; B0 b0; fun(b0); fun(b0); B1 b1; B1 b1; fun(b1); fun(b1); D d; D d; fun(d); fun(d); B0:display()B0:display()B1:display()B1:display()D:display()D:display()23纯虚函数纯虚函数在基类中不对虚函数给出有意义的实现在基类中不对虚函数给出有意义的实现,它只是它只是在派生类中有具体的意义。这时基类中的虚函数在派生类中有具体的意义。这时基类中的虚函数只是一个入口,具体的目的地由不同的派生类中只是一个入口,具体的目的地由不同的派生类
25、中的对象决定。这个虚函数称为的对象决定。这个虚函数称为纯虚函数纯虚函数。class virtual ()=0;.;24class Aprotected:int x;public: A()x =1000; virtual void print()=0; /定义纯虚函数定义纯虚函数;class B:public A /派生类派生类private: int y;public: B() y=2000;void print()cout “y=”yn;/重新定义纯虚函数重新定义纯虚函数;class C:public A /派生类派生类int z;public: C()z=3000;void print()
26、cout “z=”zprint(); pa=&c; pa-print(); A a; pa=&a; pa-print( );y=2000z=3000抽象类抽象类不能定义抽象类的对象不能定义抽象类的对象251、在定义纯虚函数时,不能定义虚函数的实、在定义纯虚函数时,不能定义虚函数的实现部分。现部分。2、把函数名赋于、把函数名赋于0,本质上是将指向函数体,本质上是将指向函数体的指针值赋为初值的指针值赋为初值0。与定义空函数不一样,。与定义空函数不一样,空函数的函数体为空,即调用该函数时,不空函数的函数体为空,即调用该函数时,不执行任何动作。执行任何动作。在没有重新定义这种纯虚函在没有重新定义这种纯
27、虚函数之前,是不能调用这种函数的。数之前,是不能调用这种函数的。263、把至少包含一个纯虚函数的类,称为抽象把至少包含一个纯虚函数的类,称为抽象类。这种类只能作为派生类的基类,不能用类。这种类只能作为派生类的基类,不能用来说明这种类的对象来说明这种类的对象。其理由是明显的:因为虚函数没有实现部分,其理由是明显的:因为虚函数没有实现部分,所以不能产生对象。但可以定义指向抽象类所以不能产生对象。但可以定义指向抽象类的指针,即指向这种基类的指针。当用这种的指针,即指向这种基类的指针。当用这种基类指针指向其派生类的对象时,基类指针指向其派生类的对象时,必须在派必须在派生类中生类中重载重载纯虚函数,否则
28、会产生程序的运纯虚函数,否则会产生程序的运行错误。行错误。274、在以抽象类作为基类的派生类中必须有在以抽象类作为基类的派生类中必须有纯虚函数的实现部分,即必须有重载纯虚函纯虚函数的实现部分,即必须有重载纯虚函数的函数体。否则,这样的派生类也是不能数的函数体。否则,这样的派生类也是不能产生对象的。产生对象的。综上所述,可把纯虚函数归结为:综上所述,可把纯虚函数归结为:抽象类的抽象类的唯一用途是为派生类提供基类,纯虚函数的唯一用途是为派生类提供基类,纯虚函数的作用是作为派生类中的成员函数的基础,并作用是作为派生类中的成员函数的基础,并实现动态多态性。实现动态多态性。28虚基类虚基类多基派生中的多
29、条路径具有公共基类时,在这条路径的汇合处就会多基派生中的多条路径具有公共基类时,在这条路径的汇合处就会因对公共基类产生多个拷贝而产生同名函数调用的二义性。因对公共基类产生多个拷贝而产生同名函数调用的二义性。解决这个问题的办法就是把解决这个问题的办法就是把公共基类定义为虚基类公共基类定义为虚基类,使由它派生的,使由它派生的多条路径的汇聚处只产生一个拷贝。多条路径的汇聚处只产生一个拷贝。class Base ;class A : public Base ;class B: public Base ;class C: public A, public B ;类类C中继承了两个类中继承了两个类Base
30、,即有两个类即有两个类Base的实现部分,的实现部分,在调用时产生了二义性。在调用时产生了二义性。29用虚基类进行多重派生时,用虚基类进行多重派生时,若虚基类没有缺若虚基类没有缺省的构造函数省的构造函数,则在每一个派生类的构造函,则在每一个派生类的构造函数中数中都必须有对虚基类构造函数的调用都必须有对虚基类构造函数的调用 (且(且首先调用)。首先调用)。由虚基类派生出的对象初始化时,由虚基类派生出的对象初始化时,直接调用直接调用虚基类的构造函数。因此,若将一个类定义虚基类的构造函数。因此,若将一个类定义为虚基类,则一定有正确的构造函数可供所为虚基类,则一定有正确的构造函数可供所有派生类调用。有
31、派生类调用。30class basepublic:virtual void a() couta() in basen;virtual void b() coutb() in basen;virtual void c() coutc() in basen;virtual void d() coutd() in basen;virtual void e() coute() in basen;virtual void f() coutf() in basen;class A:public basepublic:virtual void a() couta() in An;virtual void b
32、() coutb() in An;virtual void f() coutf() in An;class B:public basepublic:virtual void a() couta() in Bn;virtual void b() coutb() in Bn;virtual void c() coutc() in Bn;class C:public A,public Bpublic:virtual void a() couta() in Cn;virtual void d() couta(); pa-b(); pa-c(); pa-d(); pa-e(); pa-f();将类将类C
33、的地址赋的地址赋值时产生歧义值时产生歧义31a( )b( )c( )d( )e( )f( )a( )b( )c( )d( )e( )f( )a( )b( )f( )a( )b( )c( )d( )e( )f( )a( )c( )a( )b( )c( )d( )e( )f( )a( )b( )f( )a( )b( )c( )d( )e( )f( )a( )c( )baseABCa( )d( )AB32class basepublic:virtual void a() couta() in basen;virtual void b() coutb() in basen;virtual void c
34、() coutc() in basen;virtual void d() coutd() in basen;virtual void e() coute() in basen;virtual void f() coutf() in basen;class A:public basepublic:virtual void a() couta() in An;virtual void b() coutb() in An;virtual void f() coutf() in An;class B:public basepublic:virtual void a() couta() in Bn;vi
35、rtual void b() coutb() in Bn;virtual void c() coutc() in Bn;class C:public A,public Bpublic:virtual void a() couta() in Cn;virtual void d() couta(); pa-b(); pa-c(); pa-d(); pa-e(); pa-f();将类将类C的地址赋的地址赋值时产生歧义值时产生歧义类类C中有两个中有两个base,只有一个,只有一个Aa() in Cb() in Ac() in based() in Ce() in basef() in A为避免这种情况
36、,将为避免这种情况,将base定义定义为虚基类。为虚基类。33class basepublic:virtual void a() couta() in basen;virtual void b() coutb() in basen;virtual void c() coutc() in basen;virtual void d() coutd() in basen;virtual void e() coute() in basen;virtual void f() coutf() in basen;class A:virtual public basepublic:virtual void a
37、() couta() in An;virtual void b() coutb() in An;virtual void f() coutf() in An;class B:virtual public basepublic:virtual void a() couta() in Bn;virtual void c() coutc() in Bn;class C:public A,public Bpublic:virtual void a() couta() in Cn;virtual void d() couta(); pa-b(); pa-c(); pa-d(); pa-e(); pa-f
38、();34a( )b( )c( )d( )e( )f( )a( )b( )c( )d( )e( )f( )a( )b( )f( )a( )b( )c( )d( )e( )f( )a( )c( )a( )b( )c( )d( )e( )f( )a( )b( )f( )a( )c( )baseABCa( )d( )AB35class basepublic:virtual void a() couta() in basen;virtual void b() coutb() in basen;virtual void c() coutc() in basen;virtual void d() cout
39、d() in basen;virtual void e() coute() in basen;virtual void f() coutf() in basen;class A:virtual public basepublic:virtual void a() couta() in An;virtual void b() coutb() in An;virtual void f() coutf() in An;class B:virtual public basepublic:virtual void a() couta() in Bn;virtual void c() coutc() in
40、 Bn;class C:public A,public Bpublic:virtual void a() couta() in Cn;virtual void d() couta(); pa-b(); pa-c(); pa-d(); pa-e(); pa-f();类类C中只有一个中只有一个basea() in Cb() in Ac() in Bd() in Ce() in basef() in A36class basepublic:void a()couta() in basen;void b()coutb() in basen;void c()coutc() in basen;void d
41、()coutd() in basen;void e()coute() in basen;void f()coutf() in basen;class A:virtual public basepublic:void a()couta() in An;void b()coutb() in An;void f()coutf() in An;class B:virtual public basepublic:void a()couta() in Bn;void c()coutc() in Bn;class C:public A,public Bpublic:void a()couta() in Cn;void d()couta(); pa-b(); pa-c(); pa-d(); pa-e(); pa-f();类类C中只有一个中只有一个basea() in baseb() in basec() in based() in basee() in basef() in base结束结束
限制150内