虚函数,看看吧
虚 函 数
说一个人后入为主,通常是说耳根子软,人家说什么就把原先的忘掉了,别人说向东走,他就向东走,一会儿有人说向西了,他立马赶回来。不过好处显而易见,这个人特别听话,如果你有时候不知道自己要干什么,或者不确定一会儿要干什么,那么就叫他在旁边等着,到你拿定主意的时候,再告诉他你的要求,让他照办。现实中这样的事情并不少见,秘书需要作记录、写报告、安排行程、联络客户等等,而这些工作都是在你的指示下去做的。如果编程的时候也有这么听话的代码就好了,当然这不是做梦,这完全可以实现。
求解方程时有一个未知数的概念,用一个可以为任意数的符号代表一个暂时不知道值的数,然后通过运算规则得到符合要求的值,用编程的角度来看,就是加上了一个间接层,我们暂时不处理数值,而是处理一个与数值有关的符号,如果变化的是数值也就是对应的属性,我们可以用成员变量来处理;如果变化的是运算规则呢?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的对象模型如下图所示:
虚函数的多态性只能通过对象指针或对象的引用调用来实现,如下的调用:
|
将被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