多态与虚函数

news2024/11/25 13:28:40

多态与虚函数

  • 多态的引入
  • 多态与虚函数
    • 多态
    • 编译时多态
    • 运行时多态
  • 多态的原理
  • 静态联编和动态联编

多态的引入

学过C++继承的话应该都知道在继承中存在一种菱形继承,假设存在一个类(person),其派生出两个子类,分别是student类和Migrant类,我们但是这两个子类共同同派生出一个MigStu的类,我们可以通过画图来理解一下,
在这里插入图片描述
我们画出他的继承图会发现其是一个菱形,我们再画出他的内存分布图在这里插入图片描述
会发现存在两个person类,那么其中的公共成员变量就会发生冲突,比如学生是男性,打工人是女性,那么我们的在校打工人是男是女,这很显然发生了冲突。为了解决这个问题C++引入了virtual关键字,在person中我们加入virtual关键字之后,就会只在类中生成一个person对象,在学生和打工人的结构中原本存放person的地方会存在一个指针,指向我们的person类,也就解决了冗余的问题。但是菱形继承的底层实现极为复杂,所以现在很少设置这种菱形继承。而我们使用virtual继承基类,被称为虚基类,但是其并非基类是虚的,而是继承方式是虚的,而使用多态时,便使用的是虚函数,同样时virtual关键字。

多态与虚函数

多态

多态性是面向对象程序设计的关键技术之一。若程序设计语言不支持多态性,则不能称为面向对象语言。多态性是考虑在不同层次的类中,同名的成员函数之间的关系。函数的重载,运算符的重载,属于编译时的多态性。以类的虚成员函数作为基础运行时的多态性是面向对象程序设计的标志性特征。
多态分为编译时多态和运行时多态

编译时多态

编译时多态也就是函数重载,函数名相同,参数不同,其本质上是代码在编译时期对函数进行了名字粉碎技术,将函数名,参数,返回值做了一个粉碎,导致其在运行时编译器认为其名字不相同从而实现函数重载,

int Max(int a,int b) {return a>b?a:b;}
char Max(char a,char b) {return a>b?a:b;}
double Max(double a,double b) {return a>b?a:b;}

运行时多态

在一些类中会存在一些共同的特性,为此会设置一个基类将所有的共性存放到这个类中,并将这些共性设置成虚函数,在派生类中再一次实现具体的操作,这样就避免了代码的复用,并且使得代码更容易维护,也就是说以后有其他类的话直接加入派生类中即可。在调用时必须使用指针或者引用才可以将虚函数绑定到派生类的对象上重写虚函数。

#include <iostream> 
using namespace std;
 
class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual int area()
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};
class Rectangle: public Shape{
   public:
      Rectangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      { 
         cout << "Rectangle class area :" <<endl;
         return (width * height); 
      }
};
class Triangle: public Shape{
   public:
      Triangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      { 
         cout << "Triangle class area :" <<endl;
         return (width * height / 2); 
      }
};
// 程序的主函数
int main( )
{
   Shape *shape;
   Rectangle rec(10,7);
   Triangle  tri(10,5);
 
   // 存储矩形的地址
   shape = &rec;
   // 调用矩形的求面积函数 area
   shape->area();
 
   // 存储三角形的地址
   shape = &tri;
   // 调用三角形的求面积函数 area
   shape->area();
   
   return 0;
}

使用多态时我们必须存在三个条件:

  • 必须是公有继承
  • 必须加入virtual
  • 必须使用指针或者引用来调用
    定义虚函数需要注意:
  • 派生类中定义虚函数必须与基类中的虚函数同名,同参数表,同返回类型。否则会被认为是同名覆盖,不具有多态性,但存在一个例外:基类中返回基类指针,派生类中返回派生类指针(协变)。
  • 只有类的成员函数才能说明是虚函数,因为虚函数仅使用于有继承关系的类对象。有缘函数和全局函数都不能作为虚函数
  • 构造函数和拷贝构造函数不能作为虚函数,拷贝函数和拷贝构造函数是设置虚表指针。
  • 析构函数可以定义为虚函数,构造函数不能定义为虚函数,因为在调用构造函数时对象还没有完成实例化(虚表指针没有设置)。在基类中及其派生类中都动态分配内存空间时,必须把析构函数定义为虚函数,实现撤销对象的时的多态性。
  • virtual之正在函数声明前加,不能再函数定义时加入,正确的定义必须不包括virtual,函数默认参数值也必须在声明时加入,定义时不能加入。

多态的原理

多态的原理就是虚函数表简称为虚表,虚表就是虚函数指针的集合,虚函数指针表本质上是一个存储虚函数指针的指针数组,这个数组的首元素之上存储RTTI(运行时类型识别信息的指针),从数组下标开始依次存储虚表地址,最后放了一个nullptr,虚表存放于只读数据段,其在编译时确定,并且一个类只存在一个虚表。

