Presentation is loading. Please wait.

Presentation is loading. Please wait.

第6章 继承.

Similar presentations


Presentation on theme: "第6章 继承."— Presentation transcript:

1 第6章 继承

2 主要内容与学习目标 主要内容: 学习目标: (1)指定派生类与直接基类、private和protected修饰符的含义。
(2)定义派生类的构造函数。 (3)显式调用直接基类实例构造函数。 (4)派生类对象和基类对象的关系、基类变量和派生类变量的关系。 (5)隐藏继承成员、隐藏元素的继承性、使用MyBase访问基类成员。 (6)多级继承、声明可重写方法、重写方法。 (7)基于继承的多态性。 (8)多级继承中构造函数的执行过程、重载/重写和隐藏的比较。 (9)垃圾回收和Finalize方法。 (10)重写Finalize方法。 (11)声明派生类的析构函数。 (12)显式释放资源与实现Dispose方法。 (13)何时使用继承。 学习目标: (1)理解继承和软件重用性。 (2)理解基类与派生类的概念。 (3)掌握访问修饰符public、private和protected的使用。 (4)使用base引用访问基类成员。 (5)如何在基类与派生类中使用构造函数。 (6)理解虚拟方法、重写方法。 (7)理解隐藏继承成员。 (8)了解垃圾回收和Finalize方法。

3 6.1 直接基类与派生类 任务6.1:定义基类Person
6.1 直接基类与派生类 任务6.1:定义基类Person 问题描述:声明Person类,它有3个属性(身份证号、姓名、性别),一个方法(该方法返回所有属性的值),3个私有字段,一个无参数的实例构造函数和一个有3个参数的实例构造函数。 任务6.2:定义派生类Student(一) 问题描述:声明学生类。 分析:我们要声明学生类(即Student类),由于学生属于人(Person),因此学生具有Person的属性和功能,我们已经有了实现Person的功能和属性的类,要定义学生类,只须以Person类为直接基类定义一派生类,对实现Person的功能和属性的代码不需要重写,直接继承,对于复杂程序, 重复使用已定义好的类,可大大节约程序的开发时间,缩短程序的开发周期。关于如何定义派生类请参考以下程序及说明。 解决方案: (1)打开前面创建的项目InheritsTest,向项目中添加类Student (2)向项目中添加测试类TestInherits (3)添加引用System.Windows.Forms。并将输出类型设置为【Windows应用程序】 (4)按Ctrl+F5键编译并运行应用程序,输出结果如图6.2所示

4 图6.2 继承基类

5 6.2 派生类实例构造函数声明 6.2.1 运算符重载 6.2.2 显式调用直接基类实例构造函数
6.2 派生类实例构造函数声明 如果类不包含任何实例构造函数声明,则自动提供一个默认实例构造函数。默认构造函数只是调用直接基类的无参数构造函数。如果直接基类没有可访问的无参数实例构造函数,则发生编译时错误。 如上节程序中,派生类Student不包含任何实例构造函数声明,则自动提供一个默认的无参数实例构造函数调用直接基类的无参数构造函数。如果直接基类有含有参数的实例构造函数声明,则直接基类必须声明无参数实例构造函数,否则发生编译时错误。具体地说,在Person类中,由于含有带参数的实例构造函数声明: public Person(string myid, string myname, string mysex) { ssn = myid; name = name; sex = mysex; } 因此,必须再声明一个无参数构造函数: public Person(){} 否则发生编译时错误。 构造函数是不能继承的,如果派生类需要构造函数,必须声明它。 任务6.3:复数加法 运算符重载 显式调用直接基类实例构造函数

6 运算符重载 运算符重载可以允许用户定义现有的运算符的新功能, 这样,运算符的一个或两个操作数就可以采用用户定义的类。运算符重载是调用方法的一种方式,要想为类重新定义运算符的功能可采用关键字operator,其一般形式为: public static result-type operator op (object1, object2) { 语句块; } 任务6.4:复数减法 问题描述:声明ComplexNumber类的派生类DComplexNumber,在派生类DComplexNumber中定义一含有参数的实例构造函数。并声明一名称为Subtract的方法,该方法能实现复数减法运算(左操作数的实部减右操作数的实部,左操作数的虚部减右操作数的虚部)。 解决方案: (1)创建DComplexNumber的类。 ①打开前面创建的项目ComplexNumberClass,添加名称为DComplexNumber的类: ②测试以上类。向ComplexNumberClass项目中添加名称为Form1的Windows窗体。 按表6.1向窗体添加控件并设置其属性(参见图6.2)。 (2)在窗体上双击鼠标右键,选择查看代码,打开代码编辑器。 (3)在窗体类Form1中声明如下字段: (4)双击【设置第一个复数】按钮,插入下列代码: (5)双击【设置第二个复数】按钮,插入下列代码: (6)双击“+”按钮,插入下列代码: (7)双击“-”按钮,插入下列代码: (8)按Ctrl+F5键编译并运行应用程序,输出结果如图6.3所示。

