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

news2024/11/16 13:37:54

         在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 bytes
    void *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.coloradmin.cn/o/1243602.html

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

相关文章

意图交易:为用户思考,而不是让用户思考

意图叙事 在前不久&#xff0c;知名加密投资机构 Paradigm 的 CTO 、研究员 Georgios Konstantopoulos 曾在推特上对现有 ChainAsset 模式的糟糕且割裂的体验进行了吐槽&#xff0c;这也道出了很多链上用户的痛点。同时他也认为现有加密基建设施需要为用户思考&#xff0c;而不…

NSGA-II求解微电网多目标优化调度(MATLAB)

一、NSGA-II简介 NSGA-Ⅱ算法是Kalyanmoy Deb等人于 2002年在 NSGA 的基础上提出的&#xff0c;它比 NSGA算法更加优越&#xff1a;它采用了快速非支配排序算法&#xff0c;计算复杂度比 NSGA 大大的降低&#xff1b;采用了拥挤度和拥挤度比较算子&#xff0c;代替了需要指定的…

flutter,uni-app开发调试ios

一、申请ios开发者账号 二、ios开发者配置 ios 开发者需要配置的地方 https://developer.apple.com/account/resources/certificates/list Certificates&#xff08;证书&#xff09;: 作用&#xff1a; 证书用于对应用程序和开发者进行身份验证&#xff0c;确保安全性和可…

文心一言 VS 讯飞星火 VS chatgpt (141)-- 算法导论12.1 1题

一、用go语言&#xff0c;对于关键字集合(1&#xff0c;4&#xff0c;5&#xff0c;10&#xff0c;16&#xff0c;17&#xff0c;21)&#xff0c;分别画出高度为 2、3、4、5和6的二又搜索树。 文心一言&#xff1a; 以下是用Go语言为给定的关键字集合(1, 4, 5, 10, 16, 17, 2…

【深度学习】如何选择神经网络的超参数

1. 神经网络的超参数分类 神经网路中的超参数主要包括: 1. 学习率 η 2. 正则化参数 λ 3. 神经网络的层数 L 4. 每一个隐层中神经元的个数 j 5. 学习的回合数Epoch 6. 小批量数据 minibatch 的大小 7. 输出神经元的编码方式 8. 代价函数的选择 9. 权重初始化的方法 …

4.4 抗锯齿

一、锯齿是怎么产生的 二、抗锯齿介绍 1.SSAA(super sample anti-aliasing) 拿4xSSAA举例子&#xff0c;假设最终屏幕输出的分辨率是800x600, 4xSSAA就会先渲染到一个分辨率1600x1200的buffer上&#xff0c;然后再直接把这个放大4倍的buffer下采样至800x600。这种做法在数学上…

卷积神经网络(AlexNet)鸟类识别

文章目录 一、前言二、前期工作1. 设置GPU&#xff08;如果使用的是CPU可以忽略这步&#xff09;2. 导入数据3. 查看数据 二、数据预处理1. 加载数据2. 可视化数据3. 再次检查数据4. 配置数据集 三、AlexNet (8层&#xff09;介绍四、构建AlexNet (8层&#xff09;网络模型五、…

pair和typedef

文章目录 一、pair用法1.2、pair的创建和初始化1.3、pair对象的操作1.4、(make_pair)生成新的pair对象1.5、通过tie获取pair元素值 2、typedef2.1、什么是typedef2.2、typedef用法2.2.1、对于数据类型使用例如&#xff1a;2.2.2、对于指针的使用例如2.2.3、对于结构体的使用 2.…

科技与艺术如何交织出“理想之家”?三星电视给出家电行业最优解答

作者 | 曾响铃 文 | 响铃说 理想的家&#xff0c;是什么样子? 关于这个问题&#xff0c;社交媒体上有形形色色的答案。很多人的梦中情屋是原木风、奶油色&#xff0c;点缀着绿意盎然的植物&#xff1b;还有一些人的Dream house是用全屋智能将科技感拉满&#xff0c;再配上打…

Linux 用户必备的 8 大网站

