您好,欢迎访问三七文档
当前位置:首页 > 电子/通信 > 综合/其它 > 4 堆与拷贝构造函数
面向对象程序设计(C++)第4章堆与拷贝构造函数4.1关于堆C++程序的内存格局通常分为四个区:1.全局数据区2.代码区3.栈区4.堆区全局变量、静态数据、常量存放在全局数据区;所有类的成员函数和非成员函数代码存放在代码区;为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放于栈区;余下的空间作为堆区。4.2需要new和delete的原因从C++的立场来看,不使用malloc()函数的原因是它在分配空间的时候不能调用构造函数。类对象的建立包括分配空间、构造结构以及初始化,这些是由构造函数统一完成的。voidfn(){Tdate*pD;pD=(Tdate*)malloc(sizeof(TDate));//…free(pD);}classTDate{public:TDate();//构造函数voidSetDate(inty,intm,intd);intIsLeapYear();voidPrint();private:intyear,month,day;};4.2需要new和delete的原因如果构造函数被调用,则须在进行内存分配的malloc()调用时进行,而malloc()仅是一个函数调用,没有足够的信息调用构造函数。pD从malloc()获得只是一个含有随机数据的类对象空间,须在内存分配之后再进行初始化。voidfn(){Tdate*pD;pD=(Tdate*)malloc(sizeof(TDate));pD-SetDate(2011,1,1);//…free(pD);}4.3分配堆对象C++的new和delete机制更简单易懂。voidfn(){Tdate*pS;pS=newTDate;//分配堆空间并构造//…deletepS;//析构并将空间返还给堆}如果分配局部对象,则在该局部对象退出作用域时自动调用析构函数。但堆对象的作用域是整个程序生命期,所以除非程序运行完毕,否则堆对象的作用域不会到期。堆对象的析构是在释放堆对象语句delete执行之时。4.3分配堆对象如果构造函数参数,则new后面的类类型也需要相应的参数。classTDate{public:TDate(inty,intm,intd){year=y;month=m;day=d;private:intyear,month,day;};voidfn(){Tdate*pD;pD=newTdate(2011,1,1);//…deletepD;}new根据参数匹配的原则来调用构造函数,如果写成pD=newTdate;则由于TDate类没有提供无参的构造函数而出错。4.3分配堆对象从堆中还可以分配对象数组classStudent{public:Student(char*pName=“noname”){strncpy(name,pName,sizeof(name));name[sizeof(name)-1]=‘\0’;}private:charname[40];};voidfn(intnum){Student*pS=newStudent[num];//…delete[]pS;}构造函数被调用num次,依次构造pS[0]到pS[num-1]。从堆上分配对象数组,只能调用默认构造函数,不能调用任何其他构造函数。4.4拷贝构造函数如果希望生成一个对象的副本,可以创建一个新的对象,并将现有对象的数据成员值赋值给新对象的相应成员。这种方法只在一定条件下可行,但繁琐。更好的途径是使类具有某种复制本类对象的能力,这便是拷贝构造函数(CopyConstructor)的功能。拷贝构造函数是一种特殊的构造函数,具有一般构造函数的特点,其作用是用一个已经存在的对象去初始化一个新的同类对象。4.4拷贝构造函数可以根据实际问题的需要定义拷贝构造函数,以实现同类对象之间数据成员的传递。如果没有自定义类的拷贝构造函数,系统会自动生成一个默认的拷贝构造函数,其工作方式是按成员初始化(memberwiseinitialization),即通过依次拷贝每个非静态数据成员实现,如果成员是类对象,则调用其拷贝构造函数或者默认拷贝构造函数。拷贝构造函数的形式:类名(类名&对象名)形参是本类对象的引用4.4拷贝构造函数classTPoint{public:TPoint(intx=0,inty=0){X=x;Y=y;}TPoint(TPoint&p);//拷贝构造函数intGetX(){returnX;}intGetY(){returnY;}private:intX,Y;};TPoint::TPoint(TPoint&p){X=p.X;Y=p.Y;cout“拷贝构造函数被调用”endl;}4.4拷贝构造函数普通构造函数在对象创建时被调用,拷贝构造函数在以下三种情况下会被调用:1.用类的一个对象去初始化该类的另外一个对象,例如:voidmain(){TPointA(1,2);TPointB(A);//用对象A初始化对象B,拷贝构造函数被调用//…}2.函数的形参是类的对象,调用函数进行形参和实参结合时,例如voidf(TPointp)//形参p用实参的值进行构造{coutp.GetX()endl;}4.4拷贝构造函数3.函数的返回值是类的对象,函数执行完成返回调用者时,拷贝构造函数会被调用。例如:TPointg(){TPointA(1,2);returnA;}voidmain(){TPointB;B=g();}说明:函数g表面上将对象A返回给主函数,但A是局部对象,离开建立它的函数后就消亡了,不可能在返回主函数后继续生存,编译系统在处理这种情况时会在主函数中创建一个临时的无名对象,该临时对象的生存期只在函数调用所处的表达式中。执行“returnA;”时,实际上是调用拷贝构造函数将A的值复制到临时对象中。表达式B=g();计算完毕后,临时对象自动消失。4.4拷贝构造函数一般规定,创建的临时对象,只在创建它们的外部表达式中有效。Studentfn(){Studentms(“Randy”);returnms;}voidmain(){Student&ref=fn();//….}因为外部表达式Student&ref=fn();到分号处结束,之后从fn()返回的临时对象不再有效,即ref所引用的目标不复存在。4.5深拷贝和浅拷贝在默认拷贝构造函数中,拷贝的方式是逐个成员依次复制。但一个对象可能会拥有某些资源,当构造函数为其分配了一个资源(例如堆内存)的时候,如果拷贝构造函数简单地制作了一个指向该资源的副本,而不是重新分配,就会出现两个对象拥有同一个资源,当对象析构时,资源会被返还两次。这种复制对象成员,但不复制资源的方式称为浅拷贝。如果创建一个对象时,分配了资源就需要定义自己的拷贝构造函数来改变缺省的逐成员拷贝的方式,不但拷贝成员,也拷贝资源,这种方式称为深拷贝。通常,如果类需要析构函数来释放资源的话,那么它也需要一个拷贝构造函数。Obj1Obj2堆复制前浅拷贝深拷贝Obj1堆Obj2Obj1堆堆4.6无名对象可以直接调用构造函数产生无名对象。voidfn(){Student(“Randy”);//….}无名对象可以作为实参传递给函数,可以用来拷贝构造一个新对象,也可以初始化一个引用。voidfn(Student&s);voidmain(){Student&refs=Student(“Randy”);Students=Student(“Randy”);fn(Student(“Randy”));}4.7构造函数用于类型转换转换用户自定义的类类型需要定义含有一个参数的构造函数。classStudent(){public:Student(char*);//….};因为有Student(char*)的构造函数,又有函数fn(Student&s),于是fn(“Jenny”)被认为是fn(Student(“Jenny”)),予以匹配。voidfn(Student&s);voidmain(){fn(“Jenny”);}4.7构造函数用于类型转换构造函数用于类型转换时要注意以下两点:1.只会尝试含有一个参数的构造函数2.如果存在二义性,则放弃尝试classStudent(){public:Student(char*pName=“noname”);//….};classTeacher(){public:Teacher(char*pName=“noname”);//….};voidaddCourse(Student&s);voidaddCourse(Teacher&t);voidmain(){addCourse(“Prof.D”);//error:二义性}正确的方法是显示地转换:addCourse(Teacher(“Prof.D”));
本文标题:4 堆与拷贝构造函数
链接地址:https://www.777doc.com/doc-3956517 .html