new和delete表达式的工作步骤

news2024/11/15 10:57:55

new表达式工作步骤

  1. 调用一个operator new库函数开辟未类型化的空间 void *operator new(size_t);
  2. 在为类型化的空间上调用构造函数,初始化对象的成员

  3. 返回相应类型的指针

delete表达式工作步骤

  1. 调用相应类型的析构函数,但析构函数并不能删除对象所在的空间,而是释放对象申请的资源
  2. 调用operator delete库函数回收对象所在的空间 *void operator delete (void )

结论:调用析构函数,并不是回收对象所在的空间。

情况1:

class Student{

public:
    Student(const char * name,int id)
    : _name(new char[strlen(name)+1]())
      ,_id(id)
    {
        cout<<"Student(const char *name,int id)"<<endl;
        strcpy(_name,name);
    }

    ~Student()
    {
        if(_name)
        {
            delete[] _name;
            _name = nullptr;
        }
        cout<<"~Student()"<<endl;
    }
    //开辟一个未类型化的空间
    //参数n代表的就是Student类型占据的空间大小
    //不需要关心n是如何传过去的,系统已经写好了
    //该函数专属于student类
    void * operator new (size_t n)
    {
        cout<<"void *operator new (size_t)"<<endl;
        return malloc(n);
    }
    void operator delete(void *p)
    {
        cout<<"void operator delete (void *)"<<endl;
        free(p);
    }
    void print()
    {
        cout<<"name:"<<_name<<endl;
        cout<<"id:"<<_id<<endl;
    }
private:
    char *_name;
    int _id;
    

};

void test(){
    Student *pstu = new Student("Xiaoming",100);
    pstu->print();
    delete  pstu;


}
int main()
{
    test();
    return 0;
}

效果:
在这里插入图片描述
首先输出了重新定义的 new 运算符,在使用 new 进行对象创建时使用,随后执行了构造函数用于初始化对象,输出了 nameid 的值。在对象使用完毕后,执行了析构函数用于释放对象的内存空间,最后执行了重载的 delete 运算符用于释放申请的内存空间。

在这里插入图片描述
Q:为什么这里的operator new函数只调用了一次而不是两次,代码中不是有两个new么?

答:在这段代码中,虽然使用了两个 new 运算符,但实际上只调用了一次 operator new 表达式函数。这是因为在 C++ 中,每个类只需要定义一个 operator new 函数来实现内存分配即可,该函数会被用于为该类的所有对象分配内存空间。类似地,同样只需要定义一个 operator delete 函数来释放该类的所有对象申请的内存空间。在本例中,由于 Student 类只定义了其中一个 operator new 函数和一个 operator delete 函数,因此实际上只调用了一次 operator new 函数,用于为 pstu 分配内存空间。

Q:因为void * operator new (size_t n)和void * operator new (size_t n)函数是放在Student类中的,这次我们将这两个函数放在全局中,看看会出现什么结果

放在全局就不再专属于Student这个类了,因此对所有的类型都会起作用

#include <iostream>
#include<string.h>
using std::cout;
using std::endl;
    void * operator new (size_t n)
    {
        cout<<"void *operator new (size_t)"<<endl;
        return malloc(n);
    }
    void operator delete(void *p)
    {
        cout<<"void operator delete (void *)"<<endl;
        free(p);
    }

class Student{

public:
    Student(const char * name,int id)
    : _name(new char[strlen(name)+1]())
      ,_id(id)
    {
        cout<<"Student(const char *name,int id)"<<endl;
        strcpy(_name,name);
    }

    ~Student()
    {
        if(_name)
        {
            delete[] _name;
            _name = nullptr;
        }
        cout<<"~Student()"<<endl;
    }
    //开辟一个未类型化的空间
    //参数n代表的就是Student类型占据的空间大小
    //不需要关心n是如何传过去的,系统已经写好了
    //该函数专属于student类
    void print()
    {
        /* /1* Student stu1  = new Student("XiaoLan",300); *1/ */
        /* Student *stu1  = new Student("XiaoLan",300); */
        /* stu1->print(); */
        cout<<"name:"<<_name<<endl;
        cout<<"id:"<<_id<<endl;
    }
private:
    char *_name;
    int _id;
    

};

void test(){
    Student *pstu = new Student("Xiaoming",100);
    pstu->print();
    cout<<"----------------------------"<<endl;;
    /* Student *pstu1 = new Student("XiaoHong",200); */
    /* pstu1->print(); */
    delete  pstu;
    /* delete pstu1; */


}
int main()
{
    test();
    return 0;
}


效果:
在这里插入图片描述
通过这个例子再次验证了析构函数并不是要去回收对象本身所占据的空间

