这篇文章很不错!!
http://patmusing.blog.163.com/blog/static/13583496020103255219855/
典型地,C++通过虚函数实现多态性。多态性的定义:“无论发送消息的对象属于什么类,他们均发送具有相同形式的消息,对消息的处理方式可能随接受消息的对象而变。”具体地说,“在某个基类上建立起来的类的层次结构中,可以对任何一个派生类的对象中的同名成员函数进行调用,而被调用的成员变量所提供的处理可以随其所属的类而改变。”虚函数首先是一种成员函数,它可以在该类的派生类中被重新定义并被赋予另外一种处理功能。
我们先来看看下面的例子:
#include <iostream>
using namespace std;
class A
{
private:
int a;
public:
void funA0()
{
cout << "This is funA0 in class A" << endl;
}
void setA(int a)
{
this->a = a;
}
};
class B
{
private:
int b;
public:
virtual void vfunB0()
{
cout << "This is vfunB0 in class B" << endl;
}
void setB(int b)
{
this->b = b;
}
};
class C
{
private:
int c;
public:
virtual void vfunC0()
{
cout << "It's in vfunC0 in class C" << endl;
}
virtual void vfunC1()
{
cout << "It's in vfunC1 in class C" << endl;
}
void setC(int c)
{
this->c = c;
}
};
class D : public B
{
private:
int d0;
int d1;
public:
void setD0(int d0)
{
this->d0 = d0;
}
void setD1(int d1)
{
this->d1 = d1;
}
};
int main(void)
{
A aa;
B bb;
C cc;
D dd;
cout << "Size of class A = ";
cout << sizeof(aa) << endl;
cout << "Size of class B = ";
cout << sizeof(bb) << endl;
cout << "Size of class C = ";
cout << sizeof(cc) << endl;
cout << "Size of class D = ";
cout << sizeof(dd) << endl;
dd.setD0(10);
dd.setD1(11);
cout << *((unsigned long*)(&dd) + 1) << endl;
cout << *((unsigned long*)(&dd) + 2) << endl;
cout << *((unsigned long*)(&dd) + 3) << endl;
return 0;
}
输出结果:
图1
class A中定义了一个整形的成员变量a,在32-bit的操作系统中,整形是4Bytes,因此aa的大小是4Bytes;
图2
class B中定义了一个整形的成员变量b和一个虚函数vfunB0,其中整形成员变量b需要4Bytes,vfun0将导致class B生成一个指向virtual table的指针vfptr (其中virtual table中拥有该类所有虚函数的指针)。在32-bit的操作系统中一个指针需要32 bits或者4Bytes来表示,因此输出的bb的大小是8Bytes;
图3
class C中定义了一个整形的成员变量c和两个虚函数vfunC0、vfunC1,整形成员变量c需要4Bytes,两个虚函数的声明导致class C生成一个指向virtual table的指针vfptr (其中virtual table中拥有该类所有虚函数的指针)。在32-bit的操作系统中一个指针需要32 bits或者4Bytes来表示,因此输出的cc的大小也是是8Bytes。
图4
由此可见,只要在一个类中声明了一个虚函数或者多个虚函数,该类在实例化的时候,总是隐式地生成一个且仅一个指向virtual table的指针vfptr,一个或者多个该类虚函数的指针将被存放在virtual table中。另外,如果一个类存在虚函数,那么vfptr总是在最前面的,即在所有成员变量之前,如上图所示。
class D中定义了一个整形的成员变量d0、d1,整形成员变量d0、d1各需要4Bytes,另外由于它继承了class B,由于在class B中也定义了一个整形成员变量b (尽管它是private的),这个也需要4Bytes,同时在class B 也定义的一个虚函数,也被class D继承了,而“虚函数不管被继承多少次,仍然是虚函数”,因此class D也需要在被实例化的时候隐式生成一个指向virtual table的指针vfptr。由此得到dd的大小是16Bytes。
图5
为什么说是这样的顺序呢?即b、d0、d1,而不是d0、d1、b呢,从下面的代码所产生的结果就可以很清楚地知道这一点。
dd.setD0(10); // 设置d0 = 10
dd.setD1(11); // 设置d1 = 11
// *((unsigned long*)(&dd) + 0) 就是Virtual Table的指针
//
cout << *((unsigned long*)(&dd) + 1) << endl; // 参照前面console的输出:3435973836
cout << *((unsigned long*)(&dd) + 2) << endl; // 参照前面console的输出:10
cout << *((unsigned long*)(&dd) + 3) << endl; // 参照前面console的输出:11
因此可以判定最后两个分别是d0、d1,紧随vfptr之后的是class B中的似有成员变量b,由于没有被赋值,所以输出的结果是任给的。
由此我们可以得到一个重要的结论:尽管是私有成员变量,诸如class D中的d0和d1,我们还是可以通过类似
*((unsigned long*)(&dd) + offset)的方式轻松地访问到,甚至其基类的私有成员变量,诸如class B中的b。(这样似乎不太安全,但事实上就是这样的,当然很少有人这么做)
同一个类的所有对象共享同一个成员函数的地址空间(虚函数除外),而每个对象有独立的成员变量地址空间。可以说成员函数是类拥有的,成员变量是类的对象拥有的。
虚函数和虚继承问题相当复杂,尤其是两者同时存在的时候。但并不是很难,也非常有意思。