继承与多态6:多态与虚函数

来源:互联网 发布:软件测试薪水 编辑:程序博客网 时间:2024/06/03 17:00

多态与虚函数

1. 多态性

一个函数可有多种实现形式。同一个函数可通过多种方法进行调用(或用一个相同的名字定义具有不同实现的函数)。
由于1)派生类中同名函数的出现;2)基类指针可存放派生类对象地址和基类引用可作为派生类对象的别名,导致了派生类的多态。
C++中支持两种多态性,即静态多态动态多态
静态多态(也称为静态绑定static binding
编译时的多态性,即在编译过程中决定了函数的确切调用,也叫静态联编。
根据基类指针的类型在编译时确定调用基类的函数。如函数重载运算符重载,编译系统根据重载函数的参数个数、类型及顺序的差别,在编译时就确定了程序中函数的调用与哪个函数绑定。
动态多态(也称为动态绑定dynamic binding):
运行时确定调用哪个函数称为动态绑定。
在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序运行时根据具体情况来动态的确定调用哪个函数,这是在运行过程中发生的, 编译系统在编译时是无法确定的。

2. 虚函数

为使类中的一个成员函数能动态绑定,需要做两件事情(缺一不可):
1)在基类中,函数必须声明为虚函数(virtual function);
使用关键字virtual声明的函数是虚函数。
virtual只能出现在类声明中,在类实现中不可再加virtual关键字
在派生类中定义与基类的虚函数同名的函数,它仍为虚函数(当类中的某个成员函数被定义为虚函数后,则在该类所有的派生类中,该函数始终保持虚函数的特征,不必在派生类的函数声明中再使用关键字virtual。)
虚函数必须是类的非静态成员函数
2)必须使用基类的指针变量或引用形式调用该虚函数:基类指针指向基类对象和派生类对象时;基类引用作为基类对象和派生类对象的别名时(引用)。
例如:
已知,Circle类中的getArea()函数为虚函数,则:
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. Circle *p;  
  2. Cylinder t(1,2);   
  3. p=&t;   
  4. cout<<p->getArea() ;  //输出圆柱体的表面积  
  5. Circle &rc=t;  
  6. cout<<rc.getArea();     //输出圆柱体的表面积  

虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其内容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当用基类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数 。
下面举例说明虚函数的作用:
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5. public:  
  6.     virtual void disp()//虚函数定义  
  7.     {  
  8.         cout<<"class A"<<endl;  
  9.     }  
  10. };  
  11. class B: public A  
  12. {  
  13. public:  
  14.     void disp()//派生类声明中,不用再加关键字virtual,但它仍然是虚函数。  
  15.     {  
  16.         cout<<"class B"<<endl;  
  17.     }  
  18. };  
  19. class C: public B  
  20. {  
  21. public:  
  22.     void disp()  
  23.     {   cout<<"class C"<<endl;}  
  24.     void show()  
  25.     {   cout<<"class CC"<<endl;}  
  26. };  
  27. void display(A *x )//指针形式调用虚函数  
  28. {  
  29.     x->disp();  
  30. }  
  31. void main()  
  32. {  
  33.     A a;  
  34.     B b;  
  35.     C c;  
  36.     display(&a);  
  37.     display(&b);  
  38.     display(&c);  
  39. }  

