当前位置: 首页 > news >正文

C++多态练习题

目录

一.习题1: 解决下列测试代码所出现的问题

测试用例1:

测试用例2: 

代码改进: 

习题1总结:

 二.习题2.

         求类对象的大小

三.习题3:

代码解析 :

 解析图:

四.习题4: 

 代码解析:


一.习题1: 解决下列测试代码所出现的问题

class Person {
public:~Person() {cout << "~Person()析构函数: " <<_p<< endl;delete[] _p;}
public:int* _p=new int[10];
};class Student :public Person {
public:~Student() {cout << "~Student()析构函数: "<<_s<< endl;delete[] _s;}
public:int* _s=new int[20];
};

        在上方代码中有一对父子类,父子类各有一个指向堆区空间的整型指针成员变量,那么意味着创建类对象会开辟空间。在这两个类中,都各有一个显式的析构函数。

 

测试用例1:

int main() {Person p1;Student s1;return 0;
}

        在测试用例中,创建了两个父子类对象。

运行结果:

        由上图结果可知:结果显示很正常,s1是子类对象,继承了父类的成员变量,那么析构的时候需要调用父类的析构函数,没什么问题。

测试用例2: 

int main(){   Person* ptr1 = new Person;Person* ptr2 = new Student;delete ptr1;delete ptr2;return 0;}

        在这个测试用例中,父类创建了两个指针对象,分别指向Person类型的堆区空间和Student类型的堆区空间。由于new了堆区空间,肯定需要释放了这两个指针指向的堆区空间。

运行结果:

        通过结果可知,ptr1指针对象的释放没啥问题,因为ptr1开辟的是Person类的空间,那么肯定会调用Person类的析构函数;而ptr2指针对象的释放出现了问题:它只析构了它自己,没有释放Student类的堆区空间,因为释放Student类的堆区空间会调用其析构函数,但结果显示没有调用!于是出现了内存泄漏。

        主要原因:当父类指针指向子类的时候(Person* ptr2=new Student),若父类析构函数不声明为虚函数,在编译器delete时,只会调用父类而不会调用子类的析构函数,从而导致内存泄露。!!!!!


         通过之前学习多态特性我们了解到:普通调用是和调用对象的类型有关,那么编译器认为我们创建的Person类两个指针对象在delete时,采用的是普通调用,所以调用的依据是与调用对象ptr2的类型(Person)有关,所以编译器就只调用了Person类的析构函数;
       而我们真实想要采用的是多态调用,所谓多态调用的依据是指针(ptr2)或者引用与指向的对象(new Student)有关。


        所以需要在父类和子类的析构函数上加上virtual,让析构函数变成虚析构,这样编译器就认定我们采用的是多态调用了!!!

        小知识扩展:任何类的析构函数虽然都是已~符号+各自类命名,但底层上编译器统一看作是destructor。

        该句解析:虽然父类与子类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

        多态的条件有三个:

        1是在父类的函数上加virtual,形成虚函数;

        2是子类需要重写父类的虚函数,重写的要求是三同(同名、同参、同返回值类型)

        3.使用父类指针或者引用。

        所以现在父子类的析构函数都可以看作是三同函数,也都有virtual,有父类指针对象去开辟父类和子类的堆区空间,实现了多态调用。

代码改进: 

class Person {
public:virtual ~Person() {    cout << "~Person()析构函数: " <<_p<< endl;delete[] _p;}
public:int* _p=new int[10];
};class Student :public Person {
public:virtual ~Student() {    //构成重写cout << "~Student()析构函数: "<<_s<< endl;delete[] _s;}
public:int* _s=new int[20];
};

        只有子类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。 

 运行结果:

 

习题1总结:


        首先我们需要确定,新创建的类(Person)是否会有后代。如果创建的新类肯定会有后代(Student类),那就声明析构函数为虚函数,以防万一有后代,可能会发生的内存泄露。有后代继承,是造成内存泄漏的首要条件。

        创建的新类具有后代是造成内存泄漏的先决条件,但这并不是触发条件。如果我们使用的指针不是父类指针引用子类指针,那么永远不会触发因为析构而产生的内存泄漏。所以父类指针指向、引用子类对象是触发析构函数内存泄漏的条件。

        当我们创建的新类会被继承,我们会用到父类指针指向子类指针,我们必须使用虚析构函数,所以以后每当遇到有后代的父类,就无脑加上virtual即可。

 


 二.习题2.

         求类对象的大小

class Base{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};class Base2{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:double _f = 1;char _c = 'a';
};class Base3{
public:virtual void Func1(){cout << "Func1()" << endl;}virtual void Func2(){cout << "Func2()" << endl;}void Func3(){cout << "Func3()" << endl;}
private:int _i = 1;char _c = 'b';
};int main() {Base b;cout << sizeof(Base) << endl;		//第一小问Base2 b2;cout << sizeof(Base2) << endl;		//第二小问Base3 b3;cout << sizeof(Base3) << endl;		//第三小问return 0;
}

三个小问的结果为:

8字节、24字节、12字节 

        结果解析:求Base类的大小,是需要看该类的成员变量的字节大小,注:只看成员变量,成员函数不会算进类的大小中的。

        在Base类中,成员变量有1个,为int类型,是4字节大小,根据内存对齐,所以总大小为4字节,但是类中还有虚函数,虚函数中因为有虚表指针(虚表指针是指向虚函数表的地址的)!虚函数表中存放着各个虚函数的地址)而指针是默认占4个字节的,加上共占8个字节,