class object{
private: int value;
pubic:
object(int X = 0) :value(x) {}
virtual void add() { cout << "object: :add()" << end1; }
virtual void fun() { cout << "object: :fun()" << endl; }
virtual void print() const
{ cout << "object::printO" << end1; }
};
class Base: pub1ic object
private:
int num;
public:
Base(int x = 0) :object(x) ,num(x + 10) {}
virtual void add() { cout << "Base: :add()" << end1; }
virtual void fun() { cout << "Base: :fun()" << end1; }
virtual void show() { cout << "Base::show()" << end1; }
};
class Test : public Basve{
private:
int count;
public:
Test(int x = 0) :Base(x), count(x + 10) {}
virtual void add() { cout << "Test: :add()" << end1; }
virtual void print() const
{ cout << "Test: :print()" << end]; }
virtual void show() { cout << "Test: :show()" << end1; }
};
int main(){
object *op = nu11ptr;
objece obj;
Base base;
Test test;
op = &test;
op->print() ;
return 0;
}

我们观察上面代码,化出虚表内存分布图以及虚表图如下:
在这里插入图片描述
如图便是三个类的虚表,实在编译时就确定了的,例如obj派生出了Base类,所以Base的虚表就是将obj的虚表拷贝了一份然后进行同名覆盖,而在创建对象的时候,我们以创建Test对象为例,创建该对象首先创建Base,而创建Base需要创建obiect,而当类中有虚函数时类的大小就会多4(32位)字节,用于存放指向虚表的指针,创建Test时,指针首先指向object的虚表,然后指向Base的虚表,最后指向Test的虚表,根据其构建顺序。这个过程是在运行时进行的,虚表创建在编译时。

静态联编和动态联编

其实两者的差异就在于关联(函数实现和函数调用关联)的时期不一样,静态联编在编译时就确定了关联,而动态联编是在运行时确定的关联关系。
静态关联就是用直接用对象调用函数,动态联编就是用指针调用函数,注意虚表指针是否指向虚表。
C++中函数重载,函数模板都是静态联编,使用指针引用都是动态联编

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

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

相关文章

Template Method模式

文章目录 &#x1f4a1;前言分类优点 &#x1f4a1;问题引入&#x1f4a1;概念&#x1f4a1;例子&#x1f4a1;总结 &#x1f4a1;前言 此文是第一篇讲解设计模式的文章&#xff0c;而笔者我又不想另起一篇来概述设计模式的分类&#xff0c;作用&#xff0c;以及优点&#xff…

MySQL笔记(四) 函数、变量、存储过程、游标、索引、存储引擎、数据库维护、指定字符集、锁机制

MySQL笔记&#xff08;四&#xff09; 文章目录 MySQL笔记&#xff08;四&#xff09;函数文本处理函数日期和时间处理函数数值处理函数类型转换函数流程控制函数自定义函数基本语法 局部变量全局变量聚集函数 aggregate functionDISTINCT 存储过程为什么要使用使用创建 删除建…

调用api实现ChatGPT接口余额查询

先打个广告&#xff1a; 推荐一款不用科学上网就可以使用的ChatGPT工具&#xff1a;智能聊天助手 体验版入口&#xff1a;智能聊天助手体验版 在ChatGPT官网可以查询接口使用额度&#xff0c;但是官方并没有提供相应的API给开发者调用。但是可以通过破解的方式找到它的API。方法…

人生在世皆有过错,来一起看看Java中的异常吧!!!

Java中的异常问题详解 一、异常的概念与分类 1.异常概念 概念&#xff1a;Java异常是一个描述在代码段中发生异常的对象&#xff0c;当发生异常情况时&#xff0c;一个代表该异常的对象被创建并且在导致该异常的方法中被抛出&#xff0c;而该方法可以选择自己处理异常或者传…

Invicti v23.5 for Windows 发布 - 企业应用安全测试

Invicti v23.5 for Windows - 企业应用安全测试 Invicti Standard 11 May 2023 v23.5.0.40516 请访问原文链接&#xff1a;https://sysin.org/blog/invicti/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Invicti 是一种自动…

[HFCTF2022]ezchain

环境分析 环境提供了docker-compose.yml&#xff0c;nginx.conf文件&#xff0c;从两个文件中可疑分析出是不出网的环境 nginx.conf&#xff1a; server { listen 80;server_name localhost;location / {root /usr/share/nginx/html; #收到/路径请求会访问/usr/sha…

Keil5----Debug时,watch1中全局变量数值不刷新问题解决方法

问题&#xff1a; 在Keil5-MDK中&#xff0c;Debug时&#xff0c;watch1中全局变量数值不刷新。 解决方法&#xff1a; 步骤1&#xff1a;进入Debug模式 将程序调试下载器&#xff08;STlink,Jlink,Ulink&#xff09;连接&#xff0c;编译程序后。 进行如下操作&#xff1a…

