目录
在C语言中我们在堆上动态开辟内存有malloc和free 库函数,C++中我们引入了new和delete关键字来替代malloc和free,下面我们会具体学习这两个 关键字
引例
void new_test1()
{
int* p1 = (int*)malloc(sizeof(int));
int* p2 = (int*)malloc(sizeof(int) * 10);
int* p3 = new int;
int* p4 = new int[10];
free(p1);
free(p2);
delete p3;
delete[] p4;
}
调试运行到这里好像感觉new和delete这组动态开辟内存和malloc free的几乎相同,直观的感觉只是new和delete在语法上更加简洁,但C++的new和delete真的只是更简洁了吗?
new delete语法
先简单讲解一下上面的new delete的语法知识
void new_test1()
{
//new typename 在堆上开辟一个typename大小的空间,存进p1中
int* p1 = new int;
//释放p1在堆上开辟的空间
delete p1;
//在堆上开辟一个typename大小的空间,并初始化数据为10
int* p2 = new int(10);
//delete - 释放空间
delete p2;
//new typename[n] 在堆上开辟n个typename大小的空间存进p3
int* p3 = new int[10];
//delete[] - 释放这n个空间
delete[] p3;
//new typename[n]{初始化数据} 在堆上开辟n个typename大小的空间存进p4,并初始化为1,2,3,0,...,0
int* p4 = new int[10]{1,2,3};
delete[] p4;
//不写delete在程序结束后也会自动delete
int* p5 = new int;
}
其实对于内置类型,new delete和malloc free的差异真不大,主要差异在于自定义类型
new delete与自定义类型
下面我定义了一个ListNode类对new和delete分开讲解
new
class ListNode
{
public:
ListNode(int val = 0)
:_val(val)
,_next(nullptr)
{}
private:
int _val;
class ListNode* _next;
};
void new_test3()
{
ListNode* p1 = new ListNode(1);//创建一个结点
ListNode* p2 = new ListNode(2);//创建一个结点
ListNode* p3 = new ListNode(3);//创建一个结点
ListNode* p4 = new ListNode;//使用默认构造
}
p1 p2 p3每次new都自动以匿名构造的方式,自动调用了自定义类型的构造函数,这里需要注意的是,如果自定义类型没有默认构造,那么该自定义类型在new时必须加 () 进行给初始值,这里我的p4就没有给初始值,因为我有默认构造,缺省值是0,我如果把缺省值去掉,那么就没有默认构造,p4这里就会报错
对于new typename[n]
class ListNode
{
public:
ListNode(int val1 = 0, int val2 = 0)
:_val1(val1)
,_val2(val2)
,_next(nullptr)
{}
private:
int _val1;
int _val2;
class ListNode* _next;
};
class B
{
public:
B(int val)
:_val(val)
{}
private:
int _val;
};
void new_test3()
{
ListNode* p1 = new ListNode[4];
ListNode* p2 = new ListNode[4]{ {1, 2}, {2, 4} };//多参数的情况就中括号分隔
ListNode* p3 = new ListNode[4]{ 1, {2, 4} };//能这么做的前提是ListNode有默认构造,但是非常不推荐
ListNode* p4 = new ListNode[4]{ {1, 3}, {2 ,1}, {3, 2}, {5, 4} };
B* bb1 = new B[10];//err
B* bb2 = new B[10]{ 1, 2, 3 };//err
B* bb3 = new B[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//right
}
new typename[n]这种情况会把自定义类型的调用了n次,依次初始化(这里的底层实现后面会讲)
对于有默认构造的自定义类型,可以随便玩,给一部分初始值,不给初始值,都行,但自定义类型没有默认构造,初始值就需要给全
如果再想一层,如果自定义类型中的构造函数的参数类型是自定义类型的话,我们B[10]{1, 2, 3, 4…}就不太适合了
那么下例:
class A
{
public:
A(int a = 0, int b = 0)
:_a(a)
,_b(b)
{}
private:
int _a;
int _b;
};
class C
{
public:
C(A data)
:_data(data)
{}
private:
A _data;
};
void new_test()
{
C* C = new C[3]{ A(1, 2), A(2, 3), A(3, 4) };
}
这里初始化的时候我们就使用的是匿名构造的方式去进行初始化,但我们回想上例中的new B[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 这些初始值 1 2 3 4 5 … 其本质都会被编译器转换成B(1) B(2) B(3) … 去初始化,这里的A(1, 2)、A(2, 3)、A(3, 4)和B(1)、B(2)、B(3)只不过是我们显示处理和编译器隐式处理罢了
delete
class ListNode
{
public:
ListNode(int val = 0)
:_val(val)
,_next(nullptr)
{}
~ListNode()
{
cout << "析构" << endl;
}
//private:
int _val;
class ListNode* _next;
};
void new_test3()
{
class ListNode* p1 = new ListNode(1);
class ListNode* p2 = new ListNode(2);
class ListNode* p3 = new ListNode(3);
p1->_next = p2;
p2->_next = p3;
delete p1;
delete p2;
delete p3;
}
这里我们发现,在delete的时候除了释放空间,还自动调用了自定义类型的析构函数,有人会对这里产生疑惑,delete本来就会释放空间,为什么还去调用自定义类型的析构函数?
再举一个例子
class A
{
public:
A(int capacity = 10)
:_capacity(capacity)
,_size(0)
{
_arr = new int[capacity]{0};
}
~A()
{
_size = _capacity = 0;
delete[] _arr;
}
private:
int* _arr;
size_t _capacity;
size_t _size;
};
void new_test4()
{
A* a = new A(0);
delete a;
}
上面这个例子中A自定义类型的_arr变量在初始化时需要开辟堆空间,析构时需要释放,此种情况在new_test4()中的delete就能解决a中new的堆空间
和new typename[n]类似,delete[] 变量名 也是多次调用析构函数
operator new和operator delete函数
概念
new和delete是动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间
源码
operator new:
这里面有很多typedef过的类型,我们只需要看到其中的malloc(size),也大概知道了operator new其实就是对malloc进行了封装
malloc delete:
operator delete中有_free_dbg_函数,其实这个函数就是free()进行了define来的
我们如果直接使用operator new和operator delete
operator new / operator delete的效果和使用其实就是malloc和free
只不过operator new / operator delete是让malloc更加符合C++的习惯 —— 错误时抛异常(catch,抛异常的语法层面的知识以后讲解)
new和delete实现原理
前面我们知道了new的底层会调用operator new 和operator delete,但是如何证明呢?
证明
如何证明new 和 delete会去调用operator new和operator delete呢?看汇编
图中的汇编指令call 就是去调用函数的,这里就调用了operator new,同理operator delete也类似(在vs测试时,delete在调用operator delete的时候还经过了包装,可能需要call到其他函数中,由其他函数调用operator delete,这些底层的实现都不尽相同,但是其核心是会调用operator delete的),然后这里我们一步步走,会发现还会call A::A —— 也就是call A类中的构造函数
整体分析
new = operator new开空间 + 构造函数
delete = 析构函数资源释放 + operator delete释放空间
这里需要注意的是执行顺序不能反了,我们只有在空间开出来之后,才能对这段空间进行操作,赋初值,之后的delete也是需要先进行析构函数,先对这段堆空间进行资源释放,再operator delete释放这块堆空间
这里会有两个问题:问题一:如果是内置类型,也会有构造函数和析构函数吗,C++中对内置类型也有构造和析构函数?
内置类型没有构造、析构,只有在针对自定义类型的时候才会调用构造、析构,而面对内置类型,只会调用operator new operator delete,但是我们在C++中是支持 int() 这种写法的,所以我们可以理解为内置类型()也是一种针对内置类型的构造
delete中析构的资源释放和operator释放空间怎么理解?这个顺序可以反过来吗
先调用析构进行资源释放指的是,对类对象在构造函数中或者是在对类进行操作中动态空间开辟出来的空间进行释放,而operator delete是为了清理图中p这整块空间的,顺序上,只有先析构把这些类中new出来的空间释放后,再释放整块空间,才能保证不会有内存泄漏。
面试题
malloc/free和new/delete的区别?
1、malloc和free是函数放在stdlib.h中,new和delete是操作符
2、malloc申请的空间不会初始化,new可以初始化
3、malloc申请空间时,参数需要手动计算空间大小进行传参,new只需要在后面更上空间的类型,如果是多个对象,[]中指定对象个数即可
4、malloc的返回值是void*,在使用时必须强转,new不需要,因为new后跟的是空间的类型
5、malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
6、申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数和析构函数,而new在申请空间后会自动调用构造函数完成对象的初始化,delete释放空间前会调用析构函数完成对空间中资源的清理