07-2 多态的原理剖析
我们回到上一篇笔记的例子中:现在创建三个类,Animal、Cat和Dog。分别在各自的类内创建Speak()函数表示说话。之后,创建一个doSpeak()函数表示说话的动物。
我们现在看一下类Animal的占用内存大小:
class Animal
{
public:
void speak()
{
cout << "一只动物在说话。" << endl;
}
};
int main()
{
cout << "Animal 的内存大小:" << sizeof(Animal) << endl;
return 0;
}
运行结果:(环境:Windows11(arm/Apple M VM)/Visual Studio 2022/Debug/arm64)
Animal 的内存大小:1
在这里,类Animal中只有一个非静态的成员函数speak(),因为类内的成员函数和成员变量是分开存储的,所以这时候相当于这个类内没有存储内容。空类的内存大小是1。
- 复习:类内成员函数和成员变量分开存储:
- 只有非静态成员变量属于类对象上。其他的(比如说静态成员变量、成员函数)都不属于类的对象上。
- 类内为空的时候,这个类的对象占用的内存空间是 1 个字节。
class Animal
{
public:
virtual void speak()
{
cout << "一只动物在说话。" << endl;
}
};
int main()
{
cout << "Animal 的内存大小:" << sizeof(Animal) << endl;
return 0;
}
运行结果:(环境:Windows11(arm/Apple M VM)/Visual Studio 2022/Debug/arm64)
Animal 的内存大小:8
请注意,在这个例子中,类Animal下的speak()函数现在是虚函数。
由于编译环境是arm64,所以占用的内存大小是8。
所以,这时候,类Animal中存储的是一个指针。
虚指针的简单介绍在[菱形继承]中简单介绍过,现在详细讲一下。
当类Animal中是虚函数的时候,这个指针我我们称之为:vfptr,含义是:虚函数指针(或者称为:“虚函数表指针”)。(v - 即virtual、f - 即function、ptr - 即pointer)。这个指针指向了虚函数表(vftable),虚函数表中记录者虚函数的地址。(v - 即virtual、f - 即function、table - 即table)
而派生类继承的时候,会完全继承基类内容。如果是类Cat继承类Animal,则类Cat也会有一个虚函数指针(vfptr),指向虚函数表(vftable)。而类Cat是继承自类Animal,所以类Cat虚函数指针(vfptr)最终指向的是&Animal::speak。
当派生类重写基类的虚函数的时候,派生类的虚函数指针(vfptr)指向的虚函数表(vftable)的内容会被覆盖,覆盖为派生类的虚函数地址。如类Cat中,最终指向的就是&Cat::speak。
当派生类重写基类的虚函数的时候,派生类中的虚函数表内部会被替换为派生类的虚函数地址。
- 复习:发生多态的要求:
- 需要存在继承关系。
- 派生类需要重写基类的某个函数(重写的要求:函数名称相同、参数列表、返回值类型完全相同)。
- 基类中被重写的函数需要是虚函数(关键字:
virtual)。
当基类的指针或者引用指向派生类的对象的时候,就发生了多态。