1、堆与拷贝构造函数1 UML与程序设计与程序设计(C+)堆与拷贝构造函数2第第4章章堆与拷贝构造函数堆与拷贝构造函数堆与拷贝构造函数34.1 关于堆C+C+程序的内存格局通常分为四个区:程序的内存格局通常分为四个区:n全局数据区全局数据区n代码区代码区n栈区栈区n堆区堆区全局变量、静态数据、常量存放在全局数据区;所有类的成全局变量、静态数据、常量存放在全局数据区;所有类的成员函数和非成员函数代码存放在代码区;为运行函数而分配的局员函数和非成员函数代码存放在代码区;为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放于栈区;余下的部变量、函数参数、返回数据、返回地址等存放于栈区;余下的空
2、间作为堆区。空间作为堆区。堆与拷贝构造函数44.2 需要new和delete的原因n从C+的立场来看,不使用malloc()函数的原因是它在分配空间的时候不能调用构造函数。类对象的建立包括分配空间、构造结构以及初始化,这些是由构造函数统一完成的。void fn()Tdate*pD;pD=(Tdate*)malloc(sizeof(TDate);/free(pD);class TDatepublic:TDate();/构造函数构造函数void SetDate(int y,int m,int d);int IsLeapYear();void Print();private:int year,mon
3、th,day;堆与拷贝构造函数54.2 需要new和delete的原因n如果构造函数被调用,则须在进行内存分配的malloc()调用时进行,而malloc()仅是一个函数调用,没有足够的信息调用构造函数。pD从malloc()获得只是一个含有随机数据的类对象空间,须在内存分配之后再进行初始化。void fn()Tdate*pD;pD=(Tdate*)malloc(sizeof(TDate);pD-SetDate(2011,1,1);/free(pD);堆与拷贝构造函数64.3 分配堆对象nC+的的new和和delete机制更简单易懂。机制更简单易懂。void fn()Tdate*pS;pS=n
4、ew TDate;/分配堆空间并构造分配堆空间并构造/delete pS;/析构并将空间返还给堆析构并将空间返还给堆 n如果分配局部对象,则在该局部对象退出作用域时自动调用析如果分配局部对象,则在该局部对象退出作用域时自动调用析构函数。但堆对象的作用域是整个程序生命期,所以除非程序构函数。但堆对象的作用域是整个程序生命期,所以除非程序运行完毕,否则堆对象的作用域不会到期。堆对象的析构是在运行完毕,否则堆对象的作用域不会到期。堆对象的析构是在释放堆对象语句释放堆对象语句delete执行之时。执行之时。堆与拷贝构造函数74.3 分配堆对象n如果构造函数有参数,则如果构造函数有参数,则new后面的类
5、类型也需要相应的参数。后面的类类型也需要相应的参数。class TDatepublic:TDate(int y,int m,int d)year=y;month=m;day=d;private:int year,month,day;void fn()Tdate*pD;pD=new Tdate(2011,1,1);/delete pD;nnew根据参数匹配的原则来调用构造函数,如果写成根据参数匹配的原则来调用构造函数,如果写成pD=new Tdate;则由于则由于TDate类没有提供无参的构造函数而出错。类没有提供无参的构造函数而出错。堆与拷贝构造函数84.3 分配堆对象n从堆中还可以分配对象数
6、组从堆中还可以分配对象数组class Studentpublic:Student(char*pName=“no name”)strncpy(name,pName,sizeof(name);namesizeof(name)-1=0;private:char name40;void fn(int num)Student*pS=new Studentnum;/delete pS;n构造函数被调用构造函数被调用num次,依次构造次,依次构造pS0到到pSnum-1。n从堆上分配对象数组,只能调用默认构造函数,不能调用任何其他构从堆上分配对象数组,只能调用默认构造函数,不能调用任何其他构造函数。造函数。
7、堆与拷贝构造函数94.4 拷贝构造函数n如果希望生成一个对象的副本,可以创建一个新的对如果希望生成一个对象的副本,可以创建一个新的对象,并将现有对象的数据成员值赋值给新对象的相应象,并将现有对象的数据成员值赋值给新对象的相应成员。这种方法可行,但繁琐。更好的途径是使类具成员。这种方法可行,但繁琐。更好的途径是使类具有某种复制本类对象的能力,这便是拷贝构造函数有某种复制本类对象的能力,这便是拷贝构造函数(Copy Constructor)的功能。的功能。n拷贝构造函数是一种特殊的构造函数,具有一般构造拷贝构造函数是一种特殊的构造函数,具有一般构造函数的特点,其作用是用一个已经存在的对象去初始函数
8、的特点,其作用是用一个已经存在的对象去初始化一个新的同类对象。化一个新的同类对象。堆与拷贝构造函数104.4 拷贝构造函数n可以根据实际问题的需要定义拷贝构造函数,以实现可以根据实际问题的需要定义拷贝构造函数,以实现同类对象之间数据成员的传递。如果没有自定义类的同类对象之间数据成员的传递。如果没有自定义类的拷贝构造函数,系统会自动生成一个默认的拷贝构造拷贝构造函数,系统会自动生成一个默认的拷贝构造函数,其工作方式是按成员初始化函数,其工作方式是按成员初始化(memberwise initialization),即通过依次拷贝每个非静态数据成,即通过依次拷贝每个非静态数据成员实现,如果成员是类对
9、象,则调用其拷贝构造函数员实现,如果成员是类对象,则调用其拷贝构造函数或者默认拷贝构造函数。或者默认拷贝构造函数。n拷贝构造函数的形式:拷贝构造函数的形式:类名类名(类名类名&对象名对象名)形参是本形参是本类对象的类对象的引用引用堆与拷贝构造函数114.4 拷贝构造函数class TPointpublic:TPoint(int x=0,int y=0)X=x;Y=y;TPoint(TPoint&p);/拷贝构造函数拷贝构造函数int GetX()return X;int GetY()return Y;private:int X,Y;TPoint:TPoint(TPoint&p)X=p.X;Y=
10、p.Y;cout“拷贝构造函数被调用拷贝构造函数被调用”endl;堆与拷贝构造函数124.4 拷贝构造函数n 普通构造函数在对象创建时被调用,拷贝构造函数在以下三种情况下会普通构造函数在对象创建时被调用,拷贝构造函数在以下三种情况下会被调用:被调用:1.用类的一个对象去初始化该类的另外一个对象,例如:用类的一个对象去初始化该类的另外一个对象,例如:void main()TPoint A(1,2);TPoint B(A);/用对象用对象A初始化对象初始化对象B,拷贝构造函数被调用,拷贝构造函数被调用 /2.函数的形参是类的对象,调用函数进行形参和实参结合时,例如函数的形参是类的对象,调用函数进行
11、形参和实参结合时,例如void f(TPoint p)/形参形参p用实参的值进行构造用实参的值进行构造 coutp.GetX()endl;堆与拷贝构造函数134.4 拷贝构造函数3.函数的返回值是类的对象,函数的返回值是类的对象,函数执行完成返回调用者时,函数执行完成返回调用者时,拷贝构造函数会被调用。拷贝构造函数会被调用。TPoint g()TPoint A(1,2);return A;void main()TPoint B;B=g();说明说明:函数:函数g表面上将对象表面上将对象A返回给返回给主函数,但主函数,但A是局部对象,离开建立是局部对象,离开建立它的函数后就消亡了,不可能在返它的
12、函数后就消亡了,不可能在返回主函数后继续生存,编译系统在回主函数后继续生存,编译系统在处理这种情况时会在主函数中创建处理这种情况时会在主函数中创建一个临时的无名对象,该临时对象一个临时的无名对象,该临时对象的生存期只在函数调用所处的表达的生存期只在函数调用所处的表达式中。执行式中。执行“return A;”时,实际时,实际上是调用拷贝构造函数将对象上是调用拷贝构造函数将对象A复制复制到临时对象中。表达式到临时对象中。表达式 B=g();计计算完毕后,临时对象自动消失。算完毕后,临时对象自动消失。堆与拷贝构造函数144.4 拷贝构造函数n 一般规定,创建的临时对象,只在创建它们的外部表达式中有效
13、。一般规定,创建的临时对象,只在创建它们的外部表达式中有效。Student fn()Student ms(“Randy”);return ms;void main()Student&ref=fn();/.n 因为外部表达式因为外部表达式Student&ref=fn();到分号处结束,之后从到分号处结束,之后从fn()返回的临返回的临时对象不再有效,即时对象不再有效,即ref所引用的目标不复存在。所引用的目标不复存在。堆与拷贝构造函数154.5 深拷贝和浅拷贝n 在默认拷贝构造函数中,拷贝的方在默认拷贝构造函数中,拷贝的方式是逐个成员依次复制。但一个对象式是逐个成员依次复制。但一个对象可能会拥有
14、某些资源,当构造函数为可能会拥有某些资源,当构造函数为其分配了一个资源其分配了一个资源(例如堆内存例如堆内存)的时的时候,如果拷贝构造函数简单地制作了候,如果拷贝构造函数简单地制作了一个指向该资源的副本,而不是重新一个指向该资源的副本,而不是重新分配,就会出现两个对象拥有同一个分配,就会出现两个对象拥有同一个资源,当对象析构时,资源会被返还资源,当对象析构时,资源会被返还两次。这种复制对象成员,但不复制两次。这种复制对象成员,但不复制资源的方式称为资源的方式称为浅拷贝浅拷贝。堆与拷贝构造函数164.5 深拷贝和浅拷贝n 如果创建一个对象时,分配了如果创建一个对象时,分配了资源就需要定义自己的拷
15、贝构造资源就需要定义自己的拷贝构造函数来改变缺省的逐成员拷贝的函数来改变缺省的逐成员拷贝的方式,不但拷贝成员,也拷贝资方式,不但拷贝成员,也拷贝资源,这种方式称为源,这种方式称为深拷贝深拷贝。通常。通常,如果类需要析构函数来释放资,如果类需要析构函数来释放资源的话,那么它也需要一个拷贝源的话,那么它也需要一个拷贝构造函数。构造函数。堆与拷贝构造函数174.6 无名对象n 可以直接调用构造函数产生无名对象。可以直接调用构造函数产生无名对象。void fn()Student(“Randy”);/.n 无名对象可以作为实参传递给函数,可以用来拷贝构造一个新对象,也无名对象可以作为实参传递给函数,可以
16、用来拷贝构造一个新对象,也可以初始化一个引用。可以初始化一个引用。void fn(Student&s);void main()Student&refs=Student(“Randy”);Student s=Student(“Randy”);fn(Student(“Randy”);堆与拷贝构造函数184.7 构造函数用于类型转换n 转换用户自定义的类类型需要定义含有一个参数的构造函数。转换用户自定义的类类型需要定义含有一个参数的构造函数。class Student()public:Student(char*);/.;n 因为有因为有Student(char*)的构造函数,又有函数的构造函数,又有函数fn(Student&s),于是,于是fn(“Jenny”)被被认为是认为是fn(Student(“Jenny”),予以匹配。,予以匹配。void fn(Student&s);void main()fn(“Jenny”);