算法修炼之练气篇——练气十六层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

MySQL --- 多表查询

多表查询、事物、以及提升查询效率最有手段的索引 一. 多表查询 1.1 多表查询 --- 概述 1.1.1 数据准备 将资料中准备好的多表查询数据准备的SQL脚本导入数据库中。 部门表&#xff1a; 员工表&#xff1a; 1.1.2 介绍 多表查询&#xff1a;指从多张表中查询数据&#…

2023年的深度学习入门指南(13) - 写后端

2023年的深度学习入门指南(13) - 写后端 我们前面学习了用python在本机上写前端&#xff0c;也学习了使用HTML在本机写前端。同理&#xff0c;我们可以写Windows应用&#xff0c;mac应用&#xff0c;iOS应用&#xff0c;Android应用等等以适合各种终端。其实&#xff0c;最通用…

python+chrome rpc方式轻松绕过五秒盾(cloudflare)

Cloudflare 5秒盾是一种基于云技术的Web应用程序防火墙(WAF),旨在保护网站免受各种Web攻击,如SQL注入、跨站点脚本(XSS)和DDoS攻击。它能够在5秒内检测到并阻止恶意流量,并提供实时安全警报和日志记录。此外,它还提供了一系列安全功能,包括SSL / TLS加密、IP过滤、访问…

单链表你别再找我了,我怕双向链表误会

目录 带头双向循环链表的创建和初始化 创建一个新的结点&#xff08;方便复用&#xff09; 链表判空 链表打印 链表尾插 链表尾删 链表头插 链表头删 任意插入 任意删除 链表查找 链表销毁 完整代码 &#x1f60e;前言 之前我们讲了结构最简单&#xff0c;实现起来…

kettle win11 启动闪退 --启动日志

一、启动闪退 思路&#xff1a; 找原因找启动日志根据启动日志查看启动失败的原因 二、找启动日志 采用debug模式启动 查看控制台–根据控制台操作 看生成的启动日志文件 查看日志 DEBUG: Using PENTAHO_JAVA_HOME DEBUG: _PENTAHO_JAVA_HOMEE:\java8 DEBUG: _PENTAHO…

记一次靶场搭建与渗透测试

渗透目标 通过Windows7打入工作组环境&#xff0c;穿透两层内网拿到DC&#xff08;域控制器&#xff09;权限 环境搭建 环境搭建 网络拓扑 虚拟机网络配置 渗透测试 永恒之蓝外网打点 nmap -sS 192.168.2.0/24扫描外网存活主机&#xff0c;发现两台主机192.168.2.128和192…

芯片电源附近为什么放置的是0.1uF电容

日常使用情况 我们在电源滤波电路上可以看到各种各样的电容&#xff0c;100uF、10uF、100nF、10nF不同的容值&#xff0c;而在我们使用中常常会在芯片电源附近放置0.1uF电容&#xff0c;以TB67S109AFNG应用手册为例&#xff0c;其中推荐使用的也是0.1uF的电容 电容的特性 数字…

分享几款小白从零开始学习的会用到的工具/网站

大二狗接触编程也有两年了&#xff0c;差生文具多这大众都认可的一句话&#xff0c;在这里蹭一下这个活动分享一下从0开始学习编程有啥好用的工具 目录 伴侣一、Snipaste截图工具 伴侣二、Postman软件&#xff08;可用ApiPost平替&#xff09; 伴侣三、字体图标网站 伴侣四…

BlockChain-Account_TakeOver

题目描述 ECDSA 签名 假设我们的私钥为 d A d_A dA​而公钥为 Q A Q_A QA​&#xff0c; Q A d A ⋅ G Q_Ad_A\cdot G QA​dA​⋅G&#xff0c;接下来就是签名的过程&#xff0c;要签名的消息为 m m m 取 e H A S H ( m ) e HASH(m) eHASH(m)取 e e e的左边的 L n L_n L…

Baumer工业相机堡盟工业相机软件CameraExplorer常见功能使用说明二

Baumer工业相机堡盟工业相机软件CameraExplorer常见功能使用说明二 Baumer工业相机Baumer工业相机CE软件图像/视频存储功能Baumer工业相机CE软件记录日志文件功能Baumer工业相机CE软件查看图像Buffer及数据流统计信息 Baumer工业相机 Baumer工业相机堡盟相机是一种高性能、高质…

汇编五、伪指令与汇编程序结构

1、伪指令 1.1、概念 (1)伪指令是用于对汇编过程进行控制的指令&#xff0c;该类指令并不是可执行指令&#xff0c;没有对应机器码&#xff0c;只用于汇编过程中为汇编程序提供汇编信息&#xff0c;帮助编译器编译。 1.2、ASM51提供的伪指令 伪指令分为如下几类。 1.2.1、…

zuul源码分析

zuul源码解析 zuul与springboot整合的依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency>看到starter第一反应就是springboot的自动装配? 我们去…