下载

2下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 C++课件--薛景瑄chapter_3

C++课件--薛景瑄chapter_3.doc

C++课件--薛景瑄chapter_3

jingernanhang
2018-09-05 0人阅读 举报 0 0 暂无简介

简介:本文档为《C++课件--薛景瑄chapter_3doc》,可适用于IT/计算机领域

第三章类(class)及其对象(object)的封装(encapsulation)数据及其操作的统一管理封装性封装可以解决数据与函数代码之间的相容性和数据的保护问题。先看C语言中不使用封装的例子。例不用封装的例子noencapsulcppThecaseofnocapsulation#include<iostreamh>intsqint(inta){returna*a}doublesqdbl(doublea){returna*a}voidmain(){intj=doubled=j=sqint(j)d=sqdbl(d)cout<<j<<endlcout<<d<<endl}*Results:*此程序比较简练(当然还可以再简练些)但它改变了j和d的值。如果希望在不改变这两个值的情况下求出它们的平方值则可以将主程序修改如下增加两个变量:voidmain(){intj=,kdoubled=,fk=sqint(j)f=sqdbl(d)cout<<k<<endlcout<<f<<endl}以上程序中虽然两个变量j和d没有改变但其它函数仍有可能修改这两个变量。如何保证需要修改时才修改不准修改时就不修改。这要依靠封装。例使用封装以便保护数据encapsulcppThecaseofusingcapsulationtoprotectdatum'i'(不是data)#include<iostreamh>classinteg用户建立一个类用于封装{intjpublic:integ(){j=}构造函数intsq(){returnj*j}求平方函数voidinc(){j}求增量函数intread(){returnj}读数据函数}voidmain(){intjintegobj建立对象并初始化cout<<objread()<<endl读数据j=objsq()求平方cout<<j<<endl读平方值objinc()求增量cout<<objread()<<endl读增量值}*Results:*从以上程序看出不允许外部函数(如主函数main())等直接访问(读取或修改)classinteg中的数据成员j而必须通过访问类classinteg的成员函数read()才能读取j。如需求其平方值则须调用成员函数sq()但这些函数仍然无法改变数据j。如欲将数据加一则须调用成员函数inc()。封装是指将对象的属性(atdl}}voidmain(){pointobjm(){为教学目的而设的程序块pointobjb()cout<<"endofblock"<<endl}cout<<"endofprogram!"<<endl}*Results:ConsConsendofblockDesendofprogram!Des*下面利用析构函数来证明§例中的程序copyconscpp的确调用了系统所提供的拷贝构造函数。例证明拷贝构造函数的调用copyconscppexpandedversionofcopyconscpptousedestructortoprovethatcopyconstructorofsystemhasbeencalled#include<iostreamh>classpoint{intX,Ypublic:point(intx,inty){X=xY=ycout<<"Cons"<<endl}~point(){cout<<"Destructor"<<endl}voidshow(){cout<<X<<','<<Y<<endl}}voidmain(){pointp(,)pshow()pointp(p)调用缺省拷贝构造函数pshow()pointp=p调用缺省拷贝构造函数pshow()}*Results:Cons,,,DestructorDestructorDestructor*以上程序中虽然调用系统提供的缺省拷贝构造函数时没有显示但在程序结束时调用了析构函数。其中两个析构函数证明曾经两次调用过缺省拷贝构造函数。现在利用拷贝构造函数和析构函数来再次了解第二章§中“按数值调用”和“按引用调用”时形参和返回值的不同处理方式。在阅读下列程序之前先熟悉一下对象用作形参和返回值的格式。我们已熟悉:intfun(inti){returni}但不熟悉:AAfun(AAobj){returnobj}将两者对比一下就容易看出前例中形参是预定义类型int变量i而后例中形参是classAA的对象obj。例copyconscppToshowhowcopyconstructorworkswhenafunctioniscalled#include<iostreamh>classAA{public:intXAA(intj){X=j}}voidincbyvalue(AAobj){objX}voidincbyrefer(AAobj)形参是classAA的对象obj的引用{objX}voidmain(){AAobj()cout<<"beforeincbyvalue():X="<<objX<<endlincbyvalue(obj)cout<<"afterincbyvalue():X="<<objX<<endlcout<<"beforeincbyrefer():X="<<objX<<endlincbyrefer(obj)cout<<"afterincbyrefer():X="<<objX<<endl}*Results:beforeincbyvalue():X=afterincbyvalue():X=beforeincbyrefer():X=afterincbyrefer():X=*正如第二章§中所介绍的“按数值调用”方式无法改变函数以外对象的数据成员X的数值而“按引用调用”方式则能改变函数以外对象的数据成员X的数值。为了进一步了解这两种方式的区别在以上程序内增加构造函数和析构函数的显示内容。新增加内容用红笔标出见下例:例copyconscppToshowhowcopyconstructorworkswhenafunctioniscalled#include<iostreamh>classAA{public:intXAA(intx){X=xcout<<"Cons"<<X<<endl}AA(constAAa){X=aXcout<<"CopyCons"<<X<<endl}~AA(){cout<<"Destructor"<<X<<endl}}voidincbyvalue(AAobj){objX}voidincbyrefer(AAobj)形参是classAA的对象obj的引用{objX}voidmain(){AAobj()cout<<"beforeincbyvalue():X="<<objX<<endlincbyvalue(obj)cout<<"afterincbyvalue():X="<<objX<<endlcout<<"beforeincbyrefer():X="<<objX<<endlincbyrefer(obj)cout<<"afterincbyrefer():X="<<objX<<endlcout<<"programended!"<<endl}*Results:Consbeforeincbyvalue():X=CopyConsDestructorafterincbyvalue():X=beforeincbyrefer():X=afterincbyrefer():X=programended!Destructor*以上程序中定义了两个构造函数这是构造函数的重载形式将在第五章中详细讲解。从以上程序可以看出:在“按数值调用”中作为classAA的对象的形参产生副本(也即再建立对象)因此调用用户自己定义的拷贝构造函数。当对函数byvalue()的调用结束时对象副本也从堆栈中消失因而它们的析构函数被立即调用。而在“按引用调用”中引用作为“别名其表指针其实”形参不需要产生对象的副本而只产生指针副本(也即不建立临时对象)。因此这种调用方式减少了空间和时间上的开销。成员初始化列表以及调用顺序成员初始化列表(initializationtable)初始化列表具有多个用途:主要用于向直接基类和虚基类传递参数(将在第四章中阐述)。子对象的初始化。特定类型数据成员(主要是数据的引用和常量数据)的初始化。本节只介绍第二和第三种用途。特定类型数据成员的初始化类的一个特定类型数据成员是非静态常量数据它在类中以const定义在程序运行中不准改动。见下例:例在类中使用cosnt关键词定义常量数据成员constcppInamemberfunction,"const"datamembersshouldnotbemodified#include<iostreamh>classA{constintconsconstintconsintnonconspublic:A(inta,intb,intc):cons(a),cons(b){noncons=c}voidmodifyshow()}voidA::modifyshow(){inttemp=cout<<cons<<','<<cons<<','<<noncons<<endlcons=temperrorC:lvaluespecifiesconstobjectcons=temperrorC:lvaluespecifiesconstobjectnoncons=tempcout<<cons<<','<<cons<<','<<noncons<<endl}voidmain(){Aobj(,,)objmodifyshow()}*Results:,,,,*包括非静态常量数据和数据的引用等在内的数据不能在构造函数中直接赋值。例如不允许以下操作:classX{intnconstintcnpublic:X(intj,intj){n=icn=iError:CannotmodifyaconstobjectinfunctionX::X(int,int)……}同样类中的引用也不能在构造函数中直接赋值。例如classX{intnintrn整型引用public:X(intj,intj){…rn=i…}构造函数Error:Referencemember‘rn’isnotinitializedinfunctionX::X(int,int)……}以上数据必须使用初始化列表见下例:例常量和引用数据成员的初始化initabcppuseinitializationtabletoinitialize'const'parameterandreference,arithmeticoperationnotallowedintheinitialiazationtable#include<iostreamh>classbase{intnconstintcnintttpublic:intrnbase(intj,intj,intj):cn(j),rn(j){初始化列表hereyoumaynotuse"cn=j"or"rn=j"n=jtt=*rncout<<"nis"<<n<<endlcout<<"cnis"<<cn<<endlcout<<"rnis"<<rn<<endlcout<<"ttis"<<tt<<endl}}voidmain(){intj=basebb(,,j)不能使用bb(,,)因引用rn初始化必须使用变量jj=cout<<"bbrnis"<<bbrn<<endl}*Results:niscnisrnisttisbbrnis(bbrn是j的别名)*当程序中须要定义为常量的数据成员较多时可不必在每个数据成员前加上const关键词只须将成员函数定义为const即可以使程序简明。而那些可以修改的数据则可定义为mutable。见下例:例将成员函数定义为constconstcppIna'const'memberfunction,onlycanmutabledatamemberbemodified#include<iostreamh>classA{intconsintconsmutableintnonconspublic:A(inta,intb,intc){cons=acons=bnoncons=c}voidmodifyshow()const}voidA::modifyshow()const{inttemp=cout<<cons<<','<<cons<<','<<noncons<<endlcons=temperrorC:lvaluespecifiesconstobjectcons=temperrorC:lvaluespecifiesconstobjectnoncons=tempcout<<cons<<','<<cons<<','<<noncons<<endl}voidmain(){Aobj(,,)objmodifyshow()}*Results:,,,,*以上常量程序中只准许修改mutable数据成员。子对象的初始化当一个类的对象用作另一个类的数据成员时称为对象成员或子对象。包含子对象的类被称为复合类(compositeclass)。这种功能称为复合(composition)。例如:classA{……}classB{Aa……}其中classB中的成员a(即classA的对象)是子对象。子对象也需要初始化而这也要依靠初始化列表。例consordercpp#include<iostreamh>classsubclass{intxpublic:subclass(intz){x=z}constructorvoiddisplay(){cout<<x<<endl}}classcompclass{intysubclasssobjectsisaclassmembersubclassrobjectrisanotherclassmemberpublic:compclass(intz)declaretheconstructorvoiddisplaycomp(){cout<<y<<endl}voiddisplaysubs(){sdisplay()}{不必用ssubclass::display()}voiddisplaysubr(){rdisplay()}{不必用rsubclass::display()}}compclass::compclass(intz):s(),r()constructors和r的初始化值也可直接用构造函数的形参{y=z}voidmain(){compclassobj()objdisplaysubs()objdisplaysubr()objdisplaycomp()}*Results:*注意:初始化列表中只能用子对象名s和r而不能用类名因一个类可能产生多个子对象。子对象构造函数和析构函数的调用顺序上面在例的consordercpp中看了子对象的作用现为了解子对象的构造函数和析构函数的调用顺序在该consordercpp程序中加入显示语句成为consdessubobjcpp如下:consdessubobjcppToshowtheorderinwhichtheconstructorsanddestructorsarecalled用于显示调用顺序的语句以蓝色标出其余语句均与前一程序相同#include<iostreamh>classsubclass{intxpublic:subclass(intz)constructor{x=zcout<<"Constructorsub"<<x<<"called!"<<endl}~subclass(){cout<<"Destructorsub"<<x<<"called!"<<endl}voiddisplay(){cout<<x<<endl}}classcompclass{intysubclasssobjectsisaclassmembersubclassrobjectrisaclassmemberpublic:compclass(intz)declaretheconstructorvoiddisplaycomp(){cout<<y<<endl}voiddisplaysubs(){sdisplay()}voiddisplaysubr(){rdisplay()}~compclass(){cout<<"Destructorcomp"<<y<<"called!"<<endl}}compclass::compclass(intz):s(),r(){definetheconstructory=zcout<<"Constructorcomp"<<y<<"called!"<<endl}voidmain(){compclassobj()objdisplaysubs()objdisplaysubr()objdisplaycomp()}*Operatingresult:Constructorsubcalled!Constructorsubcalled!Constructorcompcalled!Destructorcompcalled!Destructorsubcalled!Destructorsubcalled!*调用对象成员的析构函数的顺序正好与调用构造函数的顺序相反(在任何情况下都如此)。如果在成员初始化列表中将顺序颠倒为:compclass::compclass(intz):r()s(){definetheconstructory=zcout<<"Constructorcomp"<<y<<"called!"<<endl}则运行结果仍然不变。由此可见:调用对象成员的结构函数的顺序只决定于compclass说明中对象成员的排列顺序而与成员初始化列表中的顺序(r()s())无关。不同类型对象构造函数和析构函数的调用顺序第二章§的例和例中的vartypecpp和extstaticfilecpp两个程序阐述了不同类型的数据的生命周期和作用域。现在看一下不同类型对象的构造函数和析构函数的调用顺序从而看出它们的生命周期和作用域。例建立全局和局部对象时不同的构造函数和析构函数的调用顺序globallocalcppToshowhowtodifferentiatebetweenlocalandglobalobjects#include<iostreamh>voidcreate(void)prototypeclassbase{intdatapublic:base(inti){data=icout<<"CONS:"<<i<<endl}~base(){cout<<"DES:"<<data<<endl}voidshow(){cout<<"data="<<data<<endl}}voidmain(){basethird()localobjectcreate()callfunctiontocreateobjectsbasesixth()localobjectthirdshow()sixthshow()}ordinaryfunctionforcreatingobjectsvoidcreate(void){basefourth()fourthshow()}*Results:CONS:CONS:data=DES:CONS:data=data=DES:DES:*以上程序中当局部对象离开其作用域时即离开其函数时就被撤销从而调动其析构函数。此外当局部对象离开其作用域后就无法再被访问。例如在主函数main()中无法访问fourth对象而在子函数create()中则无法访问third对象。例建立不同类型对象时构造函数和析构函数的调用顺序此程序按照数据隐藏方法处理共有三个文件:consdeshconsdescppconsdeshidecpp。其中不向用户提供consdeshidecpp文件而只提供consdeshideobj头文件:consdesh提供classbase接口FigofDeitels'bookDefinitionofclassbaseMemberfunctionsdefinedincreatecpp#ifndefCONSDESH#defineCONSDESH#include<iostreamh>classbase{public:base(int)constructordeclaration~base()destructordeclarationprivate:intdata}#endif隐藏源文件:consdeshidecppFigofDeitels'bookMemberfunctiondefinitionsforclassbasenotthesourcefilebutonlytheobjectfileisprovidedtotheuser#include"consdesh"#include<iostreamh>alreadyincludedinconsdeshbase::base(intvalue):data(value){cout<<"Object"<<data<<"constructor"}base::~base(){cout<<"Object"<<data<<"destructor"<<endl}主文件:consdescppFig:figcppofDeitels'bookmainfilefordemonstratingtheorderinwhichconstructorsanddestructorsarecalledtobelinkedwithconsdeshideobj#include"consdesh"#include<iostreamh>alreadyincludedinconsdeshvoidcreate(void)prototypebasefirst()globalobjectvoidmain(){cout<<"(globalcreatedbeforemain)"<<endlbasesecond()localobjectcout<<"(localautomaticinmain)"<<endlstaticbasethird()localobjectcout<<"(localstaticinmain)"<<endlcreate()callfunctiontocreateobjectsbasesixth()localobjectcout<<"(localautomaticinmain)"<<endl}ordinaryfunctionforcreatingobjectsvoidcreate(void){basefourth()cout<<"(localautomaticincreate)"<<endlstaticbasefifth()cout<<"(localstaticincreate)"<<endl}*Results:Objectconstructor(globalcreatedbeforemain)Objectconstructor(localautomaticinmain)Objectconstructor(localstaticinmain)Objectconstructor(localautomaticincreate)Objectconstructor(localstaticincreate)ObjectdestructorObjectconstructor(localautomaticinmain)ObjectdestructorObjectdestructorObjectdestructorObjectdestructorObjectdestructor*以上程序中在主程序运行之前已经建立第一个对象(即全局对象first)主程序运行之后顺序地建立自动对象second和静态对象third。接着调用函数create()在create()中建立自动对象fourth和内部静态对象fifth退出create()时除静态对象fifth外删除其它对象并调用它的析构函数即fourth。回至主程序后建立自动对象sixth。退出主程序时先删除主程序内的自动对象并调用它们的析构函数即fourth和second。然后删除内部静态对象并调用它们的析构函数即second和fifth。最后删除主程序外的全局对象并调用它的析构函数即first。附录十二中还有“为数组动态分配空间时构造函数和析构函数的调用次数”的例子。特殊指针和特殊类成员this指针建立一个对象时系统为该对象分配内存栈区空间(位于栈区内)用于存储该对象的非静态数据成员。而该类的独一的一组成员函数则存于另外的代码分区内。这些成员函数一般都用于访问(读写)该类各对象的各个数据成员。试问:各成员函数怎么知道这些非静态数据成员的地址也即各对象的地址?原来每个对象的地址都存放在它自己的this指针内。而每个成员函数的参数表中都隐含着这个指针。例如§“构造函数”的例程序initializecpp中的构造函数point(intvx,intvy){x=vxy=vy}的参数表实际为point(point*this,intvx,intvy)同时classpoint的成员函数voidprint(){cout<<x<<''<<y<<endl}的参数表实际为voidprint(point*this)。也即当一个对象的非静态数据成员被调用时它都隐含一个指向该对象的this指针。只是平时没有被显示出来而已。应该指出this指针是指向一个对象的所以它能指向该对象的非静态数据成员。由于对象的this指针不属于对象本身因此使用sizeof宏所得的对象长度中并不包括this指针在内。但每次调用非静态成员函数时this指针都作为第一个隐式参数传递给对象(通过编译器)。例thiscppUsingthethispointertorefertoobjectmembers#include<iostreamh>classTest{intxpublic:Test(inta){x=a}constructorvoidprint()const}voidTest::print()const{cout<<"x="<<x<<"nthis>x="<<this>x<<"n(*this)x="<<(*this)x<<endl()isrequiredfor*this}voidmain(){Testobj()objprint()}*Results:x=this>x=(*this)x=*遇到同名的全局数据怎么办?可用全局分辨符“::”来分辨它是一元作用域运算符。如下例:例区别局部和全局变量的程序globallocalcppToshowhowtodifferentiatebetweenlocalandglobalvariables#include<iostreamh>intx=globalvariableclasspoint{intxlocalvariable,samenamepublic:point(intj){x=i}voidprint(){cout<<"localvariableis"<<x<<endliethis>xcout<<"globalvariableis"<<::x<<endl}}voidmain(){pointp()pprint()}*Results:localvariableisglobalvariableis*例globalcppUsingtheunaryscoperesolutionoperator'::'#include<iostreamh>#include<iomaniph>constdoublePI=voidmain(){constfloatPI=float(::PI)也可用constfloatPI=staticcast<float>(::PI)将在第五章§“类型转换”中详述cout<<setprecision()<<"LocalfloatvalueofPI="<<PI<<"nGlobaldoublevalueofPI="<<::PI<<endl}*Results:LocalfloatvalueofPI=GlobaldoublevalueofPI=*this指针的一个用法是允许依靠返回该类对象的引用值来连续调用该类的成员函数。见下例。例通过this指针连续调用成员函数thiscppCascadingmemberfunctioncalls#include<iostreamh>classTime{inthourintminuteintsecondpublic:Time(){hour=minute=second=}TimesetHour(inthr){hour=(hr>=hr<)hr:return*thisenablescascading}TimesetMinute(intmn){minute=(mn>=mn<)mn:return*thisenablescascading}TimesetSecond(intsc){second=(sc>=sc<)sc:return*thisenablescascading}voidprinttime()const}voidTime::printtime()const{cout<<hour<<":"<<minute<<":"<<second<<endl}voidmain(){Timetcout<<"Timeis:"tsetHour()setMinute()setSecond()printtime()equivalentto:tsetHour()tsetMinute()tsetSecond()tprinttime()}*Result:Timeis:::*在以上程序中的三个成员函数setHour(),setMinute()和setSecond()中都使用语句return*this将this所指向的对象t返回给主函数。由于园点运算符()的结合率为从左向右因此表达式tsetHour()的运行结果是返回t主程序最后语句顺序地执行了四条语句:tsetHour()tsetMinute()tsetSecond()tprinttime()这体现了连续调用的特点。第八章中我们将会看到更多函数连续调用的例子。静态成员在第二章中谈到过各函数之间数据共享的一种方法是使用全局变量。但按照软件工程的观点这不安全应尽量少用。如果只在同一类的多个对象之间实现数据共享则可使用该类的静态数据成员即由同一类的各对象共享该类的静态数据成员。这样的做法可避免使用全局变量所带来的危险。静态成员分为静态数据成员和静态成员函数两种。在编译过程中即使一个类没有建立任何对象其静态成员也已经存在。静态数据成员属于一个类而不属于该类的各对象它由该类中的所有对象所共享。静态数据成员的生命周期是整个程序而其作用域是它被定义时所在的类的各对象。如第二章中所指出的与非静态数据成员不同一个类的静态数据成员被存放于数据区而不是栈区内。静态数据成员既然静态数据成员是一个类中所有对象所共享的因此它不存放在栈区内各对象的内存栈区空间中而是在数据区内。同时它只有一份。而这个类的任何对象都能访问它、将它更新。何以见得?且看以下各例。先看各种成员的地址。例读取非静态数据、静态数据和主程序的地址staticcppToreadtheaddressesofnonstaticdata,staticdataandfunctions#include<iostreamh>classbase{public:intistaticints}intbase::s=thisshouldnotbeomitted,otherwisetherewillbeerrorLNK:unresolvedexternalsymbol"public:staticintbase::s"voidmain(){basebbcout<<"staticmemberbbs="<<bbs<<endlcout<<"addressofstaticmemberbbs="<<bbs<<endlcout<<"addressofnonstaticmemberbbi="<<bbi<<endlcout<<"codeaddressofmain()="<<main<<endlormain}*Results:staticmemberbbs=addressofstaticmemberbbs=xDaddressofnonstaticmemberbbi=xFDFcodeaddressofmain()=x*例四个对象共享一个静态数据staticcppAstaticdatumissharedbyfourobjects#include<iostreamh>classcounter{staticintcount用于表示已建立的对象数目intobjnum用于表示对象序号public:counter(){count每建立一个对象就加一objnum=count为当前对象序号赋值}voidshow(){cout<<"obj"<<objnum<<"created"<<endlcout<<"totally"<<count<<"object(s)"<<endl}}intcounter::countthisshouldnotbeomitted,otherwisetherewillbeerrorLNK:unresolvedexternalsymbol"public:staticintcounter::count"voidmain(){counterobjobjshow()counterobjobjshow()counterobjobjshow()counterobjobjshow()}*Results:objcreatedtotallyobject(s)objcreatedtotallyobject(s)objcreatedtotallyobject(s)objcreatedtotallyobject(s)*四个对象的双区存储内容栈区数据区见staticppt例以上例也可用另一种形式表达如下:staticcppAstaticdatumissharedbytwoobjects#include<iostreamh>classcounter{staticintcount用于表示已建立的对象数intobjnum用于表示对象序号public:counter(){count每建立一个对象就加一objnum=count为当前对象序号赋值}voidshow(){cout<<"obj"<<objnum<<"created"<<endlcout<<"totally"<<count<<"object(s)"<<endl}}intcounter::countthisshouldnotbeomitted,otherwisetherewillbeerrorLNK:unresolvedexternalsymbol"public:staticintcounter::count"voidmain(){counter*ptr=newcounterptr>show()counter*ptr=newcounterptr>show()deleteptrdeleteptr}*Results:objcreatedtotallyobject(s)objcreatedtotallyobject(s)*例两个对象共享静态数据成员staticcppToshowastaticmemberfortwoobjects#include<iostreamh>classMyclass{intx,ystaticintSumpublic:Myclass(inta,intb)intGetSum()}intMyclass::SumTypename"int"mustbeusedObjectsnotcreatedyetIfthisdatumistobeused,thisstatememtshouldnotbeomitted,ortherewillbefatallinkingerror:unresolvedexternalsymbol"private:staticintMyclass::Sum"Myclass::Myclass(inta,intb){cout<<(x=a)<<""<<(y=b)<<endlSum=xy}intMyclass::GetSum(){returnSum}voidmain(){Myclassobj(,)cout<<"sum="<<objGetSum()<<endlMyclassobj(,)cout<<"sum="<<objGetSum()<<endl}*Results:sum=sum=*两个对象的双区存储内容数据区栈区MyclassM(,,)MyclassN(,,)见staticppt静态数据成员的说明、定义和初始化:从上例可以看出静态数据成员在类体内被说明后在被读写之前必须在类体之外被定义进行初始化。(如不显式地初始化为一定值则自动被初始化为零)。静态成员函数类除具有静态数据成员外还具有静态成员函数。后者有何用处?它可在所有对象建立之前或删除之后仍然使用。仍以§的例的staticcpp程序为例如果希望在没有建立对象之前或者删除对象之后检查已有的或剩余的对象数目则须使用静态成员函数。见以下两例。例不使用静态成员函数的情况staticfuncppAstaticdatumissharedbytwoobjects#include<iostreamh>classcounter{staticintcount用于表示已建立的对象数intobjnum用于表示对象序号public:counter(){count每建立一个对象就加一objnum=count为当前对象序号赋值cout<<"obj"<<objnum<<"created"<<endl}voidshow(){cout<<"totally"<<count<<"object(s)"<<endl}}intcounter::countvoidmain(){counter::show()errorC:'counter::show':illegalcallofnonstaticmemberfunctioncounter*ptr=newcounterptr>show()counter*ptr=newcounterptr>show()deleteptrdeleteptr}*Results:objcreatedtotallyobject(s)objcreatedtotallyobject(s)*主函数第一句无法运行是因为此时尚未建立任何对象。无法通过对象来调用任何非静态成员函数。但静态成员函数在对象建立之前就早已存在所以可在对象建立之前调用。如下:例在对象建立之前使用静态成员函数读取静态数据staticfuncppAstaticdatumissharedbytwoobjects#include<iostreamh>classcounter{staticintcount用于表示已建立的对象数intobjnum用于表示对象序号public:counter(){count每建立一个对象就加一objnum=count为当前对象序号赋值cout<<"建立"<<objnum<<"号对象"<<endl}~counter(){count每删除一个对象就减一cout<<"删除"<<objnum<<"号对象"<<endl}staticvoidshow(){cout<<"共有"<<count<<"个对象"<<endl}}intcounter::countvoidmain(){counter::show()counter*ptr=newcounterptr>show()counter*ptr=newcounterptr>show()deleteptrdeleteptrcounter::show()}*Results:共有个对象建立号对象共有个对象建立号对象共有个对象删除号对象删除号对象共有个对象*但静态函数也有不足之处即它无法访问非静态数据。见下例:例静态函数无法访问非静态数据staticfuncppnotworking!!!#include<iostreamh>#include<stringh>classstring{staticinttotallengthintlengthpublic:string(char*s){length=strlen(s)}staticintsettotallength(){totallength=totallengthlengthillegalreferencetodatamember'string::length'inastaticmemberfunctionreturntotallength}}intstring::totallengthvoidmain(){stringobj("TheFirstObject")cout<<objsettotallength()<<endlstringobj("TheSecondObject")cout<<objsettotallength()<<endl}*notworking*上例中程序在编译时出错指出在静态成员函数访问具体对象的数据成员'string::length'时不标明对象名是非法的。下例加以改进:例静态函数用于读取各字符串长度总和staticfuncpp#include<iostreamh>#include<stringh>classstring{staticinttotallengthintlengthpublic:string(char*s){length=strlen(s)}staticintsettotallength(stringobj){totallength=totallengthobjlengthreturntotallength}}intstring::totallengthvoidmain(){stringobj("TheFirstObject")cout<<objsettotallength(obj)<<endlstringobj("TheSecondObject")cout<<string::settotallength(obj)<<endl}*Results:*静态成员的特点:()由于静态成员只是类的成员而不是该类的任何对象的成员因此在编译过程中即使尚未建立对象静态成员就已经存在此时就可以将静态数据成员初始化和调用静态成员函数见中例和例。()静态成员可以继承。基类对象和派生类对象能够共享该静态成员。(见第四章)()静态成员函数不能说明为虚函数。(见第五章)友元友元函数类的封装性将对象隐藏起来用户无法直接访问对象的私有数据和函数(方法)(详见第四章)只能间接地通过类的公有函数来实现。有时须要给个别用户提供直接访问私有成员的条件这可以通过友元方式来实现。当然有利必有弊。这种方式将使程序的可维护性变差。只要将某个外部对象(任何类或任何函数)说明为一个类的友元该外部对象即可访问此对象的私有成员。如果一个类为友元则该类的所有成员都是友元。须要说明为友元时只须在该外部对象名前加上friend关键词即可。例非友元函数不能访问类的私有数据此程序无法工作friendcppNonfriendnonmemberfunctionscannotaccessprivatedataofaclassSothisprogramdoesn’twork#include<iostreamh>ModifiedclassCountof§classCount{public:Count(intj=){x=i}constructorvoiddisplay()const{cout<<x<<endl}private:intxdatamember}FunctiontriestomodifyprivatedataofCount,butcannot,becauseitisneitheramembernorafriendofCountvoidcannotSetX(Countc,intval){cx=valERROR:'x':cannotaccessprivatememberdeclaredinclass'Count'}intmain(){CountcountercannotSetX(counter,)?cannotSetXisneitheramembernorafriendreturn}例只有友元函数才能访问类的私有数据friendcppFriendscanaccessprivatemembersofaclass与以上friendcpp不同之处用不同颜色标出#include<iostreamh>ModifiedclassCountof§classCount{friendvoidsetX(Count,int)frienddeclarationpublic:Count(intj=){x=j}constructorvoiddisplay()const{cout<<x<<endl}private:intxdatamember}setXcanmodifyprivatedataofCount,becauseitisdeclaredasafriendfunctionofCountvoidsetX(Countc,intval){cx=vallegal:setXisafriendofCount}voidmain(){Countcountercout<<"counterxafterinstantiation:"counterdisplay()cout<<"counterxaftercalltofriendfunctionsetX():"setX(counter,)setxwithafriendcounterdisplay()}*Results:counterxafterinstantiation:counterxaftercalltofriendfunctionsetX():*须要指出一个类中所说明的友元函数不是该类的成员而是独立于该类的一个外部函数(它也可以是另一个类的成员函数)。友元类例作为子对象的友元的类可以访问子对象的私有成员friendcppfriendclassYmustbeused,otherwisetwoprivatemembersofclasssubXcannotbeaccessed#include<iostreamh>classsubX{intjstaticintspublic:friendclassYvoidSet(inta){i=a}voidDisplay(){cout<<"i="<<i<<","<<"s="<<s<<endl}}classY{subXobjXapublic:Y(intj,intj)构造函数{objXai=iprivateisubX::s=jprivates}thisrequiresYtobeafriendofsubXvoidDisplay()}intsubX::s=voidY::Display(){cout<<"i="<<objXai<<","<<"s="<<subX::s<<endltwoprivatemembers}voidmain(){subXobjsbobjsbSet()objsbDisplay()Yobjy(,)objyDisplay()objsbDisplay()}*Results:i=,s=i=,s=i=,s=*后有图解两个对象的双区存储内容数据区X::s主程序第一句Xobjsb栈区数据区objsbX::s主程序第二句objsbSet()栈区数据区objsbX::s主程序第四句Yobjy(,)栈区数据区objsbX::sobjy(第三章完)可以开始做思考题建议先在纸上做将概念考虑清楚后必要时再上机校验(不必每题都上机)objsbi=objyobjxai=X::s=pxobjsbi=X::s=NA=NB=NC=Sum=objsbiX::s=Sum=MA=MB=MC=Sum=X::s=base::scountobjobjnumobjobjnumcountobjobjnumobjobjnumcountstaticcounter::coun

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

文档小程序码

使用微信“扫一扫”扫码寻找文档

1

打开微信

2

扫描小程序码

3

发布寻找信息

4

等待寻找结果

我知道了
评分:

/45

C++课件--薛景瑄chapter_3

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利