re动态内存分配.ppt
动态内存分配,程序运行时动态内存分配(dynamic memory allocation)先复习概念与方法、堆对象与构造函数 然后进一步讨论拷贝构造函数.,堆内存分配,通常编译器在编译时都可以根据变量(或对象)的类型知道所需内存空间的大小,从而在适当的时候为他们分配确定的存储空间。这种内存分配称为静态存储分配。有些操作对象只有在程序运行时才能确定,系统根据运行时的要求进行内存分配,这种方法称为动态存储分配。,1.堆内存的分配与释放,根据申请动态分配,用后显式释放所空间,使系统能做到重复使用有限的资源。,格式如下:指针变量名=new 类型名(初始化式);delete 指针变量名;new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而动态创建的对象本身没有名字。,堆区不会自动在分配时做初始化(包括清零),所以必须用初始化操作(initializer)来显式初始化。,从堆区分配对象时,new表达式调用库操作符new()。例如:int*pi=new int(0);/对象未命名它与下列代码序列大体等价:int what=0;int*pi=,0,pi,堆,i,演示:用初始化式(initializer)来显式初始化 int*pi=new int(0);当pi生命周期结束时,必须释放pi所指向的目标:delete pi;注意这时释放了pi所指的目标的内存空间,也就是撤销了该目标,称动态内存释放(dynamic memory deallocation),但指针pi本身并没有撤销,该指针所占内存空间并未释放。,对于数组进行动态分配的格式为:指针变量名=new 类型名下标表达式;释放:Delete 指向该数组的指针变量名;,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的指针,会产生回收不彻底的问题(只回收了第一个元素所占空间),加了方括号后就转化为指向数组的指针,回收整个数组。,#include void main()int n;char*pc;coutn;/在运行时确定,可输入17pc=new charn;strcpy(pc,堆内存的动态分配);coutpcendl;delete pc;/释放pc所指向的n个字符的内存空间return;,【例1】动态一维数组的建立与撤销,动态分配数组有三个特点:,1.变量n在编译时没有确定的值,而是在运行中输入,按运行时所需分配堆空间,这一点是动态分配的优点,可克服数组“大开小用”的弊端。delete pc是将n个字符的空间释放,而用delete pc则只释放了一个字符的空间;,2.如果有一个char*pc1,令pc1=p,同样可用delete pc1来释放该空间。尽管C+不对数组作边界检查,但在堆空间分配时,对数组分配空间大小是纪录在案的。,3.没有初始化式,不可对数组初始化。,两种方法:动态创建二维数组,double*data,*score;/方法1:score=new doublemn;/方法2:data=new double*m;for(int j=0;jm;j+)dataj=new doublen;,两种方法:释放二维数组,/方法1:delete score;/方法2:for(int i=0;im;i+)delete datai;delete data;,#includeusing namespace std;const int m=4;/行数const int n=6;/列数void desarray(double(*)6);/函数声明void main()double(*score)n;/定义二级指针变量 score=new doublemn;/一次分配全部数组单元 if(score=0)cout Could not allocate.;exit(-1);,【例2】动态创建和删除一个m*n个元素的数组。/方法一:采用指向二维数组的指针,一次分配,for(int i=0;im;i+)/初始化数组元素 for(int j=0;jn;j+)scoreij=i*n+j;for(i=0;im;i+)for(int j=0;jn;j+)coutscoreijt;coutendl;desarray(score);/调用函数撤消数组 return;,/二维数组的撤销与内存释放:void desarray(double(*score)n)delete score;,【例2】动态创建和删除一个m*n个元素的数组。/方法二:采用指针数组,二次分配,const int m=4;/行数const int n=6;/列数/先看二维数组的动态创建:void main()double*data;/定义指针数组,相当于*datam data=new double*m;/设置行指针 if(data)=0)cout Could not allocate.Bye.;exit(-1);for(int j=0;jm;j+)dataj=new doublen;/分配一个一维数组为一行 if(dataj=0)cout Could not allocate.Bye.;exit(-1);,for(int i=0;im;i+)for(int j=0;jn;j+)dataij=i*n+j;/初始化数组元素 print(data);/调用函数输出数据 destroy(data);/调用函数进行撤销与内存释放 return;/再看二维数组的撤销与内存释放:void destroy(double*data)for(int i=0;im;i+)delete datai;delete data;/注意撤销次序,与设置相反,指针使用的几个问题:1.动态分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。,2.指针删除与堆空间释放。删除一个指针p(delete p;)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除本身,释放堆空间后,成了空悬指针。,内存泄漏(memory leak)和重复释放。new与delete 是配对使用的,delete只能释放堆空间。如果new返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存new返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。,动态分配的变量或对象的生命期。无名对象的生命期并不依赖于建立它的作用域,比如在函数中建立的动态对象在函数返回后仍可使用。我们也称堆空间为自由空间(free store)就是这个原因。但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错。,2.堆对象与构造函数,/动态创建对象class Point public:Point()X=Y=0;coutDefault Constructor called.n;Point(int xx,int yy)X=xx;Y=yy;cout Constructor called.n;Point()coutDestructor called.n;int GetX()return X;int GetY()return Y;void Move(int x,int y)X=x;Y=y;private:int X,Y;,int main()coutStep One:endl;Point*Ptr1=new Point;/调用缺省构造函数 delete Ptr1;coutStep Two:endl;Ptr1=new Point(1,2);/调用带参构造函数 delete Ptr1;return 0;,运行结果:Step One:Default Constructor called.Destructor called.Step Two:Constructor called.Destructor called.,3.堆对象数组与构造函数,通过new建立的对象要调用构造函数,通过deletee删除对象也要调用析构函数。CGoods*pc;pc=new CGoods;/分配堆空间,并构造一个无名的CGoods对象;.delete pc;/先析构,然后将内存空间返回给堆;,堆对象的生命期并不依赖于建立它的作用域,所以除非程序结束,堆对象(无名对象)的生命期不会到期,并且需要显式地用delete语句析构堆对象,上面的堆对象在执行delete语句时,C+自动调用其析构函数。,class CGoods char Name21;int Amount;float Price;float Total value;public:CGoods();/缺省构造函数 CGoods(char*name,int amount,float price)strcpy(Name,name);Amount=amount;Price=price;Total_value=price*amount;/下面注意如何使用:,正因为构造函数可以有参数,所以new后面类(class)类型也可以有参数。这些参数即构造函数的参数。但对创建数组,则无参数,并只调用缺省的构造函数。见下例类说明:,下面注意如何使用:void main()int n;CGoods*pc,*pc1,*pc2;pc=new CGoods(“夏利2000”,10,118000);/调用三参数构造函数 pc1=new CGoods();/调用缺省构造函数 coutn;pc2=new CGoodsn;/动态建立数组,不能初始化,需调用n次缺省构造函数 delete pc;delete pc1;delete pc2;,例6-18 动态数组类,class ArrayOfPointsprivate:Point*points;int numberOfPoints;public:ArrayOfPoints(int n)numberOfPoints=n;points=new Pointn;/动态创建数组 ArrayOfPoints()coutDeleting.endl;numberOfPoints=0;delete points;Point,23,int main()int number;coutnumber;/创建对象数组 ArrayOfPoints points(number);/通过指针访问数组元素的成员 points.Element(0).Move(5,10);/通过指针访问数组元素的成员 points.Element(1).Move(15,20);,24,运行结果如下:Please enter the number of points:2Default Constructor called.Default Constructor called.Deleting.Destructor called.Destructor called.,25,堆对象与构造函数,这里再次强调:由堆区创建对象数组,只能调用缺省的构造函数,不能调用其他任何构造函数。如果没有缺省的构造函数,则不能创建对象数组。,4.浅拷贝与深拷贝,缺省拷贝构造函数,可用一个类对象初始化另一个类对象,称为缺省的按成员拷贝,而不是对整个类对象的按位拷贝。这称为浅拷贝。,如果类中有一个数据成员为指针,该类的一个对象obj1中的这个指针p,指向了动态分配的一个堆对象,(参见图7.1拷贝前),如果用obj1按成员拷贝了一个对象obj2,这时obj2.p也指向同一个堆对象。当析构时,如用缺省的析构函数,则动态分配的堆对象不能回收。如果在析构函数中有“delete p;”语句,则如果先析构函数obj1时,堆对象已经释放,以后再析构obj2时出现了二次释放的问题。这时就要重新定义拷贝的构造函数,给每个对象独立分配一个堆对象,称深拷贝。这时先拷贝对象主体,再为obj2分配一个堆对象,最后用obj1的堆对象拷贝obj2的堆对象。,浅拷贝与深拷贝,给每个对象独立分配一个堆对象,称深拷贝。这时先拷贝对象主体,再为obj2分配一个堆对象,最后用obj1的堆对象拷贝obj2的堆对象。,结论:,在类的某个数据成员为指针的情况下:浅拷贝实现对象间数据元素的一一对应复制。(只复制指针值)深拷贝当被复制的对象数据成员是指针类型时,不仅复制该指针成员本身,而且将指针所指的对象进行复制。指针成员指向新复制的单元。,例6-20 对象的浅拷贝,#includeusing namespace std;class Point/类的声明同例6-16/;class ArrayOfPoints/类的声明同例6-18/;,class Point public:Point()X=Y=0;coutDefault Constructor called.n;Point(int xx,int yy)X=xx;Y=yy;cout Constructor called.n;Point()coutDestructor called.n;int GetX()return X;int GetY()return Y;void Move(int x,int y)X=x;Y=y;private:int X,Y;,class ArrayOfPoints public:ArrayOfPoints(int n)numberOfPoints=n;points=new Pointn;ArrayOfPoints()coutDeleting.endl;numberOfPoints=0;delete points;Point,int main()/观察浅拷贝的现象int number;cinnumber;ArrayOfPoints pointsArray1(number);pointsArray1.Element(0).Move(5,10);pointsArray1.Element(1).Move(15,20);ArrayOfPoints pointsArray2(pointsArray1);/拷贝构造 coutCopy of pointsArray1:endl;coutPoint_0 of array2:pointsArray2.Element(0).GetX(),pointsArray2.Element(0).GetY()endl;coutPoint_1 of array2:pointsArray2.Element(1).GetX(),pointsArray2.Element(1).GetY()endl;,34,pointsArray1.Element(0).Move(25,30);/数据改变 pointsArray1.Element(1).Move(35,40);coutAfter the moving of pointsArray1:endl;coutPoint_0 of array2:pointsArray2.Element(0).GetX(),pointsArray2.Element(0).GetY()endl;coutPoint_1 of array2:pointsArray2.Element(1).GetX(),pointsArray2.Element(1).GetY()endl;,35,运行结果如下:Please enter the number of points:2Default Constructor called.Default Constructor called.Copy of pointsArray1:Point_0 of array2:5,10Point_1 of array2:15,20After the moving of pointsArray1:Point_0 of array2:25,30Point_1 of array2:35,40Deleting.Destructor called.Destructor called.Deleting.接下来程序出现异常,也就是运行错误。,36,拷贝前,拷贝后,37,例6-21 对象的深拷贝,#includeusing namespace std;class Point/类的声明同例6-16;class ArrayOfPoints public:ArrayOfPoints(ArrayOfPoints,ArrayOfPoints:ArrayOfPoints(ArrayOfPointsint main()/同例6-20,39,程序的运行结果如下:Please enter the number of points:2Default Constructor called.Default Constructor called.Default Constructor called.Default Constructor called.Copy of pointsArray1:Point_0 of array2:5,10Point_1 of array2:15,20After the moving of pointsArray1:Point_0 of array2:5,10Point_1 of array2:15,20Deleting.Destructor called.Destructor called.Deleting.Destructor called.Destructor called.,40,拷贝前,拷贝后,pointsnumberOfPoints,pointsArray1,pointsArray1的数组元素占用的内存,pointsnumberOfPoints,pointsArray2,41,说明:堆内存是最常用的需要拷贝构造函数自定义的资源,但不是唯一的,如打开文件等。如果类需要析构函数来析构资源,则类也需要一个自定义的拷贝构造函数。对象的拷贝就是深拷贝了。,谢谢!,完,