文章目录
一、C++的内存管理方式
之前在C语言的动态内存管理我们学过,用来动态开辟空间的malloc/calloc/realloc和释放空间的free。C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++将malloc和free封装起来并起了新的名字new和delete。
二、new和delete的用法
1.操作内置类型
new和delete是操作符,不是函数,不需要单独添加头文件。
动态申请一个int类型的空间
int* p1 = (int*)malloc(sizeof(int)); int* p2 = new int; //释放内存 free(p1); delete p2;
动态申请一个int类型的空间并初始化
int* p3 = (int*)malloc(sizeof(int)); *p3 = 1; int* p4 = new int(1); //释放内存 free(p3); delete p4;
动态申请10个int类型的空间
int* p5 = (int*)malloc(sizeof(int) * 10); int* p6 = new int[10]; //释放内存 free(p5); delete[] p6;
动态申请10个int类型的空间并初始化(C++11)
int* p7 = (int*)malloc(sizeof(int) * 10); int* p8 = new int[10]{ 1,2,3 };//后面未初始化的部分默认为0 //释放内存 free(p7); delete[] p8;
注意:申请和释放单个元素的空间,使用new/delete操作符;申请和释放连续的空间,使用new[ ]和delete[ ],一定要配套使用!
通过对比,明显new和delete的使用更方便简洁;可以发现malloc与new的几个区别:
1.malloc和free是函数,而new和delete是操作符;
2.并且malloc申请空间时不会初始化,而new可以初始化,只需要在()
(单个空间)或者{}
(多个连续空间,C++11才引入)里赋值即可;
3.malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[ ]中指定对象个数。
4.malloc的返回值为void*,在使用时必须强转,new不需要,因为new后跟的是空间的类型
但是,这些优点并不足以解释C++不继续使用C语言的malloc和free,去创建一个新用法。new和delete最大的价值体现自定义类型的操作上。
2.操作自定义内置类型
对于内置类型,malloc/free与new/delete区别不大,真正的区别在于自定义类型。
malloc/free与new/delete最大的区别是:申请自定义类型对象时,malloc是纯粹的开辟空间,不会初始化;而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
对于需要资源申请的自定义类型更能体现其优势,以链表为例
new会先开辟空间再调用构造函数初始化,delete会先调用析构函数清理资源再释放空间,这点是与malloc/free的最大区别,也是C++新增用法的主要原因。
三、new和delete的底层实现
1.operator new和operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,也是对malloc和free进行过封装的函数。
operator new和malloc的最大区别就是当申请错误时,处理的方式不一样。
malloc申请空间失败时会返回NULL
operator new函数实际通过malloc来申请空间,申请空间成功时直接返回,失败则抛异常
new和delete是对operator new和operator delete这两个全局函数进行的封装。所以new/delete与malloc/free的不同:malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要判空,申请失败会抛异常。
2.new和delete的实现原理
new在底层调用operator new全局函数来申请空间,delete在底层调用operator delete全局函数来释放空间。
通过反汇编可以看到,new先调用operator new(本质上是通过malloc)来申请空间,然后再调用构造函数进行初始化;delete先调用析构函数完成资源的清理,再调用operator delete释放空间。
对于内置类型
new和malloc,delete和free基本类似,不同的是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
对于自定义类型
new原理:先调用operator new来申请空间,然后再调用构造函数进行初始化;
delete原理:先调用析构函数完成资源的清理,再调用operator delete释放空间。
四、定位new表达式
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
格式:
new(指针名)类型
或者new(指针名)类型(参数值)
对于自定义类型的对象,需要使用new的定义表达式进行显示调用构造函数和析构函数进行初始化和资源清理。
class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; }; int main() { // p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行 A* pa = (A*)malloc(sizeof(A)); new(pa)A;//显示调用构造函数 //new(pa)A(10); pa->~A();//显示调用析构函数 free(pa); return 0; }
为什么要显示调用构造和析构?
因为pa是是指针类型,属于内置类型,不会自动调用构造和析构,所以需要显示调用。
定位new表达式在实际中一般是配合内存池使用。 内存池的工作原理是先向系统一次性申请比较大的空间,没有初始化,当我们每次去申请空间时就直接使用内存池里的空间,然后再进行初始化。
五、malloc/free和new/delete的区别
1.malloc和free是函数,new和delete是操作符;
2.malloc申请的空间不会初始化,new可以初始化;
3.malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[ ]中指定对象个数即可;
4.malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型;
5.malloc申请空间失败时返回NULL,因此使用时必须判空,new不需要判空,申请失败会抛异常;
6.申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。