类和动态内存分配_第1页
类和动态内存分配_第2页
类和动态内存分配_第3页
类和动态内存分配_第4页
类和动态内存分配_第5页
已阅读5页,还剩53页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

类和动态内存分配第1页,共58页,2023年,2月20日,星期二提要本章介绍对类使用动态分配技术,以及由此引起的问题的处理。动态内存的使用将影响构造函数,析构函数的设计和操作符的重载第2页,共58页,2023年,2月20日,星期二12.1动态内存和类一个使用动态内存的例子classStringBad{private:char*str;//pointertostringintlen;//lengthofstringstaticintnum_strings;//numberofobjectspublic:StringBad(constchar*s);//constructorStringBad();//defaultconstructor~StringBad();//destructor//friendfunction friendstd::ostream&operator<<(std::ostream&os,constStringBad&st);};第3页,共58页,2023年,2月20日,星期二以下是其实现(stringbad.cpp)静态类成员的初始化intStringBad::num_strings=0;

///注意:静态类成员位于静态存储区,并不是类对象的组成部分;在类声明中声明,不可初始化(静态const整型或枚举除外除外);在类方法实现文件中初始化。使用类型名和定义域限制操作符,不用关键字static。第4页,共58页,2023年,2月20日,星期二然后是构造函数StringBad::StringBad(constchar*s){len=std::strlen(s);//setsizestr=newchar[len+1];//allotstoragestd::strcpy(str,s);//initializepointernum_strings++;//setobjectcountcout<<num_strings<<":\""<<str<<"\"objectcreated\n";//ForYourInformation}注意:字符串并没有保存在对象中,而是在单独的堆内存中。对象中存储字符串的地址。第5页,共58页,2023年,2月20日,星期二析构函数StringBad::~StringBad()//necessarydestructor{cout<<"\""<<str<<"\"objectdeleted,";//FYI--num_strings;//requiredcout<<num_strings<<"left\n";//FYIdelete[]str;//required}此处析构函数是必须的。在构造函数中用new分配内存,必须在析构函数中用delete释放内存第6页,共58页,2023年,2月20日,星期二文件vegnews.cpp是使用上述类的主程序。其中定义了两个函数:一个传引用作参数,一个传值作参数voidcallme1(StringBad&rsb){cout<<"Stringpassedbyreference:\n";cout<<"\""<<rsb<<"\"\n";}voidcallme2(StringBadsb){cout<<"Stringpassedbyvalue:\n";cout<<"\""<<sb<<"\"\n";}第7页,共58页,2023年,2月20日,星期二1:"CeleryStalksatMidnight"objectcreated2:"LettucePrey"objectcreated3:"SpinachLeavesBowlforDollars"objectcreatedheadline1:CeleryStalksatMidnightheadline2:LettucePreysports:SpinachLeavesBowlforDollarsStringpassedbyreference:"CeleryStalksatMidnight"headline1:CeleryStalksatMidnightStringpassedbyvalue:"LettucePrey""LettucePrey"objectdeleted,2leftheadline2:葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺第8页,共58页,2023年,2月20日,星期二Initializeoneobjecttoanother:sailor:SpinachLeavesBowlforDollarsAssignoneobjecttoanother:3:"C++"defaultobjectcreatedknot:CeleryStalksatMidnightEndofmain()"CeleryStalksatMidnight"objectdeleted,2left"SpinachLeavesBowlforDollars"objectdeleted,1left"葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺?objectdeleted,0left未处理的异常:System.NullReferenceException:未将对象引用设置到对象的实例。

atdelete[](Void*)atStringBad.__dtor(StringBad*)ind:\temp\test2\strngbad.cpp:line37atmain()ind:\temp\test2\vegnews.cpp:line39第9页,共58页,2023年,2月20日,星期二"C++"objectdeleted,-1left"葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺葺F"objectdeleted,-2left未处理的异常:System.NullReferenceException:未将对象引用设置到对象的实例。

atdelete[](Void*)atStringBad.__dtor(StringBad*)ind:\temp\test2\strngbad.cpp:line37at__CxxCallUnwindDtor(IntPtr,Void*)atmain()ind:\temp\test2\vegnews.cpp:line39第10页,共58页,2023年,2月20日,星期二跟踪程序的执行过程,当函数callme2调用时发生了析构函数的调用。析构函数的delete语句释放了实际参数headline对象的成员headline.str指向的内存,并使静态成员的计数减一:cout<<"\""<<str<<"\"objectdeleted,";--num_strings;//requiredcout<<num_strings<<"left\n";//FYIdelete[]str;注意:当以对象为实参传递给函数时,函数中要拷贝对象的副本,即创建一个临时对象;该函数结束时临时对象消失,析构函数被调用。第11页,共58页,2023年,2月20日,星期二分析程序:复制构造函数程序中没有给出复制构造函数,但却使用了复制构造函数:当用一个对象初始化一个新建对象时StringBadsailor=sports;StringBadsailor(sports);函数按值传递对象或返回对象时callme2(headline2);第12页,共58页,2023年,2月20日,星期二此时使用了默认的复制构造函数,即按值依次复制对象的非静态变量成员。该复制构造函数的原型如下:Stringbad(constStringbad&sb);它执行的是浅拷贝,即复制成员的值。对于

StringBadsailor=sports;

相当于sailor.str=sports.str即两个指针值相同,它们指向了同一块内存另外,默认复制构造函数不会对静态成员有改变,所以计数不增加。第13页,共58页,2023年,2月20日,星期二注意1:如果类中有这样的静态成员,当创建对象时其值发生改变,必须提供显式的复制构造函数注意2:如果类中有用new初始化的指针成员,应该提供显式的复制构造函数,以实现深拷贝StringBad::StringBad(constStringBad&sb){num_strings++;//handlestaticmemberupdatelen=sb.len;//samelengthstr=newchar[len+1];//allotspacestd::strcpy(str,sb.str);//copystringtonewlocation}第14页,共58页,2023年,2月20日,星期二分析程序:赋值操作符程序中允许对类对象赋值,是因为c++自动为类重载了赋值操作符。如StringBadknot;knot=headline1;使用了下面的自动重载的操作符Stringbad&Stringbad::operator=(constStringbad&);默认赋值操作符的实现方式与默认拷贝相似,也是对成员进行逐个复制,但不影响静态数据成员第15页,共58页,2023年,2月20日,星期二由于knot=headline1;

赋值操作符作了如下的工作:knot.str=headline1.str;同样是两个指针指向了同一块内存。对象knot的析构函数调用在先,释放了指针指向的内存;当对象headline1的析构函数被调用时试图释放已经释放的内存导致错误。第16页,共58页,2023年,2月20日,星期二解决该问题的方法是提供赋值操作符的重载,实现深拷贝。重载赋值操作符应具有如下功能:由于被赋值的对象在创建时已经分配了内存,所以函数使用前应该先释放它;函数要避免自身赋值,否则赋值前的释放内存操作会破坏对象的内容;函数返回一个指向对象的引用,这样可以实现连续赋值操作。第17页,共58页,2023年,2月20日,星期二重载的操作符如下StringBad&StringBad::operator=(constStringBad&sb){if(this==&sb)return*this;delete[]str;len=sb.len;str=newchar[len+1];std::strcpy(str,sb.str);return*this;}第18页,共58页,2023年,2月20日,星期二改进后的字符串类增加复制构造函数和赋值操作符添加必要的功能1.修订默认构造函数:构建一个空字符串String::String()//defaultconstructor{len=0;str=newchar[1];//为何不是newchar?str[0]='\0';//defaultstringnum_strings++;}第19页,共58页,2023年,2月20日,星期二2.比较成员函数:比较字符串的前后顺序booloperator<(constString&st1,constString&st2){//若st1中的字符排在st2之前,则返回truereturn(std::strcmp(st1.str,st2.str)<0);}booloperator>(constString&st1,constString&st2){//利用已经重载的操作符

returnst2.str<st1.str;}booloperator==(constString&st1,constString&st2){//所有字符相同返回truereturn(std::strcmp(st1.str,st2.str)==0);}第20页,共58页,2023年,2月20日,星期二比较操作符都被设置为友元,有利于C字符串与对象的比较,例如,name是一个对象

if(“Smith”==name)可以转换为

if(operator==(“Smith”,name))

再由一个构造函数将“Smith”转换为类类型从而进行函数的调用3.重载下标运算符

[]运算符的一个操作数位于括号前,一个操作数位于两个括号之间。

char&String::operator[](inti){returnstr[i];}第21页,共58页,2023年,2月20日,星期二//这个方法可以为对象的特定元素赋值Stringname(“johnsmith”);name[0]=‘J’;name[5]=‘S’;但是非const成员不能被const对象使用,所以还要定义一个const函数constchar&String::operator[](inti)const{returnstr[i];}第22页,共58页,2023年,2月20日,星期二4.静态类成员函数//staticfunction

staticintHowMany();//staticmethodintString::HowMany(){returnnum_strings;}静态类成员函数的作用:不能通过对象调用它,它也只能访问静态数据成员声明为公有,则可以由作用域操作符调用它:Intcount=String::HowMany();第23页,共58页,2023年,2月20日,星期二5.再增加一个赋值操作符String&String::operator=(constchar*s){delete[]str;len=std::strlen(s);str=newchar[len+1];std::strcpy(str,s);return*this;}通过这个方法可以用字符串对对象赋值第24页,共58页,2023年,2月20日,星期二6.重载输出输入操作符ostream&

operator<<(ostream&os,constString&st){os<<st.str;returnos;}istream&

operator>>(istream&is,String&st){chartemp[String::CINLIM];is.get(temp,String::CINLIM);if(is)st=temp;while(is&&is.get()!='\n')continue;returnis;}第25页,共58页,2023年,2月20日,星期二小结:构造函数中使用new构造函数中使用new初始化指针成员,则析构函数重要使用delete;new和delete须兼容;如果有多个构造函数,必须以同样方式使用new,也可以将指针成员初始化为0;应定义复制构造函数,实现深拷贝,其中还要注意更新受影响的静态成员;应定义赋值操作符,实现深拷贝。第26页,共58页,2023年,2月20日,星期二有关函数返回对象的总结返回指向const对象的引用对象引用作函数参数是为提高效率,不用复制对象,const表示不可改动实参如果函数要返回传递给它的对象,也可以通过传引用提高效率。constVector

&Max(constVector&v1,constVector&v2){return(v1.magval>v2.magval?V1:v2);}此例中可以返回对象,但要调用复制构造函数,降低效率。const与参数对应。第27页,共58页,2023年,2月20日,星期二返回指向非const对象的引用常用于重载赋值操作符和与流对象一起用的<<、>>操作符。ostream&operator<<(ostream&os,constString&st)

必须返回ostream&,以便实现连续输出。不能返回对象,因ostream没有公有的复制构造函数。也不能是const,因为流对象的值在改变。String&String::operator=(constString&st)可以返回对象或引用,都可起到连续赋值作用,但返回引用效率高。第28页,共58页,2023年,2月20日,星期二返回对象如果函数中要返回的是个局部对象,则不能返回引用,因为返回时该对象已消失,引用指向的对象将不存在。例如重载的算术运算符VectorVector::operator+

(constVector&b)const { returnVector(x+b.x,y+b.y); }第29页,共58页,2023年,2月20日,星期二返回const对象前面的operator+允许以下操作Vectorstep1(50,40);Vectorstep2(38,77);Vectortotal;total=step1+step2;step1+step2=total;if(step1+step2=step1)

cout<<“step2=0.”解决方法:将operator+的类型声明为const对象第30页,共58页,2023年,2月20日,星期二指向对象的指针在C++中,经常使用指向对象的指针,下面的例子演示了这种用法。(sayings2.cpp)可以使用普通的指针方法来使用对象指针,例如

String*shortest=&sayings[0];//initializeto//firstobject

String*first=&sayings[0];第31页,共58页,2023年,2月20日,星期二之后便可以利用指针间接访问if(sayings[i].length()<shortest->length())或者对指针解除引用if(sayings[i]<*first)第32页,共58页,2023年,2月20日,星期二使用new初始化对象如果className是类,value的类型为typeName,则语句className*pclass=newclassName(value);将调用构造函数className(typeName);例如String*pst=newString(“Hello.”);将调用String(constchar*s);而String*pst=newString;将调用默认构造函数String();第33页,共58页,2023年,2月20日,星期二一个特殊情况String*favorite=

newString(sayings[choice]);所给的参数是一个本类对象的名字,则调用的是复制构造函数String(constString&);//copyconstructor第34页,共58页,2023年,2月20日,星期二刚才的例子中在两个层次上使用了new和delete操作符:一是为创建每一个对象包含的字符串分配存储空间,这是在构造函数中进行的,需要在析构函数中释放该空间String::String(constchar*s)//constructStringfrom//Cstring{len=std::strlen(s);//setsize

str=newchar[len+1];//allotstorage

std::strcpy(str,s);//initializepointer

num_strings++;//setobjectcount}第35页,共58页,2023年,2月20日,星期二String::~String(){--num_strings;delete[]str;}第二个层次是为类对象分配空间,保存的是类对象的成员str指针值和字符串的长度值len

String*favorite=newString(sayings[choice]);cout<<"Myfavoritesaying:\n"<<*favorite<<endl;deletefavorite;第36页,共58页,2023年,2月20日,星期二保存对象的空间不会由析构函数释放,需要用delete释放它:只释放存放str和len的空间,而不会释放保存字符串的空间析构函数的调用有以下几种情况:如果对象是自动变量,则执行完定义该对象的程序块时调用如果对象是静态的(静态,外部,静态外部的),则在程序结束时调用如果对象是由new创建的,则当显式地使用delete时调用析构函数第37页,共58页,2023年,2月20日,星期二classAct{…};Actnice;//外部对象intmain(){ Act*pt=newAct;//动态对象

… { Actmyact;//自动对象

… }/////////////////////////调用myact的析构函数

deletept;/////////调用(*pt)的析构函数…}////////////////调用nice的析构函数第38页,共58页,2023年,2月20日,星期二使用new为对象分配空间的机制比较复杂:String*mystring=newString(“hellobaby.”);1、为对象分配内存2、调用类构造函数为“hellobaby.”分配空间,并将字符串复制到分配的空间中str:len:(对象地址:2400)Hellobaby.\0(字符串地址:2000)第39页,共58页,2023年,2月20日,星期二将字符串的地址值赋给str,将字符串的长度值赋给len,并且更新mun_strings3、创建指针变量mystring,将新对象的地址赋给变量mystringstr:2000len:11(对象地址:2400)指针值:2400(变量地址:2800)第40页,共58页,2023年,2月20日,星期二布局new操作符通常情况,new负责在堆中找一个足以满足要求的内存块;new还有一种变体称为布局new操作符,允许指定要使用的位置。通过布局new可以设置内存管理规程,或处理通过特定地址访问的硬件。使用布局new需要头文件<new>,且要指定new的参数。第41页,共58页,2023年,2月20日,星期二#include<new>Structchaff{ chardross[20]; intslag;};charbuffer1[50];charbuffer2[500];intmain(){ chaff*p1,*p2; int*p3,*p4; p1=newchaff;p3=newint[20]; p2=new(buffer1)chaff; p4=new(buffer2)int[20];}第42页,共58页,2023年,2月20日,星期二看一个例子。从中可以知道pd2通过布局new放在了buffer中,而pd1被放在很远的堆中第二个常规new查找一个新的内存块,而第二个布局new分配的位置与第一次一样。说明布局new使用传递给它的地址,它不跟踪内存单元已被使用。操作符delete只能用于释放常规操作符new分配的堆内存,本例中的buffer是静态内存,不能使用delete来释放。第43页,共58页,2023年,2月20日,星期二第44页,共58页,2023年,2月20日,星期二布局new用于对象看一个使用布局new创建对象的例子(placenew1.cpp)程序存在两个问题:pc1和pc3均由布局new创建,后者创建时会覆盖前者的内存。如果对象中的成员是由new来动态分配的,会引发问题(布局new使用指定位置,而创建成员的new要查找空闲位置)。将delete用于pc2和pc4时会自动调用它们指向的对象的析构函数;delete用于buffer时释放常规new创建的整个内存块,不会调用在其中创建的对象(由布局new创建)的析构函数。第45页,共58页,2023年,2月20日,星期二解决第一个问题的方法是程序员来控制为布局new提供不同的地址,例如pc1=new(buffer)JustTesting;

pc3=new(buffer+sizeof(JustTesting))JustTesting("BetterIdea",6);解决第二个问题的方法是显式地调用由布局new创建对象的析构函数pc3->~JustTesting();//destroyobjectpointed//tobypc3pc1->~JustTesting();//destroyobjectpointed//tobypc1需要注意,以创建对象相反的顺序删除之,并且删除完对象之后再释放所在的缓冲区。第46页,共58页,2023年,2月20日,星期二12.2队列模拟问题:一家银行想在一家超市中建立ATM机,超市担心排队会影响其经营。编程模拟ATM对超市造成的影响。使用队列描述排队顾客:队列中的项为顾客。假设,1/3的顾客1分钟接受服务,1/3为2分钟,另外1/3为3分钟;顾客的到达时间随机,但每小时到达的顾客数恒定。第47页,共58页,2023年,2月20日,星期二设计队列类,应有如下特征:存储有序项序列容纳的项目数有一定限制能够创建空队列能够判断队列为空或为满可以从队尾添加项可以从队首删除项能够确定队列中的项目数第48页,共58页,2023年,2月20日,星期二队列类的接口:classQueue{ enum{Q_SIZE=10};public:Queue(intqs=Q_SIZE);//createqueuewithaqslimit~Queue();boolisempty()const;boolisfull()const;intqueuecount()const;boolenqueue(constItem&item);//additemtoendbooldequeue(Item&item);//removeitemfromfront};第49页,共58页,2023年,2月20日,星期二队列数据的表示使用链表表示。链表由节点构成,每个节点中保存相应信息以及指向下一个节点的指针,队尾的指针通常指向NULL。structNode{ Itemitem; structNode*next;};此外还要有链表的起始位置,末尾位置,可存储的最大项数以及当前存储的项数。可以如下设计队列类的数据成员:第50页,共58页,2023年,2月20日,星期二classQueue{private://classscopedefinitions//NodeisanestedstructuredefinitionlocaltothisclassstructNode{Itemitem;structNode*next;};enum{Q_SIZE=10};//privateclassmembersNode*front;//pointertofrontofQueueNode*rear;//pointertorearofQueueintitems;//currentnumberofitemsinQueueconstintqsize;//maximumnumberofitemsinQueue…第51页,共58页,2023年,2月20日,星期二队列最初是空的,所以Queue::Queue(intqs):qsize(qs){front=rear=NULL;items=0;}注意:非静态常量数据成员和引用数据成员不能赋值,但又不能在类定义中初始化。只能使用成员初始化列表方式初始化第52页,共58页,2023年,2月20日,星期二归纳:static,const成员的初始化问题staticconst成员是类中的常数,在类定义中初始化即可:

classTest{

staticconstintsize=10;…};static成员在类中声明,其初始化要放在类定以外,通常是在实现文件中,例如

classTest{

staticintsize;

…};第53页,共58页,2023年,2月20日,星期二

//实现文件

intTest::size=10;

//…成员定义const成员,在类定义中声明,在构造函数的成员初始化列表中初始化,如前面的例子。第54页,共58页,2023年,2月20日,星期二判断队列为

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论