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
)。
当基类的指针或者引用指向派生类的对象的时候,就发生了多态。
:-)