7 表6.1 属性表

8 图6.3 复数加减法

9 显式调用直接基类实例构造函数 派生类中至少有一个实例构造函数调用直接基类的实例构造函数。在没有显式指定调用直接基类的实例构造函数时,派生类实例构造函数隐式调用直接基类无参的实例构造函数。如果直接基类没有可访问的无参数实例构造函数,则发生编译时错误。 要在派生类中显式调用直接基类实例构造函数,可通过base关键字指定调用的直接基类构造函数。 在复数减法实例中,DComplexNumber类没有显式调用ComplexNumber的实例构造函数,因此隐式调用ComplexNumber类无参的实例构造函数,如果ComplexNumber类没有可访问的无参数实例构造函数,则发生编译时错误。下面我们修改复数减法实例,在DComplexNumber类中显式地调用ComplexNumber类的实例构造函数。 任务6.5:调用基类实例构造函数

10 6.3 隐藏从基类继承的成员 可使用new修饰符显式地隐藏从基类继承的成员。若要隐藏继承的成员,请使用相同名称在派生类中声明该成员,并用new修饰符修饰它。若派生类中的成员和基类的成员使用相同的名称、相同的参数和类型等,但未使用new修饰符修饰它,程序编译和运行不会发生错误,但会发出警告。 任务6.6:隐藏继承字段 问题描述:创建一示例程序,在该示例中,基类MyBaseC和派生类MyDerivedC使用相同的字段名 x,从而隐藏了继承字段的值。该示例还说明new修饰符的使用。同时也说明如何使用完全限定名访问基类的隐藏成员。 解决方案: (1)创建一个空项目NewClass,向项目中添加代码文件 (2)添加引用System.Windows.Forms。将输出类型设置为【Windows输出类型】 (3)按Ctrl+F5键编译并运行应用程序,输出结果如图6.4所示

11 图6.4 隐藏继承字段

12 6.4 含直接基类构造函数的构造函数声明 派生类继承直接基类中除实例构造函数、静态构造函数外的所有成员,当派生类的字段、属性除了基类的字段、属性外还包括其他字段、属性时,派生类的实例构造函数可以通过指定的参数值调用基类的实例构造函数,初始化基类的字段,然后使用其他参数指定的值初始化派生类特有的字段。 任务6.7:定义派生类Student(二) 问题描述:在任务6.2中,我们声明了一个Student类,Student类除了继承的Person类的属性外,还增加了两个字段studentid、department及属性,因此在声明Student类的实例构造函数时可直接调用基类的实例构造函数,并另外指定私有字段studentid和department的值。 下面我们修改Student类声明,增加实例构造函数声明,并将Print1方法修改为Print方法。 解决方案: (1)打开任务6.2创建的项目InheritsTest。将项目中的Student类修改 (修改处见带底纹部分): (2)按Ctrl+F5键编译并运行应用程序,输出结果如图6.6所示。

13 图6.6 派生类的实例构造函数 调用基类的实例构造函数
图6.6 派生类的实例构造函数 调用基类的实例构造函数

14 6.5 虚拟方法与重写方法 任务6.8:多级继承层次结构 6.5.1 继承中构造函数的执行过程 6.5.2 重载、重写和隐藏的比较
6.5 虚拟方法与重写方法 任务6.8:多级继承层次结构 问题描述:声明Point类,它包含Name、X和Y三个属性。不同的形状类,如Rectangle类继承Point,Ellipse类继承Rectangle类,Circle类继承Ellipse,Cylinder类继承Circle类。每个派生类都有各自的Name属性、Area方法(计算图形面积)重写实现。创建每个类的实例,以类的实例作为参数,调用参数的类型为Point或Point数组的ShowShapinfo方法,通过调用重写的方法和属性为相应的图形对象计算表面积、体积并输出图形的名称。 分析:在声明Point类时,由于Point类的派生类必须重写Name属性,因此Point类的Name属性要声明为virtual属性。Rectangle类继承Point,Rectangle类继承了Point的所有的字段、属性,它有自己特有的方法Area(计算矩形面积),但是,我们不希望Rectangle类使用Point类的Name属性,因为一个矩形的名称与点的名称是不同的,Rectangle类应自己来实现其功能,因此,Rectangle类的Name属性必须使用override修饰符,表示它是重写基类的Name属性。由于Rectangle类的派生类必须重写Area方法,因此Rectangle类的Area方法要声明为virtual方法。Ellipse类继承Rectangle类,我们在Ellipse类中定义一个有4个参数的构造函数,这4个参数分别表示椭圆中心的x坐标、y坐标和横轴、纵轴的长度。因此当用“base(...)”调用基类构造函数时,“base(...)”的参数必须访问实例构造函数的参数,以正确地初始化字段的值,也就是说“base(...)”第一个参数的值是椭圆的外接矩形左上角的X坐标,第二个参数的值是椭圆的外接矩形左上角的Y坐标。椭圆中心的x坐标、y坐标与椭圆的外接矩形左上角的X、Y坐标是有关系的,其关系为: X = x - width/2 Y = y - height/2 其中:width、height是椭圆的外接矩形的宽度和高度。 Ellipse类必须重写Area方法和Name属性。Circle类继承Ellipse类,我们在Circle类中定义一有3个参数的构造函数,这3个参数分别表示圆心的X坐标、Y坐标和圆的半径。因此当调用基类构造函数时,必须用实例构造函数初始值设定项访问实例构造函数的参数。Cylinder类从Circle类继承而来,Cylinder类从Circle类继承了方法Area及属性Name。与圆形相比,圆柱体的面积计算方法有所不同,名称也不同,因此Cylinder类必须重写这些方法和属性。图6.7的类图描述了各形状类的继承关系。 继承中构造函数的执行过程 重载、重写和隐藏的比较 垃圾回收和析构函数

