前言
new和delete是C++为了类的使用方便而创造的操作符,我们在使用C++时应该尽量避免使用malloc和free而应该转向使用new和delete.
new/delete
内置类型举例
new/delete类似于malloc/free但是为了搭配我们的C++的新知识也就是类的出现我们在从堆区开辟空间的时候需要调用我们类里的构造函数,而和明显malloc他们并不可能会实现这类又为了兼容C语言C++就增加了new来方便我们自定义类型的从堆开辟空间.
老样子,我们要是讲新知识的时候还是先看看这玩意咋用的
void Test() { // 动态申请一个int类型的空间 int* ptr1 = new int; // 动态申请一个int类型的空间并初始化为10 int* ptr2 = new int(10); // 动态申请10个int类型的空间 int* ptr3 = new int[3]; //申请10个int类型的空间并进行初始化,C++11才支持这样写 int* ptr4 = new int[10]{ 1,2,3,4,5,6,7,8 }; delete ptr1; delete ptr2; delete[] ptr3; delete[] ptr4; } int main() { Test(); return 0; }
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和 delete[]
其中ptr4我们还是单独拿出来说一下,毕竟是C++11才加入的语法
内置类型的newC++11
我们ptr4是C++新加入的初始化写法,其实就和普通的数组初始化类似,而且不能直接给初始化字符串.
只能通过{}来给数组,感觉意义不大.=.=
内置类型基本就这样了
自定义类型举例
new和delete就是为了我们C++的类而创造的,所以自然在自定义类型有着malloc无法比拟的优点.
直接讲吧,new会在创建类的时候调用构造函数,delete会在销毁类之前调用析构函数,而malloc和free做不到这点
注意: 我们的类里如果有自定义类型,我们的new也会先调用他们的构造函数在delete的时候会调用他们的析构函数.
光说不看看代码还是难理解
先来看看new和delete在自定义类型怎么使用的吧.(类就随便创一个了)
class Test { public: Test(int a = 1, int b = 2) { _a = 1; _b = 0; cout << "Test()" << endl; } ~Test() { cout << "~Test()" << endl; } private: int _a; int _b; };
可以看出调用了我们的构造和析构.如果不delete就不会调用, 篇幅问题不展示了.
看一下类里有其他类的情况吧
class Test1 { public: Test1(int a = 1, int b = 2) { _a = 1; _b = 0; cout << "Test1()" << endl; } ~Test1() { cout << "~Test1()" << endl; } private: Test _t1; int _a; int _b; };
弄了个Test1里面有Test
来看看结果吧
调用了,delete也调用了作为成员类的析构函数.
我们看看malloc和free吧.
就没有调用(其实也很正常毕竟要兼容C语言).
来看看如何使用自定义类型中new的使用吧.
void Test1() { //ptr1的new调用了默认构造函数 Test* ptr1 = new Test; //ptr2调用构造函数的时候传进去了3,3. Test* ptr2 = new Test(3, 3); //自定义类型数组都调用了默认构造函数 Test* ptr3 = new Test[10]; delete ptr1; delete ptr2; delete ptr3; }
和普通类型没啥区别.嘿嘿
new失败
new失败和malloc失败时不同new失败会抛异常,如果我们没有接收异常编译器会直接报错.
来看看new失败不接收异常会发生什么吧.
那我们要如何接收他的异常呢?
int main() { //Test2(); char* ptr1 = nullptr; char* ptr2 = nullptr; try { ptr1 = new char[1024 * 1024 * 1024]; ptr2 = new char[1024 * 1024 * 1024]; } catch (const exception& e) { cout << e.what() << endl; } delete[] ptr1; delete[] ptr2; return 0; }
抱歉这部分需要后面的知识才能讲解,但是用法就是这些用法.
这样写我们的编译器就不会报错了.
operator new与operator delete
注意operator new operator delete是函数名字,不是重载.
我们也没办法重载operator new 或者operator delete .
这两个函数与malloc,free唯一的区别就是operator new失败后是抛异常而malloc是返回空指针.
其实operator new 其实就是使用malloc来实现的不过就是在失败的时候是抛异常了,而不是返回空指针.
然后我们的new又通过operator new 来实现并多加上了调用构造函数.
而我们的delete其实也类似的过程.
总体就是new调用了operator new来实现失败时抛异常然后自己再调用构造函数.
operator new operator delete 其实也是可以重载的,我们可以通过重载类专属 operator new/ operator delete,实现链表节点使用内存池申请和释放内存,提高效率. 不过这是后续的知识了,我们后续会学习的.
现式调用构造函数
代码演示一下就可以了
class Test { public: Test(int a = 1, int b = 2) { _a = 1; _b = 0; cout << "Test()" << endl; } ~Test() { cout << "~Test()" << endl; } private: int _a; int _b; }; int main() { Test* p = (Test*)operator new (sizeof(Test)); new(p)Test(1, 2);//现式调用构造函数 // new(指针)类名(要传入的值) return 0; }
现式调用构造函数形式如代码上.
或看下:
new(指向类的指针)+类名(初始化的值);
new和delete的实现原理
new的实现原理
调用operator new函数申请空间
调用构造函数来完成类的初始化.
delete的实现原理
调用析构函数完成类里的空间释放或销毁
调用operator delete函数释放开辟类的空间
new[]的实现原理
调用operator new[]
operator new[]实际调用多个operator new来完成N个空间的开辟
调用类的构造函数
delete[]的实现原理
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
malloc/free和new/delete的区别小结
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:
malloc和free是函数,new和delete是操作符
malloc申请的空间不会初始化,new可以初始化
malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间 后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
结尾
觉得好的话记得点个赞.