C++多态【下】

news2024/11/30 2:26:37

在这里插入图片描述

文章目录

  • 1.多态实现的底层
    • 1.1初识多态原理
    • 1.2深入理解虚函数表
      • 1.单继承虚函数表
      • 2.探究虚函数表存储数据
      • 3.知识点金
      • 4.多继承虚函数表
  • 2.题目讲解

1.多态实现的底层

1.1初识多态原理

class Dad 
{
public:
	virtual void Cook() 
	{ 
		cout << "佛跳墙" << endl; 
	}

	virtual void Work() 
	{ 
		cout << "Work" << endl; 
	}
	int _a = 0;
};

class Son : public Dad 
{
public:
	virtual void Cook()
	{ 
		cout << "方便面" << endl; 
	}

	int _b = 0;
};

void Test(Dad& p)
{
	p.Cook();
}

int main()
{
	Dad dad;
	Test(dad);

	Son son;
	Test(son);

	return 0;
}

在这里插入图片描述

1.2深入理解虚函数表

1.单继承虚函数表

同类型对象共用一个虚表

若子类不重写 父类虚表指向父类的虚函数 子类虚表也指向父类的虚函数
但是vs下 不管是否重写 子类跟父类虚表都不是同一个
这样实现的理由:即便子类没有重写 但是子类有自己的虚函数时 单独创建一个虚表和父类分隔开 更有条理
子类虚函数表存储:重写的父类虚函数 没有重写的父类虚函数 自己的虚函数

2.探究虚函数表存储数据

class Dad
{
public:
	virtual void BuyCar()
	{
		cout << "Dad::买车-宾利" << endl;
	}

	virtual void Func1()
	{
		cout << "Dad::Func1()" << endl;
	}
};

class Son : public Dad 
{
public:
	virtual void BuyCar()
	{
		cout << "Son::买车-奔驰" << endl;
	}

	virtual void Func2()
	{
		cout << "Son::Func2()" << endl;
	}
};

typedef void(*vftptr)();
void PrintVftable(vftptr* pt)  //void PrintVftable(vftptr pt[])
{
	for (size_t i = 0; *(pt + i) != nullptr; ++i)
	{
		printf("vft[%d]:%p->", i, pt[i]);
		//1.直接访问
		pt[i]();
		//2.间接访问
		//vftptr pf = pt[i];
		//pf();
	}
	cout << endl;
}
int main()
{
	Dad p1;
	Dad p2;

    Son s1;
	Son s2;

	//打印子类虚表
	PrintVftable((vftptr*)*(int*)&s1);
	PrintVftable((*(vftptr**)&s1));//解释见下

	//打印父类虚表
	PrintVftable((vftptr*)*(int*)&p1);
	PrintVftable((*(vftptr**)&p1));//解释见下
	
	return 0;
}

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

3.知识点金

  1. 虚表在编译阶段生成。
  2. 类实例化的对象中的虚表指针在构造函数的初始化列表初始化。
  3. 虚表存在于代码段。
    在这里插入图片描述
    在这里插入图片描述
int x = 0;
	static int y = 0;
	int* z = new int;
	const char* p = "xxxxxxxxxxxxxxxxxx";

	printf("栈对象:%p\n", &x);
	printf("堆对象:%p\n", z);
	printf("静态区对象:%p\n", &y);
	printf("常量区对象:%p\n", p);
	printf("s对象虚表:%p\n", *((int*)&s));
	printf("d对象虚表:%p\n", *((int*)&d1));

4.多继承虚函数表

#define _CRT_SECURE_NO_WARNINGS 
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <array>
#include <time.h>
#include <queue>
using namespace std;


class Dad1 
{
public:
	virtual void func1() 
	{ 
		cout << "Dad1::func1" << endl;
	}
	virtual void func2()
	{ 
		cout << "Dad1::func2" << endl;
	}
private:
	int a1 = 1;
};

class Dad2 
{
public:
	virtual void func1()
	{ 
		cout << "Dad2::func1" << endl; 
	}
	virtual void func2()
	{
		cout << "Dad2::func2" << endl; 
	}
private:
	int a2 = 2;
};

class Son : public Dad1, public Dad2 
{
public:
	virtual void func1() 
	{
		cout << "Son::func1" << endl;
	}
	virtual void func3()
	{ 
		cout << "Son::func3" << endl;
	}
private:
	int aa = 3;
};

