C/C++ - 类的多态机制

news2024/11/15 15:59:48

目录

多态概念

多态定义

多态的触发机制

虚函数

虚函数表

虚析构函

虚析构函数声明

虚析构函数的作用

纯虚函数

纯虚函数的声明

纯虚函数的作用

抽象类

多态原理

虚函数表 & 虚函数指针

继承机制下的虚函数表

动态绑定


  • 多态概念

    • 狗狗发出的声音为 -> 旺旺
    • 猫猫发出的声音为 -> 喵喵
    • 猫猫狗狗我们均可以理解为是动物类,但是其具体对象做同一功能有着不同的表现
  • 多态定义

    • 多态的触发机制

      • 在C++中,多态通过基类的指针或引用来触发。
      • 当通过基类指针或引用调用虚函数时,程序会在运行时确定实际对象的类型,并调用相应的函数。
      • 这种动态绑定的决策是通过虚函数表(vtable)来实现的。
    • 虚函数

      • 虚函数是在基类中声明的带有virtual​​​关键字的成员函数。
      • 虚函数通过动态绑定来实现多态,它可以在派生类中被重写。
      • 当通过基类的指针或引用调用虚函数时,实际调用的是指向或引用的对象的类型的版本。
    • 代码示例

      #include <iostream>
      
      class Animal {
      public:
          virtual void MakeSound() {
              std::cout << "Animal makes a sound." << std::endl;
          }
      };
      
      class Dog : public Animal {
      public:
          void MakeSound() override {
              std::cout << "Dog barks." << std::endl;
          }
      };
      
      class Cat : public Animal {
      public:
          void MakeSound() override {
              std::cout << "Cat meows." << std::endl;
          }
      };
      
      int main() {
          Animal* animalPtr;
          Dog dog;
          Cat cat;
      
          animalPtr = &dog;
          animalPtr->MakeSound();  // 输出: Dog barks.
      
          animalPtr = &cat;
          animalPtr->MakeSound();  // 输出: Cat meows.
      
          return 0;
      }
      
  • 虚函数表

    • 虚函数表是用于实现多态的关键机制之一。

    • 每个包含虚函数的类都有一个虚函数表,其中存储了虚函数的地址。

    • 每个对象都有一个指向其类的虚函数表的指针(通常称为虚函数指针或vptr)。

    • 当调用虚函数时,程序会根据对象的虚函数指针找到相应的虚函数表,并使用表中的地址调用正确的函数。

  • 虚析构函

    • 如果一个基类的析构函数是虚函数,那么派生类的析构函数也会自动成为虚函数。

    • 虚析构函数声明

      • 虚析构函数是在基类中声明的带有virtual​​​​​关键字的析构函数。
      • ​virtual ~ClassName();​​​​​
    • 虚析构函数的作用

      • 当通过基类指针删除派生类对象时,如果基类的析构函数是虚函数,则会根据实际对象的类型调用相应的析构函数。
      • 这样可以确保正确释放派生类对象所占用的资源,避免内存泄漏。
      • 虚析构函数只需要在基类中声明,派生类的析构函数会自动成为虚函数。
      • 虚析构函数应该是公有的(public),以便在派生类中可以正确访问和重写。
    • 代码示例

      • 假设我们有一个图形库,其中包含多个图形类,如矩形(Rectangle​​)、圆形(Circle​​)。我们希望能够存储这些图形对象,并能够对它们执行各种操作,比如计算总面积、打印每个图形的属性等。
      • #include <iostream>
        #include <vector>
        
        using namespace std;
        
        class Shape {
        public:
            virtual double getArea() const = 0;
            virtual void printInfo() const = 0;
            virtual ~Shape() {}
        };
        
        class Rectangle : public Shape {
        private:
            double length;
            double width;
        
        public:
            Rectangle(double length, double width) : length(length), width(width) {}
        
            double getArea() const override {
                return length * width;
            }
        
            void printInfo() const override {
                cout << "矩形,长:" << length << ",宽:" << width << endl;
            }
        
            ~Rectangle() {
                cout << "销毁矩形对象" << endl;
            }
        };
        
        class Circle : public Shape {
        private:
            double radius;
        
        public:
            Circle(double radius) : radius(radius) {}
        
            double getArea() const override {
                return 3.14159 * radius * radius;
            }
        
            void printInfo() const override {
                cout << "圆形,半径:" << radius << endl;
            }
        
            ~Circle() {
                cout << "销毁圆形对象" << endl;
            }
        };
        
        int main() {
            vector<Shape*> shapes;
            shapes.push_back(new Rectangle(5.0, 3.0));
            shapes.push_back(new Circle(4.0));
        
            double totalArea = 0.0;
            for (const auto& shape : shapes) {
                shape->printInfo();
                totalArea += shape->getArea();
            }
        
            cout << "总面积:" << totalArea << endl;
        
            for (const auto& shape : shapes) {
                delete shape;
            }
        
            return 0;
        }
        
  • 纯虚函数

    • 纯虚函数的声明

      • 纯虚函数是通过在基类中声明一个没有实际实现的虚函数来定义的。
      • ​virtual ReturnType functionName() = 0;​​
    • 纯虚函数的作用

      • 纯虚函数为基类提供接口,要求派生类实现该函数。
      • 派生类必须提供对纯虚函数的定义,以便成为具体类。
      • 纯虚函数使得基类成为抽象类,无法实例化对象。
    • 抽象类

      • 抽象类包含至少一个纯虚函数。
      • 抽象类无法实例化对象,只能用作基类。
      • 抽象类可以包含非纯虚函数,这些函数可以有实际的实现。
      • 抽象类定义了一组接口和基本行为,要求派生类实现纯虚函数。
    • 代码示例

      #include <iostream>
      
      class AbstractClass {
      public:
          virtual void PureVirtualFunction() = 0;  // 纯虚函数
      
          void NonPureVirtualFunction() {
              std::cout << "Non-pure virtual function" << std::endl;
          }
      };
      
      class ConcreteClass : public AbstractClass {
      public:
          void PureVirtualFunction() override {
              std::cout << "Pure virtual function implementation" << std::endl;
          }
      };
      
      int main() {
          // AbstractClass abstractObj;  // 错误,无法实例化抽象类对象
      
          ConcreteClass concreteObj;
          concreteObj.PureVirtualFunction();
          concreteObj.NonPureVirtualFunction();
      
          AbstractClass* abstractPtr;
          abstractPtr = &concreteObj;
          abstractPtr->PureVirtualFunction();
          abstractPtr->NonPureVirtualFunction();
      
          return 0;
      }
      
    • 抽象类不能实例化对象,只能用作基类。
    • 派生类必须实现纯虚函数才能成为具体类。
    • 如果派生类没有实现纯虚函数,它仍然被视为抽象类,无法实例化对象。
    • 抽象类可以包含非纯虚函数,但纯虚函数必须在派生类中实现。
    • 纯虚函数可以具有实现,但通常没有实际实现,只是提供一个接口。
  • 多态原理

    • 虚函数表 & 虚函数指针

      • #include <iostream>
        
        class Base
        {
        public:
        	int a = 1;
        
        	virtual void Fun1()
        	{
        
        	}
        };
        
        class Son : public Base
        {
        public:
        	int a = 1;
        
        	virtual void Fun1()
        	{
        
        	}
        };
        
        
        int main()
        {
        	Base b;
        	Son s;
        
        	return 0;
        }
        
      • 定义一个空的类,写一个虚函数,观察期内存大小?

        • 类中仅有一个虚函数时类的内存大小为4Byte

          • 虚函数指针 - virtual function ptr​​(类对象前4Byte)

            • ​​
          • ​virtual function ptr​​ - 虚函数表

            • ​​
          • FunAddr

            • ​​
          • 内存布局

            • ​​
    • 继承机制下的虚函数表

      • #include <iostream>
        #include <Windows.h>
        
        class Base1
        {
        public:
        	virtual void Fun1(){}
        };
        
        class Base2
        {
        public:
        	virtual void Fun2() {}
        };
        
        class Son : public Base1, public Base2
        {
        public:
        	virtual void Fun1() {}
        	virtual void Fun2() {}
        };
        
        int main()
        {
        	//虚函数表
        	Son obj;
        	std::cout << std::hex << *(PDWORD)((PCHAR)(&obj) + 0) << std::endl;
        	std::cout << std::hex << *(PDWORD)((PCHAR)(&obj) + 4) << std::endl;
        
        	return 0;
        }
        
      • ​​
      • ​​
      • ​​
      • ​​
      • ​​
      • ​​
      • ​​
    • 动态绑定

      • 示例1

        • 代码

          #include <iostream>
          
          class Animal {
          public:
              virtual void MakeSound() {
                  std::cout << "Animal makes a sound." << std::endl;
              }
          };
          
          class Dog : public Animal {
          public:
              void MakeSound() override {
                  std::cout << "Dog barks." << std::endl;
              }
          };
          
          class Cat : public Animal {
          public:
              void MakeSound() override {
                  std::cout << "Cat meows." << std::endl;
              }
          };
          
          int main() {
              Animal* animalPtr;
              Dog dog;
              Cat cat;
          
              animalPtr = &dog;
              animalPtr->MakeSound();  // 输出: Dog barks.
          
              animalPtr = &cat;
              animalPtr->MakeSound();  // 输出: Cat meows.
          
              return 0;
          }
          
          
        • 图解

          • 对象地址

          • 获取对象地址

          • 指向函数

      • 示例2

        • 代码

          #include <iostream>
          #include <Windows.h>
          
          class Base1
          {
          public:
          	virtual void Fun1() {}
          };
          
          class Base2
          {
          public:
          	virtual void Fun2() {}
          };
          
          class Son : public Base1, public Base2
          {
          public:
          	virtual void Fun1() {}
          	virtual void Fun2() {}
          };
          
          int main()
          {
          	Base1* pBase1;
          	Base2* pBase2;
          	Son obj;
          
          	pBase1 = &obj;
          	pBase2 = &obj;
          
          	pBase1->Fun1();
          	pBase2->Fun2();
          
          	return 0;
          }
          
        • 图解

          • Base1

          • Base2

          • pBase1 -> obj.Addr + 0 = vfptr(1)

            pBase2 -> obj.Addr + 4 = vfptr(2)

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

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

