对于动态内存的管理,可以使用malloc和free申请和释放空间, 并且对于内置类型它是有能力初始化的,那如果是自定义类型该怎么办,一般成员变量是私有,在外面不能轻易访问,这种方式仅仅只能做到开辟空间而不能初始化。
1.new的用法
当我们希望一块申请的空间不会随意被销毁时,用new来申请空间,new是在堆上开辟的空间,故不会被随意销毁。
new 类型 即可申请一个该类型大小的空间
int *p1 = new int;
int *p2 = new int(0);
在实际应用中,大部分情况都希望申请多个空间,申请多个空间的方式是:
new 类型[类型数量]
int *p3 = new int[5];
与单个类型的初始化不同,这里想要初始化就不是一个括号,而是需借助{}
new 类型[类型数量]{初始化内容}
int *p4 = new int[5]{1, 2, 3 , 4, 5};
当new的初始化是不完全初始化,比如这里的int类型,后面的数据都会初始化为0
class A
{
public:
A(int a=0)
{
cout << "A(int a=0)" << endl;
_a = a;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void Test2()
{
A *p1 = (A*)malloc(sizeof(A) * 5);
A a(0);
A *p2 = new A[5]{a};
}
通过调试可以看到,p2指向的五个成员的确是被初始化成了a
考虑到A类只有_a一个成员变量,完成初始化都无需创建a这个对象,直接拿0初始化结果一样
A *p2 = new A[5]{0};
之所以能直接拿0初始化,其实是编译器完成了隐式类型转换,先将0转换为A类,然后在拿这个A类去初始化p2里面的成员。
这样看,new好像仅仅是在堆上申请了空间,事实上new除了开辟空间它还做了一件事情,调用构造函数。
为了方便观察,每调用一次构造函数就打印出来
那这就好了,既然可以调用构造函数,不管自定义类型有多少个成员变量,统统都可以在构造函数里面初始化。
2.delete的用法
有申请空间就一定有释放空间,有new就一定有delete,delete和new的关系就和free和malloc的关系是一样的,都是捆绑使用。
不同的是,delete释放空间分为两种情况,首先是只new了一个类型的大小,直接delete即可
而new开辟了一种类型的多个对象空间,需要加上[]
void Test4()
{
A *p1 = new A(0);
A *p2 = new A[5]{0};
//...
delete p1;
delete[] p2;
}
delete的作用不仅限于释放空间,new会调用构造函数,同理,delete就会去调用析构函数,而且是先调用析构函数,然后在释放空间
3.new/delete和malloc/free的区别
在申请自定义类型的空间时,new/delete除了能够申请/释放空间,还能调用对应的构造函数/析构函数,而malloc/free不可以。
处理申请内存失败情况的方式有所差异,malloc申请内存失败时会返回一个空指针,而new申请内存失败则是抛异常。因此使用malloc避免不了判断空指针,使用new避免不了捕获异常。
还有就是之前提到的自定义类型初始化的问题,malloc无法做到,而new很好的解决了自定义类型初始化问题。
但其实这些都不是malloc/free和new/delete最本质的区别,他们最本质的区别是,malloc/free是函数,而new/delete是操作符。