成员,就会得到一个编译期错误。“friendy”(友好的)涉及“包装”或“封装”(Padkage)的概念一即Java用来构建库的方法。若某样东西是“友好的”,意味着它只能在这个包装的范围内使用(所以这一访问级别有时也叫作“包装访问”)。“protedted”(受保护的)与“private”相似,只是一个继承的类可访问受保护的成员,但不能访问私有成员。继承的问题不久就要谈到。1.4方案的重复使用创建并测试好一个类后,它应(从理想的角度)代表一个有用的代码单位。但并不象许多人希望的那样,这种重复使用的能力并不容易实现;它要求较多的经验以及洞察力,这样才能设计出一个好的方案,才有可能重复使用。许多人认为代码或设计方案的重复使用是面向对象的程序设计提供的最伟大的一种杠杆。为重复使用一个类,最简单的办法是仅直接使用那个类的对象。但同时也能将那个类的一个对象置入一个新类。我们把这叫作“创建一个成员对象”。新类可由任意数量和类型的其他对象构成。无论如何,只要新类达到了设计要求即可。这个概念叫作“组织”一一在现有类的基础上组织一个新类。有时,我们也将组织称作“包含”关系,比如“一辆车包含了一个变速箱”。对象的组织具有极大的灵活性。新类的“成员对象”通常设为“私有”(Private),使用这个类的客户程序员不能访问它们。这样一来,我们可在不干扰客户代码的前提下,从容地修改那些成员。也可以在“运行期”更改成员,这进一步增大了灵活性。后面要讲到的“继承”并不具备这种灵活性,因为编译器必须对通过继承创建的类加以限制。由于继承的重要性,所以在面向对象的程序设计中,它经常被重点强调。作为新加入这一领域的程序员,或许早已先入为主地认为“继承应当随处可见”。沿这种思路产生的设计将是非常笨拙的,会大大增加程序的复杂程度。相反,新建类的时候,首先应考虑“组织”对象;这样做显得更加简单和灵活。利用对象的组织,我们的设计可保持清爽。一旦需要用到继承,就会明显意识到这一点。1.5继承:重新使用接口就其本身来说,对象的概念可为我们带来极大的便利。它在概念上充许我们将各式各样数据和功能封装到一起。这样便可恰当表达“问题空间”的概念,不用刻意遵照基础机器的表达方式。在程序设计语言中,这些概念则反映为具体的数据类型(使用dcss关键字)。我们费尽心思做出一种数据类型后,假如不得不又新建一种类型,令其实现大致相同的功能,那会是一件非常令人灰心的事情。但若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改,情况就显得理想多了。“继承”正是针对这个目标而设计的。但继承并不完全等价于克隆。在继承过程中,若原始类(正式名称叫作基础类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫作继承类或者子类)也会反映出这种变化。在Java语言中,继承是通过extenc关键字实现的使用继承时,相当于创建了一个新类。这个新类不仅包含了现有类型的所有成员(尽管private成员被隐藏起来,且不能访问);但更重要的是,它复制了基础类的接口。也就是说,可向基础类的对象发送的所有消息亦可原样发给衍生类的对象。根据可以发送的消息,我们能知道类的类型。这意味着衍生类具有与基础类相同的类型!为真正理解面向对象程序设计的含义,首先必须认识到这种类型的等价关系。由于基础类和衍生类具有相同的接口,所以那个接口必须进行特殊的设计。也就是说,对象接收到一条特定的消息后,必须有一个“方法”能够执行。若只是简单地继承一个类,并不做其他任何事情,来自基础类接口的方法就会直接照搬到衍生类。这意味着衍生类的对象不仅有相同的类型,也有同样的行为,这一后果通常是我们不愿见到的。有两种做法可将新得的衍生类与原来的基础类区分开。第一种做法十分简单:为衍生类添加新函数(功能)。这些新函数并非基础类接口的一部分。进行这种处理时,一般都是意识到基础类不能满足我们的要求,所以需要添加更多的函数。这是一种最简单、最基本的继承用法,大多数时候都可完美地解决我们的问题。然而,事先还是要仔细调查自己的基础类是否真的需要这些额外的函数。1.5.1改善基础类尽管extenc关键字暗示着我们要为接口“扩展”新功能,但实情并非肯定如此。为区分我们的新类,第二个办法是改变基础类一个现有函数的行为。我们将其称作“改善”那个函数。为改善一个函数,只需为衍生类的函数建立一个新定义即可。我们的目标是:“尽管使用的函数接口未变,但它的新版本具有不同的表现”。30
ė٭̀~ɼŭŪȀÌسлŔ֒bfF R I ENDL YgzٔɀŮ{ҷʖf۴gʅfǏ۴gz1ACKAGE{ŮǬЀcc ʘ+AVAً͊Ȓ˂̳ŮƽƲbҖϧؑƊ֔ӢfٔɀŮg~جռ܀ԧۋϹڬڄȀ۴ŮƺճϸӘًzԥڬؤǂ ֆʙÔٓӑؗˎܜf۴ǂֆg{bfPR OT ECT EDgzӰ°ɜŮ{ٟfPR I VAT Egֱԓ~ۋӢȀʣĜŮ̧͛ ǂֆӰ°ɜŮė٭~ŢáϹǂֆԎٓė٭bʣĜŮֆԼá˽̀ؔԯŪb ¸WȺÂƓȎ Ļ˂×øөɀȀ͛ɕ~ԧؽzʼnֶ͠Ůˋƙ{śÓȀًٓŮśΩşվbŢ×áֽצƦ҇֙լŮϭؑ~ڬ ۜ۞ǢӘًŮϹͩ×áҐاӔ֬ԧؔѰˍƦŮ˭؊ؤʖƎýͩ~ڬؑéϹҹʟĮȀɀŮƽ~ȩ́ٓϹ ۞ǢӘًb צƦ҇ҊյśΩʅҹʟƽŮ۞ǢӘًӢϑּơֽŮęרҹʟԻȊŮܖոŘŮۜǴǯb յ۞ǢӘًȀ͛~ܖʵşŮ§ƲӢˣۄːӘًϭȀ͛ŮơֽbŢՋӑؗϹ˃ϭȀ͛ŮȀơֽےҔȀד ͛bևφڬˎܜfĻ˂Ȁė٭ơֽgbד҉ُ̧͛جӾͶɄ͛טŮпԦơֽȒėb֊ғɅ~ۋؔד͛ ŕŪͺҹʟؔѰʘ̧bڬȀǬЀˎܜfۂܕgccڄ͛ٓ֬ŮʋıҮۂܕȀד͛bٓӑ~ևφؗ˃ۂܕĖ ܜfȹgȢ֟~Áғf͵ďȹͺȀÎԙִgb ơֽŮۂܕٓ̋ʑŘŮ·ʁםbד͛Ůfė٭ơֽgՊąҹյfԎٓgz1R I VAT E{~ӘًڬȀ͛Ů̪ɞęר ٭áϹǂֆԧφbڬؑ~͊ևφ̧ڄáǮ̪҃ɞśΩŮєԻ֣~ʼnҐŶסǫϭ׆ė٭b̧ؗڄؤfךٽ лgȅǫė٭~ڬ˥ãڐŘͺ·ʁםbɕϑؔ˅ŪŮfʣĜg×á̋»ڬ·ۜʁם~ضյÌسшÉץơՊ ȵʣĜĻ˂Ů͛ʨؤ֯ۓb ُٙʣĜŮ۞ؔם~ԥڄؤϑּơֽŮęרҹʟۗ~ԧ˭ą¼۞żћƃbܜյדʨҔڬΈ١Ůę٭ר~ʅ أډצ֥ҔյۧŶҊյfʣĜؽťԟĴ̧ʻgbڬۜԍΓāӃŮҹʟ˃ӢDŽą¾۾Ů~ɼŘŘڐʨęרŮ ǢٿęƙbֱƸ~ד͛˂Ůӑɔ~Ӭ֥ؽ̠Ιfۂܕgơֽڬؑܛ֪ŭȅʨʵşɄ·ʁbًͥơֽŮܕ ۂ~ևφŮҹʟ̧°ĞѩԃbšףًؔŪʣĜ~̀ɼϙ֪جӕŪڬżb üSȺǪƓȎčĨ ̀п½Ҽ͊Ԉ~ơֽŮǬЀ̧յևφŚ͊ʑŘŮÍͥbԧڄǬЀҮצټևφ˃ȁӚȁؑӾ̉ɄȈϹǏ۴Ū уbڬؑÍ̧эťÓŕfֆԼ̭ʰgŮǬЀ~áً̩ڧܙجʋıʌшŮÓŕƽӚbڄęרҹʟ٠ۗ~׆ڬ ǬЀڎƸلյ̋ԽŮӾ̉͛טzӘًCL AS S Ȣʼ܉}b ևφLj˨הԍܛĮۜӾ̉͛טɕ~ʪғáŭáٗד˂͛ۜט~ΊпӔ֬ŘۑֱՋŮȈϹ~ϭɼӢʾDŽ ąΊ҇ɵהŮӟѫbŢҖϹًͥ֬ėŮӾ̉͛ט~ơп˥ךf̨Ύg~ڃȃ̉ѫ̼˥ךՁʨɄסǫ~ѫ̼̀ ֪ŭֶ͠ƦͺbfʣĜgڷӢڮơڬȀϪÒƬҹʟŮbŢʣĜ×áէѺűʫ̨ٙΎbڄʣĜȵęۗ~Җ٫ә ͛zڷӚϚĖˎܜʋı͛aċ͛ʅǤ͛{ưӃͺÎɥ~סǫȵŮf̨Ύg͛zڷӚϚĖˎܜʣĜ͛ʅ܇ګ ͛{ؗɼƸلĮڬۜÎɥbڄ+ AVA٠ۗ~ʣĜӢՊȵ EXT ENDS Ȣʼ܉Ӕ֬Ů ӘًʣĜӑ~ֱťٙĻ˂ͺȀד͛bڬȀד͛áˣȹͺ֬ٓ͛טŮԥٓė٭z˨ȦPR I VAT Eė٭1/4غñ у͊~ѢáϹǂֆ{~Ţȅ۞ؔŮӢ~ԧǢۓͺʋı͛Ůː̰bؗ̀ӢԈ~ּ̧ʋı͛ŮơֽưԕŮԥֿٓ ֘ت̧٫ؑưȂ؇Ӄ͛Ůơֽbȃ̧̉ؤưԕŮֿ֘~ևφϹۀū͛Ů͛טbجڬռ܀؇Ӄٟ͛̋ٓʋı͛ ֱՋŮ͛טwյڷڭ˙͠ϑּơֽęרҹʟŮȹد~Ӭ֥ÉץҊӕŪڬ͛ۜטŮűʫȢ֟b ُٙʋı͛Ʉ؇Ӄֱ͛̋ٓՋŮː̰~ԥؤϭȀː̰Éץ˥ךԷӱŮҹʟbؗ̀ӢԈ~ơֽːӪŪՄԷƇ Ůֿ֘ɕ~ÉץٓȀfƽƲgϹȔךۆbҖۋӢʵşŶʣĜȀ͛~×áܛпԦ҉Ʌӟѫ~͊܈ʋı͛ː ̰ŮƽƲ̀ɼۄːڧ Ū؇Ӄ͛bجڬռ܀؇Ӄ͛ŮơֽáˣֱٓՋŮ͛טٓؗ~ՋؑŮךյ~ڬɕȳՊ ąӢևφáٲʻŪŮb ٓʹۜܛƲ̧˃דŭŮ؇Ӄٟ͛٫͊Ůʋı͛ѲNJ̚bŷۜܛƲӎNJʵşյ؇Ӄ͛ՁʨדȺӾzȈ Ϲ{bד׆ڬȺӾ×DŽʋı͛ː̰ŮåNJb˥ڬךۜĴ͠ӑ~¡ƒӢجӕŪʋı͛áϹΰܑևφŮؔ Ѱ~ԥףؤؔՁʨȅƦŮȺӾbڬӢۜܖʵşaܖʋ½ŮʣĜًƲ~ŘƦӾӑɔƒ̧էςŶ˙̓ևφŮֆ ԼbѿƬ~ӟ֥ɬӢؔ܆֢ƃü܈ʜŮʋı͛ӢǕڭŮףؔ׆ڬƨդŮȺӾb Åƅñĵ ˨ȦEXT ENDS Ȣʼ܉ӛ܀ևφؔյː̰fͅڙgדȈϹ~ŢӔѫ×DŽ̬ƇғńbյѲNJևφŮד~͛ŷƯ Ȁ§ƲӢǫÎʋı͛Ȁ֬ٓȺӾŮךյbևφ˃пĖܜfǫҪgϭȀȺӾb յǫҪȀȺӾ~ףۋյ؇Ӄ͛ŮȺӾ˂ͧȀדƇدʘ̧bևφŮϪÒӢf˨ȦӘًŮȺӾː̰ջÎ~ ŢԧŮדٓ̋1/2£áՋŮÓ֬gb
1.5.2等价与类似关系针对继承可能会产生这样的一个争论:继承只能改善原基础类的函数吗?若答案是肯定的,则衍生类型就是与基础类完全相同的类型,因为都拥有完全相同的接口。这样造成的结果就是:我们完全能够将衍生类的一个对象换成基础类的一个对象!可将其想象成一种“纯替换”。在某种意义上,这是进行继承的一种理想方式。此时,我们通常认为基础类和衍生类之间存在一种“等价”关系一一因为我们可以理直气壮地说:“圆就是一种几何形状”。为了对继承进行测试,一个办法就是看看自己是否能把它们套入这种“等价”关系中,看看是否有意义。但在许多时候,我们必须为衍生类型加入新的接口元素。所以不仅扩展了接口,也创建了一种新类型。这种新类型仍可替换成基础类型,但这种替换并不是完美的,因为不可在基础类里访问新函数。我们将其称作“类似”关系;新类型拥有旧类型的接口,但也包含了其他函数,所以不能说它们是完全等价的。举个例子来说,让我们考虑一下制冷机的情况。假定我们的房间连好了用于制冷的各种控制器;也就是说,我们已拥有必要的“接口”来控制制冷。现在假设机器出了故障,我们把它换成一台新型的冷、热两用空调,冬天和夏天均可使用。冷、热空调“类似”制冷机,但能做更多的事情。由于我们的房间只安装了控制制冷的设备,所以它们只限于同新机器的制冷部分打交道。新机器的接口已得到了扩展,但现有的系统并不知道除原始接口以外的任何东西。认识了等价与类似的区别后,再进行替换时就会有把握得多。尽管大多数时候“纯替换”已经足够,但您会发现在某些情况下,仍然有明显的理由需要在衍生类的基础上增添新功能。通过前面对这两种情况的讨论相信大家已心中有数该如何做。1.6多形对象的互换使用通常,继承最终会以创建一系列类收场,所有类都建立在统一的接口基础上。我们用一幅颠倒的树形图来阐明这一点(注释③):③:这儿采用了“统一记号法”,本书将主要采用这种方法。shapedraw()erase()move()getColoro)setColor()4CircleTriangleSquare对这样的一系列类,我们要进行的一项重要处理就是将衍生类的对象当作基础类的一个对象对待。这一点是非常重要的,因为它意味着我们只需编写单一的代码,令其忽略类型的特定细节,只与基础类打交道。这样一来,那些代码就可与类型信息分开。所以更易编写,也更易理解。此外,若通过继承增添了一种新类型,如“三角形”,那么我们为“几何形状”新类型编写的代码会象在旧类型里一样良好地工作。所以说程序具备了“扩展能力”,具有“扩展性”。以上面的例子为基础,假设我们用Java写了这样一个函数vai d dostuf f (Shape s){s.ercse();1/ ...s.daw);31
þȔĵƩÖǗ ڮơʣĢ̂ϹɼāӃڬؑŮȀڵfʣĜۋϹǫҪ٫ʋı͛ŮȺӾΫ ҖŖӢ̬ƇŮ~ڎ؇Ӄ͛ט̀Ӣ ٟʋı͛էѺֱՋŮ͛ט~ضյƒمٓէѺֱՋŮː̰bڬؑڊė٢ȳ̀ӢևφէѺϹȔ˃؇Ӄ͛Ů Ȁơֽɮėʋı͛ŮȀơֽw̧˃пֶֽėۜfĽԾɮgbڄϧۜدجҮ~ڬӢ˥ךʣĜŮֶۜ͠ƽ Ӛbńӑ~ևφՊąҊյʋı͛Ʉ؇Ӄ͛ہʰŐڄۜfűʫgȢ֟ccضյևφ̧ؤ͠ۄщ۶ŶԈfٮ ̀ӢۜʛɅי۷gbյͺơʣĜ˥ךøө~Ȁ§Ʋ̀Ӣ̞̞܈ʜӢǕϹԧφԶҔڬۜfűʫgȢ֟ ۗ~̞̞ӢǕٓدجb ŢצڄƦӑɔ~ևφÉץյ؇Ӄ͛טʨҔדŮː̰٪Ԙbԥؤáˣͅڙͺː̰~ؗĻ˂ͺۜד͛טbڬۜ ד͛טҍ̧Ծɮėʋı͛ט~ŢڬۜԾɮ×áӢէςŮ~ضյá̧ڄʋı͛͡ǂֆדȺӾbևφ˃пĖܜ f͛ԓgȢ֟ד͛مט͛˿ٓטŮː̰~ŢؗȹͺпԦȺӾ~ԥؤáϹԈԧφӢէѺűʫŮb̅Ȁͦ܇ ͊Ԉ~҂ևφ̠Ι֣ۓ͜ʌŮѫ̼bʪƇևφŮƾʰͬɀͺًٙۓ͜Ůȁ̯ۜۓшؗ̀ӢԈ~ևφمأ ٓÉؔŮfː̰g̯͊ۓۓ͜b֬ڄʪҹʌшĮͺȚڣ~ևφԧɮėԩטדŮ͜a҆ʹً̭ƃ~ƋՀɄ ֤Հ̧̖Әًb͜a̭҆ƃf͛ԓgۓ͜ʌ~ŢϹܛȅƦŮӟѫbُٙևφŮƾʰۋOE۴ͺ̯ۓۓ͜Ůҹ »~ԥؤԧφۋٙ֯ՋדʌшŮۓ͜åNJŗˈūbדʌшŮː̰أŭŪͺͅڙ~Ţ֬ٓŮ֟Վ×áۀūį٫ әː̰ؤդŮ҉ɅƊ֔b Ҋӕͺűʫٟ͛ԓŮѲÔɕ~ڃ˥ךԾɮӑ̀ɼٓֈŭƦb˨ȦŘƦӾӑɔfĽԾɮgأܑ˭Ȕ~ŢЃɼ ư֬ڄϧ׆ѫ̼֣~ҍѿٓϙ֪Ůُ͠ףؔڄ؇Ӄ͛ŮʋıҮڐՁדȈϹbՊȵєϑơڬۜʹѫ̼ŮԵ~ bܛӾǪғɅۗٓהأŘʧוֱ ±ǭ°ǢäëƓȎ Պą~ʣĜܖۛɼؤĻ˂֟ͼ͛Ӫă~ԥٓ͛ƒ˂ͧڄՎŮː̰ʋıҮbևφًǗŻŨŮӻיՖ͊Ă ϙڬżz۬Ӥq{ qڬƭëًͺfՎʠɂƲg~½ӵ˃ۧؔëًڬۜƽƲb ơڬؑŮ֟ͼ͛~ևφؔ˥ךŮؔ۞ֹĴ̀͠Ӣ˃؇Ӄ͛Ůơֽťܜʋı͛ŮȀơֽơŜbڬżӢ DŽą۞ؔŮ~ضյԧجռ܀ևφףۋÌşŮśΩ~ΊпɘΟ͛טŮԷƇ֢˔~ٟۋʋı͛ŗˈūbڬؑ ~ט͛דۜՁͺڐҖՊȵʣĜ~bńդ͠˙اȅ~ؗÌاȅؤbԥ̚NJ֘וט̧ٟ͛̀śΩ׆ϭ͊~ ̋רԈęؤbԥܜͳɀŶȆؑ͡ט͛˿ڄֽŮśΩɼÌט͛ד۷gיϭμևφյfʛɅ~gיˋғfқ »ͺfͅڙϹͩg~̋ٓfͅםڙgb ؤҮϑŮͦ܇յʋı~ʪҹևφً +AVAͺڬؑȀȺӾ VOI D DO4T UF F 4HAPE S [ S ER AS E S DR AW
1这个函数可与任何“几何形状”(Shape)通信,所以完全独立于它要描绘(daw)和删除(ercse)的任何特定类型的对象。如果我们在其他一些程序里使用bstuff()函数Crde c=newrde();Triang et =newTriange():LineI=newLine():dostuff(d:dost uf f (t) :dost uf f (1):那么对dbstuff()的调用会自动良好地工作,无论对象的具体类型是什么。这实际是一个非常有用的编程技巧。请考虑下面这行代码:dastuff(d:此时,一个rde(圆)句柄传递给一个本来期待Shape(形状)句柄的函数。由于圆是一种几何形状,所以dbstuff()能正确地进行处理。也就是说,凡是dbstuff()能发给一个Shape的消息,rde也能接收。所以这样做是安全的,不会造成错误。我们将这种把衍生类型当作它的基本类型处理的过程叫作"Upccsting”(上溯造型)。其中,“ccst”(造型)是指根据一个现成的模型创建;而“Up”(向上)表明继承的方向是从“上面”来的一一即基础类位于顶部,而衍生类在下方展开。所以,根据基础类进行造型就是一个从上面继承的过程,即“ubcosting0在面向对象的程序里,通常都要用到上溯造型技术。这是避免去调查准确类型的一个好办法。请看看dbstuff()里的代码:s.ercse()://..s.daw);注意它并未这样表达:“如果你是一个Grde,就这样做;如果你是一个Square,就那样做;等等”。若那样编写代码,就需检查一个Shcpe所有可能的类型,如圆、矩形等等。这显然是非常麻烦的,而且每次添加了一种新的Shape类型后,都要相应地进行修改。在这儿,我们只需说:“你是一种几何形状,我知道你能将自己删掉,即ercse();请自己采取那个行动,并自己去控制所有的细节吧。1.6.1动态绑定在cbStuff()的代码里,最让人吃惊的是尽管我们没作出任何特殊指示,采取的操作也是完全正确和恰当的。我们知道,为rde调用dw)时执行的代码与为一个Squae或Line调用daw)时执行的代码是不同的。但在将daw)消息发给一个匿名Shape时,根据Shape句柄当时连接的实际类型,会相应地采取正确的操作。这当然令人惊诉,因为当Java编译器为dbStuff()编译代码时,它并不知道自己已要操作的准确类型是什么。尽管我们确实可以保证最终会为Shape调用ercse(),为Shape调用daw),但并不能保证为特定的Grde,Squae或者Line调用什么。然而最后采取的操作同样是正确的,这是怎么做到的呢?将一条消息发给对象时,如果并不知道对方的具体类型是什么,但采取的行动同样是正确的,这种情况就叫作“多形性”(Pdynarphism。对面向对象的程序设计语言来说,它们用以实现多形性的方法叫作“动态绑定”。编译器和运行期系统会负责对所有细节的控制;我们只需知道会发生什么事情,而且更重要的是,如何利用它帮助自己设计程序。有些语言要求我们用一个特殊的关键字来允许动态绑定。在C++中,这个关键字是virtud。在Java中,我们则完全不必记住添加一个关键字,因为函数的动态绑定是自动进行的。所以在将一条消息发给对象时,我们完全可以肯定对象会采取正确的行动,即使其中涉及上溯造型之类的处理。1.6.2抽象的基础类和接口设计程序时,我们经常都希望基础类只为自己的衍生类提供一个接口。也就是说,我们不想其他任何人实际创建基础类的一个对象,只对上溯造型成它,以便使用它们的接口。为达到这个目的,需要把那个类变成32
] ڬȀȺӾ̧ٟ҉ɅfʛɅי۷gz4HAPE{Պו~ԥؤէѺƔͧٙԧؔϓɾzDR AW{ɄҦįzER AS E{Ů҉Ʌ ԷƇ͛טŮơֽbғȳևφڄпԦ׆ęר͡Әً DO4T UF F ȺӾ $I R CL E C NEW $I R CL E 5 R I ANGL E T NEW 5 R I ANGL E -I NE L NEW -I NE DO4T UF F C DO4T UF F T DO4T UF F L ϭμơ DO4T UF F Ůƃًɼ܈ƍͳɀŶȆܜ֊~ơֽŮ̋Խ͛טӢӒμb ڬӔʢӢȀDŽąًٓŮÌęʝџbѬ̠Ι֣ϑךڬśΩ DO4T UF F C ńӑ~Ȁ $I R CL Ezٮ̍}ÕĶŹȂȀ½͊лŜ 4HAPEzי۷{̍ÕŮȺӾbُٙٮӢۜʛɅי۷~ԥ ؤDO4T UF F ϹڷѽŶ˥ךĴ͠bؗ̀ӢԈ~ƶӢ DO4T UF F ϹưȂȀ 4HAPEŮֿ֘~$I R CL EؗϹːӪb ԥڬؤؑܛӢѺŮ~áɼڊėŔ֒b ևφ˃ڬ؇ۜӃ͛טťܜԧŮʋ½͛טĴ͠Ůȵęˎܜf6PCAS T I NGgzҮԛטڊ}bпۗ~fCAS T gzڊ ט}Ӣۉȃ̉Ȁ֬ėŮϝטĻ˂Ƭf6PgzּҮ{ÓϙʣĜŮƽּӢʼnfҮϑg͊Ůccʘʋı͛վٙ Ɔå~Ƭ؇Ӄ͛ڄ֣ƽڙ̚bԥؤ~ȃ̉ʋı͛˥טڊך̀ӢȀʼnҮϑʣĜŮȵę~ʘf6PCAS T I NGgb ڄϑּơֽŮęר~͡ՊąƒًؔŪҮԛטڊʝӹbڬӢÊϐѷƃüۼѽ͛טŮȀɀ§ƲbѬ̞̞ DO4T UF F ͡ŮśΩ S ER AS E S DR AW ۬ج×ԧջڬؑÓŕfғȳϼӢȀ$I R CL E~̀ڬؑܛғȳϼӢȀ4QUAR E~̀ϭؑܛűűgbҖϭ ؑÌśΩ~̀ףʴüȀ4HAPEԥ̧ٓϹŮ͛ט~ғٮāיűűbڬ֪ѿӢDŽąΨƷŮ~ƬѢρņՁʨ ͺۜדŮ 4HAPE͛טɕ~ƒֱؔؽŶ˥סךǫbڬڄƭ~ևφףۋԈfϼӢۜʛɅי۷~ևۀūϼϹ ˃܈ʜҦƁ~ʘER AS E Ѭ܈ʜëѵϭȀךƍ~×܈ʜѷ̯ۓԥٓŮ֢˔bg ¦Ƴ[£ ڄDO4T UF F ŮśΩ͡~ܖ҂҇ĝ˫ŮӢ˨ȦևφξܜĮ҉ɅԷӱۉӛ~ëѵŮòܜؗӢէѺڷѽɄэť Ůbևφۀū~յ $I R CL EƃًDR AW ӑךۆŮśΩٟյȀ 4QUAR Eʅ -I NEƃًDR AW ӑךۆŮśΩӢá ՋŮbŢڄ˃DR AW ֿ֘ưȂȀϽϚ 4HAPEӑ~ȃ̉ 4HAPE̍ÕťӑͬːŮӔʢ͛ט~ɼֱؽŶëѵڷѽ ŮòܜbڬťѿΊ҇˫~ضյť +AVAÌسшյDO4T UF F ÌسśΩӑ~ԧ×áۀū܈ʜؔòܜŮۼѽ͛ט ӢӒμb˨ȦևφѽӔ̧ؤ°ܖڼۛɼյ4HAPEƃًER AS E ~յ4HAPEƃًDR AW ~Ţ×áϹ°ڼյԷƇ Ů$I R CL E~4QUAR Eʅګ-I NEƃًӒμbѿƬܖɕëѵŮòܜՋؑӢڷѽŮ~ڬӢڏμܛŪŮϷ ˃Մֿ֘ưȂơֽӑ~ғȳ×áۀūơƽŮ̋Խ͛טӢӒμ~ŢëѵŮךƍՋؑӢڷѽŮ~ڬۜѫ̼̀ˎ ܜfƦםיgz1OL YMOR PHI SM{bơϑּơֽŮęרҹʟ٠͊Ԉ~ԧφًؤӔ֬ƦםיŮƽƲˎܜfƍԫ ªƇgbÌسшɄךٽл֟Վɼǥڌơԥ֢ٓ˔Ů̯ۓևφۀףۋūɼưӃӒμӟѫ~ƬѢȅ۞ؔŮӢ~ ғɅًͥԧ¨۩܈ʜҹʟęרb ٓ׆٠ؔѰևφًȀԷӱŮȢʼ܉͊צټƍԫªƇbڄ~ۗ$ڬȀȢʼ܉Ӣ VI R T UAL bڄ+AVAۗ~և φڎէѺáÉʠ۫ՁʨȀȢʼ܉~ضյȺӾŮƍԫªƇӢ܈ƍ˥ךŮbԥڄؤ˃Մֿ֘ưȂơֽӑ~և φէѺ̧ؤ̬ƇơֽɼëѵڷѽŮךƍ~ʘӘпۗҷʖҮԛہטڊ͛ŮĴ͠b ǢñĵáčĨ ҹʟęרӑ~ևφ˭ąƒ֙լʋı͛ۋյ܈ʜŮ؇Ӄ͛ԻȊȀː̰bؗ̀ӢԈ~ևφáֶпԦ҉Ʌ҇Ӕʢ Ļ˂ʋı͛ŮȀơֽ~ۋơҮԛטڊėԧ~ؤÍӘًԧφŮː̰bյŕŪڬȀϪŮ~ףؔϭȀ͛Îė
“抽象”的一一使用cbstrad关键字。若有人试图创建抽象类的一个对象,编译器就会阻止他们。这种工具可有效强制实行一种特殊的设计。亦可用bstrad关键字描述一个尚未实现的方法一一作为一个“根”使用,指出:“这是适用于从这个类继承的所有类型的一个接口函数,但目前尚没有对它进行任何形式的实现。”抽象方法也许只能在一个抽象类里创建。继承了一个类后,那个方法就必须实现,否则继承的类也会变成“抽象”类。通过创建一个抽象方法,我们可以将一个方法置入接口中,不必再为那个方法提供可能毫无意义的主体代码。interface(接口)关键字将抽象类的概念更延伸了一步,它完全禁止了所有的函数定义。“接口”是一种相当有效和常用的工具。另外如果自已愿意,亦可将多个接口都合并到一起(不能从多个普通dCsS或dbstradtdass中继承)。1.7对象的创建和存在时间从技术角度说,CP(面向对象程序设计)只是涉及抽象的数据类型、继承以及多形性,但另一些问题也可能显得非常重要。本节将就这些问题进行探讨。最重要的问题之一是对象的创建及破坏方式。对象需要的数据位于哪儿,如何控制对象的“存在时间”呢?针对这个问题,解决的方案是各异其趣的。G++认为程序的执行效率是最重要的一个问题,所以它允许程序员作出选择。为获得最快的运行速度,存储以及存在时间可在编写程序时决定,只需将对象放置在堆栈(有时也叫作自动或定域变量)或者静态存储区域即可。这样便为存储空间的分配和释放提供了一个优先级。某些情况下,这种优先级的控制是非常有价值的。然而,我们同时也牺性了灵活性,因为在编写程序时,必须知道对象的准确的数量、存在时间、以及类型。如果要解决的是一个较常规的问题,如计算机辅助设计、仓储管理或者空中交通控制,这一方法就显得太局限了。第二个方法是在一个内存池中动态创建对象,该内存池亦叫“堆”或者“内存堆”。若采用这种方式,除非进入运行期,否则根本不知道到底需要多少个对象,也不知道它们的存在时间有多长,以及准确的类型是什么。这些参数都在程序正式运行时才决定的。若需一个新对象,只需在需要它的时候在内存堆里简单地创建它即可。由于存储空间的管理是运行期间动态进行的,所以在内存堆里分配存储空间的时间比在堆栈里创建的时间长得多(在堆栈里创建存储空间一般只需要一个简单的指令,将堆栈指针向下或向下移动即可)。由于动态创建方法使对象本来就倾向于复杂,所以查找存储空间以及释放它所需的额外开销不会为对象的创建造成明显的影响。除此以外,更大的灵活性对于常规编程问题的解决是至关重要的。C+充许我们决定是在写程序时创建对象,还是在运行期间创建,这种控制方法更加灵活。大家或许认为既然它如此灵活,那么无论如何都应在内存堆里创建对象,而不是在堆栈中创建。但还要考虑另外一个问题,亦即对象的“存在时间”或者“生存时间”(Lifetine)。若在堆栈或者静态存储空间里创建一个对象,编译器会判断对象的持续时间有多长,到时会自动“破坏”或者“清除”它。程序员可用两种方法来破坏一个对象:用程序化的方式决定何时破坏对象,或者利用由运行环境提供的一种“垃圾收集器”特性,自动寻找那些不再使用的对象,并将其清除。当然,垃圾收集器显得方便得多,但要求所有应用程序都必须容忍垃圾收集器的存在,并能默许随垃圾收集带来的额外开销。但这并不符合C++语言的设计宗旨,所以未能包括到C++里。但Java确实提供了一个垃圾收集器(Snadltdk也有这样的设计:尽管Dephi默认为没有垃圾收集器,但可选择安装;而G++亦可使用一些由其他公司开发的垃圾收集产品)。本节剩下的部分将讨论操纵对象时要考虑的另一些因素。1.7.1集合与继承器针对一个特定问题的解决,如果事先不知道需要多少个对象,或者它们的持续时间有多长,那么也不知道如何保存那些对象。既然如此,怎样才能知道那些对象要求多少空间呢?事先上根本无法提前知道,除非进入运行期。在面向对象的设计中,大多数问题的解决办法似乎都有些轻率一一只是简单地创建另一种类型的对象。用于解决特定问题的新型对象容纳了指向其他对象的句柄。当然,也可以用数组来做同样的事情,那是大多数语言都具有的一种功能。但不能只看到这一点。这种新对象通常叫作“集合”(亦叫作一个“容器”,但AW在不同的场合应用了这个术语,所以本书将一直沿用“集合”的称呼。在需要的时候,集合会自动扩充自己,以便适应我们在其中置入的任何东西。所以我们事先不必知道要在一个集合里容下多少东西。只需创建一个集合,以后的工作让它自己负责好了。幸运的是,设计优良的CP语言都配套提供了一系列集合。在G++中,它们是以“标准模板库”(STL)的形式提供的。jectPcscd用自己的“可视组件库”(Va)提供集合。SnadItdk提供了一套非常完整的集合。而Java也用自已的标准库提供了集合。在某些库中,一个常规集合便可满足人们的大多数要求;而在另33
fĪֽgŮccӘًABS T R ACT Ȣʼ܉bҖٓ҇өՖĻ˂Īֽ͛ŮȀơֽ~Ìسш̀ɼۊܔԦφbڬۜȆ̋ ̧ׅٓћۓӔךۜԷӱŮҹʟb ت ً̧ABS T R ACT Ȣʼ܉ϓӺȀүջӔ֬ŮƽƲccܜյȀfȃgӘً~ۉĮfڬӢӣًٙʼnڬȀ͛ʣ ĜŮԥٓ͛טŮȀː̰ȺӾ~ŢϪєүξٓơԧ˥ך҉ɅיӚŮӔ֬bgĪֽƽƲؗۋצϹڄȀĪֽ͛ ͡Ļ˂bʣĜͺȀ͛ɕ~ϭȀƽƲ̀ÉץӔ֬~ǕڎʣĜŮ͛ؗɼÎėfĪֽg͛bՊȵĻ˂ȀĪֽƽ Ʋ~ևφ̧ؤ˃ȀƽƲےҔː̰ۗ~áÉڃյϭȀƽƲԻȊ̧Ϲȿ֊دجŮۧԽśΩb I NT ER FACEzː̰{Ȣʼ܉˃Īֽ͛ŮǬЀȅһͺã~ԧէѺ˦ۊͺԥٓŮȺӾƇدbfː̰gӢֱۜ ťׅٓɄąًŮȆ̋bΉդғȳ܈ʜجٲ~ت˃̧ƦȀː̰ƒɆ×ŪуzáϹʼnƦȀкՊ CL AS S ʅ ABS T R ACT CL AS S ۗʣĜ{b °ǢĊáȠƏĀ ʼnʝӹˋƙԈ~001zϑּơֽęרҹʟ{ۋӢҷʖĪֽŮӾ̉͛טaʣĜؤʖƦםי~ŢΉ׆ֆԼ̧ؗϹ ֪ŭDŽą۞ؔb½˔˃̀׆ڬֆԼ˥ךԵb ܖؔ۞ŮֆԼہӢơֽŮĻ˂ʖдɨƽӚbơֽףؔŮӾ̉վٙϬƭ~ғɅ̯ۓơֽŮfŐڄӑʰgϷ ڮơڬȀֆԼ~˙̓ŮƽӢȁشпѶŮb$ҊյęרŮךۆׅΛӢܖؔ۞ŮȀֆԼ~ԥؤԧצټę٭ר ܜĮڍׯbյʄŭܖ̷Ůךٽԙƙ~ŐIJؤʖŐڄӑʰ̧ڄÌęרӑ̓Ƈ~ףۋ˃ơֽǃڄےƟښzٓӑ ؗˎ܈ܜƍʅƇ١ÎͶ{ʅګԫ˲ŐIJѲ١ʘ̧bڬؑÍյŐIJ̭ʰŮNJМɄӤǃԻȊͺȀٌ֥ʙbϧ׆ ѫ̼֣~ڬٌ֥ۜʙŮ̯ۓӢDŽąٓʫۇŮbѿƬ~ևφՋӑؗ֗ӄͺ·ʁם~ضյڄÌęרӑ~Éۀץ ūơֽŮۼѽŮӾͶaŐڄӑʰaؤʖ͛טbғȳؔ˙̓ŮӢȀˍąȬŮֆԼ~ғʟԝʌǝ۩ҹʟaðIJ Ȧ͠ʅګˈ̭ۗՊ̯ۓ~ڬƽƲ֪̀ŭԪ̃֯ͺb ŷƯȀƽƲӢڄȀϸŐğۗƍԫĻ˂ơֽ~ǪϸŐğتˎfƟgʅګfϸŐƟgbҖëًڬۜƽӚ~įDŽ ˥Ҕךٽл~Ǖڎȃ½áۀūŪŵףؔƦұȀơֽ~ؗáۀūԧφŮŐڄӑʰٓƦĆ~ؤʖۼѽŮ͛טӢӒ μb׆ڬîӾƒڄęڷרӚךٽӑé̓ƇŮbҖףȀדơֽ~ףڄףۋԧؔŮӑɔڄϸŐƟ͡ʵşŶĻ˂ ԧʘ̧bُٙŐIJ̭ʰŮȦ͠Ӣךٽлʰƍԫ˥ךŮ~ԥڄؤϸŐƟ͡NJМŐIJ̭ʰŮӑʰÁڄƟښ͡Ļ˂ ŮӑʰĆŭƦzڄƟښ͡Ļ˂ŐIJ̭ʰ¡ףۋؔȀʵşŮۉΊ~˃Ɵڮۉښּ֣ʅּ֣ؠƍʘ̧{bُ ٙƍԫĻ˂ƽƲӘơֽ½͊̀ѨּٙǢٿ~ԥؤüڦŐIJ̭ʰؤʖӤǃԧԥףŮƨդ̚־áɼյơֽŮĻ˂ ڊėϙ֪Ůفַbįńؤդ~ȅŘŮ·ʁםơٙąȬÌęֆԼŮ˙̓ӢېȢ۞ؔŮb $צټևφ̓ƇӢڄęרӑĻ˂ơֽ~ɬӢךٽڄлʰĻ˂~ڬ̯ۜۓƽƲȅʨ·ʁbŘʧʅצҊյʡѿ ԧғń·ʁ~ϭμ֊ғɅƒڄؽϸŐƟ͡Ļ˂ơֽ~ƬáӢڄƟښۗĻ˂bŢɬ̠ؔΙΉդȀֆԼ~ت ʘơֽŮfŐڄӑʰgʅګfӃŐӑʰgz-I F ET I ME{bҖڄƟښʅګԫ˲ŐIJ̭ʰ͡Ļ˂Ȁơֽ~Ìس шɼЕƞơֽŮĞӑʰٓƦĆ~Ūӑɼ܈ƍfдɨgʅګfѩįgԧbę٭רۜʹً̧ƽƲ͊дɨȀơ ֽًęרɥŮƽӚ̓ƇɅӑдɨơֽ~ʅګًُiךٽɪ˳ԻȊŮۜf͇ʊӪʕшgԷם~܈ƍڦ״ϭ ׆áڃӘًŮơֽ~×˃пѩįbťѿ~͇ʊӪʕш֪ŭƽÍŭƦ~ŢؔѰԥٓؽًęרƒÉץҐ҈͇ʊӪ ʕшŮŐڄ×~Ϲϥצԟ͇ʊӪʕŚ͊Ůƨդ̚־bŢڬ×áǘɆ$٠Ůҹʟی܋~ԥؤջϹ̈́Ū $ ͡bŢ +AVAѽӔԻȊͺȀ͇ʊӪʕшz4MAL L T AL K ؗٓڬؑŮҹʟ˨Ȧ %EL PHI ϥҊյξ͇ٓʊӪʕ ш~Ţ̧ڍׯOE۴Ƭ $ت̧Әً׆ُпԦȋԏ̚ưŮ͇ʊӪʕāЭ{b ½˔ӈ֣ŮåNJ˃Եòơֽӑ̠ؔΙŮΉض׆Ԙb öãȔüů ڮơȀԷƇֆԼŮ˙̓~ғȳӟ֥áۀūףؔƦұȀơֽ~ʅګԧφŮĞӑʰٓƦĆ~ϭμؗáۀūғ Ʌ°Őϭ׆ơֽbʡѿғń~ڏؑéϹۀūϭ׆ơֽؔѰƦұ̭ʰϷ ӟ֥Үȃ½֊ƲԻєۀū~įDŽ˥Ҕ лbךٽ ڄϑּơֽŮҹʟۗ~ŘƦӾֆԼŮ˙̓§Ʋԓɗƒٓ׆ѧΛccۋӢʵşŶĻ˂Ή͛ۜטŮơֽbًٙ ˙̓ԷƇֆԼŮטדơֽҐϮͺۉּпԦơֽŮ̍Õbťѿ~̧ؗؤًӾܕ͊ܛՋؑŮӟѫ~ϭӢŘƦӾ٠ ƒ̋ٓŮۜȈϹbŢáϹۋ̞ŪڬżbڬۜדơֽՊąˎܜfʕɆgzتˎܜȀfҐшg~Ţ "85 ڄáՋŮăɆؽًͺڬȀӹ٠~ԥؤ1/2ӵ˃ۄًfʕɆgŮĖɖbףڄؔŮӑɔ~ʕɆɼ܈ƍͅĥ܈ ʜ~ؤÍӣؽևφڄпۗےҔŮ҉ɅƊ֔bԥؤևφӟ֥áÉۀūؔڄȀʕɆ͡Ґ֣ƦұƊ֔bףۋĻ˂ ɀͺbڌʜǥ܈ԧ҂ܜɕŮȆؤ~ȀʕɆ ٽלŮӢ~ҹʟٌͳŮ 001٠ƒМԶԻȊͺ֟ͼʕɆbڄԧ~ۗ$ φӢؤfÒۼϝ¢̳gz45-{Ůי ӚԻȊŮb0BJ ECT 1AS CAL ً܈ʜŮf̧Өܕʾ̳gz7$-{ԻȊʕɆb4MAL L T AL K ԻȊͺԶDŽąէڶŮʕ ɆbƬ +AVAًؗ܈ʜŮÒۼ̳ԻȊͺʕɆbڄϧ׆~̳ۗȀąȬʕɆÍ̧ΰܑ҇φŮŘƦӾؔѰƬڄΉ
一些库中(特别是G+的库),则面向不同的需求提供了不同类型的集合。例如,可以用一个失量统一对所有元素的访问方式:一个链接列表则用于保证所有元素的插入统一。所以我们能根据自己的需要选择适当的类型。其中包括集、队列、散列表、树、堆栈等等。所有集合都提供了相应的读写功能。将某样东西置入集合时,采用的方式是士分明显的。有一个叫作“推”(Push)、“添加”(Add)或其他类似名字的函数用于做这件事情。但将数据从集合中取出的时候,方式却并不总是那么明显。如果是一个数组形式的实体,比如一个失量(Vectar),那么也许能用索引运算符或函数。但在许多情况下,这样做往往会无功而返。此外,单选定函数的功能是非常有限的。如果想对集合中的一系列元素进行操纵或比较,而不是仅仅面向一个,这时又该怎么办呢?办法就是使用一个“继续器”(Iterαtar),它属于一种对象,负责选择集合内的元素,并把它们提供给继承器的用户。作为一个类,它也提供了一级抽象。利用这一级抽象,可将集合细节与用于访问那个集合的代码隔离开。通过继承器的作用,集合被抽象成一个简单的序列。继承器允许我们遍历那个序列,同时毋需关心基础结构是什么一一换言之,不管它是一个量、一个链接列表、一个堆栈,还是其他什么东西。这样来,我们就可以灵活地改变基础数据,不会对程序里的代码造成干扰。Java最开始(在1.0和1.1版中)提供的是一个标准继承器,名为Enuneration(枚举),为它的所有集合类提供服务。Java1.2新增一个更复杂的集合库,其中包含了一个名为Iteratar的继承器,可以做比老式的Enunration更多的事情。从设计角度出发,我们需要的是一个全功能的序列。通过对它的操纵,应该能解决自己的问题。如果一种类型的序列即可满足我们的所有要求,那么完全没有必要再换用不同的类型。有两方面的原因促使我们需要对集合作出选择。首先,集合提供了不同的接口类型以及外部行为。堆栈的接口与行为与队列的不同,而队列的接口与行为又与一个集(Set)或列表的不同。利用这个特征,我们解决问题时便有更大的灵活性。其次,不同的集合在进行特定操作时往往有不同的效率。最好的例子便是矢量(Vectar)和列表(List)的区别。它们都属于简单的序列,拥有完全一致的接口和外部行为。但在执行一些特定的任务时,需要的开销却是完全不同的。对矢量内的元素进行的随机访问(存取)是一种常时操作;无论我们选择的选择是什么,需要的时间量都是相同的。但在一个链接列表中,若想到处移动,并随机挑选一个元素,就需付出“惨重”的代价。而且假设某个元素位于列表较远的地方,找到它所需的时间也会长许多。但在另一方面,如果想在序列中部插入一个元素,用列表就比用失量划算得多。这些以及其他操作都有不同的执行效率,具体取决于序列的基础结构是什么。在设计阶段,我们可以先从一个列表开始。最后调整性能的时候,再根据情况把它换成失量。由于抽象是通过继承器进行的,所以能在两者方便地切换,对代码的影响则显得微不足道。最后,记住集合只是一个用来放置对象的储藏所。如果那个储藏所能满足我们的所有需要,就完全没必要关心它具体是如何实现的(这是大多数类型对象的一个基本概念)。如果在一个编程环境中工作,它由于其他因素(比如在Wndow下运行,或者由垃圾收集器带来了开销)产生了内在的开销,那么矢量和链接列表之间在系统开销上的差异就或许不是一个大问题。我们可能只需要一种类型的序列。甚至可以想象有一个“完美”的集合抽象,它能根据自己的使用方式自动改变基层的实现方式。1.7.2单根结构在面向对象的程序设计中,由于++的引入而显得尤为突出的一个问题是:所有类最终是否都应从单独一个基础类继承。在JaVa中(与其他几乎所有CP语言一样),对这个问题的答案都是肯定的,而且这个终级基础类的名字很简单,就是一个“Qject”。这种“单根结构”具有许多方面的优点。单根结构中的所有对象都有一个通用接口,所以它们最终都属于相同的类型。另一种方案(就象G++那样)是我们不能保证所有东西都属于相同的基本类型。从向后兼容的角度看,这一方案可与C模型更好地配合而且可以认为它的限制更少一些。但假期我们想进行纯粹的面向对象编程,那么必须构建自己的结构,以期获得与内建到其他CP语言里的同样的便利。需添加我们要用到的各种新类库,还要使用另一些不兼容的接口。理所当然地,这也需要付出额外的精力使新接口与自己的设计方案配合(可能还需要多重继承)。为得到G+额外的“灵活性”,付出这样的代价值得吗?当然,如果真的需要一一如果早已是C专家,如果对C有难舍的情结一一那么就真的很值得。但假如你是一名新手,首次接触这类设计,象JVva那样的替换方案也许会更省事一些。单根结构中的所有对象(比如所有JCVα对象)都可以保证拥有一些特定的功能。在自已的系统中,我们知道对每个对象都能进行一些基本操作。一个单根结构,加上所有对象都在内存堆中创建,可以极大简化参数的传递(这在++里是一个复杂的概念)。利用单根结构,我们可以更方便地实现一个垃圾收集器。与此有关的必要支持可安装于基础类中,而垃圾收集器可将适当的消息发给系统内的任何对象。如果没有这种单根结构,而且系统通过一个句柄来操纵对象,那么实现垃圾收集器的途径会有很大的不同,而且会面临许多障碍。由于运行期的类型信息肯定存在于所有对象中,所以永远不会遇到判断不出一个对象的类型的情况。这对系34
׆̳ۗzԷÔӢ $Ů̳{~ڎϑּáՋŮףѰԻȊͺáՋ͛טŮʕɆbͦғ~̧ؤًȀӗͶՎơԥ ӣťŮڍׯؔףʜŮ܈̉ևφϹȃؤbԥԘŮúҔՎٓ٪ԥڼ°ًٙڎȀͰːͼÓԘŮǂֆƽӚٓ٪ ͛טbпۗ̈́ʕaƠͼaҜͼÓaӻaƟښűűb ԥٓʕɆƒԻȊͺֱؽŮƕȈϹb˃ϧؑƊ֔ےҔʕɆӑ~ëًŮƽӚӢӎNJϙ֪ŮbٓȀˎܜf՜g z1USH{afՁʨgz"DD{ʅпԦ͛ԓϚ܉ŮȺӾًٙڬܛʾӟѫbŢ˃Ӿ̉ʼnʕɆۗѵĮŮӑɔ~ƽӚѼ ×á܍Ӣϭμϙ֪bғȳӢȀӾיܕӚŮӔԽ~ÁғȀӗͶz7ECT OR {~ϭμؗצϹًԣٽعԝǘʅȺ ӾbŢצڄƦѫ̼֣~ڬؑܛժժɼ֊ȈƬƹbńդ~şׯƇȺӾŮȈϹӢDŽąٓ֯ŮbғȳֶơʕɆۗŮ ֟ͼ٪Ԙ˥ךòʅÁˍ~ƬáӢˣˣϑּȀ~ڬӑٗǪڏμ§Ϸ §Ʋ̀ӢӘًȀfʣшgz* T ER AT OR {~ԧӸٙۜơֽ~ǥڍׯڌʕɆϸŮ٪Ԙ~×ԧφԻȊȂʣ ĜшŮًɞbܜյȀ͛~ԧؗԻȊͺʙĪֽbًͥڬʙĪֽ~̧˃ʕɆ֢˔ًٟٙǂֆϭȀʕɆŮś Ωǿ̚͞bՊȵʣĜшŮܜ~ًʕɆ¼ĪֽėȀʵşŮרͼbʣĜшצټևφÑͤϭȀרͼ~ՋӑףȢ הʋı˘ȒӢӒμccɮہ~áȦԧӢȀӗͶaȀͰːͼÓaȀƟښ~ɬӢпԦӒμƊ֔bڬؑ ͊~ևφ̧̀ؤ·ʁŶǫÎʋıӾ̉~áɼơęר͡ŮśΩڊėǮ҃b+AVAܖ̚әzڄ Ʉ £ۗ{Ի ȊŮӢȀÒۼʣĜш~Ϛյ &NUMER AT I ONzν̅{~յԧŮԥٓʕɆ͛ԻȊǛ֑b+AVA ڐדȀȅǢ ٿŮʕɆ̳~пۗȹͺȀϚյ * T ER AT OR ŮʣĜш~̧ܛؤÁ͖ӚŮ&NUMER AT I ONȅƦŮӟѫb ʼnҹʟˋƙĮư~ևφףؔŮӢȀѺȈϹŮרͼbՊȵơԧŮò~ؽǪϹ˙̓܈ʜŮֆԼbғȳ͛ۜ טŮרͼʘ̧ΰܑևφŮԥٓؔѰ~ϭμէѺξٓÉؔڃɮًáՋŮ͛טbٓʹƽϑŮ٫ضōӘևφףؔơ ʕɆܜĮڍׯbӬ֥~ʕɆԻȊͺáՋŮː̰͛ؤטʖդåךյbƟښŮː̰ךٟյٟƠͼŮáՋ~ƬƠͼ Ůː̰ךٟյٟٗȀʕz4ET{ʅͼÓŮáՋbًͥڬȀԷڴ~ևφ˙̓ֆԼӑÍٓȅŘŮ·ʁםb пņ~áՋŮʕɆڄ˥ךԷƇòܜӑժժٓáՋŮׅΛbܖɀŮͦ܇ÍӢӗͶz7ECT OR{ɄͼÓz-I ST {Ů ѲÔbԧφƒӸٙʵşŮרͼ~مٓէѺۑŮː̰ɄդåךյbŢ׆ךۆڄԷƇŮ҉֑ӑ~ףؔŮ̚־ ѼӢէѺáՋŮbơӗͶϸŮ٪Ԙ˥ךŮԟʌǂֆzŐѵ{Ӣۜąӑòܜ֊ևφڍׯŮڍׯӢӒμ~ ףؔŮӑʰͶƒӢֱՋŮbŢڄȀͰːͼÓۗ~ҖֶŪĴؠƍ~×ԟʌՃׯȀ٪Ԙ~̀ףǣĮfï۞g ŮśʫbƬѢʪҹϧȀ٪ԘվٙͼÓˍٱŮŶƽ~ڦŪԧԥףŮӑʰؗɼĆצƦbŢڄΉƽϑ~ғȳֶڄ רͼۗåúҔȀ٪Ԙ~ًͼÓ̀ÁًӗͶɤԝŭƦbؤ׆ڬʖпԦòܜƒٓáՋŮךۆׅΛ~̋Խѵ̓ٙ רͼŮʋı˘ȒӢӒμbڄҹʟ˒Ɲ~ևφ̧ؤ֥ʼnȀͼÓ̚әbܖɕƃםڶϹŮӑɔ~ڃȃ̉ѫ̼ԧ ɮėӗͶbُٙĪֽӢՊȵʣĜш˥ךŮ~ԥؤϹڄʹګƽÍŶѡɮ~ơśΩŮفַڎ֪ŭհáܑūb ܖɕ~ʠ۫ʕɆۋӢȀً͊ǃےơֽŮIJñԥbғȳϭȀIJñԥϹΰܑևφŮԥٓף̀~ؔէѺξÉؔȢ ה̋ԧԽӢғɅӔ֬ŮzڬӢŘƦӾ͛טơֽŮȀʋ½ǬЀ{bғȳڄȀÌęɪ˳ۗȆܜُٙԧ~пԦ ضԘzÁғڄ8I NDOWS ֣ךٽ~ʅګُ͇ʊӪʕшŚ͊ͺ̚־}āӃͺϸڄŮ̚־~ϭμӗͶɄͰːͼÓہ Ȁfէֶֽٓؤ̧ېͼbӀרŮט͛ۜؔףۋϹ̧ȀŘֆԼbևφáӢצʅ̀شҮŮþ־̚Վ֟ڄʰ ςgŮʕɆĪֽ~ԧϹȃ̉܈ʜŮӘًƽӚ܈ƍǫÎʋùŮӔ֬ƽӚb ÌđÒ ڄϑּơֽŮęרҹʟۗ~ُٙ$ŮعҔƬ֪ŭَյՕĮŮȀֆԼӢԥٓ͛ܖۛӢǕƒؽʼnşƔȀ ʋı͛ʣĜbڄ+AVAۗzٟпԦʛɗԥٓ001٠~}ؑơڬȀֆԼŮŖƒӢ̬ƇŮ~ƬѢڬȀۛʙʋ ı͛ŮϚ܉Ɍʵş~̀ӢȀf0BJ ECT gbڬۜfşȃ˘Ȓg̋ٓצƦƽϑŮٌżb şȃ˘ȒۗŮԥٓơֽƒٓȀՊًː̰~ԥؤԧφܖۛƒӸֱٙՋŮ͛טbΉۜƽzֽ̀ $ϭؑ{ ӢևφáϹ°ڼԥٓƊ֔ƒӸֱٙՋŮʋ½͛טbʼnּɕʱҐŮˋƙ̞~ڬƽ̧ٟ$ϝטȅɀŶМɆ~ ƬѢ̧ؤҊյԧŮ֯ۓȅұ׆bŢʪлևφֶ˥ךĽŏŮϑּơֽÌę~ϭμÉץȒ˂܈ʜ٢Ȓ~ؤл ʄŭٟϸ˂ŪпԦ 001 ٠͡ŮՋؑŮÍͥbףՁʨևφًؔŪŮȁۜד~̳͛ɬؔӘًΉ׆áʱҐŮː ̰b͠ԥťѿŶ~ڬؗףؔǣĮƨդŮˬͩӘדː̰܈ٟʜŮҹʟƽМɆz̧ϹɬףؔƦ۞ʣĜ{bյŭ Ū$ƨդŮf·ʁםg~ǣĮڬؑŮśʫۇŭΫ ťѿ~ғȳڭŮףؔccғȳأډӢ $۱ʧ~ғȳơ$ ٓϳҵŮѫ˘ccϭμ̀ڭŮɌۇŭbŢʪғϼӢϚדӫ~Ӭņːijڬ͛ҹʟ~ֽ+AVAϭؑŮԾɮƽؗ b׆ɼȅӆӟצ şȃ˘ȒۗŮԥٓơֽzÁғԥٓ +AVAơֽ{ƒ̧ؤ°مڼٓ׆ԷƇŮȈϹb܈ڄʜŮ֟Վۗ~ևφۀū ơρȀơֽƒϹ˥׆ךʋ½òܜbȀşȃ˘Ȓ~ʨҮԥٓơֽƒڄϸŐƟۗĻ˂~̧ؤʑŘʵɥîӾŮ ĶŹzڄڬ͡$ ӢȀǢٿŮǬЀ{b ًͥşȃ˘Ȓ~ևφ̧ؤȅƽÍŶӔ֬Ȁ͇ʊӪʕшbٟńٓȢŮÉؔڿĢ̆۴ٙʋı͛ۗ~Ƭ͇ʊӪ ʕш̧˃ӣťŮֿ֘ưȂ֟ՎϸŮ҉Ʌơֽbғȳξٓڬۜşȃ˘Ȓ~ƬѢ֟ՎՊȵȀ̍Õ͊òơֽ~ ϭμӔ͇֬ʊӪʕшŮ˶ɼٓɌŘŮáՋ~ƬѢɼϑצƦڣSb ُٙךٽлŮ͛וט̬֘ƇŐڄٙԥٓơֽۗ~ԥٱيؤáɼ٢ŪЕƞáĮȀơֽŮ͛טŮѫ̼bڬơ֟