- new / delet e动态管理对象。
- new [] / delet e[]动态管理对象数组。
这里要强调一点:malloc/free、new/delete、new[ ]/delete[ ]一定要匹配使用。为什么呢?我们用下面的几个例子一步步来说明一下
例一:
int*p1 = new int;
int*p2 = new int(3);
int*p3 = new int[10];
delete []p1;
delete p3;
这里我们并没有匹配使用 new/delete、new[ ]/delete[ ],但是由运行结果来看,好像没有什么问题;
接下来再举一个例子-->
例二:
class AA
{
public:
AA()
{
cout << "AA()" << endl;
}
AA(const AA&aa)
{
cout << "AA(const AA&aa)" << endl;
}
~AA()
{
cout << "~AA()" << endl;
}
private:
int _aa;
};
void test2()
{
AA*p1 = (AA*)malloc(sizeof(AA));
AA*p2 = new AA;
//free(p1);
//delete p2;
}
int main()
{
test2();
return 0;
}
会发现它调用了构造函数,这里经过调试,我们可以发现:是AA*p2=new AA调用了构造函数。
如果把代码稍作改动(构造函数部分)-->
例二(1):
AA(int aa)
:_aa(aa)
{
cout << "AA()" << endl;
}
同理,delete会调用析构函数:
void test2()
{
AA*p1 = (AA*)malloc(sizeof(AA));
AA*p2 = new AA(10);
free(p1);
delete p2;
}
接下来我们调换一下顺序:malloc开出的空间用delete释放、new开出的空间用free释放;
看似好像也没有问题,但是这里要注意上图第2中情况(new/free)很危险,请看下面-->
例二(2):
class AA
{
public:
AA(int aa)
:_aa(aa)
{
cout << "AA()" << endl;
p = 开空间;
}
AA(const AA&aa)
{
cout << "AA(const AA&aa)" << endl;
}
~AA()
{
cout << "~AA()" << endl;
释放p所指向的空间
}
private:
int _aa;
int*p;//类的私有成员中定义了一个指针
};
void test2()
{
AA*p2 = new AA(10);
free(p2);
}
这里就会出现问题,为什么呢?
原因就在于:在类的私有成员中我们定义了一个指针,在构造函数中我们为其开辟了一块空间,相应地,在析构函数中释放p所指向的这块空间;但是这里test2()函数中,new没有与delete匹配使用,但是用了free.有前面的讲解我们得知new会调用构造函数,而delete会调用析构函数。重点来了,问题就出在这--->new调用了构造函数,在构造函数中已经给p开了空间(p=开空间),但是free并不会调用析构函数,所以p开出的空间没有被释放掉,这就导致了一个很严重的问题:内存泄露。
接着看:还是上面的代码(构造函数改为以下,test2函数改为以下)
例二(3):
AA(int aa=0)
:_aa(aa)
{
cout << "AA()" << endl;
}
结果:两种情况程序都崩溃。
再来看下面一段代码-->
例三:
class Array
{
public:
Array(size_t size = 10)
: _size(size)
, _a(0)
{
cout << "Array(size_t size)" << endl;
if (_size > 0)
{
_a = new int[size];
}
}
~Array()
{
cout << "~Array()" << endl;
if (_a)
{
delete[] _a;
_a = 0;
_size = 0;
}
}
private:
int*_a;
size_t _size;
};
void Test()
{
Array* p1 = (Array*)malloc(sizeof (Array));
free(p1);
}
这里我们用了malloc/free,但是会出现一个问题:malloc没有调用构造函数进行初始化,导致类中的_a指针就会是随机值,也就是所谓的“野指针”,free(p1)没有调析构函数,也就没有清理掉里边包含的那段空间,这个时候就会出现“内存泄露”。
综上:尽量使用new/delete
---------------------------------------------------------------------------------------------------------
1.全局变量、全局静态变量、局部静态变量、局部变量之间的区别是什么?
-----------------------------------------------------------------------------------------------------------------------
下面这幅图帮助我们对变量的存储区域有更深刻的理解:
--------------------------------------------------------------------------------------------------------------
再来看一个问题:
void * operator new (s ize_ t s ize);
void operator delete (s ize_ t s ize);
void * operator new [](s ize_ t s ize);
void * operator delet e[] (s ize_ t s ize);
乍一眼看,很像运算符的重载,其实并不然(这里我们可以试验一下)
AA*p5 = (AA*) new(sizeof(AA));
再来看一点malloc和new的不同之处:
void test2()
{
AA*p5 = (AA*) operator new[](sizeof(AA)*1000000000);
//AA*p6 = (AA*) malloc(sizeof(AA)* 1000000000);
}
很明显,我们的目的是:让它们分配内存失败,然后观察malloc与new分配内存失败后二者的处理方式
调出监视窗口进行调试,结果如下--->
可见,malloc分配空间失败会返回一个空指针,而new分配空间失败会“抛异常”。
捕获异常-->
int main()
{
try
{
test2();
}
catch (exception&e)
{
cout << e.what() << endl;
}
system("pause");
return 0;
}
再来看下面一段代码:(我们详细分析一下函数的调用步骤)-->
例四:
void test3()
{
AA*p1 = new AA;
delete p1;
}
详解如图:
回到我们之前所讲的例子:malloc/delete、new/free混合用为什么都没有出问题呢?因为前面我们所举例的类型都是int,属于内置类型;对于内置类型而言,没有构造函数、没有析构函数,那么无论调new还是malloc都没有区别;why?调用malloc就相当于直接去开辟空间,new相当于是--->new会调用operator new、operator new里面又去调用malloc。也就是说对于内置类型而言,如果是new出来的空间,调用malloc去释放和调用delete去释放是一样的,最终都会调用free;
但是对于自定义类型就不同了;如果用malloc开辟了空间,free去释放,就不会调用析构函数,没调用析构函数可能就会出现“内存泄露”。
再来看下面一个例子-->
例五:
class AA
{
public:
AA(int aa=0)
:_aa(aa)
{
cout << "AA()" << endl;
}
AA(const AA&aa)
{
cout << "AA(const AA&aa)" << endl;
}
~AA()
{
cout << "~AA()" << endl;
}
private:
int _aa;
};
void test3()
{
AA*p2 = new AA[10];
delete p2;
}
运行会崩溃,为什么呢?这里就涉及到了一个重点;按我们之前所想:这里应该是开辟了40个空间,事实上呢?并不是这样,事实上开辟了44个空间
- 问题(1):为什么会开44个空间呢?多开出来的4个空间其实是存放对象个数
- 问题(2):为什么要存对象的个数?原因就在于析构要去做一件事:它首先会去调用析构函数,其次会调用operator delete[ ](),调用operator delete[ ]()的时候p的地址是0x003c51e8。申请空间时多开了4个字节在头上,然后释放的时候也会减去4个字节(到0x003c51e8),传给operator delete,operator delete再传给free。
正确的书写方式--->
AA*p2 = new AA[10];
delete []p2;
详解调用析构函数的过程:
错误的写法(1):(程序会崩溃)
AA*p2 = new AA[10];
free(p2);
这里讲一下崩溃的原因(如下图所示)最初开辟空间的时候多开了4个字节,在图中蓝色箭头;而不delete[ ]p2直接free的话就会从下面(图中黑色箭头所指位置)开始释放空间;这样就出现了问题:开辟了一整块空间,最后却释放了部分。
错误的写法(2):(程序也会崩溃)
AA*p2 = new AA[10];
delete p2;
原因:与写法(1)相同。都是只释放了部分空间。
--------------------------------------------------------------------------------------------------------------------------
为什么delete【】p2就不会有问题呢?
原因就在于:它除了要释放空间,还要先调用析构函数,有多少个对象就会调用多少次析构函数;但是只有一个指针,编译器并不知道有多少个对象,所以这里多开辟的4个字节(指针指向)就是方便能找到对象的个数,找到个数再去调用析构函数,再把指针减去4个字节;然后调用operator delete[ ] 去释放这段空间
还有一个问题:为什么delete p2就会崩溃?
答:只有operator delete[ ] 才会去找那4个字节,why?只有operator delete[ ] 出来的时候,它才会多开辟4个字节存放内存个数;而delete p2不需要存个数,因为它永远只有一个。所以奔溃的原因是因为指针释放的位置不对。
最后一个问题(一):
int *p1 = new int[10];
delete p1;
这里为什么就不会崩溃?开辟空间开了多少个?不是44个?
原因:注意-->这不是自定义类型,自定义类型要调用构造和析构函数,而这是int(内置类型),不需要调用构造和析构函数;注意-->只有调用析构函数才会知道要调用多少次(由对象个数确定),才多开4个字节把对象的个数存下来;对于基本类型直接free就可以,压根不用多开4个字节。
问题(二)所有的内置类型都会这样吗?这里开辟了多少个字节?
class BB
{
private:
int _bb;
};
int main()
{
BB*p1 = new BB[10];
return 0;
}
其实本来应该开44个,但是这里编译器优化了,本来再开4个存放个数就会知道究竟调用多少次析构函数,但是这里没有显式地写析构函数,没有显式写的话编辑器就认为它是缺省的,缺省的析构函数可以认为什么都不做。
显式地写上析构函数:
public:
~BB()
{
}