简单设计网站,做电脑租赁网站,网站建设仟金手指六六14,网站规划与制作目录
引言
多态
4.多态的原理
4.1 虚函数表指针
4.2 多态的原理
5.单继承和多继承关系的虚函数表
5.1 单继承中的虚函数表
5.2 多继承中的虚函数表
结束语 引言
接下来我们继续学习多态。
没有阅读多态#xff08;上#xff09;的可以点击下面的链接哦~
C——多态…目录
引言
多态
4.多态的原理
4.1 虚函数表指针
4.2 多态的原理
5.单继承和多继承关系的虚函数表
5.1 单继承中的虚函数表
5.2 多继承中的虚函数表
结束语 引言
接下来我们继续学习多态。
没有阅读多态上的可以点击下面的链接哦~
C——多态上
多态
4.多态的原理
4.1 虚函数表指针
我们先来看看这段代码
class Base
{
public:virtual void Func1(){cout Func1() endl;}
private:int _b 1;
};int main()
{Base b;cout sizeof(b) endl;return 0;
}
输出结果为 我们通过监视来观察一下 通过观察测试我们发现b对象是8bytes除了_b成员还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面这个跟平台有关)对象中的这个指针我们叫做虚函数表指针(v代表virtualf代表function)。它指向的对象就是虚函数表一个含有虚函数的类中都至少都有一个虚函数表指针因为虚函数的地址要被放到虚函数表中虚函数表也简称虚表。
对于大多数现代编译器在32位系统上虚指针通常占用4个字节而在64位系统上则占用8个字节。
编译器可能会为对象添加一些填充以确保内存对齐从而提高访问速度。因此实际的对象大小可能会比这些值的简单相加要大。
这就解释了为什么上面的代码输出结果为16。
针对上面的代码我们对其进行改造
class Base
{
public:virtual void Func1(){cout Base::Func1() endl;}virtual void Func2(){cout Base::Func2() endl;}void Func3(){cout Base::Func3() endl;}
private:int _b 1;
};class Derive : public Base
{
public:virtual void Func1(){ cout Derive::Func1() endl;}
private:int _d 2;
};int main()
{Base b;Derive d;return 0;
}
通过监视来观察一下 1. 派生类对象d中也有一个虚表指针d对象由两部分构成一部分是父类继承下来的成员虚表指针也就是存在部分的另一部分是自己的成员。
2.基类b对象和派生类d对象虚表是不一样的这里我们发现Func1完成了重写所以d的虚表中存的是重写的Derive::Func1所以虚函数的重写也叫作覆盖覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法覆盖是原理层的叫法。
3. 另外Func2继承下来后是虚函数所以放进了虚表Func3也继承下来了但是不是虚函数所以不会放进虚表。
4. 虚函数表本质是一个存虚函数指针的指针数组一般情况这个数组最后面放了一个nullptr。
5. 总结一下派生类的虚表生成 a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。 以下是对几个名词的补充 虚函数 虚函数是C类中的成员函数它们被声明为virtual用于支持多态性。虚函数本身和普通函数一样都是存储在程序的代码段中的。当我们调用一个虚函数时实际上是在调用存储在代码段中的函数体。 虚表vtable 虚表是一个全局的或静态的表它包含了指向类中所有虚函数的指针。这个表并不是存储在对象中的而是存储在程序的某个全局或静态数据段中。每个包含虚函数的类都有一个与之对应的虚表。 虚表指针vptr 虚表指针是存储在对象实例中的一个特殊指针它指向该对象所属类的虚表。这个指针是对象的一部分通常位于对象的起始位置但这也取决于编译器的具体实现和对象的内存布局。当我们通过对象的指针或引用来调用虚函数时编译器会使用这个虚表指针来查找并调用正确的虚函数。 4.2 多态的原理 在C中多态性允许我们通过基类指针或引用来调用派生类中的重写方法。为了实现这一点编译器为每个包含虚函数的类生成一个虚函数表vtable。这个表包含了指向类中所有虚函数的指针。 每个对象实例中都含有一个指向其所属类的虚函数表的指针虚表指针vptr。当通过基类指针或引用来调用虚函数时编译器会使用这个虚表指针来查找并调用正确的虚函数。 (1)对于父类对象其虚表指针指向基类的虚函数表。 (2)对于派生类对象其虚表指针指向派生类的虚函数表。如果派生类重写了基类的虚函数那么派生类的虚函数表中相应位置将指向派生类的重写函数如果派生类没有重写某个虚函数那么该位置将指向基类中的原始函数。 因此当通过基类指针或引用来调用虚函数时实际调用的函数取决于对象的实际类型即对象的虚表指针指向的虚函数表。这种机制允许C在运行时根据对象的实际类型来确定应该调用哪个虚函数从而实现多态性。 简单来说就是
C通过为每个包含虚函数的类生成一个虚函数表该表存储了指向类中所有虚函数的指针。每个对象实例内部都含有一个指向其所属类虚函数表的指针。当使用基类指针或引用来调用虚函数时编译器会利用这个虚表指针在运行时查找并调用与对象实际类型相匹配的虚函数。
多态实现的两个条件 (1) 继承或基类与派生类的关系继承或基类与派生类的关系是多态的基础。它允许一个类派生类继承另一个类基类的属性和方法。通过继承派生类可以获取基类的所有公有和保护成员注意私有成员对派生类是不可见的但它们在派生类对象的内存布局中仍然存在只是不可直接访问。在C等语言中继承还涉及虚函数表的继承。每个包含虚函数的类都有一个虚函数表该表存储了类中所有虚函数的地址。派生类继承基类时会继承基类的虚函数表并可能根据需要对某些虚函数进行重写。 (2) 虚函数重写动态绑定/后期绑定虚函数是C等语言中实现多态的关键机制。虚函数允许在运行时而不是编译时根据对象的实际类型来确定调用哪个函数。当派生类重写了基类的虚函数时派生类对象的虚函数表中对应虚函数的地址会被更新为派生类实现的地址。这样当通过基类指针或引用指向派生类对象并调用虚函数时实际调用的是派生类实现的版本。虚函数重写是多态性的核心它使得相同的函数调用可以根据对象的实际类型产生不同的行为。 综上所述要实现多态必须满足两个条件一是通过继承或基类与派生类的关系建立类之间的层次关系二是在派生类中重写基类的虚函数以实现动态绑定在运行时根据对象的实际类型调用正确的方法。这两个条件共同作用使得程序能够根据对象的实际类型来调用相应的方法从而实现多态性。
5.单继承和多继承关系的虚函数表
5.1 单继承中的虚函数表
我们来看看以下代码
class Base
{
public:virtual void func1() { cout Base::func1 endl; }virtual void func2() { cout Base::func2 endl; }
private:int a;
};
class Derive :public Base
{
public:virtual void func1() { cout Derive::func1 endl; }virtual void func3() { cout Derive::func3 endl; }virtual void func4() { cout Derive::func4 endl; }
private:int b;
};int main()
{Base b;Derive d;return 0;
}
通过调试来观察一下 我们可以发现监视窗口并没有显示fun3和fun4。我们要如何查看d的虚表呢我们可以通过下面的代码实现需求
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout 虚表地址 vTable endl;for (int i 0; vTable[i] ! nullptr; i){printf( 第%d个虚函数地址 :0X%x,-, i, vTable[i]);VFPTR f vTable[i];f(); }cout endl;
}int main()
{Base b;Derive d;// 思路取出b、d对象的头8bytes就是虚表的指针前面我们说了虚函数表本质是一个存虚函数// 指针的指针数组这个数组最后面放了一个nullptr// 1.先取b的地址强转成一个int*的指针// 2.再解引用取值就取到了b对象头8bytes的值这个值就是指向虚表的指针// 3.再强转成VFPTR*因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。// 4.虚表指针传递给PrintVTable进行打印虚表// 5.需要说明的是这个打印虚表的代码经常会崩溃因为编译器有时对虚表的处理不干净虚表最// 后面没有放nullptr导致越界这是编译器的问题。我们只需要点目录栏的 - 生成 - 清理解决方案再// 编译就好了。// 这里我使用了reinterpret_cast来进行类型转换这是更安全的做法// 因为它明确指出了我们正在执行低级别的、可能依赖于实现的转换。VFPTR* vTableb reinterpret_castVFPTR*(*reinterpret_castintptr_t*(b));PrintVTable(vTableb);VFPTR* vTabled reinterpret_castVFPTR*(*reinterpret_castintptr_t*(d));PrintVTable(vTabled);return 0;
}
输出结果为 5.2 多继承中的虚函数表
我们来了解多继承的虚函数表来看看这段代码
class Base1
{
public:virtual void func1() { cout Base1::func1 endl; }virtual void func2() { cout Base1::func2 endl; }
private:int b1;
};class Base2
{
public:virtual void func1() { cout Base2::func1 endl; }virtual void func2() { cout Base2::func2 endl; }
private:int b2;
};class Derive : public Base1, public Base2
{
public:virtual void func1() { cout Derive::func1 endl; }virtual void func3() { cout Derive::func3 endl; }
private:int d1;
};typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout 虚表地址 vTable endl;for (int i 0; vTable[i] ! nullptr; i){printf( 第%d个虚函数地址 :0X%x,-, i, vTable[i]);VFPTR f vTable[i];f();}cout endl;
}int main()
{Derive d;VFPTR* vTableb1 reinterpret_castVFPTR*(*reinterpret_castintptr_t*(d));PrintVTable(vTableb1);VFPTR* vTableb2 *reinterpret_castVFPTR**(reinterpret_castchar*(d) sizeof(Base1));PrintVTable(vTableb2);return 0;
}
输出结果为 多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中。 结束语
把有关多态的一些基础内容写了一下。
感谢各位大佬的支持
希望这篇文章对您理解C多态有所帮助
求点赞收藏评论关注