对于某些函数来说,基类希望它的派生类定义适合自身的版本,此时基类就将这些函数声明为虚函数。
在存在虚函的类,创建对象时会产生虚表指针,虚表指针指向一个虚表,这时就可以通过虚表访问自己定义的函数。
通过下面两种继承进行分析:
【带有虚函数的菱形继承】
以下图的模型为例进行分析:
class A
{
public :
A()
:a(1)
{}
virtual void add()
{
cout << "A::add()" << endl;
}
int a;
};
class B1 : public A
{
public:
B1()
:b1(2)
{}
virtual void add()
{
cout << "B1::add()" << endl;
}
virtual void FunB1()
{
cout << "FunB1()" << endl;
}
int b1;
};
class B2 : public A
{
public:
B2()
:b2(3)
{}
virtual void add()
{
cout << "B2::add()" << endl;
}
virtual void FunB2()
{
cout << "FunB2()" << endl;
}
int b2;
};
class C : public B1,public B2
{
public:
C()
:c(4)
{}
virtual void add()
{
cout << "C::add()" << endl;
}
int c;
};
我们观察C类对象在内存中的结构:
当发生继承时,如果派生类重写了基类的虚函数,那么派生类的对象中会修改基类的虚表,虚表中的函数指针会指向派生类自己重写的函数,如果派生类没有重写基类的虚函数,那么派生类不会改变那个虚函数的指向只是把它继承下来。
通过上图发现,B1类的虚表指针中add()函数被替换为C类的add()函数指针,B2类的虚表指针中add()函数也被替换为C类的add()函数指针,但是却发现这两个指针不相同,这可以说明两个函数不是同一个函数么?这是不可以的!通过上图发现,所说的函数指针其实指向的是一条跳转语句,在跳转之后会做一些修正最后使他们访问的是同一个函数。
在发生动态绑定时,运行的过程:
【带有虚函数的菱形虚拟继承】
对于菱形虚拟继承我们知道,派生类在继承时加上virtual关键字就说明,它可以共享它的基类,当多个派生类都虚继承一个基类,并且有其他的类继承于派生类,那么在派生类的子类的对象中只会有一份基类的成员,在每个派生类成员中都会有一个指针指向一块空间,空间里面存放了派生类相对于自己的偏移量和相对于基类的偏移量。
我们同通过实例来进一步研究(以下图的模型为例):
【带有虚函数的菱形虚拟继承】
对于菱形虚拟继承我们知道,派生类在继承时加上virtual关键字就说明,它可以共享它的基类,当多个派生类都虚继承一个基类,并且有其他的类继承于派生类,那么在派生类的子类的对象中只会有一份基类的成员,在每个派生类成员中都会有一个指针指向一块空间,空间里面存放了派生类相对于自己的偏移量和相对于基类的偏移量。
我们同通过实例来进一步研究(以下图的模型为例):
class A
{
public :
A()
:a(1)
{}
virtual void add()
{
cout << "A::add()" << endl;
}
int a;
};
class B1 : virtual public A
{
public:
B1()
:b1(2)
{}
virtual void add()
{
cout << "B1::add()" << endl;
}
virtual void FunB1()
{
cout << "FunB1()" << endl;
}
int b1;
};
class B2 :virtual public A
{
public:
B2()
:b2(3)
{}
virtual void add()
{
cout << "B2::add()" << endl;
}
virtual void FunB2()
{
cout << "FunB2()" << endl;
}
int b2;
};
我们观察C类对象在内存中的结构:
总结:
- 当一个类有虚函数时,在其对象的开始会生成一个虚表指针,指向的虚表中会存放虚函数的地址。
- 当一个派生类继承一个带有虚函数的基类时,派生类对象成员中基类部分中的虚表指针会被派生类所修改,成为派生类自己的虚表指针。在虚表中,如果虚函数在派生类中被重写那么就会存放被重写过的虚函数指针,如果没有重写基类的虚函数,就会单纯的继承下来存放基类的虚函数指针。
- 当一个派生类继承多个带有虚函数的基类时,派生类对象成员中基类部分中的虚表指针会被派生类所修改,成为派生类自己的虚表指针(相当于派生类自己有多个虚表指针)。如果重写了某个基类的虚函数,那么对应的去修改继承于这个基类的成员中的虚表,使指针指向派生类重写过得虚函数,如果没有重写就单纯的继承。