【c++ 之 多态】

2023-11-07

前言

打怪升级:第61天
在这里插入图片描述

多态

认识多态

所谓多态,通俗来讲就是多种形态,也就是当一件事情由不同的人去完成会表现出不同的形态,例如买车票:成人全价,学生半价,军人优先等;
再例如测量体重,不同的人去测量,体重仪的表现也会不同。

多态的定义与实现

构成多态的条件

下面我们先来“见一见猪跑”:

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "成人,全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "学生,半价" << endl;
	}
};

void Buy(Person& p)
{
	p.BuyTicket();
}

void Test_p2()
{
	Person p1;
	Student t1;
	Buy(p1);
	Buy(t1);
}

在这里插入图片描述

虚函数

  • 虚函数的定义
    虚函数就是使用 virtual 修饰的成员函数
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "成人,全价" << endl;
	}
};
  • 虚函数的重写
    **重写(覆盖)**的条件:
    在子类中存在与父类完全相同的虚函数(三同:函数名、参数、返回值都必须相同),我们称为子类对父类的虚函数进行了重写。
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "成人,全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "学生,半价" << endl;
	}
};

这里是引用

上面,我们说的十分肯定 – 必须由三同才可构成重写,
然而,其实是有两个特例存在的 – 1.子类的重写返回值在特殊情况下可以不同;2.析构函数的重写

1.协变(基类与派生类虚函数返回值不同)

当子类和父类的虚函数返回值为有父子关系的类对象时,返回值也可以不同。

class A
{

};

class B :public A
{

};

class Person
{
public:
	virtual Person& BuyTicket()
	{
		cout << "成人,全价" << endl;
		return *this;
	}
};

class Student : public Person
{
public:
	virtual Student& BuyTicket()
	{
		cout << "学生,半价" << endl;
		return *this;
	}
};

在这里插入图片描述

可以让父类虚函数返回子类引用,子类虚函数返回父类引用吗?
不可,虚函数的重写实际上是对 从父类继承下来的虚函数的实现进行重写,声明部分是完全继承的,因此,
如果父类虚函数返回子类引用,就会使得子类中的虚函数使用父类对象初始化子类对象,(我们可以使用子类对象初始化父类对象 – 会进行切片,但是父类中不一定拥有子类的全部成员,无法完成对子类的初识化)。

2.析构函数的重写

如果基类的析构函数是虚函数,此时派生类的析构函数无论是否加 virtual,都与基类的析构函数构成重写。
这里虽然基类与派生类的析构函数函数名不同,看起来好像违反了 三同 的规则,
其实不然,这里是编译器在底层做了特殊处理:编译之后所有析构函数的名称都会被处理为destruction

这里是引用在这里插入图片描述

c++11.两个虚函数修饰关键字:final & override

final修饰父类虚函数:该虚函数不可再在子类中进行重写了。
override修饰子类虚函数:该虚函数必须是父类的虚函数的重写。

在这里插入图片描述

重载、重写、重定义再理解

也就是说:两个基类和派生类中的同名函数 不构成重写 就是重定义。


抽象类

抽象类的概念

虚函数后面写上 = 0 ,这个虚函数就变成了纯虚函数, 包含纯虚函数的类称为抽象类(也叫接口类),包含纯虚函数的类无法实例化出对象,抽象类的派生类想要实例化出对象必须对纯虚函数实现重写,否则派生类也是抽象类。

接口继承与实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
所以如果不实现多态,不要把函数定义成虚函数。

这里是引用


多态的原理

多态,如果只看表面应用 – 不同的对象调用"同一个函数"表现也不一样,看起来感觉好像很神奇、很厉害,居然可以“进行判断?”,
那么到底是不是这样呢,让我们去底层一探究竟吧~。
(注:以下数据测试环境为 vs2022,x86)

虚函数表

class Base
{
public:
	virtual void Print()
	{
		cout << "Base::Print" << endl;
	}
	int _bval;
};


void Test_p3()
{
	Base b1;
	cout << sizeof(b1) << endl;

}

我们来计算一下Base类的大小:
按照我们以前的知识:成员函数是放在代码段,对象中只有普通成员变量, 因此,Base的大小应该是4;

这里是引用

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

class Base
{
public:
	virtual void Print1() {}

	virtual void Print2() {}

	int _bval = 1;
};

class Derive :public Base
{
public:
	virtual void Print1() {}

	virtual void Print3() {}

	int _dval = 10;
};

void Test_p4()
{
	Base b1;

	Derive d1;
}

这里是引用