typedef void(*vftptr)();
void PrintVftable(vftptr* pt)  //void PrintVftable(vftptr pt[])
{
	for (size_t i = 0; *(pt + i) != nullptr; ++i)
	{
		printf("vft[%d]:%p->", i, pt[i]);
		//1.直接访问
		pt[i]();
		//2.间接访问
		//vftptr pf = pt[i];
		//pf();
	}
	cout << endl;
}

int main()
{
	Dad1 d1;
	Dad2 d2;
	Son s;
	cout << "d1所占字节数为" << sizeof(d1) << endl;//8
	cout << "d2所占字节数为" << sizeof(d2) << endl;//8
	cout << "s所占字节数为"  << sizeof(s)  << endl;//20
	//显示虚表Ⅰ
	PrintVftable((vftptr*)(*(int*)&s)); //int只能访问4个字节 在64位下不再适用1
	//PrintVftable((*(vftptr**)&s)); 高级写法
	//显示虚表Ⅱ法一:
	PrintVftable((vftptr*)(*(int*)((char*)&s+sizeof(Dad1))));
	//PrintVftable((*(vftptr**)((char*)&s + sizeof(Dad1))));高级写法
	//显示虚表tⅡ法二:
	//Dad2* ptr = &s;
	//PrintVftable((vftptr*)(*(int*)(ptr)));
	//PrintVftable((*(vftptr**)ptr)); 高级写法

	cout << "单独调用Son中的func1->" ;
	printf("%p\n", &Son::func1); //成员函数需要加&才能取到地址 普通函数名就可作为地址

	//普通调用
	s.func1();

	//多态调用
	Dad1* ptr1 = &s;
	ptr1->func1();

	Dad2* ptr2 = &s;
	ptr2->func1();

	return 0;
}

在这里插入图片描述

问题:这三次调用的func1是不是同一个函数?答案是肯定的,从运行结果最三行可以看出。但是为什么这三次调用的地址都不一样???答案见下
在这里插入图片描述
图中可以看出 调用ptr2时执行了在这里插入图片描述为什么呢?答案见下。
在这里插入图片描述
以上汇编代码仅供参考。解读:调用ptr2时,先执行了在这里插入图片描述目的是使得此时的this指针能够指向s对象的首地址。为什么ptr1调用时没有此动作?因为ptr1调用时,this指针指向Dad1部分,恰好就是s对象的首地址。而ptr2调用时,是s中间的某个位置。需要修正this指针到s对象的首地址。
所以有在这里插入图片描述。也就解释了为什么从监视窗口看到两个func1函数地址不同。实际上是同一个函数,只不过其中一个要到另一个地方,做一些特定的事情。

2.题目讲解

  1. 什么是多态?
  2. 什么是重载、重写(覆盖)、重定义(隐藏)?
  3. 多态的实现原理?
  4. inline函数可以是虚函数吗?
  5. 静态成员可以是虚函数吗?
  6. 构造函数可以是虚函数吗?
  7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
  8. 对象访问普通函数快还是虚函数更快?
  9. 虚函数表是在什么阶段生成的,存在哪的?
  10. C++菱形继承的问题?虚继承的原理?
  11. 什么是抽象类?抽象类的作用?

