当前位置: 首页 > news >正文

多态(C++)

多态(C++)

本文如果有错误或者不足的地方,希望各位大佬多多指点。

【本文目录】

  • 1.多态的概念
  • 2.多态的定义及实现
  • 3.抽象类
  • 4.多态的原理
  • 5.单继承和多继承的虚函数表

在这里插入图片描述


1.多态的概念

多态的概念就是:多种形态

多态就是可以有多种的形态。不同的身份去实现同一件事情,会有不同状态。例如当你是学生时,去买火车票会有学生的专属优惠,但是当你是社会人士时,就不会有这样的优惠。就像电影变形金刚中的汽车可以用多种形态。
在这里插入图片描述

2.多态的定义及实现

2.1.虚函数

被关键字virtual所修饰的函数,称为虚函数

2.2.多态的构成条件🚩

  • 子类重写父类的虚函数
  • 父类的指针或引用来调用

2.3.重写

函数名、返回值、参数列表一致(协变除外)

注意:在子类中,不添加关键字virtual也构成重写,因为从基类继承下来的虚函数,也具有虚函数的属性。

2.4.多态的实现


class Person
{
public:virtual void BuyTickets() { cout << "票价--全价" << endl; }
};class Student :public Person
{
public://子类重写父类的虚函数virtual void BuyTickets() { cout << "票价--学生价" << endl; }
};//父类的引用 ,父类的指针也可以
void fun(Person& p)
{p.BuyTickets();
}int main()
{Person p;Student s;fun(p); // 不同的对象调用会有不同的状态fun(s);return 0;
}

3.抽象类

类中包含纯虚函数的类称为抽象类

3.1纯虚函数

纯虚函数就是在虚函数后面加上=0,内部有纯虚函数的类是无法实例化对象的。子类也会继承这个特性。子类只有重写这个纯虚函数才可以实例化对象。

class A
{
public:virtual void show() = 0; //纯虚函数
};class B : public A
{
public:virtual void show() {} //子类重写父类的纯虚函数
};int main()
{//A a; // err 无法实例化B b; //没有重写纯虚函数时,无法实例化对象return 0;
}

4.多态的原理

4.1虚函数表

下面这个类的是多少?

class Person
{
public:virtual void show() {};
protected:int _p;
};int main()
{Person p;cout<< sizeof(p) << endl; //这个对象多大?return 0;
}

答案:这个对象的大小在32位平台是8字节,在64位平台下是16字节。

看图

在这里插入图片描述

解析:其中__vfptr是一个指针,叫做虚函数表指针,一个含有虚函数的类至少存在一个虚函数表指针,该指针指向的是虚函数表,虚函数表简称虚表。虚表可以看做一个数组,内部存放的虚函数的地址,一般这个数组最后存放的是nullptr。

指针在32位平台下占4字节,在64位平台占8字节空间。因此在32位平台对象p的大小是8字节空间,而在64位平台大小是16字节,在64位下时需要内存对齐因此是16字节,不是12字节。

虚函数存在哪?虚表存在哪?

  • 代码段

