第十章 类、对象与实现(五)

2015-01-27 06:28:22 · 作者: · 浏览: 122
派生类可以继承和访问基类中的成员,但基类无法访问派生类的成员;这是由编译器、及公私有等变量限定属性实现的。被继承的类称为父类或超类,继承了父类或超类的所有数据和操作的类称为子类或派生类。

1)、继承的特点:

第一、子类拥有父类的属性和方法;

第二、子类可以有自己新的属性和方法;

第三、子类可以重写父类的方法;

第四、可以声明自己为父类,创建子类。

2)、多重继承:

在APO中,可以说表、类继承关系数组变量就很自然地实现了多重继承。多重继承是普遍的、自然的。但多重继承需解决二义性问题或说是菱形继承问题。


何时出现二义性?APO如何解决?


1))、当一个派生类从多个基类派生,而这些基类又有同名成员,在对该同名成员进行访问时,可能会出现二义性。


例子:类C3继承了2个类C1,C2中的同名方法f(),一个是c1的,另一个是c2的,如果不显式声明调用哪一个,编译器就不知道你要调用哪一个。这时就必须使用作用域分辨符:写为C3.C1.f();或C3.C2.f();就可以了。但C++非得通过C3的对象才可以,因为f()本质上是C1或者是C2的成员函数,而类成员函数都是要通过对象C3来调用的,如果你想通过C1.f()来直接调用,那么f()必须是static。为何这样?如果C1是C3的派生类,那么在C3对象中调用C1.f()就变成父类可以调用子类的方法了。所以,C3.C1.f();或this.C1.f();编译器就会查找C3对象是否有对C1的引用,有则调用,否则报错。APO的调用都是:类名.方法名;而不管类名是父类的、基类的、本类的、库类的、系统类的、动态类的等等;本类的派生类则报错。所以,APO调用是可以直接写为:C1.f()的,编译器把C1对象号和类号编译传过来后,汇编代码会在C3对象所属的类表项中的类继承关系变量中查找是否有C1的类号,如有则从类表项中找到对应的方法表基址;再加上方法表的方法相对地址最终指向方法f()。静态类由编译器直接给出方法的相对入口地址;如果C1、C2是动态对象,那只能在C3的对象所属的类表项中的类继承关系变量中分别查到C1、C2的类号,没有则报错;从而在类表项中找到对应的方法表基址;再加上方法表的方法相对地址最终指向方法f()。所以,APO在这点上是没有二意的。


2))、当一个派生类从多个基类派生,而这些基类又有一个公共的基类,在对该基类中说明的成员进行访问时,可能会出现二义性。这就是著名的菱形继承问题。

当在多条继承路径上有一个公共的基类时,在这些路径中的某几条汇合处,这个公共的基类就会产生多个副本;从而出现二义性。比如一个类继承了100个父类,而父类又继承100个祖父类。。。形成许许多多的路径,最终都到达10个根类。就算只是2级,都有10000条路径通向10个根类,也会多产生了9990个对象副本;并造成二意。

C++中,为解决菱形继承还引入了一个更扭曲的虚继承。Python支持多种继承, 并且因为所有的对象都继承于共同的基类Object,导致任何多重继承其实都是菱形继承,当你真的开始自己用多重继承时,其实继承结构已经是一张网了,对此, Guido VanRossum的答案是用深度优先搜索,靠基类排列的顺序来决定, 显的更加神奇。

