之前学习了C语言中的动态内存管理:C语言中动态内存管理
我们这里简单复习一下:
C语言中动态内存管理的函数:malloc,calloc,realloc,free
int main()
{
int* p1 = (int*)malloc(sizeof(int) * 4);
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p1, sizeof(int) * 10);
free(p2);
free(p3);
}
这里我们可以看出,C语言中开辟动态资源,需要类型强转,需要写入开辟具体的字节数
其实还不是很方便
所以C++内存管理解决了这个问题
C++兼容C,所以malloc,calloc,realloc,free在C++中可以继续使用
C++提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理
int
类型空间:int* p1 = new int;
这里可以看出C++中开辟一个int类型,就直接在new后面写上int类型就可以,在C中,我们需要写入Int类型的大小
这里没有进行初始化,所以开辟出的空间里的是随机值
int
类型空间,并且初始化为3:int* p2 = new int(3);
C++中,new的同时是可以初始化的,这是C语言中动态内存开辟中不具备的能力
int
类型空间:int* p3 = new int[3];
这里没有进行初始化,所以开辟出的空间里的是随机值
int
类型空间,并且初始化:int* p4 = new int[5] {1, 2, 3, 4, 5};
delete p1;
delete p2;
delete[] p3;
delete[] p4;
申请和释放单个元素的空间,使用
new
和delete
操作符
申请和释放连续的空间,使用new[]
和delete[]
操作符
new
和delete
配合,malloc
与free
配合使用,不要交叉混合使用
在申请自定义类型空间时,new
会自动调用构造函数,delete
会自动调用析构函数,而malloc
与free
不会
这也是new/delete和malloc/free最大的区别
现在有如下的类:
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
用malloc和free管理一个自定义对象
int main()
{
A* pa1 = (A*)malloc(sizeof(A));
free(pa1);
return 0;
}
可以看到没有任何输出,也就是不会自动调用构造函数和析构函数
接着用new/delete管理一个自定义对象
int main()
{
A* pa1 = (A*)malloc(sizeof(A));
free(pa1);
A* pa2 = new A;
delete pa2;
return 0;
}
可以看到,结果有输出内容,也就说明new可以调用构造函数,delete会调用析构函数
因为有默认构造函数,所以A* pa2 = new A;
这里自动按照默认构造函数赋值
如果没有默认构造函数,A* pa2 = new A;
这样的写法会报错的
这时就需要传值
A* pa2 = new A(1);
开辟连续空间的自定义对象:
A* pa3 = new A[3]{ 1,2,3 };
上面这种写法会涉及到隐式类型转换,把int类型转成自定义类型
也可以使用匿名对象来进行初始化:
A* pa3 = new A[3]{ A(1),A(2),A(3) };
对于A* pa3 = new A[3]{ A(1),A(2),A(3) };
这样开辟连续空间时,也会多次调用构造函数
同理delete[] pa3
也会多次调用析构函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间
面向对象的语言处理失败,不喜欢用返回值,喜欢用抛出异常
所以malloc申请空间失败返回NULL,而new申请失败是抛出异常
operator new 对malloc进行封装,如果失败抛异常
所以operator new不是直接给我们用的,而是给new用的
同理operator delete是对free函数进行封装,同时如果失败抛出异常,operator delete 不是给我们用的,而是给delete用的
下面是operator new和operator delete函数的源码,的确可以看到对malloc和free的封装,以及检测异常的部分:
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
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
//operator delete: 该函数最终是通过free来释放空间的
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
new的原理:先调用operator new函数申请空间,再在申请的空间上执行构造函数,完成对对象的初始化
delete的原理:先调用析构函数,完成对对象中资源的清理工作,再调用operator delete函数
这里需要注意的是:new是先开空间再构造,而delete是先析构再释放空间,不要搞混
下面我们通过一个Stack类能够更好的理解:
class Stack
{
public:
Stack(int capacity = 4, int top = 0)
:_capacity(capacity)
, _top(top)
{
_a = new int[capacity];
}
~Stack()
{
delete[] _a;
}
private:
int* _a;
int _capacity;
int _top;
};
在运行Stack* pst = new Stack
语句时,只能先开辟一段空间,然后再调用构造函数malloc出一段空间给_a
接着在运行delete[] pst
时,必须先调用析构函数。这里如果先释放空间,则是先释放在堆区中对象的空间,那么_a
指向的空间没有释放,这里造成内存泄露
所以必须先用析构,将数组空间释放,再释放对象整体
其实除了operator new和 operator delete外,还有operator new[]和operator delete[]函数,这两个是用来开辟和释放连续空间的对象
new T[N]:调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的开辟,然后再执行N次构造函数
delete[]:先调用N次析构函数,完成对象中资源的清理,再调用operator delete[]释放空间,operator delete[]中是调用N次operator delete来释放空间