C++笔记之实现多态的所有方法

C++笔记之实现多态的所有方法

code review!

文章目录

  • C++笔记之实现多态的所有方法
    • 1.C++中多态是是什么?请用简洁准确的话描述
    • 2.虚函数实现多态
      • 2.1.虚函数(Virtual Functions)
      • 2.2.纯虚函数(Pure Virtual Functions)
      • 2.3.虚析构函数(Virtual Destructor)
      • 2.4.虚函数表(Virtual Function Table)
    • 3.函数指针(Function Pointers)
    • 4.模板(Templates)
    • 5.达到多态性的效果
      • 5.1.接口类(Interface Classes)
      • 5.2.可调用对象(Callable Objects)
    • 6.C++中函数重载是不是一种实现多态的方法?
    • 7.虚函数表
    • 8.虚函数指针
    • 9.抄自别的文章——C++中虚函数的实现
      • 虚函数表(vtable)
      • 虚函数指针(vptr)
      • 多态的工作机制
      • 注意事项

1.C++中多态是是什么?请用简洁准确的话描述

在这里插入图片描述

2.虚函数实现多态

在C++中,实现多态性(polymorphism)的主要方法是通过使用虚函数(virtual functions)。下面是C++中实现多态性的方法:

2.1.虚函数(Virtual Functions)

在基类中声明一个虚函数,在派生类中可以对该函数进行重写。通过使用指向基类的指针或引用来调用该函数时,实际上会根据对象的实际类型调用相应的派生类函数。这是C++中最常见的多态性实现方式。