15 图6.7 Point类的继承层次

16 继承中构造函数的执行过程 每当创建类的实例时,就会调用构造函数。构造函数用于在对象中的任何其他代码执行之前初始化新对象。构造函数可用于打开文件、连接到数据库、初始化变量以及处理任何需要在可使用对象前完成的其他任务。 当创建派生类的实例时,基类的构造函数首先执行,然后执行派生类中的构造函数。这是因为构造函数中方法头后使用语法base(...)。如果没有显示调用基类构造函数,系统隐式调用基类无参的构造函数base()。然后调用该类层次结构中每个类的构造函数,直到到达基类的构造函数。此时,基类构造函数中的代码执行,接着执行所有派生类中每个构造函数的代码,最后执行最相近派生的类中的代码。

17 重载、重写和隐藏的比较 重载、重写和隐藏是很容易混淆的类似概念。虽然所有这三种技术都使我们得以创建同名的成员,但它们之间有一些重要的差异。 重载的成员用于提供属性或方法的不同版本,这些版本具有相同名称但是接受不同数量的参数或者接受不同数据类型的参数。 重写的属性和方法用于替换在派生类中不适合的继承的属性或方法。重写的成员必须接受同一数据类型和参数数量。派生类继承重写的成员。重写的属性和方法基类中要求有virtual修饰符;派生类中要求有override修饰符。不能扩展被重写的元素的可访问性(例如,无法用public重写protected)。不能更改被重写的属性的可读性或可写性。不能用属性重写方法,或是用方法重写属性。不能用一个void方法重写一个有返回值的方法,反之亦然。重写的元素类型(无返回值方法、有返回值方法或属性)、名称、参数列表和返回类型必须相同。重写的用途主要是为了实现多态性。 隐藏的成员用于局部替换具有更广范围的成员。任何类型都可隐藏任何其他类型。例如,一个int变量可以隐藏一个方法。如果用另外一个方法隐藏某一方法,可以使用一个不同的参数列表以及一个不同的返回类型。若隐藏元素在后来的派生类中不可访问,则没有继承隐藏。例如,如果声明隐藏元素为private,则派生类的继承类就会继承原始元素,而不是隐藏元素。隐藏的用途主要是为了防止后面的基类修改引入已在派生类中声明的成员。它不能实现多态性,例如,如果Ellipse类的Area方法隐藏基类Rectangle类的Area方法,当把Ellipse类的对象传递给需要Rectangle对象的ShowArea方法时,则执行Rectangle对象的Area方法,而不会执行Ellipse对象的Area方法。 修改“实例:多级继承层次结构”,将Ellipse、Circle、Cylinder类中的Area方法的overrides修饰符改为new修饰符,则程序运行结果如图6.9所示。

18 图6.9 隐藏不能实现多态性

19 垃圾回收和析构函数 分配并且确保进行对应的释放会非常费时,而且可能会很难。.NET Framework使用名为“引用跟踪垃圾回收”的系统,该系统定期释放未使用的资源。当系统确定对象不再需要时,公共语言运行库(CLR)便定期销毁这些对象。当系统资源短缺时,对象释放会快一些,否则就不那么频繁。至于何时销毁不再需要的对象,进行垃圾回收释放未使用的资源,是不确定的。在此类情况下,称对象具有“非确定性生存期”。在大多数情况下,非确定性生存期并不会对如何编写应用程序产生影响,只要记住销毁对象的动作可能不会在对象丧失范围时立即执行即可。 垃圾回收有一个缺点:在你创建封装非托管资源的对象时,当你在应用程序中使用完这些非托管资源之后,你必须显式地释放它们。最常见的一类非托管资源就是包装操作系统资源的对象,例如文件、窗口或网络连接。虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但它不了解具体如何清理这些资源。对于这些类型的对象,C#提供析构函数,它允许对象在垃圾回收器回收该对象使用的内存时适当清理其非托管资源。 1. 析构函数 任务6.9:保存状态信息 2. 声明派生类的析构函数 任务6.10:保存雇员状态信息


Download ppt "第6章 继承."

Similar presentations


Ads by Google