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://xiahunao.cn/news/3031455.html

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

相关文章

SQL的命令

目录 创建用户 ​编辑 DDL数据库操作 查询 创建 使用 删除 创建数据库表 在表中修改字段 查询表 DML 添加数据 修改 删除 DQL 查询 创建用户 DDL数据库操作 查询 show databases; 创建 权限问题导致无法创建&#xff0c;连接root修改用户权限 CREATE DATABAS…

企业微信创建应用(一)

登录到企业微信后台管理(https://work.weixin.qq.com/)进入自建应用(应用管理-应用-创建应用) 3.查看参数AgentId和 Secret 4.企业微信查看效果

报表控件Stimulsoft指南:在 JavaScript 报告工具中使用节点计划

我们最近发布了一篇关于使用Quartz.NET 库自动执行报告任务的文章。继续这个主题&#xff0c;今天我们将深入探讨我们的报告如何与 Node Schedule 作业调度程序集成。 Stimulsoft Ultimate &#xff08;原Stimulsoft Reports.Ultimate&#xff09;是用于创建报表和仪表板的通用…

《C语言文件处理:从新手到高手的跃迁》

&#x1f4c3;博客主页&#xff1a; 小镇敲码人 &#x1f49a;代码仓库&#xff0c;欢迎访问 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f30f; 任尔江湖满血骨&#xff0c;我自踏雪寻梅香。 万千浮云遮碧…

matlab-贪婪算法寻找最小覆盖

文章目录 一、最小结点集是什么二、贪婪算法实现查找最小结点集代码结果 一、最小结点集是什么 最小覆盖集&#xff08;也称为最小点覆盖集&#xff09;是图论中的一个重要概念&#xff0c;指的是一个节点子集&#xff0c;使得图中的每一条边都与这个子集中的至少一个节点关联…

一款助力工程项目管理智能化的神器——企智汇工程项目管理系统!

大家好&#xff0c;今天我要向大家介绍一款能够助力工程项目管理智能化的神器——企智汇工程项目管理系统。 在工程项目管理中&#xff0c;信息不对称、数据不共享、沟通不畅等问题一直困扰着管理者和工程师们。而企智汇正是为了解决这些问题而生的。 一、项目全过程可视化&a…

qt: undefined reference to `vtable for aaa‘

版本qt4.8.6&#xff0c;编译报错“main.cpp:(.text0x3b): undefined reference to vtable for aaa” 就一个main.cpp #include <QApplication> #include <QTimer> #include <QCursor> #include <QMouseEvent> #include <QDesktopWidget> #inc…

力扣每日一题-统计已测试设备-2024.5.10

力扣题目&#xff1a;统计已测试设备 题目链接: 2960.统计已测试设备 题目描述 代码思路 根据题目内容&#xff0c;第一感是根据题目模拟整个过程&#xff0c;在每一步中修改所有设备的电量百分比。但稍加思索&#xff0c;发现可以利用已测试设备的数量作为需要减少的设备电…

解决 git 因输入密码错误而导致的报错无法推送问题

报错内容如下&#xff1a; > git push origin master:master fatal: unable to access https://gitee.com/spring-in-huangxian-county/web-tts-vue.git/: OpenSSL SSL_connect: Connection was reset in connection to gitee.com:443 出错原因 根本原因是本机存储的 账户…

三下乡社会实践投稿攻略在这里

在当今信息爆炸的时代&#xff0c;如何让自己的声音被更多人听到&#xff0c;成为许多人和企业所关心的问题。其中&#xff0c;向各大媒体网站投稿&#xff0c;成为了一种常见的宣传方式。但是&#xff0c;如何投稿各大媒体网站&#xff1f;新闻媒体发文策略又有哪些呢&#xf…

探秘未来科技:数字化无人巡检的奇妙之旅

嘿&#xff0c;朋友们&#xff01;下午茶时间到&#xff01;趁着这会儿咱们来聊一个超级炫酷的话题——数字化无人巡检。想象一下&#xff0c;那些曾经需要人工跋山涉水、风吹日晒的巡检工作&#xff0c;现在正被一群“智能小分队”悄悄接手&#xff0c;是不是觉得既神奇又方便…

【C++】C++中的template模板

一、泛型编程 关于模板的出现其实是在广大程序员编程中偷懒省下来的。我举个例子你们就知道了。 下述例子是用来实现swap函数的&#xff0c;利用的方式是最基础的重载。 void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(d…

仿真算法验证成功后,如何快速实现真机无缝切换?

Prometheus仿真优势 首先&#xff0c;我们先通过下面这个视频了解一下Prometheus仿真有哪些优势&#xff1a; 开源自主无人机平台重大更新&#xff01;Promethus仿真到真机无缝切换 Prometheus仿真最大的优势之一是采用了模块化设计&#xff0c;对每个操作节点进行了封装&…

Ubuntu20.04 设置路由器

1. 网络拓扑图 2. 查看网卡信息 ip a得出如下网卡信息&#xff0c;enp1s0和enp2s0为两个网卡名称&#xff0c;以及相关两个网卡的详细信息&#xff0c;不同设备的网卡名称可能不一样 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group defaul…

idea-自我快捷键-2

1. 书签 创建书签&#xff1a; 创建书签&#xff1a;F11创建特色标记书签&#xff1a;Ctrl F11快速添加助记符书签&#xff1a;ctrl shift 数字键 查看书签&#xff1a; shift F11快速定位到助记符书签&#xff1a;Ctrl 数字键 删除书签&#xff1a; delete 2. 自动…

设计模式 六大原则之单一职责原则

文章目录 概述代码例子小结 概述 先看下定义吧&#xff0c;如下&#xff1a; 单一职责原则的定义描述非常简单&#xff0c;也不难理解。一个类只负责完成一个职责或者功能。也就是说在类的设计中&#xff0c; 我们不要设计大而全的类,而是要设计粒度小、功能单一的类。 代码例…

Android之给Button上添加按压效果

一、配置stateListAnimator参数实现按压效果 1、按钮控件 <Buttonandroid:id"id/mBtnLogin"android:layout_width"match_parent"android:layout_height"48dp"android:background"drawable/shape_jfrb_login_button"android:state…

无人播剧直播收益在哪里!快手无人播剧新秘籍:版权无忧,日入四位数攻略

无人播剧顾名思义就是通过短视频平台直播不需要真人出镜受众群体通过网络短视频平台看到的经典影视剧集可以实现24小时不停断的播放利用多种途径变现的一种直播形式 1、操作简单、不露脸、不出镜2、手机、电脑都可以操作3、可以矩阵操作4、0粉丝、0作品、0保证金就可以开播5、…

数据库管理-第187期 23ai:怎么用SQL创建图(20240510)

数据库管理187期 2024-05-10 数据库管理-第187期 23ai:怎么用SQL创建图&#xff08;20240510&#xff09;1 安装PGX1.1 数据库配置对应用户1.2 使用RPM包安装Graph Server1.3 安装Oracle Graph Client1.4 访问PGX页面 2 SQL Property Graph2.1 创建SQL属性图2.2 关于点和边图元…

双目相机标定流程(MATLAB)

一&#xff1a;经典标定方法 1.1OPENCV 1.2ROS ROS进行双目视觉标定可以得到左右两个相机的相机矩阵和畸变系数&#xff0c;如果是单目标定&#xff0c;用ROS会非常方便。 3.MATLAB标定&#xff08;双目标定&#xff09; MATLAB用来双目标定会非常方便&#xff0c;主要是为…