new和delete表达式的工作步骤

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.mfbz.cn/a/299971.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

处cp社交类微信小程序前端开源(二)

在上一篇文章介绍如何用SpringBoot整合websocket实现在线聊天&#xff0c;这篇文章介绍如何将uniapp社交类前端源码打包部署微信小程序&#xff0c;和如何上线微信小程序&#xff0c;上线需要的资料&#xff0c;并且介绍我是如何获取用户&#xff0c;如何变现&#xff0c;现在的…

nginx下upstream模块详解

目录 一&#xff1a;介绍 二&#xff1a;特性介绍 一&#xff1a;介绍 Nginx的upstream模块用于定义后端服务器组&#xff0c;以及与这些服务器进行通信的方式。它是Nginx负载均衡功能的核心部分&#xff0c;允许将请求转发到多个后端服务器&#xff0c;并平衡负载。 在upst…

前端-基础 常用标签-超链接标签( 锚点链接 )

锚点链接 &#xff1a; 点击链接&#xff0c;可以快速定位到 页面中的某个位置 如果不好理解&#xff0c;讲一个例子&#xff0c;您就马上明白了 >>> 这个是 刘德华的百度百科 &#xff0c;可以看到&#xff0c;页面里面有很多内容&#xff0c;那就得有个目录了 …

RabbitMQ高级

文章目录 一.消息可靠性1.生产者消息确认2.消息持久化3.消费者确认4.消费者失败重试 MQ的一些常见问题 1.消息可靠性问题:如何确保发送的消息至少被消费一次 2.延迟消息问题:如何实现消息的延迟投递 3.高可用问题:如何避免单点的MQ故障而导致的不可用问题 4.消息堆积问题:如…

狂肝100小时,各大厂20W字面试真题分享

有很多童靴问我&#xff0c;有没有大厂的面试集合&#xff0c;可以针对性备考一下&#xff0c;我说面试题网络上有很多&#xff0c;随便搜索一下&#xff0c;就一大把吧。他们回复说&#xff0c;都是针对各个知识点的题目&#xff0c;想要吃透&#xff0c;至少要1-3个月的时间&…

NCC基础开发技能培训

YonBuilder for NCC 是一个带插件的eclipse工具&#xff0c;跟eclipse没什么区别 NC Cloud2021.11版本开发环境搭建改动 https://nccdev.yonyou.com/article/detail/495 不管是NC Cloud 新手还是老NC开发&#xff0c;在开发NC Cloud时开发环境搭建必看&#xff01;&#xff…

命令行模式的rancher如何安装?

在学习kubectl操作的时候&#xff0c;发现rancher也有命令行模式&#xff0c;学习整理记录此文。 说明 rancher 命令是 Rancher 平台提供的命令行工具&#xff0c;用于管理 Rancher 平台及其服务。 前提 已经参照前文安装过了rancher环境了&#xff0c;拥有了自己的k8s集群…

腾讯云代金券介绍及领取教程分享

腾讯云为了吸引用户经常推出各种优惠活动&#xff0c;其中就包括腾讯云代金券&#xff0c;领取之后可用于抵扣腾讯云平台上购买的部分产品或服务的费用。以下是腾讯云代金券的详细介绍及领取教程。 一、腾讯云代金券介绍 腾讯云代金券是腾讯云优惠券的一种&#xff0c;代金券是…

IMU用于无人机故障诊断

最近&#xff0c;来自韩国的研究团队通过开发以IMU为中心的数据驱动诊断方法&#xff0c;旨在多旋翼飞行器可以自我评估其性能&#xff0c;即时识别和解决推进故障。该方法从单纯的常规目视检查跃升为复杂的诊断细微差别&#xff0c;标志着无人机维护的范式转变。 与依赖额外传…

深入理解并解析Flutter Widget

文章目录 完整代码程序入口构建 Widget 结构定义 widget 状态定义 widget UI获取上下文关于build()build() 常用使用 完整代码 import package:english_words/english_words.dart; import package:flutter/material.dart; import package:provider/provider.dart;void main() …

