班级网站设计模板wordpress 程序更新
- 作者: 五速梦信息网
- 时间: 2026年03月21日 10:04
当前位置: 首页 > news >正文
班级网站设计模板,wordpress 程序更新,成都怎么成立网站,网站开发有哪些书籍文章目录 C 多态详解#xff08;进阶篇#xff09;前言第一章#xff1a;多态的原理1.1 虚函数表的概念1.1.1 虚函数表的生成过程 1.2 虚表的存储位置 第二章#xff1a;动态绑定与静态绑定2.1 静态绑定2.1.1 静态绑定的实现机制#xff1a;2.1.2 示例代码#xff1a; 2.… 文章目录 C 多态详解进阶篇前言第一章多态的原理1.1 虚函数表的概念1.1.1 虚函数表的生成过程 1.2 虚表的存储位置 第二章动态绑定与静态绑定2.1 静态绑定2.1.1 静态绑定的实现机制2.1.2 示例代码 2.2 动态绑定2.2.1 动态绑定的实现机制2.2.2 示例代码2.2.3 动态绑定的汇编分析 2.3 静态绑定与动态绑定的区别2.3.1 编译时绑定 vs 运行时绑定2.3.2 选择何时使用静态或动态绑定2.3.3 示例对比 第三章单继承和多继承中的虚函数表3.1 单继承中的虚函数表3.1.1 虚表的结构3.1.2 单继承虚表示例3.1.3 生成的虚函数表结构 3.2 多继承中的虚函数表3.2.1 多继承虚表示例3.2.2 多继承虚函数表的结构 3.3 菱形继承中的虚函数表3.3.1 菱形继承的问题示例3.3.2 虚拟继承的解决方案3.3.3 虚拟继承下的内存布局3.3.3.1 Base 类的内存布局 3.3.3.2 Derived1 类的内存布局3.3.3.3 Derived2 类的内存布局3.3.3.4 Final 类的内存布局 3.3.4 调用过程解析3.3.5 小结 写在最后 C 多态详解进阶篇 欢迎讨论在学习过程中如果有任何疑问或想法欢迎在评论区留言一起讨论。 点赞、收藏与分享觉得这篇文章对你有帮助吗记得点赞、收藏并分享给更多的朋友吧你们的支持是我不断进步的动力 分享给更多人如果你觉得这篇文章对你有帮助欢迎分享给更多对 C 感兴趣的朋友一起学习进步 前言 在 C 中多态Polymorphism是一种允许不同对象通过同一接口表现不同行为的机制。通过继承和虚函数的结合多态为程序设计提供了灵活性和可扩展性。上一章我们讨论了多态的基础知识涵盖了虚函数的基本概念及实现。这一章我们将深入分析多态的原理包括虚函数表的构造及其在单继承和多继承中的表现以及如何通过动态绑定实现灵活的函数调用。 第一章多态的原理
1.1 虚函数表的概念
虚函数表Virtual Table, VTable是 C 实现运行时多态的核心机制。它是一个存储虚函数指针的数组每个包含虚函数的类都至少有一个虚表。当一个类的虚函数被调用时程序并不是直接调用函数的地址而是通过虚函数表间接调用。每个对象实例都会保存一个指向虚表的指针vptr通过 vptr程序可以找到对象对应的虚函数实现。
1.1.1 虚函数表的生成过程
继承基类虚表当一个派生类继承了基类并且基类包含虚函数时派生类会继承基类的虚表。覆盖虚函数如果派生类重写了基类的虚函数则派生类的虚表中会用派生类的函数覆盖基类的函数。派生类新函数派生类新增的虚函数会被添加到虚表的末尾。
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;
};class Derived : public Base {
public:void func1() override {cout Derived::func1 endl;}virtual void func4() {cout Derived::func4 endl;}
private:int _d;
};int main() {Base b;Derived d;return 0;
}在这个例子中Derived 类重写了 Base 的 func1并且增加了一个新的虚函数 func4。此时 Derived 的虚表包含重写的 func1以及新增的 func4但不会包含 func3因为它不是虚函数。
1.2 虚表的存储位置 虚表并不存储在对象内部。每个对象只包含一个指向虚表的指针vptr。虚表本身存储在程序的全局静态区中。每个包含虚函数的类的所有对象共享同一个虚表而 vptr 是指向这个表的指针。虚表中记录了类中所有虚函数的地址用于动态绑定函数调用。 第二章动态绑定与静态绑定
2.1 静态绑定 静态绑定Static Binding也称为早期绑定是在编译阶段决定函数调用的过程。编译器通过变量的静态类型即声明时的类型来确定调用的函数。这意味着函数的地址在编译时就已经确定调用效率较高。通常非虚函数和普通函数使用静态绑定。 2.1.1 静态绑定的实现机制
对于静态绑定编译器根据对象的声明类型直接生成目标代码。在这种情况下编译器会直接调用函数的地址而不需要在运行时查找。
2.1.2 示例代码
class Base {
public:void print() {cout Base::print() endl;}
};int main() {Base b;b.print(); // 静态绑定编译时已确定调用 Base::printreturn 0;
}在这个例子中print() 是一个非虚函数因此编译器在编译时已经决定了 Base::print() 将被调用。这就是静态绑定的体现函数的调用在编译时已知执行时无需额外的查找。
2.2 动态绑定 动态绑定Dynamic Binding也称为晚期绑定是在程序运行时根据对象的实际类型而非声明类型决定函数调用的过程。通过使用虚函数动态绑定允许程序在运行时灵活选择调用哪一个派生类的函数。这种绑定方式依赖于虚函数表VTable机制。 2.2.1 动态绑定的实现机制
动态绑定通过虚函数表实现。虚函数表是一个存储类中虚函数地址的数组。每个包含虚函数的类都拥有一个虚函数表每个对象实例通过一个指针vptr指向它所属类的虚函数表。当程序调用虚函数时实际的调用流程如下
通过对象找到虚表指针vptr。根据虚函数的偏移量从虚表中获取函数指针。通过函数指针进行实际的函数调用。
2.2.2 示例代码
class Base {
public:virtual void print() {cout Base::print() endl;}
};class Derived : public Base {
public:void print() override {cout Derived::print() endl;}
};int main() {Base* basePtr new Derived();basePtr-print(); // 动态绑定运行时调用 Derived::print()delete basePtr;return 0;
}在这个例子中print() 是一个虚函数。当 basePtr 指向 Derived 对象时尽管 basePtr 的类型是 Base但在运行时程序通过虚表找到 Derived::print() 并调用它。这就是动态绑定的核心通过虚表间接调用函数。
2.2.3 动态绑定的汇编分析
动态绑定的底层实现可以通过汇编代码更直观地理解。在调用虚函数时编译器会生成以下类似的汇编代码
参考示例读者大大可以自己通过调试来看喔
mov eax, dword ptr [basePtr] ; 加载 basePtr 对象的地址到寄存器 eax
mov edx, dword ptr [eax] ; 通过 eax 获取虚函数表指针vptr
mov eax, dword ptr [edx] ; 从虚表中获取实际虚函数的地址
call eax ; 调用虚函数这段汇编代码可以解释为
mov eax, dword ptr [basePtr]将对象 basePtr 的值加载到寄存器 eax 中eax 此时存放了 basePtr 对象的地址。mov edx, dword ptr [eax]通过对象地址获取对象的虚函数表指针 vptr并存放在 edx 中。mov eax, dword ptr [edx]从虚函数表中获取对应虚函数的地址。call eax调用虚函数的实际地址。
通过这一流程可以看到动态绑定并不是直接调用函数地址而是通过虚表间接访问函数地址这就是动态绑定的底层实现。 2.3 静态绑定与动态绑定的区别
2.3.1 编译时绑定 vs 运行时绑定
静态绑定函数调用在编译阶段就已确定调用效率高。适用于非虚函数和静态函数。动态绑定函数调用在运行时决定灵活性高但引入了一定的性能开销。适用于虚函数尤其是在多态场景中。
2.3.2 选择何时使用静态或动态绑定
性能要求高的场景如果程序的性能要求高并且函数调用的多态性不强可以选择静态绑定。静态绑定没有虚表的查找开销。多态需求强的场景当需要通过基类指针或引用调用派生类的不同实现时动态绑定是必不可少的。虚函数通过虚表机制可以在运行时调用不同的派生类函数。
2.3.3 示例对比
class Base {
public:void staticPrint() { // 静态绑定cout Base static print endl;}virtual void dynamicPrint() { // 动态绑定cout Base dynamic print endl;}
};class Derived : public Base {
public:void staticPrint() { // 覆盖非虚函数仍是静态绑定cout Derived static print endl;}void dynamicPrint() override { // 重写虚函数动态绑定cout Derived dynamic print endl;}
};int main() {Base ptr new Derived();ptr-staticPrint(); // 静态绑定调用 Base::staticPrintptr-dynamicPrint(); // 动态绑定调用 Derived::dynamicPrintdelete ptr;return 0;
}在这个例子中staticPrint() 是静态绑定而 dynamicPrint() 是动态绑定。尽管 ptr 指向的是 Derived 对象但因为 staticPrint() 是静态绑定调用的是 Base 类中的实现。而 dynamicPrint() 是虚函数最终调用的是 Derived 类中的实现。 第三章单继承和多继承中的虚函数表
3.1 单继承中的虚函数表 在单继承的场景下派生类会继承基类的虚函数表VTable。当派生类重写了基类的虚函数时虚表中的基类函数指针会被派生类函数的指针替换。如果派生类定义了新的虚函数这些新的虚函数指针将会追加到派生类的虚表末尾。 3.1.1 虚表的结构
在单继承的情况下虚表的构造过程可以分为以下几个步骤
继承基类虚表当派生类继承了基类并且基类中含有虚函数时派生类会自动继承基类的虚表。派生类可以通过这个虚表调用基类中的虚函数。重写虚函数如果派生类重写了基类的虚函数则派生类的虚表中相应的函数指针将会被覆盖为派生类的函数实现重写的虚函数替换基类的虚函数。添加新虚函数派生类定义的新虚函数会被添加到派生类的虚表末尾。
3.1.2 单继承虚表示例
class Base {
public:virtual void func1() { cout Base::func1() endl; }virtual void func2() { cout Base::func2() endl; }
};class Derived : public Base {
public:void func1() override { cout Derived::func1() endl; }virtual void func3() { cout Derived::func3() endl; }
};在这个例子中
Base 类中有两个虚函数 func1() 和 func2()这些虚函数的地址会存储在 Base 类的虚表中。Derived 类继承了 Base 类并且重写了 func1()。因此Derived 类的虚表会用 Derived::func1() 的地址替换 Base::func1() 的地址而 func2() 仍然指向 Base::func2()。Derived 类定义了一个新的虚函数 func3()因此 func3() 的指针会被追加到 Derived 类的虚表末尾。
3.1.3 生成的虚函数表结构 Base 类虚表 func1 - Base::func1func2 - Base::func2 Derived 类虚表 func1 - Derived::func1替换了基类的 func1func2 - Base::func2func3 - Derived::func3新增 3.2 多继承中的虚函数表 在多继承中派生类继承自多个基类每个基类都有自己的虚函数表。派生类会为每个基类维护一个独立的虚表来存储对应基类的虚函数指针。当调用虚函数时派生类会根据继承自哪个基类选择相应的虚表来查找虚函数的地址。 3.2.1 多继承虚表示例
class Base1 {
public:virtual void func1() { cout Base1::func1() endl; }
};class Base2 {
public:virtual void func2() { cout Base2::func2() endl; }
};class Derived : public Base1, public Base2 {
public:void func1() override { cout Derived::func1() endl; }void func2() override { cout Derived::func2() endl; }
};在这个例子中Derived 类从 Base1 和 Base2 继承。Derived 类会生成两个虚表一个用于继承自 Base1 的虚函数另一个用于继承自 Base2 的虚函数。
3.2.2 多继承虚函数表的结构 Base1 类虚表 func1 - Base1::func1 Base2 类虚表 func2 - Base2::func2 Derived 类虚表 Base1 虚表 - func1 - Derived::func1Derived 重写了 Base1 的虚函数 func1Base2 虚表 - func2 - Derived::func2Derived 重写了 Base2 的虚函数 func2
多继承的情况下派生类会为每个基类生成单独的虚表当调用派生类的虚函数时会根据调用的基类函数选择相应的虚表。例如当调用 Derived 对象的 func1() 时程序会访问 Base1 的虚表而调用 func2() 时程序会访问 Base2 的虚表。 3.3 菱形继承中的虚函数表
菱形继承指的是派生类通过两个基类继承而这两个基类又继承自同一个公共祖先类。由于这种继承路径派生类可能会从多个路径继承相同的基类从而产生两个问题
数据冗余每条继承路径都会创建一个独立的基类实例导致派生类中出现多个相同的基类副本。函数调用的二义性如果公共基类中有虚函数派生类会有多个虚函数表导致编译器不知道调用哪个基类的虚函数实现。
3.3.1 菱形继承的问题示例
以下是一个典型的菱形继承示例
class Base {
public:virtual void func() { cout Base::func() endl; }
};class Derived1 : public Base {};
class Derived2 : public Base {};class Final : public Derived1, public Derived2 {};在这个例子中
Derived1 和 Derived2 都继承自 Base。Final 类通过 Derived1 和 Derived2 继承自 Base。
由于没有使用虚拟继承Final 类会继承两个独立的 Base 类实例这就带来了以下问题
数据冗余Final 类将会有两个 Base 类的实例。函数调用的二义性如果我们调用 Final 对象的 func() 方法编译器不知道该调用 Derived1 的 Base::func() 还是 Derived2 的 Base::func()。
3.3.2 虚拟继承的解决方案
为了解决菱形继承带来的数据冗余和函数调用二义性问题C 提供了虚拟继承。通过虚拟继承派生类只会保留一个公共基类的实例而不是在每条继承路径上都生成一个基类实例。
class Base {
public:virtual void func() { cout Base::func() endl; }
};class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};class Final : public Derived1, public Derived2 {};在这个版本中Derived1 和 Derived2 都使用了 虚拟继承virtual public Base。这意味着 Final 类最终只有一个 Base 类实例解决了以下两个问题
数据冗余无论通过多少条继承路径Final 类中最终只有一个 Base 实例。函数调用的二义性因为 Final 类中只有一个 Base 实例虚函数调用时不会产生二义性。
3.3.3 虚拟继承下的内存布局
在使用虚拟继承时类的内存布局变得更加复杂特别是对于菱形继承的情况。我们会通过图解展示 Base、Derived1、Derived2 和 Final 类的内存布局重点关注 虚函数表指针vptr 和 虚基表指针vbase ptr。 3.3.3.1 Base 类的内存布局
Base 类有一个 虚函数表指针vptr它指向虚函数表用于记录 func() 函数的地址。
Base 内存布局
| vptr - Base VTable |
|---|
| 其他成员数据如果有 |
——————-Base VTable 包含虚函数 Base::func() 的地址。
Base VTable
| func - Base::func| ——————3.3.3.2 Derived1 类的内存布局 Derived1 通过虚拟继承自 Base因此它不会直接包含 Base 的实例。相反它有两个指针 虚函数表指针vptr指向 Derived1 的虚函数表用于存储虚函数的地址。虚基表指针vbase ptr指向共享的 Base 类实例。
Derived1 内存布局
| vptr - Derived1 VTable |
|---|
| vbase ptr - Base |
———————–Derived1 VTable 继承了 Base::func()因此虚函数表包含 Base::func() 的地址。
Derived1 VTable
| func - Base::func | ——————-3.3.3.3 Derived2 类的内存布局 Derived2 也通过虚拟继承自 Base因此它的内存布局与 Derived1 类似。 虚函数表指针vptr指向 Derived2 的虚函数表。虚基表指针vbase ptr指向 Base 类的唯一实例。
Derived2 内存布局
| vptr - Derived2 VTable |
|---|
| vbase ptr - Base |
———————–Derived2 VTable 同样继承了 Base::func()。
Derived2 VTable
| func - Base::func | ——————-3.3.3.4 Final 类的内存布局 Final 类通过 Derived1 和 Derived2 继承 Base它拥有 两个 虚函数表指针vptr分别指向 Derived1 和 Derived2 的虚函数表。两个 虚基表指针vbase ptr它们都指向唯一的 Base 实例。唯一的 Base 类实例。
Final 内存布局
| vptr - Derived1 VTable |
|---|
| vbase ptr - Base |
| vptr - Derived2 VTable |
|---|
| vbase ptr - Base |
| Base | (唯一的 Base 实例) ————————Final 类通过 虚基表指针vbase ptr 确保它共享同一个 Base 类实例而不会有多个 Base 类副本。 3.3.4 调用过程解析 当我们调用 Final 类对象的 func() 方法时虚拟继承保证调用过程如下 Final 类中的 vptr虚函数表指针指向唯一的 Base 类实例的虚函数表。Final 类中的 vbase ptr虚基表指针确保所有路径指向唯一的 Base 类实例。最终通过虚表找到 Base::func() 的地址并执行避免了函数调用的二义性。 int main() {Final f;f.func(); // 调用 Base::func()只有一个 Base 实例return 0; }输出 Base::func()3.3.5 小结 虚拟继承消除了冗余通过虚基表Final 类只会包含一个 Base 类实例避免了菱形继承中的数据冗余。函数调用无二义性虚拟继承保证了虚表指针只指向唯一的 Base 实例从而解决了函数调用时的歧义。 写在最后 在这篇文章中我们深入探索了 C 中的多态机制从静态绑定与动态绑定的差异到虚函数表VTable背后的运作原理再到菱形继承中的虚拟继承解决方案逐步揭开了多态在编程中的神秘面纱。我们看到了 C 如何通过虚表实现动态调用的灵活性如何在多继承和虚拟继承中有效解决基类重复和函数调用二义性的问题。通过掌握这些知识不仅能够更高效地设计系统还能够在实际项目中运用多态的强大力量使代码更加灵活、可扩展。 多态不仅仅是编程的一个特性它更像是一首灵动的乐章让代码在不同对象之间自由流动展现出不同的形态与生命力。在未来的开发中多态将继续成为构建强大、灵活系统的核心要素值得我们深入研究与灵活运用。 以上就是关于【C篇】虚境探微多态的流动诗篇解锁动态的艺术密码的内容啦各位大佬有什么问题欢迎在评论区指正或者私信我也是可以的啦您的支持是我创作的最大动力❤️
- 上一篇: 佰汇康网站建设做海外市场什么网站推广
- 下一篇: 搬瓦工做网站优化建站
相关文章
-
佰汇康网站建设做海外市场什么网站推广
佰汇康网站建设做海外市场什么网站推广
- 技术栈
- 2026年03月21日
-
百色住房和城乡建设部网站国涟建设集团有限公司网站
百色住房和城乡建设部网站国涟建设集团有限公司网站
- 技术栈
- 2026年03月21日
-
百科网站源码佛山怎么做网站
百科网站源码佛山怎么做网站
- 技术栈
- 2026年03月21日
-
搬瓦工做网站优化建站
搬瓦工做网站优化建站
- 技术栈
- 2026年03月21日
-
版面设计图大全 模板网络优化公司有哪些
版面设计图大全 模板网络优化公司有哪些
- 技术栈
- 2026年03月21日
-
办公家具网站建设公司淘宝网站建设分析
办公家具网站建设公司淘宝网站建设分析
- 技术栈
- 2026年03月21日






