面向对象继承_第1页
面向对象继承_第2页
面向对象继承_第3页
面向对象继承_第4页
面向对象继承_第5页
已阅读5页,还剩76页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

面向对象继承第一页,共八十一页,编辑于2023年,星期五6/25/20232这种把继承作为一种扩展同时也作为一种收缩的思想,正是面向对象技术强大的原因,同时也会在正常的部署中引起混淆

。扩展与简化第二页,共八十一页,编辑于2023年,星期五6/25/20233继承总是向下传递的,因此一个类可以从它上面的多个超类中继承各种属性

。如果Dog是Mammal的派生类,而Mammal又是Animal的派生类,则Dog不仅继承了Mammal的属性,同时也继承了Animal的属性。派生类可以覆盖从基类继承来的行为。继承是向下传递的第三页,共八十一页,编辑于2023年,星期五6/25/20234代码复用概念复用。共享方法的定义。继承的作用第四页,共八十一页,编辑于2023年,星期五6/25/20235检验两个概念是否为继承关系“是一个”检验第五页,共八十一页,编辑于2023年,星期五6/25/20236classParent{private: intthree;protected: inttwo;public: intone; Parent(){one=two=three=42;}voidinParent(){cout<<one<<two<<three;//alllegal}};Public,PrivateandProtected第六页,共八十一页,编辑于2023年,星期五6/25/20237classChild:publicParent{public: voidinChild(){ cout<<one;//legal cout<<two;//legal cout<<three;//error-notlegal }};Public,PrivateandProtected第七页,共八十一页,编辑于2023年,星期五6/25/20238voidmain(){Childc;cout<<c.one;//legalcout<<c.two;//error-notlegalcout<<c.three;//error-notlegal}Public,PrivateandProtected第八页,共八十一页,编辑于2023年,星期五6/25/20239在静态类型语言中父类和子类数据类型的关系?子类实例必须拥有父类的所有数据成员。子类的实例必须至少通过继承实现父类所定义的所有功能。这样,在某种条件下,如果用子类实例来替换父类实例,那么将会发现子类实例可以完全模拟父类的行为,二者毫无差异。观察第九页,共八十一页,编辑于2023年,星期五6/25/202310指如果类B是类A的子类,那么在任何情况下都可以用类B来替换类A,而外界毫无察觉。替换原则第十页,共八十一页,编辑于2023年,星期五6/25/202311指符合替换原则的子类关系。区别于一般的可能不符合替换原则的子类关系子类型第十一页,共八十一页,编辑于2023年,星期五6/25/202312子类有时为了避免继承父类的行为,需要对其进行改写语法上:子类定义一个与父类有着相同名称且类型签名相同的方法。运行时:变量声明为一个类,它所包含的值来自于子类,与给定消息相对应的方法同时出现于父类和子类。改写与替换结合时,想要执行的一般都是子类的方法。改写第十二页,共八十一页,编辑于2023年,星期五6/25/202313Java、Smalltalk等面向对象语言,只要子类通过同一类型签名改写父类的方法,自然便会发生所期望的行为。C++中,需要父类中使用关键字Virtual来表明这一含义。改写机制第十三页,共八十一页,编辑于2023年,星期五6/25/202314与类一样,接口可以继承于其他接口,甚至可以继承于多个父接口。虽然继承类和实现接口并不完全相同,但他们非常相似,因此使用继承这一术语来描述这两种行为抽象方法:介于类和接口之间的概念。定义方法但不实现。创建实例前,子类必须实现父类的抽象方法。Java,C#:abstractC++:virtual接口和抽象类第十四页,共八十一页,编辑于2023年,星期五6/25/202315特殊化(specialization)继承

规范化(specification)继承构造(Construction)继承泛化继承扩展继承

限制继承

变体继承合并继承

(多重继承)继承的形式第十五页,共八十一页,编辑于2023年,星期五6/25/202316很多情况下,都是为了特殊化才使用继承。在这种形式下,新类是基类的一种特定类型,它能满足基类的所有规范。

用这种方式创建的总是子类型,并明显符合可替换性原则。与规范化继承一起,这两种方式构成了继承最理想的方式,也是一个好的设计所应追求的目标。Window-TextWindow特殊化继承第十六页,共八十一页,编辑于2023年,星期五6/25/202317规范化继承用于保证派生类和基类具有某个共同的接口,即所有的派生类实现了具有相同方法界面的方法。基类中既有已实现的方法,也有只定义了方法接口、留待派生类去实现的方法。派生类只是实现了那些定义在基类却又没有实现的方法。规范化继承

第十七页,共八十一页,编辑于2023年,星期五6/25/202318派生类并没有重新定义已有的类型,而是去实现一个未完成的抽象规范。

也就是说,基类定义了某些操作,但并没有去实现它。只有派生类才能实现这些操作。在这种情况下,基类有时也被称为抽象规范类。规范化继承

第十八页,共八十一页,编辑于2023年,星期五6/25/202319在Java中,关键字abstract确保了必须要构建派生类。声明为abstract的类必须被派生类化,不可能用new运算符创建这种类的实例。除此之外,方法也能被声明为abstract,同样在创建实例之前,必须覆盖类中所有的抽象方法。规范化继承可以通过以下方式辨认:基类中只是提供了方法界面,并没有实现具体的行为,具体的行为必须在派生类中实现。GraphicalObject没有实现关于描绘对象的方法,因此它是一个抽象类。其子类Ball,Wall和Hole通过规范子类化实现这些方法。规范化继承

第十九页,共八十一页,编辑于2023年,星期五6/25/202320一个类可以从其基类中继承几乎所有需要的功能,只是改变一些用作类接口的方法名,或是修改方法中的参数列表。即使新类和基类之间并不存在抽象概念上的相关性,这种实现也是可行的。构造继承

第二十页,共八十一页,编辑于2023年,星期五6/25/202321当继承的目的只是用于代码复用时,新创建的子类通常都不是子类型。这称为构造子类化。一般为了继承而继承,如利用一些工具类已有的方法。构造子类化第二十一页,共八十一页,编辑于2023年,星期五6/25/202322构造子类化经常违反替换原则(形成的子类并不是子类型)构造子类化第二十二页,共八十一页,编辑于2023年,星期五6/25/202323与特化子类化相反?派生类扩展基类的行为,形成一种更泛化的抽象。Window-ColoredWindow泛化子类化第二十三页,共八十一页,编辑于2023年,星期五6/25/202324泛化子类化通常用于基于数据值的整体设计,其次才是基于行为的设计。泛化子类化第二十四页,共八十一页,编辑于2023年,星期五6/25/202325如果派生类只是往基类中添加新行为,并不修改从基类继承来的任何属性,即是扩展继承。(泛化子类化对基类已存在的功能进行修改或扩展,扩展子类化则是增加新功能)由于基类的功能仍然可以使用,而且并没有被修改,因此扩展继承并不违反可替换性原则,用这种方式构建的派生类还是派生类型。扩展继承

第二十五页,共八十一页,编辑于2023年,星期五6/25/202326如果派生类的行为比基类的少或是更严格时,就是限制继承。常常出现于基类不应该、也不能被修改时。限制继承可描述成这么一种技术:它先接收那些继承来的方法,然后使它们无效。双向队列-〉堆栈限制继承

第二十六页,共八十一页,编辑于2023年,星期五6/25/202327由于限制继承违反了可替换性原则,用它创建的派生类已不是派生类型,因此应该尽可能不用。限制继承

第二十七页,共八十一页,编辑于2023年,星期五6/25/202328两个或多个类需要实现类似的功能,但他们的抽象概念之间似乎并不存在层次关系。控制鼠标=控制触摸屏但是,在概念上,任何一个类作为另一个类的子类都不合适因此,可以选择其中任何一个类作为父类,并改写与设备相关的代码变体子类化第二十八页,共八十一页,编辑于2023年,星期五6/25/202329但是,通常使用的更好的方法是将两个类的公共代码提炼成一个抽象类,比如PointingDevice,并且让这两个类都继承于这个抽象类。与泛化子类化一样,但基于已经存在的类创建新类时,就不能使用这种方法了。变体子类化第二十九页,共八十一页,编辑于2023年,星期五6/25/202330可以通过合并两个或者更多的抽象特性来形成新的抽象。一个类可以继承自多个基类的能力被称为多重继承。助教合并继承

第三十页,共八十一页,编辑于2023年,星期五6/25/202331创建匿名类的条件只能创建一个匿名类的实例匿名类必须继承于父类或接口,并且不需要构造函数进行初始化。p.add(newButtonAdapter(“Quit”){ publicvoidpressed(){System.exit(0);} });Java语言中的匿名类

第三十一页,共八十一页,编辑于2023年,星期五6/25/202332继承使得构造函数这个过程变得复杂由于父类和子类都有待执行的初始化代码,在创建新对象时都要执行Java等语言只要父类构造函数不需要参数,父类的构造函数和子类的构造函数都会自动地执行。当父类需要参数时,子类必须显示地提供参数。在java中通过super这个关键字来实现。继承和构造函数

第三十二页,共八十一页,编辑于2023年,星期五6/25/202333子类关系是通过创建新类的声明语句来建立的,但它并未解释子类存在的意义和目的。第10章子类和子类型第三十三页,共八十一页,编辑于2023年,星期五6/25/202334可替换性是面向对象编程中一种强大的软件开发技术。可替换性的意思是:变量声明时指定的类型不必与它所容纳的值类型相一致。这在传统的编程语言中是不允许的,但在面向对象的编程语言中却常常出现。可替换性第三十四页,共八十一页,编辑于2023年,星期五6/25/202335如果说新类是已存在类的子类型,那么这个新类不仅要提供已存在类的所有操作,而且还要满足于这个已存在类相关的所有属性。因此,即使符合堆栈的接口定义,但是不满足堆栈的属性特征,也不是子类型子类与子类型间的差异第三十五页,共八十一页,编辑于2023年,星期五6/25/202336子类型关系是通过行为这个术语描述的,与新类的定义或构造无关。例如:Dictionary类支持与Array类相同的接口,因此即使Dictionary类与Array类之间并不存在继承关系,但是也可以说Dictionary是Array的子类型。第三十六页,共八十一页,编辑于2023年,星期五6/25/202337面向对象语言的强大之处在于对象可以在运行时动态地改变其行为。编程语言中,术语静态总是用来表示在编译时绑定于对象并且不允许以后对其进行修改的属性或特征。术语动态用来表示直到运行时绑定于对象的属性或特征。第11章静态行为和动态行为第三十七页,共八十一页,编辑于2023年,星期五6/25/202338变量的静态类是指用于声明变量的类。静态类在编译时就确定下来,并且再也不会改变。变量的动态类指与变量所表示的当前数值相关的类。动态类在程序的执行过程中,当对变量赋新值时可以改变。静态类和动态类第三十八页,共八十一页,编辑于2023年,星期五6/25/202339对于静态类型面向对象编程语言,在编译时消息传递表达式的合法性不是基于接收器的当前动态数值,而是基于接收器的静态类来决定的。P167例子静态类型和动态类型的区别第三十九页,共八十一页,编辑于2023年,星期五6/25/202340替换原则可以通过提升数值在继承层次上的位置来体现。有时则相反,还需要判断一种变量目前所包含的数值是否为类层次中的低层次类。运行时类型决定第四十页,共八十一页,编辑于2023年,星期五6/25/202341做出数值是否属于指定类的决定之后,通常下一步就是将这一数值的类型由父类转换为子类。这一过程称为向下造型,或者反多态,因为这一操作所产生的效果恰好与多态赋值的效果相反。向下造型(反多态)第四十一页,共八十一页,编辑于2023年,星期五6/25/202342静态方法绑定/动态方法绑定响应消息时对哪个方法进行绑定是由接收器当前所包含的动态数值来决定的。方法绑定第四十二页,共八十一页,编辑于2023年,星期五6/25/202343Animalpet;pet=newDog();pet.speak();pet=newbird();pet.speak();例???第四十三页,共八十一页,编辑于2023年,星期五6/25/202344如果方法所执行的消息绑定是由最近赋值给变量的数值的类型来决定的,那么就称这个变量是多态的。Java,Smalltalk等变量都是多态的。C++声明为简单类型的变量,非多态。多态变量?第四十四页,共八十一页,编辑于2023年,星期五6/25/202345使用指针或引用;相关方法声明为virtual;才可以实现多态消息传递。C++第四十五页,共八十一页,编辑于2023年,星期五6/25/202346继承和替换原则的引入对编程语言的影响类型系统赋值的含义等价测试复制建立存储分配第12章替换的本质第四十六页,共八十一页,编辑于2023年,星期五6/25/202347从特定的类实例化对象是需要多少存储空间?内存布局第四十七页,共八十一页,编辑于2023年,星期五6/25/202348引入派生类包含基类所不包含的数据?内存布局第四十八页,共八十一页,编辑于2023年,星期五6/25/202349classWindow{public: virtualvoidoops();private: intheight; intwidth;};classTextWindow:publicWindow{public: virtualvoidoops();private:

char*contents; intcursorLocation;};例第四十九页,共八十一页,编辑于2023年,星期五6/25/202350Windowwin;为win分配空间的方案?例第五十页,共八十一页,编辑于2023年,星期五6/25/202351只分配基类所需的存储空间。无论基类还是派生类,都分配可用于所有合法的数值的最大的存储空间。只分配用于保存一个指针所需的存储空间。在运行时通过对来分配书之所需的存储空间,同时将指针设为相应的合适值。分配方案第五十一页,共八十一页,编辑于2023年,星期五6/25/202352C++使用最小静态空间分配策略。运行高效?Windowx;TextWindowy;x=y;???最小静态空间分配第五十二页,共八十一页,编辑于2023年,星期五6/25/202353

后果?切割Slicing第五十三页,共八十一页,编辑于2023年,星期五6/25/202354C++保证变量x只能调用定义于Window类中的方法,不能调用定义于TextWindow类中的方法。定义并实现于Window类中的方法无法存取或修改定义于子类中的数据,因此不可能出现父类存取子类的情况。最小静态空间分配第五十四页,共八十一页,编辑于2023年,星期五6/25/202355对于指针(引用)变量:当消息调用可能被改写的成员函数时,选择哪个成员函数取决于接收器的动态数值。对于其他变量:关于调用虚拟成员函数的绑定方式取决于静态类(变量声明时的类),而不取决于动态类(变量所包含的实际数值的类)。C++规则第五十五页,共八十一页,编辑于2023年,星期五6/25/202356变量赋值过程中,数值从子类表示的类型转换为父类所表示的类型。类似于将整型变量赋值给浮点型变量。对于基于堆栈的变量,可以确保动态类总是等同于静态类。所以不能对对象中不存在的字段进行存取。不必考虑赋值过程中的内存数据丢失。解释第五十六页,共八十一页,编辑于2023年,星期五6/25/202357分配变量值可能使用的最大存储空间。最大静态空间分配第五十七页,共八十一页,编辑于2023年,星期五6/25/202358对象大小?对整个程序扫描?问题第五十八页,共八十一页,编辑于2023年,星期五6/25/202359堆栈中不保存对象值。堆栈通过指针大小空间来保存标识变量,数据值保存在堆中。指针变量都具有恒定不变的大小,变量赋值时,不会有任何问题。Smalltalk、Java都采用该方法。动态内存分配第五十九页,共八十一页,编辑于2023年,星期五6/25/202360classBox{ publicintvalue;}Boxx=newBox();x.value=7;Boxy=x;y.value=12;//whatisx.value?观察第六十页,共八十一页,编辑于2023年,星期五6/25/202361内存分配方法影响赋值的含义:复制语义:变量值独立指针语义:同一(Java)赋值第六十一页,共八十一页,编辑于2023年,星期五6/25/202362浅复制(shallowcopy):共享实例变量。深复制(deepcopy):建立实例变量的新的副本。复制和克隆第六十二页,共八十一页,编辑于2023年,星期五6/25/202363C++:拷贝构造函数Java:改写clone方法深复制第六十三页,共八十一页,编辑于2023年,星期五6/25/202364什么是"clone"?

在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。第六十四页,共八十一页,编辑于2023年,星期五6/25/202365Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDKAPI的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。第六十五页,共八十一页,编辑于2023年,星期五6/25/202366什么是影子clone?

下面的例子包含三个类UnCloneA,CloneB,CloneMain。CloneB类包含了一个UnCloneA的实例和一个int类型变量,并且重载clone()方法。CloneMain类初始化UnCloneA类的一个实例b1,然后调用clone()方法生成了一个b1的拷贝b2。最后考察一下b1和b2的输出:第六十六页,共八十一页,编辑于2023年,星期五6/25/202367packageclone;

classUnCloneA{

privateinti;

publicUnCloneA(intii){i=ii;}

publicvoiddoublevalue(){i*=2;}

publicStringtoString(){

returnInteger.toString(i);

}

}

classCloneBimplementsCloneable{

publicintaInt;

publicUnCloneAunCA=newUnCloneA(111);

publicObjectclone(){

CloneBo=null;

try{

o=(CloneB)super.clone();

}catch(CloneNotSupportedExceptione){

e.printStackTrace();

}

returno;

}

}

publicclassCloneMain{

publicstaticvoidmain(String[]a){

CloneBb1=newCloneB();

b1.aInt=11;

System.out.println("beforeclone,b1.aInt="+b1.aInt);

System.out.println("beforeclone,b1.unCA="+b1.unCA);

CloneBb2=(CloneB)b1.clone();

b2.aInt=22;

b2.unCA.doublevalue();

System.out.println("=================================");

System.out.println("afterclone,b1.aInt="+b1.aInt);

System.out.println("afterclone,b1.unCA="+b1.unCA);

System.out.println("=================================");

System.out.println("afterclone,b2.aInt="+b2.aInt);

System.out.println("afterclone,b2.unCA="+b2.unCA);

}

}

/**RUNRESULT:

beforeclone,b1.aInt=11

beforeclone,b1.unCA=111

=================================

afterclone,b1.aInt=11

afterclone,b1.unCA=222

=================================

afterclone,b2.aInt=22

afterclone,b2.unCA=222

*/第六十七页,共八十一页,编辑于2023年,星期五6/25/202368输出的结果说明int类型的变量aInt和UnCloneA的实例对象unCA的clone结果不一致,int类型是真正的被clone了,因为改变了b2中的aInt变量,对b1的aInt没有产生影响,也就是说,b2.aInt与b1.aInt已经占据了不同的内存空间,b2.aInt是b1.aInt的一个真正拷贝。相反,对b2.unCA的改变同时改变了b1.unCA,很明显,b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用!从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。

大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为“影子clone”。要想让b2.unCA指向与b2.unCA不同的对象,而且b2.unCA中还要包含b1.unCA中的信息作为初始信息,就要实现深度clone。

默认的克隆方法为浅克隆,只克隆对象的非引用类型成员

第六十八页,共八十一页,编辑于2023年,星期五6/25/202369怎么进行深度clone?

把上面的例子改成深度clone很简单,需要两个改变:一是让UnCloneA类也实现和CloneB类一样的clone功能(实现Cloneable接口,重载clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA=(UnCloneA)unCA.clone();

第六十九页,共八十一页,编辑于2023年,星期五Java的深拷贝和浅拷贝Java中常用的拷贝操作有三个,operator=、拷贝构造函数和clone()方法6/25/202370第七十页,共八十一页,编辑于2023年,星期五预定义非集合类型的拷贝过程intx=1;inty=x;y=2;6/25/202371Integera=1;Integerb=a;b=2;Stringm="ok";Stringn=m;n="no";第七十一页,共八十一页,编辑于2023年,星期五6/25/202372Integera=1;Integerb=newInteger(a);b=2;Stringm="ok";Stringn=newString(m);n="no";第七十二页,共八十一页,编辑于2023年,星期五自定义类型的拷贝过程6/25/202373publicclassPersonimplementsCloneable{

privateintage;

privateStringname;

publicintgetAge(){

returnage;

}

publicvoidsetAge(intage){

this.age=age;

}

publicStringgetName(){

returnname;

}

voidsetName(Stringname){

=name;

}

}

测试代码:

Personp=newPerson();

p.setAge(32);

p.setName("陈抒");

Personp2=p;

p.setAge(33);

p.setName("老陈");第七十三页,共八十一页,编辑于2023年,星期五6/25/202374publicclassCompany{

publicCompany(){

}

publicCompany(Companyc){

name=;

person=newPerson(c.person);

}

privateStringname;

privatePersonperson;

publicPersongetPerson(){

returnperson;

}

publicvoidsetPerson(Personperson){

this.person=person;

}

publicStringgetName(){

returnname;

}

publicvoidsetName(Stringname){

=name;

}

@Override

publicObjectclone()throwsCloneNotSupportedException{

Companyc=newCompany();

c.setName(name);

c.setPerson((P

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论