这里有一点我们需要注意:虚函数表存在哪里?虚函数又存在哪里?
虚函数表存在对象中,虚函数存在虚函数表中,吗?
不是的,对象中存的是一个虚函数表指针,虚函数表中存的也只是虚函数的指针,
至于虚函数表和虚函数,其实都存在于内存中的代码段

打印虚函数表

typedef void(*VFPTR)();  //  定义 VFPTR为  void(*)() -- 函数指针类型

void VFTable(VFPTR*table)
{
	/*while (*table)
	{
		(*table)();
		++table;
	}*/
	for (int i = 0; table[i]; ++i)
	{
		printf("[%d]->", i);
		table[i](); // 函数调用
	}
	cout << endl;
}

void Test_p4()
{
	Base b1;
	Derive d1;

	//  要打印虚函数表,我就要先获取虚函数表的地址 -- 通过上面几次的查看我们可以看到 -- 虚函数表地址存放在对象的最前面
	VFTable((VFPTR*)(*(int*)&b1));
	VFTable((VFPTR*)(*(int*)&d1));

	VFTable(*(VFPTR**)&b1); 
	VFTable(*(VFPTR**)&d1);
}

这里是引用在这里插入图片描述


原理剖析

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

补充:

  1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
  2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

经典例题

  • 1
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };

int main() 
{
	Derive d;
	Base1* p1 = &d;
	Base2* p2 = &d;
	Derive* p3 = &d;
	return 0;
}

在这里插入图片描述

  • 2
class AA
{
public:
	virtual void Print(int a = 1)
	{
		cout << "a = " << a << endl;
	}

	virtual void Call() { Print(); }
};

class BB :public AA
{
public:
	virtual void Print(int b = 0) 
	{ 
		cout << "b = " << b << endl; 
	}
};

void Test_p1()
{
	BB p;
	p.Call();
}

在这里插入图片描述

总结

