【C++】动态联编、delete/free【有无析构】的使用,虚析构

news2024/11/14 18:17:59

文章目录

  • 动态联编的条件:
    • 联编的概念:
      • 1. 动态联编:
      • 2. 静态联编:
      • 静态联编时确认了那些属性:
      • 结论:
  • 基类指针和派生类指针访问虚函数
    • 结论:
  • delete和free的使用条件:
    • 1. 没有析构函数时:
      • 结论:
    • 2.有析构函数
      • 申请一个对象
      • 申请10个对象
      • 结论:
      • malloc和delete可不可以混用?
  • 虚析构函数的使用
  • 重置虚表
    • 构造函数:置虚表
    • 析构函数重置虚表

动态联编的条件:

1.必须是公有继承
2.类中的函数必须为虚函数
3.必须使用指针和引用的方式进行。(->,&)
静态联编。,就是我们使用对象名调用我们的函数。函数名和对象的关系在编译阶段就已经确认。

联编的概念:

联编是指计算机程序自身彼此关联的过程,是把一个标识符和存储地址连载在一起的过程,也就是把一个对象的操作相结合的过程。

1. 动态联编:

如果使用基类指针或引用指明派生类对象,并使用该指针调用虚函数,则程序动态地选择派生类的虚函数,称为动态联编(运行时绑定),也叫滞后联编。
如:Obect为基类 Base为派生类

Object *op = *base  op->fun() (动态选择虚函数)

2. 静态联编:

如果使用对象名加.成员函数或者原则运算符“ ”引用特定的一个对象调用虚函数,则被调用的虚函数在编译时是确定的。则是静态联编。
如:

virtual void fun();
Object obj; obj.fun();

不管对象是够是虚的,均采用静态联编,因为对象和成员函数在编译器就已经绑定。

静态联编时确认了那些属性:

1.确认类型(class)
2.访问限定符
3.函数默认值(很重要)
函数的默认值在编译结点就已经固定了。
注意

  1. 编译的过程和执行的过程一定要分开!!!
  2. 变量的生存期是在运行阶段确定的。
class Object
{
public:
    virtual void print(int x = 20)
    {
        cout << "object ::print::x:" << x << endl;
    }
};
class Base :public Object
{

public:
    //普通成员函数 
    virtual void print(int a)
    {
        cout << "Base ::print::a:" << a << endl;
    }
};
int main(void)
{
	Base base;
	Object* op = &base;
	  //op指向的base对象的地址。
	op->print();
	//所以op调用的是base中print()。
	//但调用的值是在编译期就已经确定的。
	return 0;
}

请添加图片描述
值在编译阶段,就已经确认。
请添加图片描述
调用的是派生类方法。
**请添加图片描述
问题
请添加图片描述

结论:

  1. 虽然派生类的虚函数会覆盖掉同名的基类虚函数。但是函数默认值在编译阶段就已经确认了。
  2. 上部分代码,实际上,是因为已经编译完成,故基类指针可以访问到。但是派生类指针是无法访问的。因为派生类的默认值属于私有。

基类指针和派生类指针访问虚函数

class Object
{
private:
    int value;
public:
    Object(int x = 0) :value(x) {}
    virtual void add() { cout << "Object::add" << endl; }
    virtual void fun() { cout << "Object::fun" << endl; }
};
class Base :public Object
{

private:
    int num;
public:
    Base(int x = 0) :num(x), Object(x + 10) {}
    virtual void add() { cout << "Base::add" << endl; }
    virtual void fun(int x) { cout << "Base::fun" << endl; }

};
int main()
{
    Base base;
    Object* op = &base;
    Base* bp = &base;
    op->fun();
    op->add();
    op->fun(12); error //无法访问,通过隐藏基类指针,查找派生类虚表,访问不到派生类中有,而基类中没有的虚方法。
    op->Base::fun(12);error//访问不到,如下图:
    bp->add();
    bp->fun();   error   //通过查表的方式,直接访问不到,但是可以通过加作用域范围,进行访问。因为派生类继承它没有
    bp->Object::fun();                                                                    //而基类有的虚函数
    bp->fun(10);
   
}