如果 没有virtual关键字的声明,由于B,C是A的派生类,而display()函数的参数是A类的指针,此时为静态绑定,会显示3个class A;相反地如果带有virtual关键字,动态绑定,会显示class A,class B, class C。
下面再举一例,以Circle类为基类公有派生出Cylinder类和Sphere类:
[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //circle.h  
  2. #ifndef CIRCLE_H  
  3. #define CIRCLE_H  
  4. class  Circle   
  5. {  
  6. protected:  
  7.     double radius;  
  8. public:  
  9.     Circle(double r);  
  10.     virtual double getArea();//虚函数声明  
  11.     double getRadius();  
  12.     void setRadius(double);  
  13.     double getPerimeter();  
  14. };  
  15. #endif  

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //  cylinder.h文件  
  2. #include "circle.h"  
  3. class  Cylinder: public Circle  
  4. {  
  5. protected:  
  6.     double height;  
  7. public:  
  8.     Cylinder();  
  9.     Cylinder(double h);  
  10.     Cylinder(double r,double h);  
  11.     double getArea();//虚函数,不带virtual关键字  
  12.     double getVolume();  
  13.     double getHeight();  
  14.     void setHeight (double h);  
  15. };  

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //  sphere.h文件,Sphere类的定义  
  2. #include "circle.h"  
  3. class  Sphere: public Circle  
  4. {  
  5. public:  
  6.     Sphere():Circle(1)  
  7.     {}  
  8.     Sphere(double r):Circle(r)  
  9.     {}    
  10.     double getArea()//虚函数的实现  
  11.     {  
  12.         return 4*Circle::getArea();  
  13.     }  
  14.     double getVolume()  
  15.     {  
  16.         return 4*Circle::getArea()*radius/3;  
  17.     }  
  18. };  

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //circle.cpp  
  2. #include "circle.h"  
  3. Circle::Circle(double r)  
  4. {  
  5.     radius= r;  
  6. }  
  7. double Circle::getArea()//虚函数的定义  
  8. {  
  9.     return radius* radius *3.14159;  
  10. }  
  11. double Circle::getPerimeter ()  
  12. {  
  13.     return 2*3.14159*radius;  
  14. }  
  15. double Circle::getRadius()  
  16. {  
  17.     return radius;  
  18. }  
  19. void Circle::setRadius(double r)  
  20. {  
  21.     radius=r;  
  22. }  

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //cylinder.cpp文件,Cylinder类的实现  
  2. #include "Cylinder.h"  
  3. Cylinder::Cylinder():Circle(1)  
  4. {       height=1;  }  
  5. Cylinder::Cylinder(double h) :Circle(1)  
  6. {       height=h;  }  
  7. Cylinder::Cylinder(double r,double h):Circle (r)  
  8. {       height=h;}  
  9. double Cylinder::getHeight()  
  10. {      return height;}  
  11. void Cylinder::setHeight(double h)  
  12. {      height=h;}  
  13. double Cylinder::getArea()//虚函数的实现  
  14. {      return 2*Circle::getArea()+getPerimeter()*height;  }  
  15. double Cylinder::getVolume()  
  16. {      return Circle::getArea()*height;  }  

[cpp] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. //test.cpp  
  2. #include <iostream>  
  3. using namespace std;  
  4. #include "cylinder.h"  
  5. #include "sphere.h"  
  6. void main()  
  7. {  
  8.     Circle *p;  
  9.     Cylinder d(1.5,10);  
  10.     Sphere s(2);  
  11.     p=&d;  
  12.     cout<<p->getArea()<<endl;  
  13.     p=&s;  
  14.     cout<<p->getArea();  
  15.     //cout<<p->getVolume();//error: class circle没有getVolume()成员  
  16. }  

注:类的对象不能实现运行时多态,只能用对象指针对象引用形式。

3. 虚函数总结

虚函数是一种类似于函数重载的方法,用于实现动态绑定。
什么样的成员函数应声明为虚函数?
如果一个基类中定义的函数需要在派生类中重定义,应该将其声明为虚函数,以实现不同功能,从而避免混淆和错误(如Circle类的getArea()函数)。
另一方面,如果一个函数不会被重定义,将其声明为非虚函数会更有效(如Circle类的getRadius(), setRadius()等),因为在运行时动态绑定虚函数会花费更多的时间和系统资源。
虚析构函数:
如果一个基类的析构函数被说明为虚函数,则它的派生类中的析构函数是虚析构函数,不管它是否使用了virtual关键字。
注意:构造函数不能声明为虚函数。

指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。
多态还有个关键之处就是一切用指向基类的指针或引用来操作对象

0 0
原创粉丝点击