C++多态【下】

在这里插入图片描述

文章目录

  • 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;//8cout << "d2所占字节数为" << sizeof(d2) << endl;//8cout << "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.hqwc.cn/news/102134.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

elasticsearch的DSL查询文档

DSL查询分类 查询所有&#xff1a;查询出所有数据&#xff0c;一般测试用。例如&#xff1a;match_all 全文检索&#xff08;full text&#xff09;查询&#xff1a;利用分词器对用户输入内容分词&#xff0c;然后去倒排索引库中匹配。例如&#xff1a; match_query multi_ma…

3D封装技术发展

长期以来&#xff0c;芯片制程微缩技术一直驱动着摩尔定律的延续。从1987年的1um制程到2015年的14nm制程&#xff0c;芯片制程迭代速度一直遵循摩尔定律的规律&#xff0c;即芯片上可以容纳的晶体管数目在大约每经过18个月到24个月便会增加一倍。但2015年以后&#xff0c;芯片制…

sql_mode详解

文章目录 一、sql_mode作用二、查询sql_mode三、mysql8默认的mode配置&#xff08;6个默认配置&#xff09;四、常见mode详细解释mysql8默认配置了的mode&#xff08;6个&#xff09;需要自己配置的mode&#xff08;4个&#xff09; 五、设置sql_mode&#xff08;一旦设置了&am…

华为云使用脚本初始化Linux数据盘

初始化新挂载的磁盘 登录云服务器&#xff0c;执行以下命令获取自动初始化磁盘脚本。 wget https://ecs-instance-driver.obs.cn-north-1.myhuaweicloud.com/datadisk/LinuxVMDataDiskAutoInitialize.sh 说明&#xff1a; 若回显异常&#xff0c;请检查云服务器是否绑定弹性公…

Android逆向学习(四)app修改smali函数跳过弹窗广告,等待广告,更新提醒

Android逆向学习&#xff08;四&#xff09;app修改smali函数跳过弹窗广告&#xff0c;等待广告&#xff0c;更新提醒 一、写在前面 这是吾爱破解课程的第三个练习&#xff0c;我在写这篇博客时遇到了vscode插件bug&#xff0c;已经想办法联系原作者了&#xff0c;希望能够尽…

明确企业知识库及知识平台搭建的重要性,开启企业成长之路

在企业运营过程中产生经营数据、管理规范、文化、资料、文档等大量数据&#xff0c;这些数据是花费了大量时间和金钱成本所积累的数据&#xff0c;如果不加以整理总结会造成巨大的浪费。 想要形成结构化、易操作、易利用、易储存、可传承的知识集群&#xff0c;是使用HelpLook在…

考研资料共享系统的设计说明

考研资料共享系统的设计说明 设计意义及目的模块划分技术难点写项目中遇到的问题该项目的后端模块介绍该项目的前端模块介绍运行演示Gitee链接 设计意义及目的 为了方便找资料&#xff0c;了解考研形式&#xff1b;另一方面是锻炼编写系统的能力 模块划分 主要划分为&#xff1…

Druid LogFilter输出可执行的SQL

配置 测试代码&#xff1a; DruidDataSource dataSource new DruidDataSource(); dataSource.setUrl("xxx"); dataSource.setUsername("xxx"); dataSource.setPassword("xxx"); dataSource.setFilters("slf4j"); dataSource.setVal…

Java“牵手”淘宝商品列表数据,关键词搜索淘宝商品数据接口,淘宝API申请指南

淘宝商城是一个网上购物平台&#xff0c;售卖各类商品&#xff0c;包括服装、鞋类、家居用品、美妆产品、电子产品等。要获取淘宝商品列表和商品详情页面数据&#xff0c;您可以通过开放平台的接口或者直接访问淘宝商城的网页来获取商品详情信息。以下是两种常用方法的介绍&…

大模型技术实践(三)|用LangChain和Llama 2打造心灵疗愈机器人

上期文章我们实现了Llama 2-chat-7B模型的云端部署和推理&#xff0c;本期文章我们将用“LangChainLlama 2”的架构打造一个定制化的心灵疗愈机器人。有相关知识背景的读者可以直接阅读「实战」部分。 01 背景 1.1 微调 vs. 知识库 由于大模型在垂直行业领域的问答效果仍有待提…

Revit SDK:Selections 选择

前言 Revit 作为一款成熟的商业软件&#xff0c;它将自己的UI选择功能也通过 API 暴露出来。通过 API 可以按照特定的过滤规则来选择相应的元素&#xff0c;能力和UI基本上是等价的。这个 SDK 用四个例子展示了 API 的能力&#xff0c;内容如下。 内容 PickforDeletion 核心…

A133P EC200M模块调试

Linux USB驱动框架&#xff1a; USB 是一种分层总线结构。USB 设备与主机之间的数据传输由 USB 控制器控制。Linux USB 驱动程序架构如下图所示。Linux USB 主机驱动包括三部分&#xff1a;USB 主机控制器驱动、USB 核心和 USB 设备驱动。 模块加载 USB 转串口 option 驱动程序…