相关文章

【C语言】整数和浮点数在内存中的存储

前言 我们都知道&#xff0c;在创建一个变量的时候&#xff0c;编译器会自动开辟一块内存空间用于存放它&#xff0c;但是对于不同的数据类型&#xff0c;它们的存储形式也会有所不同。今天就让我们一起来学习整数和浮点数在内存中的存储 1. 整数在内存中的存储 我们都知道&…

编译Opencv3.3.1遇到的编译器无法识别的警告的问题解除:

问题描述&#xff1a; 本文&#xff0c;就是在一个硬件的SDK中用到了opencv3.3.1的版本&#xff0c;在笔者目前的VS2019,CUDA11版本下编译的问题和解决。在做Cmake的configure的时候&#xff0c;Cmake报了一个找不到编译器版本的错误, Selecting windows SDK version 10.0.1904…

Linux下使用信号量实现PV操作

一.信号量与PV操作概述 在多道程序系统中&#xff0c;由于资源共享与进程合作&#xff0c;使各进程之间可能产生两种形式的制约关系&#xff0c;一种是间接相互制约&#xff0c;例如&#xff0c;在仅有一台打印机的系统&#xff0c;同一时刻只能有一个进程分配到到打印机&…

Axure9知识点复盘

axure9 Axure有四种类型文件&#xff1a; 团队文件示例.rpteam 元件库文件示例.rplib 原型导出文件示例.html 原型文件示例.rp 将反复被调用都模板作成母版 鼠标和键盘的交互 形状交互 登录页面的实现 动态面版滚动条的使用 如何在Axure中动态加载图表&#xff08;Axh…