应用:

要求:一个类只能生成栈对象

意思就是该类的对象只能在栈上创建,而不能使用 new 运算符在堆上创建或者说一个类只能创建在栈上的对象,不能生成堆对象

当说一个类只能生成栈对象时,意思是该类的对象只能在栈上创建,而不能使用 new 运算符在堆上创建。

这种限制可以通过将构造函数声明为 privateprotected 来实现。这样,类的对象只能在类的成员函数或友元函数中创建,而不能直接通过 new 运算符在堆上创建。

有时候需要限制对象只能在栈上创建的原因包括:

  1. 简化内存管理:栈对象的生命周期是与其所在的作用域一致的,当对象离开作用域时,会自动调用析构函数释放对象所占用的内存。这样,可以避免手动管理堆上对象的内存,减少内存泄漏的风险。

  2. 性能考虑:在栈上创建对象比在堆上创建对象更高效。栈上的对象分配和释放内存只涉及栈指针的移动,而堆上对象需要通过动态内存分配来完成,这可能涉及较大的开销。

  3. 线程安全性:栈上的对象只能在所在的线程中访问,这可以简化对象的线程同步问题。而在多线程环境下,管理堆上对象的并发访问可能会更复杂。

需要注意的是,如果一个类只能生成栈对象,那么在使用该类时需要遵循这个规定,不能通过 new 运算符在堆上创建对象。否则,编译器将会报错。

总结起来,限制一个类只能生成栈对象可以简化内存管理,提高性能,以及简化线程同步,但也需要在使用时遵循这个限制。

对象要放在栈上需要哪些条件:

对构造函数和析构函数都有要求

  • 必须要确保构造函数和析构函数都放在public区
  • 将类中的operator new库函数放在private区域

代码:

类声明

class Student{

public:
    Student(const char * name,int id)
    : _name(new char[strlen(name)+1]())
      ,_id(id)
    {
        cout<<"Student(const char *name,int id)"<<endl;
        strcpy(_name,name);
    }

    ~Student()
    {
        if(_name)
        {
            delete[] _name;
            _name = nullptr;
        }
        cout<<"~Student()"<<endl;
    }
    void print()
    {
        cout<<"name:"<<_name<<endl;
        cout<<"id:"<<_id<<endl;
    }
private:
    void * operator new (size_t n)
    {
        cout<<"void *operator new (size_t)"<<endl;
        return malloc(n);
    }
	void operator delete(void *p)
 	{
        cout<<"void operator delete (void *)"<<endl;
      	free(p);
    }
private:
    char *_name;
    int _id;
};
void test1()
{
	//生成栈对象的要求:
	//必须要将构造函数和析构函数都放在public区域
	Student s1("XiaoHong",101);
	s1.print();
}
int main()
{
     test1();
     return 0;
}

效果:
在这里插入图片描述
在这里插入图片描述
因为我们没有用到operator new 和operaotr delete表达式函数,故只需要在private区域声明即可,无需定义出来。栈对象的生命周期是与其所在的作用域一致的,当对象离开作用域时,会自动调用析构函数释放对象所占用的内存。这样,可以避免手动管理堆上对象的内存,减少内存泄漏的风险。

故最终代码如下:

#include <iostream>
#include<string.h>
using std::cout;
using std::endl;
class Student{

public:
    Student(const char * name,int id)
    : _name(new char[strlen(name)+1]())
      ,_id(id)
    {
        cout<<"Student(const char *name,int id)"<<endl;
        strcpy(_name,name);
    }

    ~Student()
    {
        if(_name)
        {
            delete[] _name;
            _name = nullptr;
        }
        cout<<"~Student()"<<endl;
    }
       void print()
    {
        cout<<"name:"<<_name<<endl;
        cout<<"id:"<<_id<<endl;
    }
private:
    void * operator new (size_t n);

    void operator delete(void *p);

private:
    char *_name;
    int _id;
};

void test1()
{
    //生成栈对象的要求:
    //必须要将构造函数和析构函数都放在public区域
    Student s1("XiaoHong",101);
    s1.print();
}
int main()
{
    test1();
    return 0;
}

效果如上述图片所示

要求:一个类只能生成堆对象

一个类只能创建在堆上的对象,不能创建位于栈上的对象

结论:只需要将析构函数放在私有的区域就可以

代码:

