C++ 多态的相关问题

news2024/11/25 1:27:44

目录

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.coloradmin.cn/o/1660469.html

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

相关文章

Vue开发中Element UI/Plus使用指南:常见问题(如Missing required prop: “value“)及中文全局组件配置解决方案

文章目录 一、vue中使用el-table的typeindex有时不显示序号Table 表格显示索引自定义索引报错信息解决方案 二、vue中Missing required prop: “value” 报错报错原因解决方案 三、el-table的索引值index在翻页的时候可以连续显示方法一方法二 四、vue3中Element Plus全局组件配…

安卓开发--环境配置

本次项目选择使用 Andrio Studio 进行开发。虽然这款软件版本更新也很快。不过开发一款APP的技术流程是大差不差的。我几年前的安卓笔记放到现在还是能用。 现在CSDN网上写一个笔记留作以后参考&#xff0c;开始吧&#xff01;&#xff01;&#xff01; 1 安装 Andrio Studio …

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

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

ECO 视频分类模型

ECO分类模型 ECO 分类模型&#xff0c;可以对视频进行分类&#xff0c;视频是静止画面的集合&#xff0c;并短时间内进行播放&#xff0c;在人眼中形成了视频&#xff0c;通过 FPS 单位进行计算&#xff0c;指的是每秒显示多少张图片。如果直接把图片组合一张大图&#xff0c;…

whisper使用

whisper使用 1. 直接调用 语音识别2. 语种识别 whisper.detect_language()和whisper.decode()3. 指定要识别的语种做语音识别**whisper 源码的transcribe函数** 函数解析1. transcript.py2. tokenizer.py3. audio.py4. __ init__.py github: https://gitcode.com/openai/whispe…

JAVA排序相关习题7

1.插入排序 1.1 基本思想 直接插入排序是一种简单的插入排序法&#xff0c;其基本思想是&#xff1a; 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为止&#xff0c;得到一个新的有序序列 。 /*** 时间复杂度&…

(附Git的cherry pick神操作)GitLab远程分支多次合并后发现其中一次有问题该如何解决和回滚?

目录 问题现象&#xff1a; 问题分析&#xff1a; 1、不需回滚 2、需要回滚 解决方法&#xff1a; 步骤1&#xff1a; 步骤2&#xff1a; 步骤3&#xff1a; 步骤4&#xff1a; 步骤5&#xff1a; 拓展&#xff1a;git代码回滚的可视化操作和命令操作 可视化操作步…

利用OpenShift的ImageStream部署临时版本

公司是港企&#xff0c;项目都部署在OpenShift上统一管理&#xff0c;因为运行环境为香港网络(外网)&#xff0c;配置、中间件等大陆无法直接访问联通。因此在大陆开发时&#xff0c;测试是个很大的问题。为了避免往Git上频繁提交未确定可用的版本&#xff0c;选择用利用OpenSh…

ruoyi-vue-pro 使用记录

ruoyi-vue-pro 使用记录 项目地址文档 数据库bmp 项目地址 ruoyi-vue-pro github地址ruoyi-vue-pro gitee地址 文档 文档地址知乎帖子 吾爱帖子 数据库 请根据实体类&#xff0c;直接给与Mysql创建数据表 bpm_process_listener&#xff0c;字段和数据库为英文名&#xff…

Linux操作系统中管理磁盘的另外一种操作方式。即LVM——逻辑卷管理操作

在Linux操作系统中管理磁盘的一种方法名称——LVM&#xff0c;这种管理磁盘的优势。 1.使用LVM去管理磁盘可以在不影响原来数据的前提下去扩容磁盘空间或者是缩减磁盘空间。 在LVM中除了上层逻辑券可以扩容&#xff0c;下层的券组也可以扩容。 2.使用LVM管理的磁盘支持快照功…

如何将图片表格转成excel?分享3种好用的软件!

在信息爆炸的时代&#xff0c;我们每天都会接触到大量的图片表格。这些表格中可能包含着我们需要的各种数据和信息&#xff0c;但是如何将它们快速、准确地转化为Excel格式&#xff0c;以便我们进行编辑、分析呢&#xff1f;今天&#xff0c;就让我们一起来探讨一下如何将图片表…

日本OTC机械手维修需要注意哪些问题呢?

随着工业4.0时代的到来&#xff0c;机器人在制造业中的应用越来越广泛。OTC&#xff08;Over The Counter&#xff09;机器人作为工业机器人的一种&#xff0c;以其高效、精准、稳定的特点受到众多企业的青睐。然而&#xff0c;在实际使用过程中&#xff0c;可能会出现一些OTC机…

你的计算机配置似乎是正确的,但该设备或资源DNS没有响应

方法/步骤 方法一&#xff1a; 快捷键“winr”,输入services.msc&#xff0c;进入服务界面&#xff0c;找到dnsclient&#xff0c;确保是运行状态&#xff0c;如果没有运行&#xff0c;则选中该条目&#xff0c;右键选择运行。 电脑提示“您的计算机配置似乎是正确”&#xf…

生成式AI+跨境电商有哪些新玩法?店匠科技与亚马逊云科技已经在路上

导读 跨境电商一直是生成式AI最热门的应用领域之一。 生成式AI在跨境电商行业的核心应用场景有哪些&#xff1f;AI跨境电商又有哪些新玩法&#xff1f; 根据海关数据&#xff0c;2023年我国跨境电商进出口总额达2.38万亿元&#xff0c;增长15.6%。我国跨境电商主体已超10万家…

ABB机器人IRB360介绍

随着自动化技术的不断发展&#xff0c;分拣和包装行业的应用也越来越广泛。 工业机器人扮演的角色也随之不断增加&#xff0c;其中ABB机器人的一款产品IRB 360 FlexPicker 在抓取和包装技术方面占有重要的地位。与传统的刚性自动化技术相比较&#xff0c;IRB 360具有高灵活性、…

在家轻松挣钱:深入解析问卷调查项目

在这个快速发展的互联网时代&#xff0c;谁不想找到一种既方便又能赚钱的方式呢&#xff1f;今天&#xff0c;我们就要深入了解一种既不需要经验&#xff0c;又可以在家轻松上手&#xff0c;甚至日赚100至300元的项目——问卷调查项目。不论你是学生、家庭主妇&#xff0c;还是…

EPIC本周送《电气马戏团》,下周送神秘游戏

EPIC Games下周将为玩家们送上一款神秘游戏&#xff01;这是一个令人兴奋的消息&#xff0c;让我们拭目以待看看他们会送上什么样的游戏吧。 而本周&#xff0c;EPIC Games送出的免费游戏是《Circus Electrique》。这款游戏融合了多种元素&#xff0c;包括故事驱动的角色扮演、…

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

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

SliderCaptcha滑块验证码功能

SliderCaptcha滑块验证码功能 资源文件及文档&#xff1a;https://gitee.com/LongbowEnterprise/SliderCaptcha <!DOCTYPE html> <html lang"en" xmlns:th"http://www.thymeleaf.org"> <head><meta charset"UTF-8"><…

Mysql中表的创建以及数据类型

DDL 在表结构的操作 表的创建 creat table 表名&#xff08; 字段1 字段类型 [约束] &#xff0c; 字段2 字段类型 [约束] &#xff09;[comment 标注释]; create table tb_user(id int comment ID,一行字段的唯一标识,username varchar(20) comment 用户名,name varchar(…