项目7 面向对象高级
C# 程序设计 项目1 :认知.NET与 C# 项目2 :数据运算 项目3 :程序流程控制 项目4 :收集同一类型数据 项目5 :堆积木——方法 项目6 :面向对象基础 项目7 :面向对象高级 项目8:调试程序和异常处理 项目9:开发 Windows 窗体应用程序 项目10:开发图形应用程序 项目11:文件流操作 项目12: 数据库访问——ADO.NET 2
目标 本章学习目标: 理解并使用继承 理解并使用多态性 理解重写的概念 理解接口的概念
项目7 面向对象高级 继承性 多态性 接口 小结 实验 习题
继承性 继承是指派生类可以获得其基类(此基类本身也可以是派生类)特征的能力。继承允许把行为的公共集合(定义为属性和方法)包含在基类中,这些公共集合可以在基类的派生类中得到重用。利用继承性,可以以现有的通用类型为基础,创建出新的特殊类型。
继承示例 继承的层次结构示例 动物 基类 老鼠 猫 派生类
继承性 (续) 继承性: 优点:继承最主要的优点就是代码重用 继承的传递性:如果C 从B 中派生,B 又从A 中派生,那么C 不仅继承了B 中声明的成员,同样也继承了A 中的成员 继承的单一性:派生类只能继承单个基类 7
继承性 (续) 派生类 在派生类中调用基类构造函数 密封类 8
派生类 创建派生类的语法如下: [访问修饰符] class 派生类类名 : 基类类名 { //派生类成员定义 } 9
继承示例 继承而来 基类 派生类 class Animal { // 成员字段 protected int eyes, nose; public Animal() eyes = 2; nose = 1; } protected void Eat() // 定义 class Dog : Animal { // 成员字段、方法 public void Barking() // 定义 } public void Wagging_Tail() static void Main() //定义 Static void Main() { Dog d = new Dog() ; d.Eat(); } 继承而来
派生类(续) 访问修饰符“protected”可以将类成员的作用域限制在本类及其派生类中。 Protected 访问修饰符 11
在派生类中调用基类构造函数 执行顺序 因为派生类要使用基类,所以基类实例化必须在派生类实例化之前进行。 调用特定的构造函数 如果想要调用基类的非默认构造函数,那么必须使用 base 关键字。 12
构造函数示例 public class Animal { public Animal() Console.WriteLine("Constructing Animal"); } static void Main(string[] args) { Mouse m = new Mouse(); } public class Mouse : Animal { public Mouse() //先调用基类的构造函数 Console.WriteLine("Constructing Mouse"); } ...
base示例 public class Animal { public Animal(string color) Console.WriteLine(color + " Animal"); } static void Main(string[] args) { Mouse m = new Mouse("White"); } public class Mouse : Animal { public Mouse(string color) : base(color) //调用基类的构造函数 Console.WriteLine(color + " Mouse"); } ...
密封类 在 C# 中,有一种类称为密封类。当把某个类声明为密封类时,编译器将禁止所有类继承该类。 一般情况下,类是不“密封的”,因为一旦把某个类声明为密封类,这个类将永远不能再扩展了。但是,如果某个类具有特殊的实现,并且不允许其他任何类继承该类,那么就需要把此类声明为密封类。 15
隐藏成员 派生类如果定义了与继承而来的成员同名的新成员,就可以隐藏已继承的成员,但这并不因为这派生类删除了这些成员,只是不能再访问这些成员而已。这时,编译器不会报错,而是一个警告。通过在派生类同名成员前面使用new关键字,可以明确的告知编译器,我的确是故意覆盖父类的成员。 派生类如果声明了与基类同名的成员,但成员方法的参数不同,此时称派生类成员对基类成员的重载。
new示例 public class Animal { public void Eat() Console.WriteLine("Eating"); } public class Mouse : Animal { new public void Eat() //使用new关键字 Console.WriteLine("Eating cheese"); } static void Main(string[] args) { Mouse m = new Mouse(); m.Eat(); }
方法重载示例 public class Animal { public void Eat() Console.WriteLine("Eating"); } public class Mouse : Animal { public void Eat(string food) //方法参数列表不同,重载 Console.WriteLine("Eating " + food); } static void Main(string[] args) { Mouse m = new Mouse(); m.Eat(); //调用基类的Eat()方法 m.Eat("cheese"); //调用派生类的Eat()方法 }
项目7 面向对象高级 继承性 多态性 接口 小结 实验 习题 19
多态性——青蛙和蝌蚪 多态性是一个面向对象的概念,同一操作作用于不同的类的实例,不同的类将进行不同的解释,最后产生不同的执行结果。 20
多态性(续) 编写虚方法 抽象方法和抽象类 21
编写虚方法 virtual 关键字 override 关键字 调用虚方法 在派生类中调用基类成员 22
virtual 关键字 如果基类中的方法,需要在派生类中进行重写,那么必须在基类的方法声明的时候,将方法声明为虚方法,虚方法的定义要用到virtual关键字 ,如下所示: public class Animal { public virtual void Eat() { Console.WriteLine(″Eat something″); } 这样,Animal 类的任何派生类都可以实现新的 Eat() 方法。 如果基类中的方法,需要在派生类中进行重写,那么必须在基类的方法声明的时候,将方法声明为虚方法,在派生类中,明确的告知编译器进行重写。 23
override 关键字 如果在派生类中要重写基类方法,那么需要使用 override 关键字,如下所示: public class Cat : Animal { public override void Eat() { Console.WriteLine(″Eat Mouse″); } 24
调用虚方法 通过使用多态性,可以采用一种通用的方式来处理派生类,可以认为派生类对象的类型都是基类类型。 运行时多态的经典用法: Father f=new Son(); f.Work();//将调用Son的Work方法
派生类中调用基类成员示例 public class Animal { public virtual void Move() Console.WriteLine(“Move"); } static void Main(string[] args) { Fish f = new Fish(); f.Move(); } public class Fish : Animal { public override void Move() base.Move(); Console.WriteLine(“Swim”); }
抽象方法和抽象类 当创建一个类时,有时需要让该类包含一些特殊的方法,该类对这些方法不提供实现,但是该类的派生类必须实现这些方法,这些方法称为抽象方法。抽象方法是没有被实现的空方法。 包含抽象成员的类称为抽象类,但是,并不要求抽象类都包含抽象成员。包含抽象成员的类一定是抽象类。抽象类可以包含非抽象成员。 抽象类是用来作为基类的,抽象类不能直接实例化,而且也不能被密封。 27
抽象方法和抽象类 语法 优点 重写 具有虚方法的抽象类 抽象属性 28
语法 抽象方法和抽象类的声明使用abstract 修饰符: 下列代码创建了一个具有抽象Eat()方法的Animal抽象类: [访问修饰符] abstract class 类名 { //... } [访问修饰符] abstract 返回值类型 方法名 ([参数]) ; 下列代码创建了一个具有抽象Eat()方法的Animal抽象类: public abstract class Animal{ public abstract void Eat(); } 29
优点 抽象方法的优点在于基类无需对抽象方法提供实现,但是基类的派生类必须为基类中的抽象方法提供实现。 30
重写 当派生类从抽象类中继承一个抽象方法时,派生类必须重写该抽象方法。这个规则在编译时强制实施。 public class Mouse : Animal { public override void Eat () { Console.WriteLine(“Eat cheese”); } 31
具有虚方法的抽象类 public abstract class Animal { public virtual void Sleep() //虚方法 Console.WriteLine(“sleeping”); } public abstract void Eat(); } 如果有一个通用方法,该方法对所有派生类来说是公共的,并且强制要求所有派生类都必须实现这个方法,那么必须把该方法定义为基类中的抽象方法。 32
抽象属性 在声明抽象属性时,需要指定属性名和派生属性应该实现的访问函数。下面的代码创建了一个 Animal 类,并且声明了一个名为 IsHungry 的抽象属性: public abstract class Animal { public abstract bool IsHungry{get; set;} } 若要把该属性声明为只读属性或只写属性,可以移除相应的访问器。 33
项目7 面向对象高级应用 继承性 多态性 接口 小结 实验 习题 34
接口 什么是接口 接口的使用方法 如何使用实现了某接口的对象 如何继承多个接口 接口与抽象类的比较 35
定义 在某种程度上,接口像一个抽象类,可以定义接口的成员,但是,接口不提供接口成员的实现。 继承接口的类必须提供接口成员的实现。 成员包括:方法、属性、索引器、事件。 36
接口的使用方法 声明接口 实现接口 37
声明接口 声明接口要使用 interface 关键字,语法如下: [访问修饰符] interface <接口名> { public protected internal private 接口名称一般都会以大写的I开头,表示为接口类型。 声明接口要使用 interface 关键字,语法如下: [访问修饰符] interface <接口名> { //接口成员 } 接口的成员可以是 方法 属性 索引器 事件 interface IAnimal { bool IsHungry { get; set; } void Hunt(); } 定义了一个IAnimal接口,该接口具有一个Hunt()方法和一个IsHungry属性。 38
声明接口 接口的成员必须是方法、属性、事件或索引器。 接口不能包含常量、字段、运算符、构造函数、析构函数,也不能包含任何种类的静态成员。 所有接口成员都隐式地具有public访问属性。 接口成员声明中包含任何修饰符都属于编译时错误。
实现接口 要实现一个接口,必须要有类继承该接口。 接口的实现类可以是派生类,并且这些派生类可以包括一些自己特有的类成员。 40
示例代码 public class Lion : IAnimal static void Main(string [] args) { { private bool hungry; public bool IsHungry get { return hungry; } set { hungry = value; } } public void Hunt() Console.WriteLine(“Lion is hunting little animal"); static void Main(string [] args) { Lion l = new Lion(); l. IsHungry = true; l. Hunt(); }
继承多个接口 若要实现多接口继承,则需要列出这些接口,这些接口之间用逗号分开,如下所示: class Chimpanzee: Animal, ICarnivore, IHerbivore { … } 继承接口的类必须实现所有接口的所有成员。 42
接口与抽象类的比较 与抽象类不同的是,接口是一个完全抽象的成员集,这个成员集为相关操作定义了一组规则。接口的实现完全留给了开发者。 抽象类为管理组件版本提供了一种简单易行的方法。通过更新基类,所有派生类都将自动进行相应更新。而接口在创建后就不能再更改。如果需要修改接口,则必须创建新接口。 43
接口与抽象类的比较 当准备为组件提供多态性时,表 中的建议将有助于在抽象类和接口之间做出正确的选择。 目标 选择 创建多个组件版本 抽象类 设计小而简练的功能块 接口 设计大型功能单元 44
项目7 面向对象高级应用 继承性 多态性 接口 小结 实验 习题 45
小结 本章继续上一章面向对象的内容更进一步阐述了面向对象的重要概念—继承和多态性。 面向对象的继承原则允许创建一个基类,然后从这个基类派生出更多的派生类,其中派生类能够继承基类的属性和方法。 46
项目7 面向对象高级应用 继承性 多态性 接口 小结 实验 习题 47
实验 参照实验手册,并在教师指导下完成实验报告。 48
项目7 面向对象高级应用 继承性 多态性 接口 小结 实验 习题 49
习题 1. 填空题 (1) 将一个基类对象当做_________对象可能引发错误。 1. 填空题 (1) 将一个基类对象当做_________对象可能引发错误。 (2) 如果一个类包含一个或多个抽象方法,它是一个_________类。 (3) 使用关键字_________声明的类不能被继承。 (4) 使用关键字_________声明抽象类。 (5) 多态性包括使用基类的引用来操纵一个__________对象。
习题(续) 2. 判断正误,如果错误,请说明原因。 (1) 抽象类中所有的方法必须被声明为abstract。 2. 判断正误,如果错误,请说明原因。 (1) 抽象类中所有的方法必须被声明为abstract。 (2) 具有抽象方法的类必须声明为abstract。 (3) 多态性允许程序员使用基类的引用管理派生类。 (4) 声明为abstract类的方法必须同时进行声明和实现。 51
习题(续) 3. 程序改错 阅读下面的程序,找出其中错误并改正过来:using system class Triangle { 6.6 习题 3. 程序改错 阅读下面的程序,找出其中错误并改正过来:using system class Triangle { private int a; private int b; private int c; public Triangle(int va, int vb,int vc){ a=va; b=vb; c=vc; } 52
习题(续) public void Main(){ Triangle tr = new Triangle(2,2); 6.6 习题 public void Main(){ Triangle tr = new Triangle(2,2); Console.WriteLine(“{0},{1}”,tr.a,tr.b); return 0; } 53