答案:

  1. 多态是指同一种行为(方法)在不同对象上产生不同的结果。在面向对象编程中,多态是通过继承和重写(覆盖)实现的。子类可以重写父类的方法,从而产生不同的行为。

  2. 重载(Overload)是指在同一个作用域内,使用相同的函数名,但参数类型或个数不同的多个函数。

    重写(Override/覆盖)是指在派生类中重新定义(覆盖)基类中定义的虚函数,使其能够根据具体的派生类对象来执行对应的操作。

    重定义(Hide)是指在派生类中定义与基类中相同函数名的非虚函数,该函数会屏蔽基类中的同名函数,无法通过基类指针或引用调用派生类中重新定义的函数。

  3. 多态的实现原理是通过基类的指针或引用来访问派生类的对象,在运行时确定具体调用哪个类的函数。这是因为基类中的虚函数使用了虚函数表的机制,每个对象都有一个指向对应虚函数表的指针。当通过基类指针或引用调用虚函数时,根据对象的实际类型,在虚函数表中查找需要调用的函数,并执行相应的操作。

  4. inline函数可以是虚函数,但编译器会忽略inline属性,将该函数从inline函数列表中移除,因为虚函数需要放在虚函数表中。

  5. 静态成员函数不能是虚函数,因为静态成员函数没有this指针,使用 类型::成员函数 的调用方式无法访问虚函数表,所以静态成员函数无法放入虚函数表。

  6. 构造函数不能是虚函数,因为构造函数中的虚函数表指针是在构造函数初始化列表阶段才初始化的,此时对象尚未完全建立。

  7. 析构函数可以是虚函数,并且最好将基类的析构函数定义为虚函数。这样当通过基类指针或引用来删除一个派生类对象时,会调用正确的析构函数并避免内存泄漏。虚析构函数通常用于处理多态对象的释放问题。

  8. 对象访问普通函数和访问虚函数的速度相同,对于普通对象,直接调用函数就可以了,不需要查找虚函数表。而对于指针对象或引用对象,由于可能存在多态性,需要根据实际类型查找虚函数表,稍微慢一些。

  9. 虚函数表是在编译阶段生成的,一般情况下存储在代码段(常量区)。每个类有一个独立的虚函数表,其中存储了该类及其基类的虚函数信息。对象在创建时会分配一块内存用来存储动态分派所需的虚函数表指针,通过这个指针来访问虚函数表并执行对应的函数。

  10. C++中的菱形继承问题是指一个派生类同时继承了两个共同基类,而这两个基类又继承了同一个虚基类,造成了二义性和资源浪费的问题。为了解决这个问题,可以使用虚继承(virtual inheritance)来共享同一个虚基类,避免重复继承。

  11. 抽象类是指含有纯虚函数(只有函数声明,没有函数体)的类,无法实例化对象。抽象类一般用作基类,强制派生类重写纯虚函数,从而达到接口继承的目的。抽象类的作用是定义一组接口(纯虚函数),规范具体派生类的行为。

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

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

相关文章

Kafka3.0.0版本——消费者(消费者组原理)

目录 一、消费者组原理1.1、消费者组概述1.2、消费者组图解示例1.3、消费者组注意事项 一、消费者组原理 1.1、消费者组概述 Consumer Group&#xff08;CG&#xff09;&#xff1a;消费者组&#xff0c;由多个consumer组成。形成一个消费者组的条件&#xff0c;是所有消费者…

Qt实现图书管理系统(C++)

文章目录 数据库表的实现创建表将powerDesigner里面的表导出成xxx.sql脚本将SQL文件导入数据库创建表 图书管理系统思维导图创建工程开发阶段创建Dlg_login登录页面login页面样式主页页面布局主函数测试login设置logo打包程序子页面的样子将子页面放到StackedWidget里面按钮直接…

springboot MongoDB 主从 多数据源

上一篇&#xff0c;我写了关于用一个map管理mongodb多个数据源&#xff08;每个数据源&#xff0c;只有单例&#xff09;的内容。 springboot mongodb 配置多数据源 临到部署到阿里云的测试环境&#xff0c;发现还需要考虑一下主从的问题&#xff0c;阿里云买的数据库&#x…

C语言——指针完全版

一、指针的运算 1.1指针 - 整数 总结&#xff1a;指针的类型决定了指针向前或者向后走一步有多大&#xff08;距离&#xff09;。 1.2指针 - 指针 int main() {int arr[10] { 0 };printf("%d\n", &arr[9] - &arr[0]);return 0; } 当我们想用两个指针相减时…

【100天精通Python】Day57:Python 数据分析_Pandas数据描述性统计,分组聚合,数据透视表和相关性分析

目录 1 描述性统计&#xff08;Descriptive Statistics&#xff09; 2 数据分组和聚合 3 数据透视表 4 相关性分析 1 描述性统计&#xff08;Descriptive Statistics&#xff09; 描述性统计是一种用于汇总和理解数据集的方法&#xff0c;它提供了关于数据分布、集中趋势和…

怎么把pdf转换成jpg图片?

怎么把pdf转换成jpg图片&#xff1f;在工作中&#xff0c;如果我们收到无法修改编辑的PDF文件&#xff0c;可能会遇到一些困难。尤其是当平台或网站只支持JPG图片格式&#xff0c;而领导又要求我们将pdf文件改为JPG格式时&#xff0c;情况就更为棘手了。这对于我们打工一族来说…

二、模型驱动测试设计

如果能够提升抽象层级&#xff0c;测试设计师会更加有效和有效率。 完全改正软件是不可能到达的&#xff0c;其原因是可以以形式化的方式来表述的而且是富有哲理的。聪明的软件工程师不再追求软件的完全正确&#xff0c;而是试着评判软件的行为来决定其是否为可接受的。**包括可…

Linux之SELinux