#include <iostream>
#include<string.h>
using std::cout;
using std::endl;
class Student{

public:
    Student(const char * name,int id)
    : _name(new char[strlen(name)+1]())
      ,_id(id)
    {
        cout<<"Student(const char *name,int id)"<<endl;
        strcpy(_name,name);
    }
    //new表达式
    void * operator new (size_t n)
    {
        cout<<"void *operator new (size_t)"<<endl;
        return malloc(n);
    }
    //delete表达式
    void operator delete(void *p)
    {
        cout<<"void operator delete (void *)"<<endl;
        free(p);
    }
    void print()
    {
        cout<<"name:"<<_name<<endl;
        cout<<"id:"<<_id<<endl;
    }
    void realse()
    {
        /* this->~Student(); */
        /* operator delete (this); */
        //上面两行代码实际上是delete表达式的工作
        //故可合并为下面这行代码
        delete this;
    }
//析构函数私有化
private:
    ~Student()
    {
        if(_name)
        {
            delete[] _name;
            _name = nullptr;
        }
        cout<<"~Student()"<<endl;
    }
private:
    char *_name;
    int _id;
};

void test(){
    Student *pstu = new Student("Xiaoming",100);
    pstu->print();
    /* delete pstu; //无法在类之外回收对象 */
    //因为析构函数私有化了
    pstu->realse();

}
int main()
{
    test();
    return 0;
}

效果:
在这里插入图片描述
在这里插入图片描述

实现了在栈上是无法创建对象的功能。

有时候需要限制对象只能在堆上创建的原因包括:

对象生命周期的灵活性:堆对象的生命周期不受作用域的限制,可以在需要的时候手动管理对象的创建和销毁。这可以用于动态地创建对象,并在需要时将对象传递给其他函数或对象。

对象共享和持久性:堆对象可以被多个函数或对象共享访问,而不受作用域的限制。这使得堆对象可以在多个上下文中传递和使用,从而提供了更大的灵活性。

对象的生存期延长:堆对象的生存期可以延长到其显式释放或删除为止。这可以用于创建长期存在的对象,或者需要跨函数或模块传递的对象。

需要注意的是,如果一个类只能生成堆对象,那么在使用该类时应该遵循这个规定,不应该直接在栈上创建对象。否则,编译器可能会报错。

总结起来,限制一个类只能生成堆对象可以提供灵活的对象生命周期、对象共享和持久性,以及延长对象生存期的能力。但也需要在使用时遵循这个限制,避免在栈上直接创建对象。

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

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

相关文章

【linux学习笔记】网络

目录 【linux学习笔记】网络检查、监测网络ping-向网络主机发送特殊数据包traceroute-跟踪网络数据包的传输路径netstat-检查网络设置及相关统计数据 【linux学习笔记】网络 检查、监测网络 ping-向网络主机发送特殊数据包 最基本的网络连接命令就是ping命令。ping命令会向指…

062:vue中将一维数组变换为三维数组(图文示例)

第062个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

Leetcode2487. 从链表中移除节点

Every day a Leetcode 题目来源&#xff1a;2487. 从链表中移除节点 解法1&#xff1a;单调栈 遍历链表&#xff0c;建立一个单调栈&#xff08;单调递减&#xff09;。 再用头插法将单调栈的元素建立一个新的链表&#xff0c;返回该链表的头结点。 代码&#xff1a; /*…

计算机基础面试题 |16.精选计算机基础面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

how2heap-2.23-11-poison_null_byte

什么是poison_null_byte 当然不止这一种&#xff0c;下面最简单的形式 #include <malloc.h> int main() {char * a malloc(0x200);char * b malloc(0x200);size_t real_size malloc_usable_size(a);a[real_size] 0;return 0; }影响&#xff1a; chunk a&#xff0…

【Hadoop】说下HDFS读文件和写文件的底层原理?

文件读取文件的写入 文件读取 客户端调用 FileSystem 对象的 open&#xff08;&#xff09;函数&#xff0c;打开想要读取的文件。其中FileSystem 是 DistributeFileSystem 的一个实例&#xff1b;DistributedFileSystem 通过使用 RPC&#xff08;远程过程调用&#xff09; 访N…

Large Language Models Paper 分享

论文1&#xff1a; ChatGPTs One-year Anniversary: Are Open-Source Large Language Models Catching up? 简介 2022年11月&#xff0c;OpenAI发布了ChatGPT&#xff0c;这一事件在AI社区甚至全世界引起了轰动。首次&#xff0c;一个基于应用的AI聊天机器人能够提供有帮助、…

vue3组件传参

1、props: 2、自定义事件子传父 3、mitt任意组件通讯 4、v-model通讯(v-model绑定在组件上) (1)V2中父子组件的v-model通信&#xff0c;限制了popos接收的属性名必须为value和emit触发的事件名必须为input,所以有时会有冲突; 父组件: 子组件: (2)V3中:限制了popos接收的属性名…

读元宇宙改变一切笔记02_元素(上)