// 并且还需要进行内存对齐,取最大的变量的字节倍数作为最终的类的字节大小,最大变量字节为4字节,所以8符合4的倍数,所以结果为8字节。


注:采用f11进行调试,你就可以看到Base的对象b中多出了一个虚表指针vfptr,它是指向虚函数表的地址的指针!!! 

        第二小题:Base2类的成员变量有俩,一个double-8字节,一个char-1字节,共9字节,根据内存对齐,最终大小得是8的倍数,所以补齐到16字节,因为还有虚表指针,占4字节,加上就是20字节,最终大小得是8的倍数,所以补齐到24字节,所以最终大小是24字节。

        第三小题:Base3类中有两个成员变量,一个int-4字节,一个char-1字节,内存对齐,补齐到8字节,但是此时Base3类中有两个虚函数,那么是不是意味着就会有两个虚表指针呢???



        答案:不是!!! 无论一个类中有多少个虚函数,而虚表指针只会有一个,尽管Base3中有俩虚函数,但在虚表指针指向的虚函数表(可以理解为一个指针数组,由指针指向的一个数组)中,有俩个地址这俩地址就代表着这两个虚函数的地址!虚函数的增多仅仅代表着虚函数表指针指向的虚函数表中多了两个虚函数的地址,只是表增大了,而不是虚表指针变多了!
        所以虚表指针-4字节,再进行内存对齐,8+4=12,12是最大对齐数4的倍数,所以结果为12字节 

强调:虚表指针在32位机器下是4字节大小,而在64位机器下是8字节大小,我测试的机器是32位的!所以是4字节


三.习题3:

class Base1 { public:  int _b1; };
class Base2 { public:  int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };int main() {Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;}

请计算最终的结果,选择下面正确的选项:
        A:p1 == p2 == p3    B:p1 < p2 < p3

        C:p1 == p3 != p2     D:p1 != p2 != p3 

代码解析 :

子类Derive继承了两个父类Base1,Base2,当Derive创建对象时,d的成员变量有三部分:
        第一部分是Base1继承过来的成员变量_b1;

        第二部分是Base2继承过来的成员变量_b2;
        第三部分就是自家类创建的成员变量内置类型 _d。


         在代码中,类Base1创建了一个指针,指向了子类对象d的地址,根据切割原理,子类对象d是向上赋值转换父类对象的,子类对象d会将从Base1那里继承来的成员变量切割出来值赋给父类Base1的对象,那么p1指向的就是子类对象从Base1那里继承的成员_b1的地址
       

        同理,Base2* p2指向的就是子类对象d从Base2那里继承的成员_b2的地址,而子类Derive又创建了一个指针,指向了子类对象d,那么p3指向的就是整个子类对象d的地址了。根据之前学的数组原理,当指针p1指向一整个数组时,其实指针指向的是数组的首地址,而p3指向数组的第一个元素时,指针指向的也是数组的首地址,p1与p3虽然指向的具体内容不同,但它们都指向了同一块地址。

        基于此,我们回到题中,p1和p3虽然指向的内容不一样,但都指向了对象d的首部,所以p1==p3,p2指向的位置!=p1和p3

 解析图:


四.习题4: 

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(int argc, char* argv[]){B* p3 = new B;p3->test();cout << endl;return 0;
}

该题运行的结果是什么?
A:A->1    B. B->0        C. A->0      D. B->1  E.编译出错 

 代码解析:

       B类型创建了一个指针对象,指向一块B类型的地址空间    ,之后指针对象调用test()函数,这个test()函数是从A类继承过来的成员函数,在调用test()函数后,test中需要调用func()函数,而在调用func()函数的时候,这是一个陷阱,我们都会认为对象在调用的时候,用的是this指针,这个this指针是A类型的,虽然test()被B继承过来了,但是原封不动的继承过来,在继承前就是A* this指针才能调用,继承后仍然由A* this指针调用,千万不要以为是B* 本类的this指针去调用的test()函数!!!

  至此,多态的特性就体现出来了!

        之后,this指针就会采用多态调用!
        再温习一下:我们在学习多态前,采用的调用都是普通调用,该调用的原理是根据调用对象的类型有关。而学习了多态特性后,看题时就需要再多考虑一种新的调用——多态调用了。
        多态调用特性:某类的指针或者引用采用的调用方向 ->是与指向的对象有关的。

        如上代码: B* p3=new B; 指针(p3) 的调用方向是与指向的对象(=new B)有关的,所以隐式的this指针调用的func函数是B类的func函数,所以是 "B->"(多态调用),但val的参数采用的仍是A类的val缺省值(原因:this指针类型是A*),所以答案为:"B->1" ——有些坑人这道题。

