C++中虚函数的实现背后

摘要: 虚函数是很多编程语言中一个特性,比如c#,java,当然在c++语言中也有。这三种语言都是面向对象的语言。我们都知道面向对象语言有三个最基本的特征就是:继承,多态,封装。在c++语言中,这种多态的特征就是通过虚函 ...

虚函数是很多编程语言中一个特性,比如c#,java,当然在c++语言中也有。这三种语言都是面向对象的语言。我们都知道面向对象语言有三个最基本的特征就是:继承,多态,封装。在c++语言中,这种多态的特征就是通过虚函数(virtual)来实现的。这种实现方法在其它语言中(比如c#)中也是如此。

   我要说的就是虚函数到底是怎么实现的。还是以例子来说明。我引用了上一篇随笔中的代码。

 

class ClassA
{
public:
    void fun1();
    void fun2();
    virtual void fun3();
};
void ClassA::fun1()
{
    cout << "ClassA.fun1"<<endl;
};
void ClassA::fun2()
{
    cout << "ClassA.fun2"<<endl;
};
void ClassA::fun3()
{
    cout << "ClassA.fun3"<<endl;
};
class ClassB : public ClassA
{
public:
    void fun1();
    void fun2();
    virtual void fun3();
};
void ClassB::fun3()
{
    cout << "ClassB.fun3"<<endl;
};
class ClassC : public ClassB
{
public:
    void fun1();
    void fun2();
    virtual void fun3();
};
void ClassC::fun3()
{
    cout << "ClassC.fun3"<<endl;
};
void main()
{
    ClassA *a[3];
    ClassA a1;
    ClassB b1;
    ClassC c1;

    a1.fun3();
    b1.fun3();
    c1.fun3();

    a[0] = &a1;
    a[1] = &b1;
    a[2] = &c1;
    cout << "virtual function array test" <<endl;
    for(int i=0;i<3;i++)
    {
        a[i]->fun3();
    }

    cout << "((ClassA)&b1).fun3():";
    ((ClassA*)&b1)->fun3();
    //object slicing
    cout << "object slicing"<<endl;
    cout <<"((ClassA)b1).fun3():"; 
    ((ClassA)b1).fun3();
}

  类继承结构图如下:

  其中fun3是虚拟函数,对ClassB,ClassC子类中分别进行了重写。

   下面我解释一下虚函数的背后是怎么实现的:

  我们都知道,虚函数可以做到动态绑定,为了实现动态绑定,编译器是通过一个表格(虚拟函数表),在运行时间接的调用实际上绑定的函数来达到动态绑定,其中这个我刚所说的表格其实现就是一个“虚拟函数表”。这张表对我们程序来说是透明的。是编译器为我们的代码自动加上去的(更准确的讲,并不是为所有的代码都添加一张虚拟函数表,而是只针对那些包括虚函数的代码才加上这张表的)。

  既然有了这么一张虚拟函数表,自然而然我们就会想到,这个虚拟函数表里到底是存放一些什么东西呢?很简单,即然叫做虚拟函数表,当然是存放虚拟函数了,呵呵,在c++中,该表每一行的元素应该就是我们代码中虚拟函数地址了,也就是一个指针。有了这个地址,我们可以调用实际代码中的虚拟函数了。

  编译器既然为我们的代码加了一张虚拟函数表,那这张虚拟函数表怎么与我们的代码关联起来呢? 要实现动态绑定,我们应该利用这张虚拟函数表来调用虚拟函数,为了达到目的,编译器又会为我们的代码增加一个成员变量,这个成员变量就是一个指向该虚拟函数表的指针,该成员变量通常被命名为:vptr。

  

  说到了这里,上面代码中的ClassA中的在内存中应该如下图所示:

每一个ClassA的实例,都会有一个虚拟函数表vptr,当我们在代码中通过这个实例来调用虚拟函数时,都是通过vptr先找到虚拟函数表,接着在虚拟函数表中再找出指向的某个真正的虚拟函数地址。虚拟函数表中的内容就是类中按顺序声明的虚拟函数组织起来的。在派生的时候,子类都会继承父类的虚拟函数表vptr,我们只在把这个vptr成员在继承体系中一般看待就成了。

  有一点要说明一下,当子类在改写了父类中的虚拟函数时,同时子类的vptr成员也会作修改,此时,子类的vptr成员指向的虚拟函数表中的存放的虚拟函数指针不再是父类的虚拟函数地址了,而是子类所改写父类的虚拟函数地址。理解这一点就很容易想到了:原来多态体现在这里!

  有了上面的说明,接下来ClassB,ClassC类的内存占据空间应该如下图所示:

 

   同理,ClassC也一样。(这些图画得真是丑啊!)

  于是一个指向ClassA的对象的实例,调用 fun3就是ClassA::fun3(),一个指向ClassB的对象的实例,调用 fun3就是ClassB::fun3(),一个指向ClassC的对象的实例,调用 fun3就是ClassC::fun3(),这些调用通过都是通过虚拟函数表来进行的。

  最后,上面代码中main函数的示例的执行结果也就是恍然大悟了,答案就在上一篇随笔的回复里面。已经有人帮我回复了,就此谢过了! 欢迎大家一起探讨!

你可能感兴趣的:(编程,C++,C#,语言,编译器,fun)