C++虚函数详解&实现机制&多态性

目录:

PART1

PART2

PART3


PART1转载:http://blog.chinaunix.net/uid-24178783-id-370328.html

PART2转载:http://blog.csdn.net/jiangnanyouzi/article/details/3720807


PART1

 说到虚函数的实现方法,我们就不得不说到动态联编(dynamic binding)和静态联编(static binding)。静态联编意味着编译器能够直接将标识符和存储的物理地址联系在一起。每一个函数都有一个唯一的物理地址,当编译器遇到一个函数调用时,它将用一个机械语言说明来替代函数调用,用来告诉CPU跳至这个函数的地址,然后对此函数进行操作。这个过程是在编译过程中完成的(注:调用的函数在编译时必须能够确定),所以静态联编也叫前期联编(early binding)。但是,如果使用哪个函数不能在编译时确定,则需要采用动态联编的方式,在程序运行时在调用函数,所以动态联编也叫后期联编(late binding)。
      在C++继承多态中,如若要在派生类中重新定义基类的方法,则要把它声明为虚函数,并且用指针或者引用去调用它的方法,实现动态联编,否则编译器默认的将是静态联编。小看一下这个例子:

Example1:

#include 
using namespace std;

class A
{
    public:
    void f() { cout << "A" << endl; } //注意此处的函数不是虚函数



};

class B : public A
{ 
    public:
        void f() { cout << "B" << endl;}
};
int main (void)
{

    A     a, *pa; 
    B     b;  
        
      a = b; //将子类对象赋给基类对象
      a.f();
      pa = &b; //用子类的对象的地址给基类指针初始化(符合赋值兼容规则)
      pa->f();
       
      return 0;
}


运行结果:

A

A


     原因:编译器默认为静态联编方式,所以函数f(),在编译过程中就已经定死了,在子类中尽管你重新定义了f()的方法,但是编译器不知道应该调用哪个函数,所以就只会用的静态联编时的函数方法。

Example 2:
#include 
using namespace std;

class A
{
    public:
    virtual void f() { cout << "A" << endl; } //注意此处声明了虚函数

};

class B : public A
{    
    public:
        void f()    { cout << "B" << endl;}
};
int main (void)
{
    A     a, *pa; 
    B    b;
       
        a = b; //将子类对象赋给基类对象,这样做不能实现动态联编,虚函数特性失效
        a.f();
        b.f();
        pa = &b;
        pa->f();
        A &aa = b; //定义成引用类型也是可以的
        aa.f ();

        return 0;
}


运行结果:

A
B
B
B


     先 总结一下两个程序,若基类不声明为虚函数,则在A   a = b时,或者 A  &a = b, A  *a = &b,创建并初始化对象时,是根据引用或指针的类型来选择方法的。这也就是我们第一个程序运行的结果的具体解释(觉得自己上面说的静态联编有点抽象,不知道这样说明会不会更容易理解一些。。O(∩_∩)O~)。若使用了virtual声明为虚函数,则程序将根据引用或指针指向的对像的类型来选择方法。这就程序二运行结果的原因。
      对比两个结果就能很清楚的看到虚函数的作用。但是它具体的实现原理是什么呢?
      C++采用了动态联编的一种特殊形式去实现虚函数,称为虚函数表。虚函数表是一张函数查找表,用以解决以动态联编方式调用函数。它为每个可以被类对象调用的虚函数提供一个入口,这样当我们用基类的指针或者引用来操作子类的对象时,这张虚函数表就提供了编译器实际调用的函数。虚函数表其实是存储了为类对象进行声明的虚函数地址。当我们创建一个类对象时,编译器会自动的生成一个指针*__vptr(一个隐藏指针),该指针指向这个类中所有虚函数的地址表。(实际上,虚函数表就是一个函数地址数组表。),请注意,*__vptr和*this指针不同,*this是一个被编译器用作解决自引用的函数参数,而*__vptr则是一个真正的指针。
     每一个类,不管是基类还是子类都有一个自己的virtual table,而*__vptr也是被继承过来的。(更正:vptr只有一个,除非使用了多继承
     我们再看一个例子:

Example:

你可能感兴趣的:(就这样吧?)