1. 很多组织和机构都想在元宇宙的定义上掌握话语权&#xff0c;使得它的定义中存在矛盾之处&#xff0c;也有大量含义混淆之处 1.1. 微软 1.1.1. 在谈论“多个元宇宙” 1.1.2. 微软首席执行官萨提亚纳德拉将元宇宙描述为一种可以将“整个…

Dockerfile的ENV

文章目录 环境总结测试测试1测试2测试3测试4测试5测试6 参考 环境 RHEL 9.3Docker Community 24.0.7 总结 如果懒得看测试的详细信息&#xff0c;可以直接看结果&#xff1a; 一条 ENV 指令可以定义多个环境变量。Dockerfile里可以包含多条 ENV 指令。环境变量的值不需要用…

(低级错误)IDEA/Goland报错连接数据库失败:URL错误和权限问题。

前言 做毕设ing&#xff0c;使用Goland自带的数据库工具连接服务器的数据库。报错 错误: Malformed database URL, failed to parse the main URL sections. (view)服务器是华为云&#xff0c;使用宝塔面板。数据库版本5.6.50。 排查过程 鉴于Goland报错报的狗屁不是&#…

H266/VVC率失真优化与速率控制概述

率失真优化技术 率失真优化&#xff1a; 视频编码的主要目的是在保证一定视频质量的条件下尽量降低视频的编码比特率&#xff0c;或者在一定编码比特率限制条件下尽量地减小编码失真。在固定的编码框架下&#xff0c;为了应对不同的视频内容&#xff0c;往往有多种候选的编码方…

YOLOv5改进 | 损失函数篇 | EIoU、SIoU、WIoU、DIoU、FocusIoU等二十余种损失函数

一、本文介绍 这篇文章介绍了YOLOv5的重大改进,特别是在损失函数方面的创新。它不仅包括了多种IoU损失函数的改进和变体,如SIoU、WIoU、GIoU、DIoU、EIOU、CIoU,还融合了“Focus”思想,创造了一系列新的损失函数。这些组合形式的损失函数超过了二十余种,每种都针对特定的…

万界星空科技商业开源MES主要功能介绍

一、系统概述&#xff1a; MES制造执行系统&#xff0c;其定位于制造执行系统的Java开源版本。系统包括系统管理&#xff0c;车间基础数据建模&#xff0c;计划管理&#xff0c;物料控制&#xff0c;生产执行&#xff0c;质量管理&#xff0c;库存管理&#xff0c;看板管理&am…

System学习笔记 - MacOs编译环境配置(一)

前言 好几年没有记录过东西&#xff0c;一是确实很忙&#xff0c;二是人也变懒了。新年开个新的学习计划&#xff0c;希望能坚持下去。 SystemC 简介 SystemC是一个建模语言&#xff0c;其本质是一个C的库&#xff0c;一般用于SoC建模&#xff0c;具体介绍不赘述&#xff0…

GPDB - 高可用 - 流复制状态

GPDB - 高可用 - 流复制状态 GPDB的高可用基于流复制&#xff0c;通过FTS进行自动故障切换。自动故障切换需要根据primary-mirror流复制的各种状态进行判断。本节就聊聊primary-mirror流复制的各种状态。同样适用于PgSQL 1、WalSndState typedef enum WalSndState {WALSNDSTATE…

为什么 Kafka 这么快?它是如何工作的?

随着数据以指数级的速度流入企业&#xff0c;强大且高性能的消息传递系统至关重要。Apache Kafka 因其速度和可扩展性而成为热门选择&#xff0c;但究竟是什么让它如此之快&#xff1f; 在本期中&#xff0c;我们将探讨&#xff1a; Kafka 的架构及其核心组件&#xff0c;如生…

如何编写高效的正则表达式?

正则表达式&#xff08;Regular Expression&#xff0c;简称regex&#xff09;是一种强大的文本处理技术&#xff0c;广泛应用于各种编程语言和工具中。本文将从多个方面介绍正则表达式的原理、应用和实践&#xff0c;帮助你掌握这一关键技术。 正则可视化 | 一个覆盖广泛主题…

嵌入式(六)模数转换ADC | ADC 工作模式 寄存器 轮询和中断方式

文章目录 1 CC2530的ADC模块2 ADC工作模式3 ADC相关寄存器3.1数据寄存器3.2 控制寄存器 4 ADC初始化配置5 ADC使用方式5.1 轮询方式5.2 中断方式 模拟/数字转换 (Analog to Digital Converter&#xff0c;简称ADC) 是将输入的模拟信号转换为数字信号。 各种被测控的物理量&…

基于SSM的企业员工管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…