C++的多重继承很麻烦,而且又容易出错。而无数的JAVA用户者则是把C++的多重继承当作笑话和C++混乱的根源,自豪的宣布JAVA没有这个问题。J那就是没有没有C++自由的多重继承, 只支持对实现的单继承, 并引入了接口, 只允许接口的多重继承。并且, 在继承接口和继承基类形式上特别加了一些区别,所以实际中, 很多人并不把这叫做多重继承,并宣称JAVA没有多重继承。“组合优于继承”这一设计原则,体会了JAVA社区提倡的面向接口编程。“继承本身就是一种强耦合”,就是一种子类对父类的依赖耦合,在一些书籍中提到, 甚至继承本身都是不提倡的, 也就是说, 提倡的是基于对象的设计(OB), 而不是面向对象的设计(OO)。JAVA最多算半个多重继承,只是行为多继承。大家都知道, 加一个继承只要一个单词,而加一个对象的组合调用,往往需要增加N个函数接口,以及N个调用(虽然有种东西叫做委托)。

Mix-in类是具有以下特征的抽象类:
不能单独生成实例
不能继承普通类

通过这种在接口和普通多重继承之间的折衷,Mix-in提供了对实现的多重继承,同时对其进行了限制, 使得继承结构不会成菱形, 而是和单一继承一样的树型. 在Ruby中, Mix-in是通过模块(module)的概念来实现的。有趣的事情是,在编程这件事情上, 很多时候, 折衷的方法却往往是优秀的方法。但不符合自然的东东,终是遗憾。

APO是很自然的支持多重继承。在对象所属的类表项中的类继承关系变量中有类关系数组,包括父类的,所有的关系类都是唯一的;不会出现多副本的情形。对象初始化;是先调用父类对象初始化方法,再对关系数组中的具体类的对象作初始化;其实,对象乱序初始化也没问题的。

扭曲式多重继承、循环式多重继承会产生父子不清的现象。比如,一只黑狗有一个儿子白狗,而黑狗的母亲狗又跟儿子白狗生了一只公黄狗。公黄狗是孙子狗?还是儿子狗?还是算父亲狗?我都转晕了。尽管自然界,这种现象极少;但还是有的。这种扭曲多重继承的特点就是:相互引用就可相互调用;也就是基类可调用派生类的方法。APO的多重继承也同样是自然的支持这种扭曲式多重继承。

(4)多态性(多形性)

多态性是指相同的操作或方法、过程可作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果,这种现象称为多态性。多态性允许每个对象以适合自身的方式去响应共同的消息。多态性增强了软件的灵活性和重用性。

(5)、抽象类

边学、边抄、边写下这段章节后的感想:何为抽象?那是亿众瞩目的地方,那是灵魂深处的悠游,那是联想的发源地,那是万川归一的大海,那是世界的最顶峰,也是宇宙的边缘。从那看进去:一眼星河涌现、神光飞舞、浮想缠绵。

抽象,顾名思义,就是抽掉了具体形象的东西。把具体概念的诸多个性排出,集中描述其共性,就会产生一个抽象性的概念。抽象概念的外延大,内涵小,具体概念的外延小,内涵大。要抽象,就必须进行比较,没有比较就无法找到在本质上共同的部分;所以抽象的过程也是一个裁剪的、分类的过程。在抽象时,同与不同,决定于从什么角度上来抽象;抽象的角度取决于分析问题的目的。抽象通过分析与综合的途径,运用概念在人脑中再现对象的质和本质的方法,分为质的抽象和本质的抽象。分析形成质的抽象,综合形成本质的抽象(也叫具体的抽象)。万物都是容器,类名就是从无数的容器中分离出具有一些相同属性、相同方法的容器集的名称;所以分类的过程就是抽象过程。类名就是一种抽象名,我们把类的个体容器称为对象;所以,也说万物都是对象。但从另一角度看,“万物”、“对象”就是一个更为抽象的概念;它们泛指一切。我们根据一些特性,也即是属性;抽象出“苹果”类,“苹果”也有大小、青红之分等等;所以“苹果”类下还可再分成具体类,之后才到具体的某个苹果。类似的还有“香蕉”、“梨子”等等,我们又从这些“苹果”、“香蕉”、“梨子”等抽象概念中,抽象出它们的共性,得到“水果”类。而“水果”类可以说它们的成员也是类,所以说“