无私分享(C#高级编程第6版doc)第04章 继承.doc
《无私分享(C#高级编程第6版doc)第04章 继承.doc》由会员分享,可在线阅读,更多相关《无私分享(C#高级编程第6版doc)第04章 继承.doc(18页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、目 录第4章 继 承24.1 继承的类型24.1.1 实现继承和接口继承24.1.2 多重继承24.1.3 结构和类24.2 实现继承34.2.1 虚方法44.2.2 隐藏方法44.2.3 调用函数的基类版本54.2.4 抽象类和抽象函数64.2.5 密封类和密封方法64.2.6 派生类的构造函数74.3 修饰符114.3.1 可见性修饰符114.3.2 其他修饰符124.4 接口124.4.1 定义和实现接口134.4.2 派生的接口164.5 小结18第4章 继 承第3章介绍了如何使用C#中的各个类,其重点是如何定义方法、构造函数、属性和单个类(或单个结构)中的其他成员。我们指出,所有的类
2、最终都派生于System.Object类,但并没有说明如何创建继承类的层次结构。继承是本章的主题。我们将讨论C#和.NET Framework如何处理继承。本章的主要内容如下:继承的类型实现继承访问修饰符接口4.1 继承的类型首先介绍C#在继承方面支持和不支持的功能。4.1.1 实现继承和接口继承在面向对象的编程中,有两种截然不同的继承类型:实现继承和接口继承。实现继承:表示一个类型派生于一个基类型,拥有该基类型的所有成员字段和函数。在实现继承中,派生类型的每个函数采用基类型的实现代码,除非在派生类型的定义中指定重写该函数的实现代码。在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公
3、共功能时,这种类型的继承是非常有效的。例如第31章讨论的Windows Forms类。第31章也讨论了基类System.Windows.Forms.Control,该类提供了常用Windows控件的非常复杂的实现代码,第31章还讨论了许多其他的类,例如System. Windows.Forms.TextBox和System.Windows.Forms.ListBox,这两个类派生于Control,并重写了函数,或提供了新的函数,以实现特定类型的控件。接口继承:表示一个类型只继承了函数的签名,没有继承任何实现代码。在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。例如,某些类型可以指
4、定从接口System.IDisposable(详见第12章)中派生,从而提供一种清理资源的方法Dispose()。由于某种类型清理资源的方式可能与另一种类型的完全不同,所以定义通用的实现代码是没有意义的,此时就适合使用接口继承。接口继承常常被看做提供了一种契约:让类型派生于接口,来保证为客户提供某个功能。在传统上,像C+这样的语言在实现继承方面的功能非常强大。实际上,实现继承是C+编程模型的核心。另一方面,VB6不支持类的任何实现继承,但因其底层的COM基础体系,所以它支持接口继承。在C#中,既有实现继承,也有接口继承。它们没有强弱之分,因为这两种继承都完全内置于语言中,因此很容易为不同的情形
5、选择最好的体系结构。4.1.2 多重继承一些语言如C+支持所谓的多重继承,即一个类派生于多个类。使用多重继承的优点是有争议的:一方面,毫无疑问,可以使用多重继承编写非常复杂、但很紧凑的代码,如C+ ATL库。另一方面,使用多重实现继承的代码常常很难理解和调试(这也可以从C+ ATL库中看出)。如前所述,使健壮代码的编写容易一些,是开发C#的重要设计目标。因此,C#不支持多重实现继承。而C#又允许类型派生于多个接口。这说明,C#类可以派生于另一个类和任意多个接口。更准确地说,因为System.Object是一个公共的基类,所以每个C#类(除了Object类之外)都有一个基类,还可以有任意多个基接
6、口。4.1.3 结构和类第3章区分了结构(值类型)和类(引用类型)。使用结构的一个限制是结构不支持继承,但每个结构都自动派生于System.ValueType。实际上还应更仔细一些:不能建立结构的类型层次,但结构可以实现接口。换言之,结构并不支持实现继承,但支持接口继承。事实上,定义结构和类可以总结为:结构总是派生于System.ValueType,它们还可以派生于任意多个接口。类总是派生于用户选择的另一个类,它们还可以派生于任意多个接口。4.2 实现继承如果要声明一个类派生于另一个类,可以使用下面的语法:class MyDerivedClass : MyBaseClass/ function
7、s and data members here注意:这个语法非常类似于C+和Java中的语法,但是,C+程序员习惯于使用公共和私有继承的概念,要注意C#不支持私有继承,因此基类名上没有public或private限定符。支持私有继承会大大增加语言的复杂性,实际上私有继承在C+中也很少使用。如果类(或结构)也派生于接口,则用逗号分隔开基类和接口:public class MyDerivedClass : MyBaseClass, IInterface1, IInterface2/etc.对于结构,语法如下:public struct MyDerivedStruct : IInterface1,
8、IInterface2/etc.如果在类定义中没有指定基类,C#编译器就假定System.Object是基类。因此下面的两段代码生成相同的结果:class MyClass : Object /derives from System.Object/etc. 和class MyClass /derives from System.Object/etc. 第二种形式比较常用,因为它较简单。C#支持object关键字,它用作System.Object类的假名,所以也可以编写下面的代码:class MyClass : object /derives from System.Object/etc. 如果要
9、引用Object类,可以使用object关键字,智能编辑器(如Visual Studio)会识别它,因此便于编辑代码。4.2.1 虚方法把一个基类函数声明为virtual,该函数就可以在派生类中重写了:class MyBaseClasspublic virtual string VirtualMethod()return This method is virtual and defined in MyBaseClass;也可以把属性声明为virtual。对于虚属性或重写属性,语法与非虚属性是相同的,但要在定义中加上关键字virtual,其语法如下所示:public virtual string
10、 ForeNameget return foreName; set foreName = value; private string foreName;为了简单起见,下面的讨论将主要集中于方法,但其规则也适用于属性。C#中虚函数的概念与标准OOP概念相同:可以在派生类中重写虚函数。在调用方法时,会调用对象类型的合适方法。在C#中,函数在默认情况下不是虚拟的,但(除了构造函数以外)可以显式地声明为virtual。这遵循C+的方式,即从性能的角度来看,除非显式指定,否则函数就不是虚拟的。而在Java中,所有的函数都是虚拟的。但C#的语法与C+的语法不同,因为C#要求在派生类的函数重写另一个函数时,
11、要使用override关键字显式声明:class MyDerivedClass : MyBaseClasspublic override string VirtualMethod()return This method is an override defined in MyDerivedClass;方法重写的语法避免了C+中很容易发生的潜在运行错误:当派生类的方法签名无意中与基类版本略有差别时,派生类方法就不能重写基类方法了。在C#中,这会出现一个编译错误,因为编译器会认为函数已标记为override,但没有重写它的基类方法。成员字段和静态函数都不能声明为virtual,因为这个概念只对类中
12、的实例函数成员有意义。4.2.2 隐藏方法如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有声明为virtual 和 override,派生类方法就会隐藏基类方法。在大多数情况下,是要重写方法,而不是隐藏方法,因为隐藏方法会存在为给定类的实例调用错误方法的危险。但是,如下面的例子所示,C#语法可以确保开发人员在编译时收到这个潜在错误的警告,使隐藏方法更加安全。这也是类库开发人员得到的版本方面的好处。假定有人编写了类HisBaseClass:class HisBaseClass/ various members在将来的某一刻,要编写一个派生类,给HisBaseClass添加某个功能,特
13、别是要添加一个目前基类中没有的方法MyGroovyMethod():class MyDerivedClass: HisBaseClasspublic int MyGroovyMethod()/ some groovy implementationreturn 0;一年后,基类的编写者决定扩展基类的功能。为了保持一致,他也添加了一个名为MyGroovyMethod()的方法,该方法的名称和签名与前面添加的方法相同,但并不完成相同的工作。在使用基类的新方法编译代码时,程序在应该调用哪个方法上就会有潜在的冲突。这在C#中完全合法,但因为我们的MyGroovyMethod()与基类的MyGroovyM
14、ethod()不相关,运行这段代码的结果就可能不是我们希望的结果。C#已经为此设计了一种方式,可以很好地处理这种情况。首先,系统会发出警告。在C#中,应使用new关键字声明我们要隐藏一个方法,如下所示:class MyDerivedClass : HisBaseClasspublic new int MyGroovyMethod()/ some groovy implementationreturn 0;但是,我们的MyGroovyMethod()没有声明为new,所以编译器会认为它隐藏了基类的方法,但没有显式声明,因此发出一个警告(这也适用于把MyGroovyMethod()声明为 virt
15、ual)。如果愿意,可以给我们的方法重命名。这么做,是最好的情形,因为这会避免许多冲突。但是,如果觉得重命名方法是不可能的(例如,已经为其他公司把软件发布为一个库,所以无法修改方法的名称),则所有的已有客户机代码仍能正确运行,选择我们的MyGroovyMethod()。这是因为访问这个方法的已有代码必须通过对MyDerivedClass(或进一步派生的类)的引用进行选择。已有的代码不能通过对HisBaseClass的引用访问这个方法,因为在对HisBaseClass的早期版本进行编译时,会产生一个编译错误。这个问题只会发生在将来编写的客户机代码上。C#会发出一个警告,告诉用户在将来的代码中可能
16、会出问题-用户应注意这个警告,不要试图在将来的代码中通过对HisBaseClass的引用调用MyGroovyMethod()方法,但所有已有的代码仍会正常工作。这是比较微妙的,但很好地说明了C#如何处理类的不同版本。4.2.3 调用函数的基类版本C#有一种特殊的语法用于从派生类中调用方法的基类版本:base.()。例如,假定派生类中的一个方法要返回基类的方法返回的值的90%,就可以使用下面的语法:class CustomerAccountpublic virtual decimal CalculatePrice()/ implementationreturn 0.0M; class GoldA
17、ccount : CustomerAccountpublic override decimal CalculatePrice()return base.CalculatePrice() * 0.9M; 这个语法类似于Java,但Java使用关键字super而不是base。C+没有类似的关键字,但需要显式指定类名(CustomerAccount:CalculatePrice()。C+中对应于base的内容都比较模糊,因此C+允许多重继承。注意,可以使用base.()语法调用基类中的任何方法,不必在同一个方法的重载中调用它。4.2.4 抽象类和抽象函数C#允许把类和函数声明为abstract,抽象
18、类不能实例化,而抽象函数没有执行代码,必须在非抽象的派生类中重写。显然,抽象函数也是虚拟的(但也不需要提供virtual关键字,实际上,如果提供了该关键字,就会产生一个语法错误)。如果类包含抽象函数,该类将也是抽象的,也必须声明为抽象的:abstract class Buildingpublic abstract decimal CalculateHeatingCost(); / abstract methodC+开发人员要注意C#中的一些语法区别。C#不能采用=0语法来声明抽象函数。在C#中,这个语法有误导作用,因为可以在类声明的成员字段上使用=,提供初始值:abstract class B
19、uildingprivate bool damaged = false; / fieldpublic abstract decimal CalculateHeatingCost(); / abstract method注意:C+开发人员还要注意术语上的细微差别:在C+中,抽象函数常常描述为纯虚函数,而在C#中,仅使用抽象这个术语。4.2.5 密封类和密封方法C#允许把类和方法声明为sealed。对于类来说,这表示不能继承该类;对于方法来说,这表示不能重写该方法。sealed class FinalClass/ etcclass DerivedClass : FinalClass / wrong
20、. Will give compilation error / etc注意:Java开发人员可以把C#中的sealed当作Java中的final。在把类或方法标记为sealed时,最可能的情形是:如果要对库、类或自己编写的其他类进行操作,则重写某些功能会导致错误。也可以因商业原因把类或方法标记为sealed,以防第三方以违反注册协议的方式扩展该类。但一般情况下,在把类或方法标记为sealed时要小心,因为这么做会严重限制它的使用。即使不希望它能继承一个类或重写类的某个成员,仍有可能在将来的某个时刻,有人会遇到我们没有预料到的情形。.NET基类库大量使用了密封类,使希望从这些类中派生出自己的类的
21、第三方开发人员无法访问这些类。例如string就是一个密封类。把方法声明为sealed也可以实现类似的目的,但很少这么做。class MyClasspublic sealed override void FinalMethod()/ etc.class DerivedClass : MyClasspublic override void FinalMethod() / wrong. Will give compilation error要在方法或属性上使用sealed关键字,必须先在基类上把它声明为重写。如果基类上不希望有重写的方法或属性,就不要把它声明为virtual。4.2.6 派生类的构
22、造函数第3章介绍了单个类的构造函数是如何工作的。这样,就产生了一个有趣的问题,在开始为层次结构中的类(这个类继承了其他类,也可能有定制的构造函数)定义自己的构造函数时,会发生什么情况? 假定没有为类定义任何显式的构造函数,这样编译器就会为所有的类提供默认的构造函数,在后台会进行许多操作,编译器可以很好地解决层次结构中的所有问题,每个类中的每个字段都会初始化为默认值。但在添加了一个我们自己的构造函数后,就要通过派生类的层次结构高效地控制构造过程,因此必须确保构造过程顺利进行,不要出现不能按照层次结构进行构造的问题。为什么派生类会有某些特殊的问题?原因是在创建派生类的实例时,实际上会有多个构造函数
23、起作用。要实例化的类的构造函数本身不能初始化类,还必须调用基类中的构造函数。这就是为什么要通过层次结构进行构造的原因。为了说明为什么必须调用基类的构造函数,下面是手机公司MortimerPhones开发的一个例子。这个例子包含一个抽象类GenericCustomer,它表示顾客。还有一个(非抽象)类Nevermore60Customer,它表示采用特定付费方式(称为Nevermore60付费方式)的顾客。所有的顾客都有一个名字,由一个私有字段表示。在Nevermore60付费方式中,顾客前几分钟的电话费比较高,需要一个字段highCostMinutesUsed,它详细说明了每个顾客该如何支付这
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 无私分享C#高级编程第6版doc第04章 继承 无私 分享 C# 高级 编程 doc 04
限制150内