首先要了解一下几个概念:
虚函数、虚函数表、虚函数表指针、虚函数表在支持多态方面的工作原理
首先我们先创建一个空类,看一下它的sizeof值(C++中的运算符,用于获取对象的长度,即所占用内存的大小,以字节为单位)
class A
{
};
A a;
cout<<"sizeof a = "<<sizeof(a)<<endl;
会发现此时返回值为sizeof a = 1,说明一个空类所占内存空间为1字节。
继续,若在类中加入两个普通的成员函数
class A
{
public:
void func1() {}
void func2() {}
};
A a;
cout<<"sizeof a = "<<sizeof(a)<<endl;
此时返回的sizeof a = 1依旧是1字节,说明类中的普通成员函数并不占用类对象的内存空间。
再放入一个虚函数
class A
{
public:
void func1() {}
void func2() {}
virtual void vfunc(){}
};
A a;
cout<<"sizeof a = "<<sizeof(a)<<endl;
此时返回的sizeof值增加了,说明一个或多个虚函数加入类中之后,编译器会为对象a插入一个虚函数表指针,虚函数表指针是占用内存空间的。
类中有一个或多个虚函数时,编译器会为类的对象生成一个虚函数表,虚函数表会一直伴随着类走过编译、链接后和类一起存在可执行文件中,在执行时也会一并装载到内存中。
编译过程中,编译器会将虚函数表指针指向虚函数表,虚函数表中存储指向虚函数的指针。
多态的经典场景:父类指针/引用指向子类对象,然后调用子类重写的虚函数
父类虚函数与子类虚函数重名时,父类指针绑定子类对象,那么父类指针调用虚函数其实是调用的子类虚函数。
父类有虚函数1,2,3,子类重写了父类的虚函数2,那么此时父类虚函数表中的虚函数指针分别指向自身的虚函数1,2,3,而子类的虚函数表指针指向父类的虚函数1,3和自身重写后的虚函数2。
父类想调用子类的方法时,这个方法一定要在父类中声明为虚函数;而纯虚函数所在的父类是抽象类,只声明,没有实现,只能在被子类继承后,在子类中实现。
也就是说,父类如果声明了虚函数,那这个虚函数是有实现的,即使是空实现,他的作用是让这个函数在子类中被重新实现,从而父类指针指向子类时调用此函数,调用的是子类重新实现的,以达到多态的目的。当然子类也可以不进行重写。
如果:
1.大多数子类有自己的实现方法,并且也需要父类有一个备选方法时,定义虚函数。
2.父类的方法必须由子类自行定义时,定义纯虚函数。