第四章C++语言中的类数据类型.docx
- 文档编号:9207202
- 上传时间:2023-02-03
- 格式:DOCX
- 页数:29
- 大小:235.09KB
第四章C++语言中的类数据类型.docx
《第四章C++语言中的类数据类型.docx》由会员分享,可在线阅读,更多相关《第四章C++语言中的类数据类型.docx(29页珍藏版)》请在冰豆网上搜索。
第四章C++语言中的类数据类型
第四章C++语言中的类数据类型
部门:
xxx
时间:
xxx
整理范文,仅供参考,可下载自行编辑
习题、思考题与上机实验题:
⒈试判断下列命题的对错:
·C++语言中的struct是只有public区的class。
·在class中如果不定义缺省构造函数,则该class的缺省构造函数便不存在。
·只要包含有private区的的类便具有封装特性。
·类的常数型成员只能用成员初始化表的手段初始赋值。
·delete语句可以删除任何对象。
·如果类内含有静态成员,则类的对象不会有静态成员的拷贝。
⒉若要在例3中增加一修改函数但又不允许类外使用,则这个函数应在何处定义?
⒋能不能在例4中的main(>函数内直接调change(>>呢?
请思考后上机实验。
⒌若用C语言完成例5的运算并将其作用域算符取消会产生何种效果呢?
请上机实验观察。
⒎例11中r指向i后还能在运行时改变指向吗?
请上机实验证。
⒏this指针不能用于对静态成员(数据或函数>的操作或访问,为什么?
⒐例19内的函数gn(>中this->p->name是属于哪个对象的?
返回后所显示的内容是什么?
第四章C++语言中的类数据类型
在所有的C++语言的最基本的概念中,类 读者若已系统地学习过面向对象的分析与程序设计方法,相信学习本章的内容将不会有很大的困难。 在掌握了本章论述的C++语言的最基本的概念之后,再回头重温面向对象的程序设计方法便会更深刻地体会其精深之处了。 b5E2RGbCAP §1类的数据封装 在给出类的定义前先回顾一下C++中的struct的声明过程。 §1.1C++语言中的结构数据类型相对于C语言的变化 在C语言中,struct声明一般是同对其操作的程序代码相分离的。 这种声明上的分离带来了两个弊端。 一是软件维护人员(不一定是设计人>不知道对其操作的函数叫什么? 在何处? 有多少个? 二是一旦修改struct内的成员,对应程序也必需跟着修改。 但这容易造成程序设计人员的疏漏。 对此C++语言作出了质的修改。 C++语言允许将对该结构内的数据成员进行操作的函数也作为其成员在struct内予以声明。 p1EanqFDPw 例1: #include structDt {unsignedintyy,mm,dd。 voiddsp(>{cout< } }。 voidmain(> { Dtd。 d.dsp(>。 } 请读者注意例1中main(>引用Dt的成员函数dsp(>的书写格式与引用其数据成员数据的书写格式完全一致。 C++语言的struct正是后面要讲的class声明的一个特例,类的概念也由此引出。 在以后的章节中,还将多次引用这一概念。 DXDiTa9E3d 要强调的另一点是在C++语言中含有成员函数原形声明的struct必须做为全局结构安排。 因为C++不支持局部函数嵌套的声明结构。 RTCrpUDGiT §1.2C++语言中的类 在例1中声明了一个“structDt”的数据结构,随后又以“Dt”直接去定义一个对象“d”。 此时的“Dt”已如同int,char等数据类型符号一样成了另一种数据类型的定义符号。 深究一下又可以说这个“Dt”是一个用户自定义的一个新类型。 这和在C语言中用typedef来定义除struct、union之外的各种类型符号不同的是“Dt”中的结构完全是由程序员安排的,而typedef产生的符号只能是一个已知的类型符号的派生(Derived>形式。 例中的struct不仅有成员数据而且还包含成员函数,它就是C++语言中的一个类的特例。 5PCzVD7HxA 由此推论,C++语言中的类实际上是一种由用户自定义的数据结构的广义数据类型,它不仅包括有一组结构数据成员而且包括将对这些数据成员进行操作的专用的成员函数。 后者是C语言的结构类型中所不允许的。 在C++语言中的struct虽然也允许插入成员函数,但其内特性与后面即将论述的“class”有着质的不同,至多可以看做是向“class”的过渡。 类作为自定义的数据结构只是一种抽象的描述。 按照面向对象的分析理论,类还具有定义其实体—对象的能力。 在C++语言中,用类定义对象的过程就是按照类所需的容量,动态占用内存资源、复制类中的全部数据结构并向其内部注入各项初始的数值。 类在编程书写时用一个“class”的保留字声明,类名惯用大写字母开头,其格式是: jLBHrnAILg 格式一: class类名{成员名字定义表}[对象名表]。 格式二: class已声明的类名对象名表; 格式三: class已声明的类名; 格式一是用来实现类声明的。 也可以通过跟在其后的对象名表同时声明该类的一至多个对象。 格式二是用已声明好的类名来定义对象(object>的。 格式三是类的引用声明,用于类间引用场合(即先声明后引用>。 格式一的成员名表的结构如下: xHAQX74J0X public: 成员声明区 private: 成员声明区 protected: 成员声明区 这里public、private和protected同class一样都是C++语言的保留字。 在一个class的最前端,如不指明区名则缺省区名为private。 成员声明区内各种成员数据的书写格式与struct内的成员数据书写格式一样。 与之发生操作关系的成员函数只能对声明在同一class内的成员数据进行操作。 LDAYtRyKfE §1.3类的三个区 一个class中的三个区public(公有区>、private(专有区>和protected(保护区>分别对声明于各区中的成员附加了三种不同的属性。 由于protected区与类的派生有极密切的关系,故将其内容合并到第六章论述。 Zzz6ZB2Ltk 一.public区属性 凡声明在该区中的所有成员,当本类对象生成后可以被类外的全局程序或其它类中的成员函数直接访问。 例2: #include classDay { public: unsignedintmm,dd,yy。 voiddateset(unsignedintm=1,unsignedintd=1,unsignedinty=1>dvzfvkwMI1 {mm=m。 dd=d。 yy=y。 } }。 voidmain(> { Daydt。 dt.dateset(>。 cout<<”Date: ”< rqyn14ZNXI }/*main函数访问dt的函数的数据*/ 二.private区属性 凡声明在该区内的所有成员,当本类对象生成后只能被本类的成员函数直接访问。 类外的全局程序则要通过一个定义在public区内的成员函数为媒介间接访问。 如在例2中要想看到日期值就必须在public区内安排一显示函数<如dsp(>)来实现了。 EmxvxOtOco §1.4数据的封装 仔细分析一下class的结构,便会发现private区就如同一个黑盒子。 类外的全局程序不能直接访问而对类内的成员却完全透明。 其形象可由下图表现出来: SixE2yXPq5 InputWindow→public→OutputWindow ↓↑ class外部全局数据区 private 图4-1 图中示意的窗口为class对外的信息交换通道。 可归纳为下述的两个基本特点: ①class中必须有public区以作为class对外的接口。 否则class的对象无法与外界实现数据交换。 6ewMyirQFL ②class中必须有private区以使class内的部分或全部成员可以被封装在其内而不易受到外界的影响。 kavU42VRUs 此时再来看图4-l如同一块集成电路芯片,框内是芯片的内部工作流程,而Window则如同芯片上的管脚(Pin>。 一个class就好像是制造芯片的模具,可以定义出许多具有完全相同内部工作流程和外部管脚排列的具体芯片来。 因此,在C++语言中的class恰是面向对象理论中的数据封装。 但要强调指出的是一个类对象内只有类数据结构的拷贝,但并没有成员函数的拷贝。 当一个类对象诞生时,其内部仅有一张指向类的所有成员函数的地址和状态表,以供运行之需。 class的数据封装特性只是相对于类外世界的,而对同类的不同对象却不起作用。 因此,一个类对象可以通过引用本类的某个成员函数去访问位于另一个同类对象中任何区内的成员数据。 y6v3ALoS89 当回过头再审视一下例1中的struct,若在其中也插人一些函数是否可以形成数据封装呢? 显然它不满足刚提到的特点②,即数据不能隐藏(DataHidden>,也就是说struct之外的程序依然可以自如地对struct内所定义的数据和函数实施访问。 所以只能说它是一个类的特例(无private区>而不是数据封装。 M2ub6vSTnP 例3: #include classST { charname[10]。 unsignedintnum,cnum。 public: voidinit(> { cout<<”\nPleaseinputthedataofstudent: \nNAME: ”。 0YujCfmUCw cin>>name。 cout<<”NUMBER: ”。 cin>>num。 cout>>”CLASSNUMBER: ”。 cin>>cnum。 } voiddsp(> {cout<<”NAME: ”< ”< ”< }eUts8ZQVRd }。 voidmain(> { STs1。 s1.init(>。 s1.dsp(>。 } 例3是一个简单的学生档案处理系统,类ST中只定义了两个成员函数用于对学生数据的初始化和显示。 例4: #include classCount { characcount_num[10]。 intaccount_value。 intchange(> { intval。 cout<<”Pleaseinputthevalue: ”。 cin>>val。 if(val<=account_value> { account_value=account-val。 returnaccount_value。 } else { cout<<”Overflow! ! NotChange! \n”。 return0。 } } public: voidinit(inti>{account_value=i。 } voidaccount_chk(> { cout<<”Pleaseinputtheaccountnumber: “。 cin>>account_num。 cout<<”Takevalue: ”< } }。 voidmain(> { Countct。 ct.init(10000>。 ct.account_chk(>。 ct.account_chk(>。 ct.account_chk(>。 } 例4设想在一个银行中用作取款操作<为节省篇幅而有意取捎了账号查验函数)。 要强调的是为了使用户数据不被类外程序伤害,连同修改函数都放在了private区内定义。 这样使用时必须通过接口函数account_chk(>才能修改,同时也只有这一种流程。 sQsAEJkW5T §1.3类中成员函数的一些特性。 若注意前面的几个例子后会发现定义在class中的成员函数的声明与定义是在一起的。 根据前一章的论述,本应将其声明与定义分离,这里何以又集中了呢? 根本原因在于若在类中将声明与定义合二为一,则成员函数便被编译器自动地认定为内联函数(inlinefunction>。 如果分离,则要在函数的前面插入“inline”关继字才能成为内联函数。 一般地声明与定义合二为一的函数都比较短小,结构也很简单(通常不包含循环>,反之就应当分离。 由于C++语言中每个类成员函数都是有其所属的类作用域范围的,声明与定义分开后则必须在类外再对成员函数进行定义。 此时要使用一个称为类作用域算符“∷”的特殊符号来表明函数的归属。 则类成员函数在类外的通用定义格式为: GMsIasNXkA 返回值类型类名∷函数名(参数表>{过程语句} 如: classA { … public: intinit(int>。 … }。 intA: : init(inti>{…} 若要定义函数为内联的话,可前缀“inline”关键字,则上一行可写成: inlineintA: : init(inti>{…} 实际上,类作用域算符不仅仅用于对类成员函数(包括成员数据>的定义,还可以用于struct和非类属的全局程序作用域中。 若在全局(Global>数据区中定义了与在某函数的局部(Local>数据区中相同的变量名,则程序以局部优先的原则总是先访问当前函数内的局部变量。 而在C++语言中,不同位置的程序可以通过类作用域算符来访问不同作用域的变量<当然首先应有权访问)。 当程序要访问全局数据区中的变量时,只要在变量名前缀以一个“∷”符号即可。 TIrRGchYzg 例5: #include staticinti=10。 voidmain(> { inti。 for(i=0。 i<: : i。 i++>printf(“i=%d\n”,i>。 printf(“Gloabli=%d\n”,: : i>。 } 最后要强调的是“class{…}。 ”全式最后的分号切不可丢失,以免把下一行的内容当作该类的对象处理。 这种疏忽往往要花费许多时间才能查出。 7EqZcWLZNX §2构造函数、析构函数和类对象成员数据的初始化 类对象在生成时必须进行对象成员数据的初始化,但这种过程的处理并非易事。 如程序员如何能够将对象初始数据便捷地传递给类对象? 常数、引用、静态成员数据如何初始化? C++语言对对象成员数据的初始化过程提供了那些功能? 这就是本节要讨论的构造函数、析构函数和成员初始表将涉及的主要问题。 lzq7IGf02E §2.1构造函数(constructorfunction> 若仔细观察前面所举的例子,便会发现各例中定义的成员函数名都与其class名不相同。 如果在class中出现了一个与自身类名相同的成员函数则成为一个具有特殊意义的函数——构造函数。 构造函数除了具有普通成员函数的所有特性外还有下面的几个特殊点: zvpgeqJ1hk ①构造函数名必须和类名相同,且不得声明任何返回值类型。 例6: classStudent { … public: Student(>{…}/*不得声明任何返回值类型*/ }。 ②构造函数可以用形参形式带进各成员数据的初值,也可以重载出多个构造函数。 其中不带任何参数的构造函数又称为缺省构造函数。 当定义不带有任何参数的对象时则会引用缺省构造函数。 若程序员没有特别的需要,可以不必显性的编制出缺省构造函数而由C++编译器将其自动插入到类内。 无论存在多少种重载的构造函数体,构造函数都具有一个特殊功能,这就是测算本类对象所需的静态内存容量并动态占用该容量的内存资源。 因此,当一个类对象生成时,该类的某个构造函数必然被引用。 NrpoJac3v1 ③构造函数并非没有返回值,它所返回的是一个指向其所定义对象的首地址的指针,但不能被传递。 ④若直接引用声明于public区中的构造函数将导致该类的一个新对象的生成,但若没有相关的句柄与之联系,则无法与对象进行通信联系。 1nowfTG4KI ⑤声明于private区中的构造函数只能经由定义在public区中的函数引用。 §2.2构造函数的引用 一.缺省构造函数的引用 对缺省构造函数引用实际上是在用类定义无参数的对象时自动发生的。 例7: #include classDemo { inti。 public: Demo(>{i=0。 cout< } }。 voidmain(> {Demoh。 } 例7中classDemo定义对象h的同时自动引用缺省构造函数Demo(>。 不过为了明确起见,定义对象的格式也可写成“Demoh=Demo(>。 ”但不能写成“Demoh(>。 ”因为C++编译器会认为h(>是Demo的一个对象。 fjnFLDa5Zo 二.非缺省构造函数的引用 对于非缺省构造函数的引用要在定义该类对象时通过所给的参数来指明要引用的具体构造函数。 例8: #include classDemo { inti。 public: Demo(intk>{i=k。 cout< } }。 voidmain(> {Demoh(0>。 }/*或者写成: Demoh=4。 */ 例8中h不是函数而是对象名,括号所包括的4则表示引用构造函数“Demo(intk>”。 §2.3析构函数(DeconstructorFunction> 一个类中的构造函数被引用后要负责为新对象向操作系统申请内存空间。 当引用该类对象的分程序结束时,应当能够释放该对象对内存的占用从而尽量减少内存碎片的出现。 析构函数便是实现这一功能的特殊函数,可以说析构函数是构造函数的清洁工。 析构函数由定义类对象的分程序在其运行结束之前被自动地引用。 析构函数的书写极为简单,只要在构造函数名前附加一个“~”符号即可。 与构造函数一样,析构函数也不得声明任何的返回类值型、不得带有任何参数且不得重载,也不能被置于private区中。 一句话,一个类中只能有一个析构函数。 tfnNhnE6e5 例9: #include classDemo { chars。 public: Demo(>{s=newchar[1024]。 } ~Demo(>{deletes。 } }。 voidmain(> {Demoh。 } 实际上,析构函数除了可以(用delete>释放在构造函数中指明占用的内存外,还要释放整个对象的成员等所占用的内存,这些只是不必写进去罢了。 若未明确声明成员数据的内存占用便可用空函数体表示,甚至可以不写析构函数就缺省存在一个的析构函数。 HbmVN777sL §2.4成员初始化表(MemberInitiationTable> 用构造函数初始化类对象的成员数据不外乎程序直接初始(即引用缺省构造函数>和参数初始两种。 当只做一次较为简单的赋值初始(如一个学生的成绩数据>或对const类的数据的初始时,要么就显得过于庞大(动用大量程序或占用过长的参数表>,要么似乎又不可能。 因此,C++语言又在此基础上专门提供了一个称为成员初始化表的特别手段来简化这种初始过程。 由于此种手段是在编译阶段由编译器将要初始的成员数据与参数建立了对应联系,所以用此法的系统在运行阶段的开销较之其它方法都要小得多。 V7l4jRB8Hs 成员初始化表放在构造函数名与构造函数体之间,用冒号与函数名部分相分隔。 每个表的格式为: 类中成员名(初值>,类中成员名(初值>…, 若一个表中含有多组初始化值可用逗号将彼此分开。 现以§1.2中的综合例l为基础,加入出生日期后列在下面以说明成员初始化表的应用方法。 83lcPA59W9 例10: #include classST { charname[10]。 unsignedintnum,cnum,yy,mm.dd。 public: ST(unsignedinty,unsignedintm,unsignedintd,>: yy(y>,mm(m>,dd(d>mZkklkzaaP { cout<<”\nPleaseinputthedataofstudent: \nName: ”。 AVktR43bpw cin>>name。 cout<<”Number: ”。 cin>>num。 cout<<”ClassNumber: ”。 cin>>cnum。 } voiddsp(> { cout<<”Name: ”< ”< ”< ORjBnOwcEd cout<<”\nBirthdayis”< } }。 voidmain(> { STs1(1974,3,26>。 si.dsp(>。 } 例10中将原来的init(>改为构造函数,出生日期由参数代入,但在构造函数中看不到任何赋值语句。 这里的赋值是由成员初始化表实观的。 当然出生日期也可以在构造函数中临时输入,这里是为了说明成员初始化表的作用才如此安排的。 2MiJTy0dTT 仔细分析一下赋值与初始赋值的区别,就会明白两者有本质的不同。 在例10之前的各例中的赋初值操作是在执行阶段由程序向固定存储单元的变量写入数值的操作,此后仍容许用其它赋值函数修改其内容。 而初始赋值则是对const类的常数仅在编译时完成予留内存单元并同时填入初值,此后不允许再改动。 所以不能在构造函数内使用赋值语句(即等号>在运行阶段对常数或引用类型数据进行赋值。 在C++中语言只有const和引用类型是要在编译时就要指明其初值的。 由于定义在类中的成员都是抽象的数据结构描述,不可能分配内存单元,因此在对构造函数进行编译的阶段也就不可能完成赋初值的操作了。 为了解决这一矛盾,C++语言只有借助于类对象“成员初始化表”的描述区将要赋初值的成员名及初值予先声明,待执行时产生了对象(即分配了内存单元>后再补作上述的赋初值操作。 所以从某种意义上讲,成员初始化表是特意为这两种数据成员准备的也不为过,由此切记成员初始化表不可写在声明语句上。 gIiSpiue7A 引用类型与const类型的初值问题有所不同。 因为编译器不知道在使用时将与哪个对象成员相对应,自然就不允许在class中定义联系。 然而此联系最终又必须确定,否则在运行阶段真的出现了“int&i=3。 ”的错误是没有措施可用的。 所以也只好将其与const类型同等对待了。 uEh0U1Yfmh 例11: #include classDemo { constintl。 inti,&r。 public: Demo(intm,intn>: l(n>,r((int>i>//注: r作为常量l的引用将改变性质,既经r可写lIAg9qLsgBX {i=m。 r++。 cout<<”L=”< } }。 voidmain(> { Demoh(8,10>。 } 类中的const类型成员除了上面的特点以外还可用来临时设定成员函数的操作特性。 凡含有const保留字的成员函数统称为常数型成员函数。 其在类中声明的格式有三种: WwghWvVhPE 防止本函数误写参数变量的: 返回值类型函数名(const参数,const参数,…>; 防止本函数误写类对象内全部的变量的: 返回值类型函数名(参数表>const; 防止其它函数误写返回地址或引用的: co
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第四 C+ 语言 中的 数据类型
![提示](https://static.bdocx.com/images/bang_tan.gif)