H5适配iOS顶部和底部安全区域

在移动端Web开发中&#xff0c;适配不同设备的屏幕是一个重要且挑战性的任务。对于iOS设备来说&#xff0c;这一任务尤为关键&#xff0c;因为自iPhone X起&#xff0c;苹果的设备引入了刘海屏、圆角等设计&#xff0c;这就要求开发者在Web页面中特别处理顶部和底部的安全区域。…

在RunnerGo测试平台中做WebSocket、Dubbo、TCP/IP接口测试

大家好&#xff0c;RunnerGo作为一款一站式测试平台不断为用户提供更好的使用体验&#xff0c;最近得知RunnerGo新增对&#xff0c;WebSocket、Dubbo、TCP/IP&#xff0c;三种协议API的测试支持&#xff0c;本篇文章跟大家分享一下使用方法。 WebSocket协议 WebSocket 是一种…

用 CanvasKit 实现超级丝滑的原神地图(已开源)!!!

首先给大家送上预览地址&#xff1a; 官网地址&#xff1a;https://webstatic.mihoyo.com/ys/app/interactive-map/index.html canvaskit地址&#xff1a;http://106.55.55.247/ky-genshin-map/ 为什么 canvaskit 有如此高的性能&#xff1f; 第一个问题&#xff0c;官方网页…

万户 ezOFFICE DocumentEditExcel.jsp SQL注入漏洞

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…

day38_MySQL