相关文章:

C++多态练习题

目录 一.习题1&#xff1a; 解决下列测试代码所出现的问题 测试用例1&#xff1a; 测试用例2&#xff1a; 代码改进&#xff1a; 习题1总结&#xff1a; 二.习题2. 求类对象的大小 三.习题3&#xff1a; 代码解析 &#xff1a; 解析图&#xff1a; 四.习题4&#xff…...

ELD透明屏在智能家居中有哪些优点展示?

ELD透明屏是一种新型的显示技术&#xff0c;它能够在不需要背光的情况下显示图像和文字。 ELD透明屏的原理是利用电致发光效应&#xff0c;通过在透明基板上涂覆一层特殊的发光材料&#xff0c;当电流通过时&#xff0c;发光材料会发出光线&#xff0c;从而实现显示效果。 ELD…...

第十三章 利用PCA简化数据

文章目录 第十三章 利用PCA简化数据13.1降维技术13.2PCA13.2.1移动坐标轴 13.2.2在NumPy中实现PCA13.3利用PCA对半导体制造数据降维 第十三章 利用PCA简化数据 PCA&#xff08;Principal Component Analysis&#xff0c;主成分分析&#xff09;是一种常用的降维技术&#xff0…...

开源中文分词Ansj的简单使用

ANSJ是由孙健&#xff08;ansjsun&#xff09;开源的一个中文分词器&#xff0c;为ICTLAS的Java版本&#xff0c;也采用了Bigram HMM分词模型&#xff1a;在Bigram分词的基础上&#xff0c;识别未登录词&#xff0c;以提高分词准确度。 虽然基本分词原理与ICTLAS的一样&#…...

251_多线程_创建一个多线程的图像处理应用,其中每个线程负责对一部分图像进行处理,然后将处理后的结果合并为最终图像

举一个更丰富的例子来说明多线程的用法。 我们将创建一个多线程的图像处理应用,其中每个线程负责对一部分图像进行处理,然后将处理后的结果合并为最终图像。 这个例子可以更好地展示多线程并发处理的优势。 假设有一个函数 processImageSection,它会对图像的一个特定区域进…...

[吐槽Edge浏览器]关于Edge浏览器的闪退问题

这个浏览器嘛&#xff0c;在谷歌浏览器不能页面翻译后&#xff0c;一直是用的高高兴兴的&#xff0c;可突然有一天&#xff0c;Edge浏览器页面加载不出来了。 很慌&#xff0c;大概就是页面崩溃、加载失败什么的都出现过。 修了整整一天&#xff0c;不知道原因在哪&#xff0c;…...

数据包在网络中传输的过程

ref: 【先把这个视频看完了】&#xff1a;数据包的传输过程【网络常识10】_哔哩哔哩_bilibili 常识都看看 》Ref&#xff1a; 1. 这个写的嘎嘎好&#xff0c;解释了为啥4层7层5层&#xff0c;还有数据包封装的问题:数据包在网络中的传输过程详解_数据包传输_张孟浩_jay的博客…...

Acwing.875 快速幂

题目 给定n组ai , bi, pi&#xff0c;对于每组数据&#xff0c;求出akimod pi的值。 输入格式 第一行包含整数n。 接下来n行&#xff0c;每行包含三个整数ai , bi,pi。输出格式 对于每组数据&#xff0c;输出一个结果&#xff0c;表示aibimod pi的值。 每个结果占一行。 数…...

【决策树-鸢尾花分类】

决策树算法简介 决策树是一种基于树状结构的分类与回归算法。它通过对数据集进行递归分割&#xff0c;将样本划分为多个类别或者回归值。决策树算法的核心思想是通过构建树来对数据进行划分&#xff0c;从而实现对未知样本的预测。 决策树的构建过程 决策树的构建过程包括以…...

类与对象(中--构造函数)

类与对象&#xff08;中--构造函数&#xff09; 1、构造函数的特性2、默认构造函数3、编译器自动生成的默认构造函数&#xff08;无参的&#xff09;&#xff08;当我们不写构造函数时&#xff09;3.1 编译器自动生成的默认构造函数只对 自定义类型的成员变量 起作用&#xff0…...

