首页 > 代码库 > 虚函数 动态绑定 实现方式是:虚函数表

虚函数 动态绑定 实现方式是:虚函数表

定义为 virtual的 函数是基类期待派生类(derived class)重定义的, 基类期待 派生类继承的函数不能定义为虚函数。

动态绑定:程序能 使用继承层次中任意类型的对象,无需关心对象的具体类型。  使用这些类的程序无须区分函数在基类还是在继承类中定义的。

尽管不是必须,派生类一般会重定义基类中的虚函数。如果没有重定义,则继承基类中定义的版本。派生类中虚函数的声明原型必须与基类中的定义方式完全匹配,但有一个例外:基类中返回值是对基类性的引用(或指针)的虚函数,在派生类中虚函数可以返回 基类和派生类 的引用(或指针)。

C++默认不适用动态绑定,要触发动态绑定,得满足2条件:1,只有指定为虚函数的成员函数才能进行动态绑定,2,必须通过基类类型的引用(或指针)才能进行函数调用

可以使用基类类型的引用(或指针)来引用基类类型或派生类类型对象。基类类型引用和指针的关键点在于:静态类型(编译时可知的引用类型或指针类型)与动态类型(运行时才可知的,指针或引用绑定的对象类型)可能不同

即动态绑定是由实参决定的。 

 1 class Cshape
 2 {
 3 public:
 4     void SetColor(intcolor){m_nColor=color;}
 5     virtual void Display(void){cout<<"Cshape"<<endl;}
 6 private:
 7     int m_nColor;
 8 };
 9  
10 class Crectangle:public Cshape{
11 public:
12     virtual void Display(void)    
13     {
14         cout<<"Crectangle"<<endl;
15     }
16 };
17  
18 class Ctriangle:public Cshape{
19                 virtual void Display(void)
20                 {cout<<"Ctriangle"<<endl;}
21 };
22  
23 class Cellipse:public Cshape{
24 public:
25     virtual void Display(void)
26     {cout<<"Cellipse"<<endl;}
27 };
28  
29 voidmain()
30 {
31     Cshape obShape;
32     Cellipse obEllipse;
33     Ctriangle obTriangle;
34     Crectangle obRectangle;
35     Cshape*pShape[4]={&obShape,&obEllipse,&obTriangle,&obRectangle};
36     for(int i=0;i<4;i++)
37         pShape[i]->Display();//动态绑定
38 }

本程序运行结果:

Cshape
Cellipse
Ctriangle
Crectangle
如果把Cshape类里面virtual void Display(void) 中的virtual去掉的话
运行结果就不一样了:
Cshape
Cshape
Cshape
Cshape
 

c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此,在子类从新声明该虚函数时,可以加,也可以不加,但习惯上每一层声明函数时都加virtual,使程序更加清晰。

继承层次的根类 一般要定义虚析构函数
 
找工作时,面试官经常会问虚函数是如何实现的,我就懵了,以为知道以上理论虚函数就过关了,书上也没说过虚函数是如何实现的,现在在此补一补:
 
C++中的虚函数的实现一般是通过虚函数表(C++规范并没有规定具体用哪种方法,但大部分的编译器厂商都选择此方法)。
类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。
注意的是,编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员占据虚函数表中的一行。如果类中有N个虚函数,那么其虚函数表将有N*4字节的大小。
虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

虚基类与虚函数没有关系。用于多重继承,防止二义性产生。