文章目录
- new 和 delete
- 对于内置类型
- 对于自定类型
- operator new 和 operator delete
- new 可以抛异常
- new[] 和 delete[]
- (补充)定位new
- 总结
以下测试都是在 VS2019环境下测试。
new 和 delete
对于内置类型
在C语言中,我们动态开辟内存用的是 m a l l o c malloc malloc 和 f r e e free free,而在C++中,则是 n e w new new 和 d e l e t e delete delete。
int main()
{
int* p1 = new int; // malloc
int* p2 = new int[10]{ 0 };
delete p1;
delete [] p2;
return 0;
}
在C++中,我们都用 n e w new new 来开辟空间,语法格式如下:
n e w new new + 类型 (对象个数) {初始化}
释放空间使用 d e l e t e delete delete 。对于内置类型来说本质上和 m a l l o c malloc malloc 和 f r e e free free 是没有区别的。
如上图所示:对于这两个来说,本质都是去调用
m
a
l
l
o
c
malloc
malloc 和
f
r
e
e
free
free。
对于自定类型
class A
{
private:
int _aa = 0;
public:
void Print()
{
cout << "void Print()" << endl;
}
A(int _x = 0)
:_aa(_x)
{
cout << "A()" << endl;
};
~A()
{
cout << "~A()" << endl;
};
A(const A& AA)
{
_aa = AA._aa;
}
};
对于自定义类型来说我们的 n e w new new 或去调用默认的构造函数, d e l e t e delete delete 会去调用析构函数。这就是 n e w new new 和 d e l e t e delete delete 和 C语言中 m a l l o c malloc malloc 和 f r e e free free 的区别。
operator new 和 operator delete
这两个函数是干什么的呢?
operator new 约等于 malloc
operator delete 与等于 free
对于
n
e
w
new
new 来说,他会先去调用 operator new 来开辟空间,然后再调用自定义类型的构造函数。
而对于 d e l e t e delete delete 来说,它先去调用 自定义类型的析构,然后再去调用 operator delete。
new 可以抛异常
在C语言中,我们 每次使用
m
a
l
l
o
c
malloc
malloc都要去检查返回值,如果开辟失败,
m
a
l
l
o
c
malloc
malloc 返回
N
U
L
L
NULL
NULL,而在C++中则是抛异常。以下是一个简单示例:
所以
n
e
w
new
new 我们就不需要像C语言那样每次使用
m
a
l
l
o
c
malloc
malloc 都要去检查返回值,如上图,当我们遇到异常(空间开辟异常)直接抛异常,不会执行下面的代码,但这里会出现一个问题,那就是前面开辟的并没有释放,存在内存泄漏(以后学的更加深入了再来解决…)。
这里只需要记住:
n
e
w
new
new = 先
o
p
e
r
a
t
o
r
operator
operator
n
e
w
new
new + 再 构造
d
e
l
e
t
e
delete
delete = 先 析构 + 再
o
p
e
r
a
t
o
r
operator
operator
d
e
l
e
t
e
delete
delete
new[] 和 delete[]
这里的 n e w [ ] new[] new[] 是依次开辟 n n n个对象,每个对象都去调用构造函数,同时 d e l e t e [ ] delete[] delete[] 每个对象都要去调用 析构函数。
同时 这些方法最好是配套使用:
例如: 用了
n
e
w
new
new 了 最好用
d
e
l
e
t
e
delete
delete
用了
n
e
w
p
[
]
newp[]
newp[] 最好用
d
e
l
e
t
e
[
]
delete[]
delete[]。尤其是这种。
这里我们的A对象只有一个成员变量,根据以往的知识直到我们类的大小计算时,成员函数等是不计算的。所以我们开辟10个应该只需要40个字节啊?可是他这里为什么给了44个呢?
而这时,当我们把析构函数的显示定义关了之后,有只有40个字节了。
直接说结论:对于自定义类型来说,我们使用
n
e
w
[
]
new[]
new[]开辟空间之后会默认再多开辟一个4个字节的空间,存储我们后续需要析构的次数。就像这样一样:
我们会在p2的前面再开辟一个空间,来存储对象的数量。这个时候我们就必须要调用
d
e
l
e
t
e
[
]
delete[]
delete[] 来释放空间了。如果只用
d
e
l
e
t
e
delete
delete 来释放空间。编译器只会释放 p2 往后,那么前面那个空间就没有 释放掉,就会报错。这也是为什么我们要强调最好配套使用。
当我们需要析构(显示定义析构)的时候,编译器会调整位置到前面那个存储析构次数的位置上,然后再调用
o
p
e
r
a
t
o
r
operator
operator
d
e
l
e
t
e
[
]
delete[]
delete[] 来释放空间。
同时,
o
p
e
r
a
t
o
r
operator
operator
d
e
l
e
t
e
[
]
delete[]
delete[] 本质上是调用
o
p
e
r
a
t
o
r
operator
operator
d
e
l
e
t
e
delete
delete ,
o
p
e
r
a
t
o
r
operator
operator
d
e
l
e
t
e
delete
delete 又是去调用的
f
r
e
e
free
free。
(补充)定位new
对于自定义类型而言,我们析构函数可以显示调用,但是构造函数却不可以。
这里的 operator new 只是单独开辟一块空间,并不进行任何其他活动。(就是malloc)。
那对于我们 operator new 开辟的空间(自定义类型)如何调用构造函数呢?
语法格式:
n e w new new + (地址) + 类型名 + {初始化列表}。
地址就像上图的 p2,初始化列表就是我们构造的形式(单参数,多参数)。
再来看一下多参数:
开辟多个对象:
总结
对于我们使用 n e w new new 或 d e l e t e delete delete的时候我们直接配套使用就可以了,最好不要混着使用。
当我们使用
n
e
w
new
new
[
]
[]
[] 开辟空间但我们却使用
f
r
e
e
free
free 释放空间的时候,我们并没有正确的释放空间(我们并没有调整到存储析构次数空间的那个位置)。所以运行报错。
如下图:此时我们又可以运行了,因为此时我们并不有显示定义,所以前面不会开辟那个空间存储析构次数,所以运行并没有报错,但是这只是因为我们类 A 里面并没有资源空间,如果我们有资源空间但是我们不显示定义析构的时候还是会出问题(同一片空间析构两次问题(栈:两个栈拷贝构造浅拷贝指向同一份空间,析构的也是同一份空间,free一块已经free得空间))。
所以:在使用的时候我们要配套使用