目录
operator new 与 operator delete
简单回顾C语言动态内存管理
在C语言的学习阶段
我们接触到了三个能在堆上开辟空间的函数——malloc、calloc、realloc
其中,malloc是开辟空间但是不初始化,calloc是开辟空间并且会将空间内容都初始化为0,而realloc则主要用于扩容,如果传入的指针为空,则效果和malloc一致
realloc又分为原地扩容和异地扩容
原地扩容就是扩容后的空间直接在原空间上往后延申,但有一个前提,就是在后扩展这段空间里面不能存在数据阻挡,否则就会变成异地扩
异地扩容就是原空间后面有被使用的空间,导致无法直接原地扩容,就需要另外开辟一块完整空间,最后再将数据拷贝到新空间,原空间销毁
但是在C++就不一样了,C++中的new会更为简洁,各位看官且往下看
new、delete的用法
内置类型
new
我们在C++如果要使用C语言中的malloc、calloc、realloc也是可以的,因为C++兼容C语言
但是日常没有人会这么用,因为有更好的选择——new
我们在C++中如果想要动态开辟一个空间,可以选择直接new,如下:
int main()
{
//动态开辟一块空间
int* ptr = new int;
return 0;
}
如果我们要连续开辟一段空间呢?比如开辟10个,如下:
int main()
{
//动态开辟一块空间
int* ptr1 = new int;
//动态开辟一段空间
int* ptr2 = new int[10];
return 0;
}
那如果我想要开辟了空间之后进行初始化呢?也是可以的,我们先来看单单开辟了一个空间的情况:
int main()
{
//动态开辟一块空间并初始化
int* ptr1 = new int(5);
return 0;
}
接着我们再来看一看一段空间的情况:
int main()
{
//动态开辟一段空间并初始化
int* ptr2 = new int[10] {1, 2, 3, 4, 5, 6};
return 0;
}
如上,我们会看到我们开辟了一段空间,如果要初始化的话,后面就需要跟上花括号{},而如上代码我们只输入了6个数字,这也就代表着我们对应的初始化了前六个,而后面剩下的四个则会默认初始化为0
delete
同样的,C语言中清除空间有free,在C++中对应有delete,如下展示几个例子:
int main()
{
//动态开辟一块空间
int* ptr1 = new int;
delete ptr1;
//动态开辟一块空间并初始化
int* ptr2 = new int(5);
delete ptr2;
//动态开辟一段空间
int* ptr3 = new int[10];
delete[] ptr3;
//动态开辟一段空间并初始化
int* ptr4 = new int[10] {1, 2, 3, 4, 5, 6};
delete[] ptr4;
return 0;
}
如上我们可以看到,C++中的delete一共有俩种用法——delete、delete[]
当只有单独一个空间的时候,就使用delete
当有一段空间的时候,就使用delete[]
当然这也比较好记,因为这也和new一一对应
如上都是针对内置类型的情况,但其实祖师爷搞出这块儿主要针对的是自定义类型
自定义类型
我们先来写一个类,在该类的构造函数和析构函数里面分别打印一些内容方便我们检查结果
struct A
{
A()
{
cout << "A()" << " ";
}
~A()
{
cout << "~A()" << " " ;
}
};
然后我们再使用我们的new:
int main()
{
A* a1 = new A;
delete a1;
cout << endl << endl;
A* a2 = new A[10];
cout << endl;
delete[] a2;
return 0;
}
我们可以看到,当我们使用了new来开辟一块A类型的空间的时候,他会在开辟空间的同时,还会调用对应的构造函数
如果是开辟了一段空间比如10个的话,就会连续调用10次构造函数并开辟对应大小的空间
而我们的delete就相反,他会调用对应的析构,如果是连续的就连续调用多次析构并销毁相应空间
new、delete底层讲解(重要)
operator new 与 operator delete
operator new 和 operator delete对标的是malloc和free
也就是说,这个就是开辟空间的,如果我们去看源码的话,我们会发现,operator new 其实就是对malloc的一个封装,operator new 的底层就是malloc,operator delete同理
但是好好的为什么要对 malloc 封装呢?直接用 malloc 不好吗?
各位回想一下,我们每次在使用完malloc之后,都需要检查一下空间有没有开辟成功,如果没有开辟成功,我们还需要进行返回,而且每写一个就需要检查一下,写10个malloc就需要对应写10个检查,这样子写得确实有点恼火
而在C语言种检查错误用的是错误码,是errno
但是C++使用了一种给更好的方法——异常(这个知识太超纲了,后面会重点学,这里知道即可)
所以本质上,operator new 就是对 malloc 和异常进行了一个封装,这也就使得C++中的new变得异常简洁,因为我们不需要检查,直接使用new即可,new里面有operator new,operator new 有malloc 和异常,所以我们不需要检查
但是我们上面又看到,开辟一个自定义类型的时候,还会调用其构造函数
综上,我们可以 得出一个结果:
new = operator new + 构造函数
operator new = malloc + 异常(错误检查)
相对的,delete也是大差不差,但是operator delete可没有异常,祖师爷在这块设计成这样单纯是为了对称,所以:
delete = operator delete+ 析构函数
operator delete = free
同理,再来看看 new[],其本质就是多次调用operator new和构造,仅此而已,delete[] 同理
也就是在内部会多开四个字节用来记录连续开了几块空间,再根据这四个字节里面的内容,来决定到底要调用几次构造,几次析构,简图如以下(假设连续开辟10个空间):
定位 new
一般情况下,我们会使用new,而new会自动调用构造函数
但是我们会遇到一个情况,这个情况出现在内存池中:我们如果频繁地申请空间,会使效率变低,所以就有了内存池,要申请先申请一大块空间,需要申请就到内存池中申请,内存池空间不够了再到堆里面去
但是,我们如果要在内存池中申请空间的话,就不能使用new,因为new默认会调用堆上面的空间,所以我们只能使用operator new,但是我们这时候需要在类外面调用构造,可是析构能在类外面显式调用,但是构造不行,所以就有了定位new的产生
定位new的语法有点子奇怪:
new (place_address) type或者new (place_address) type(initializer-list)
举个例子:
A* ptr = (A*)operator new(sizeof(A));
new(ptr)A(10); //定位new
ptr->~A();
operator delete(ptr);
如上这是只申请一块空间的情况,但如果是operator new[]的话,就应该写一个for循环,循环的次数就是需要调用构造函数的次数
A* ptr = (A*)operator new(sizeof(A) * 10);
for(int i = 0; i < 10; i++)
new(ptr+i)A(5); //定位new
for(int i = 0; i < 10; i++)
(ptr+i)->~A();
operator delete[](ptr);
结语
这篇博客详细讲解了C++中的动态内存管理,如果觉得对你有帮助的话,希望可以多多支持!!❀