多态的重点

  1. 就是要了解多态构成的条件:父类的指针或引用;虚函数重写。
  2. 就是知道了解虚函数表的原理:存的是虚函数地址。
  3. 清楚多态实现的原理。
  4. 虚表地址存放在地址空间的最上方,此处要区分虚继承中的虚基表,虚基表地址存放在地址空间的最下方。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【c++ 之 多态】 的相关文章

  • 检测到 NuGet 包的版本冲突

    我正在开发 ASP Net core 2 1 Web 应用程序项目 我的解决方案中有 1 个项目和 3 个其他库 它是高级架构 数据访问层 DAL 业务层 BL 公共层 CL 所以我需要添加引用来连接一些库和项目 我已经添加了CL参考我的项
  • 将处理后的图形绘制到另一个图形中

    我想将一个经过处理的图形绘制到另一个图形中 I have two graphics var gHead Graphics FromImage h var gBackground Graphics FromImage b Transform
  • 如何进行带有偏差的浮点舍入(始终向上或向下舍入)?

    我想以偏置舍入浮动 要么总是向下 要么总是向上 代码中有一个特定的点 我需要这个 程序的其余部分应该像往常一样四舍五入到最接近的值 例如 我想四舍五入到最接近的 1 10 倍数 最接近 7 10 的浮点数约为 0 69999998807 但
  • 获取两个字符串之间的公共部分c# [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我需要的是获取两个单词之间的共同部分并获取差异 例子 场景1 word1 感言 word2 Test 将返回 公共部分Test 不同之
  • 当我单击 C# 中的“取消”按钮时重定向到新页面(Web 部分)

    Cancel button tc new TableCell btnCancel new Button btnCancel Text Cancel btnCancel Click new EventHandler btnCanel Clic
  • Linux TUN/TAP:无法从 TAP 设备读回数据

    问题是关于如何正确配置想要使用 Tun Tap 模块的 Linux 主机 My Goal 利用现有的路由软件 以下为APP1和APP2 但拦截并修改其发送和接收的所有消息 由Mediator完成 我的场景 Ubuntu 10 04 Mach
  • std::map 和二叉搜索树

    我读过 std map 是使用二叉搜索树数据结构实现的 BST 是一种顺序数据结构 类似于数组中的元素 它将元素存储在 BST 节点中并按其顺序维护元素 例如如果元素小于节点 则将其存储在节点的左侧 如果元素大于节点 则将其存储在节点的右侧
  • TextBox 焦点的 WinForms 事件?

    我想添加一个偶数TextBox当它有焦点时 我知道我可以用一个简单的方法来做到这一点textbox1 Focus并检查布尔值 但我不想那样做 我想这样做 this tGID Focus new System EventHandler thi
  • VS30063:您无权访问 https://dev.azure.com

    我正在尝试在 asp net core 2 1 mvc 应用程序中使用以下代码连接 Azure DevOps Uri orgUrl new Uri https dev azure com xxxxx String personalAcces
  • C++11 函数局部静态 const 对象的线程安全初始化

    这个问题已在 C 98 上下文中提出 并在该上下文中得到回答 但没有明确说明有关 C 11 的内容 const some type create const thingy lock my lock some mutex static con
  • 事件日志写入错误

    很简单 我想向事件日志写入一些内容 protected override void OnStop TODO Add code here to perform any tear down necessary to stop your serv
  • 在 C 中复制两个相邻字节的最快方法是什么?

    好吧 让我们从最明显的解决方案开始 memcpy Ptr const char a b 2 调用库函数的开销相当大 编译器有时不会优化它 我不会依赖编译器优化 但即使 GCC 很聪明 如果我将程序移植到带有垃圾编译器的更奇特的平台上 我也不
  • UWP 无法在两个应用程序之间创建本地主机连接

    我正在尝试在两个 UWP 应用程序之间设置 TCP 连接 当服务器和客户端在同一个应用程序中运行时 它可以正常工作 但是 当我将服务器部分移动到一个应用程序并将客户端部分移动到另一个应用程序时 ConnectAsync 会引发异常 服务器未
  • 从匿名类型获取值

    我有一个方法如下 public void MyMethod object obj implement 我这样称呼它 MyMethod new myparam waoww 那么我该如何实施MyMethod 获取 myparam 值 Edit
  • 如何检测 C# 中该字典键是否存在?

    我正在使用 Exchange Web 服务托管 API 和联系人数据 我有以下代码 即功能性的 但并不理想 foreach Contact c in contactList string openItemUrl https service
  • Fluent NHibernate 日期时间 UTC

    我想创建一个流畅的 nhibernate 映射来通过以下方式映射 DateTime 字段 保存时 保存 UTC 值 读取时 调整为本地时区值 实现此映射的最佳方法是什么 就我个人而言 我会将日期存储在 UTC 格式的对象中 然后在读 写时在
  • 热重载时调用方法

    我正在使用 Visual Studio 2022 和 C 制作游戏 我想知道当您热重新加载应用程序 当它正在运行时 时是否可以触发一些代码 我基本上有 2 个名为 UnloadLevel 和 LoadLevel 的方法 我想在热重载时执行它
  • 在基类集合上调用派生方法

    我有一个名为 A 的抽象类 以及实现 A 的其他类 B C D E 我的派生类持有不同类型的值 我还有一个 A 对象的列表 abstract class A class B class A public int val get privat
  • Swagger 为 ASP.CORE 3 中的字典生成错误的 URL

    当从查询字符串中提取的模型将字典作为其属性之一时 Swagger 会生成不正确的 URL 如何告诉 Swagger 更改 URL 中字典的格式或手动定义输入参数模式而不自动生成 尝试使用 Swashbuckle 和 NSwag 控制器 pu
  • 从类模板参数为 asm 生成唯一的字符串文字

    我有一个非常特殊的情况 我需要为类模板中声明的变量生成唯一的汇编程序名称 我需要该名称对于类模板的每个实例都是唯一的 并且我需要将其传递给asm关键字 see here https gcc gnu org onlinedocs gcc 12

