C++ 多态的相关问题

目录

1. 第一题

2. 第二题

3. inline 函数可以是虚函数吗

4. 静态成员函数可以是虚函数吗

5. 构造函数可以是虚函数吗

6. 析构函数可以是虚函数吗

7. 拷贝构造和赋值运算符重载可以是虚函数吗

8. 对象访问普通函数快还是访问虚函数快

9. 虚函数表是什么阶段生成的?存在哪里的?


1. 第一题

class A
{
public:virtual void Func(int val = 1) { std::cout << "A->" << val << std::endl; }virtual void Test() { Func(); }
};class B : public A
{
public:void Func(int val = 0) { std::cout << "B->" << val << std::endl; }
};int main()
{B* ptr = new B;ptr->Test();return 0;
}// A: A->0
// B: B->1
// C: A->1
// D: B->0
// E: 编译出错
// F: 以上都不正确

答案是什么呢? 

分析过程:

  • 首先,派生类 B 继承 A类,会将B类的方法继承下来,但注意,继承是派生类有访问基类方法的权限,而并不是说基类的方法在派生类中也有一份,继承后的基类方法依旧属于基类;
  • 其次,多态的条件以及注意多态是接口继承;
  • 最后,根据多态判别调用什么方法,即指向的什么对象,就调用谁的方法。

如下:

2. 第二题

class A{
public:A(char *s) { std::cout << s << std::endl; }~A(){}
};
class B :virtual public A
{
public:B(char *s1, char*s2) :A(s1) { std::cout << s2 << std::endl; }
};
class C :virtual public A
{
public:C(char *s1, char*s2) :A(s1) { std::cout << s2 << std::endl; }
};
class D :public B, public C
{
public:D(char *s1, char *s2, char *s3, char *s4) :B(s1, s2), C(s1, s3), A(s1){std::cout << s4 << std::endl;}
};int main() {D *p = new D("class A", "class B", "class C", "class D");delete p;return 0;
}// A:class A class B class C class D
// B:class D class B class C class A
// C:class D class C class B class A
// D:class A class C class B class D

分析过程如下:

第一个问题:为什么D类的实例化对象要显示调用A的构造呢?并且此时我们发现如果不调用A的构造还会报错,如下:

class D :public B, public C
{
public:D(char *s1, char *s2, char *s3, char *s4) :B(s1, s2), C(s1, s3) /*  A(s1) */{std::cout << s4 << std::endl;}
};

现象如下:

 

原因是因为这是一个菱形虚拟继承 ,如图所示:

而菱形虚拟继承带来的结果就是A只有一份,既然只有一份(B和C类共享),在B和C类进行初始化是不合适的。因此需要在D类调用A的构造函数。

但是我们发现,B和C类也在初始化列表中显式调用了A的构造函数。那这是为什么呢?因为有些情况下,我们可能会单独实例化B和C的对象,此时就需要在B和C中初始化A类了。因此B和C类也需要显示调用A的构造函数。

但是对于D实例化的对象来说,只会在D中对A类的资源进行初始化。

清楚了这个问题 ,接下来就简单了,之前说过,初始化列表的初始化顺序是由继承的先后顺序决定的。谁先继承,就先初始化谁。而在这里,继承的先后顺序:A、B、C;

因此,初始化列表的初始化顺序:A、B、C

即最后的答案就是 class A class B class C class D

3. inline 函数可以是虚函数吗

我们之前学习过 inline 函数,内联函数的特点就是:

会在调用的地方展开,潜台词就是没有函数地址,而虚函数的地址会进入虚函数表,那么既然内联函数都没有地址了,也就不可能是虚函数了。

因此我们的结论就是:inline不可以是虚函数。

但是,结果不是这样:

class A
{
public:inline virtual void Func(){std::cout << "haha" << std::endl;}
};void Test22()
{A a;a.Func();
}

现象如下: 

 

当我们用A实例化的对象a去调用 Func() 时,发现不仅没有编译报错,还能正常调用 Func()。

那是不是我们分析错了呢?

  • 首先,内联函数我们当初学的时候说过,inline 只是一个建议,具体这个函数最后会不会是一个内联函数是由编译器决定的;
  • 具体就是,如果编译器认为这个函数是符合需求的 (如没有递归,且代码量很少) 那么编译器就会将这个函数声明为 inline 函数,会在调用的地方展开该函数。