class Base {
public:virtual void polymorphicFunction() {// Base class implementation}
};class Derived : public Base {
public:void polymorphicFunction() override {// Derived class implementation}
};

2.2.纯虚函数(Pure Virtual Functions)

纯虚函数是在基类中声明的虚函数,但没有提供实际实现。派生类必须重写这些纯虚函数才能被实例化。类包含纯虚函数的类称为抽象类,抽象类不能被实例化,只能被继承。

class AbstractBase {
public:virtual void pureVirtualFunction() = 0; // Pure virtual function
};class Derived : public AbstractBase {
public:void pureVirtualFunction() override {// Derived class implementation}
};

2.3.虚析构函数(Virtual Destructor)

如果基类需要被其他类继承,而且基类中有使用了new关键字动态分配内存的成员,那么必须在基类中将析构函数声明为虚函数,以确保在删除指向派生类对象的基类指针时能正确调用派生类的析构函数,避免内存泄漏。

class Base {
public:virtual ~Base() {// Base class destructor}
};class Derived : public Base {
public:~Derived() override {// Derived class destructor}
};

2.4.虚函数表(Virtual Function Table)

C++编译器通过在包含虚函数的类中插入一个虚函数表来实现多态性。该表包含了类的虚函数地址,允许在运行时动态调用正确的函数实现。

这些是C++中实现多态性的主要方法。虚函数和纯虚函数是最常见和推荐的实现方式。

除了使用虚函数,C++中还有另外两种实现多态性的方法:

3.函数指针(Function Pointers)

使用函数指针可以在运行时实现多态性。通过定义一个指向函数的指针,然后在派生类中赋值为相应的函数,可以实现动态调用不同的函数实现。

class Base {
public:void nonPolymorphicFunction() {// Base class implementation}
};class Derived : public Base {
public:void polymorphicFunction() {// Derived class implementation}
};int main() {Base* ptr;Derived derivedObj;ptr = &derivedObj;// Function pointer for polymorphic behaviorvoid (Base::*functionPtr)() = &Base::nonPolymorphicFunction;(ptr->*functionPtr)(); // Calls Base::nonPolymorphicFunction()functionPtr = &Base::polymorphicFunction;(ptr->*functionPtr)(); // Calls Derived::polymorphicFunction()return 0;
}

4.模板(Templates)

C++的模板也可以实现多态性,特别是通过模板类和模板函数。使用模板参数,可以在编译时根据不同的参数类型生成不同的代码,从而实现多态性。

template <typename T>
class PolymorphicClass {
public:void polymorphicFunction() {// Common implementation for all types}
};template <>
void PolymorphicClass<int>::polymorphicFunction() {// Implementation specific to int
}int main() {PolymorphicClass<float> floatObj;PolymorphicClass<int> intObj;floatObj.polymorphicFunction(); // Calls the common implementationintObj.polymorphicFunction();   // Calls the specialization for intreturn 0;
}

虽然虚函数是实现多态性的最常见和推荐方法,但是这两种方法也提供了额外的灵活性,特别是在特定情况下可以使用函数指针来实现更高度定制化的多态性。模板也可以在编译时优化代码,提高性能。但这些方法相对较少使用,主要是因为虚函数在C++中提供了更为直接和方便的多态性实现。

5.达到多态性的效果

除了前面提到的虚函数、函数指针、模板和虚拟继承,C++中没有其他内置的语言特性来直接实现多态性。这些是C++中主要的方法来实现多态行为。

然而,C++是一门非常灵活的语言,允许通过各种技术和设计模式来实现更高级的多态性。下面列举了一些可能的方法,虽然它们不是直接的语言特性,但仍然可以达到多态性的效果:

5.1.接口类(Interface Classes)

C++中没有内置的接口类概念,但可以通过定义只包含纯虚函数的抽象类来模拟接口。其他类可以继承这个接口类并实现其纯虚函数,从而实现多态性。

class Interface {
public:virtual void doSomething() = 0;
};class ConcreteClass : public Interface {
public:void doSomething() override {// Implementation in ConcreteClass}
};

5.2.可调用对象(Callable Objects)

C++中的函数对象(Functor)或Lambda表达式可以作为可调用对象,使得可以在运行时动态决定调用哪个函数,从而实现多态性。

#include <functional>class Base {
public:virtual void polymorphicFunction() {// Base class implementation}
};class Derived : public Base {
public:void polymorphicFunction() override {// Derived class implementation}
};int main() {Base* ptr = new Derived;// Using std::function as a callable object for polymorphic behaviorstd::function<void(Base*)> func = [](Base* obj) { obj->polymorphicFunction(); };func(ptr); // Calls Derived::polymorphicFunction()return 0;
}

这些方法都是在C++中模拟多态性的常见手段。请注意,这些方法可能需要更多的代码和额外的管理,相比于虚函数的直接性和简洁性,可能并不是首选方案。因此,虚函数仍然是实现多态性的最常用和推荐的方式。

6.C++中函数重载是不是一种实现多态的方法?

在严格意义上,C++中的函数重载并不被认为是一种实现多态的方法。

函数重载是一种在同一个作用域内定义多个同名函数,但参数列表不同的机制。当调用这个函数时,编译器会根据传递的参数类型或数量来决定调用哪个具体的函数。函数重载主要用于提供一种方便的方式,让程序员能够使用相同的函数名处理不同类型或不同数量的数据,以增强代码的可读性和灵活性。

虽然函数重载使得代码更具灵活性和可读性,但它并没有实现多态性。多态性涉及到在运行时选择调用哪个函数的能力,这通常通过虚函数来实现,允许根据对象的实际类型来调用适当的函数。

在函数重载中,函数调用在编译时就已经确定了,不会根据对象的实际类型在运行时决定调用哪个函数。相比之下,使用虚函数时,函数调用是在运行时动态绑定的,允许在处理基类指针或引用时调用派生类中的函数实现,实现了真正的多态性。

总结来说,函数重载和多态性是不同的概念。函数重载是一种静态多态,而多态性通常指动态多态,它通过虚函数实现,允许在运行时根据对象的实际类型来选择调用适当的函数。

7.虚函数表

虚函数表(Virtual Function Table),通常简称为虚表,是C++中实现多态性的关键机制之一。它是一个用于存储虚函数地址的表格,每个具有虚函数的类都会在内存中维护一个虚函数表。

虚函数表的工作原理如下:

  1. 虚函数表是一个包含指向虚函数的指针的数组。每个虚函数在表中占据一个槽位。
  2. 对于每个包含虚函数的类,编译器会在类的布局中插入一个指向其虚函数表的指针,通常称为虚函数指针(vptr)。
  3. 当一个对象被创建时,它的虚函数指针被初始化为指向该类的虚函数表。
  4. 调用虚函数时,实际上是通过对象的虚函数指针查找虚函数表,然后调用相应的虚函数。

这个机制使得基类指针或引用可以在运行时动态地调用派生类中适当的虚函数,从而实现多态性。

以下是一个简化的示例来说明虚函数表的概念:

class Base {
public:virtual void show() {std::cout << "Base class" << std::endl;}
};class Derived : public Base {
public:void show() override {std::cout << "Derived class" << std::endl;}
};int main() {Base* basePtr;Derived derivedObj;basePtr = &derivedObj;basePtr->show(); // Calls Derived::show() through the virtual function tablereturn 0;
}

在这个示例中,Base 类和 Derived 类都有一个虚函数 show()。当通过 basePtr->show() 调用虚函数时,实际上会根据对象 derivedObj 的实际类型在虚函数表中查找正确的虚函数地址,然后调用 Derived::show()。这就是虚函数表的工作方式。

虚函数表的内部结构在不同的编译器和平台上可能会有所不同。我无法在这里直接为您提供一个完整的虚函数表的打印输出,因为虚函数表的具体结构可能会受到编译器、类的层次结构以及继承关系等因素的影响。

但是,我可以向您展示一个概念性的虚函数表结构,帮助您理解它的大致原理。请注意,这只是一个示意图,实际虚函数表的结构可能会更加复杂。

假设我们有一个类 Base 和一个派生类 Derived,它们都有一个虚函数 show()

虚函数表示意图:Base类虚函数表
+---------------------+
| 指向 Base::show()   |
+---------------------+Derived类虚函数表(继承自Base类)
+---------------------+
| 指向 Derived::show()|
+---------------------+

在这个示意图中,每个类都有一个指向其虚函数的指针,这个指针构成了虚函数表的一部分。当一个类没有虚函数时,它的虚函数表可能为空。派生类会继承基类的虚函数表,并在需要时进行覆盖。这样,通过对象的虚函数指针,可以在运行时查找到正确的虚函数实现。

请注意,虚函数表的实际结构会受到编译器优化和内存布局等因素的影响,因此它的具体形式可能会因编译器而异。

8.虚函数指针

虚函数指针的具体样子会受到编译器和平台的影响,因此无法直接为您提供精确的打印输出。虚函数指针通常是一个指向虚函数表的指针,用于在运行时查找和调用适当的虚函数。

以下是一个简化的示例来说明虚函数指针的概念:

#include <iostream>class Base {
public:virtual void show() {std::cout << "Base class" << std::endl;}
};class Derived : public Base {
public:void show() override {std::cout << "Derived class" << std::endl;}
};int main() {Base* basePtr;Derived derivedObj;basePtr = &derivedObj;// 虚函数指针的示意,这是一个虚函数表的指针void** vptr = reinterpret_cast<void**>(basePtr);// 第一个槽位通常是指向虚函数表本身的指针,后面是实际的虚函数指针// 注意:实际虚函数指针的内容可能因编译器和平台而异void* firstSlot = *vptr;void* showFunctionPtr = *(vptr + 1);std::cout << "Address of virtual function table: " << firstSlot << std::endl;std::cout << "Address of show() function: " << showFunctionPtr << std::endl;return 0;
}

请注意,这个示例中展示的内容是概念性的,实际虚函数指针的结构会更加复杂,并且受到编译器和平台的影响。虚函数指针通常位于对象的内存布局的开头部分,并指向类的虚函数表。在运行时,通过虚函数指针查找虚函数表,然后找到正确的虚函数地址进行调用。

9.抄自别的文章——C++中虚函数的实现

在这里插入图片描述
C++ 中虚函数的实现涉及到虚函数表(vtable)和虚函数指针(vptr),这是实现多态性的关键。

虚函数表和虚函数指针允许程序在运行时确定要调用的函数,而不是在编译时确定。

虚函数表(vtable)

对于每个拥有虚函数的类,编译器会生成一个虚函数表,通常在类的内部,作为类的一部分。这个虚函数表是一个指向函数指针的数组,其中包含了该类中每个虚函数的地址。虚函数表的第一个函数指针通常指向类的析构函数。接下来是按照虚函数在类中声明的顺序排列的函数指针。子类将继承父类的虚函数表,并可以在其末尾添加自己的虚函数指针。

虚函数指针(vptr)

对于每个类的对象,编译器会生成一个虚函数指针(通常称为vptr),该指针指向对象所属类的虚函数表。这个vptr通常位于对象的内存布局的开头。当调用虚函数时,实际上是通过对象的vptr找到正确的虚函数表,然后在虚函数表中查找要调用的函数的地址,最后执行该函数。

多态的工作机制

当通过基类指针或引用调用虚函数时,程序首先访问对象的vptr,然后从虚函数表中找到要调用的函数。这个机制允许在运行时根据对象的实际类型而不是指针或引用的静态类型来调用正确的函数,实现了多态性。

注意事项

构造函数不能是虚函数,因为在对象的构造过程中,虚函数表可能尚未完全设置。而析构函数可以为虚函数。C++标准并未规定虚函数表和虚函数指针的实现方式,因此不同编译器可能有不同的底层实现。但在大多数情况下,它们遵循上述概念。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/160217.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【【嵌入式开发 Linux 常用命令系列 10 -- Linux 修改终端下 ls 各种类型文件的显示颜色】

文章目录 Linux 修改终端下各种类型文件的显示颜色LS_COLORS 详细介绍 Linux 修改终端下各种类型文件的显示颜色 在 ~/.bashrc 文件最下面添加如下内容&#xff0c;就可以配置目录、文件、sh类型文件的颜色了。 export LS_COLORSdi1:fi0:*.sh33:$LS_COLORS这句话的意思就是在…

【C语法学习】15 - fopen()函数

文章目录 1 函数原型2 返回值3 参数3.1 文件名3.2 模式3.2.1 以"r"模式打开3.2.2 以"w"模式打开3.2.3 以"a"模式打开3.2.4 以"r"模式打开3.2.5 以"w"模式打开3.2.6 以"a"模式打开 1 函数原型 fopen()&#xff1a…

排序篇(六)----排序小结

排序篇(六)----排序小结 排序算法复杂度及稳定性分析 直接插入排序的算法复杂度&#xff1a; 最好情况下&#xff0c;当数组已经有序时&#xff0c;直接插入排序的时间复杂度为O(n)&#xff0c;其中n是数组的大小。最坏情况下&#xff0c;当数组逆序排列时&#xff0c;直接插…

selenium自动化测试入门 —— 定位frame和iframe中的元素对象

< frame> <iframe> 标签&#xff0c;浏览器会在标签中打开一个特定的页面窗口&#xff08;框架&#xff09;&#xff0c;它在本窗口中嵌套进入一个网页&#xff0c;当用selenium定位页面元素的时候会遇到定位不到frame框架内的元素的问题。 定位frame中的元素前我…

大数据(十):数据可视化(二)

专栏介绍 结合自身经验和内部资料总结的Python教程&#xff0c;每天3-5章&#xff0c;最短1个月就能全方位的完成Python的学习并进行实战开发&#xff0c;学完了定能成为大佬&#xff01;加油吧&#xff01;卷起来&#xff01; 全部文章请访问专栏&#xff1a;《Python全栈教…

电路正负反馈,电压电流反馈,串并联反馈详细判别方法

正/负反馈&#xff1a;假设输出升高&#xff0c;转一圈回来仍使其升高就是正反馈&#xff0c;反之就是负反馈。作图法&#xff1a;在RL的信号端画一个向上的小箭头&#xff0c;沿着反馈环路&#xff0c;每经过一个元器件就画一个相应的箭头&#xff0c;一直画到放大器的输出端&…

DBeaver关闭代码的提示

在DBeaver中会遇到如下现象&#xff0c;很烦&#xff0c;怎么取消这个提示框呢&#xff1f; 解决方案&#xff1a;

antv/g6元素之combo

介绍 在 G6 中&#xff0c;“Combo” 是一种特殊的元素&#xff0c;用于组合和展示多个节点元素的一种方式。它通常用于表示一个组或子图&#xff0c;将多个相关节点组织在一起&#xff0c;并在图形中以单一的形状显示。 属性 type&#xff1a;Combo 的类型&#xff0c;通常是…

《python深度学习》笔记(二十):神经网络的解释方法之CAM、Grad-CAM、Grad-CAM++、LayerCAM

原理优点缺点GAP将多维特征映射降维为一个固定长度的特征向量①减少了模型的参数量&#xff1b;②保留更多的空间位置信息&#xff1b;③可并行计算&#xff0c;计算效率高&#xff1b;④具有一定程度的不变性①可能导致信息的损失&#xff1b;②忽略不同尺度的空间信息CAM利用…

​ iOS App Store上传项目报错 缺少隐私政策网址(URL)解决方法

一、问题如下图所示&#xff1a; ​ 二、解决办法&#xff1a;使用Google浏览器&#xff08;翻译成中文&#xff09;直接打开该网址 https://www.freeprivacypolicy.com/free-privacy-policy-generator.php 按照要求填写APP信息&#xff0c;最后将生成的网址复制粘贴到隐私…

BIOS开发笔记 - DDR中的时序参数

通过前一篇文章学习,我们可以大致知道内存条(Module)的组成及SDRAM内部的结构,这一篇再介绍下SDRAM中常见的时序参数以及整个读写操作的流程。 一、外部信号 图1 DDR4的外部线路图 DDR是一种高带宽的传输接口,其外部信号较多,图1是一个DDR4的外部线路图,以下对图中跟通…

Open3D(C++) 最小二乘拟合平面(间接平差法)

目录 一、算法原理1、原理概述2、参考文献二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。 一、算法原理 1、原理概述 通过传统最小二乘法对点云数据进行平面拟合时,可将误差只归因于一个方向上,本文假设误差只存在于 Z Z