请添加图片描述
op能访问:add() fun()
bp能访问:add() Object::fun() fun(10)
请添加图片描述

结论:

  1. 基类指指针只能访问派生类同名覆盖掉和继承的基类中的虚函数,基类中没有而派生类中有的虚函数访问不到。
  2. 而派生类指正均可。如果基类虚函数,派生类中没有,由于是公有继承关系,派生类也可以通过加作用域的方式进行访问。

delete和free的使用条件:

1. 没有析构函数时:

class Object
{
private:
    int value;
public:
    Object(int x = 0) :value(x) {cout<<"create construct" <<endl;}
    //~Object() {}
};
  1. 创建一个对象
OBject *ip = new Object(10);
  1. 创建10个对象
OBject *ip = new Object[10];
int main()
{
    //创建了10个对象
    Object* op2 = new Object[10];
    delete[]op2;//ok
    delete op2;//ok
    free(op2);//ok
}
int main()
{
    //创建一个对象
   Object* op1 = new Object(10);
   free(op1); //ok
    delete(op1); //ok
    delete[]op1;  //ok
}

结论:

在没有析构函数时,不论申请多少个对象,free,delete detete [] 随便用

2.有析构函数

申请一个对象

OBject *op1 = new Object(10);

在申请堆空间:有上越界和下越界标志。
free(op1);delete(op1);不会读取计数位置。
delete[]op1; 会把上越界当做计数位读取,从而导致错误。
请添加图片描述

申请10个对象

OBject *op1 = new Object[10];

申请10个空间,还会申请计数位置和头部信息。如下图:
如果使用:free(op1);delete(op1);会导致读取头部信息失败。(因为不会把记录的个数也认为是头部信息,最终在读的时候出问题)
只有使用 delete[]op1; 才能够获取头部信息和计数位置。

请添加图片描述

结论:

free无法调动析构函数来析构对象,delete无法将记录的对象个个数也认为是头部信息,所以只能使用delete[]来释放多个对象。

malloc和delete可不可以混用?

如果是内置类型,就可以。
如果是自己设置的类型,要看有没有析构函数。
如果有,如果申请多个对象,就需要调用10次析构函数。

虚析构函数的使用

class Object
{
private:
	int value;
public:
	Object(int x = 0) : value(x) { cout << "Obejct::Create: " << this << endl; }
	~Object() { cout << "Obejct::destroy: " << this << endl; }
};

class Base : public Object
{
private:
	int num;
public:
	Base(int x = 10) : Object(x + 10), num(x)
	{
		cout << "Base::Create: " << this << endl;
	}
	~Base() { cout << "Base::Destroy: " << this << endl; }
};

int main(void)
{
	Object* op = new Base(10);

	delete op;
	return 0;
}

请添加图片描述
当有基类指针指向派生类的对象,如果基类和派生类都有析构函数,那么析构op指向的base对象时,使用的是基类的析构函数,。如果想连级调用base中的析构函数,必须将基类中的析构函数设置为虚函数,那么派生类中的析构函数就会覆盖掉基类中的析构函数,从而调用派生类中的析构函数。
采用虚析构

 virtual ~Object() { cout << "Obejct::destroy: " << this << endl; }

virtual~Base() { cout << "Base::Destroy: " << this << endl; }

请添加图片描述

重置虚表

重置虚表的意义
当析构完Base中的资源,如果不把虚表指针指向Object的虚表,那么析构Object的时候,会再次析构Base中的add(),从而导致重复析构。

  • 在之前的代码析构函数中加入add()
class Object
{
private:
    int value;
public:
    Object(int x = 0) : value(x) { cout << "Obejct::Create: " << this << endl; }
    virtual void add() { cout << "Object::add" << endl; }
    ~Object() 
    {
        add();
        cout << "Obejct::destroy: " << this << endl; }
};

class Base : public Object
{
private:
    int num;
public:
    Base(int x = 10) : Object(x + 10), num(x)
    {
        cout << "Base::Create: " << this << endl;
    }
    virtual void add() { cout << "Object::add" << endl; }
    ~Base()
    {
        add();
        cout << "Base::Destroy: " << this << endl; }
};
int main()
{
    Object* op = new Base(10);

    delete op;
    return 0;
}