因此,最后的结论就是,inline函数可以是虚函数,但是这个 inline 是否会有效,即 inline 函数是否会在调用的地方展开,就不一定了,测试 demo 如下

class A
{
public:inline virtual void Func(){std::cout << "haha" << std::endl;}
};class B
{
public:virtual void Func() { std::cout << "hehe" << std::endl; }
};void Test23(void)
{A* ptr = new B;// 多态调用ptr->Func();A a;// 普通调用a.Func();
}

 现象如下:

多态调用,但此时函数未被展开,即 inline 无效。

 普通调用,此时函数就被展开了,inline 有效。

可以看到,如果一个虚函数被声明为 inline 时:

  • 如果这个函数是多态调用,inline 就会失效;
  • 如果这个函数是普通调用,inline 就会有效,但最后该函数会不会被展开 (inline是否有效) 是由编译器决定的。

总而言之,inline 函数可以是虚函数。

4. 静态成员函数可以是虚函数吗

测试 demo 如下:

class A
{
public:static virtual void Func(){std::cout << "haha" << std::endl;}
};void Test24(void)
{A a;a.Func();
}

现象如下:

 可以看到,故静态成员函数不可以是虚函数,为什么呢?

  • 首先,静态成员函数是没有 this 指针的 (因为它属于整个类,而不属于某个对象),没有 this 指针就无法访问对象中的虚表指针,也就无法找到虚表;
  • 而虚函数存在的价值就是为了构成多态,而静态成员函数都无法访问虚表,怎么能构成多态呢? 因此,将虚函数声明为静态函数是无意义的,编译器进行了强制检查,如果一个虚函数是静态的,那么会编译报错。

总而言之,静态成员函数不可以是虚函数。

5. 构造函数可以是虚函数吗

测试 demo 如下:

class A
{
public:virtual A() { std::cout << "A()" << std::endl; }
};void Test25(void)
{A a;
}

现象如下: 

 

可以看到,发生了编译报错,构造函数不可以是虚函数,为什么呢?

首先我们需要搞明白一个问题:对象中的虚表指针是在什么创建好的呢? 测试 demo 如下:

class A
{
public:A() { std::cout << "A()" << std::endl; }virtual void Func()  { std::cout << "haha" << std::endl; }
};void Test25(void)
{A a;
}

启动进程,调出监视窗口,如下: 

可以看到,当 A 实例化的对象 a 还没有进入构造函数之前,具体是初始化列表的时候,虚表指针是没有被初始化的 (虚表是在编译阶段就已经构造好了)。 

可以看到对象中的虚表的指针是在初始化列表阶段中才进行初始化的。

那么也就是说先在初始化列表中初始化虚表指针,但如果此时将构造函数声明为虚函数,而虚函数的多态调用,需要到虚表去找,但是此时虚表指针都没有被初始化,怎么找到虚表你呢?此时就出问题了。

因此如果将构造函数定义为虚函数,那么此时构造函数无法进入虚表 (找不到虚表),换言之,构造函数不可以是虚函数。

6. 析构函数可以是虚函数吗

可以,并且最好是将析构函数定义为虚函数。

因为这样就可以做到,如果我指向的是一个基类,调用的就是基类的析构;如果我指向的是一个派生类,调用的是派生类的析构,可以做到合理释放资源。

7. 拷贝构造和赋值运算符重载可以是虚函数吗

拷贝构造不可以是虚函数,因为拷贝构造函数也是一个构造函数,原因与构造函数类似;

赋值运算符重载可以是虚函数,因为调用赋值的两个对象是已经存在的对象,既然已经存在的对象,如果有虚函数,那么虚表的指针是被初始化过了的,也就是说赋值运算符重载可以进入虚表,虽然赋值运算重载可以是虚函数,但是赋值运算符重载实现多态是没有实际价值的。

8. 对象访问普通函数快还是访问虚函数快

  • 如果符合多态调用,访问普通函数快,因为此时调用虚函数是一个运行时决议,需要去虚表中找虚函数的地址;
  • 如果符合普通调用,且此时调用虚函数是一个编译时决议,那么一样快。

9. 虚函数表是什么阶段生成的?存在哪里的?

构造函数中的初始化列表阶段初始化的是虚函数表的指针(虚表指针是存于对象中的),不是虚函数表,虚函数表是编译阶段时生成的。

