当前位置: 首页 > 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…...

hbase优化:客户端、服务端、hdfs

hbase优化 一.读优化 1.客户端&#xff1a; scan。cache 设置是否合理&#xff1a;大scan场景下将scan缓存从100增大到500或者1000&#xff0c;用以减少RPC次数使用批量get进行读取请求离线批量读取请求设置禁用缓存&#xff0c;scan.setBlockCache(false)以指定列族或者列进行…...

docker安装memcached

查找容器是否有该镜像存在 docker search memcached拉取镜像 docker pull memcached创建容器 docker create --name memcache1 memcached或者映射一下端口 docker create -p 11211:11211 --name memcache1 memcached启动 docker start memcache1指定容器的 IP docker net…...

Redis 客户端有哪些?

文章目录 JedisLettuceRedisson最佳实践 - 到底用哪个&#xff1f; Redis 最常见的 Java 客户端有两个&#xff0c;Jedis 和 Lettuce&#xff0c;高级客户端有 Redisson&#xff0c;见下图&#xff08;图源 Clients | Redis&#xff09; Jedis Github地址&#xff1a;redis/j…...

smbms 超市订单管理系统设计与实现计划表

smbms 超市订单管理系统 项目描述 smbms-JDBC&#xff1a;不使用 SSM 框架进行开发bookStore&#xff1a;学完ssm框架后的整合项目smbms-SSM&#xff1a;使用 SSM 框架开发 项目记录 smbms-JDBC 2023-10-28&#xff1a;第一天&#xff0c;搭建环境&#xff0c;写好基本的工…...

如何解决制造业数字化改造的障碍?

制造业的数字化转型可能是一个复杂且具有挑战性的过程&#xff0c;但解决以下障碍有助于为成功实施铺平道路&#xff1a; 抵制变革&#xff1a;数字化转型中最常见的挑战之一是员工的抵制&#xff0c;尤其是那些习惯传统方法的员工。为了克服这一问题&#xff0c;组织需要培养一…...

代码随想录算法训练营day49

文章目录 Day49买卖股票的最佳时机题目思路代码贪心算法动态规划法(推荐) 买卖股票的最佳时机II题目思路代码 Day49 买卖股票的最佳时机 121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; 题目 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i]…...

云计算与大数据——部署Kubernetes集群+完成nginx部署(超级详细!)

云计算与大数据——部署Kubernetes集群完成nginx部署(超级详细&#xff01;) 部署 Kubernetes 集群的基本思路如下&#xff1a; 准备环境&#xff1a; 选择适合的操作系统&#xff1a;根据需求选择适合的 Linux 发行版作为操作系统&#xff0c;并确保在所有节点上进行相同的选…...

Maven 打包项目后,接口识别中文乱码

背景 项目在Idea里面运行&#xff0c;调用接口发送中文消息正常&#xff0c;用Maven打包项目后&#xff0c;运行jar包&#xff0c;调用接口发送中文出现乱码。 解决方法 1.Idea编译配置 2.如果更改了上述配置之后还是没有效果&#xff0c;则在运行jar包的前面加上 -Dfile.en…...

计算机视觉项目中的文件批量操作与文件批量预处理

计算机视觉项目中的文件批量操作与文件批量预处理 目录 数据集制作文件批量重命名文件批量移动将文件批量按照一定格式进行重命名修改xml文件内容的方法 引言 在计算机视觉项目中&#xff0c;文件批量操作和文件批量预处理是必不可少的步骤。它们涉及处理大量的图像文件&am…...

PHP数组转对象和对象转数组

PHP数组转对象和对象转数组 <?php function array_to_object($arr){$obj new stdClass();foreach ($arr as $key > $val) {if (is_array($val) || is_object($val)) {$obj->$key array_to_object($val);} else {$obj->$key $val;}}return $obj; } function o…...