目录 前言 1、ArchWiki 2、ExplainShell 3、Crontab.guru 4、DistroWatch 5、Phoronix 6、Ubuntu Blog 7、GamingOnLinux 8、Reddit 的 /r/Linux 子论坛 我收藏了什么&#xff1f; 前言 这里列举的是我个人最喜欢的 Linux 网站&#xff0c;希望你也能喜欢。 对 Linux …

Redis报错:JedisConnectionException: Could not get a resource from the pool

1、问题描述&#xff1a; redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool 2、简要分析&#xff1a; redis.clients.util.Pool.getResource会从JedisPool实例池中返回一个可用的redis连接。分析源码可知JedisPool 继承了 r…

无需公网IP,使用内网穿透实现公网访问本地OpenWRT管理界面

文章目录 1.openWRT安装cpolar2.配置远程访问地址3.固定公网地址 简单几步实现在公网环境下远程访问openWRT web 管理界面&#xff0c;使用cpolar内网穿透创建安全隧道映射openWRT web 界面面板443端口&#xff0c;无需公网IP&#xff0c;无需设置路由器。 1.openWRT安装cpola…

C++11『lambda表达式 ‖ 线程库 ‖ 包装器』

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f383;操作环境&#xff1a; Visual Studio 2022 版本 17.6.5 文章目录 &#x1f307;前言&#x1f3d9;️正文1.lambda表达式1.1.仿函数的使用1.2.lambda表达式的语法1.3.lambda表达式的使用…

基于5G+物联网+SaaS+AI的农业大数据综合解决方案:PPT全文44页,附下载

关键词&#xff1a;智慧农业大数据&#xff0c;5G智慧农业&#xff0c;物联网智慧农业&#xff0c;SaaS智慧农业&#xff0c;AI智慧农业&#xff0c;智慧农业大数据平台 一、智慧农业大数据建设背景 1、应对全球人口快速增长带来的粮食生产压力&#xff0c;未来的粮食生产力必…

鸿蒙(HarmonyOS)应用开发——安装DevEco Studio安装

前言 HarmonyOS华为开发的操作系统&#xff0c;旨在为多种设备提供统一的体验。它采用了分布式架构&#xff0c;可以在多个设备上同时运行&#xff0c;提供更加流畅的连接和互动。HarmonyOS的目标是提供更高的安全性、更高效、响应更快的用户体验&#xff0c;并通过跨设备功能…

【MySQL】宝塔面板结合内网穿透实现公网远程访问

文章目录 前言1.Mysql服务安装2.创建数据库3.安装cpolar3.2 创建HTTP隧道4.远程连接5.固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 宝塔面板的简易操作性,使得运维难度降低,简化了Linux命令行进行繁琐的配置,下面简单几步,通过宝塔面板cpo…

程序员接单,宝藏好平台抄底攻略清单!五大平台精选。

前阵子”双十一“购物节狂欢促销&#xff0c;各种好货清单席卷而来。 程序员购不购物我不知道&#xff0c;但是这个兼职、接单清单相信你一定用得着。 搜罗海量信息&#xff0c;整理大量数据与评价&#xff0c;挖出了5个宝藏平台&#xff0c;绝对个个精选&#xff0c;保证量大…

好的程序员有什么特质呢?

程序员想要提升自己&#xff0c;一定要关注到工作中的方方面面。而一个好的程序员&#xff0c;一般都有这些特质&#xff1a; 弱者抱怨环境&#xff0c;强者改变环境 不要试图通过抱怨环境来获得工作环境上的改变&#xff0c;这不仅不会给你带来任何实质性的改变&#xff0c;…

别熬夜了!人真的会变臭

身为当代社畜&#xff0c;你一定经历过如下瞬间——— 周一早高峰的地铁车厢&#xff0c;拥挤的人群里若有若无地飘荡出一股刺鼻臭味&#xff0c;即使戴着口罩也难以抵挡其穿透性&#xff1b; 深夜还灯火通明的办公室工位上&#xff0c;浑浊的空气裹挟着疲惫的身体&#xff0…

每日一练:质因数分解

1. 题目 从键盘输入一个整数&#xff0c;开始整数的质因数分解&#xff0c;最后打印出该整数的所有质因数。 2.质因数概念 质因数&#xff08;素因数或质因子&#xff09;在数论里是指能整除给定正整数的质数。除了1以外&#xff0c;两个没有其他共同质因子的正整数称为互质。…