第6章 继承和接口设计 6.1 继 承 6.2 多态性 6.3 抽象类 6.4 接口 6.5 接口在集合排序中的应用.

Slides:



Advertisements
Similar presentations
第3-2章 类与 对象 Java类的特性 教学内容: 类的私有成员与公共成员 方法的重载 构造方法 实例成员与静态成员 重点: 重载 难点:
Advertisements

单元二:面向对象程序设计 任务二:借书卡程序设计.
第四章 类、对象和接口.
JAVA 编 程 技 术 主编 贾振华 2010年1月.
项目7 面向对象高级.
项目6 通用堆栈.
四資二甲 第三週作業 物件導向程式設計.
第一章 面向对象程序设计.
C#程序设计 10软件1、2班 王槐彬 计算机工程学院.
第二章 JAVA语言基础.
類別與物件 Class & Object.
Ch07 介面與多重繼承 物件導向程式設計(II).
第八章 类和对象.
1 Department of Computing.
第5章 Java中类、对象、接口 及包的概念 5.1 类的基本概念 5.2 类的继承概念 5.3 抽象类和接口 5.4 包.
鄭士康 國立台灣大學 電機工程學系/電信工程研究所/ 資訊網路與多媒體研究所
2.1 基本資料型別 2.2 變數 2.3 運算式與運算子 2.4 輸出與輸入資料 2.5 資料型別轉換 2.6 實例
C#程序设计 c# programming 泛型 C#程序设计课程组.
第八章 C#高级编程.
物件導向程式設計 (Object-Oriented rogramming)
.NET 程式設計入門(使用 C#) 講師:鄧智鴻.
第二章 C# 基础知识.
第四章 在 C# 中实现 OOP 概念.
Classes Lecturer: 曾學文.
CHAPTER 9 建構方法 ROBERT.
教材 《C++程序设计》.谭浩强. 清华大学出版社 王雪晶
第六章 类的扩展与继承.
程式敘述執行順序的轉移 控制與重複、方法 Lecturer:曾學文.
第三章 C#面向对象初级编程 面向对象得程序设计越来越受到编程人员的喜爱。类和对象是面向对象程序设计中的重要概念。封装性、继承性和多态性是面向对象的特点,本章旨在全面说明C#编写面向对象程序设计的方法。
Ch10 類別與物件-方法 Java程式設計(2).
程式設計實作.
第3章 继承和派生.
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
Java程序设计 第9章 继承和多态.
第4章 数组和集合 4.1 一维数组 4.2 二维数组 4.3 Array类 4.4 交错数组 4.5 ArrayList类
C#面向对象程序设计 $7 继承和多态性.
類別的繼承 Vehicle Car.
中国矿大计算机学院杨东平 第5章 接口和包 中国矿大计算机学院杨东平
鄭士康 國立台灣大學 電機工程學系/電信工程研究所/ 資訊網路與多媒體研究所
Ch02-基礎語法.
谭浩强 编著 中国高等院校计算机基础教育课程体系规划教材 C++程序设计.
C/C++/Java 哪些值不是头等程序对象
$10 可空类型.
C#程序设计基础 第二章 数据类型.
第六章 属性、索引器、委托和事件.
* 單元:電腦與問題解決 主題:Java物件導向程式設計-類別與物件 台南縣國立善化高中 蕭嘉民 老師
第16章 虛擬與多形 16-1 虛擬函數 16-2 純虛擬函數與抽象類別 16-3 多形 16-4 虛擬繼承與虛擬解構子.
第三章 C# 基础知识.
第7章 繼承/多型/介面 注意: 本投影片僅供本書上課教師使用,非經同意請勿上網轉載或供拷貝.
JAVA 编 程 技 术 主编 贾振华 2010年1月.
第二章 Java基本语法 讲师:复凡.
第五章 介面/集合/泛型 注意: 本投影片僅供本書上課教師使用,非經同意請勿上網轉載或供拷貝.
C#程序设计基础 $3 成员、变量和常量.
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
第二章 Java基本语法 讲师:复凡.
第四章 类 4.1 基础知识 4.2 构造函数 4.3 方法 4.4 属性与索引 4.5 String类
第二章 Java语法基础.
面向对象技术 练习 ffh.
鄭士康 國立台灣大學 電機工程學系/電信工程研究所/ 資訊網路與多媒體研究所
#include <iostream.h>
第二章 Java基本语法 讲师:复凡.
方法進階及物件導向基礎 Lecturer: 楊昌樺.
C++语言程序设计 C++语言程序设计 第八章 继承 C++语言程序设计.
C++语言程序设计 C++语言程序设计 第十章 多态 第十一组 C++语言程序设计.
第6單元 6-1 類別的繼承 (Class Inheritance) 6-2 抽象類別 (Abstract Class)
JAVA 程式設計與資料結構 第三章 物件的設計.
第2章 Java语言基础.
第二章 Java基础语法 北京传智播客教育
第6章 继承和多态 伍孝金
鄭士康 國立台灣大學 電機工程學系/電信工程研究所/ 資訊網路與多媒體研究所
Presentation transcript:

第6章 继承和接口设计 6.1 继 承 6.2 多态性 6.3 抽象类 6.4 接口 6.5 接口在集合排序中的应用

6.1 继 承 6.1.1什么是继承 一个类从另一个类派生出来时,称之为派生类或子类,被派生的类称为基类或父类。 6.1 继 承 6.1.1什么是继承 一个类从另一个类派生出来时,称之为派生类或子类,被派生的类称为基类或父类。 派生类从基类那里继承特性,派生类也可以作为其他类的基类,从一个基类派生出来的多层类形成了类的层次结构。

C#中的继承具有以下特点: C#中只允许单继承,即一个派生类只能有一个基类。 C#中继承是可传递的,如果C从B派生,B从A派生,那么C不仅继承B的成员,还继承A的成员。 C#中派生类可添加新成员,但不能删除基类的成员。 C#中派生类不能继承基类的构造函数和析构函数,但能继承基类的属性。 C#中派生类可隐藏基类的同名成员,如果在派生类可以隐藏了基类的同名成员,基类该成员在派生类中就不能被直接访问,只能通过“base.基类方法名”来访问。 C#中派生类对象也是基类的对象,但基类对象却不一定是基派生类的对象。也就是说,基类的引用变量可以引用基派生类对象,而派生类的引用变量不可以引用基类对象。

6.1.2 派生类的声明 派生类的声明格式如下: C#中派生类可以从它的基类中继承字段、属性、方法、事件、索引器等。 [类修饰符] class 派生类:基类; C#中派生类可以从它的基类中继承字段、属性、方法、事件、索引器等。   实际上除了构造函数和析构函数,派生类隐式地继承了基类的所有成员。

在主函数中包含以下代码: class A { private int n; //私有字段 protected int m; //保护的字段 public void afun()  //公有方法 { //方法的代码 } class B : A { private int x;  //私有字段 public void bfun() //公有方法 从中看出Base_fun()方法在B类中不用重写,因为B类继承了A类,所以可以不用重写A类中的Base_fun()方法,就可以被B类调用。 在主函数中包含以下代码: B b = new B(); //定义对象并实例化 b.afun();

类的继承的表示

6.1.3 基类成员的可访问性 派生类将获取基类的所有非私有数据和行为。 6.1.3 基类成员的可访问性 派生类将获取基类的所有非私有数据和行为。 如果希望在派生类中隐藏某些基类的成员,可以在基类中将这些成员设为private访问成员。

6.1.4 按次序调用构造函数和析构函数 1. 调用默认构造函数的次序 如果类是从一个基类派生出来的,那么在调用这个派生类的默认构造函数之前会调用基类的默认构造函数。调用的次序将从最远的基类开始。

在主函数中执行以下语句: 运行结果如下: class A //基类 { public A() { Console.WriteLine("调用类A的构造函数");} } class B : A //从A派生类B public B() { Console.WriteLine("调用类B的构造函数"); } class C:B //从B派生类C public C() { Console.WriteLine("调用类C的构造函数"); } 在主函数中执行以下语句: C b=new C();  //定义对象并实例化 运行结果如下: 调用类A的构造函数 调用类B的构造函数 调用类C的构造函数

2. 调用默认析构函数的次序  当销毁对象时,它会按照相反的顺序来调用析构函数。首先调用派生类的析构函数,然后是最近基类的析构函数,最后才调用那个最远的析构函数。

在主函数中执行语句C b=new C();其运行结果如下: class A //基类 { ~A() { Console.WriteLine("调用类A的析构函数");} } class B : A //从A派生类B ~B() { Console.WriteLine("调用类B的析构函数"); } class C:B //从B派生类C ~C() { Console.WriteLine("调用类C的析构函数"); } 在主函数中执行语句C b=new C();其运行结果如下: 调用类C的析构函数 调用类B的析构函数 调用类A的析构函数

3. 调用重载构造函数的次序 调用基类的重载构造函数需使用base关键字。base关键字主要是为派生类调用基类成员提供一个简写的方法,可以在子类中使用base关键字访问的基类成员。调用基类中重载构造函数的方法是将派生类的重载构造函数作如下设计: public 派生类名(参数列表1):base(参数列表2) {  } 其中,“参数列表2”和“参数列表1”存在对应关系。

【例6.1】 分析以下程序的运行结果。 public A(int x1) public B(int x1,int y1):base(x1) using System; namespace Proj7_1 { class A { private int x; public A() { Console.WriteLine("调用类A的构造函数");} public A(int x1) { x = x1; Console.WriteLine("调用类A的重载构造函数"); } ~A() { Console.WriteLine("A:x={0}", x); } class B : A { private int y; public B() { Console.WriteLine("调用类B的构造函数"); } public B(int x1,int y1):base(x1) { y = y1; Console.WriteLine("调用类B的重载构造函数"); ~B() { Console.WriteLine("B:y={0}", y); }

class C:B { private int z; public C() { Console.WriteLine("调用类C的构造函数"); } public C(int x1,int y1,int z1):base(x1,y1) { z = z1; Console.WriteLine("调用类C的重载构造函数"); } ~C() { Console.WriteLine("C:z={0}", z); } class Program { static void Main(string[] args) { C c=new C(1,2,3);

6.1.5 使用sealed修饰符来禁止继承 C#中提供了sealed关键字用来禁止继承。要禁止继承一个类,只需要在声明类时加上sealed关键字就可以了,这样的类称为密封类。例如: sealed class 类名 {  } 这样就不能从该类派生任何子类。

6.2 多态性 面向对象程序设计中的多态性是一个重要的概念。所谓多态性,就是同一签名具有不同的表现行为,运算符重载和函数重载都属于多态性的表现形式。

6.2.1 隐藏基类方法 方法1:使用新的派生成员替换基成员 方法2:重写虚拟的基成员。 6.2.1 隐藏基类方法   当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。若要更改基类的数据和行为,有两种选择: 方法1:使用新的派生成员替换基成员 方法2:重写虚拟的基成员。 

方法1示例:在使用新的派生方法替换基方法时应使用new关键字。例如:  class A  { public void fun() { Console.WriteLine("A"); }  }  class B:A  { new public void fun() //隐藏基类方法fun Console.WriteLine("B"); 在主函数中执行以下语句:  B b=new B();  b.fun(); 运行结果如下:  B

6.2.2 重 写 重写是指在子类中编写有相同名称和参数的方法。 6.2.2 重 写   重写是指在子类中编写有相同名称和参数的方法。   重写和重载的区别:后者是指编写(在同一个类中)具有相同的名称,却有不同的参数的方法。也就是说,重写是指子类中的方法与基类中的方法具有相同的签名,而重载方法具有不同的签名。

调用虚方法时,首先调用派生类中的该重写成员,如果没有派生类重写该成员,则它可能是原始成员。 1. virtual关键字 virtual关键字用于修饰方法、属性、索引器或事件声明,并且允许在派生类中重写这些对象。例如,以下定义了一个虚拟方法并可被任何继承它的类重写: public virtual double Area() { return x * y; } 调用虚方法时,首先调用派生类中的该重写成员,如果没有派生类重写该成员,则它可能是原始成员。 注意:默认情况下,方法是非虚拟的,不能重写非虚方法。virtual修饰符不能与static、abstract和override修饰符一起使用。在静态属性上使用virtual修饰符是错误的。

2. 重写方法 override方法提供从基类继承的成员的新实现。通过override声明重写的方法称为重写基方法。重写的基方法必须与override方法具有相同的签名。 注意:不能重写非虚方法或静态方法。重写的基方法必须是virtual、abstract或override的。

【例6.3】 分析以下程序的运行结果。 using System; namespace proj6_3 { class Student { protected int no;  //学号 protected string name; //姓名 protected string tname; //班主任或指导教师 public void setdata(int no1, string name1,string tname1) { no = no1; name = name1;tname=tname1; } public virtual void dispdata() //虚方法 { Console.WriteLine("本科生 学号:{0} 姓名:{1} 班 主 任:{2}",        no,name,tname);

class Graduate : Student { public override void dispdata() //重写方法 { Console.WriteLine("研究生 学号:{0} 姓名:{1} 指导教师:{2}",        no, name, tname); } class Program { static void Main(string[] args) { Student s = new Student(); s.setdata(101, "王华","李量"); s.dispdata(); Graduate g = new Graduate(); g.setdata(201,"张华","陈军"); g.dispdata();

【例6.4】 设计一个控制台应用程序,采用虚方法求长方形、圆、圆球体和圆柱体的面积或表面积。 using System; namespace proj6_4 { public class Rectangle   //长方形类 { public const double PI = Math.PI; protected double x, y; public Rectangle() {} public Rectangle(double x1, double y1) { x = x1;y = y1; } public virtual double Area() //求面积 return x * y;

public class Circle : Rectangle //圆类 { public Circle(double r): base(r, 0) { } public override double Area() //求面积 { return PI * x * x; } class Sphere : Rectangle //圆球体类 { public Sphere(double r): base(r, 0) {} public override double Area() //求面积 return 4 * PI * x * x; class Cylinder : Rectangle //圆柱体类 { public Cylinder(double r, double h): base(r, h) {} return 2 * PI * x * x + 2 * PI * x * y;

class Program { static void Main(string[] args) { double x = 2.4, y = 5.6; double r = 3.0, h = 5.0; Rectangle t = new Rectangle(x,y); Rectangle c = new Circle(r); Rectangle s = new Sphere(r); Rectangle l = new Cylinder(r, h); Console.WriteLine("长为{0},宽为{1}的长方形面积={2:F2}",x,y, t.Area()); Console.WriteLine("半径为{0}的圆面积={1:F2}",r, c.Area()); Console.WriteLine(" 半径为{0}的圆球体表面积={1:F2}",r, s.Area()); Console.WriteLine("半径为{0},高度为{1}的圆柱体表面积={2:F2}",r,h, l.Area()); }

6.2.3 dynamic类型 C#的多态性就像C++的动态联编一样,不是在程序编译时进行静态连接,而是在程序运行时进行动态连接。 C#还基于动态性引入了dynamic类型,dynamic类型的变量只有在运行时才能被确定具体类型,而编译器也会绕过对这种类型的语法检查。

该类只有一个Name属性,设计以下Main方法: 例如,声明如下类: class MyClass { public string Name { get; set; } } 该类只有一个Name属性,设计以下Main方法: static void Main(string[] args) { dynamic s = new MyClass(); s.Name = "Mary"; s.Age = 25; Console.WriteLine(s.Name);

显然,其中s.Age=25语句是错误的,因为MyClass类中并没有Age字段或属性。但在编译时不会给出任何错误,因为s指定为dynamic类型,编译器也会绕过对s的语法检查,将鼠标指针移动到该语句上时,则智能感知显示“(动态表达式)此操作将在运行时解析”。 只有在程序执行时系统会抛出RuntimeBinderException异常,指出“MyClass类未包含Age的定义”的错误。

6.2.4 对象的类型判别和类对象引用的转换 1. 类对象引用的转换 6.2.4 对象的类型判别和类对象引用的转换 1. 类对象引用的转换 对于具有继承关系的类,可以将派生类对象引用转换为基类对象引用,例如,有以下类声明: class A //声明类A { public void funa() { Console.WriteLine("A.funa"); } } class B : A //B类派生自A类 { public void funb() { Console.WriteLine("B.funb"); }

一个基类的对象引用变量可以指向其子类的对象。 下面的转换是正确的: B b = new B(); A a = b; //类对象引用的隐式转换 a.funa(); 因此,类对象引用转换的规则如下: 一个基类的对象引用变量可以指向其子类的对象。 一个基类的对象引用变量不可以访问其子类的对象新增加的成员。

2. is运算符 is运算符用于检查对象是否为某种类型,或者可以转换为给定的类型。如果是,这个运算符返回true;否则,返回false。is运算符不能重载。is运算符的语法格式如下: operand is type 如果type是一个类类型,而operand也是该类型,或者它继承了该类型,或者它可以装箱到该类型,则结果为true。 如果type是一个接口类型,而operand也是该类型,或者它是实现该接口的类型,则结果为true。 如果type是一个值类型,而operand也是该类型,或者它可以拆箱到该类型,则结果为true。

3. as运算符 C#还提供了as运算符,用于在兼容的引用类型之间执行转换。它类似于强制转换,所不同的是,当转换失败时,运算符将产生空(null),而不是引发异常。as的语法格式如下: operand as type 等效于 operand is type ? (type)operand : (type)null

其中,“表达式”只被计算一次。前面的A a = b语句可以改为: A a=b as A; as运算符仅适合以下情况: operand的类型是type类型。 operand可以隐式转换为type类型。 operand可以装箱到type中。

6.3 抽 象 类 6.3.1 抽象类的特性 在类声明中使用abstract修饰符的类称为抽象类。抽象类具有以下特点: 抽象类不能实例化。 6.3 抽 象 类 6.3.1 抽象类的特性 在类声明中使用abstract修饰符的类称为抽象类。抽象类具有以下特点: 抽象类不能实例化。 抽象类可以包含抽象方法和抽象访问器。 抽象类中可以存在非抽象的方法。 不能用sealed修饰符修改抽象类,这也意味着抽象类不能被继承。 从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实现。 抽象类可以被抽象类所继承,结果仍是抽象类。

6.3.2 抽象方法 在方法声明中使用abstract修饰符以指示方法不包含实现的,即为抽象方法。抽象方法具有以下特性: 抽象方法是隐式的虚方法。 只允许在抽象类中使用抽象方法声明。 一个类中可以包含一个或多个抽象方法。 因为抽象方法声明不提供实际的实现,所以没有方法体;方法声明只是以一个分号结束,并且在签名后没有大括号{}。 抽象方法实现由一个重写方法提供,此重写方法是非抽象类的成员。 实现抽象类用“:”,实现抽象方法用override关键字。 在抽象方法声明中使用static或virtual修饰符是错误的。 抽象方法被实现后,不能更改修饰符。

【例6.5】 分析以下程序的运行结果。 using System; namespace proj6_5 { abstract class A //抽象类声明 { abstract public int fun(); //抽象方法声明 } class B : A { int x, y; public B(int x1, int y1) //抽象方法实现 x = x1; y = y1; public override int fun() return x * y;

{ static void Main(string[] args) { B b = new B(2, 3); class Program { static void Main(string[] args) { B b = new B(2, 3); Console.WriteLine("{0}", b.fun()); } 6

6.3.3 抽象属性 除了在声明和调用语法上不同外,抽象属性的行为与抽象方法类似,另外,抽象属性具有如下特性: 在静态属性上使用abstract修饰符是错误的。 在派生类中,通过包括使用override修饰符的属性声明,可以重写抽象的继承属性。 抽象属性声明不提供属性访问器的实现,它只声明该类支持属性,而将访问器实现留给其派生类。

【例6.6】 分析以下程序的运行结果。 using System; namespace proj6_6 { abstract class A //抽象类声明 { protected int x = 2; protected int y = 3; public abstract void fun(); //抽象方法声明 public abstract int px { get;set; }  //抽象属性声明 public abstract int py { get; } //抽象属性声明 }

class B : A { public override void fun() //抽象方法实现 { x++; y++; } public override int px //抽象属性实现 { set { x = value; } get { return x + 10; } } public override int py //抽象属性实现 { get { return y + 10; } class Program { static void Main(string[] args) { B b = new B(); b.px = 5; b.fun(); Console.WriteLine("x={0}, y={1}", b.px, b.py); x=16,y=14

6.4 接 口 6.4.1 什么是接口 接口是类之间交互内容的一个抽象,把类之间需要交互的内容抽象出来定义成接口,可以更好的控制类之间的逻辑交互。接口具有下列特性: 接口类似于抽象基类。继承接口的任何非抽象类型都必须实现接口的所有成员。 不能直接实例化接口。 接口可以包含事件、索引器、方法和属性。 接口不包含方法的实现。 类和结构可从多个接口继承。 接口自身可从多个接口继承。

 接口只包含成员定义,不包含成员的实现,成员的实现需要在继承的类或者结构中实现。   接口的成员包括方法、属性、索引器和事件,但接口不包含字段。

6.4.2 接口的定义 1. 声明接口 一个接口声明属于一个类型说明,其一般语法格式如下: [接口修饰符] interface 接口名[:父接口列表] { //接口成员定义体 } 其中,接口修饰符可以是new、public、protected、internal和private。new修饰符是在嵌套接口中唯一被允许存在的修饰符,表示用相同的名称隐藏一个继承的成员。

2. 接口的继承 接口可以从零个或多个接口中继承。当一个接口从多个接口中继承时,用“:”后跟被继承的接口名称,这多个接口之间用“,”号分隔。被继承的接口应该是可以被访问的,即不能从internal或internal类型的接口继承。

对一个接口的继承也就继承了接口的所有成员。例如: public interface Ia //接口Ia声明 { void mymethod1(); } public interface Ib //接口Ib声明 int mymethod2(int x); public interface Ic : Ia, Ib  //接口Ic从Ia和Ib继承

6.4.3 接口的成员 接口可以声明零个或多个成员。一个接口的成员不止包括自身声明的成员,还包括从父接口继承的成员。 6.4.3 接口的成员 接口可以声明零个或多个成员。一个接口的成员不止包括自身声明的成员,还包括从父接口继承的成员。 所有接口成员默认都是公有的,接口成员声明中包含任何修饰符都是错误的。

语法格式:返回类型 方法名([参数表]); 2. 接口属性成员 语法格式:返回类型 属性名{get; 或 set;}; 1. 接口方法成员 语法格式:返回类型 方法名([参数表]); 2. 接口属性成员 语法格式:返回类型 属性名{get; 或 set;}; 例如,以下声明一个接口Ia,其中接口属性x为只读的,y为可读可写的,z为只写的: public interface Ia { int x { get;} int y { set;get;} int z { set;} }

语法格式:数据类型 this[索引参数表]{get; 或set;}; 例如: 3. 接口索引器成员 语法格式:数据类型 this[索引参数表]{get; 或set;}; 例如: public interface Ia { string this[int index] { get; set; }

4. 接口事件成员 语法格式:event 代表名 事件名; 例如: public delegate void mydelegate();  //声明委托类型 public interface Ia { event mydelegate myevent; }

6.4.4 接口的实现 接口的实现分为隐式实现和显式实现。如果类或者结构要实现的是单个接口,可以使用隐式实现,如果类或者结构继承了多个接口,那么接口中相同名称成员就要显式实现。显式实现是通过使用接口的完全限定名来实现接口成员的。 接口实现的语法格式如下: class 类名:接口名列表 { //类实体 }

说明: 当一个类实现一个接口时,这个类就必须实现整个接口,而不能选择实现接口的某一部分。 一个接口可以由多个类来实现,而在一个类中也可以实现一个或多个接口。 一个类可以继承一个基类,并同时实现一个或多个接口。

1. 隐式实现接口成员 如果类实现了某个接口,它必然隐式地继承了该接口成员,只不过增加了该接口成员的具体实现。 若要隐式实现接口成员,类中的对应成员必须是公共的、非静态的,并且与接口成员具有相同的名称和签名。

【例6.7】 分析以下程序的运行结果 。 using System; namespace proj6_7 {  interface Ia //声明接口Ia { float getarea(); //接口成员声明 } public class Rectangle : Ia   //类A继承接口Ia { float x,y; public Rectangle(float x1, float y1)   //构造函数 x = x1; y = y1; public float getarea()  //隐式接口成员实现,必须使用public return x*y;

class Program { static void Main(string[] args) { Rectangle box1 = new Rectangle(2.5f, 3.0f); //定义一个类实例 Console.WriteLine("长方形面积: {0}", box1.getarea()); } 长方形面积:7.5

2. 显式实现接口成员 当类实现接口时,如给出了接口成员的完整名称即带有接口名前缀,则称这样实现的成员为显式接口成员,其实现被称为显式接口实现。 显式接口成员实现不能使用任何修饰符。

【例6.9】 分析以下程序的运行结果 。 using System; namespace proj6_9 { interface Ia //声明接口Ia { float getarea();   //接口成员声明 } public class Rectangle : Ia   //类Rectangle继承接口Ia { float x,y; public Rectangle(float x1, float y1)  //构造函数 x = x1; y = y1; float Ia.getarea() //显式接口成员实现,带有接口名前缀,不能使用public return x*y;

class Program { static void Main(string[] args) { Rectangle box1 = new Rectangle(2.5f, 3.0f);//定义一个类实例 Ia ia = (Ia)box1; //定义一个接口实例 Console.WriteLine("长方形面积: {0}", ia.getarea()); } 长方形面积:7.5

6.5 接口在集合排序中的应用 6.5.1 ArrayList类的排序方法 6.5 接口在集合排序中的应用 6.5.1 ArrayList类的排序方法 ArrayList类对象不仅可以存放数值、字符串,还可以存放其他类的对象和结构变量。其提供的排序方法如下: ArrayList.Sort ():使用每个元素的IComparable接口实现对整个ArrayList中的元素进行排序。 ArrayList.Sort (IComparer):使用指定的比较器对整个ArrayList中的元素进行排序。 ArrayList.Sort (Int32, Int32, IComparer):使用指定的比较器对ArrayList中某个范围内的元素进行排序。 其中涉及到IComparable和IComparer两个系统接口。

6.5.2 IComparable接口 IComparable接口定义通用的比较方法,由值类型或类实现以创建类型特定的比较方法。其公共成员有CompareTo,它用于比较当前实例与同一类型的另一对象。其使用语法格式如下: int CompareTo(Object obj) 其中,obj表示与此实例进行比较的对象。其返回值是一个32位有符号整数,指示要比较的对象的相对顺序。

返回值的含义如下: 小于零:此实例小于obj。 零:此实例等于obj。 大于零:此实例大于obj。 IComparable接口的CompareTo方法提供默认排序次序,如果需要改变其排序方式,可以在相关类中实现CompareTo方法,以订制其比较功能。

【例6.13】 分析以下程序的运行结果。 using System; using System.Collections; //新增 namespace proj6_13 { class Program { class Stud : IComparable //从接口派生 { int xh; //学号 string xm; //姓名 int fs; //分数 public int pxh //pxh属性 { get { return xh; } } public string pxm //pxm属性 get { return xm; } public int pfs //pfs属性 get { return fs; }

public Stud(int no, string name, int degree) { xh = no; xm = name; fs = degree; } public void disp() //输出学生信息 Console.WriteLine("\t{0}\t{1}\t{2}", xh, xm, fs); public int CompareTo(object obj) //实现接口方法 { Stud s = (Stud)obj;    //转换为Stud实例 if (pfs < s.pfs) return 1; else if (pfs == s.pfs) return 0; else return -1; static void disparr(ArrayList myarr, string str)  //输出所有学生信息 { Console.WriteLine(str); Console.WriteLine("\t学号\t姓名\t分数"); foreach (Stud s in myarr) s.disp();

  static void Main(string[] args) { int i, n = 4; ArrayList myarr = new ArrayList(); Stud[] st = new Stud[4] { new Stud(1, "Smith", 82), new Stud(4, "John", 88), new Stud(3, "Mary", 95), new Stud(2, "Cherr", 64) }; for (i = 0; i < n; i++) //将对象添加到myarr集合中 myarr.Add(st[i]); disparr(myarr, "排序前:"); myarr.Sort(); disparr(myarr, "按分数降序排序后:"); }

6.5.3 IComparer接口      IComparer接口定义两个对象的通用比较方法。其公共成员有Compare。Compare方法比较两个对象并返回一个值,指示一个对象是小于、等于还是大于另一个对象。其使用语法格式如下:   int Compare(Object x,Object y)   其中,x表示要比较的第一个对象。y表示要比较的第二个对象。 其返回值是一个32位有符号整数,指示要比较的对象的相对顺序。 

 返回值的含义如下:  小于零:x小于y。 零:x等于y。 大于零:x大于y。 通常声明一个类(从IComparer接口派生),其中实现Compare方法,以订制其比较功能,然后再调用排序方法以该类的对象作为参数,这样在排序时会自动使用该订制的Compare方法。

例6.14】 分析以下程序的运行结果。 using System; using System.Collections; //新增 namespace proj6_14 { class Program { class Stud { int xh; //学号 string xm; //姓名 int fs; //分数 public int pxh //pxh属性 { get { return xh; } } public string pxm //pxm属性 get { return xm; } public int pfs //pfs属性 get { return fs; }

public Stud(int no, string name, int degree) { xh = no; xm = name; fs = degree; } public void disp() Console.WriteLine("\t{0}\t{1}\t{2}", xh, xm, fs); public class myCompareClassxh : IComparer { int IComparer.Compare(object x, object y) { Stud a = (Stud)x; Stud b = (Stud)y; if (a.pxh > b.pxh) return 1; else if (a.pxh == b.pxh) return 0; else return -1;

public class myCompareClassxm : IComparer { int IComparer.Compare(object x, object y) { Stud a = (Stud)x; Stud b = (Stud)y; return String.Compare(a.pxm, b.pxm); } public class myCompareClassfs : IComparer if (a.pfs < b.pfs) return 1; else if (a.pfs == b.pfs) return 0; else return -1;

static void disparr(ArrayList myarr, string str) //输出所有学生信息 { Console.WriteLine(str); Console.WriteLine("\t学号\t姓名\t分数"); foreach (Stud s in myarr) s.disp(); }

static void Main(string[] args) { int i, n = 4; IComparer myComparerxh = new myCompareClassxh(); IComparer myComparerxm = new myCompareClassxm(); IComparer myComparerfs = new myCompareClassfs(); ArrayList myarr = new ArrayList(); Stud[] st = new Stud[4] { new Stud(1, "Smith", 82), new Stud(4, "John", 88), new Stud(3, "Mary", 95), new Stud(2, "Cherr", 64) }; for (i = 0; i < n; i++) myarr.Add(st[i]); disparr(myarr, "排序前:"); myarr.Sort(myComparerxh); disparr(myarr, "按学号升序排序后:"); myarr.Sort(myComparerxm); disparr(myarr, "按姓名词典次序排序后:"); myarr.Sort(myComparerfs); disparr(myarr, "按分数降序排序后:"); }