概述:虚函数的目的是实现运行时多态,程序在运行时才知道要调用那些函数。这属于泛型技术(包括模板、RTTI、虚函数)。
使用:用于父类型指针指向子类的对象时。
虚函数表V-table
类中成员函数在内存中不占空间,这个比较好理解。例如一个汽车类,有成员轮子、方向盘,有成员方法 跑(),我们制造出一辆汽车的时候只有轮子、方向盘占用空间,而汽车会跑并不会占用内存的。
假如声明一个虚函数,汽车带车斗(),具体带什么车斗呢,不晓得,让子类去实现。大卡车类有个7吨位的车斗,小货车类有个2吨位的车斗,三轮车类有个小车斗。父类汽车类中是什么样子的呢,父类中声明虚函数,就是拿一个小本本(指针),记下来所有的虚函数。当然,这个小本本(指针)是占用内存的。
子类中,父类中所有声明的虚函数都会默认认为是虚函数,不管是否有virtual关键字。子类中虚函数覆盖掉父类中的虚函数,当我们用指针调用时就调用的就是子类的函数了。
class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } };
在内存中是什么样子的呢。
当子类继承这个类时,如果是继承自该基类的虚函数,则会出现覆盖,如果不是继承自基类的虚函数,则跟到虚函数表的后面。如以下的继承关系。
子类的中的内存:
多重继承,无虚函数覆盖:
子类的实例内存结构:
多重继承,有虚函数覆盖:
子类的实例内存结构:
虚函数的问题
一、通过父类型的指针访问子类自己的虚函数
我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:
Base1 *b1 = new Derive();
b1->f1(); //编译出错
任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)
二、访问non-public的虚函数
另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。
如:
class Base {
private:
virtual void f() { cout << "Base::f" << endl; }
};
class Derive : public Base{
};
typedef void(*Fun)(void);
void main() {
Derive d;
Fun pFun = (Fun)*((int*)*(int*)(&d)+0);
pFun();
}