软件工程专业毕业设计题目怎么选?

文章目录 0 简介1 如何选题2 最新软件工程毕设选题3 最后 0 简介 学长搜集分享最新的软件工程业专业毕设选题&#xff0c;难度适中&#xff0c;适合作为毕业设计&#xff0c;大家参考。 学长整理的题目标准&#xff1a; 相对容易工作量达标题目新颖 1 如何选题 最近非常多的…

单电阻落地扇电机驱动 DEMO 方案

SYNWIT DEMO方案 低压 PMSM 电机&#xff0c;软件上采用SVPWM空间电压矢量调制技术&#xff0c;直接闭环启动&#xff0c;相比传统方波效率提高15%&#xff0c;具有更小的谐波分量及转矩脉动&#xff0c;同时采用32位MCU芯片SWM201G6S7 SSOP28 封装为主控&#xff0c;为驱动算…

Long类型转换精度丢失问题解决

问题: 启动前端项目 页面传递的ID 和数据库保存的ID不一致 原因&#xff1a;给前端返回的id为long类型&#xff0c;在转换json传递到前端以后精度丢失&#xff0c;所以前端给我们的id也是丢失精度的id,不能查询数据。 因为js数字类型最大长度为16位&#xff0c;而java的long类…

好物周刊#35:图标资源获取

https://github.com/cunyu1943/JavaPark https://yuque.com/cunyu1943 村雨遥的好物周刊&#xff0c;记录每周看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;每周五发布。 一、项目 1. 正则大全 常用正则大全&#xff0c;支持 web/vscode/idea/Alfred Work…

红日靶场-3

目录 前言 外网渗透 外网渗透打点 1、arp 2、nmap 3、nikto 4、whatweb 5、gobuster 6、dirsearch CMS 1、主页内容 2、/configuration.php~ 目录 3、/administrator 目录 4、Joomla!_version探测 5、joomlascan python脚本 6、joomscan perl脚本 MySQL 1、远…

Java学习——设计模式——行为型模式2

文章目录 行为型模式状态模式观察者模式中介者模式迭代器模式访问者模式备忘录模式解释器模式 行为型模式 行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务&#xff0c;涉及算法与对象间职责的…

(leetcode)Z字形变换 -- 模拟算法

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 题目链接 . - 力扣&#xff08;LeetCode&#xff09; 输入描述 string convert(string s, int numRows)&#xff0c;输入一个字符串s&#xff0c;以及一个行数numRows&#xff0c;将字符串按照这个行数进行Z字形排列&…

三菱plc学习入门(二,三菱plc指令,触点比较,计数器,交替,四则运算,转换数据类型)

今天&#xff0c;进行总结对plc的学习&#xff0c;下面是对plc基础的学习&#xff0c;希望对读者有帮助&#xff0c;欢迎点赞&#xff0c;评论&#xff0c;收藏&#xff01;&#xff01;&#xff01; 目录 触点比较 当数据太大了的时候&#xff08;LDD32位&#xff09; CMP比…

文献阅读:Sparse Low-rank Adaptation of Pre-trained Language Models

文献阅读&#xff1a;Sparse Low-rank Adaptation of Pre-trained Language Models 1. 文章简介2. 具体方法介绍 1. SoRA具体结构2. 阈值选取考察 3. 实验 & 结论 1. 基础实验 1. 实验设置2. 结果分析 2. 细节讨论 1. 稀疏度分析2. rank分析3. 参数位置分析4. 效率考察 4.…

Win11开始菜单怎么改成经典模式-Win11切换Win10风格开始菜单方法

Win11切换Win10风格开始菜单方法 方法一&#xff1a; 1. 在Win11电脑上下载一个“Startallback”软件&#xff0c;下载安装完成后&#xff0c;在“控制面板”里打开该软件。 2. 打开后&#xff0c;在“欢迎界面”&#xff0c;选择使用“Windows10主题样式”并重启电脑即可。…
最新文章