C高级编程 第4章继

发布 2021-04-23 05:41:28 阅读 1327

第3章介绍了如何使用c#中的各个类,其重点是如何定义方法、构造函数、属性和单个类(或单个结构)中的其他成员。我们指出,所有的类最终都派生于类,但并没有说明如何创建继承类的层次结构。继承是本章的主题。

我们将讨论c#和。net framework如何处理继承。本章的主要内容如下:

继承的类型。

实现继承。访问修饰符。

接口。首先介绍c#在继承方面支持和不支持的功能。

在面向对象的编程中,有两种截然不同的继承类型:实现继承和接口继承。

实现继承:表示一个类型派生于一个基类型,拥有该基类型的所有成员字段和函数。在实现继承中,派生类型的每个函数采用基类型的实现**,除非在派生类型的定义中指定重写该函数的实现**。

在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公共功能时,这种类型的继承是非常有效的。例如第31章讨论的windows forms类。第31章也讨论了基类该类提供了常用windows控件的非常复杂的实现**,第31章还讨论了许多其他的类,例如system.

和这两个类派生于control,并重写了函数,或提供了新的函数,以实现特定类型的控件。

接口继承:表示一个类型只继承了函数的签名,没有继承任何实现**。在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。

例如,某些类型可以指定从接口详见第12章)中派生,从而提供一种清理资源的方法dispose()。由于某种类型清理资源的方式可能与另一种类型的完全不同,所以定义通用的实现**是没有意义的,此时就适合使用接口继承。接口继承常常被看做提供了一种契约:

让类型派生于接口,来保证为客户提供某个功能。

在传统上,像c++这样的语言在实现继承方面的功能非常强大。实际上,实现继承是c++编程模型的核心。另一方面,vb6不支持类的任何实现继承,但因其底层的com基础体系,所以它支持接口继承。

在c#中,既有实现继承,也有接口继承。它们没有强弱之分,因为这两种继承都完全内置于语言中,因此很容易为不同的情形选择最好的体系结构。

一些语言如c++支持所谓的"多重继承",即一个类派生于多个类。使用多重继承的优点是有争议的:一方面,毫无疑问,可以使用多重继承编写非常复杂、但很紧凑的**,如c++ atl库。

另一方面,使用多重实现继承的**常常很难理解和调试(这也可以从c++ atl库中看出)。如前所述,使健壮**的编写容易一些,是开发c#的重要设计目标。因此,c#不支持多重实现继承。

而c#又允许类型派生于多个接口。这说明,c#类可以派生于另一个类和任意多个接口。更准确地说,因为是一个公共的基类,所以每个c#类(除了object类之外)都有一个基类,还可以有任意多个基接口。

第3章区分了结构(值类型)和类(引用类型)。使用结构的一个限制是结构不支持继承,但每个结构都自动派生于实际上还应更仔细一些:不能建立结构的类型层次,但结构可以实现接口。

换言之,结构并不支持实现继承,但支持接口继承。事实上,定义结构和类可以总结为:

结构总是派生于它们还可以派生于任意多个接口。

类总是派生于用户选择的另一个类,它们还可以派生于任意多个接口。

如果要声明一个类派生于另一个类,可以使用下面的语法:

注意:这个语法非常类似于c++和j**a中的语法,但是,c++程序员习惯于使用公共和私有继承的概念,要注意c#不支持私有继承,因此基类名上没有public或private限定符。支持私有继承会大大增加语言的复杂性,实际上私有继承在c++中也很少使用。

如果类(或结构)也派生于接口,则用逗号分隔开基类和接口:

对于结构,语法如下:

如果在类定义中没有指定基类,c#编译器就假定是基类。因此下面的两段**生成相同的结果:

和。第二种形式比较常用,因为它较简单。

c#支持object关键字,它用作类的假名,所以也可以编写下面的**:

如果要引用object类,可以使用object关键字,智能编辑器(如visual studio)会识别它,因此便于编辑**。

把一个基类函数声明为virtual,该函数就可以在派生类中重写了:

也可以把属性声明为virtual。对于虚属性或重写属性,语法与非虚属性是相同的,但要在定义中加上关键字virtual,其语法如下所示:

为了简单起见,下面的讨论将主要集中于方法,但其规则也适用于属性。