那虚函数表存在哪里呢?

首先看看虚拟进程地址空间,具体如下:

我们用下面的 demo 验证下虚表的大概位置:

class A
{
public:A() {}virtual void Func() { std::cout << "Func()" << std::endl; }
};int global_val = 10;int main()
{A a;// 代码段的地址printf("code address: %p\n", main);// 字符常量区的地址const char* str = "haha\n";printf("string address: %p\n", str);// 静态区的地址static int i = 0;printf("static address: %p\n", &i);// 全局变量的地址printf("global address: %p\n", &global_val);// 虚表的地址printf("vft_ptr: %p\n", *(int*)(&a));return 0;
}

运行结果如下: 

可以看到, 虚表指针是在代码段和字符常量区之间的,事实上,菱形虚拟继承中的虚基表也是在这个范围之间的。

最后,再补充一句:

  • 对象中只有虚表指针,而无虚表;
  • 虚表指针是在类的构造函数中初始化的,而虚表是在编译阶段就生成了的。 

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

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

相关文章

华为 Huawei 交换机 配置 Dot1q 终结子接口实现同设备 VLAN 间通信示例

组网需求 企业的不同部门拥有相同的业务&#xff0c;如上网、 VoIP 等业务&#xff0c;且各个部门中的用户位于不同的网段。目前存在不同的部门中相同的业务所属的VLAN 不相同&#xff0c;现需要实现不同VLAN中的用户相互通信。 如 图 7-7 所示&#xff0c;部门 1 和部门 2 中…

【拼多多笔试题汇总】2024-05-09-拼多多春招笔试题-三语言题解(Cpp/Java/Python)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新拼多多近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f…

常见物联网面试题详解

物联网一直是非常火热的行业&#xff0c;G端如智慧城市、智慧工厂、智慧园区、智慧水利、智慧矿山等行业&#xff0c;都会涉及到物联网&#xff0c;基本都是软硬一体&#xff0c;因此当面试相关企业时&#xff0c;物联网平台是面试企业重点考察的项&#xff0c;小伙伴如果从事相…

前端使用Compressor.js实现图片压缩上传

前端使用Compressor.js实现图片压缩上传 Compressor.js官方文档 安装 npm install compressorjs使用 在使用ElementUI或者其他UI框架的上传组件时&#xff0c;都会有上传之前的钩子函数&#xff0c;在这个函数中可以拿到原始file&#xff0c;这里我用VantUI的上传做演示 a…

财务管理|基于SprinBoot+vue的财务管理系统(源码+数据库+文档)

财务管理系统 目录 基于SprinBootvue的财务管理系统 一、前言 二、系统设计 三、系统功能设计 系统功能实现 1管理员功能模块 2员工功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1…

Docker快速搭建NAS服务——NextCloud

Docker快速搭建NAS服务——NextCloud 文章目录 前言NextCloud的搭建docker-compose文件编写运行及访问 总结 前言 本文主要讲解如何使用docker在本地快速搭建NAS服务&#xff0c;这里主要写如下两种&#xff1a; FileBrowser1&#xff1a;是一个开源的Web文件管理器&#xff…

idea运行项目报错提示:java: 错误: 不支持发行版本 19,让我来看看

在项目经常切换jdk时&#xff0c;这个error经常能遇到“不支持发行版本19”&#xff0c;这个问题修改起来其实很简单&#xff0c;但在真正操作到能够解决问题的那一步前&#xff0c;通常习惯先去查看配置的jdk版本是否是选择正确的&#xff0c;也就是先确认当前这个项目选择的j…

西湖大学英语听力考试音频无线发射系统-英语听力发射系统浅析

西湖大学英语听力考试音频无线发射系统-英语听力发射系统浅析 由北京海特伟业科技任洪卓发布于2024年5月10日 西湖大学&#xff0c;这所矗立于时代前沿的高等学府&#xff0c;始终秉持着创新精神和追求卓越的坚定信念&#xff0c;不断致力于教学质量的提升与学术研究的深化。其…

实体门店超-常规营销获客:218套落地方案/打造引流/锁客/复购/裂变营销

课程内容&#xff1a; 1 记住&#xff0c;生意不好不一定是你产品出了问题,mp4 2 生意人为什么要从产品思维向流量思维转型&#xff0c;社区超市每月多5万.mp4 3 实体老板不懂鱼塘理论只能等死&#xff0c;美业1招锁定275名年用户卡,mp4 4 餐饮赢销八部&#xff0c;帮你引爆…