请添加图片描述

构造函数:置虚表

:调用object类中的构造函数,创建隐藏基类对象,让该对象内保存的虚表指针,指向虚表。即为置虚表
上面的程序;是先new一个base对象,先构造隐藏基类对象,使其内的虚表指针指向Object的虚表,当在此对base实例化时,基类的虚表指针会指向Base的虚表。
如下如:
请添加图片描述

析构函数重置虚表

在析构过程中,析构函数没第一部是先将虚表指针重新指向该对象的虚表。
因此会出现这个结果:请添加图片描述
很明显,虚表指针指向Base的虚表时,析构base对象
虚表指针指向Object对象时,析构隐藏基类对象。
请添加图片描述

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

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

相关文章

由一个按键程序引发的思考(上)

说起按键程序&#xff0c;只要会单片机的肯定都很熟悉。一般开始学习单片机的时候&#xff0c;入门程序基本都是LED灯和按键。那么这个按键程序有什么特别的吗&#xff0c;还需要专门去思考吗&#xff1f;如果我刚开始学单片机的时候也会这么想&#xff0c;但是随着项目的积累&…

微信早安消息推送(大升级版-企业微信)

微信早安消息推送&#xff08;企业微信&#xff09; 在微信公众号推送的基础上的一个升级 新增图文、疫情状况等 可以修改头像 、 昵称 效果展示 更多资讯 源代码获取 → 薇信公粽号“Cloud技术栈”&#xff0c;回复“企业微信推送” ———————————————————…

【贪心算法-LeetCode3:无重复字符的最长子串(Java实现)】

无重复字符的最长子串一、题目描述1.题目内容2.样例二、解决方案1.算法流程1&#xff09;分析2&#xff09;算法流程2.Java代码1&#xff09;核心代码2&#xff09;完整测试代码个人社区&#xff1a;https://bbs.csdn.net/forums/smile 个人主页&#xff1a;https://blog.csdn.…

STM32G070RBT6基于STM32CubeMX创建定时器中断控制LED闪烁

STM32G070RBT6基于STM32CubeMX创建定时器中断控制LED闪烁&#x1f4cd;相关篇《【硬件开源电路】STM32G070RBT6开发板》&#x1f33a;配置内容演示&#xff1a; &#x1f4da;功能介绍 &#x1f4d1;通过STM32CubeMX配置定时器1和定时器3分别作为两个led的定时闹钟。这里作为…

单片机原理及应用实验一交通信号系统(基于Proteus仿真)

1.实验内容与要求 交通信号系统是保障交通安全高效的重要设施。51单片机价格低廉、体积小、低功耗、抗干扰性好等优点&#xff0c;适用于交通信号系统中&#xff0c;本实验利用51单片机IO口的开关量的输入输出功能及内部CPU运算功能&#xff0c;设计一个简易的交通信号灯系统&…

生成.keystore 安卓签名

需要有openssl.exe和keytool.exe支持 signapk.jar手动签名命令 java -jar signapk.jar pl.x509.pem pl.pk8 smartrecord_3.5.8.apk smartrecord_3.5.8-signed.apk除了直接使用signapk.jar签名外&#xff0c;还可以将签名文件生成keystore文件&#xff0c;然后配置编译器给apk进…

数据分析 | Pandas 200道练习题,每日10道题,学完必成大神(5)

文章目录前期准备1. 将create Time列设置为索引2. 生成一个和df长度相同的随机数DataFrame3. 将上一题生成的DataFrame与df合并4. 生成的新的一列new值为salary列减去之前生成的随机数列5. 检查数据中是否含有空值6. 将salary类型转换成浮点数7. 计算salary 大于10000的次数8. …

多线程之线程池

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、什么是线程池二.为什么要有线程池引入&#xff1a;为什么从池子里取&#xff0c;比创建线程速度要快什么是用户态&#xff0c;什么是内核态最终结论&#xf…

aws 负载均衡clb/nlb/alb

