1、 本章将继续深入介绍继承和多态两个重要思想以及在本章将继续深入介绍继承和多态两个重要思想以及在C#中的实现,并详细介绍由继承和封装而衍生出的抽象类、中的实现,并详细介绍由继承和封装而衍生出的抽象类、抽象函数、密封类、密封函数、运算符重载、虚函数等重抽象函数、密封类、密封函数、运算符重载、虚函数等重要的知识点。要的知识点。章节内容章节内容8.1 继承的基础知识继承的基础知识8.2 派生类的构造函数和析构函数派生类的构造函数和析构函数 8.3 抽象类和抽象函数抽象类和抽象函数 8.4 密封类和密封方法密封类和密封方法 8.5 多态的基础知识多态的基础知识 8.6 虚方法虚方法8.7 运算符重载运算
2、符重载8.8 接口接口8.9 小结小结8.1.1 简单继承C+C+中的多重继承中的多重继承8.1.1 简单继承C为什么为什么摒弃了多继摒弃了多继承承?C#允许通允许通过多个接口过多个接口来变相地实来变相地实现有控制的现有控制的多继承多继承 8.1.2 使用继承public class Mobile /定义基类定义基类Mobile public Mobile()/基类的构造函数基类的构造函数 Console.WriteLine(I am the baseclass,Mobile.);public Mobile(string name)/基类的构造函数基类的构造函数 Console.WriteLi
3、ne(Hello,My name is +name+!);public void MemorySize(int size)Console.WriteLine(MemorySize is+size+I can store a lot.);8.1.2 使用继承public class Moto:Mobile /定义派生类定义派生类Moto public Moto()/派生类的构造函数派生类的构造函数 Console.WriteLine(I am the derivedclass,Moto.);class Program static void Main(string args)Moto m=new
4、 Moto();m.MemorySize(100);Console.ReadLine();8.1.3 在派生类中使用基类的方法class CustomerAccount public decimal CalculatePrice()/实现代码实现代码 return 0.0M;class GoldAccount:CustomerAccount public decimal CalculatePrice()return base.CalculatePrice()*0.9M;8.2 派生类的构造函数和析构函数构造函数的调用顺序为,先调用基类的构造函数,构造函数的调用顺序为,先调用基类的构造函数,再调
5、用派生类中的构造函数体。再调用派生类中的构造函数体。因为基类没有不带参数的构造函数,所以即使是派因为基类没有不带参数的构造函数,所以即使是派生类的不带参数的构造函数,也必须显式调用基类生类的不带参数的构造函数,也必须显式调用基类的带参数的构造函数,尽管该参数本身毫无意义。的带参数的构造函数,尽管该参数本身毫无意义。对基类构造函数的调用必须使用对基类构造函数的调用必须使用base关键字,而不关键字,而不能像能像C+一样用具体的类名代替。一样用具体的类名代替。析构函数的调用顺序同构造函数的调用顺序完全相析构函数的调用顺序同构造函数的调用顺序完全相反。反。8.2 派生类的构造函数和析构函数using
6、 System;public class Mobile string parentString;public Mobile()Console.WriteLine(Mobile Constructor.);public Mobile(string myString)parentString=myString;Console.WriteLine(parentString);public void Print()Console.WriteLine(Im a Mobile Class.);8.2 派生类的构造函数和析构函数public class Moto:Mobile public Moto():b
7、ase(From Derived)Console.WriteLine(Moto Constructor.);public void Print()base.Print();Console.WriteLine(Im a Moto Class.);public static void Main()Moto moto=new Moto();moto.Print();(Mobile)moto).Print();Console.ReadLine();程序运行结果:From DerivedMoto Constructor.Im a Mobile Class.Im a Moto Class.Im a Mob
8、ile Class.8.3 抽象类和抽象函数抽象类表示一种抽象的概念,用于为其派生类提供抽象类表示一种抽象的概念,用于为其派生类提供一个公共接口。抽象类由一个公共接口。抽象类由abstract修饰符声明。抽修饰符声明。抽象类只能作为其他类的基类,而不能直接实例化,象类只能作为其他类的基类,而不能直接实例化,对抽象类使用对抽象类使用new关键字会发生错误。关键字会发生错误。抽象函数没有实现代码,而是由抽象函数没有实现代码,而是由abstract修饰符声修饰符声明。抽象函数只提供函数名称,具体实现交由继承明。抽象函数只提供函数名称,具体实现交由继承的派生类。可见,抽象函数同抽象类类似,也是虚的派生
9、类。可见,抽象函数同抽象类类似,也是虚拟的,但是这里的虚拟不需要而且绝对不能使用拟的,但是这里的虚拟不需要而且绝对不能使用virtual关键字(即关键字(即abstract和和virtual修饰符不能一修饰符不能一起使用),否则会产生语法错误。起使用),否则会产生语法错误。8.3 抽象类和抽象函数 抽象类可以包含抽象函数,也可包含非抽象函数,而非抽象抽象类可以包含抽象函数,也可包含非抽象函数,而非抽象类不能包含抽象函数。也就是说,包含了抽象函数的类一定类不能包含抽象函数。也就是说,包含了抽象函数的类一定是抽象类(假如抽象函数可以包含在非抽象类中,那么非抽是抽象类(假如抽象函数可以包含在非抽象类
10、中,那么非抽象类实例调用没有实现代码的抽象函数时,显然不合理)。象类实例调用没有实现代码的抽象函数时,显然不合理)。当从抽象类派生非抽象类时,非抽象类必须具体实现所继承当从抽象类派生非抽象类时,非抽象类必须具体实现所继承的所有抽象函数,且必须使用的所有抽象函数,且必须使用override关键字重写抽象类所关键字重写抽象类所定义的函数。定义的函数。抽象类可以被抽象类所继承,结果可以仍是抽象类。抽象类可以被抽象类所继承,结果可以仍是抽象类。抽象方法被实现后,不能更改原方法的修饰符。抽象方法被实现后,不能更改原方法的修饰符。在在C+中,对抽象函数有纯虚函数一说;在中,对抽象函数有纯虚函数一说;在C#
11、中,仅使用抽中,仅使用抽象这个术语。象这个术语。8.3 抽象类和抽象函数using System;public abstract class Person public abstract void SayHello();public void About()Console.WriteLine(Abstract Demo);public class Student:Person public override void SayHello()Console.WriteLine(SayHello);8.4 密封类和密封方法当类和方法被当类和方法被sealed修饰时,就成为了密封类和密修饰时,就成为了
12、密封类和密封方法。对类来说,这表示不能继承该类;对于方封方法。对类来说,这表示不能继承该类;对于方法来说,这表示不能重写该方法。实际上,法来说,这表示不能重写该方法。实际上,Java中中也有类似的定义,但使用的关键字为也有类似的定义,但使用的关键字为final。对于密封类和密封方法的应用场合通常是商业上保对于密封类和密封方法的应用场合通常是商业上保护版权,防止被他人扩展。护版权,防止被他人扩展。8.4 密封类和密封方法class BaseClass public virtual void FinalMethod()/代码代码 class MyClass:BaseClass public sea
13、led override void FinalMethod()/代码代码 class DerivedClass:MyClass public override void FinalMethod()/错误!错误!/代码代码 8.5 多态的基础知识多态性(多态性(Polymorphism)是一个希腊单词,指)是一个希腊单词,指“同一个名称,多种形态同一个名称,多种形态”。或者可以理解为,对。或者可以理解为,对同一个名称,可以有不同的理解,有不同的反应。同一个名称,可以有不同的理解,有不同的反应。例如,当理发师听到例如,当理发师听到“cut”的时候会开始剪头发,的时候会开始剪头发,演员听到演员听到“
14、cut”的时候会停止表演,医生听到的时候会停止表演,医生听到“cut”的时候会在病人身上进行手术。的时候会在病人身上进行手术。在面向对象编程中,多态是指调用同一个方法名,在面向对象编程中,多态是指调用同一个方法名,却执行不同的方法体。多态性是类为方法(这些方却执行不同的方法体。多态性是类为方法(这些方法以相同的名称调用)提供不同实现方式的能力。法以相同的名称调用)提供不同实现方式的能力。多态性允许对类的某个方法进行调用而无需考虑该多态性允许对类的某个方法进行调用而无需考虑该方法所提供的特定实现。方法所提供的特定实现。8.5 多态的基础知识在在C#中,多态可以分为静态多态和动态多态。中,多态可以
15、分为静态多态和动态多态。静态多态就是方法的重载(静态多态就是方法的重载(overload),也称为静),也称为静态联编,由编译器在编译期间确定真正执行方法的态联编,由编译器在编译期间确定真正执行方法的地址。方法重载和运算符重载(关于运算符重载的地址。方法重载和运算符重载(关于运算符重载的介绍可参见本章介绍可参见本章8.7节)都实现了编译时的多态性。节)都实现了编译时的多态性。对于方法重载,需要:对于方法重载,需要:方法名必须相同。方法名必须相同。参数列表必须不相同(参数个数不同、参数类型不同或参数个参数列表必须不相同(参数个数不同、参数类型不同或参数个数与类型都不相同)。数与类型都不相同)。和
16、返回值类型无关。和返回值类型无关。8.5 多态的基础知识动态多态,也称为动态联编,指的是系统在编译期动态多态,也称为动态联编,指的是系统在编译期间无法确定真正执行方法的地址,在运行期间由运间无法确定真正执行方法的地址,在运行期间由运行时(行时(Common Language Runtime,CLR)根据)根据虚方法表可动态计算出来。动态多态通过虚方法的虚方法表可动态计算出来。动态多态通过虚方法的重写(重写(override)来实现(关于虚方法的介绍可详)来实现(关于虚方法的介绍可详见本章见本章8.6节)。节)。编译时的多态性能够提供运行速度快的特点,而运编译时的多态性能够提供运行速度快的特点,
17、而运行时的多态性能够提供高度灵活和抽象的特点。行时的多态性能够提供高度灵活和抽象的特点。8.6 虚方法对于类中的方法,若声明时加上对于类中的方法,若声明时加上virtual修饰符,则修饰符,则称该方法为虚方法,否则为非虚方法。称该方法为虚方法,否则为非虚方法。和和Java不同,不同,Java中的方法默认情况下都是虚方法;中的方法默认情况下都是虚方法;C#中类的方法默认情况下都是非虚方法。中类的方法默认情况下都是非虚方法。在基类中把某个方法声明为虚方法,在派生类中就在基类中把某个方法声明为虚方法,在派生类中就可以通过在该方法名前加可以通过在该方法名前加override关键字实现对基关键字实现对基
18、类中该方法的重写类中该方法的重写 虚方法和其他一般方法有较大的区别。一般方法在虚方法和其他一般方法有较大的区别。一般方法在编译时就静态地编译到了执行文件中,其相对地址编译时就静态地编译到了执行文件中,其相对地址在程序运行期间不发生变化;而虚方法在编译期间在程序运行期间不发生变化;而虚方法在编译期间不被静态编译,它的相对地址是不确定的,会根据不被静态编译,它的相对地址是不确定的,会根据运行时期对象实例来动态判断要调用的方法。运行时期对象实例来动态判断要调用的方法。8.6 虚方法关于虚方法,需要注意:关于虚方法,需要注意:不仅仅只有方法可以虚拟,属性、事件和索引器成员都可以不仅仅只有方法可以虚拟,
19、属性、事件和索引器成员都可以虚拟,但字段不能是虚拟的。虚拟,但字段不能是虚拟的。虚成员的访问修饰符不能定义为私有。虚成员的访问修饰符不能定义为私有。使用了使用了virtual修饰符后,不允许再有修饰符后,不允许再有static、abstract或或override修饰符。修饰符。使用了使用了override修饰符后,不允许再有修饰符后,不允许再有new、static或或virtual修饰符。修饰符。在派生类中重写虚方法时,要求方法名称、返回值类型、参在派生类中重写虚方法时,要求方法名称、返回值类型、参数表中的参数个数、类型、顺序、访问修饰符都必须与基类数表中的参数个数、类型、顺序、访问修饰符都
20、必须与基类中的虚方法完全一致。中的虚方法完全一致。只有虚方法和抽象方法才能被重写只有虚方法和抽象方法才能被重写。8.6 虚方法using System;class A public virtual void Method()Console.WriteLine(A.method);class B:A public new virtual void Method()Console.WriteLine(B.method);class C:B public override void Method()Console.WriteLine(C.method);public static void Main
21、()A a=new A();B b=new C();A c=b;a.Method();b.Method();c.Method();Console.ReadLine();执行结果:A.methodC.methodA.method8.6 虚方法class C:B public override void Method()Console.WriteLine(C.method);public static void Main()A a=new A();B b=new C();A c=b;a.Method();b.Method();c.Method();Console.ReadLine();8.6 虚方
22、法关于虚方法,需要注意:关于虚方法,需要注意:不仅仅只有方法可以虚拟,属性、事件和索引器成员都可以不仅仅只有方法可以虚拟,属性、事件和索引器成员都可以虚拟,但字段不能是虚拟的。虚拟,但字段不能是虚拟的。虚成员的访问修饰符不能定义为私有。虚成员的访问修饰符不能定义为私有。使用了使用了virtual修饰符后,不允许再有修饰符后,不允许再有static、abstract或或override修饰符。修饰符。使用了使用了override修饰符后,不允许再有修饰符后,不允许再有new、static或或virtual修饰符。修饰符。在派生类中重写虚方法时,要求方法名称、返回值类型、参在派生类中重写虚方法时,
23、要求方法名称、返回值类型、参数表中的参数个数、类型、顺序、访问修饰符都必须与基类数表中的参数个数、类型、顺序、访问修饰符都必须与基类中的虚方法完全一致。中的虚方法完全一致。只有虚方法和抽象方法才能被重写只有虚方法和抽象方法才能被重写。8.7 运算符重载运算符重载是指允许用户使用自定义的类型编写表达式,运算符重载是指允许用户使用自定义的类型编写表达式,允许用户定义的类型与预定义的类型具有相同的功能,允许用户定义的类型与预定义的类型具有相同的功能,是对重载概念的一个重要补充和发展,它针对对象关系是对重载概念的一个重要补充和发展,它针对对象关系中的多元关系、四则运算和关系运算等常规运算提供了中的多元
24、关系、四则运算和关系运算等常规运算提供了重载支持。重载支持。C#中定义运算符重载的语法形式如下:中定义运算符重载的语法形式如下:public static 返回值类型返回值类型 operator ()注意,运算符重载的方法必须是注意,运算符重载的方法必须是public和和static,operator是关键字,是关键字,opertorName是要重载的运算符。是要重载的运算符。8.7 运算符重载class complex private double real,imag;/复数实部和虚部复数实部和虚部 public complex(double a,int b)real=a;imag=b;pu
25、blic static complex operator+(complex c)c.real+;c.imag+;return c;public void Display()Console.WriteLine(complex.real=0,complex.imag=1,real,imag);8.7 运算符重载 public static complex operator+(complex c1,complex c2)complex c=new complex(0,0);c.real=c1.real+c2.real;c.imag=c1.imag+c2.imag;return c;static vo
26、id Main(string args)complex c1=new complex(10,20);complex c2=new complex(30,40);c1=c1+c2;c1.Display();c1+;c1.Display();Console.ReadLine();代码执行结果:complex.real=40,complex.real=60complex.real=41,complex.real=618.7 运算符重载并非所有的运算符都可以重载,运算符重载规则如下:并非所有的运算符都可以重载,运算符重载规则如下:+、-、!、+、-、true、false,这些一元运算符可被重载。,这些
27、一元运算符可被重载。+、-、*、/、%、&、|、,这些二元运算符可被重载。,这些二元运算符可被重载。=、!=、=,这些关系运算符可被重载。,这些关系运算符可被重载。&、|,这两个条件运算符不能被重载,但它们的值能够被,这两个条件运算符不能被重载,但它们的值能够被“&”和和“|”评估,而这两个运算符可以被重载。评估,而这两个运算符可以被重载。数组运算符(数组运算符()不能被重载,可以定义索引器。)不能被重载,可以定义索引器。转换运算符(转换运算符(())不能被重载,可以定义隐式类型转换和显式类型转换)不能被重载,可以定义隐式类型转换和显式类型转换运算符。运算符。+=、-=、*=、/=、%=、&=
28、、|=、=、=,这些赋值运算符不,这些赋值运算符不能被重载,但它们的值(如能被重载,但它们的值(如+=)会被)会被“+”评估,而评估,而“+”可以被重载。可以被重载。=、.、?:、-、new、is、sizeof、typeof,这些运算符不能被重载。,这些运算符不能被重载。8.8 接口与类相同,接口也定义了一些方法、属性、索引和事件。但与类与类相同,接口也定义了一些方法、属性、索引和事件。但与类不同的是,接口并不提供实现,它只是一种约定,实现接口的类不同的是,接口并不提供实现,它只是一种约定,实现接口的类或结构必须遵守该接口定义的约定。一个接口可以从多个基接口或结构必须遵守该接口定义的约定。一个
29、接口可以从多个基接口继承,而一个类或结构可以实现多个接口。继承,而一个类或结构可以实现多个接口。当类或结构继承接口时,意味着该类或结构为该接口定义的所有当类或结构继承接口时,意味着该类或结构为该接口定义的所有成员提供实现。接口本身不提供类或结构能够以继承基类功能的成员提供实现。接口本身不提供类或结构能够以继承基类功能的方式继承的任何功能。但是,如果基类实现接口,派生类将继承方式继承的任何功能。但是,如果基类实现接口,派生类将继承该实现。该实现。接口是体现面向对象编程思想优越性的一件利器。接口是为继承接口是体现面向对象编程思想优越性的一件利器。接口是为继承而存在的,如果没有继承,那就自然不需要接
30、口了。既然有继承,而存在的,如果没有继承,那就自然不需要接口了。既然有继承,就需要把可能被多个类所继承的一些公共部分抽象出来,接口封就需要把可能被多个类所继承的一些公共部分抽象出来,接口封装的就是这些公共的行为规范(方法定义),类可以通过继承多装的就是这些公共的行为规范(方法定义),类可以通过继承多个接口来丰富自己的行为机制。但是,在个接口来丰富自己的行为机制。但是,在C#中,类是不可以继承中,类是不可以继承多个类的,所以接口的变相多继承作用就显得灵活而至关重要。多个类的,所以接口的变相多继承作用就显得灵活而至关重要。接口可以包含一个或多个成员,这些成员可以是接口可以包含一个或多个成员,这些成
31、员可以是方法、属性、索引指示器或事件,但不能是常量、方法、属性、索引指示器或事件,但不能是常量、域、操作符、构造函数或析构函数。域、操作符、构造函数或析构函数。接口的成员是从基接口继承的成员和由接口本身接口的成员是从基接口继承的成员和由接口本身定义的成员。定义的成员。接口成员默认访问方式是接口成员默认访问方式是public。接口成员定义。接口成员定义不能包含任何修饰符,比如成员定义前不能加不能包含任何修饰符,比如成员定义前不能加abstract、public、protected、internal、private、virtual、override或或static修饰符。修饰符。接口中不包含任何程
32、序代码,也不能有接口中不包含任何程序代码,也不能有“”。接口不能包含构造函数、析构函数和静态成员。接口不能包含构造函数、析构函数和静态成员。接口不能直接实例化。接口不能直接实例化。8.8 接口using System;interface IProduct int GetPrice(int id);void ShowName(string name);interface ISize int GetSize();8.8 接口class Shoe:IProduct,ISize public int GetPrice(int id)if(id=1)return 50;else return 100;p
33、ublic void ShowName(string name)Console.WriteLine(name);public int GetSize()return 35;8.8 接口接口和第接口和第7章介绍的抽象类既有相似之处,也有不同之处。章介绍的抽象类既有相似之处,也有不同之处。其相同之处如下。其相同之处如下。都不能被直接实例化,都可以通过继承实现其抽象方法。都不能被直接实例化,都可以通过继承实现其抽象方法。都是面向抽象编程的技术基础,实现了诸多的设计模式。都是面向抽象编程的技术基础,实现了诸多的设计模式。其不同之处如下。其不同之处如下。接口支持多继承;抽象类不能实现多继承。接口支持多继
34、承;抽象类不能实现多继承。接口只能定义抽象规则;抽象类既可以定义规则,还可能提供已实现接口只能定义抽象规则;抽象类既可以定义规则,还可能提供已实现的成员。的成员。接口是一组行为规范;抽象类是一个不完全的类,着重类族的概念。接口是一组行为规范;抽象类是一个不完全的类,着重类族的概念。接口可以用于支持回调;抽象类不能实现回调,因为不支持继承。接口可以用于支持回调;抽象类不能实现回调,因为不支持继承。接口只包含方法、属性、索引器、事件的签名,但不能定义字段和包接口只包含方法、属性、索引器、事件的签名,但不能定义字段和包含实现的方法;抽象类也可以定义字段、属性和包含有实现的方法。含实现的方法;抽象类也
35、可以定义字段、属性和包含有实现的方法。接口可以作用于值类型和引用类型;抽象类只能作用于引用类型。例接口可以作用于值类型和引用类型;抽象类只能作用于引用类型。例如,如,struct就可以继承接口,而不能继承类。就可以继承接口,而不能继承类。继承、多态以及第继承、多态以及第7章介绍的封装,是面向对象思想的章介绍的封装,是面向对象思想的3大基大基本特征。继承使得在基类中进行的更改能够自动传播到派生本特征。继承使得在基类中进行的更改能够自动传播到派生类中,即只修改基类就可以对继承的类进行全部更改。但是类中,即只修改基类就可以对继承的类进行全部更改。但是应避免更改基类成员的名称或类型,因为容易导致使用原
36、成应避免更改基类成员的名称或类型,因为容易导致使用原成员的扩充类出现问题。继承最适合于分级相对较少(通常为员的扩充类出现问题。继承最适合于分级相对较少(通常为低于低于6级)的类层次结构。多态性允许用户对一个对象进行级)的类层次结构。多态性允许用户对一个对象进行操作,由对象完成一系列操作,具体实现何种动作以及如何操作,由对象完成一系列操作,具体实现何种动作以及如何实现将由系统负责解释。实现将由系统负责解释。对于一个类、函数或方法,声明中如果加上对于一个类、函数或方法,声明中如果加上absrtact修饰符,修饰符,则为抽象的类、函数或方法。抽象的方法同时也默认为虚方则为抽象的类、函数或方法。抽象的
37、方法同时也默认为虚方法,不提供具体的方法实现代码,必须被派生类重写,而且法,不提供具体的方法实现代码,必须被派生类重写,而且在派生类中不能使用在派生类中不能使用base关键字进行访问。关键字进行访问。同样密封类使用同样密封类使用sealed修饰符,可以防止该类被其他类继承。修饰符,可以防止该类被其他类继承。密封类可以阻止其他开发人员在无意中继承该类,而且密封密封类可以阻止其他开发人员在无意中继承该类,而且密封类可以起到运行时优化的效果,或者用于商业用途。对方法类可以起到运行时优化的效果,或者用于商业用途。对方法使用使用sealed修饰符,即称该方法为一个密封方法。密封方法修饰符,即称该方法为一
38、个密封方法。密封方法可以防止该方法所在类的派生类中对该方法的重写。可以防止该方法所在类的派生类中对该方法的重写。8.9 小结虚方法可以有实现体。调用虚方法,运行时将确定虚方法可以有实现体。调用虚方法,运行时将确定调用对象的实例类型,并调用适当的重写方法。调用对象的实例类型,并调用适当的重写方法。运算符重载允许对系统中已有的运算符赋予新增的运算符重载允许对系统中已有的运算符赋予新增的功能。运算符重载是对方法重载的扩充,运算符重功能。运算符重载是对方法重载的扩充,运算符重载和方法重载非常相似,不同的是方法的名称是由载和方法重载非常相似,不同的是方法的名称是由operator关键字和运算符共同构成,且必须是关键字和运算符共同构成,且必须是public和和static的。的。接口和类很相似,是用接口和类很相似,是用interface关键字定义的。接关键字定义的。接口可由方法、属性、事件、索引器或这口可由方法、属性、事件、索引器或这4种成员类种成员类型的任何组合构成,不能包含字段。接口成员一定型的任何组合构成,不能包含字段。接口成员一定是公共的,接口可以多继承。是公共的,接口可以多继承。8.9 小结