目录 概述 定义 作用 SELinux与传统的权限区别 SELinux工作原理 名词解释 主体&#xff08;Subject&#xff09; 目标&#xff08;Object&#xff09; 策略&#xff08;Policy&#xff09; 安全上下文&#xff08;Security Context&#xff09; 文件安全上下文查看 …

【MySQL基础】事务隔离03

目录 隔离性与隔离级别事务隔离的实现事务的启动方式MySQL事务代码示例 在MySQL中&#xff0c;事务支持是在引擎层实现的。MySQL是一个支持多引擎的系统&#xff0c;但并不是所有的引擎都支持事务。比如 MySQL 原生的 MyISAM 引擎就不支持事务&#xff0c;这也是 MyISAM 被 Inn…

永安通配符和多域名SSL证书的区别

随着互联网的快速发展&#xff0c;现在大多数人都已经习惯在网上交流、购物、学习&#xff0c;因此互联网上的各种类型的网站越来越多&#xff0c;不仅是企事业单位创建各种类型的网站&#xff0c;个人开发者创建的网站也越来越多&#xff0c;一张单域名SSL就不能满足个人或者企…

Windows云服务器 PHP搭建网站外网无法访问的问题

前言&#xff1a;本人在华为云上租了一台windows的云主机&#xff0c;可以远程访问桌面的那种&#xff0c;然后想搭个网站&#xff0c;最开始想到的是IIS&#xff0c;测试了下用html的文件&#xff0c;没有问题。但是&#xff0c;php文件却不能用&#xff0c;因为少了PHP环境。…

【LeetCode - 每日一题】2594. 修车的最少时间(23.09.07)

2594. 修车的最少时间 题意 给定每个师傅修车的时间和需要修的车辆总数&#xff0c;计算修理所有汽车需要的最少时间。师傅可以同时修车。 解法 二分 看到题目没有任何头绪&#xff0c;直接查看题解。 至于为什么用二分做呢&#xff0c;讨论区有个友友这么说到&#xff1a…

【Linux】LVM原理及核心概念

LVM是什么&#xff1f;LVM核心概念LVM的优势在Linux上使用LVM感谢 &#x1f496; LVM是什么&#xff1f; LVM是一种高级的磁盘管理工具&#xff0c;用于在Linux和其他类Unix操作系统中管理磁盘存储。它的核心思想是将底层物理存储抽象为逻辑存储单元&#xff0c;从而提供了更大…

如何使用HTTP代理爬虫,防止对网站造成负面影响

在当今大数据时代&#xff0c;爬虫技术已经成为了获取数据的重要手段之一。但是&#xff0c;由于爬虫程序的高频访问容易对目标网站造成负面影响&#xff0c;如增加服务器负载、影响网站性能等&#xff0c;因此&#xff0c;如何使用HTTP代理爬虫防止对网站造成负面影响成为了一…

idea中mapper直接跳转到xml的插件

一.点击File | Settings | Plugins&#xff0c;下载插件 二、重启idea

Shopify电子邮件营销方法?邮件营销的技巧?

Shopify电子邮件营销怎么操作&#xff1f;独立站如何做邮件营销? Shopify电子邮件营销是一种强大的工具&#xff0c;可帮助电商企业与其客户建立联系并提高销售。蜂邮EDM将探讨一些有效的Shopify电子邮件营销方法&#xff0c;以帮助您最大限度地利用这一策略。 Shopify电子邮…

suning苏宁API接入说明(苏宁商品详情+关键词搜索商品列表)

API地址:https://o0b.cn/anzexi 调用示例&#xff1a;https://api-gw.onebound.cn/suning/item_get/?keytest_api_key& &num_iid0070134261/703410301&&langzh-CN&secret 参数说明 通用参数说明 version:API版本key:调用key,测试key:test_api_keyapi_na…

攻防世界-WEB-ics-05

打开靶机 只有设备维护中心可以点开 点标签得到新的url pageindex 想到文件包含漏洞&#xff08;URL中出现path、dir、file、pag、page、archive、p、eng、语言文件等相关关键字眼 利用php伪协议查看源码 出现一段base64源码&#xff0c;进行转码得出源码 ?pagephp://filter…

Java-day13(IO流)

IO流 凡是与输入&#xff0c;输出相关的类&#xff0c;接口等都定义在java.io包下 1.File类的使用 File类可以有构造器创建其对象&#xff0c;此对象对应着一个文件(.txt,.avi,.doc,.mp3等)或文件目录 File类对象是与平台无关的 File中的方法仅涉及到如何创建&#xff0c;…

数据挖掘的学习路径

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…