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.mzph.cn/news/604656.shtml

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

相关文章

黑马程序员Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目——第三部分

黑马程序员Java项目实战《瑞吉外卖》&#xff0c;轻松掌握springboot mybatis plus开发核心技术的真java实战项目——第三部分 1. 菜品管理的业务功能1.1 文件的上传和下载&#x1f647;‍♂️1.2 新增菜品1.3 接收页面提交的数据&#x1f647;‍♂️&#xff08;涉及两张表&a…

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

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

dnSpy调试Web应用程序

文章目录 前言一、dnSpy是什么&#xff1f;二、如何使用dnSpy三、如何调试Web应用程序四、下载总结 前言 dnSpy是一个.NET程序集调试器和编辑器&#xff0c;主要用于调试和编辑没有源码的.NET程序集。 一、dnSpy是什么&#xff1f; dnSpy是一个.NET程序集调试器和编辑器&#…

IDEA 控制台中文乱码问题解决方法(UTF-8 编码)

设置 IDEA 编码格式 1&#xff1a;打开 IntelliJ IDEA>File>Setting>Editor>File Encodings&#xff0c;将 Global Encoding、Project Encoding、Default encodeing for properties files 这三项都设置成 UTF-8 2&#xff1a;将 vm option 参数改为&#xff1a; -…

Linux Capabilities 基础概念与基本使用

目录 1. Linux capabilities 是什么&#xff1f; 2. capabilities 的赋予和继承 线程的 capabilities Permitted* 允许 Effective* 有效 Inheritable* 遗传 Bounding&#xff08;集合&#xff09; Ambient 文件的 capabilities Permitted Inheritable Effective 3…

链表

目录 单链表 双链表 单链表 题目如下&#xff1a;模拟一个单链表&#xff0c;实现插入删除操作 解题代码 #include <iostream>using namespace std;const int N 100010;// head 表示头结点的下标 // e[i] 表示节点i的值 // ne[i] 表示节点i的next指针是多少 // idx …

墨墨智库正式上线:开启您的AI智慧之旅

在这个由数据驱动的时代&#xff0c;AI技术正迅速改变我们的工作和生活方式。有没有想过一个平台可以为您提供所有AI相关资源的便捷访问&#xff1f;这就是「墨墨智库」的使命。我们非常高兴地宣布&#xff0c;经过精心准备和期待&#xff0c;「墨墨智库」现已正式上线&#xf…

图像解析力测试

什么是图像解析力测试 图像解析力测试是衡量成像系统性能的关键指标之一,它决定了摄像头捕捉到的图像细节和清晰度。目前主流的图像解析力测试方法主要有TV line检测、MTF检测和SFR检测。 TV line检测主要用于主观测试,通过观察图像中的线条来评估解析力。然而,这种方法缺乏…

学习笔记——C++一维数组

1&#xff0c;一维数组的定义方式 三种定义方式 1&#xff0c;数据类型 数组名[ 数组长度 ]&#xff1b; 2&#xff0c;数据类型 数组名[ 数组长度 ]{值1&#xff0c;值2&#xff0c;值3 ……}&#xff1b;//未说明的元素用0填补 3&#xff0c;数据类型 数组名[ ]{值1&…

Joplin配合teracloud进行多版本客户端分别笔记同步

最近瞎搜索joplin&#xff0c;意外在github上搜到plugins&#xff0c;插件仓库&#xff0c;里面有一个思维导图的插件我还是蛮喜欢的&#xff0c;结果下载后安装发现&#xff0c;我当前的Jopin的版本如下 &#xff08;Joplin 2.7.15 (prod, win32) 同步版本: 3 配置文件版本: 4…

MySQL练习-DDL语法练习

文章目录 1、数据库操作2、表操作3、DDL数据类型 突然想起来好久没写过SQL了&#xff0c;写一下SQL练习一下&#x1f60a; 个人写sql比较喜欢用小写&#x1f601; 什么是DDL&#xff1a;DDL是对数据库和表的操作 在这里练习DLL的时候先不添加约束&#xff0c;后面会把约束集中…

KVM虚拟化技术

在当今的云计算时代&#xff0c;虚拟化技术已经成为了企业和个人用户的首选。而在众多虚拟化技术中&#xff0c;KVM&#xff08;Kernel-based Virtual Machine&#xff09;虚拟化技术因其高性能、低成本和灵活性而备受青睐。本文将介绍KVM虚拟化技术的原理、特点以及应用场景。…

陀螺研究院发布《中国产业区块链生态图谱 2024版》

从发展实践来看&#xff0c;产业区块链在我国已历经了4年的高速发展&#xff0c;发展至今&#xff0c;我国区块链发展环境基本夯实&#xff0c;形成了技术突破与应用拓宽的创新土壤&#xff0c;围绕区块链为主体的产业链条不断纵深延伸&#xff0c;在基础设施支撑、融合创新拓展…

学习c语言,隐形类型转换,整形提升

把整形定义字符型的话&#xff0c;字符型指挥提取整形前8位&#xff0c;但是整形有32位&#xff0c;如果字符型最后一位为0全部补0&#xff0c;为1全部补1。

java案例知识点

一.会话技术 概念 技术 二.跨域 三.过滤器 四.拦截器

【读书笔记】《白帽子讲web安全》浏览器安全

目录 第二篇 客户端脚本安全 第2章 浏览器安全 2.1同源策略 2.2浏览器沙箱 2.3恶意网址拦截 2.4高速发展的浏览器安全 第二篇 客户端脚本安全 第2章 浏览器安全 近年来随着互联网的发展&#xff0c;人们发现浏览器才是互联网最大的入口&#xff0c;绝大多数用户使用互联…

C#上位机与三菱PLC的通信01--搭建仿真环境

1、三菱PLC介绍 三菱PLC是三菱电机生产的主力产品。 它采用一类可编程的存储器&#xff0c;用于其内部存储程序&#xff0c;执行逻辑运算、顺序控制、定时、计数与算术操作等面向用户的指令&#xff0c;并通过数字或模拟式输入/输出控制各种类型的机械或生产过程。三菱PLC在中国…

LeetCode-移动零(283)

题目描述&#xff1a; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 思路&#xff1a; 这里的思路跟以前做过的去重复数字的思路有点像&…

【leetcode】力扣算法之有效的数独【中等难度】

题目描述 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图&…

【JaveWeb教程】(7)Web前端基础:Vue组件库Element介绍与快速入门程序编写并运行 示例

目录 Element介绍快速入门示例 Element介绍 不知道同学们还否记得我们之前讲解的前端开发模式MVVM&#xff0c;我们之前学习的vue是侧重于VM开发的&#xff0c;主要用于数据绑定到视图的&#xff0c;那么接下来我们学习的ElementUI就是一款侧重于V开发的前端框架&#xff0c;主…