随机推荐

  • 使用vue-cli脚手架创建vue项目

    本文主要介绍使用的是安装vue cli脚手架并使用vue cli脚手架来创建vue项目 1 前置条件 需要安装npm 和nodejs 查看npm 和nodejs版本是否符合要求 如果版本不符合要求 有可能会导致安装失败 查看npm版本 np
  • 数学(3) 各种数学分布,高斯,伯努利,二项,多项,泊松,指数,Beta,Dirichlet

    打算这里记录各种数学分布 随时更新 正态分布 正态分布又名高斯分布 若随机变量X服从一个数学期望为 mu 标准差为 sigma的正态分布 则记为 X N 2 X 65374 N mu sigma 2 其中期望 mu决定了分布位置 标准差 s
  • Linux-eth0 eth0:1 和eth0.1关系、ifconfig以及虚拟IP实现介绍

    eth0 eth0 1 和eth0 1三者的关系对应于物理网卡 子网卡 虚拟VLAN网卡的关系 物理网卡 物理网卡这里指的是服务器上实际的网络接口设备 这里我服务器上双网卡 在系统中看到的2个物理网卡分别对应是eth0和eth1这两个网络接
  • Android开机自启动C程序调试

    Android开机自启动C程序调试 本次记录是关于如何在rk3566的Android11版本下将led时钟显示添加成开机自启动的C程序 首先 当然是在sdk中会被执行到的 rc文件中将我们所需要执行的C程序添加为服务 可以在init rc或
  • 剑指 Offer 58 - II. 左旋转字符串(java+python)

    字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部 请定义一个函数实现字符串左旋转操作的功能 比如 输入字符串 abcdefg 和数字2 该函数将返回左旋转两位得到的结果 cdefgab 示例 1 输入 s abcdefg k
  • python-爬虫初识-自动登录(二)

    目录 一 BeautifulSoup模块详细介绍 二 自动登录github 一 BeautifulSoup模块详细介绍 BeautifulSoup是一个模块 该模块用于接收一个HTML或XML字符串 然后将其进行格式化 之后遍可以使用他提供
  • 自定义一个类加载器

    为什么要自定义类加载器 类加载机制 http www cnblogs com xrq730 p 4844915 html 类加载器 http www cnblogs com xrq730 p 4845144 html 这两篇文章已经详细讲解
  • HKPCA Show携手电巢直播开启“云”观展!掀起一场电子人的顶级狂欢!

    近日 国际电子电路 深圳 展览会 HKPCA Show 已于深圳国际会展中心圆满举办 本次展览划分七大主题专区 面积超50 000平方米 展位超2500个 汇聚众多行业知名 有影响力的参展商 引用现场观众的一句话讲 这种规模的电子展览会真是
  • 北欧--2022年Python爬虫心得

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 前言 网络爬虫也称为 蜘蛛 它可以在海量的互联网信息爬取需要的信息 简单地说它是模拟人类请求网站的行为 即自动请求网页 抓取数据 然后从中提取有价值的数据 具体步骤如下 首先
  • oracle主键约束删除,oracle删除主键查看主键约束及创建联合主键

    oracle删除主键查看主键约束及创建联合主键 1 主键的删除 ALTER TABLE TABLENAME DROP PRIMARY KEY 执行上面的SQL可以删除主键 如果不成功可以用 ALTER TABLE TABLENAME DRO
  • C/C++访问MySQL数据库

    C C 访问MySQL数据库 VS2019配置 打开mysql的安装目录 默认安装目录如下 C Program Files MySQL MySQL Server 8 0 确认 lib 目录和include 目录是否存在 打开VS2019 新
  • 如何将自己的项目jar包打包成docker 镜像

    首先将自己的项目打包成jar 并在自己本地先用java jar xxx jar启动下 看是否可以启动 随后将自己的jar包同级目录创建一个Dockerfile文件 并用notepad打开 文件无后缀 FROM kdvolder jdk8 V
  • linux下手动安装编译的通用步骤

    手动安装编译的通用步骤 在自己的下载目录中 1 在官网下载压缩包 wget xxx 2 解压文件 tar zxf xxx 3 开始编译安装 查看解压的目录下的文件 是config还是autogen之类的 来决定使用 autogen sh还是
  • 如何使用memset函数

    如何使用memset函数 memset用处 memset使用方法 memset用处 memset函数是主要用于初始化字符串的一个函数 也可以用于初始化自定义类型数组 结构体数组和其他类型数组 memset使用方法 memset函数原型如下
  • 机器学习19:反卷积算法

    机器学习19 反卷积算法 转载和整理 在整理全卷积网络的过程中 被反卷积的概念困扰很久 于是将反卷积算法单独整理为一篇博客 本文主要转载和整理自知乎问题如何通俗易懂地解释反卷积 中的高票答案 1 反卷积概述 应用在计算机视觉的深度学习领域
  • java将本地图片复制添加水印并导出到本地

    模板信息 package com example demo ChartGraphics import com sun image codec jpeg JPEGCodec import com sun image codec jpeg JP
  • Mybatis动态公用sql

  • 文件夹自动同步工具

    这是我之前开发的文件夹自动同步工具 主要实现开发机和服务器之间的文件夹同步 项目地址 https github com mike zhang autoSync 问题描述 在windows下修改代码 到服务器上去编译 但每次都要通过winsc
  • JAR 文件揭密

    JAR 文件揭密 探索 JAR 文件格式的强大功能 转载 原文地址 http www ibm com developerworks cn java j jar 简介 大多数 Java 程序员都熟悉对 JAR 文件的基本操作 但是只有少数程序
  • 【c++ 之 多态】

    目录 前言 多态 认识多态 多态的定义与实现 构成多态的条件 虚函数 1 协变 基类与派生类虚函数返回值不同 2 析构函数的重写 c 11 两个虚函数修饰关键字 final override 重载 重写 重定义再理解 抽象类 抽象类的概念