C++的new / delete 与 C语言的malloc/realloc/calloc / free 的讲解

         在C语言中我们通常会使用malloc/realloc/calloc来动态开辟的空间,malloc是只会开辟你提供的空间大小,并不会初始化内容;calloc不但会开辟空间,还会初始化;realloc是专门来扩容的,当你第一次开辟的空间不够用的时候,就要使用realloc;如果你第一次使用realloc的时候,前面没有开辟过空间,那么realloc的行为会跟malloc一样,之后再发挥realloc自己的行为。而我们的C++是面向对象的编程,当开辟空间失败了malloc只会返回一个空指针,我们还需要自己来判断。所以在C++这里就将malloc升级成为了new,new在开辟空间失败的时候会抛出异常,这跟我们面向对象的理念是一致的。

        其实我在C语言阶段学习动态开辟空间的时候,是有点迷糊的,就是不知道如何去开辟,给谁开辟?现在想想其实不难理解,假设我们要开辟10个int类型的空间大小,我们有两种办法,一种就是直接定义一个int类型的数组:int arr[10],但是这样的空间是定长的空间,我们无法对定长数组进行扩容。第二种就是用到动态开辟的空间了,我们肯定是先要malloc10个int类型的空间,但是我们应该如何取到这段空间呢?用指针变量接收是吧,但是为什么是指针变量呢?我们继续讲解,因为我们先开辟了一段空间,正常情况下是无法取到这段空间的,只有拿到这段空间的地址,才可以访问这段空间。这也就是为什么我们会用指针变量来接收malloc开辟的空间了。

        下面是malloc的函数声明:通过malloc的返回值也不难看出要用指针接收。所以我们要开辟空间,一种就是直接定义类型,比如int a、int arr[10]、char ch等等,一种就是用malloc/realloc/calloc开辟空间,用指针接收。

一、介绍

1. malloc、realloc、calloc的介绍

1. malloc只是动态开辟空间,并没有初始化这段空间的内容;

2. realloc是用来扩容的,如果没有预先开辟空间,直接使用realloc,realloc的作用相当于malloc;

3. calloc相当于是malloc的加强版,不仅可以开辟空间,也顺便初始化我们的内容;

4. 面对内置类型可以直接开辟空间,但是对于自定义类型的话,比如我们的栈,则无法一步到位,因为栈里面还有数组空间的开辟,需要两层开辟。

        下面就是栈的举例:因为我们仅仅是用的malloc来开辟一个栈的空间,所以栈内部的数组空间还需要开辟,如果我们要是写一个构造函数去初始化,在主函数中是无法调用的,也就完成不了对数组空间的开辟,不过在了解定位new之后是可以调用的,我们这里使用写一个成员函数Init来完成对数组空间的开辟,这样我们在主函数中也可以调用。然后其实这样的方法有点麻烦,而且在malloc失败的时候只是返回空指针,判断条件也需要我们自己去写,这很不满足面向对象的要求,所以C++基于这样的原因创造了new这个操作符。

#include <iostream>
using namespace std;
class Stack
{
public:void Init(int capacity){_a = (int*)malloc(sizeof(int) * capacity);if(_a == nullptr){perror("malloc fail");exit(-1);}_size = 0;_capacity = capacity;}
private:int *_a;int _size;int _capacity;
};
int main()
{Stack *st = (Stack*)malloc(sizeof(Stack));st->Init(4);return 0;
}

2. free的介绍

        free专门释放动态开辟的空间,如果释放的不是动态开辟的空间,就会报错,所以这里一定要多加注意!!!

        一般就是free( ),括号里面就是指向那段空间的指针就行。

3. new、operator new的介绍(内置类型和自定义类型)

        new是在C++中才有的操作符,因为C++兼容C语言,所以在C++中可以写C,但是在C中写不了C++。new也是用来动态开辟空间的,也可以初始化,即可以开辟内置类型的空间,也可以开辟自定义类型的空间,这两种类型都可以初始化;

1. new对内置类型开辟空间:直接开辟

int *a = new int;      //单纯开辟空间
int *b = new int(3);   //开辟空间并初始化int *arr1 = new int[5]; //单纯开辟5个整型的空间
int *arr2 = new int[5]{1, 2, 3, 4, 5}; //开辟5个整型空间并初始化

2. new对自定义类型开辟空间:第一步:开辟这个自定义类型需要的空间;

                                                  第二步:调用这个自定义类型的构造函数;

所以我们上面对于栈的开辟,可以改善为下面这样:

