第7章虚函数 7.1虚函数 ■72虚析构函数 m7.3抽象类 ■7.4友元、绑定 ■7.5类的存储空间 2
2 第7章 虚函数 7.1 虚函数 7.2 虚析构函数 7.3 抽象类 7.4 友元、绑定 7.5 类的存储空间
71虚函数 虚函数:是一种动态多态函数,通过动态绑定完成 虚函数的调用。(静态多态与静态绑定) 。“单界面,多实现版本”的思想 虚函数到对象的函数成员的映射通过存储在对象中 的一个指针完成。(虚函数入口地址表VFT) 定义: virtual函数原型 【看例71】 3
3 7.1 虚函数 虚函数:是一种动态多态函数,通过动态绑定完成 虚函数的调用。(静态多态与静态绑定) “单界面,多实现版本”的思想 虚函数到对象的函数成员的映射通过存储在对象中 的一个指针完成。(虚函数入口地址表VFT) 定义: virtual 函数原型 【看例7.1】
虚函数使用说明1 1虚函数必须是类的成员函数,非成员函数不能说明 为虚函数 2.虚函数一般是基类的public或protected部分函数,可 在一个或多个public派生类中重新定义(即,可有不 同的实现版本) 函数原型必须完全相同 3.虚函数只有在具有继承关系的类层次结构中定义才 有意义,否则引起额外开销(通过VFT访问) 4.虚函数有隐含的this参数,参数表后可出现const和 volatile 4
4 虚函数使用说明 1 1. 虚函数必须是类的成员函数,非成员函数不能说明 为虚函数 2. 虚函数一般是基类的public 或protected部分函数,可 在一个或多个public派生类中重新定义(即,可有不 同的实现版本) 3. 虚函数只有在具有继承关系的类层次结构中定义才 有意义,否则引起额外开销(通过VFT访问) 4. 虚函数有隐含的this参数,参数表后可出现const 和 volatile 函数原型必须完全相同
使用说明2 5.用虚函数实现程序运行时,必须用指向基类(父类)的 指针(或引用)访问虚函数。根据父类指针指向的对象 类型不同,动态绑定调用相应对象不同版本的虚函 数,实现表现不同行为的操作,这就是“虚函数根据 对象类型表现出的多态性”。【分析例71】 6.一旦在基类中定义为虚函数,则所有后续派生类中 原型相同的no-statici函数将自动成为虚函数,即使没 有“virtual"声明(无限传递性) 【分析例72】 7.虚函数同普通函数成员一样,可以声明为inline函 数,也可以重载、缺省和省略参数。【分析例73】 5
5 5. 用虚函数实现程序运行时,必须用指向基类(父类)的 指针(或引用)访问虚函数。根据父类指针指向的对象 类型不同,动态绑定调用相应对象不同版本的虚函 数,实现表现不同行为的操作,这就是“虚函数根据 对象类型表现出的多态性”。【分析例7.1】 6. 一旦在基类中定义为虚函数,则所有后续派生类中 原型相同的no-static函数将自动成为虚函数,即使没 有“virtual”声明(无限传递性) 【分析例7.2】 7. 虚函数同普通函数成员一样,可以声明为inline 函 数,也可以重载、缺省和省略参数。 【分析例7.3】 使用说明2
使用说明3 8.下列函数成员不能定义为有this参数的虚函 数: 静态函数成员(没有this指针) ·构造函数(构造时对象类型是确定的,不需根据 类型表现出多态性) 9.析构函数可通过基(父)类指针(引用)调用,而 父类指针指向的对象类型是不确定的,因此, 析构函数可定义为虚函数,以便必要时表 现出多态特性。如delete*f调用析构函数。 6
6 8. 下列函数成员不能定义为有this参数的虚函 数: 静态函数成员(没有this 指针) 构造函数(构造时对象类型是确定的,不需根据 类型表现出多态性) 9.析构函数可通过基(父)类指针(引用)调用,而 父类指针指向的对象类型是不确定的,因此, 析构函数可定义为虚函数,以便必要时表 现出多态特性。如delete *f调用析构函数。 使用说明3
使用说明4 10.虚函数只能定义为其他类的友元,而不能定义为当 前类的友元(友元非当前类的成员)。即不能同时用 virtual和friend定义函数。 11.虚函数能根据对象类型适当地绑定函数成员,且绑 定函数成员的效率非常之高,因此,最好将普通函 数成员全部定义为虚函数。 12.注意:虚函数主要通过基类和派生类对象表现出多 态特性,由于union既不能定义基类又不能定义派生 类,故不能在union中定义虚函数。 转7.2 7
7 10. 虚函数只能定义为其他类的友元,而不能定义为当 前类 的友元(友元非当前类的成员)。即不能同时用 virtual 和friend 定义函数。 11. 虚函数能根据对象类型适当地绑定函数成员,且绑 定函数成员的效率非常之高,因此,最好将普通函 数成员全部定义为虚函数。 12. 注意:虚函数主要通过基类和派生类对象表现出多 态特性,由于union既不能定义基类又不能定义派生 类,故不能在union中定义虚函数。 使用说明4 转7.2
【例7.1】定义父类PONT和子类CIRCLE的绘图函数成员show #include class POINT{ int x,y; public: int getx(){return x; int gety(){return y; virtual void show(){cout<<"Show a pointin"; POINT(int x,int y){POINT:x=x;POINT:y=y;} }; class CIRCLE:public POINT int r; public: int getr()return r; void show(){cout<<"Show a circleln";} CIRCLE(int x,int y,int r):POINT(x,y){CIRCLE::r=r; Return定义 8
8 【例7.1】定义父类POINT 和子类CIRCLE 的绘图函数成员 的绘图函数成员show #include class POINT{ int x, y; public: int getx( ) { return x; } int gety( ) { return y; } virtual void show( ) { cout<<"Show a point\n"; } POINT(int x, int y) { POINT::x=x; POINT::y=y; } }; class CIRCLE: public POINT{ int r; public: int getr( ) { return r; } void show( ) { cout<<"Show a circle\n"; } CIRCLE(int x, int y, int r):POINT(x, y) { CIRCLE::r=r; } }; Return定义
void main(void) CIRCLE c(3,7,8); PONT*p=&c;//父指针p实际指向的是子类Circle对象 coutgetx()gety()show(); /Ip->show()动态绑定,并调用相应的虚函数 输出: The circle with radius 8 is at(3,7) Show a circle 考虑:若去掉virtual,结果如何? Return说明2 9
9 void main(void) { CIRCLE c(3, 7, 8); POINT *p=&c; coutgetx( )gety( )show( ); } //父指针p实际指向的是子类Circle对象 //p->show( ) 动态绑定,并调用相应的虚函数 考虑:若去掉virtual,结果如何? 输出: The circle with radius 8 is at (3, 7) Show a circle Return说明2
【例7.2】虚函数的使用方法 void main(void) #include struct A{ C c; virtual void f1(){coutf1(); /调用B:f1() virtual void f40){coutf2(); /调用B:f2() p->f3(); /调用A:f3() class B:Af p->f4); /调用C:f4() virtual void fl() irtual可省略 p->A:f2();1/调用A:f2() cout<<"B::flln";} void f2() /2自动成为虚函数 输出: B::f1 { cout<<"B::f2\n";} 语法检查静 ; B::f2 态进行,考 class C:B A::f3 虑(对否): void f4() /f4自动成为虚函数 C::f4 c.f1();/? { cout<<"C::f4\n";} A::f2 c.f2();1/? Return说明2 10
10 【 例7.2 】虚函数的使用方法 虚函数的使用方法 #include struct A{ virtual void f1( ){ coutf1( ); p->f2( ); p->f3( ); p->f4( ); p->A::f2( ); } //调用B::f1( ) //调用B::f2( ) //调用A::f3( ) //调用C::f4( ) //调用A::f2( ) 输出:B::f1 B::f2 A::f3 C::f4 A::f2 Return说明 2 语法检查静 态进行,考 虑 (对否 ): c.f1( ); //? c.f2( ); //?
【例7.3】虚函数的重载方法 void main(void) { #include Cc; struct Af A*p=(A*)&C; virtual void f1(){coutf1('X');/调用C:f1(char) virtual void fl(char c){coutf1()为 /调用C:f1() void fl(int x){coutf1(3) /调用A:f1(int) class B:Af void f1(){cout长f1(3) /输出? void fl(char c){cout长<"C1";}/自动成为虚函数 public: void f1(ongx){cout长<"C3";}/普通函数成员 为 11
11 【 例7.3 】虚函数的重载方法 虚函数的重载方法 #include struct A{ virtual void f1( ) { coutf1('X'); p->f1( ); c.f1(23L); p->f1(3) } //自动成为虚函数 //普通函数成员 //普通函数成员 //自动成为虚函数 //自动成为虚函数 //调用C::f1(char) //调用C::f1( ) //调用C::f1(long) 输出:C1C0C3A2 考虑: 1. c.f1(3); // 对否 2. 若去掉A::f1(int) p->f1(3) //输出? //调用A::f1(int)