在说今天的主题前我们先来回顾 C语言中的动态内存管理。
在C语言,使用的是malloc/calloc/realloc/free在堆上动态的开辟或修改一块连续的空间。我们先来回顾一下这些函数的区别。
(1)malloc:void *malloc( size_t size );
malloc函数在使用时,只需要传递一个参数size,那就是将要分配内存的大小,一般需用sizeof(type)*num手动计算。有时malloc所申请的空间可能会比实际所申请的空间要大,要用来存放维护信息等。
(2)calloc: void *calloc( size_t num, size_t size );
calloc与malloc都是在堆上申请一块连续的空间,不过calloc申请的空间都已初始化,并且需要传递两个参数,所以,calloc是在堆上申请num个大小为size的连续空间。
(3)realloc: void *realloc( void *memblock, size_tsize);
memblock函数有两个参数,memblock是所要修改空间的地址,size是重新申请的地址长度。
如果memblock不为NULL,则会对已分配好的内存进行修改,如果所要修改的内存过大,realloc会重新申请一块空间,并将内容完全复制过来,返回新空间的首地址,并将原有空间进行销毁。
如果memblock为NULL,则分配行为与malloc相同。
了解了上述内容,我们来看看关于c++的动态内存管理是怎么回事呢?
c++是通过new和delete操作符实现的。
new/delete动态管理对象。
new[]/delete[]动态管理对象数组。
特别注意new和delete它不是函数,而是操作符!
那么接下来我们来看看具体的实例吧
void Test ()
{
int* p4 = new int; // 动态分配4个字节(1个 int)的空间单个数据
int* p5 = new int(3); // 动态分配4个字节(1个 int)的空间并初始化为3
int* p6 = new int[3]; // 动态分配12个字节(3个 int)的空间
delete p4 ;
delete p5 ;
delete[] p6 ;
}
特别注意:new/delete,new[]/delete[]一定要匹配使用!!!
接着我们先来看一段代码
#include <iostream>
using namespace std;
class p1
{
public:
p1(int a=0)
:_a(a)
{
cout << "p1(int a)" << endl;
}
~p1()
{
cout << "~p1()" << endl;
}
void display()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
p1* p = new p1(10);//创建类
p->display();
delete p;
return 0;
}
代码运行成功后的结果如图所示:
这就说明了new在创建一个类时,会默认调用构造函数使类初始化。
而我们的delete是先调用析构函数,然后在进行释放。
在动态内存管理中
new做了两件事:
1.调用operator new分配空间。
2.调用构造函数初始化对象。
delete也做了两件事:
1.调用析构函数清理对象
2.调用operator delete释放空间
new[N]做了两件事:
1.调用operator new分配空间。
2.调用N次构造函数分别初始化每个对象。
delete[]也做了两件事:
1.调用N次析构函数清理对象。
2.调用operator delete释放空间。
总结:
1.operator new/operator delete ,operator new[]/operator delete[] 和 malloc/free用法一样。
2.他们只负责分配空间/释放空间,不会调用对象构造函数/析构函数来初始化/清理对象。
3.实际operator new和operator delete只是malloc和free的一层封装。
知道了c++的动态内存的基本实现方法,我们来看看malloc/free与new/delete的有什么区别与联系?
(1). 它们都是动态管理内存的接口。
(2). malloc/free是C/C++标准库的函数,new/delete是C++操作符。
(3). malloc/free只是动态分配内存空间/释放空间。new/delete除了分配空间还会调构造函数和析构函数进初始化与清理(清理成员)。
(4). malloc/free需要手动计算类型并且返回值是void*,new/delete可以自己计算类型的大小,并返回对应类型的指针。
(5).malloc失败会返回0,new失败会抛出异常。
接下来我们来看看定位new表达式
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象,用法如下
new (place_address) type
new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表。
接着我们来看它的具体实现吧:
#include <iostream>
#include<cstdlib>
using namespace std;
class AA
{
public:
AA()
{
cout << "AA()" << endl;
}
~AA()
{
cout << "~AA()" << endl;
}
void display()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
AA* p1 = new AA;
AA* p2 = (AA*)malloc(sizeof(AA));//开辟空间
new(p2)AA;//已分配的原始内存空间中调用构造函数初始化一个对象
p2->display();
delete p1;
p2->~AA();
//析构函数的显式调用,构造函数不能显示调用,一般不建议使用显示调用
free(p2);
}
代码运行成功后的结果是:
由_a的输出结果是0我们可以知道定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
接着我们来通过定位new表达式来模拟实现new[]和delete[]
#include <iostream>
#include<cstdlib>
using namespace std;
class AA
{
public:
AA()
:_a(0)
{
cout << "AA()" << endl;
}
~AA()
{
cout << "~AA()" << endl;
}
void display()
{
cout << _a << endl;
}
private:
int _a;
};
#define NEW_ARRAY(ptr,type,n)\
do \
{ \
ptr = (type*)operator new(sizeof(type)*n + 4); \
*(int*)ptr = n; \
ptr = (type*)((char*)ptr + 4); \
for (size_t i = 0; i < n; ++i)\
{ \
new(ptr + i)type; \
} \
} while (false);
#define DELETE_ARRAY(PTR,TYPE) \
do{ \
int N = *((int*)PTR - 1); \
for (int i = 0; i < N; ++i) \
PTR[i].~TYPE(); \
PTR =(TYPE*)((char*)PTR - 4); \
operator delete(PTR); \
}while (false);
int main()
{
AA*p2;
NEW_ARRAY(p2, AA, 10);
DELETE_ARRAY(p2, AA);
system("pause");
return 0;
}
代码运行成功后的结果如图: