《关于类和对象的进一步讨论》由会员分享,可在线阅读,更多相关《关于类和对象的进一步讨论(152页珍藏版)》请在金锄头文库上搜索。
1、1C+程序设计基础程序设计基础东软IT人才实训中心Copyright 2009-2010 By Neusoft Group. All rights reserved胳胳硼硼驶驶晓晓恼恼段段迫迫锋锋阔阔倦倦祖祖飞飞锚锚百百锣锣瘪瘪耶耶悸悸露露菏菏遣遣惰惰乾乾鳃鳃臭臭韦韦沛沛仙仙泉泉皆皆撼撼鸟鸟关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论2总总 目目 录录内容总计:48学时第1章 C+的初步知识1 第2章 数据类型与表达式1第3章 程序设计初步3第4章 函数与预处理2第5章 数组2第6章 指针4第7章 自定义数据类型3第8章 类和对象6第9章 关
2、于类和对象的进一步讨论4第10章 运算符重载4第11章 继承与派生5第12章 多态性与虚函数4第13章 输入输出流5第14章 C+工具4沤沤扇扇筹筹怀怀银银鸽鸽热热涯涯忙忙呢呢札札极极觉觉吐吐鱼鱼赌赌式式购购貌貌夜夜侵侵焚焚毗毗写写裹裹泊泊锁锁赵赵孜孜渤渤带带集集关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论3第第9章章 关于类和对象的进一步讨论关于类和对象的进一步讨论本章主要在向学员介绍:本章主要在向学员介绍:1 构造函数2 析构函数3 调用构造函数和析构函数的顺序4 对象数组5 对象指针6 共用数据的保护7 对象的动态建立和释放8 对象的赋
3、值和复制9 静态成员10 友元11 类模板时间:时间:6 学时学时教学方法:教学方法:PPT+练习练习 葱葱混混察察横横状状黄黄骗骗北北毁毁韵韵羽羽舅舅酮酮悬悬赞赞孩孩酿酿廉廉尉尉府府鸵鸵笛笛岔岔包包品品掷掷日日泄泄碉碉产产谆谆卫卫关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论49.1 构造函数构造函数9.1.1 对象的初始化9.1.2 构造函数的作用9.1.3 带参数的构造函数9.1.4 用参数初始化表初始化成员9.1.5 构造函数的重载9.1.6 使用默认参数的构造函数纶纶追追换换傅傅诀诀年年隙隙红红赏赏偷偷厢厢硝硝垂垂徘徘掠掠棚棚兜兜蚜蚜
4、抽抽辟辟倘倘铰铰颂颂徐徐悬悬参参码码烽烽些些矗矗挽挽皖皖关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论59.1.1 对象的初始化对象的初始化在建立一个对象时,常常需要作某些初始化的工作,例如对数据成员赋初值。如果一个数据成员未被赋值,则它的值是不可预知的,因为在系统为它分配内存时,保留了这些存储单元的原状,这就成为了这些数据成员的初始值。这种状况显然是与人们的要求不相符的,对象是一个实体,它从沉丝凸凼挛锏氖粜(例如时钟的时、分、秒的值),是应该有确定的值的。注意: 类的数据成员是不能在声明类时初始化的。棵棵旅旅夜夜果果滁滁旨旨攻攻眷眷谅谅激激纪
5、纪枣枣彭彭擞擞婿婿闺闺枫枫阴阴吩吩穿穿转转宁宁陛陛右右脉脉颐颐社社宾宾落落啸啸柒柒侗侗关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论69.1.1 对象的初始化对象的初始化(续续)如果一个类中所有的成员都是公用的,则可以在定义对象时对数据成员进行初始化。如class Timepublic: /声明为公用成员 hour; minute; sec;Time t1=14,56,30; /将t1初始化为14:56:30这种情况和结构体变量的初始化是差不多的,在一个花括号内顺序列出各公用数据成员的值,两个值之间用逗号分隔。但是,如果数据成员是私有的,或者类
6、中有private或protected的成员,就不能用这种方法初始化。苦苦勺勺亏亏万万镰镰妹妹缴缴高高铃铃认认莽莽棵棵哀哀宁宁散散吟吟艘艘住住逊逊吏吏钩钩笨笨活活琅琅姨姨洽洽犯犯骄骄还还凭凭梨梨溉溉关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论79.1.1 对象的初始化对象的初始化(续续)在第8章的几个例子中,是用成员函数来对对象中的数据成员赋初值的(例如例8.3中的set_time函数)。从例8.3中可以看到,用户在主函数中调用set_time函数来为数据成员赋值。如果对一个类定义了多个对象,而且类中的数据成员比较多,那么,程序就显得非常臃肿
7、烦琐。鹤鹤祟祟麓麓殉殉间间杭杭哀哀渤渤尸尸床床鲤鲤躺躺愤愤套套双双眯眯棕棕变变蒂蒂者者绕绕掏掏驻驻坏坏访访驶驶龋龋浊浊夜夜申申霉霉拇拇关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论89.1.2 构造函数的作用构造函数的作用为了解决这个问题,C+提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。构造函数的名字必须与类名同名,而不能由用户任意命名,以便编译系统能识别它并把它作为构造函数处理。它不具有任何类型,不返回任何值。构造函数的功能是由用户
8、定义的,用户根据初始化的要求设计函数体和函数参数。规规寂寂奥奥播播匪匪疲疲蹈蹈怜怜拘拘烤烤链链愈愈邵邵因因职职睹睹有有构构迢迢凤凤稼稼嘲嘲曼曼毁毁淹淹例例页页韦韦颐颐吻吻貌貌常常关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论99.1.2 构造函数的作用构造函数的作用(续续)例9.1 在例8.3基础上定义构造成员函数。#include using namespace std;class Timepublic: Time( ) /定义构造成员函数,函数名与类名相同 hour=0; /利用构造函数对对象中的数据成员赋初值 minute=0; sec=
9、0; 怀怀弟弟氨氨撂撂贾贾帮帮村村眠眠脊脊毕毕伪伪尼尼筒筒缆缆惦惦浆浆脓脓夜夜修修沁沁冰冰私私拂拂宅宅要要车车敦敦韩韩怂怂宇宇唯唯佳佳关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论109.1.2 构造函数的作用构造函数的作用(续续) void set_time( ); /函数声明 void show_time( ); /函数声明private: int hour; /私有数据成员 int minute; int sec;void Time:set_time( ) /定义成员函数,向数据成员赋值 cinhour; cinminute; cinse
10、c;趾趾渝渝梭梭利利块块兔兔恬恬亏亏拆拆类类所所砾砾鞍鞍镑镑汗汗端端雾雾齐齐越越秽秽小小诊诊兴兴苹苹廉廉迎迎营营散散寅寅桃桃倚倚汰汰关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论119.1.2 构造函数的作用构造函数的作用(续续)void Time:show_time( ) /定义成员函数,输出数据成员的值 couthour:minute:secendl;int main( ) Time t1; /建立对象t1,同时调用构造函数t1.Time( ) t1.set_time( ); /对t1的数据成员赋值 t1.show_time( ); /显示
11、t1的数据成员的值 Time t2; /建立对象t2,同时调用构造函数t2.Time( ) t2.show_time( ); /显示t2的数据成员的值 return 0;隔隔激激曳曳堰堰嗣嗣领领拇拇念念汉汉氓氓汰汰著著需需泼泼午午欲欲莎莎辰辰弱弱卉卉呵呵烹烹堤堤认认支支葡葡枪枪些些阅阅供供送送曾曾关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论129.1.2 构造函数的作用构造函数的作用(续续)程序运行的情况为: 10 25 54 (从键盘输入新值赋给t1的数据成员)10:25:54 (输出t1的时、分、秒值)0:0:0 (输出t2的时、分、秒值
12、)上面是在类内定义构造函数的,也可以只在类内对构造函数进行声明而在类外定义构造函数。将程序中的第47行改为下面一行: Time( ); /对构造函数进行声明在类外定义构造函数:Time:Time( ) /在类外定义构造成员函数,要加上类名Time和域限定符“:”hour=0; minute=0;sec=0;梦梦读读闯闯嘎嘎梆梆辖辖潜潜侠侠锭锭泪泪菲菲久久浸浸糠糠什什蛛蛛内内嫁嫁缓缓跨跨业业苗苗滦滦苗苗囚囚遮遮驳驳通通淀淀赚赚宣宣隧隧关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论139.1.2 构造函数的作用构造函数的作用(续续)有关构造函数的使
13、用,有以下说明: (1) 在类对象进入其作用域时调用构造函数。(2) 构造函数没有返回值,因此也不需要在定义构造函数时声明类型,这是它和一般函数的一个重要的不同之点。(3) 构造函数不需用户调用,也不能被用户调用。(4) 在构造函数的函数体中不仅可以对数据成员赋初值,而且可以包含其他语句。但是一般不提倡在构造函数中加入与初始化无关的内容,以保持程序的清晰。(5) 如果用户自己没有定义构造函数,则C+系统会自动生成一个构造函数,只是这个构造函数的函数体是空的,也没有参数,不执行初始化操作。印印淆淆耶耶濒濒诉诉悬悬在在语语蜜蜜赎赎毫毫图图叶叶奈奈桓桓睁睁裴裴彝彝萄萄邮邮靴靴耘耘块块费费辕辕沙沙荐荐
14、朴朴盒盒窜窜戎戎仁仁关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论149.1.3 带参数的构造函数带参数的构造函数在例9.1中构造函数不带参数,在函数体中对数据成员赋初值。这种方式使该类的每一个对象都得到同一组初值(例如例9.1中各数据成员的初值均为0)。但是有时用户希望对不同的对象赋予不同的初值。可以采用带参数的构造函数,在调用不同对象的构造函数时,从外面将不同的数据传递给构造函数,以实现不同的初始化。构造函数首部的一般格式为构造函数名(类型 1 形参1,类型2 形参2,)前面已说明: 用户是不能调用构造函数的,因此无法采用常规的调用函数的方
15、法给出实参。实参是在定义对象时给出的。定义对象的一般格式为 类名 对象名(实参1,实参2,);亡亡恋恋腿腿待待投投沾沾亮亮萤萤病病片片儒儒熔熔悼悼复复栏栏泻泻廖廖饰饰晋晋舷舷鞭鞭篓篓至至戴戴龋龋勤勤拔拔选选碱碱城城个个俏俏关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论159.1.3 带参数的构造函数带参数的构造函数(续续)例9.2 有两个长方柱,其长、宽、高分别为: (1)12,20,25;(2)10,14,20。求它们的体积。编一个基于对象的程序,在类中用带参数的构造函数。#include using namespace std;class
16、Boxpublic: Box(int,int,int); /声明带参数的构造函数 int volume( ); /声明计算体积的函数private: int height; 肋肋销销饲饲为为抚抚雏雏阂阂赎赎富富悄悄舜舜胡胡兽兽堆堆颐颐猾猾滁滁式式瓢瓢钩钩和和岿岿框框完完毒毒辐辐汲汲谐谐款款护护垢垢柜柜关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论169.1.3 带参数的构造函数带参数的构造函数(续续) int width; int length;Box:Box(int h,int w,int len) /在类外定义带参数的构造函数 height
17、=h; width=w; length=len;int Box:volume( ) /定义计算体积的函数return(height*width*length);int main( ) /建立对象box1,并指定box1长、宽、高的值 Box box1(12,25,30);燃燃若若回回伴伴奇奇亦亦子子抵抵儒儒芦芦浚浚寝寝对对整整晴晴薪薪混混敢敢堰堰嘴嘴但但飘飘裹裹凹凹协协援援必必呜呜铁铁玩玩胜胜奉奉关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论179.1.3 带参数的构造函数带参数的构造函数(续续) coutThe volume of box1
18、is box1.volume( )endl; /建立对象box2,并指定box2长、宽、高的值 Box box2(15,30,21); coutThe volume of box2 is box2.volume( )endl; return 0;程序运行结果如下: The volume of box1 is 9000The volume of box2 is 9450可以知道: (1) 带参数的构造函数中的形参,其对应的实参在定义对象时给定。(2) 用这种方法可以方便地实现对不同的对象进行不同的初始化。淆淆蛹蛹衣衣甫甫财财创创欣欣齐齐云云接接堵堵畜畜锻锻枣枣页页症症褂褂肚肚兹兹造造串串遇遇迟迟
19、咖咖盒盒碉碉愉愉松松贰贰磁磁绿绿否否关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论189.1.4 用参数初始化表初始化成员用参数初始化表初始化成员在9.1.3节中介绍的是在构造函数的函数体内通过赋值语句对数据成员实现初始化。C+还提供另一种初始化数据成员的方法参数初始化表来实现对数据成员的初始化。这种方法不在函数体内对数据成员初始化,而是在函数首部实现。例如例9.2中定义构造函数可以改用以下形式: Box:Box(int h,int w,int len):height(h),width(w),length(len) 这种写法方便、简练,尤其当需
20、要初始化的数据成员较多时更显其优越性。甚至可以直接在类体中(而不是在类外)定义构造函数。联联坠坠樊樊刑刑糜糜粒粒敏敏险险筹筹旅旅铂铂囱囱唁唁果果家家据据症症谬谬色色迎迎诉诉韭韭贿贿只只刚刚价价组组暇暇资资撂撂责责蝶蝶关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论199.1.5 构造函数的重载构造函数的重载在一个类中可以定义多个构造函数,以便对类对象提供不同的初始化的方法,供用户选用。这些构造函数具有相同的名字,而参数的个数或参数的类型不相同。这称为构造函数的重载。在第4章第4.6节中所介绍的函数重载的知识也适用于构造函数。通过下面的例子可以了解
21、怎样应用构造函数的重载。逢逢蔡蔡理理拯拯至至饲饲舞舞副副晰晰舞舞稿稿十十赴赴庄庄巳巳革革钠钠缎缎窍窍移移闪闪兽兽蹬蹬哦哦垢垢铁铁撑撑翁翁调调狗狗虚虚挥挥关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论209.1.5 构造函数的重载构造函数的重载(续续)例9.3 在例9.2的基础上,定义两个构造函数,其中一个无参数,一个有参数。#include using namespace std;class Boxpublic: Box( ); /声明一个无参的构造函数 Box(int h,int w,int len):height(h),width(w),l
22、ength(len) /声明一个有参的构造函数,用参数的初始化表对数据成员初始化 int volume( );private: int height;答答用用卿卿呛呛沉沉凋凋猖猖帮帮砷砷匀匀毯毯铜铜摄摄苦苦很很邱邱杯杯偶偶谋谋梢梢松松隅隅呼呼拉拉云云去去洋洋叙叙寸寸腆腆锦锦笆笆关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论219.1.5 构造函数的重载构造函数的重载(续续) int width; int length;Box:Box( ) /定义一个无参的构造函数 height=10; width=10; length=10;int Box:v
23、olume( ) return(height*width*length);int main( ) Box box1; /建立对象box1,不指定实参位位绪绪落落剿剿灯灯突突魁魁予予几几务务坝坝氨氨坚坚风风妈妈烽烽辽辽作作参参什什舶舶账账涤涤卢卢饮饮譬譬诞诞蓝蓝狠狠顷顷咆咆琢琢关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论229.1.5 构造函数的重载构造函数的重载(续续) coutThe volume of box1 is box1.volume( )endl; Box box2(15,30,25); /建立对象box2,指定3个实参 cout
24、The volume of box2 is box2.volume( )endl; return 0; 在本程序中定义了两个重载的构造函数,其实还可以定义其他重载构造函数,其原型声明可以为Box:Box(int h); /有1个参数的构造函数Box:Box(int h,int w); /有两个参数的构造函数在建立对象时分别给定1个参数和2个参数。照照境境劫劫铁铁哇哇赘赘率率啦啦泡泡堤堤艳艳恒恒避避恤恤辗辗珐珐真真膏膏琼琼盏盏利利咳咳认认岛岛庞庞涪涪凿凿坠坠秽秽典典巷巷靶靶关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论239.1.5 构造函数的重
25、载构造函数的重载(续续)说明: (1) 调用构造函数时不必给出实参的构造函数,称为默认构造函数(default constructor)。显然,无参的构造函数属于默认构造函数。一个类只能有一个默认构造函数。(2) 如果在建立对象时选用的是无参构造函数,应注意正确书写定义对象的语句。(3) 尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象时只执行其中一个构造函数,并非每个构造函数都被执行。更更饮饮遵遵祟祟亲亲旭旭仍仍彪彪恭恭酉酉藩藩苫苫拌拌倘倘郁郁释释墩墩拱拱讼讼贷贷虹虹恼恼街街蠕蠕软软仆仆绦绦丢丢阶阶址址负负蔗蔗关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和
26、和对对象象的的进进一一步步讨讨论论249.1.6 使用默认参数的构造函数使用默认参数的构造函数构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参值,编译系统就使形参取默认值。在第4章第4.8节中介绍过在函数中可以使用有默认值的参数。在构造函数中也可以采用这样的方法来实现初始化。例9.3的问题也可以使用包含默认参数的构造函数来处理。 例9.4 将例9.3程序中的构造函数改用含默认值的参数,长、宽、高的默认值均为10。在例9.3程序的基础上改写如下: #include using namespace std;class Boxpublic: / 在声明构造函数时指定
27、默认参数 Box(int h=10,int w=10,int len=10); 绽绽衰衰俭俭村村征征股股迁迁帐帐谍谍效效奖奖芜芜慨慨血血朋朋巍巍蓄蓄散散幼幼冷冷填填烹烹绘绘母母裳裳炊炊硫硫界界脾脾石石并并阀阀关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论259.1.6 使用默认参数的构造函数使用默认参数的构造函数(续续)int volume( );private: int height; int width; int length;Box:Box(int h,int w,int len) /在定义函数时可以不指定默认参数 height=h; w
28、idth=w; length=len;int Box:volume( )return(height*width*length);抖抖乳乳瞒瞒普普篇篇狙狙墅墅哭哭勺勺陇陇我我弃弃卖卖硫硫怨怨梳梳商商丧丧村村襄襄衷衷世世妙妙弛弛伞伞耐耐漂漂惠惠枢枢贾贾箩箩杉杉关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论269.1.6 使用默认参数的构造函数使用默认参数的构造函数(续续)int main( ) Box box1; /没有给实参 coutThe volume of box1 is box1.volume( )endl; Box box2(15); /
29、只给定一个实参 coutThe volume of box2 is box2.volume( )endl; Box box3(15,30); /只给定2个实参 coutThe volume of box3 is box3.volume( )endl; Box box4(15,30,20); /给定3个实参 coutThe volume of box4 is box4.volume( )endl; return 0;玲玲硬硬嚼嚼碰碰箱箱捕捕业业螺螺项项滚滚纬纬滴滴二二渴渴恋恋汉汉用用禹禹睁睁唤唤沦沦孙孙厂厂纤纤高高陷陷洛洛鸥鸥婆婆袄袄蜘蜘弥弥关关于于类类和和对对象象的的进进一一步步讨讨论论关关于
30、于类类和和对对象象的的进进一一步步讨讨论论279.1.6 使用默认参数的构造函数使用默认参数的构造函数(续续)程序运行结果为The volume of box1 is 1000The volume of box2 is 1500The volume of box3 is 4500The volume of box4 is 9000程序中对构造函数的定义(第1216行)也可以改写成参数初始化表的形式: Box:Box(int h,int w,int len):height(h),width(w),length(len) 可以看到: 在构造函数中使用默认参数是方便而有效的,它提供了建立对象时的多种
31、选择,它的作用相当于好几个重载的构造函数。它的好处是: 即使在调用构造函数时没有提供实参值,不仅不会出错,而且还确保按照默认的参数值对对象进行初始化。尤其在希望对蚂蚂较较驾驾梭梭砒砒怜怜募募廓廓给给搁搁邻邻邪邪艳艳涸涸愈愈侥侥椽椽净净图图醋醋瞩瞩匆匆慌慌坦坦昨昨储储忆忆叼叼庄庄枢枢慨慨渍渍关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论289.1.6 使用默认参数的构造函数使用默认参数的构造函数(续续)每一个对象都有同样的初始化状况时用这种方法更为方便。说明: (1) 应该在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。(2) 程
32、序第5行在声明构造函数时,形参名可以省略。(3) 如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。(4) 在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。铅铅比比原原诀诀休休杏杏募募邮邮政政汽汽牙牙那那忠忠恢恢伤伤讽讽匝匝斩斩窘窘金金拼拼付付再再结结欢欢团团牵牵胃胃栖栖疗疗鸯鸯幻幻关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论299.2 析构函数析构函数析构函数(destructor)也是一个特殊的成员函数,它的作用与构造函数相反,它的名字是类名的前面加一个“”符号。在C+中“”是
33、位取反运算符,从这点也可以想到: 析构函数是与构造函数作用相反的函数。当对象的生命期结束时,会自动执行析构函数。具体地说如果出现以下几种情况,程序就会执行析构函数: 如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。窿窿绳绳据据驰驰输输儡儡岛岛媚媚汾汾磺磺天天贵贵吾吾隋隋淫淫扮扮燥燥削削至至捷捷淡淡婉婉痊痊椿椿掸掸付付聂聂瘦瘦午午惯惯致致毋毋关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论309.2 析构函数析构函数(续续)static局部对象在函数调用结束时对象并不释放,因此
34、也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或调用exit函数) 时,调用该全局对象的析构函数。如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。程序设计者事先设计好析构函数,以完成所需的功能,只要对象的生命期结束,程序就自动执行析构函数来完成这些工作。踢踢碳碳米米里里惠惠褐褐才才狠狠破破请请剃剃梗
35、梗樊樊抖抖催催岁岁荆荆碟碟里里显显逝逝煮煮醛醛敲敲米米粗粗嘻嘻左左玲玲蠢蠢辣辣腾腾关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论319.2 析构函数析构函数(续续)析构函数不返回任何值,没有函数类型,也没有函数参数。因此它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。实际上,析构函数的作用并不仅限于释放资源方面,它还可以被用来执行“用户希望在最后一次使用对象之后所执行的任何操作”,例如输出有关的信息。这里说的用户是指类的设计者,因为,析构函数是在声明类的时候定义的。也就是说,析构函数可以完成类的设计者所指定的任何操作。一般情况下,
36、类的设计者应当在声明类的同时定义析构函数,以指定如何完成“清理”的工作。如果用户没有定义析构函数,C+编译系统会自动生成一个析构函数,但它只是徒有析构函数的名称和形式,实际上什么操作都不进行。想让析构函数完成任何工作,都必须在定义的析构函数中指定。治治筏筏咕咕瑰瑰去去蠢蠢崖崖扩扩旗旗陀陀偏偏扒扒钡钡匿匿烫烫迪迪柿柿原原釉釉窗窗矫矫咒咒演演厂厂虎虎乌乌帘帘瘪瘪玛玛注注弦弦爱爱关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论329.2 析构函数析构函数(续续)例9.5 包含构造函数和析构函数的C+程序。#include#includeusing na
37、mespace std;class Student /声明Student类public: student(int n,string nam,char s ) /定义构造函数 num=n; name=nam; sex=s; coutConstructor called.endl; /输出有关信息 留留戒戒圭圭清清眺眺豺豺吼吼痴痴糜糜酒酒北北返返西西盔盔识识磷磷蒸蒸揍揍度度标标瘴瘴箭箭尸尸笆笆秦秦腹腹纶纶赂赂态态志志具具餐餐关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论339.2 析构函数析构函数(续续) Student( ) /定义析构函数 co
38、utDestructor called.endl; /输出有关信息 void display( ) /定义成员函数 coutnum: numendl; coutname: nameendl; coutsex: sexendlendl; private: int num; char name10; char sex;咱咱氏氏潞潞福福俗俗烦烦到到破破邓邓零零艘艘蚕蚕沪沪湿湿篇篇卷卷卷卷械械渍渍取取戌戌责责嘴嘴摊摊纲纲永永辰辰履履票票菲菲谦谦仑仑关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论349.2 析构函数析构函数(续续)int main( )
39、Student stud1(10010,Wang_li,f); /建立对象stud1 stud1.display( ); /输出学生1的数据 Student stud2(10011,Zhang_fun,m); /定义对象stud2 stud2.display( ); /输出学生2的数据 return 0;程序运行结果如下: Constructor called. (执行stud1的构造函数)num: 10010 (执行stud1的display函数)name:Wang_lisex: f改改牙牙寐寐烙烙焉焉御御榷榷乃乃窖窖愈愈袱袱挺挺旧旧熟熟赖赖棱棱焕焕韶韶澳澳蜀蜀妨妨眠眠姥姥慰慰涉涉报报秆秆勘
40、勘赎赎抓抓粟粟帝帝关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论359.2 析构函数析构函数(续续)Constructor called. (执行stud2的构造函数)num: 10011 (执行stud2的display函数)name:Zhang_funsex:mDestructor called. (执行stud2的析构函数)Destructor called. (执行stud1的析构函数)俺俺屎屎区区咙咙绚绚像像脓脓绅绅世世掇掇犊犊瞄瞄痔痔斩斩口口吨吨蛤蛤纱纱倚倚弯弯户户共共脱脱勺勺淡淡柯柯挛挛骡骡条条辑辑状状欠欠关关于于类类和和对对象象
41、的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论369.3 调用构造函数和析构函数顺序调用构造函数和析构函数顺序在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用顺序。在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反: 最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。如图9.1示意。哟哟舌舌潦潦庄庄壬壬惊惊臣臣笔笔崖崖彻彻鸽鸽村村砾砾铭铭肛肛通通摸摸家家寅寅夺夺刀刀微微绰绰热热因因价价稻稻癌癌出出合合证证帧帧关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象
42、象的的进进一一步步讨讨论论379.3 调用构造函数和析构函数顺序调用构造函数和析构函数顺序(续续)图9.1嘶嘶醛醛孜孜企企钙钙逮逮暴暴圈圈溅溅忱忱猾猾直直状状聋聋宿宿瞳瞳烘烘硷硷脱脱谅谅蝗蝗厘厘罚罚薄薄汲汲饿饿折折特特缴缴搓搓弛弛氛氛关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论389.3 调用构造函数和析构函数顺序调用构造函数和析构函数顺序(续续)但是,并不是在任何情况下都是按这一原则处理的。在第4章第4.11和4.12节中曾介绍过作用域和存储类别的概念,这些概念对于对象也是适用的。对象可以在不同的作用域中定义,可以有不同的存储类别。这些会影
43、响调用构造函数和析构函数的时机。下面归纳一下什么时候调用构造函数和析构函数: (1) 在全局范围中定义的对象(即在所有函数之外定义的对象),它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时(此时程序终止),调用析构函数。盛盛图图伍伍检检慑慑彬彬说说做做度度氏氏妙妙遁遁卞卞木木擂擂会会柜柜膊膊荤荤挂挂勇勇坏坏丘丘营营炉炉绒绒蔗蔗拆拆舀舀氦氦骸骸奎奎关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨
44、论论399.3 调用构造函数和析构函数顺序调用构造函数和析构函数顺序(续续)(2) 如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。(3) 如果在函数中定义静态(static)局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。构造函数和析构函数在面向对象的程序设计中是相当重要的。以上介绍了最基本的、使用最多的普通构造函数,在本章第9.8节中
45、将会介绍复制构造函数,在第10章第10.7节中还要介绍转换构造函数。谅谅娠娠峨峨嗜嗜翻翻拳拳霹霹脖脖亚亚腋腋臂臂驮驮洗洗降降贸贸后后纸纸蔚蔚甫甫辙辙貌貌骂骂缠缠荐荐癌癌否否诞诞疹疹权权柄柄率率政政关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论409.4 对象数组对象数组数组不仅可以由简单变量组成(例如整型数组的每一个元素都是整型变量),也可以由对象组成(对象数组的每一个元素都是同类的对象)。在日常生活中,有许多实体的属性是共同的,只是属性的具体内容不同。例如一个班有50个学生,每个学生的属性包括姓名、性别、年龄、成绩等。如果为每一个学生建立一个
46、对象,需要分别取50个对象名。用程序处理很不方便。这时可以定义一个“学生类”对象数组,每一个数组元素是一个“学生类”对象。例如 Student stud50; /假设已声明了Student类,定义stud数组,有50个元素益益斗斗娥娥株株麓麓务务调调埋埋匀匀概概灼灼晚晚均均黔黔褒褒伐伐何何掷掷围围桶桶登登岿岿卡卡算算缺缺播播乖乖兹兹桌桌叶叶侯侯挝挝关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论419.4 对象数组对象数组(续续)在建立数组时,同样要调用构造函数。如果有50个元素,需要调用50次构造函数。在需要时可以在定义数组时提供实参以实现初始
47、化。如果构造函数只有一个参数,在定义数组时可以直接在等号后面的花括号内提供实参。如/合法,3个实参分别传递给3个数组元素的构造函数Student stud3=60,70,78; 如果构造函数有多个参数,则不能用在定义数组时直接提供所有实参的方法,因为一个数组有多个元素,对每个元素要提供多个实参,如果再考虑到构造函数有默认参数的情况,很容易造成实参与形参的对应关系不清晰,出现歧义性。例如,类Student的构造函数有多个参数,且为默认参数: /定义构造函数,有多个参数,且为默认参数Student: Student(int=1001,int=18,int=60); 般般冗冗汁汁筹筹摔摔鹤鹤膘膘良良
48、呼呼娘娘窄窄秀秀靡靡角角费费越越肾肾脏脏隆隆瓮瓮车车砍砍磺磺莱莱灾灾置置鉴鉴朋朋吁吁飞飞厢厢砂砂关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论429.4 对象数组对象数组(续续)如果定义对象数组的语句为Student stud3=1005,60,70;在程序中最好不要采用这种容易引起歧义性的方法。编译系统只为每个对象元素的构造函数传递一个实参,所以在定义数组时提供的实参个数不能超过数组元素个数,如 /不合法,实参个数超过对象数组元素个数Student stud3=60,70,78,45; 那么,如果构造函数有多个参数,在定义对象数组时应当怎样实
49、现初始化呢?回答是: 在花括号中分别写出构造函数并指定实参。如果构造函数有3个参数,分别代表学号、年龄、成绩。则可以这样定义对象数组: Student Stud3= /定义对象数组/调用第1个元素的构造函数,为它提供3个实参Student(1001,18,87),蝶蝶窄窄予予泄泄丹丹武武乌乌括括喘喘密密朴朴走走茎茎献献芽芽似似谴谴部部禄禄及及外外购购奉奉椿椿锚锚嫁嫁贰贰蓉蓉侄侄物物锥锥另另关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论439.4 对象数组对象数组(续续)/调用第2个元素的构造函数,为它提供3个实参Student(1002,19,
50、76), /调用第3个元素的构造函数,为它提供3个实参Student(1003,18,72);在建立对象数组时,分别调用构造函数,对每个元素初始化。每一个元素的实参分别用括号包起来,对应构造函数的一组形参,不会混淆。例9.6 对象数组的使用方法。#include using namespace std;class Box玖玖翟翟农农瑞瑞锌锌羽羽煎煎庞庞韵韵村村埃埃钢钢俏俏痉痉南南沸沸荷荷燥燥酥酥湍湍猛猛羊羊佃佃环环潭潭酝酝点点侠侠底底枝枝兔兔娇娇关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论449.4 对象数组对象数组(续续)public: B
51、ox(int h=10,int w=12,int len=15): height(h),width(w),length(len) /声明有默认参数的构造函数,用参数初始化表对数据成员初始化 int volume( );private: int height; int width; int length;int Box:volume( )return(height*width*length);的的求求锈锈衣衣武武褂褂妨妨溜溜番番藕藕卿卿局局芍芍双双揉揉虱虱笛笛顽顽非非愧愧空空躺躺霉霉蛾蛾惮惮屹屹薛薛腿腿啊啊垢垢足足通通关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进
52、进一一步步讨讨论论459.4 对象数组对象数组(续续)int main( ) Box a3= /定义对象数组 Box(10,12,15), /调用构造函数Box,提供第1个元素的实参 Box(15,18,20), /调用构造函数Box,提供第2个元素的实参 Box(16,20,26) /调用构造函数Box,提供第3个元素的实参 ; /调用a0的volume函数 coutvolume of a0 is a0.volume( )endl; /调用a1 的volume函数 coutvolume of a1 is a1.volume( )endl; /调用a2 的volume函数 coutvolume
53、 of a2 is a2.volume( )endl; 猛猛悦悦夷夷括括米米抡抡掖掖栖栖赠赠鲸鲸氨氨蠢蠢梨梨挨挨洛洛阑阑溜溜贸贸忿忿谴谴翻翻误误恩恩肯肯评评廊廊键键戮戮咀咀斋斋久久设设关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论469.4 对象数组对象数组(续续)运行结果如下: volume of a0 is 1800volume of a1 is 5400volume of a2 is 8320博博肋肋柔柔呻呻萄萄樱樱佐佐趁趁丽丽窒窒啸啸花花辖辖筒筒栋栋拜拜休休灸灸蚁蚁端端莽莽涌涌朵朵哩哩泽泽雪雪畏畏孩孩锌锌涌涌痔痔稍稍关关于于类类和和对对
54、象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论479.5 对象指针对象指针9.5.1 指向对象的指针9.5.2 指向对象成员的指针9.5.3 this 指针母母噶噶沏沏镍镍鸵鸵扮扮饵饵驭驭肄肄汉汉谨谨易易苛苛怯怯瑞瑞芒芒懒懒婿婿抹抹拇拇决决椒椒感感绎绎李李两两访访缸缸寇寇侄侄谍谍减减关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论489.5.1 指向对象的指针指向对象的指针在建立对象时,编译系统会为每一个对象分配一定的存储空间,以存放其成员。对象空间的起始地址就是对象的指针。可以定义一个指针变量,用来存放对象的指针。
55、如果有一个类:class Timepublic: int hour; int minute; int sec; void get_time( );童童囱囱诽诽甚甚狗狗驳驳嘱嘱琐琐毡毡初初彬彬瞪瞪竖竖撂撂艇艇幕幕绽绽后后琶琶浸浸畸畸辞辞孙孙赵赵蚌蚌磁磁群群打打班班叶叶氨氨栋栋关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论499.5.1 指向对象的指针指向对象的指针(续续)void Time:get_time( ) couthour:minute:sechour/调用pt所指向的对象中的get_time函数,即t1.get_time(*pt).ge
56、t_time ( )/调用pt所指向的对象中的get_time函数,即t1.get_timept-get_time ( )薄薄巾巾二二齐齐掸掸匀匀欺欺爵爵苗苗搓搓坎坎薛薛捣捣朋朋椿椿材材鞘鞘傅傅鼎鼎饲饲晦晦董董蚂蚂扮扮酣酣闻闻以以酿酿泛泛琐琐锣锣蜀蜀关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论519.5.2 指向对象成员的指针指向对象成员的指针对象有地址,存放对象初始地址的指针变量就是指向对象的指针变量。对象中的成员也有地址,存放对象成员地址的指针变量就是指向对象成员的指针变量。仔仔循循拉拉倘倘蹭蹭苗苗扳扳瓤瓤龟龟趣趣裴裴磨磨彰彰滨滨鹃鹃召召
57、准准调调煮煮江江纠纠籍籍辫辫形形事事酸酸曹曹浇浇半半伐伐稠稠苯苯关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论529.5.2 指向对象成员的指针指向对象成员的指针(续续)1. 指向对象数据成员的指针定义指向对象数据成员的指针变量的方法和定义指向普通变量的指针变量方法相同。例如int *p1; /定义指向整型数据的指针变量定义指向对象数据成员的指针变量的一般形式为数据类型名 *指针变量名;如果Time类的数据成员hour为公用的整型数据,则可以在类外通过指向对象数据成员的指针变量访问对象数据成员hour。/将对象t1的数据成员hour的地址赋给p
58、1,p1指向t1.hourp1=&t1.hour; cout*p1endl; /输出t1.hour的值熙熙武武柒柒玖玖镣镣顺顺灸灸妆妆胖胖耿耿彻彻帕帕钻钻员员嘶嘶痹痹虎虎刊刊转转喳喳拙拙官官之之蛰蛰稀稀囊囊疲疲秆秆救救湾湾喜喜石石关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论539.5.2 指向对象成员的指针指向对象成员的指针(续续)2. 指向对象成员函数的指针需要提醒读者注意: 定义指向对象成员函数的指针变量的方法和定义指向普通函数的指针变量方法有所不同。成员函数与普通函数有一个最根本的区别: 它是类中的一个成员。编译系统要求在上面的赋值语句
59、中,指针变量的类型必须与赋值号右侧函数的类型相匹配,要求在以下3方面都要匹配: 函数参数的类型和参数个数;函数返回值的类型;所属的类。定义指向成员函数的指针变量应该采用下面的形式: /定义p2为指向Time类中公用成员函数的指针变量void (Time:*p2)( ); 镰镰月月嘴嘴肥肥苫苫辩辩谤谤匡匡庸庸酷酷涎涎追追序序现现静静鹅鹅施施俄俄棺棺声声效效蛹蛹资资娜娜滨滨岩岩宣宣亏亏掇掇骗骗槽槽蔡蔡关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论549.5.2 指向对象成员的指针指向对象成员的指针(续续)定义指向公用成员函数的指针变量的一般形式为
60、数据类型名 (类名:*指针变量名)(参数表列);可以让它指向一个公用成员函数,只需把公用成员函数的入口地址赋给一个指向公用成员函数的指针变量即可。如 p2=&Time:get_time;使指针变量指向一个公用成员函数的一般形式为 指针变量名=&类名:成员函数名;例9.7 有关对象指针的使用方法。#include using namespace std;class Time炊炊橱橱巫巫染染仁仁鱼鱼舍舍毫毫灸灸盘盘据据走走谈谈首首毙毙映映叭叭疯疯控控伤伤谎谎悦悦尺尺惶惶看看吾吾迟迟赐赐鼓鼓费费裕裕猾猾关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论5
61、59.5.2 指向对象成员的指针指向对象成员的指针(续续)public: Time(int,int,int); int hour; int minute; int sec; void get_time( ); /声明公有成员函数;void Time:get_time( ) /定义公有成员函数couthour:minute: secendl;int main( ) 漆漆檀檀南南珠珠桐桐讹讹蛙蛙去去殖殖奉奉统统雌雌笺笺拧拧凯凯殴殴轨轨星星吱吱瞩瞩哈哈筑筑梁梁唾唾委委沏沏烟烟韭韭平平屿屿助助抠抠关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论569.5
62、.2 指向对象成员的指针指向对象成员的指针(续续) Time t1(10,13,56); /定义Time类对象t1 /定义指向整型数据的指针变量p1,并使p1指向t1.hour int *p1=&t1.hour; cout*p1get_time( ); /调用p2所指向对象(即t1)的get_time函数 void (Time:*p3)( ); /定义指向Time类公用成员函数的指针变量p3 p3=&Time:get_time; /使p3指向Time类公用成员函数get_time (t1.*p3)( ); /调用对象t1中p3所指的成员函数(即t1.get_time( )户户壳壳某某踏踏透透共
63、共朱朱娟娟诲诲父父獭獭轧轧普普潍潍矢矢帅帅钳钳摇摇赶赶谴谴弯弯崔崔桓桓盖盖剪剪弘弘冉冉坊坊杜杜邀邀磊磊疚疚关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论579.5.2 指向对象成员的指针指向对象成员的指针(续续)程序运行结果为10 (main函数第4行的输出)10:13:56 (main函数第5行的输出)10:13:56 (main函数第7行的输出)10:13:56 (main函数第10行的输出)可以看到为了输出t1中hour,minute和sec的值,可以采用3种不同的方法。说明: (1) 从main函数第9行可以看出: 成员函数的入口地址的
64、正确写法是: &类名:成员函数名。(2) main函数第8、9两行可以合写为一行: /定义指针变量时指定其指向void (Time:*p3)( )=&Time:get_time; 碰碰萨萨汛汛臭臭辜辜奇奇公公芝芝主主狗狗穷穷篱篱潦潦艳艳矾矾占占嚷嚷烈烈薯薯险险策策抡抡榆榆刺刺金金靴靴坍坍颁颁妙妙媚媚旧旧泰泰关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论589.5.3 this 指针指针在第8章中曾经提到过: 每个对象中的数据成员都分别占有存储空间,如果对同一个类定义了n个对象,则有n组同样大小的空间以存放n个对象中的数据成员。但是,不同对象都调
65、用同一个函数代码段。那么,当不同对象的成员函数引用数据成员时,怎么能保证引用的是所指定的对象的数据成员呢?假如,对于例9.6程序中定义的Box类,定义了3个同类对象a,b,c。如果有a.volume( ),应该是引用对象a中的height,width和length,计算出长方体a的体积。如果有b.volume( ),应该是引用对象b中的height,width和length,计算出长方体b的体积。而现在都用同一个函数段,系统怎样使它分别引用a或b中的数据成员呢?净净呆呆适适刊刊滇滇拂拂抉抉屿屿侥侥剂剂紧紧峭峭藉藉别别计计奋奋帜帜汾汾亩亩霄霄锣锣英英知知拒拒这这猖猖绰绰瓮瓮溯溯砒砒烙烙宴宴关关于
66、于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论599.5.3 this 指针指针(续续)在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this。它是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。例如,当调用成员函数a.volume时,编译系统就把对象a的起始地址赋给this指针,于是在成员函数引用数据成员时,就按照this的指向找到对象a的数据成员。例如volume函数要计算height*width*length的值,实际上是执行: (this-height)*(this-width)*(this-len
67、gth)由于当前this指向a,因此相当于执行: (a.height)*(a.width)*(a.length)这就计算出长方体a的体积。同样如果有b.volume( ),编译系统就把对象b的起始地址赋给成员函数volume的this指针,显然计算出来的是长方体b的体积。挠挠铸铸屎屎华华乔乔肄肄子子姆姆壳壳咱咱讲讲馁馁蠕蠕则则笋笋镍镍旬旬纳纳赌赌诌诌历历楞楞毛毛矛矛稻稻掐掐傻傻谰谰奥奥菌菌旧旧厘厘关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论609.5.3 this 指针指针(续续)this指针是隐式使用的,它是作为参数被传递给成员函数的。本来
68、,成员函数volume的定义如下: int Box:volume( )return (height*width*length); C+把它处理为int Box:volume(Box *this)return(this-height * this-width * this-length); 即在成员函数的形参表列中增加一个this指针。在调用该成员函数时,实际上是用以下方式调用的: a.volume(&a);将对象a的地址传给形参this指针。然后按this的指向去引用其他成员。赠赠叙叙博博场场练练概概享享葱葱闺闺湍湍夹夹价价统统漓漓阿阿箭箭裴裴性性架架播播乖乖破破埃埃糯糯跪跪陶陶摔摔榔榔杀杀打
69、打嘶嘶浊浊关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论619.5.3 this 指针指针(续续)需要说明: 这些都是编译系统自动实现的,编程序者不必人为地在形参中增加this指针,也不必将对象a的地址传给this指针。在需要时也可以显式地使用this指针。例如在Box类的volume函数中,下面两种表示方法都是合法的、相互等价的。return(height * width * length); /隐含使用this指针return(this-height * this-width * this-length); /显式使用this指针 可以用*
70、this表示被调用的成员函数所在的对象,*this就是this所指向的对象,即当前的对象。例如在成员函数a.volume( )的函数体中,如果出现*this,它就是本对象a。上面的return语句也可写成 return(*this).height * (*this).width * (*this).length);斩斩血血子子坟坟袜袜惰惰瞩瞩赦赦食食飘飘绘绘恍恍喝喝袁袁牟牟均均片片麻麻飞飞膏膏尸尸孝孝舆舆伶伶迫迫肝肝甜甜挡挡绽绽炎炎骚骚吃吃关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论629.5.3 this 指针指针(续续)注意*this两侧
71、的括号不能省略,不能写成*this.height。所谓“调用对象a的成员函数f”,实际上是在调用成员函数f时使this指针指向对象a,从而访问对象a的成员。在使用“调用对象a的成员函数f”时,应当对它的含义有正确的理解。即即割割榴榴亩亩罪罪帽帽怨怨少少直直篇篇尊尊糕糕缀缀旭旭芹芹必必焙焙锑锑论论尖尖札札蔑蔑菌菌横横谭谭忍忍凝凝迫迫孜孜旬旬边边陕陕关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论639.6 共用数据的保护共用数据的保护C+虽然采取了不少有效的措施(如设private保护)以增加数据的安全性,但是有些数据却往往是共享的,人们可以在不同
72、的场合通过不同的途径访问同一个数据对象。有时在无意之中的误操作会改变有关数据的状况,而这是人们所不希望出现的。既要使数据能在一定范围内共享,又要保证它不被任意修改,这时可以使用const,即把有关的数据定义为常量。音音递递弃弃盗盗芋芋服服颗颗饯饯碑碑德德罢罢肠肠碳碳塌塌习习史史经经姿姿掩掩佃佃彻彻芹芹现现百百冯冯呆呆暮暮郊郊惜惜展展撼撼其其关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论649.6.1 常对象常对象在定义对象时指定对象为常对象。常对象必须要有初值,如Time const t1(12,34,46); /t1是常对象这样,在所有的场合
73、中,对象t1中的所有成员的值都不能被修改。凡希望保证数据成员不被改变的对象,可以声明为常对象。定义常对象的一般形式为类名 const 对象名(实参表列);也可以把const写在最左面: const 类名 对象名(实参表列);二者等价。跟跟晒晒鲤鲤盖盖芭芭虹虹痔痔涵涵憨憨巍巍遍遍通通厢厢髓髓黍黍聂聂腆腆鲤鲤砧砧房房倾倾蝇蝇百百祭祭享享庄庄勒勒适适钢钢娩娩短短彤彤关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论659.6.1 常对象常对象(续续)如果一个对象被声明为常对象,则不能调用该对象的非const型的成员函数(除了由系统自动调用的隐式的构造函数
74、和析构函数)。例如,对于例9.7中已定义的Time类,如果有const Time t1(10,15,36); /定义常对象t1t1.get_time( ); /企图调用常对象t1中的非const型成员函数,非法这是为了防止这些函数会修改常对象中数据成员的值。不能仅依靠编程者的细心来保证程序不出错,编译系统充分考虑到可能出现的情况,对不安全的因素予以拦截。现在,编译系统只检查函数的声明,只要发现调用了常对象的成员函数,而且该函数未被声明为const,就报错,提请编程者注意。引用常对象中的数据成员很简单,只需将该成员函数声明为const即可。如 幽幽豹豹采采秆秆运运晾晾于于膊膊韭韭竹竹恿恿炉炉更更
75、陕陕煌煌冬冬皱皱观观频频荒荒韧韧狐狐道道瀑瀑眉眉卷卷聂聂涸涸贼贼莹莹橡橡股股关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论669.6.1 常对象常对象(续续)void get_time( ) const; /将函数声明为const这表示get_time是一个const型函数,即常成员函数。常成员函数可以访问常对象中的数据成员,但仍然不允许修改常对象中数据成员的值。有时在编程时有要求,一定要修改常对象中的某个数据成员的值,ANSI C+考虑到实际编程时的需要,对此作了特殊的处理,对该数据成员声明为mutable,如 mutable int cou
76、nt;把count声明为可变的数据成员,这样就可以用声明为const的成员函数来修改它的值。痪痪警警克克僧僧沧沧骨骨诚诚彬彬溃溃引引戳戳权权锣锣踢踢酱酱畦畦诡诡隧隧制制弗弗蚀蚀枉枉神神虑虑遥遥书书追追贱贱私私算算望望序序关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论679.6.2 常对象成员常对象成员可以将对象的成员声明为const,包括常数据成员和常成员函数。巴巴饭饭写写裕裕许许荐荐氢氢临临附附薪薪澎澎宵宵逆逆催催萄萄尖尖矿矿园园舜舜绒绒窜窜职职涨涨峻峻药药蒙蒙堆堆即即顺顺唯唯掣掣技技关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类
77、类和和对对象象的的进进一一步步讨讨论论689.6.2 常对象成员常对象成员(续续)1. 常数据成员其作用和用法与一般常变量相似,用关键字const来声明常数据成员。常数据成员的值是不能改变的。有一点要注意: 只能通过构造函数的参数初始化表对常数据成员进行初始化。如在类体中定义了常数据成员hour:const int hour; /声明hour为常数据成员不能采用在构造函数中对常数据成员赋初值的方法。在类外定义构造函数,应写成以下形式: /通过参数初始化表对常数据成员hour初始化Time:Time(int h):hour(h)常对象的数据成员都是常数据成员,因此常对象的构造函数只能用参数初始化
78、表对常数据成员进行初始化。嫩嫩匿匿瞎瞎浴浴睡睡扶扶掩掩豹豹凯凯羡羡濒濒朋朋沏沏挛挛烈烈岂岂监监示示榆榆岗岗段段汀汀记记鲸鲸掇掇塌塌骡骡痕痕齿齿订订仙仙票票关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论699.6.2 常对象成员常对象成员(续续)2. 常成员函数 前面已提到: 一般的成员函数可以引用本类中的非const数据成员,也可以修改它们。如果将成员函数声明为常成员函数,则只能引用本类中的数据成员,而不能修改它们,例如只用于输出数据等。如 void get_time( ) const; /注意const的位置在函数名和括号之后const是函数
79、类型的一部分,在声明函数和定义函数时都要有const关键字,在调用时不必加const。常成员函数可以引用const数据成员,也可以引用非const的数据成员。const数据成员可以被const成员函数引用,也可以被非const的成员函数引用。具体情况可以用书中表9.1表示。怎样利用常成员函数呢?抢抢前前皱皱哥哥詹詹略略兑兑飘飘脚脚诫诫奄奄口口昆昆父父芦芦绣绣哉哉例例谐谐等等蛮蛮被被逛逛抡抡尝尝纹纹撮撮烬烬正正析析您您豁豁关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论709.6.2 常对象成员常对象成员(续续)(1) 如果在一个类中,有些数据成员
80、的值允许改变,另一些数据成员的值不允许改变,则可以将一部分数据成员声明为const,以保证其值不被改变,可以用非const的成员函数引用这些数据成员的值,并修改非const数据成员的值。(2) 如果要求所有的数据成员的值都不允许改变,则可以将所有的数据成员声明为const,或将对象声明为const(常对象),然后用const成员函数引用数据成员,这样起到“双保险”的作用,切实保证了数据成员不被修改。句句少少粗粗江江忽忽紧紧谦谦踞踞青青端端厩厩僧僧干干憋憋炼炼韵韵蚀蚀砸砸绸绸烈烈粟粟党党了了李李权权硷硷巷巷满满澄澄酷酷南南耍耍关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象
81、象的的进进一一步步讨讨论论719.6.2 常对象成员常对象成员(续续)(3) 如果已定义了一个常对象,只能调用其中的const成员函数,而不能调用非const成员函数(不论这些函数是否会修改对象中的数据)。这是为了保证数据的安全。如果需要访问对象中的数据成员,可将常对象中所有成员函数都声明为const成员函数,但应确保在函数中不修改对象中的数据成员。不要误认为常对象中的成员函数都是常成员函数。常对象只保证其数据成员是常数据成员,其值不被修改。如果在常对象中的成员函数未加const声明,编译系统把它作为非const成员函数处理。还有一点要指出: 常成员函数不能调用另一个非const成员函数。纂纂
82、姑姑凤凤卿卿馆馆捂捂荷荷惕惕钡钡姑姑渊渊添添嘻嘻邻邻妈妈俺俺袁袁纲纲袱袱砷砷觅觅券券蹭蹭宿宿羡羡式式豪豪钓钓等等榷榷例例雇雇关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论729.6.3 指向对象的常指针指向对象的常指针将指针变量声明为const型,这样指针值始终保持为其初值,不能改变。如Time t1(10,12,15),t2; /定义对象/const位置在指针变量名前面,规定ptr1的值是常值Time * const ptr1; ptr1=&t1; /ptr1指向对象t1,此后不能再改变指向ptr1=&t2; /错误,ptr1不能改变指向定义
83、指向对象的常指针的一般形式为类名 * const 指针变量名;也可以在定义指针变量时使之初始化,如将上面第2,3行合并为Time * const ptr1=&t1; /指定ptr1指向t1芍芍挟挟公公掐掐撬撬蛹蛹抡抡亭亭魂魂佯佯斥斥载载缀缀玛玛狼狼拘拘经经男男釉釉拎拎策策添添骂骂林林俗俗折折镀镀岿岿煞煞爹爹砷砷潜潜关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论739.6.3 指向对象的常指针指向对象的常指针(续续)请注意: 指向对象的常指针变量的值不能改变,即始终指向同一个对象,但可以改变其所指向对象(如t1)的值。如果想将一个指针变量固定地与
84、一个对象相联系(即该指针变量始终指向一个对象),可以将它指定为const型指针变量。往往用常指针作为函数的形参,目的是不允许在函数执行过程中改变指针变量的值,使其始终指向原来的对象。衬衬樟樟卜卜垫垫景景鲜鲜肢肢蛋蛋仰仰达达络络晋晋握握肄肄课课苯苯吃吃通通营营氖氖链链湖湖腮腮钟钟袋袋冲冲淑淑汤汤饼饼俏俏肯肯津津关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论749.6.4 指向常对象的指针变量指向常对象的指针变量为了更容易理解指向常对象的指针变量的概念和使用,首先了解指向常变量的指针变量,然后再进一步研究指向常对象的指针变量。下面定义了一个指向常变
85、量的指针变量ptr: const char *ptr;注意const的位置在最左侧,它与类型名char紧连,表示指针变量ptr指向的char变量是常变量,不能通过ptr来改变其值的。定义指向常变量的指针变量的一般形式为const 类型名 *指针变量名;赐赐旧旧喀喀荤荤窟窟拦拦寐寐巾巾强强湃湃烤烤衫衫雅雅滨滨模模缠缠移移耶耶窃窃顶顶寺寺浸浸剿剿疫疫砷砷缨缨主主庙庙癣癣驱驱澳澳孤孤关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论759.6.4 指向常对象的指针变量指向常对象的指针变量(续续)说明: (1) 如果一个变量已被声明为常变量,只能用指向常变
86、量的指针变量指向它,而不能用一般的(指向非const型变量的)指针变量去指向它。(2) 指向常变量的指针变量除了可以指向常变量外,还可以指向未被声明为const的变量。此时不能通过此指针变量改变该变量的值。如果希望在任何情况下都不能改变c1的值,则应把它定义为const型。(3) 如果函数的形参是指向非const型变量的指针,实参只能用指向非const变量的指针,而不能用指向const变量的指针,这样,在执行函数的过程中可以改变形参指针变量所指向的变量(也就是实参指针所指向的变量)的值。韧韧卡卡贸贸攻攻断断亭亭州州茁茁拍拍虹虹叠叠佐佐驭驭贾贾职职本本待待矿矿查查匪匪硼硼犹犹匪匪殉殉钡钡萝萝滴滴
87、港港盟盟妒妒舆舆楚楚关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论769.6.4 指向常对象的指针变量指向常对象的指针变量(续续)如果函数的形参是指向const型变量的指针,在执行函数过程中显然不能改变指针变量所指向的变量的值,因此允许实参是指向const变量的指针,或指向非const变量的指针。使用形参和实参的对应关系见书中表9.2。以上的对应关系与在(2)中所介绍的指针变量和其所指向的变量的关系是一致的: 指向常变量的指针变量可以指向const和非const型的变量,而指向非const型变量的指针变量只能指向非const的变量。以上介绍的是
88、指向常变量的指针变量,指向常对象的指针变量的概念和使用是与此类似的,只要将“变量”换成“对象”即可。虑虑札札韩韩艰艰冯冯富富逞逞吞吞斧斧佩佩糖糖奋奋舞舞抠抠孺孺乞乞熙熙瓣瓣篷篷睁睁遭遭晾晾壶壶劈劈题题龟龟德德互互够够感感豌豌端端关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论779.6.4 指向常对象的指针变量指向常对象的指针变量(续续)(1) 如果一个对象已被声明为常对象,只能用指向常对象的指针变量指向它,而不能用一般的(指向非const型对象的)指针变量去指向它。(2) 如果定义了一个指向常对象的指针变量,并使它指向一个非const的对象,则
89、其指向的对象是不能通过指针来改变的。如果希望在任何情况下t1的值都不能改变,则应把它定义为const型。(3) 指向常对象的指针最常用于函数的形参,目的是在保护形参指针所指向的对象,使它在函数执行过程中不被修改。估估挪挪柠柠樟樟粉粉眩眩醉醉绢绢盖盖遇遇年年推推塞塞乒乒扇扇烦烦劈劈躇躇懒懒骆骆误误切切殴殴裁裁云云痪痪蟹蟹账账黔黔普普类类惕惕关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论789.6.4 指向常对象的指针变量指向常对象的指针变量(续续)请记住这样一条规则: 当希望在调用函数时对象的值不被修改,就应当把形参定义为指向常对象的指针变量,同
90、时用对象的地址作实参(对象可以是const或非const型)。如果要求该对象不仅在调用函数过程中不被改变,而且要求它在程序执行过程中都不改变,则应把它定义为const型。(4) 如果定义了一个指向常对象的指针变量,是不能通过它改变所指向的对象的值的,但是指针变量本身的值是可以改变的。淋淋谩谩紊紊荔荔织织弟弟贩贩衣衣硬硬椒椒凋凋拷拷陛陛胶胶昔昔僵僵讹讹蝉蝉夕夕任任智智巨巨尘尘郝郝旱旱识识教教芍芍贡贡撰撰钞钞揣揣关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论799.6.5 对象的常引用对象的常引用过去曾介绍: 一个变量的引用就是变量的别名。实质上,
91、变量名和引用名都指向同一段内存单元。如果形参为变量的引用名,实参为变量名,则在调用函数进行虚实结合时,并不是为形参另外开辟一个存储空间(常称为建立实参的一个拷贝),而是把实参变量的地址传给形参(引用名),这样引用名也指向实参变量。笑笑铝铝符符起起岂岂哄哄腻腻镐镐魁魁罢罢躬躬豌豌赶赶寿寿诚诚李李换换绚绚痒痒方方深深饰饰扶扶毖毖止止酮酮嫡嫡彦彦肩肩掂掂键键围围关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论809.6.5 对象的常引用对象的常引用(续续)例9.8 对象的常引用。#include using namespace std;class Ti
92、mepublic: Time(int,int,int); int hour; int minute; int sec;Time:Time(int h,int m,int s) /定义构造函数瓢瓢请请彰彰顷顷怠怠摹摹怨怨炭炭矫矫虚虚遵遵禄禄渍渍秧秧绍绍犀犀桔桔传传是是鸣鸣纪纪授授媳媳旷旷恃恃圈圈匈匈妮妮叹叹欣欣拈拈迪迪关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论819.6.5 对象的常引用对象的常引用(续续) hour=h; minute=m; sec=s;void fun(Time &t) /形参t是Time类对象的引用 t.hour=18;
93、int main( ) Time t1(10,13,56); / t1是Time类对象 /实参是Time类对象,可以通过引用来修改实参t1的值 fun(t1); coutt1.hourendl; /输出t1.hour的值为18 return 0;率率练练影影宜宜秀秀云云凉凉募募庐庐身身谓谓随随莲莲泰泰闲闲总总藏藏辩辩庙庙褥褥炕炕簧簧墒墒互互桂桂赴赴晋晋籍籍吾吾详详灵灵式式关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论829.6.5 对象的常引用对象的常引用(续续)如果不希望在函数中修改实参t1的值,可以把引用变量t声明为const(常引用),函
94、数原型为 void fun(const Time &t);则在函数中不能改变t的值,也就是不能改变其对应的实参t1的值。在C+面向对象程序设计中,经常用常指针和常引用作函数参数。这样既能保证数据安全,使数据不能被随意修改,在调用函数时又不必建立实参的拷贝。用常指针和常引用作函数参数,可以提高程序运行效率。谭谭斤斤硅硅休休念念镣镣肮肮场场朽朽银银活活将将霍霍诅诅帮帮沮沮三三唐唐乘乘型型弊弊室室娘娘迪迪请请骤骤枣枣棒棒远远屏屏危危舌舌关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论839.6.6 const型数据的小结型数据的小结表9.3形式形式含义
95、含义Time const t1; t1是常对象,其值在任何情况下都不能改变是常对象,其值在任何情况下都不能改变void Time:fun( )constfun是是Time类类中中的的常常成成员员函函数数,可可以以引引用用,但不能修改本类中的数据成员但不能修改本类中的数据成员Time * const p;p是是指指向向Time对对象象的的常常指指针针,p的的值值(即即p的的指向指向)不能改变不能改变const Time *p;p是是指指向向Time类类常常对对象象的的指指针针,其其指指向向的的类类对象的值不能通过指针来改变对象的值不能通过指针来改变Time &t1=t;t1是是Time类类对对象
96、象t的的引引用用,二二者者指指向向同同一一段段内存空间内存空间骂骂遥遥漆漆悦悦夷夷逾逾缝缝撬撬涡涡返返淤淤蓑蓑诸诸抿抿油油獭獭贮贮测测知知睁睁迂迂佑佑芽芽太太瑟瑟枪枪亚亚向向叠叠插插袱袱缕缕关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论849.7 对象的动态建立和释放对象的动态建立和释放用前面介绍的方法定义的对象是静态的,在程序运行过程中,对象所占的空间是不能随时释放的。但有时人们希望在需要用到对象时才建立对象,在不需要用该对象时就撤销它,释放它所占的内存空间以供别的数据使用。这样可提高内存空间的利用率。在第7章7.1.7节中介绍了用new运算
97、符动态地分配内存,用delete运算符释放这些内存空间。这也适用于对象,可以用new运算符动态建立对象,用delete运算符撤销对象。如果已经定义了一个Box类,可以用下面的方法动态地建立一个对象: 唆唆统统王王棉棉苹苹郊郊睡睡败败茨茨轨轨邮邮队队烦烦泣泣钡钡恬恬葱葱饮饮茄茄炬炬萨萨苏苏承承洱洱灼灼阮阮朽朽姆姆檀檀坝坝叫叫攘攘关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论859.7 对象的动态建立和释放对象的动态建立和释放(续续)new Box;编译系统开辟了一段内存空间,并在此内存空间中存放一个Box类对象,同时调用该类的构造函数,以使该对象
98、初始化(如果已对构造函数赋予此功能的话)。但是此时用户还无法访问这个对象,因为这个对象既没有对象名,用户也不知道它的地址。这种对象称为无名对象,它确实是存在的,但它没有名字。用new运算符动态地分配内存后,将返回一个指向新对象的指针的值,即所分配的内存空间的起始地址。用户可以获得这个地址,并通过这个地址来访问这个对象。需要定义一个指向本类的对象的指针变量来存放该地址。如Box *pt; /定义一个指向Box类对象的指针变量ptpt=new Box; /在pt中存放了新建对象的起始地址峭峭冻冻沪沪操操跋跋幅幅挖挖逻逻必必摆摆润润笔笔棕棕鼓鼓蔑蔑廓廓剂剂众众近近琐琐尽尽丫丫弥弥毛毛温温溺溺宁宁外外
99、厘厘皋皋朽朽幢幢关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论869.7 对象的动态建立和释放对象的动态建立和释放(续续)在程序中就可以通过pt访问这个新建的对象。如 coutheight; /输出该对象的height成员/调用该对象的volume函数,计算并输出体积coutvolume( ); C+还允许在执行new时,对新建立的对象进行初始化。如 Box *pt=new Box(12,15,18);这种写法是把上面两个语句(定义指针变量和用new建立新对象)合并为一个语句,并指定初值。这样更精炼。新对象中的height,width和leng
100、th分别获得初值12,15,18。调用对象既可以通过对象名,也可以通过指针。用new建立的动态对象一般是不用对象名的,是通过指针访问的,它主要应用于动态的数据结构,如链表。访问链表中的结点,并不需要通过对象名,畅畅扰扰娠娠溅溅萤萤官官叉叉秩秩淡淡拳拳嫁嫁强强群群汾汾韦韦斥斥愚愚概概斋斋瞒瞒笆笆胰胰萎萎梳梳侮侮赔赔颤颤焉焉眉眉厦厦射射获获关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论879.7 对象的动态建立和释放对象的动态建立和释放(续续)而是在上一个结点中存放下一个结点的地址,从而由上一个结点找到下一个结点,构成链接的关系。在执行new运算时
101、,如果内存量不足,无法开辟所需的内存空间,目前大多数C+编译系统都使new返回一个0指针值。只要检测返回值是否为0,就可判断分配内存是否成功。ANSI C+标准提出,在执行new出现故障时,就“抛出”一个“异常”,用户可根据异常进行有关处理。但C+标准仍然允许在出现new故障时返回0指针值。当前,不同的编译系统对new故障的处理方法是不同的。在不再需要使用由new建立的对象时,可以用delete运算符予以释放。如delete pt; /释放pt指向的内存空间滓滓唐唐淖淖芒芒抒抒大大斜斜翘翘刃刃妖妖钠钠慎慎须须愚愚姜姜航航丁丁脸脸凉凉恫恫夹夹甭甭尹尹煽煽叹叹彤彤咏咏劫劫噪噪清清祟祟酥酥关关于于类
102、类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论889.7 对象的动态建立和释放对象的动态建立和释放(续续)这就撤销了pt指向的对象。此后程序不能再使用该对象。如果用一个指针变量pt先后指向不同的动态对象,应注意指针变量的当前指向,以免删错了对象。在执行delete运算符时,在释放内存空间之前,自动调用析构函数,完成有关善后清理工作。另另挽挽湍湍懊懊睫睫恕恕竞竞憋憋吨吨铡铡浆浆琉琉暑暑晦晦仕仕奈奈刑刑咋咋诡诡抢抢慌慌械械衫衫罐罐丸丸偏偏俞俞妇妇矿矿调调衬衬梭梭关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论8
103、99.8 对象的赋值和复制对象的赋值和复制9.8.1 对象的赋值9.8.2 对象的复制冻冻孵孵浸浸抢抢字字帖帖即即缚缚擒擒增增优优拳拳冠冠拭拭诉诉棍棍所所采采读读肩肩兜兜岛岛指指氨氨薯薯酵酵玛玛卒卒钎钎慑慑嫉嫉煽煽关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论909.8.1 对象的赋值对象的赋值如果对一个类定义了两个或多个对象,则这些同类的对象之间可以互相赋值,或者说,一个对象的值可以赋给另一个同类的对象。这里所指的对象的值是指对象中所有数据成员的值。对象之间的赋值也是通过赋值运算符“=”进行的。本来,赋值运算符“=”只能用来对单个的变量赋值,
104、现在被扩展为两个同类对象之间的赋值,这是通过对赋值运算符的重载实现的。实际这个过程是通过成员复制来完成的,即将一个对象的成员值一一复制给另一对象的对应成员。对象赋值的一般形式为涕涕痕痕身身菊菊穴穴权权捂捂惋惋棒棒令令脂脂枢枢钙钙荫荫墨墨转转销销锁锁瓶瓶源源耪耪朴朴胜胜春春闸闸腑腑土土肛肛簇簇栏栏昭昭蹭蹭关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论919.8.1 对象的赋值对象的赋值(续续)对象名1 = 对象名2;注意对象名1和对象名2必须属于同一个类。例如Student stud1,stud2; /定义两个同类的对象stud2=stud1;
105、/将stud1赋给stud2通过下面的例子可以了解怎样进行对象的赋值。例9.9 对象的赋值。#include using namespace std;class Boxpublic:倦倦幻幻店店斗斗集集瓦瓦豪豪二二认认梭梭奸奸迈迈纸纸勋勋酥酥龙龙庆庆员员州州濒濒瑟瑟到到本本霉霉虞虞淀淀拈拈柏柏侥侥鲤鲤榨榨喷喷关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论929.8.1 对象的赋值对象的赋值(续续) Box(int=10,int=10,int=10); /声明有默认参数的构造函数 int volume( );private: int height
106、; int width; int length;Box:Box(int h,int w,int len) height=h; width=w; length=len;屉屉程程俐俐丙丙马马苟苟莎莎臆臆胸胸攻攻鳞鳞领领芯芯沿沿共共仲仲破破枪枪工工洋洋劝劝姐姐砰砰直直拈拈崩崩邢邢吉吉炯炯湿湿瞪瞪袒袒关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论939.8.1 对象的赋值对象的赋值(续续) int Box:volume( )return(height*width*length); /返回体积int main( ) Box box1(15,30,25),
107、box2; /定义两个对象box1和box2 coutThe volume of box1 is box1.volume( )endl; box2=box1; /将box1的值赋给box2 coutThe volume of box2 is box2.volume( )endl; return 0;骨骨始始顿顿束束玉玉卵卵桂桂撵撵跟跟董董颁颁兼兼此此览览钱钱劣劣桨桨磕磕末末挺挺砚砚耳耳叠叠莉莉孝孝抵抵取取眯眯橱橱躲躲咐咐界界关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论949.8.1 对象的赋值对象的赋值(续续)运行结果如下: The volu
108、me of box1 is 11250The volume of box2 is 11250说明: (1) 对象的赋值只对其中的数据成员赋值,而不对成员函数赋值。(2) 类的数据成员中不能包括动态分配的数据,否则在赋值时可能出现严重后果。臆臆识识瀑瀑黎黎温温粱粱轰轰海海距距蔡蔡帝帝夕夕澄澄御御挎挎筒筒璃璃姻姻存存洒洒净净翘翘玻玻演演避避屯屯靖靖捣捣亿亿旦旦送送竣竣关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论959.8.2 对象的复制对象的复制有时需要用到多个完全相同的对象。此外,有时需要将对象在某一瞬时的状态保留下来。这就是对象的复制机制。用
109、一个已有的对象快速地复制出多个完全相同的对象。如Box box2(box1);其作用是用已有的对象box1去克隆出一个新对象box2。其一般形式为类名 对象2(对象1);用对象1复制出对象2。可以看到: 它与前面介绍过的定义对象方式类似,但是括号中给出的参数不是一般的变量,而是对象。在建立对象时调用一个特殊的构造函数复制构造函数(copy constructor)。这个函数的形式是这样的: 雀雀汛汛伊伊在在帛帛打打笨笨爪爪使使早早赘赘谐谐遮遮参参切切隔隔又又秧秧粉粉梧梧汰汰来来岛岛敢敢权权潦潦栖栖甭甭垒垒焊焊皇皇爸爸关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进
110、进一一步步讨讨论论969.8.2 对象的复制对象的复制(续续)/The copy constructor definition.Box:Box(const Box& b) height=b.height; width=b.width; length=b.length;复制构造函数也是构造函数,但它只有一个参数,这个参数是本类的对象(不能是其他类的对象),而且采用对象的引用的形式(一般约定加const声明,使参数值不能改变,以免在调用此函数时因不慎而使对象值被修改)。此复制构造函数的作用就是将实参对象的各成员值一一赋给新的对象中对应的成员。盏盏滞滞帽帽窑窑梅梅瘪瘪亿亿边边酪酪已已碴碴脊脊盼盼吭吭
111、窖窖亢亢达达击击绘绘彩彩盖盖挚挚劝劝庙庙勤勤谚谚菩菩卯卯鱼鱼政政峡峡它它关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论979.8.2 对象的复制对象的复制(续续)回顾复制对象的语句Box box2(box1);这实际上也是建立对象的语句,建立一个新对象box2。由于在括号内给定的实参是对象,因此编译系统就调用复制构造函数(它的形参也是对象),而不会去调用其他构造函数。实参box1的地址传递给形参b(b是box1的引用),因此执行复制构造函数的函数体时,将box1对象中各数据成员的值赋给box2中各数据成员。如果用户自己未定义复制构造函数,则编译
112、系统会自动提供一个默认的复制构造函数,其作用只是简单地复制类中每个数据成员。诣诣危危浆浆距距氯氯歼歼瑶瑶拭拭渍渍宙宙售售藤藤疆疆必必效效鼠鼠厅厅嘛嘛劳劳猛猛拱拱化化凤凤减减闭闭撞撞均均级级帛帛瞬瞬抽抽仪仪关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论989.8.2 对象的复制对象的复制(续续)C+还提供另一种方便用户的复制形式,用赋值号代替括号,如Box box2=box1; /用box1初始化box2其一般形式为类名 对象名1 = 对象名2;可以在一个语句中进行多个对象的复制。如Box box2=box1,box3=box2;按box1来复制
113、box2和box3。可以看出: 这种形式与变量初始化语句类似,请与下面定义变量的语句作比较: int a=4,b=a;这种形式看起来很直观,用起来很方便。但是其作用都是调用复制构造函数。紊紊振振蓬蓬盅盅赐赐眯眯皆皆垦垦循循欧欧蔡蔡拈拈朔朔虾虾辖辖客客惑惑块块铀铀径径任任臭臭领领友友伟伟钧钧观观浇浇园园坊坊贬贬天天关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论999.8.2 对象的复制对象的复制(续续)请注意对象的复制和9.8.1节介绍的对象的赋值在概念上和语法上的不同。对象的赋值是对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋
114、值。而对象的复制则是从无到有地建立一个新对象,并使它与一个已有的对象完全相同(包括对象的结构和成员的值)。可以对例9.7程序中的主函数作一些修改:int main( ) Box box1(15,30,25); /定义box1 coutThe volume of box1 is box1.volume( )endl; Box box2=box1,box3=box2; /按box1来复制box2,box3 coutThe volume of box2 is box2.volume( )endl;常常杆杆盾盾饶饶慰慰勺勺聚聚襟襟竟竟嫩嫩丈丈惯惯浓浓忆忆哗哗拂拂位位腥腥鞘鞘浆浆劈劈个个卒卒战战呀呀贞贞
115、丢丢疫疫嫌嫌渊渊熙熙催催关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1009.8.2 对象的复制对象的复制(续续) coutThe volume of box3 is box3.volume( )endl;执行完第3行后,3个对象的状态完全相同。请注意普通构造函数和复制构造函数的区别。(1) 在形式上类名(形参表列); /普通构造函数的声明,如Box(int h,int w,int len);类名(类名& 对象名); /复制构造函数的声明,如Box(Box &b);(2) 在建立对象时,实参类型不同。系统会根据实参的类型决定调用普通构造函数或
116、复制构造函数。如Box box1(12,15,16); /实参为整数,调用普通构造函数Box box2(box1); /实参是对象名,调用复制构造函数 六六定定卢卢滑滑釜釜唤唤坡坡痊痊灿灿闹闹膀膀岳岳鼠鼠毁毁预预倚倚厩厩懦懦芯芯棱棱廷廷狙狙滩滩真真讥讥蔬蔬需需驼驼帮帮雷雷厌厌钒钒关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1019.8.2 对象的复制对象的复制(续续)(3) 在什么情况下被调用普通构造函数在程序中建立对象时被调用。复制构造函数在用已有对象复制一个新对象时被调用,在以下3种情况下需要克隆对象: 程序中需要新建立一个对象,并用另一
117、个同类的对象对它初始化,如前面介绍的那样。 当函数的参数为类的对象时。在调用函数时需要将实参对象完整地传递给形参,也就是需要建立一个实参的拷贝,这就是按实参复制一个形参,系统是通过调用复制构造函数来实现的,这样能保证形参具有和实参完全相同的值。如 典典穴穴婿婿祸祸耘耘滁滁箍箍碎碎墩墩狱狱镀镀瓦瓦镰镰搔搔暮暮据据矣矣魏魏震震螟螟阿阿谊谊棒棒撂撂耶耶盏盏艾艾逆逆职职枢枢欢欢哭哭关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1029.8.2 对象的复制对象的复制(续续)void fun(Box b) /形参是类的对象 int main( ) Box
118、box1(12,15,18); /实参是类的对象,调用函数时将复制一个新对象b fun(box1); return 0; 函数的返回值是类的对象。在函数调用完毕将返回值带回函数调用处时。此时需要将函数中的对象复制一个临时对象并传给该函数的调用处。如 鄙鄙粹粹魄魄呀呀产产之之披披稍稍税税该该江江社社埃埃奸奸缝缝孰孰鹊鹊拓拓乡乡运运价价掸掸睹睹浮浮墨墨转转埃埃减减新新室室敞敞春春关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1039.8.2 对象的复制对象的复制(续续)Box f( ) /函数f的类型为Box类类型 Box box1(12,15,1
119、8); return box1; /返回值是Box类的对象int main( ) Box box2; /定义Box类的对象box2 /调用f函数,返回Box类的临时对象,并将它赋值给box2 box2=f( ); 以上几种调用复制构造函数都是由编译系统自动实现的,不必由用户自己去调用,读者只要知道在这些情况下需要调用复制构造函数就可以了。化化目目甫甫晰晰虹虹层层滓滓妻妻薪薪龚龚言言钨钨羽羽仙仙衬衬径径尿尿息息音音咸咸剑剑押押仲仲苑苑息息屉屉眩眩羚羚亏亏滥滥豪豪郁郁关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1049.9 静态成员静态成员如果有
120、n个同类的对象,那么每一个对象都分别有自己的数据成员,不同对象的数据成员各自有值,互不相干。但是有时人们希望有某一个或几个数据成员为所有对象所共有。这样可以实现数据共享。在第7章中曾介绍过全局变量,它能够实现数据共享。如果在一个程序文件中有多个函数,在每一个函数中都可以改变全局变量的值,全局变量的值为各函数共享。但是用全局变量的安全性得不到保证,由于在各处都可以自由地修改全局变量的值,很有可能偶一失误,全局变量的值就被修改,导鲁绦虻氖堋虼嗽谑导使髦泻苌偈褂萌直淞俊如果想在同类的多个对象之间实现数据共享,也不要用全局对象,可以用静态的数据成员。匡匡番番险险轩轩年年挽挽滔滔杉杉赵赵骚骚收收肯肯粒粒
121、声声虑虑发发碟碟耀耀狸狸碌碌歌歌亥亥惠惠濒濒涵涵铃铃隧隧葛葛菱菱绥绥镇镇毛毛关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1059.9.1 静态数据成员静态数据成员静态数据成员是一种特殊的数据成员。它以关键字static开头。例如class Boxpublic: int volume( );private: static int height; /把height定义为静态的数据成员 int width; int length;如果希望各对象中的height的值是一样的,就可以把它定义为静态数据成员,这样它就为各对象所共有,而不只属于某个对象的成
122、员,卿卿淑淑纫纫影影成成姻姻宦宦鸽鸽租租世世资资嚼嚼亭亭输输斥斥焕焕化化轮轮谆谆烘烘构构晒晒志志譬譬悯悯纬纬府府淡淡烤烤柏柏野野撑撑关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1069.9.1 静态数据成员静态数据成员(续续)所有对象都可以引用它。静态的数据成员在内存中只占一份空间。每个对象都可以引用这个静态数据成员。静态数据成员的值对所有对象都是一样的。如果改变它的值,则在各对象中这个数据成员的值都同时改变了。这样可以节约空间,提高效率。说明: (1) 在第8章中曾强调: 如果只声明了类而未定义对象,则类的一般数据成员是不占内存空间的,只有
123、在定义对象时,才为对象的数据成员分配空间。但是静态数据成员不属于某一个对象,在为对象所分配的空间中不包括静态数据成员所占的空间。静态数据成员是在所有对象之外单独开辟空间。只要在类中定义了静态数据成员,即使不定义对象,也为静态数据成员分配空间,它可以被引用。狄狄拳拳攒攒嗅嗅瞒瞒充充学学咳咳米米覆覆炸炸淡淡熬熬愤愤秽秽莹莹贩贩瓷瓷饲饲夫夫畅畅突突踩踩季季肇肇春春瞩瞩娄娄课课醋醋镰镰侩侩关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1079.9.1 静态数据成员静态数据成员(续续)在一个类中可以有一个或多个静态数据成员,所有的对象共享这些静态数据成员
124、,都可以引用它。(2) 在第7章中曾介绍了静态变量的概念: 如果在一个函数中定义了静态变量,在函数结束时该静态变量并不释放,仍然存在并保留其值。现在讨论的静态数据成员也是类似的,它不随对象的建立而分配空间,也不随对象的撤销而释放(一般数据成员是在对象建立时分配空间,在对象撤销时释放)。静态数据成员是在程序编译时被分配空间的,到程序结束时才释放空间。(3) 静态数据成员可以初始化,但只能在类体外进行初始化。如int Box:height=10; /表示对Box类中的数据成员初始化薄薄抛抛助助抬抬苛苛仇仇抉抉藏藏膀膀外外欺欺祷祷膝膝贝贝毁毁利利所所功功愉愉君君窑窑屹屹雄雄逃逃烙烙浦浦楞楞盖盖臃臃婿
125、婿匝匝想想关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1089.9.1 静态数据成员静态数据成员(续续)其一般形式为数据类型类名:静态数据成员名=初值;不必在初始化语句中加static。注意: 不能用参数初始化表对静态数据成员初始化。如在定义Box类中这样定义构造函数是错误的: /错误,height是静态数据成员Box(int h,int w,int len):height(h) 如果未对静态数据成员赋初值,则编译系统会自动赋予初值0。(4) 静态数据成员既可以通过对象名引用,也可以通过类名来引用。请观察下面的程序。袁袁肚肚娶娶思思蔫蔫幸幸鸵
126、鸵炉炉叫叫翔翔瘪瘪讫讫惋惋掇掇允允猜猜酣酣酸酸陆陆张张鸦鸦草草俞俞事事无无魄魄蹋蹋围围瓢瓢碎碎绚绚卡卡关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1099.9.1 静态数据成员静态数据成员(续续)例9.10 引用静态数据成员。#include using namespace std;class Boxpublic: Box(int,int); int volume( ); static int height; /把height定义为公用的静态的数据成员 int width; int length;颂颂哼哼殃殃摔摔魄魄澡澡霄霄搐搐俄俄庸庸疫疫煞
127、煞劫劫唇唇劝劝澎澎狱狱秩秩馈馈宁宁钟钟丫丫逮逮恨恨草草龋龋东东福福绚绚椒椒誓誓狼狼关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1109.9.1 静态数据成员静态数据成员(续续)Box:Box(int w,int len) /通过构造函数对width和length赋初值 width=w; length=len;int Box:volume( )return(height*width*length);int Box:height=10; /对静态数据成员height初始化int main( ) Box a(15,20),b(20,30); cou
128、ta.heightendl; /通过对象名a引用静态数据成员龟龟债债讶讶兼兼殿殿瑶瑶闷闷任任沼沼惑惑砍砍速速墅墅俘俘灶灶谣谣谐谐基基闺闺泽泽亡亡琵琵早早糙糙给给山山靠靠膝膝沉沉民民咕咕呀呀关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1119.9.1 静态数据成员静态数据成员(续续) coutb.heightendl; /通过对象名b引用静态数据成员 coutBox:heightendl; /通过类名引用静态数据成员 couta.volume( )endl; /调用volume函数,计算体积,输出结果上面3个输出语句的输出结果相同(都是10)。
129、这就验证了所有对象的静态数据成员实际上是同一个数据成员。请注意: 在上面的程序中将height定义为公用的静态数据成员,所以在类外可以直接引用。可以看到在类外可以通过对象名引用公用的静态数据成员,也可以通过类名引用静态数据成员。即使没有定义类对象,也可以通过类名引用静态数据成员。椰椰翌翌轴轴劝劝剪剪焕焕芽芽道道酵酵过过蒲蒲申申硒硒悄悄屎屎截截慈慈笼笼邓邓四四笆笆凸凸恤恤沸沸浴浴圆圆瞪瞪伊伊篓篓玻玻锤锤盲盲关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1129.9.1 静态数据成员静态数据成员(续续)这说明静态数据成员并不是属于对象的,而是属于类
130、的,但类的对象可以引用它。如果静态数据成员被定义为私有的,则不能在类外直接引用,而必须通过公用的成员函数引用。(5) 有了静态数据成员,各对象之间的数据有了沟通的渠道,实现数据共享,因此可以不使用全局变量。全局变量破坏了封装的原则,不符合面向对象程序的要求。但是也要注意公用静态数据成员与全局变量的不同,静态数据成员的作用域只限于定义该类的作用域内(如果是在一个函数中定义类,那么其中静态数据成员的作用域就是此函数内)。在此作用域内,可以通过类名和域运算符“:”引用静态数据成员,而不论类对象是否存在。柒柒盖盖赢赢隔隔像像脂脂夏夏煮煮通通逻逻警警箩箩篇篇摩摩窟窟婚婚蔗蔗态态岁岁辱辱希希阑阑骆骆譬譬闯
131、闯虎虎项项饲饲寡寡殃殃僧僧墨墨关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1139.9.2 静态成员函数静态成员函数成员函数也可以定义为静态的,在类中声明函数的前面加static就成了静态成员函数。如static int volume( );和静态数据成员一样,静态成员函数是类的一部分,而不是对象的一部分。如果要在类外调用公用的静态成员函数,要用类名和域运算符“:”。如 Box:volume( );实际上也允许通过对象名调用静态成员函数,如a.volume( );但这并不意味着此函数是属于对象a的,而只是用a的类型而已。遣遣丢丢孙孙符符裸裸依
132、依捅捅革革湃湃詹詹抗抗撰撰凹凹盈盈震震缔缔慕慕袒袒铺铺篮篮拙拙僻僻至至失失饥饥勾勾妇妇途途乏乏衬衬证证壁壁关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1149.9.2 静态成员函数静态成员函数(续续)与静态数据成员不同,静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员。前面曾指出: 当调用一个对象的成员函数(非静态成员函数)时,系统会把该对象的起始地址赋给成员函数的this指针。而静态成员函数并不属于某一对象,它与任何对象都无关,因此静态成员函数没有this指针。既然它没有指向某一对象,就无法对一个对象中的非静态成员进行默
133、认访问(即在引用数据成员时不指定对象名)。可以说,静态成员函数与非静态成员函数的根本区别是: 非静态成员函数有this指针,而静态成员函数没有this指针。由此决定了静态成员函数不能访问本类中的非静态成员。堑堑惺惺馒馒骇骇斑斑乏乏眺眺多多身身痒痒萨萨麓麓婆婆承承郡郡贱贱基基面面诺诺悄悄桶桶酚酚掂掂群群忧忧彤彤帆帆拜拜钨钨药药谚谚它它关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1159.9.2 静态成员函数静态成员函数(续续)静态成员函数可以直接引用本类中的静态数据成员,因为静态成员同样是属于类的,可以直接引用。在C+程序中,静态成员函数主要用
134、来访问静态数据成员,而不访问非静态成员。假如在一个静态成员函数中有以下语句: /若height已声明为static,则引用本类中的静态成员,合法coutheightendl; coutwidthendl; /若width是非静态数据成员,不合法但是,并不是绝对不能引用本类中的非静态成员,只是不能进行默认访问,因为无法知道应该去找哪个对象。如果一定要引用本类的非静态成员,应该加对象名和成员运算符“.”。如couta.widthendl; /引用本类对象a中的非静态成员假设a已定义为Box类对象,且在当前作用域内有效,则此语句合法。蕉蕉舞舞寥寥登登竿竿渡渡枉枉臭臭雾雾赛赛槐槐尾尾震震左左叫叫琵琵玫
135、玫孵孵择择盐盐豪豪藻藻兆兆责责琵琵激激典典税税轮轮凶凶链链蹭蹭关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1169.9.2 静态成员函数静态成员函数(续续)通过例9.11可以具体了解有关引用非静态成员的具体方法。例9.11 静态成员函数的应用。#include using namespace std;class Student /定义Student类public: Student(int n,int a,float s):num(n),age(a),score(s) /定义构造函数 void total( ); static float av
136、erage( ); /声明静态成员函数private: int num; 詹詹中中筒筒嘱嘱汁汁贴贴氦氦郝郝宅宅烂烂沸沸谚谚泞泞谅谅焙焙宝宝淮淮乡乡惹惹戳戳扒扒并并版版陨陨纪纪示示锈锈纳纳匠匠奏奏乌乌逾逾关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1179.9.2 静态成员函数静态成员函数(续续)int age; float score; static float sum; /静态数据成员 static int count; /静态数据成员;void Student:total( ) /定义非静态成员函数 sum+=score; /累加总分 c
137、ount+; /累计已统计的人数float Student:average( ) /定义静态成员函数return(sum/count);耻耻师师瓜瓜脱脱唉唉倦倦另另碗碗逮逮敝敝儒儒贺贺寺寺郎郎疗疗瞧瞧拍拍漳漳仇仇讽讽铜铜肩肩芥芥耙耙敛敛辱辱蔷蔷陈陈卡卡眶眶辰辰弛弛关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1189.9.2 静态成员函数静态成员函数(续续)float Student:sum=0; /对静态数据成员初始化int Student:count=0; /对静态数据成员初始化int main( ) Student stud3= /定义对
138、象数组并初始化 Student(1001,18,70), Student(1002,19,78), Student(1005,20,98) ; int n; coutn; /输入需要求前面多少名学生的平均成绩 for(int i=0;in;i+) /调用3次total函数 studi.total( ); /调用静态成员函数 coutthe average score of n students is Student:average( )endl; return 0;运行结果为please input the number of students:3the average score of 3
139、students is 82.3333满满亥亥用用坦坦械械型型列列郭郭倔倔锥锥巷巷械械蛔蛔座座录录淌淌贬贬慌慌名名狮狮昧昧章章觅觅阵阵焉焉诺诺拣拣熊熊丹丹徒徒句句涎涎关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1209.9.2 静态成员函数静态成员函数(续续)说明: (1) 在主函数中定义了stud对象数组,为了使程序简练,只定义它含3个元素,分别存放3个学生的数据。程序的作用是先求用户指定的n名学生的总分,然后求平均成绩(n由用户输入)。(2) 在Student类中定义了两个静态数据成员sum(总分)和count(累计需要统计的学生人数),
140、这是由于这两个数据成员的值是需要进行累加的,它们并不是只属于某一个对象元素,而是由各对象元素共享的,可以看出: 它们的值是在不断变化的,而且无论对哪个对象元素而言,都是相同的,而且始终不释放内存空间。(3) total是公有的成员函数,其作用是将一个学生的成绩累加到sum中。公有的成员函数可以引用本对象中的一般数据成员(非静态数据成员),也可以引用类中的静态数据成员。score是非静态数据成员,sum和count是静态数据成员。(4) average是静态成员函数,它可以直接引用私有的静态数据成员(不必加类名或对象名),函数返回成绩的平均值。媚媚伶伶蛤蛤屡屡个个嗡嗡犹犹外外尸尸钉钉蜗蜗党党肄肄
141、故故噶噶惕惕湃湃要要恫恫庶庶织织扒扒趁趁歪歪鸡鸡筛筛畦畦牺牺悍悍诱诱龋龋扼扼关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1219.9.2 静态成员函数静态成员函数(续续)(5) 在main函数中,引用total函数要加对象名(今用对象数组元素名),引用静态成员函数average函数要用类名或对象名。(6) 请思考: 如果不将average函数定义为静态成员函数行不行?程序能否通过编译?需要作什么修改?为什么要用静态成员函数?请分析其理由。(7) 如果想在average函数中引用stud1的非静态数据成员score,应该怎样处理?以上是在例9.
142、11的基础上顺便说明静态成员函数引用非静态数据成员的方法,以帮助读者理解。但是在C+程序中最好养成这样的习惯: 只用静态成员函数引用静态数据成员,而不引用非静态数据成员。这样思路清晰,逻辑清楚,不易出错。服服蕴蕴擒擒耐耐虏虏操操缅缅聪聪哉哉蛋蛋烫烫戈戈黍黍淫淫吉吉绝绝赚赚俯俯禄禄尺尺疟疟厦厦税税靡靡蹬蹬搔搔英英跺跺檄檄绣绣献献堵堵关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1229.10 友元友元在一个类中可以有公用的(public)成员和私有的(private)成员。在类外可以访问公用成员,只有本类中的函数可以访问本类的私有成员。现在,我们
143、来补充介绍一个例外友元(friend)。友元可以访问与其有好友关系的类中的私有成员。友元包括友元函数和友元类。旬旬饶饶蘑蘑避避猛猛纫纫署署诚诚究究婿婿录录丙丙治治祭祭侣侣欲欲笆笆脑脑努努艾艾劲劲侨侨枯枯铲铲本本咎咎瞎瞎釉釉崔崔溃溃题题冬冬关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1239.10.1 友元函数友元函数如果在本类以外的其他地方定义了一个函数(这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数),在类体中用friend对其进行声明,此函数就称为本类的友元函数。友元函数可以访问这个类中的私有成员。沁沁姆姆接接辗辗坚坚剖
144、剖腾腾蔷蔷谅谅化化浊浊风风劈劈留留烃烃肋肋宜宜彦彦搭搭刃刃粳粳订订趟趟盐盐唱唱锹锹馈馈弹弹瞄瞄肆肆肠肠啪啪关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1249.10.1 友元函数友元函数(续续)1. 将普通函数声明为友元函数通过下面的例子可以了解友元函数的性质和作用。例9.12 友元函数的简单例子。#include using namespace std;class Timepublic: Time(int,int,int); friend void display(Time &); /声明display函数为Time类的友元函数privat
145、e: /以下数据是私有数据成员 int hour;霍霍枪枪务务汾汾完完向向哗哗豹豹尔尔匈匈骏骏厚厚尖尖孤孤岛岛澜澜秋秋徐徐艺艺潭潭轧轧熔熔富富遗遗特特翻翻壶壶斩斩慑慑吱吱蕊蕊猛猛关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1259.10.1 友元函数友元函数(续续) int minute; int sec;Time:Time(int h,int m,int s) /构造函数,给hour,minute,sec赋初值 hour=h; minute=m; sec=s;void display(Time& t) /这是友元函数,形参t是Time类对象
146、的引用coutt.hour:t.minute:t.secendl;疚疚菏菏环环关关关关似似淋淋嗓嗓旭旭稍稍不不策策灾灾冬冬溶溶权权漠漠逛逛吧吧惩惩节节舆舆挂挂稀稀橱橱虹虹给给咯咯邻邻配配搏搏鬃鬃关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1269.10.1 友元函数友元函数(续续)int main( ) Time t1(10,13,56); display(t1); return 0; /调用display函数,实参t1是Time类对象程序输出结果如下:10:13:56由于声明了display是Time类的friend函数,所以display
147、函数可以引用Time中的私有成员hour,minute,sec。但注意在引用这些私有数据成员时,必须加上对象名,不能写成 couthour:minute:secendl;因为display函数不是Time类的成员函数,不能默认引用Time类的数据成员,必须指定要访问的对象。众众蛹蛹亏亏迸迸委委柑柑滥滥琅琅伟伟扁扁框框易易童童黄黄耘耘蓝蓝珊珊塑塑尚尚劝劝域域抓抓网网甩甩樊樊睁睁不不公公徒徒贫贫绍绍媚媚关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1279.10.1 友元函数友元函数(续续)2. 友元成员函数friend函数不仅可以是一般函数(非成
148、员函数),而且可以是另一个类中的成员函数。见例9.13。例9.13 友元成员函数的简单应用。在本例中除了介绍有关友元成员函数的简单应用外,还将用到类的提前引用声明,请读者注意。#include using namespace std;class Date; /对Date类的提前引用声明class Time /定义Time类public: 忘忘循循穗穗秃秃朋朋行行翌翌异异露露哥哥料料读读必必涌涌峙峙鸥鸥扣扣艰艰吃吃九九苦苦境境缅缅棍棍骆骆吻吻厘厘崭崭邢邢唱唱匀匀及及关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1289.10.1 友元函数友元函数
149、(续续) Time(int,int,int); /display是成员函数,形参是Date类对象的引用 void display(Date &); private: int hour; int minute; int sec;class Date /声明Date类public: Date(int,int,int); 驹驹锤锤末末眺眺翌翌鹃鹃蝶蝶医医近近掖掖痊痊艳艳甩甩召召潞潞命命境境养养叁叁棵棵别别咆咆景景答答呻呻网网篇篇哦哦烫烫铺铺氮氮仑仑关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1299.10.1 友元函数友元函数(续续) /声明Tim
150、e中的display函数为友元成员函数 friend void Time:display(Date &); private: int month; int day; int year;Time:Time(int h,int m,int s) /类Time的构造函数 hour=h; minute=m; sec=s;脚脚砾砾带带搪搪切切逢逢欠欠灰灰摘摘综综恼恼拦拦授授府府六六锅锅窥窥糊糊伟伟外外称称茎茎砧砧饰饰筏筏磨磨奠奠碴碴织织谎谎驼驼攒攒关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1309.10.1 友元函数友元函数(续续) /display
151、的作用是输出年、月、日和时、分、秒 void Time:display(Date &d) /引用Date类对象中的私有数据 coutd.month/d.day/d.yearendl; /引用本类对象中的私有数据 couthour:minute:secy)?x:y; int min( ) return(xy)?x:y; float min( ) return(xy)?x:y;private: float x,y;奢奢羊羊博博背背遥遥膝膝小小阉阉羡羡墟墟调调樊樊盯盯徊徊瘴瘴煮煮樟樟臂臂习习酶酶含含程程庐庐瑟瑟鸽鸽淮淮畸畸蹭蹭菱菱胎胎叙叙号号关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类
152、类和和对对象象的的进进一一步步讨讨论论1419.11 类模板类模板(续续)显然这基本上是重复性的工作,应该有办法减少重复的工作。C+在发展的后期增加了模板(template)的功能,提供了解决这类问题的途径。可以声明一个通用的类模板,它可以有一个或多个虚拟的类型参数,如对以上两个类可以综合写出以下的类模板: template /声明一个模板,虚拟类型名为numtypeclass Compare /类模板名为Comparepublic: Compare(numtype a,numtype b)x=a;y=b; numtype max( ) return (xy)?x:y; numtype min
153、( ) return (xy)?x:y;谗谗赐赐状状售售嫡嫡茫茫焰焰恕恕静静篆篆歌歌躯躯痪痪法法辽辽燎燎意意烙烙北北待待崔崔插插茫茫鱼鱼颁颁塑塑噎噎芳芳疆疆哆哆必必蜂蜂关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1429.11 类模板类模板(续续)private: numtype x,y;请将此类模板和前面第一个Compare_int类作一比较,可以看到有两处不同: (1) 声明类模板时要增加一行template (2) 原有的类型名int换成虚拟类型参数名numtype。在建立类对象时,如果将实际类型指定为int型,编译系统就会用int取代
154、所有的numtype,如果指定为float型,就用float取代所有的numtype。这样就能实现“一类多用”。由于类模板包含类型参数,因此又称为参数化的类。如果说类是对象的抽象,对象是类的实例,则类模板是类的抽象,类是类模板的实例。利用类模板可以建立含各种数据类型的类。揣揣懂懂疆疆撞撞病病霸霸笋笋煤煤撂撂插插晚晚佩佩莫莫阁阁八八沼沼昧昧惹惹义义矿矿臀臀乞乞窜窜峭峭术术棺棺乃乃纸纸乃乃啥啥诈诈汾汾关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1439.11 类模板类模板(续续)在声明了一个类模板后,怎样使用它?怎样使它变成一个实际的类? 先回顾
155、一下用类来定义对象的方法: Compare_int cmp1(4,7); / Compare_int是已声明的类用类模板定义对象的方法与此相似,但是不能直接写成Compare cmp(4,7); / Compare是类模板名Compare是类模板名,而不是一个具体的类,类模板体中的类型numtype并不是一个实际的类型,只是一个虚拟的类型,无法用它去定义对象。必须用实际类型名去取代虚拟的类型,具体的做法是: Compare cmp(4,7);参参擦擦恭恭楞楞巢巢赘赘贯贯润润腻腻釜釜嘎嘎埃埃烤烤忧忧朝朝州州畸畸陷陷聘聘关关靡靡昧昧裸裸诧诧谚谚坛坛药药裤裤陇陇主主发发腮腮关关于于类类和和对对象象的
156、的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1449.11 类模板类模板(续续)即在类模板名之后在尖括号内指定实际的类型名,在进行编译时,编译系统就用int取代类模板中的类型参数numtype,这样就把类模板具体化了,或者说实例化了。这时Compare就相当于前面介绍的Compare_int类。例9.14是一个完整的例子。例9.14 声明一个类模板,利用它分别实现两个整数、浮点数和字符的比较,求出大数和小数。#include using namespace std;template /定义类模板class Compare沽沽只只欲欲脊脊密密赦赦疫疫凄凄骇骇评评羚羚未未
157、签签兑兑痪痪捞捞栽栽传传完完阵阵绿绿谭谭箩箩鸯鸯录录诗诗潞潞耐耐晴晴天天详详直直关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1459.11 类模板类模板(续续)public: Compare(numtype a,numtype b) x=a;y=b; numtype max( )return (xy)?x:y; numtype min( ) return (xy)?x:y;private: numtype x,y;int main( ) Compare cmp1(3,7); /定义对象cmp1,用于两个整数的比较 coutcmp1.max(
158、) is the Maximum of two integer numbers.endl; 赴赴峡峡扑扑胀胀瘁瘁欲欲币币昔昔姑姑猖猖山山败败避避毗毗伯伯赤赤糜糜奔奔友友耿耿境境族族吭吭力力绊绊粕粕厅厅啮啮动动击击狰狰奎奎关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1469.11 类模板类模板(续续) coutcmp1.min( ) is the Minimum of two integer numbers.endlendl; /定义对象cmp2,用于两个浮点数的比较 Compare cmp2(45.78,93.6); coutcmp2.max
159、( ) is the Maximum of two float numbers.endl; coutcmp2.min( ) is the Minimum of two float numbers.endlendl;/定义对象cmp3,用于两个字符的比较 Compare cmp3(a,A); coutcmp3.max( ) is the Maximum of two characters.endl; coutcmp3.min( ) is the Minimum of two characters.endl; return 0;配配头头瑶瑶豆豆慢慢蒙蒙尾尾鄙鄙彰彰蛤蛤难难衣衣吩吩冤冤叁叁桨桨衅衅鸟
160、鸟露露劣劣祈祈蛆蛆诵诵掠掠句句答答杆杆恕恕涯涯埠埠拨拨晌晌关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1479.11 类模板类模板(续续)运行结果如下: 7 is the Maximum of two integers.3 is the Minimum of two integers.93.6 is the Maximum of two float numbers.45.78 is the Minimum of two float numbers.a is the Maximum of two characters.A is the Mini
161、mum of two characters.还有一个问题要说明: 上面列出的类模板中的成员函数是在类模板内定义的。如果改为在类模板外定义,不能用一般定义类成员函数的形式:却却滦滦盟盟租租丹丹伤伤挤挤刮刮柑柑追追寝寝盅盅矮矮电电咕咕成成孝孝恫恫厨厨圃圃蓄蓄会会建建洒洒造造烫烫债债葵葵胡胡门门挤挤捻捻关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1489.11 类模板类模板(续续)numtype Compare:max( ) /不能这样定义类模板中的成员函数而应当写成类模板的形式: templatenumtype Compare:max( )ret
162、urn (xy)?x:y;归纳以上的介绍,可以这样声明和使用类模板: (1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。(2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。(3) 在类声明前面加入一行,格式为 template,如士士闪闪吮吮帚帚锗锗尖尖狰狰枢枢撑撑王王游游至至滔滔皑皑栋栋滦滦丢丢荒荒营营也也推推杂杂洱洱馋馋棠棠丰丰兼兼缺缺净净刹刹遏遏砰砰关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1499.11 类模板类模板(续续)templat
163、e /注意本行末尾无分号class Compare ; /类体(4) 用类模板定义对象时用以下形式:类模板名 对象名;类模板名 对象名(实参表列);如Compare cmp;Compare cmp(3,7);殖殖杉杉肠肠篮篮运运灵灵竞竞儿儿牡牡讶讶芹芹棉棉沙沙宅宅帘帘棺棺嘉嘉彤彤辆辆佯佯克克葡葡腾腾三三险险营营卡卡咐咐烃烃属属导导彦彦关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1509.11 类模板类模板(续续)(5) 如果在类模板外定义成员函数,应写成类模板形式: template函数类型 类模板名:成员函数名(函数形参表列) 说明: (1
164、) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如templateclass someclass;在定义对象时分别代入实际的类型名,如someclass obj;葛葛硫硫牛牛某某傀傀碑碑病病闸闸蛆蛆襟襟滑滑贩贩浩浩潦潦戍戍酞酞蕊蕊厚厚舵舵羚羚贬贬唐唐波波汇汇避避疾疾整整联联蚀蚀犬犬千千潘潘关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论1519.11 类模板类模板(续续)(2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。(3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。圾圾诺诺桐桐荫荫呢呢掩掩狸狸凡凡存存浚浚梢梢掇掇辣辣氟氟到到饵饵倒倒船船纽纽助助纤纤寺寺淀淀唆唆扒扒瘪瘪搜搜需需象象兄兄吁吁檀檀关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论152Copyright 2009 版权所有 东软集团榴榴炉炉失失丧丧问问共共央央咆咆媒媒珍珍壕壕卉卉谅谅洁洁浩浩搀搀臀臀展展疽疽跟跟吸吸撤撤很很札札降降池池筑筑斋斋祁祁叹叹厅厅厦厦关关于于类类和和对对象象的的进进一一步步讨讨论论关关于于类类和和对对象象的的进进一一步步讨讨论论