上一个案例中,我们先把父类中的virtual去掉。来看一下一般情况。
class Animal
{
public:
void Speak()
{
cout << "动物会说话" << endl;
}
};
此时Animal类只有一个非静态的成员函数。不属于类的对象上面。分开存储的。
int main()
{
cout << "size of the Animal : " << sizeof(Animal) << endl;
system("pause");
return 0;
}
此时Animal类类似于一个空类。空类默认大小为1字节。
验证方式:开发人员命令提示工具。

此时如果加上virtual:非静态成员函数变成虚函数。
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void Speak()
{
cout << "动物会说话" << endl;
}
};
int main()
{
cout << "size of the class Animal : " << sizeof(Animal) << endl;
system("pause");
return 0;
}
变成了4个字节。原来Animal类是占一个字节,现在占4个字节。
验证方式:开发人员命令提示工具。

什么东西占四个字节:int类型、float类型、指针。
不论什么类型的指针都占四个字节。
底层原理:
父类中增加了virtual之后,类的大小变为了4字节。增加的这个量就是虚函数指针的大小。
vfptr:虚函数(表)指针(virtual function pointer):指针指向虚函数表vftable(Virtual function table)。
表的内部,会记录一个虚函数的地址。virtual void Speak函数地址。为了记录函数的地址,还要加上一个作用域:Animal::Speak。
成员函数的函数地址要加上一个作用域。告诉编译器,这是Animal作用域下的一个成员函数。为了记录这个地址,还要在前面加一个取址符号&。
这样虚函数表中记录了虚函数的入口地址。
然后,我们用一个Cat类继承Animal。
先不写重写,看看情况:假设没有重写,就是普通的继承。
class Cat : public Animal
{
};
此时父类中的,所有内容全部复制一份过来。 父类中的指针也要拿过来,即:子类中也有一个vfptr指针,指向子类的虚函数表。子类的虚函数表内部记录着虚函数。

但是当发生子类重写之后呢?
class Cat : public Animal
{
public:
void Speak()
{
cout << "小猫喵喵叫" << endl;
}
};
子类会把子类中虚函数表内容做一个 覆盖 操作。
即把虚函数表内部保存的虚函数入口地址,修改为子类自己重写的函数地址。

父类中的虚函数地址并未改变,修改的只是子类自己重写的虚函数地址。
当父类的指针或者引用指向子类对象时,发生动态多态。
Animal& animal = cat;
当赋值发生时,animal指向子类对象(cat或者dog)时,它会去子类中寻找这个重写的虚函数。如果没有重写,那么就执行子类继承的父类的虚函数。
动态多态:
当父类指针或者引用指向不同的子类对象时,比如指向Cat或者指向Dog的对象。那么就会动态(碰到哪个子类,就去找哪个子类)去寻找这两个子类的虚函数,如果没有,那就直接执行继承的虚函数。如果有,就多态(每个子类都有不同的继承和重写)的执行各自类别的重写的虚函数。
实现方式:虚函数表指针。
这个指针,指向虚函数表:虚函数表中存储的是虚函数的函数入口地址。子类会继承这个虚函数表指针,如果子类重写了这个虚函数,那么子类的虚函数表指针就会指向新的虚函数入口地址。(虚函数表指针指向的变更。)