richardli79

导航

虚函数,看看吧

虚 函 数

    说一个人后入为主,通常是说耳根子软,人家说什么就把原先的忘掉了,别人说向东走,他就向东走,一会儿有人说向西了,他立马赶回来。不过好处显而易见,这个人特别听话,如果你有时候不知道自己要干什么,或者不确定一会儿要干什么,那么就叫他在旁边等着,到你拿定主意的时候,再告诉他你的要求,让他照办。现实中这样的事情并不少见,秘书需要作记录、写报告、安排行程、联络客户等等,而这些工作都是在你的指示下去做的。如果编程的时候也有这么听话的代码就好了,当然这不是做梦,这完全可以实现。

    求解方程时有一个未知数的概念,用一个可以为任意数的符号代表一个暂时不知道值的数,然后通过运算规则得到符合要求的值,用编程的角度来看,就是加上了一个间接层,我们暂时不处理数值,而是处理一个与数值有关的符号,如果变化的是数值也就是对应的属性,我们可以用成员变量来处理;如果变化的是运算规则呢?y=f(x),这个f(x)是可变的,要让我们的解决方案通用于各种可能的f(x),那就要提供一个间接层来连接不同的函数,在C++中用函数指针就可以办到。

    指针的作用就是可以保存某种类型的对象在内存中的位置,改变指针的值就可以让他指向不同对象。(指针本身也是一个类型)

    编译器是如何实现对虚函数在运行时刻确定被调用函数的代码呢?也就是说,虚函数实际上是如何被编译器处理的呢?这里把“标准的”方式简单介绍一下。

    “标准”方式,也就是所谓的“VTABLE”机制。编译器发现一个类中有被声明为virtual的函数,就会为其建立一个虚函数表,也就是VTABLE。VTABLE实际上是一个函数指针的数组,每个虚函数占用这个数组的一个位置。一个类只有一个VTABLE,不管它有多少个实例。派生类有自己的VTABLE,但是派生类的VTABLE与基类的VTABLE有相同的函数排列顺序,同名的虚函数被放在两个数组的相同位置上。在创建类实例(对象)的时候,编译器还会在每个实例的内存中增加一个vptr字段(虚函数表指针),该字段指向本类的VTABLE。通过这些手段,编译器在看到一个虚函数调用的时候,就会将这个调用改写,如下面的例子:

class A
{
public:
    virtual void foo() { cout << "A::foo() is called" << endl;}
};

class B: public A
{
public:
    virtual void foo() { cout << "B::foo() is called" << endl;}
};

那么,在使用的时候,我们可以:

A * a = new B();
a->foo();       // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!

void bar(A * a)
{
    a->foo();  // 被调用的是A::foo() 还是B::foo()?
}
因为foo()是个虚函数,所以在bar这个函数中,只根据这段代码,无法确定这里被调用的是A::foo()还是B::foo(),但是可以肯定的说:如果a指向的是A类的实例,则A::foo()被调用,如果a指向的是B类的实例,则B::foo()被调用。

编译时bar这个函数会被改写为:

void bar(A * a)
{
    (a->vptr[1])();
}

    因为派生类和基类的foo()函数具有相同的VTABLE索引,而他们的vptr又指向不同的VTABLE,因此通过这样的方法可以在运行时刻决定调用哪个foo()函数。

    虽然实际情况远非这么简单,但是基本原理大致如此。如类X的对象模型如下图所示:

   虚函数的多态性只能通过对象指针或对象的引用调用来实现,如下的调用:
 

X obj;
X* ptr = &obj; X& ref = obj;
ptr->VirtualFunc();
ref.VirtualFunc();

  将被C++编译器转换为如下的形式。
 

( *ptr->vptr[2] )(ptr);
( *ptr->vptr[2] )(&ref);

  其中的2表示VirtualFunc在类的虚函数表的第2个槽位。因此,在C++中用函数指针实现虚函数,编译器在类中隐藏了一个函数指针数组(因为成员函数可能有很多,叫做虚函数表)。显然这里增加了一个寻找函数位置的步骤,所以,使用虚函数将降低执行效率,需要在效率和灵活性之间的取舍。虚函数的调用相当于一个C的函数指针调用,其效率也并未降低多少。

我们用C++来看一下虚函数的效果

#include <iostream>

#include <stdlib.h>

class NormalA{

public:

void DoSomething( void ){ cout << "This is class NormalA." << endl; }

};

class NormalB : public NormalA{

public:

void DoSomething( void ){ cout << "This is class NormalB." << endl; }

};

class NormalC : public NormalB{

public:

void DoSomething( void ){ cout << "This is class NormalC." << endl; }

};

 

class VirtualA{

public:

virtual void DoSomething( void ){ cout << "This is class VirtualA." << endl; }

};

class VirtualB : public VirtualA{

public:

void DoSomething( void ){ cout << "This is class VirtualB." << endl; }

};

class VirtualC : public VirtualB{

public:

void DoSomething( void ){ cout << "This is class VirtualC." << endl; }

};

int main()

{

NormalA na;

NormalB nb;

NormalC nc;

VirtualA va;

VirtualB vb;

VirtualC vc;

NormalA * pna;

VirtualA * pva;

na.DoSomething();

nb.DoSomething();

nc.DoSomething();

 

va.DoSomething();

vb.DoSomething();

vc.DoSomething();

 

pna = &na;

pna->DoSomething();

pna = &nb;

pna->DoSomething();

pna = &nc;

pna->DoSomething();

 

pva = &va;

pva->DoSomething();

pva = &vb;

pva->DoSomething();

pva = &vc;

pva->DoSomething();

cin.get();

return 0;

}

结果是

This is class NormalA. //调用的是NormalA::DoSomething();

This is class NormalB. //调用的是NormalB::DoSomething();

This is class NormalC. //调用的是NormalC::DoSomething();

This is class VirtualA. //调用的是VirtualA::DoSomething();

This is class VirtualB. //调用的是VirtualB::DoSomething();

This is class VirtualC. //调用的是VirtualC::DoSomething();

This is class NormalA. //调用的是NormalA::DoSomething(); 没有虚函数表,NormalA *指定了类型

This is class NormalA. //调用的是NormalA::DoSomething(); 没有虚函数表,NormalA *指定了类型

This is class NormalA. //调用的是NormalA::DoSomething(); 没有虚函数表,NormalA *指定了类型

This is class VirtualA. //调用的是VirtualA::DoSomething(); 虚函数表指针指向的是VirtualA

This is class VirtualB. //调用的是VirtualB::DoSomething(); 虚函数表指针指向的是VirtualB

This is class VirtualC. //调用的是VirtualC::DoSomething(); 虚函数表指针指向的是VirtualC

posted on 2005-12-19 10:01  Richard  阅读(266)  评论(0编辑  收藏  举报