Makefile学习1

文章目录 Makefile学习1Makefile简介Makefile重要性Makefile内容1) 显式规则2) 隐晦规则3) 变量的定义4) 文件指示5) 注释 Makefile规则规则默认目标多目标多规则目标伪目标 Makefile目标依赖头文件依赖自动生成头文件依赖关系 Makefile命令Makefile变量变量定义和使用赋值立即…...

城市内涝监测预警系统,科学“智治”应对灾害

近日&#xff0c;台风“杜苏芮”以摧枯拉朽之势给我国东南沿海地区带来狂风骤雨&#xff0c;福建的三个国家气象观测站日降水量突破历史极值。之后&#xff0c;“杜苏芮”一路北上。中央气象台预报称&#xff0c;7月29日至8月1日&#xff0c;北京、天津、河北、山东西部、河南北…...

切片[::-1]解析列表list表示的“非负整数加1”

列表数位表示非负整数&#xff0c;熟练操作“满十进位”。 (本笔记适合熟练操作Python列表list的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅…...

Mac下certificate verify failed: unable to get local issuer certificate

出现这个问题&#xff0c;可以安装证书 在finder中查找 Install Certificates.command找到后双击&#xff0c;或者使用其他终端打开 安装完即可...

Django项目启动错误

uwsgi项目启动错误信息如下Did you install mysqlclient?Command pkg-config --exists mysqlclient returned non-zero exit status 1Command pkg-config --exists mariadb returned non-zero exit status 1.Traceback (most recent call last):File "/home/dream21th/co…...

Vue2 第十二节 Vue组件化编程 (二)

1. VueComponent 2. 单文件组件 一. VueComponent 组件本质上是一个名为VueComponent的构造函数&#xff0c;不是程序员定义的&#xff0c;是Vue.extend生成的只需要写<school/>或者<school><school/>&#xff0c;Vue解析时&#xff0c;会帮我们创建schoo…...

pycharm 远程连接服务器并且debug, 支持torch.distributed.launch debug

未经允许&#xff0c;本文不得转载&#xff0c;vx&#xff1a;837007389 文章目录 step1&#xff1a;下载专业版本的pycharmstep2 配置自动同步文件夹&#xff0c;即远程的工程文件和本地同步2.1 Tools -> Deployment -> configuration2.2 设置同步文件夹2.3 同步服务器…...

SAP ABAP 基础语法超详细

1&#xff0e;表声明 Tables: 表名[,表名]. 声明多个表时可用逗号分隔当你声明了一个数据表的同时&#xff0c;系统也同时自动生成了一个和数据表同名的结构&#xff0c;结构的变量集等于数据表里面的字段。 2&#xff0e;定义变量 Data: v1[(l)] [type t] [decimals d] [v…...

html学习3(表格table、列表list)

1、html表格由<table>标签来定义。 <thead>用来定义表格的标题部分&#xff0c;其内部用 <th > 元素定义列的标题&#xff0c;可以使其在表格中以粗体显示&#xff0c;与普通单元格区分开来。<tbody>用来定义表格的主体部分&#xff0c;其内部用<t…...

【SpringBoot】85、SpringBoot中Boolean类型数据转0/1返回序列化配置

在 SpringBoot 中,前端传参数 0,1,后端可自动解析为 boolean 类型,但后端返回前端 boolean 类型时,却无法自动转换为 0,1,所以我们需要自定义序列化配置,将 boolean 类型转化为 0,1 1、类型对应 boolean 类型有false,true对应的 int 类型0,12、序列化配置 import com.f…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

linux 下常用变更-8

1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行&#xff0c;YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID&#xff1a; YW3…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

云安全与网络安全:核心区别与协同作用解析

在数字化转型的浪潮中&#xff0c;云安全与网络安全作为信息安全的两大支柱&#xff0c;常被混淆但本质不同。本文将从概念、责任分工、技术手段、威胁类型等维度深入解析两者的差异&#xff0c;并探讨它们的协同作用。 一、核心区别 定义与范围 网络安全&#xff1a;聚焦于保…...

rm视觉学习1-自瞄部分

首先先感谢中南大学的开源&#xff0c;提供了很全面的思路&#xff0c;减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接&#xff1a;https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架&#xff1a; 代码框架结构&#xff1a;readme有…...

使用VMware克隆功能快速搭建集群

自己搭建的虚拟机&#xff0c;后续不管是学习java还是大数据&#xff0c;都需要集群&#xff0c;java需要分布式的微服务&#xff0c;大数据Hadoop的计算集群&#xff0c;如果从头开始搭建虚拟机会比较费时费力&#xff0c;这里分享一下如何使用克隆功能快速搭建一个集群 先把…...