目录 概述 传统负载均衡器&#xff08;Classic Load Balancer&#xff09; DNS解析 健康检查&#xff08;Health Check) 监听器&#xff08;Listeners&#xff09; 连接耗尽&#xff08;Connection Draining&#xff09; 粘性会话/会话关联&#xff08;Sticky Sessions/…

(四)手写简单版MyBatis框架

文章目录环境搭建第一步&#xff1a;资源⼯具类第二步&#xff1a;定义SqlSessionFactoryBuilder类第三步&#xff1a;定义SqlSessionFactory类第四步&#xff1a;定义JDBC事务管理器第五步&#xff1a;定义数据源类第六步&#xff1a;定义MyMappedStatement类第七步&#xff1…

基于遗传算法卡车无人机旅行推销员问题(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Leetcode刷题111. 二叉树的最小深度

给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a;叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;2 示例 2&#xff1a; 输入…

Python_机器学习_算法_第7章_7.拓展知识

Python_机器学习_算法_第7章_7.拓展知识 文章目录Python_机器学习_算法_第7章_7.拓展知识7.拓展知识7.1. 其他距离公式1 标准化欧氏距离2 余弦距离3 汉明距离【了解】4 杰卡德距离【了解】5 马氏距离【了解】7.2.再议数据分割1 留出法2 交叉验证法2.1 交叉验证法基本介绍2.2 KF…

ARM发布Cortex-X3和Cortex-A715

快速链接: . &#x1f449;&#x1f449;&#x1f449; 个人博客笔记导读目录(全部) &#x1f448;&#x1f448;&#x1f448; 付费专栏-付费课程 【购买须知】: 【精选】ARMv8/ARMv9架构入门到精通-[目录] &#x1f448;&#x1f448;&#x1f448; ARM公司发布了第二代 AR…

x86 smbus 下挂eeprom不能写问题

目录 背景 分析 驱动影响 SPD register 接口 只读 修改验证 总结 背景 x86 smbus上下挂一个eeprom&#xff0c;只能读取&#xff0c;不能写入。 写入命令采用&#xff1a; i2cset -y -f 0 0x50 0 0x33 即向总线0 下的0x50 地址的eeprom偏移量0 写入数据0x33&#xff0c; 命令…

哈夫曼树原理及Java编码实现

文章目录前言一、哈夫曼树原理二、哈夫曼编码&#xff08;Java题解&#xff09;参考资料前言 所有博客文件目录索引&#xff1a;博客目录索引(持续更新) 源代码&#xff1a;Gitee—Huffman.java、Github—Huffman.java 一、哈夫曼树原理 对于哈夫曼树的构造以及权值计算原理…

Java语言高级-10MySQL-第2节MySQL安装与使用

2、MySQL数据库软件 1、安装 详细见视频 2、卸载 1、去mysql的安装目录找到my.ini文件 “复制datadir “C:/programData/MySQL/MySQL Server 5.5/Data” 2、卸载MySQL 控制面板->程序卸载&#xff0c;但是此时并没有卸载干净 3、删除C:/ProgramData目录下得MySQL文件夹 注意…

java 内部类

小镇做题家 前段时间很火的一个话题“小镇做题家”&#xff0c;我没有具体了解&#xff0c;我以为是在鼓励一些努力拼搏的人。 某一天&#xff0c;禁不住好奇&#xff0c;我打开了百度百科&#xff0c;看了看小镇做题家的解释 就是指一些村里或者小镇的人&#xff0c;通过学习…

模板和泛型编程(上)

目录函数模板泛型编程函数模板概念及原理函数模板的实例化函数模板的匹配原则类模板类模板实例化函数模板 泛型编程 泛型编程&#xff08;Generic Programming&#xff09;最初提出时的动机很简单直接&#xff1a;发明一种语言机制&#xff0c;能够帮助实现一个通用的标准容器…

猿创征文|Python迭代器、生成器、装饰器、函数闭包

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 Python迭代器、生成器、装饰器、函数闭包1. 迭代器 iterator☞迭代器协议☞Python中的for循环2. 生成器…