class Person
{
public:virtual void BuyTickets() { cout << "票价--全价" << endl; }
};class Student :public Person
{
public://子类重写父类的虚函数virtual void BuyTickets() { cout << "票价--学生价" << endl; }
};void Print()
{int i = 0;//存放栈区int* pt = &i;int* pi = new int; //存放堆区const char* pc = "hello";printf("%p\n", pt);printf("%p\n", pi);printf("%p\n", pc);printf("%p\n",&(Person::BuyTickets));}int main()
{Print();return 0;
}

输出结果:
在这里插入图片描述

根据实例分析:分别打印栈区、堆区、代码段的变量地址得出,虚函数表和虚函数时存放在代码段的。

class Person
{
public:virtual void show1() {};virtual void show() {};
protected:int _p;
};
class Student:public Person
{public:virtual void show() {};virtual void show2() {};
protected:int _ps;};int main()
{Person p;Student s;return 0;
}

打开VS2022的监视窗口

在这里插入图片描述

看图解析:

  1. 基类和派生类都有一个虚函数指针(__vfptr)

  2. 派生类是由两部份组成,一部分继承基类的成员,一部分是自己的成员。

3.派生类从基类继承下来的show1函数,并没有重写时,会发现和基类的show1函数地址是一样的。派生类中的show函数重写父类的show函数,会发现地址不同的。可以知道当子类重写父类的虚函数时,会进行覆盖的父类的虚函数。

【总结】

  • 编译器会将基类的虚表拷贝一份给派生类,当派生类重写基类的虚函数时,会把基类虚函数覆盖

4.2多态的原理

进行上面的分析,可以知道子类的虚函数重写会覆盖父类的虚函数。下面进行分析多态的原理

class Person
{
public:virtual void BuyTickets() { cout << "票价--全价" << endl; }
};class Student :public Person
{
public://子类重写父类的虚函数virtual void BuyTickets() { cout << "票价--学生价" << endl; }
};void fun(Person& p)
{p.BuyTickets();
}int main()
{Person p;fun(p);Student s;fun(s);return 0;
}

在这里插入图片描述

解析:

  • 当fun函数的调用参数是Person类的对象时,p的指向就是Person类的虚函数
  • 当fun的函数调用参数是Student类的对象时,子类重写父类的虚函数,将其覆盖,此时p的指向是Student的虚函数

4.3动态绑定和静态绑定

【静态绑定】

静态绑定也称为前期绑定(早绑定),在编译期间确定程序的行为。例如函数重载

【动态绑定】

动态绑定也称为后期绑定(晚绑定),在程序运行期间,根据拿到的类型确定程序的行为,调用具体的函数。也叫做动态多态

5.单继承和多继承的虚函数表

【单继承】

class C1 
{
public:virtual void fun1() {}virtual void fun2() {}
};class C2:public C1
{
public:virtual void fun1() {}virtual void fun3() {}virtual void fun4() {}
};typedef void(*VFPTR) ();
void PrintVPTable(VFPTR p[])
{cout << "虚函数指针" << p << endl;for (int i = 0; p[i] != nullptr; i++){printf("第 %d 个函数地址:OX%x\n",i,p[i]);VFPTR f = p[i];f();}cout << endl;
}int main()
{C1 c1;VFPTR* VFPTR_c1 = (VFPTR*)(*(int*)&c1); PrintVPTable(VFPTR_c1);C2 c2;VFPTR* VFPTR_c2 = (VFPTR*)(*(int*)&c2);PrintVPTable(VFPTR_c2);return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

当子类继承父类的成员时,子类内部具有父类同时也具有子类成员,当子类调用父类成员时,会进行切割行为。子类重写虚函数时,切割子类中的父类成员,覆盖父类的拷贝出来虚函数表。

【多继承】

class C1 
{
public:virtual void fun1() { cout << "C1::fun1" << endl; }virtual void fun2() { cout << "C1::fun2" << endl; }
};class C2
{
public:virtual void fun1() { cout << "C2::fun1" << endl; }virtual void fun2() { cout << "C2::fun2" << endl; }
};class C3:public C2,public C1
{
public:virtual void fun1() { cout << "C3::fun1" << endl; }virtual void fun3() { cout << "C3::fun3" << endl; }virtual void fun4() { cout << "C3::fun4" << endl; }
};typedef void(*VFPTR) ();
void PrintVPTable(VFPTR p[])
{cout << "虚函数指针" << p << endl;for (int i = 0; p[i] != nullptr; i++){printf("第 %d 个函数地址:OX%x -> ",i,p[i]);VFPTR f = p[i];f();}cout << endl;
}int main()
{C1 c1;VFPTR* VFPTR_c1 = (VFPTR*)(*(int*)&c1); PrintVPTable(VFPTR_c1);C2 c2;VFPTR* VFPTR_c2 = (VFPTR*)(*(int*)&c2);PrintVPTable(VFPTR_c2);C3 c3;VFPTR* VFPTR_c3 = (VFPTR*)(*(int*)&c3);PrintVPTable(VFPTR_c3);return 0;
}

在这里插入图片描述

看图得知:

  • 多继承的派生类未重写的虚函数会放第一个继承虚函数表中
  • 多继承的派生类重写虚函数时,会进行全部覆盖

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Ubuntu22.04之扩展并挂载4T硬盘(二百三十三)
  • 【typescript】omit和pick的好处,以及区别和用法
  • 怎么做好客户信息管理?
  • linux日常运维2
  • web前端三大主流框架
  • PHP preg_replace正则表达式涉及汉字乱码
  • c++——模板初始识
  • mysql内存和磁盘的关系
  • 数学建模——线性回归模型
  • Apache Doris 基础 -- 数据表设计(数据模型)
  • 充电器快充协议与PW6606快充电压诱骗芯片
  • Linux完整版命令大全(二十一)
  • 前端面试题12-22
  • 惯性测量单元M-G370系列广泛用于工业系统各个领域
  • 工控屏(触摸屏)怎么连接电脑
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • angular2 简述
  • CSS盒模型深入
  • JavaWeb(学习笔记二)
  • JS实现简单的MVC模式开发小游戏
  • NLPIR语义挖掘平台推动行业大数据应用服务
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • Webpack 4 学习01(基础配置)
  • Windows Containers 大冒险: 容器网络
  • 不上全站https的网站你们就等着被恶心死吧
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 简单易用的leetcode开发测试工具(npm)
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 如何合理的规划jvm性能调优
  • 实战:基于Spring Boot快速开发RESTful风格API接口
  • 微服务框架lagom
  • 微信端页面使用-webkit-box和绝对定位时,元素上移的问题
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • 扩展资源服务器解决oauth2 性能瓶颈
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​一些不规范的GTID使用场景
  • # Spring Cloud Alibaba Nacos_配置中心与服务发现(四)
  • (5)STL算法之复制
  • (HAL库版)freeRTOS移植STMF103
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • (原創) 物件導向與老子思想 (OO)
  • (转)EOS中账户、钱包和密钥的关系
  • .NET Core 实现 Redis 批量查询指定格式的Key
  • .NET 编写一个可以异步等待循环中任何一个部分的 Awaiter
  • .NET 读取 JSON格式的数据
  • .NET 设计模式—适配器模式(Adapter Pattern)
  • .NET 使用配置文件
  • .net/c# memcached 获取所有缓存键(keys)