c#中虚函数的概念与标准oop概念相同:可以在派生类中重写虚函数。在调用方法时,会调用对象类型的合适方法。

在c#中,函数在默认情况下不是虚拟的,但(除了构造函数以外)可以显式地声明为virtual。这遵循c++的方式,即从性能的角度来看,除非显式指定,否则函数就不是虚拟的。而在j**a中,所有的函数都是虚拟的。

但c#的语法与c++的语法不同,因为c#要求在派生类的函数重写另一个函数时,要使用override关键字显式声明:

方法重写的语法避免了c++中很容易发生的潜在运行错误:当派生类的方法签名无意中与基类版本略有差别时,派生类方法就不能重写基类方法了。在c#中,这会出现一个编译错误,因为编译器会认为函数已标记为override,但没有重写它的基类方法。

成员字段和静态函数都不能声明为virtual,因为这个概念只对类中的实例函数成员有意义。

如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有声明为virtual 和 override,派生类方法就会隐藏基类方法。

在大多数情况下,是要重写方法,而不是隐藏方法,因为隐藏方法会存在为给定类的实例调用错误方法的危险。但是,如下面的例子所示,c#语法可以确保开发人员在编译时收到这个潜在错误的警告,使隐藏方法更加安全。这也是类库开发人员得到的版本方面的好处。

假定有人编写了类hisbaseclass:

在将来的某一刻,要编写一个派生类,给hisbaseclass添加某个功能,特别是要添加一个目前基类中没有的方法mygroovymethod():

一年后,基类的编写者决定扩展基类的功能。为了保持一致,他也添加了一个名为mygroovymethod()的方法,该方法的名称和签名与前面添加的方法相同,但并不完成相同的工作。在使用基类的新方法编译**时,程序在应该调用哪个方法上就会有潜在的冲突。

这在c#中完全合法,但因为我们的mygroovymethod()与基类的mygroovymethod()不相关,运行这段**的结果就可能不是我们希望的结果。c#已经为此设计了一种方式,可以很好地处理这种情况。

首先,系统会发出警告。在c#中,应使用new关键字声明我们要隐藏一个方法,如下所示:

但是,我们的mygroovymethod()没有声明为new,所以编译器会认为它隐藏了基类的方法,但没有显式声明,因此发出一个警告(这也适用于把mygroovymethod()声明为 virtual)。如果愿意,可以给我们的方法重命名。这么做,是最好的情形,因为这会避免许多冲突。

但是,如果觉得重命名方法是不可能的(例如,已经为其他公司把软件发布为一个库,所以无法修改方法的名称),则所有的已有客户机**仍能正确运行,选择我们的mygroovymethod()。这是因为访问这个方法的已有**必须通过对myderivedclass(或进一步派生的类)的引用进行选择。

已有的**不能通过对hisbaseclass的引用访问这个方法,因为在对hisbaseclass的早期版本进行编译时,会产生一个编译错误。这个问题只会发生在将来编写的客户机**上。c#会发出一个警告,告诉用户在将来的**中可能会出问题--用户应注意这个警告,不要试图在将来的**中通过对hisbaseclass的引用调用mygroovymethod()方法,但所有已有的**仍会正常工作。

这是比较微妙的,但很好地说明了c#如何处理类的不同版本。

C高级编程 第48章Syndication

其中onclick属性告诉运行库,在生成窗体的 模型时,把按钮的单击事件包装到triggerbutton click方法中。修改triggerbutton click 中的 注意标签控件类型是从 中推断出来的,所以可以直接在后台 中使用 下面准备运行它。不需要建立项目,只需保存所有的内容,把web浏...

C高级编程 第5章数

如果需要使用同一类型的多个对象,就可以使用集合和数组。c 用特殊的记号声明和使用数组。array类在后台发挥作用,为数组中元素的排序和过滤提供了几个方法。使用枚举器,可以迭代数组中的所有元素。本章讨论如下内容 简单数组。多维数组。锯齿数组。array类。数组的接口。枚举。如果需要使用同一类型的多个对...

C高级编程 第3版

前言。对于开发人员来说,把c 语言及其相关环境。net framework描述为多年来最重要的新技术一点都不夸张。net提供了一种新环境。在这个环境中,可以开发出运行在windows上的几乎所有应用程序,而c 是专门用于。net的新编程语言。例如,使用c 可以编写出动态web页面 xml web服务...