目录
前提:
动态多态:
背后逻辑:
代码引入:
1.没有设置虚函数的类建立的对象的字节大小:
结果如下:
2.设置虚函数的类建立的对象的字节大小
结果如下:
动态多态解释:
a的内部结构可大致用图表示为:
cat的内部结构可大致用图表示为:
第二种验证方法:
利用vs stdio 2022提供的开发人员命令工具来进行查询:编辑
未设置animal.speak为虚函数时候的内存结构图:
animal的:
cat的:
设置animal.speak为虚函数时候的内存结构图:
animal的:
cat的:
总结:
关于C++多态的详细解释我们已经在C++ 多态关系详解_我是一盘牛肉的博客-CSDN博客这篇文章中讲过,不了解的可以先去观看,本篇我们只简单的说一下作为背景知识。
C++中的动态多态是指在程序运行时根据对象实际运行时类型来决定调用哪个函数。它是通过虚函数实现的。
在C++中,当父类一个函数被声明为虚函数时,它的子类可以重写该函数并提供自己的实现。在程序运行时,当通过父类指针或引用调用该函数时,实际调用的是运行时类型所对应的派生类实现。
这种通过对象实际运行时类型决定函数调用的特性称为动态多态。它能够让程序更加灵活、可扩展,并且大大提高了代码的可读性和可维护性。
#include
using namespace std;
class animal
{
public:
void speak()
{
cout << "动物在说";
}
};
class cat : public virtual animal
{
public:
void speak()
{
cout << "猫在说话";
}
};
int main()
{
cat cat;
animal a;
cout<
此时我们还没有将animal类中的speak函数设为虚函数,此时我们创建一个类型是animal类型的对象a,我们输出a的大小。
结果是1。这是因为在C++中,类的成员函数是属于类的,但是它们并不占用对象的空间,这是因为成员函数是在编译时被编译器所处理的,而不是在运行时被实例化的。
类的成员函数只有一个函数实例,它被所有的对象所共享。当一个对象调用成员函数时,编译器会自动将该对象的地址作为一个隐含参数传递给成员函数,因此成员函数可以访问该对象的数据成员。
因此,成员函数不会占用类的对象的空间。只有类的数据成员才会占用对象的空间。这也是为什么在C++中,不管一个类有多少个对象,它的成员函数都只有一个实例的原因之一。
#include
using namespace std;
//动物
class animal
{
public:
//虚函数:
virtual void speak()
{
cout << "动物在说";
}
};
class cat : public virtual animal
{
public:
void speak()
{
cout << "猫在说话";
}
};
int main()
{
cat cat;
animal a;
cout<
将类型为animal的对象a里面的成员函数设为虚拟函数之后,字节变为了8(x64系统下),也就是说此时有什么东西存在了这个对象里,这里我们直接揭晓谜底:指针
如果我们把一个成员函数变为了虚函数,此时我们类内部会创建一个名字叫做vfptr(虚函数表指针)的指针,这个指针的指向是虚函数表(vftable),该表的内部会记录虚函数的地址。
而如果我们写以下代码的话⬇️:
Ainmal &animal =cat
animal.speak();
那么实际类型是cat,那就会从cat里面调用 虚函数表,此时虚函数表内的地址是cat的speak函数的地址,那么就执行cat里面的speak函数,打印“小猫在说话”。
通过该窗口打开 我们所写的代码文件⬇️:
我们可以通过该窗口查看图像化的类内部存储结构,我们先看
此时cat继承animal,得到如上内存结构
可以看到与我们之前讲的概念一致,类内部存储vfptr指针,指针指向虚函数表,表内存放函数地址。这里需要注意的是由于操作系统的不同,我采用的编译器是x64,而此开发人员窗口的系统是x86,因此这里显示字节数为4,而我的运行结果为8。
此时我们可以看到与初始的cat函数内部已经不同,此时cat内部也有了vfptr指针,而这个指针指向的是cat自己的函数,而并非像以前一样。也就是说如果只是简单的继承,那父类和子类之间只是简单的复制操作,但是虚函数的出现让子类的函数能够覆盖虚函数表里的地址,使其按照我们的意愿来运行想要运行的函数。
在C++中,动态多态是通过虚函数(Virtual Function)和运行时类型识别(Run-time Type Identification, RTTI)来实现的。
虚函数是在基类中声明的函数,在派生类中可以被重写(Override),并且通过基类指针或引用调用时,会根据实际对象的类型来调用相应的派生类函数,而不是基类函数。这种调用方式被称为动态绑定(Dynamic Binding)。
运行时类型识别允许程序在运行时获取对象的类型信息。它提供了两个关键的运算符:dynamic_cast和typeid。dynamic_cast可以将指向基类对象的指针或引用转换为指向派生类对象的指针或引用,并在转换时进行类型检查,避免了类型转换时的错误。typeid运算符可以返回某个对象的类型信息,可以用来判断对象的类型是否为某个特定的类。
通过虚函数和运行时类型识别,C++实现了动态多态,使得程序能够在运行时根据对象的实际类型来进行调用,从而更加灵活和扩展。