今日内容 0 复习昨日 1 引言 2 数据库 3 数据库管理系统 4 MySQL 5 SQL语言 0 复习昨日 1 引言 1.1 现有的数据存储方式有哪些&#xff1f; Java程序存储数据&#xff08;变量、对象、数组、集合&#xff09;&#xff0c;数据保存在内存中&#xff0c;属于瞬时状态存储。文件&…

D4890——单通道 BTL 音频功率放大器电路,采用SOP8/MSOP8封装形式,无需输出耦合电容、缓冲网络或自举电容

D4890是一个AB类音频功率放大器专为移动电话MID和其他便携式通信设备。它能够从5Vn的电源以小于1%的失真(THDN) 传输1.1wts到8QBlo d。专为提供高品质的输出功率而设计&#xff0c;只需最少的外部元件。它不需要输出耦合电容或自举电容。和超低关断电流&#xff0c;D4890非常适…

DataCanvas会员中心正式上线,这些新春福利请接住!

重大消息&#xff1a;“九章云极DataCanvas智能研究院”服务号会员中心正式上线了 &#xff01;注册成为DataCanvas会员&#xff0c;接好这些新春福利&#xff01; 新岁将至&#xff0c;福启九章&#xff0c;作为集智库、服务、干货分享、互动交流于一体的用户综合服务平台&am…

对象原型和原型对象

在浏览器中显示的[[Prototype]]实际上就是__proto__,是对象原型&#xff0c;可以被实例访问。 prototype是构造函数的属性&#xff0c;__proto__是实例的属性。有点绕口。

浅析云性能监控的重要性及核心功能

随着企业日益依赖云计算服务&#xff0c;云性能监控变得至关重要。云性能监控是一种实时监测、分析和报告云基础设施及应用程序性能的方法。本文将深入探讨云性能监控的目的、重要性以及其核心功能&#xff0c;以帮助企业更好地理解和实施这一关键的运维实践。 一、云性能监控的…

AI算力专题:AI时代领先者,大装置+大模型推动AGI落地

今天分享的是AI算力系列深度研究报告&#xff1a;《AI算力专题&#xff1a;AI时代领先者&#xff0c;大装置大模型推动AGI落地》。 &#xff08;报告出品方&#xff1a;中银证券&#xff09; 报告共计&#xff1a;28页 四核驱动引领智慧科技新潮流 商汤是一家行业领先的人工…

C++ 数论相关题目 博弈论 Nim游戏

给定 n 堆石子&#xff0c;两位玩家轮流操作&#xff0c;每次操作可以从任意一堆石子中拿走任意数量的石子&#xff08;可以拿完&#xff0c;但不能不拿&#xff09;&#xff0c;最后无法进行操作的人视为失败。 问如果两人都采用最优策略&#xff0c;先手是否必胜。 输入格式…

惬意上手python —— python中的术语及案例解析

面向对象编程 面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;是一种编程范式&#xff0c;它将数据和操作数据的方法封装在一起&#xff0c;以对象的形式表示。在Python中&#xff0c;一切皆为对象&#xff0c;因此Python是一种面向对象的语…

【开源】SpringBoot框架开发天然气工程运维系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统角色分类2.2 核心功能2.2.1 流程 12.2.2 流程 22.3 各角色功能2.3.1 系统管理员功能2.3.2 用户服务部功能2.3.3 分公司&#xff08;施工单位&#xff09;功能2.3.3.1 技术员角色功能2.3.3.2 材料员角色功能 2.3.4 安…

带自执行安装脚本的ROS包的生成

带自执行安装脚本的ROS包的生成 在打包和安装ROS包时, 会有一些固定的配置需要去人为实现, 比如网络配置, 设备树的管理等, 比较麻烦, 不如一次性解决掉, 所以查了相关文档 过程: # 使用bloom-generate rosdebian生成debian文件夹 bloom-generate rosdebian # 进入debian文件…

Unix/Linux上的五种IO模型

a.阻塞 blocking 调用者调用了某个函数&#xff0c;等待这个函数返回&#xff0c;期间什么也不做&#xff0c;不停的去检查这个函数有没有返回&#xff0c;必须等这个函数返回才能进行下一步动作。 注意&#xff1a;阻塞并不是函数的行为&#xff0c;而是跟文件描述符有关。通…

法律视角下的数据出境《2023年数据出境合规年鉴》

关注国际云安全联盟CSA公众号&#xff0c;回复关键词“数据安全”获取报告 在全球数字产业以及大数据和云计算技术快速发展的背景下&#xff0c;数据流动对世界经济的影响日益显著。由此带来的数据红利和数据安全之间的冲突&#xff0c;将对未来数字经济的发展方向产生深刻影响…