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

news2024/11/24 3:20:12

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

文章目录

  • 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 behavior
    void (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 implementation
    intObj.polymorphicFunction();   // Calls the specialization for int
    return 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 behavior
    std::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 table
    return 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.coloradmin.cn/o/1168064.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

lesson3-C++类和对象(下)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 再谈构造函数 构造函数体赋值 初始化列表 explicit关键字 Static成员 概念 特性 友元 友元函数 友元类 内部类 匿名对象 拷贝对象时的一些编译器优化 再次理解封装 练习题 再谈构造函数 构造函数体赋值…

瑞禧生物分享~普鲁士蓝纳米酶 Prussian Blue PB

名称&#xff1a;普鲁士蓝纳米酶&#xff1a;Fe4[Fe(CN)6]3、KFe[Fe(CN)6] NZs 包装&#xff1a;1mg/ml 应用领域&#xff1a;可作为抗氧化、抗炎新材料&#xff0c;同时具有载药、光热疗、炎症组织成像、构建生物传感器件等领域。 普鲁土蓝(Prusian Blue)&#xff0c;是一种…

gma 2 教程(三)坐标参考系统:3.投影方法

安装 gma&#xff1a;pip install gma 地图投影是利用一定数学法则把地球表面的经、纬线转换到平面上的理论和方法。由于地球是一个赤道略宽两极略扁的不规则的梨形球体&#xff0c;故其表面是一个不可展平的曲面&#xff0c;所以运用任何数学方法进行这种转换都会产生误差和变…

tbh常用的绘图快捷键

1、Altb -> 笔刷 2、Alt/ -> 画笔 3、按住Shift 绘出的线条是直线 4、按住shiftalt 绘出来的线条是水平线或垂直线 5、alte ->橡皮擦 6、alts ->选择工具 7、altq -> 轮廓编辑器 以下操作都是在选中轮廓编辑器下操作的&#xff1a; 按住alt…

高效管理:在文件夹名称左边添加关键字,实现批量重命名

在高效的文件管理中&#xff0c;对文件夹进行合理命名和重命名是十分关键的。有时候&#xff0c;我们可能需要在一批文件夹的名称左边添加特定的关键字&#xff0c;以便更好地组织和管理这些文件夹。为了实现这个目标&#xff0c;我们可以使用云炫文件管理器一些简单的步骤来实…

Java配置47-Spring Eureka 未授权访问漏洞修复

文章目录 1. 背景2. 方法2.1 Eureka Server 添加安全组件2.2 Eureka Server 添加参数2.3 重启 Eureka Server2.4 Eureka Server 升级版本2.5 Eureka Client 配置2.6 Eureka Server 添加代码2.7 其他问题 1. 背景 项目组使用的 Spring Boot 比较老&#xff0c;是 1.5.4.RELEASE…

云栖侧记:3点感悟…

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 2023年10月30日-11月2日&#xff0c;4天时间&#xff0c;我有幸参加了阿里云一年一度最重要的云栖大会&#xff0c;不过今天才写了个侧记文章&#xff0c;因为都拍视频去了&#xff0c;先说说参会感…

推广产品难?媒介盒子分享如何在软文中植入产品信息

产品想要卖得好&#xff0c;商家除了保证产品质量外&#xff0c;还需要善用推广&#xff0c;将产品打出知名度&#xff0c;软文推广就是很不错的方式&#xff0c;但是企业在推广时经常无法做到自然融入产品信息&#xff0c;导致推广没啥效果&#xff0c;今天媒介盒子就来告诉大…

uniapp小程序九宫格抽奖

定义好奖品下标&#xff0c;计时器开始抽奖&#xff0c;请求接口&#xff0c;出现中奖奖品之后&#xff0c;获取中奖商品对应的奖品下标&#xff0c;再次计时器判断当前移动的小标是否为中奖商品的下标&#xff0c;并且是否转到3圈&#xff08;防止转1圈就停止&#xff09;&…

3 数据库系统核心知识点

一、事务 先回顾一下事务的概念&#xff1a; 事务指的是满足 ACID 特性的一组操作&#xff0c;可以通过 Commit 提交一个事务&#xff0c;也可以使用 Rollback 进行回滚ACID 1. 原子性(Atomicity) 1.事务被视为不可分割的最小单元&#xff0c;事务的所有操作要么全部提交成…

【算法】背包问题——01背包

题目 有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 vi&#xff0c;价值是 wi。 求解将哪些物品装入背包&#xff0c;可使这些物品的总体积不超过背包容量&#xff0c;且总价值最大。 输出最大价值。 输入格式 第一行两个整数&#xff0…

算法通关村第六关|青铜|树通过序列构造二叉树

1.二叉树的性质 1.在二叉树的第 i 层上至多有 2^(i-1) 个结点。 2.深度为 k 的二叉树至多有 2^k - 1 个结点。 3.对于任意一棵二叉树&#xff0c;如果叶节点数为 N0 &#xff0c;度数为 2 的结点总数为 N2 &#xff0c;则 N0N21 。 4.具有 n 个结点的完全二叉树的深度为 lo…

uniapp原生插件之安卓华为统一扫码HMS Scan Kit

插件介绍 华为统一扫码服务&#xff08;Scan Kit&#xff09;提供便捷的条形码和二维码扫描、解析、生成能力 插件地址 安卓华为统一扫码HMS Scan Kit - DCloud 插件市场 超级福利 uniapp 插件购买超级福利 详细使用文档 详细使用文档 插件申请权限 android.permi…

Uniapp中使用Echarts

在小程序中使用echarts 我直接调用了HbuilderX的百度图表进行使用 LimeUi - 多端uniapp组件库 (qcoon.cn) 通用代码去官网找一下就好了赋值进去第一个柱状图表就可以显示了 地图的使用 <template><view style"height: 750rpx"><l-echart ref"…

软件测试用例方法---边界值法

原则&#xff1a; 输入最小值&#xff08;min&#xff09;、稍大于最小值&#xff08;min&#xff09;、域内任意值&#xff08;nom&#xff09;、稍小于最大值&#xff08;max-&#xff09;、最大值&#xff08;max&#xff09; 写法&#xff1a;“单故障”假设&#xff08;致…

java后端响应结果Result

目录 一、Result1-1、响应代码1-2、调用响应1-3、在前端vue页面使用方法 一、Result 1-1、响应代码 package com.aaa.common;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;Data AllArgsConstructor NoArgsConstructor public cla…

Breakpad在Windows,Linux双平台编译、集成以及dump文件的分析

Breakpad在Windows&#xff0c;Linux双平台编译、集成以及dump文件的分析 1、Windows平台 Windows平台上非常好的参考文档&#xff1a; https://r12f.com/posts/google-breakpad-1-introduction-with-windows/ https://r12f.com/posts/google-breakpad-2-implementations-o…

数字银行:数据安全的挑战与对策

随着科技的进步和互联网的普及&#xff0c;传统的银行业务逐渐向数字化转型。数字银行&#xff0c;以其高效、便捷、个性化的服务特点&#xff0c;正在改变着人们的生活方式。然而&#xff0c;与此同时&#xff0c;数据安全问题也日益凸显。如何在享受数字银行便利的同时&#…

项目实战:添加新库存记录

1、在index.html添加超链接&#xff0c;添加新库存add.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><link rel"stylesheet" href"style/index.css"…

项目实战:修改水果库存系统特定库存记录

1、在edit.html修改库存页面添加点击事件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><link rel"stylesheet" href"style/index.css"><script s…