#include <iostream>
using namespace std;
class Stack
{
public:Stack(int capacity = 4, int size = 0, int num = 0): _a(new int[capacity]{0}), _size(size), _capacity(capacity){cout << "Stack(int capacity = 4)" << endl; //方便观察确实调用了构造函数}private:int *_a;int _size;int _capacity;
};
int main()
{Stack *st = new Stack;//Stack *st = new Stack(4, 0, 0);这种就是对于显式构造函数的写法,或者是你想传入的初始化内容return 0;
}

3. 那operator new 又是什么呢?

        相信大家对这个并不熟悉,所以这部分知识了解即可,operator new 是一种全局函数,对malloc进行了封装,也就是让malloc函数更加面向对象,而不是面向过程了。因为我们在C语言的时候,malloc开辟空间失败,会返回个空指针,我们会根据空指针去找过程中的错误,然后C++是面向对象编程,所以就必须知道是哪个对象出错了,new出错就报new。所以operator new就是对malloc进行了一个包装,底层还是通过malloc实现,只不过在出错的时候,operator new是抛异常,不是返回空指针了。下面就是operator new这个函数的定义:

所以operator new 还是通过malloc实现的,看不懂下面的也没关系,记住operator new 开辟空间失败抛异常,malloc失败是返回NULL;

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytesvoid *p;while ((p = malloc(size)) == 0){if (_callnewh(size) == 0){// report no memory// 如果申请内存失败了,这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}}return (p);
}

4. delete、operator delete的介绍

delete是用来释放动态开辟的空间,如果释放的不是动态开辟的空间会报错;

他跟free的区别是在自定义类型这里,free仅仅释放他括号里指向的空间;

delete在释放自定义类型的空间时候,会做两件事:

1. 先调用自定义类型的析构函数;

2. 再去调用operator delete函数;

    int *a = new int;char *ch = new char;double *d = new double[5];delete a;delete ch;delete []d;

而operator delete 我们会在new、delete针对数组这里拿出来讲解

二、new、delete的原理

1. new对于自定义类型:

1. new 会先调用 operator new 开自定义类型的空间;

2. 在申请的空间上,再去调用自定义类型的构造函数 ;

这里解答一下为什么new步调用malloc,而是调用的operator new

因为还是C++是面向对象编程,所以我们不使用malloc这套判断错误的方式,而是选择operator new 这个函数去封装一下new,再让new来调用自己,一切都是为了面向对象,所以new的产生原因也是这样的,因为面向对象。

2. delete对于自定义类型

1. 在空间上执行析构函数,完成对象中资源的清理工作
2. 调用operator delete函数释放对象的空间

需要注意的就是为什么delete要先调用析构函数

因为我们要知道,再对于栈这样的数据结构,他里面数组的空间是被栈空间的指针指着的,如果先释放栈的空间,就相当于把数组的地址释放了,然后再去调用析构函数就会找不到要析构的位置了,所以就会出错;

基于这样的原因一定要先析构,再释放自定义类型的空间;

3. new、delete对于数组、自定义类型的数组(重点,难点)

        这里其实又涉及到一些不一样的知识,我们知道,在new一个数组的时候,我们是告诉他开辟多少个元素的空间的,如下图,我们是知道开5个空间的大小的,那在释放的时候,delete又是如何知道需要释放多大的空间的呢?我们保持这个问题,继续往下看。

#include <iostream>
using namespace std;
class Stack
{
public:Stack(int capacity = 4, int size = 0, int num = 0): _a(new int[capacity]{0}), _size(size), _capacity(capacity){cout << "Stack(int capacity = 4)" << endl; //方便观察确实调用了构造函数}private:int *_a;int _size;int _capacity;
};
int main()
{Stack *st = new Stack[5];delete []st;return 0;
}

其实,是因为在开辟空间之前,我们多开辟了一个int的空间,这个空间就是用来存放数组的个数的,相当于是图中这样开辟的空间:

        所以delete面对数组这样的空间,他先通过st指针指向的位置,先调用多次析构函数,连续释放完5个_a指针指向的空间之后,返回到a位置处,再用通过调用operator delete释放自定义类型数组的这段空间。

        换句话说delete 的[ ] 是识别这个类型是数组,然后知道数组会多开辟4个字节的空间,进而从多开辟的空间的地址处释放。而单独的delete就不会认为这个类型是数组类型,也就不会寻找这段空间,直接在数组首元素这里释放空间,当然一定要注意自定义类型,还需要调用析构函数。

new --> 内置类型数组

new --> operator new[ ] 函数  --> operator new 函数 --> malloc  

这里要知道的是,上面都是调用了一次,因为开辟的空间是提前算好的,开辟一次就行

delete --> 内置类型数组

delete --> operator delete[ ] 函数  --> operator delete 函数 --> free  

new --> 自定义类型数组

1. new --> operator new[ ] 函数  --> operator new 函数 --> malloc  

2. 每一个自定义类型再调用构造函数

开辟的空间都是一次性开辟好的,所以只需要new一次,而每个自定义类型都需要调用构造函数,所以是多个。

delete --> 自定义类型数组

1. 每一个自定义类型先调用自己的析构函数

2. delete --> operator delete[ ] 函数  --> operator delete 函数 --> free

三、面试题:new/delete 和 malloc/free 的 异同 

共同点:
都是从堆上申请空间,并且需要用户手动释放。
不同点:
1. malloc和free是函数,new和delete是操作符;
2. malloc申请的空间不会初始化,new可以初始化;
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[ ]中指定对象个数即可;
4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需
要捕获异常;
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理;

四、练习题

1. C++中,类ClassA的构造函数和析构函数的执行次数分别为( )

ClassA *p = new ClassA[5];delete p;

A.5,1

B.1,1

C.5,5

D.程序可能崩溃

答案及解析:D

大家可以参考下面这个图:

        delete p这就相当于先调用b位置的析构函数,然后直接在b位置释放了。因为是delete p;并不是delete [ ]p;所以delete并不知道说这是一个数组,并不会跳到最前面来释放,释放的位置不对,导致了程序的崩溃;

new和delete一定要匹配,避免未定义行为;

new —— delete

new [ ] —— delete[ ]

malloc —— free

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

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

相关文章

目标检测YOLO实战应用案例100讲-基于YOLO的小目标检测改进算法(续)

目录 3.3基于混合注意力的多尺度特征融合改进方法 3.3.1整体网络架构 3.3.2特征金字塔的构建

Vue 2.0源码分析-实例挂载的实现

Vue 中我们是通过 $mount 实例方法去挂载 vm 的&#xff0c;$mount 方法在多个文件中都有定义&#xff0c;如 src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js、src/platform/weex/runtime/index.js。因为 $mount 这个方法的实现是和平台…

Python 使用tkinter复刻Windows记事本UI和菜单功能(三)

上一篇&#xff1a;Python 使用tkinter复刻Windows记事本UI和菜单功能&#xff08;二&#xff09;-CSDN博客 下一篇&#xff1a;敬请耐心等待&#xff0c;如发现BUG以及建议&#xff0c;请在评论区发表&#xff0c;谢谢&#xff01; 本文章完成了记事本的新建、保存、另存、打…

【技巧】前端开发技巧 增加前端的请求缓存 提高开发效率

定义变量 /*** 开发缓存 开关* 说明* 方便开发使用 提升开发效率* true 打开缓存* false 关闭缓存 这里上线的时候必须改为* type {boolean}*/ const cacheFlag true/*** 排除某个url 方便开发时的数据实时生效* 这里根据开发到哪个功能 实时变更&#xff0c; 比如开…

京东数据分析(京东大数据):2023年10月京东手机行业品牌销售排行榜

鲸参谋监测的京东平台10月份手机市场销售数据已出炉&#xff01; 根据鲸参谋平台的数据显示&#xff0c;今年10月份&#xff0c;京东平台手机行业的销量约340万&#xff0c;环比增长约11%&#xff0c;同比则下滑约2%&#xff1b;销售额为108亿&#xff0c;环比增长约17%&#x…

请你说一下Vue中v-if和v-for的优先级谁更高

v-if 与 v-for简介 v-ifv-forv-if & v-for使用 v-if 与 v-for优先级比较 vue2 中&#xff0c;v-for的优先级高于v-if 例子进行分析 vue3 v-if 具有比 v-for 更高的优先级 例子进行分析 总结 在vue2中&#xff0c;v-for的优先级高于v-if在vue3中&#xff0c;v-if的优先级高…

RubyMine 2023:提升Rails/Ruby开发效率的强大利器

在Rails/Ruby开发领域&#xff0c;JetBrains RubyMine一直以其强大的功能和优秀的性能而备受开发者的青睐。现如今&#xff0c;我们迎来了全新的RubyMine 2023版本&#xff0c;它将为开发者们带来更高效的开发体验和无可比拟的工具支持。 首先&#xff0c;RubyMine 2023提供了…

Java-使用poi-tl根据word模板动态生成word

作者wangsz&#xff0c;想写一些关于word的工具&#xff0c;所以就写了这篇文章 1.首先&#xff0c;先导入所需要的依赖&#xff08;poi相关依赖即可&#xff09; <!-- POI --><dependency><groupId>org.apache.poi</groupId><artifactId>poi&l…

【libGDX】使用Mesh绘制立方体

1 前言 本文主要介绍使用 Mesh 绘制立方体&#xff0c;读者如果对 Mesh 不太熟悉&#xff0c;请回顾以下内容&#xff1a; 使用Mesh绘制三角形使用Mesh绘制矩形使用Mesh绘制圆形 在绘制立方体的过程中&#xff0c;主要用到了 MVP &#xff08;Model View Projection&#xff0…

目标检测YOLO系列从入门到精通技术详解100篇-【目标检测】计算机视觉(最终篇)

目录 知识储备 KITTI数据集 1.KITTI数据集概述 2.数据采集平台 3.Dataset详述 算法原理

GIT无效的源路径/URL

ssh-add /Users/haijunyan/.ssh/id_rsa ssh-add -K /Users/haijunyan/.ssh/id_rsa

windows11上enable WSL

Windows电脑上要配置linux&#xff08;这里指ubuntu&#xff09;开发环境&#xff0c;主要有三种方式&#xff1a; 1&#xff09;在windows上装个虚拟机&#xff08;比如vmware&#xff09;。缺点是vmware加载ubuntu后系统会变慢很多&#xff0c;而且需要通过samba来实现window…

git clone -mirror 和 git clone 的区别

目录 前言两则区别git clone --mirrorgit clone 获取到的文件有什么不同瘦身仓库如何选择结语开源项目 前言 Git是一款强大的版本控制系统&#xff0c;通过Git可以方便地管理代码的版本和协作开发。在使用Git时&#xff0c;常见的操作之一就是通过git clone命令将远程仓库克隆…

【vue2】axios请求与axios拦截器的使用详解

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;当我们在路由跳转前与后我们可实现触发的操作 【前言】ajax是一种在javaScript代码中发请…

低代码开发与IT开发的区别

目录 一、含义不同 二、开发门槛不同 三、两者之间的区别 1、从技术特征来看 2、从目标开发者来看 四、低代码平台使用感受&#xff1f; &#xff08;1&#xff09;自定义模块&#xff0c;满足不同的业务需求 &#xff08;2&#xff09;工作流引擎&#xff0c;简化复杂流程的管…

机器学习实战-第4章 基于概率论的分类方法: 朴素贝叶斯

朴素贝叶斯 概述 贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。本章首先介绍贝叶斯分类算法的基础——贝叶斯定理。最后,我们通过实例来讨论贝叶斯分类的中最简单的一种: 朴素贝叶斯分类。 贝叶斯理论 & 条件概率 贝叶斯理论 …

linux网络之网络层与数据链路层

文章目录 一、网络层 1.IP协议 2.IP协议头格式 3.网段划分 4.特殊ip地址 5.IP地址的数量限制 6.私有ip和公网IP 7.路由 二、数据链路层 1.以太网 2.以太网帧格式 3.MAC地址 4.对比理解MAC地址和IP地址 5.MTU 6.ARP协议 ARP协议的工作流程 ARP数据报的格式 7.DNS 8.ICMP协议 9.N…

839 - Not so Mobile (UVA)

题目链接如下&#xff1a; Online Judge 这道题刘汝佳的解法极其简洁&#xff0c;用了20来行就解决了问题。膜拜…… 他的解法如下&#xff1a;天平&#xff08;UVa839紫书p157&#xff09;_天平 uva 839_falldeep的博客-CSDN博客 我写了两个&#xff08;都很冗长&#xff…

浅谈电气设备的绝缘在线监测与状态维修探究

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;在线监测是控制好电气设备绝缘的重要方式&#xff0c;为电力系统稳定奠定重要基础。在线监测电气设备时&#xff0c;要利用检测技术促进电力系统运行效率提升&#xff0c;让电气设备在具体工作过程中发挥更大作…

升级jdk17过程中,原来的jdk8下的webservice客户端怎样处理

背景&#xff1a;之前jdk8环境下&#xff0c;使用的cxf框架&#xff0c;而且是动态加载解析作为客户端。大家一直相处的很愉快。但是最近升级jdk17&#xff0c;发现cxf不好用了。网上百度&#xff0c;大部分都是说升级cxf版本&#xff0c;并且添加jaxb的相关依赖就可以了。但是…