MathType2024官方版数学公式编辑器功能全面介绍

在数字化学习和科研的浪潮中&#xff0c;数学公式的编辑与展示成为了不可或缺的一部分。MathType&#xff0c;作为一款专业的数学公式编辑器&#xff0c;凭借其强大的功能和便捷的操作&#xff0c;为科研人员、教师、学生等广大用户提供了极大的便利。下面&#xff0c;我们将对…

【Linux】模拟实现bash(简易版)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

视频号小店应该如何开店呢?详细的开店流程分享给你!

大家好&#xff0c;我是电商小V 视频号小店就是威信视频号团队为咱们商家提供的卖货平台&#xff0c;可以说是支持咱们商家在视频号场景中开店进行经营的模式&#xff0c; 视频号大概的开店流程那就是&#xff1a;找到视频号开店&#xff0c;选择企业入驻&#xff0c;填写信息&…

5.9代码

1.选素数 从数学来看&#xff0c;n素数*k,k2,3,4...&#xff0c;而我们要进行两次&#xff0c;由于有多个解时我们要选最小的&#xff0c;所以要找到最大的素数&#xff0c;但是这个最大素数要小于等于n/2的整数&#xff0c;然后中间那一次的n的选择要选比最大素数大的最少的合…

【二分查找 滑动窗口】100257找出唯一性数组的中位数

本文涉及知识点 二分查找算法合集 C算法&#xff1a;滑动窗口总结 LeetCode 100257找出唯一性数组的中位数 给你一个整数数组 nums 。数组 nums 的 唯一性数组 是一个按元素从小到大排序的数组&#xff0c;包含了 nums 的所有非空子数组中不同元素的个数。 换句话说&#xf…

java图片水印字体乱码问题

问题描述&#xff1a;在linux Centos-7.5_64bit系统的其他服务器上不乱码&#xff0c;在部署项目的正式服务器乱码 水印字体设置是 微软雅黑 Font wordFont new Font("微软雅黑", Font.ITALIC,(srcImgHeightsrcImgWidth)/50); 一.Springboot项目&#xff0c;部署在…

高效视频剪辑:批量剪辑添加srt字幕,快速制作专业视频

在视频制作过程中&#xff0c;字幕扮演着至关重要的角色&#xff0c;它们不仅能增强观众对视频内容的理解&#xff0c;还能提高视频的观感体验。然而&#xff0c;手动为每一个视频添加字幕是一项既耗时又繁琐的任务。现在有了云炫AI智剪和技巧&#xff0c;我们可以轻松地实现批…

Photoshop中选区工具的应用

Photoshop中选区工具的应用 前言Photoshop中选区工具的基本操作创建选区的工具及方法选择、取消、隐藏选区选区的增加、减少选区的应用变换扩大选取与选取相似 Photoshop中采用快速选择工具来创建选区Photoshop中采用色彩范围命令来创建选区Photoshop中采用快速蒙版来创建选区P…

安全加固

目录 1.文件锁定管理 2.设置用户账户有效期 3.查看并清除命令历史记录 4.设置用户超时登出时间 5.用户切换 6.用户提权 7.禁用重启热键CtrlAltDel 8.设置单用户模式密码 9.调整BIOS引导设置 10.禁止root用户从本地登录&#xff1a; 11.禁止root用户通过ss…

大数据------JavaWeb------Tomcat(完整知识点汇总)

Web服务器——Tomcat Web服务器定义 它是一个应用程序&#xff08;软件&#xff09;&#xff0c;对HTTP协议的操作进行封装&#xff0c;使得程序员不必直接对协议进行操作&#xff0c;让Web开发更便捷 Web服务器主要功能 封装HTTP协议操作&#xff0c;简化开发将Web项目部署到…

如何免费获得进仓数据库专家认证(帮你省50块钱)

这篇文章分三个部分 50块钱解决&#xff08;全靠自己钱可能打水漂考试只有三次机会&#xff09;50块钱解决&#xff08;全靠自己考试只有三次机会。&#xff09;30块钱解决&#xff08;考试靠我&#xff0c;报名费帮你0元处理&#xff0c;要求只有在线大学生。能力有限只能考K…