《5 面向对象编程进阶.ppt》由会员分享,可在线阅读,更多相关《5 面向对象编程进阶.ppt(60页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、第4章 面向对象编程进阶n教学要求n掌握类的继承与多态n掌握操作符的重载n基本掌握类型转换n理解结构与接口的含义n了解集合与索引器n理解异常处理n理解并掌握委托与事件n了解预处理命令、组件与程序集n教学重点n类的继承与多态n操作符的重载n委托与事件n教学难点n多态与接口的含义 n委托与事件n学时分配:8+8教学内容n类的继承与多态n操作符的重载n基掌握类型转换n结构与接口n集合与索引器n异常处理n委托与事件n预处理命令n组件与程序集类的继承与多态类的继承与多态 当今的面向对象编程语言都提供了继承和多态的功能,C#作为一种面向对象的高级编程语言也具有这样的特点。继承是面向对象语言的基本特征,是实
2、现代码复用的手段。继承使得在原有的类基础之上,对原有的程序进行扩展,从而提高程序开发的速度,实现代码的复用。同一种方法作用于不同对象可以产生不同的结果,这就是多态性。它是在基类中使用虚方法,在其派生类中使用重载实现的。继承n继承是自动地共享类、子类和对象中的方法和数据的机制,类与类之间通过继承关系组成的层次结构。n以原有的类为基础产生新类,即从原有的类派生出新类。在这个过程中原有的类称为基类(超类或父类),新类称为派生类(子类)。n汽车类派生出卡车类。在此过程中,我们称汽车类为基类,卡车类为汽车类的派生类。n面向对象程序设计提供了类的继承机制,它描述类与类之间的IS-A关系,该机制自动的为派生
3、类提供了其基类的操作和属性。n国光苹果是一种苹果,苹果是一种水果。n猫是一种猫科动物,猫科动物是一种哺乳动物。n一个类可以有多个子类,也可以有多个父类。C#限制一个类至多只能直接继承一个类(单一继承,树结构),但对接口可以直接继承多个类(多重继承,网状结构)n继承的作用:扩充现有的类来构造新类(子类);组合现有的类来构造新类(父类,通过接口来实现);类型替换原则:如果B继承A,则所有要求对象为A类型的地方都可以用B类型的对象来替换;提高了代码的重用率继承的语法类访问修饰符class 子类名:父类名 /成员变量,同一般类的定义 /成员函数,同一般类的定义 ;n类成员的访问性:主要是指外部对它的访
4、问权限npublic:成员可以从任何代码访问。nprotected:成员只能从派生类的内部访问。ninternal:成员只能从同一程序集的内部访问。nprotected internal:成员只能从同一程序集内的派生类访问。nprivate:成员只能在当前类的内部访问。n继承成员的访问控制规则n由父类成员的访问控制方式和继承访问控制方式共同决定nprivate+public(internal)=不可访问npubic+public(internal)=public(internal)nprotected+public(internal)=protectedninternal+public(int
5、ernal)=internalnprotected internal+public(internal)=protected internal继承要遵循的规则n继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员同样也继承了A中的成员n派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义n构造函数和析构函数不能被继承,除此以外的其他成员不论对它们定义了怎样的访问方式,都能被继承,基类中成员的访问方式只能决定派生类能否访问它们。n派生类如果定义了与继承而来的成员同名的的新成员,就可以覆盖(重写)已继承的成员,但这并不意味着派生类删除了这些成员
6、,只是不能再访问这些成员n类可以定义虚方法、虚属性,以及虚索引指示器,派生类能够重载这些成员,从而实现类的多态性n在执行派生类的构造函数代码之前,基类的构造函数被调用了,即基类在派生类初始化之前自动进行初始化。这意味着,构造函数代码实际上是自上向下执行的,而大多数派生类代码则是最后执行。使用继承n继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。使用继承而产生的类被称为派生类或者子类,而被继承的类则称为基类、超类或父类。客观世界中的许多事物之间往往都是具有相同的特征,具有继承的特点。继承举例例:例:类的继承。using System;/定义基
7、类Shapepublic class Shape protected string Color;public Shape();public Shape(string Color)this.Color=Color;public string GetColor()return Color;/定义Circle类,从Shape类中派生public class Circle:Shape private double Radius;public Circle(string Color,double Radius)this.Color=Color;this.Radius=Radius;public doub
8、le GetArea()return System.Math.PI*Radius*Radius;继承举例/派生类Rectangular,从Shape类中派生public class Rectangular:Shapeprotected double Length,Width;public Rectangular()Length=Width=0;public Rectangular(string Color,double Length,double Width)this.Color=Color;this.Length=Length;this.Width=Width;public double A
9、reaIs()return Length*Width;public double PerimeterIs()/周长return(2*(Length+Width);/派生类Square,从 Rectangular类中派生public class Square:Rectangularpublic Square(string Color,double Side)this.Color=Color;this.Length=this.Width=Side;继承举例public class TestInheritancepublic static void Main()Circle Cir=new Circ
10、le(orange,3.0);Console.WriteLine(Circle color is 0,Circle area is 1,Cir.GetColor(),Cir.GetArea();Rectangular Rect=new Rectangular(red,13.0,2.0);Console.WriteLine(Rectangualr color is 0,Rectangualr area is 1,Rectangular perimeter is 2,Rect.GetColor(),Rect.AreaIs(),Rect.PerimeterIs();Square Squ=new Sq
11、uare(green,5.0);Console.WriteLine(Square color is 0,Square Area is 1,Square perimeter is 2,Squ.GetColor(),Squ.AreaIs(),Squ.PerimeterIs();继承base关键字关键字n可以在派生类的构造函数定义语句之后加上base(形参),表示当对派生类对象实例化时,调用的是基类的对应构造函数;若无形参,则调用基类的无参构造函数。public class Square:Rectangular public Square(string Color,double Side):base
12、(Color,Side,Side);n调用基类的方法:base.基类方法名继承中的构造函数与析构函数继承中的构造函数与析构函数n在前面讨论过派生类的构造函数会隐式调用父类的无参构造函数。那么,如果父类也是派生于其它类,会是什么样的情形呢?C#的执行顺序是这样的:根据层次链找到最顶层的基类,先调用基类的构造函数,再依次调用各级派生类的构造函数。而析构函数的次序正好相反。继承与System.Object类类nC#所有类都派生于System.Object类。在定义类时如果没有指定派生于哪一个类,系统就默认其派生于Object类。public class Shape 等同于public class S
13、hape:System.Object nSystem.Object类常见的公有方法是nEquals:如果两个对象具有相同值时,方法将返回true。nGetHashCode:方法返回对象的值的散列码。nToString:通过在派生类中重写该方法,返回一个表示对象状态的字符串。多态n多态也是面向对象语言的基本特征之一,是指在程序执行之前无法根据函数名和参数确定调用哪一个操作,而是程序执行过程中,根据实际运行情况动态确定,从而带来编程高度的灵活性。实现多态的方法是使用虚方法。n虚方法的重载:在类的方法前加上关键字virtual,则该方法被称为虚方法。通过对虚方法的重载,实现在程序运行过程中确定调用的
14、方法。需要注意的是这里所讲的重载与第3章所讲的通过参数类型与参数个数的不同实现的重载含义是不同的。虚方法举例例:例:虚方法的重载。using System;class Apublic void F()Console.WriteLine(A.F);public virtual void G()Console.WriteLine(“A.G”);/虚方法class B:Anew public void F()Console.WriteLine(B.F);public override void G()Console.WriteLine(B.G);class Teststatic void Main(
15、)B b=new B();A a=b;a.F();/a.Fb.F();/b.Fa.G();/b.Gb.G();/b.G多态n抽象类与抽象方法抽象类与抽象方法:抽象类是一种特殊的基类,并不与具体的事物联系。抽象类的定义使用关键字abstract。n抽象类是不能被实例化,它只能作为其它类的基类。将Shape类定义为抽象类:public abstract class Shape n在抽象类中也可以使用关键字abstract定义抽象方法,要求所有的派生非抽象类都要重载实现抽象方法。引入抽象方法的原因在于抽象类本身是一种抽象的概念,有的方法并不要具体的实现,而是留下来让派生类来重载实现。Shape类中G
16、etArea方法本身没什么具体的意义,而只有到了派生类Circle类和Rectangular才可以计算具体的面积。n抽象方法写法:public abstract double GetArea();则派生类重载实现为:public override double GetArea()抽象类举例【例例4.6】抽象类和抽象方法的实现using System;/定义基类Shapepublic abstract class Shape protected string Color;public Shape();public Shape(string Color)this.Color=Color;publi
17、c string GetColor()return Color;public abstract double GetArea();抽象类举例/定义Circle类,从Shape类中派生public class Circle:Shape private double Radius;public Circle(string Color,double Radius)this.Color=Color;this.Radius=Radius;public override double GetArea()return System.Math.PI*Radius*Radius;public class Tes
18、tInheritancepublic static void Main()Circle Cir=new Circle(orange,3.0);Console.WriteLine(Circle color is 0,Circle area is 1,Cir.GetColor(),Cir.GetArea();操作符重载n一元操作符有一个参数,二元操作符有二个参数。重载操作符开始必须以public static修饰。可以重载的操作符包括:n一元操作符:+-!+-true false n二元操作符:+-*/%&|=!=和=和=操作符重载举例例:例:操作符重载的实现using System;class
19、Complexdouble r,v;/r+v ipublic Complex(double r,double v)this.r=r;this.v=v;/二元操作符”+”重载public static Complex operator+(Complex a,Complex b)return new Complex(a.r+b.r,a.v+b.v);/一元操作符”-”重载public static Complex operator-(Complex a)return new Complex(-a.r,-a.v);/一元操作符”+”重载public static Complex operator+(
20、Complex a)double r=a.r+1;double v=a.v+1;return new Complex(r,v);public void Print()Console.Write(r+v+in);操作符重载举例class Testpublic static void Main()Complex a=new Complex(3,4);Complex b=new Complex(5,6);Complex c=-a;c.Print();Complex d=a+b;d.Print();a.Print();Complex e=a+;/先赋值后+a.Print();e.Print();Com
21、plex f=+a;/先+后赋值a.Print();f.Print();结构nC#中的结构除了包含有数据成员,还有构造函数、方法、属性、事件、索引等成员,结构也可以实现多个接口。结构与类很类似,但也有很多的区别。首先结构是值类型,而类是引用类型。结构举例例:值类型的结构。using System;struct MyStruct/定义结构MyStruct public int x;/定义字段x,ypublic int y;public MyStruct(int i,int j)/定义构造函数x=i;y=j;public void Sum()/定义方法int sum=x+y;Console.Wri
22、teLine(The sum is 0,sum);class Class1 static void Main()MyStruct s1=new MyStruct(1,2);MyStruct s2=s1;s1.x=2;s1.Sum();s2.Sum();运行结果如下:The sum is 4The sum is 3结构与类的区别接口n接口是用来定义一种程序的协定。接口好比一种模版,这种模版定义了实现接口的对象必须实现的方法,其目的就是让这些方法可以作为接口实例被引用。接口的定义如:public interface IPartAvoid SetDataA(string dataA);n接口使用关键
23、字interface定义,接口可以使用的修饰符包括new,public,protected,internal,private等。接口的命名通常是以I开头,如IPartA,IPartB。接口的成员可以是方法、属性、索引器和事件,但不可以有任何的成员变量,也不能在接口中实现接口成员。接口不能被实例化。接口的成员默认是公共的,因此不允许成员加上修饰符。n接口只能定义方法,实现要由类或者结构来完成。接口举例例例:接口演示。using System;public interface IPartA/定义接口IPartAvoid SetDataA(string dataA);public interface
24、 IPartB:IPartA/定义接口IPartB,继承IPartAvoid SetDataB(string dataB);public class SharedClass:IPartB/定义类SharedClass,继承接口IPartBprivate string DataA;private string DataB;public void SetDataA(string dataA)/实现接口IPartA的方法SetDataADataA=dataA;Console.WriteLine(0,DataA);public void SetDataB(string dataB)/实现接口IPart
25、B的方法SetDataBDataB=dataB;Console.WriteLine(0,DataB);class teststatic void Main()SharedClass a=new SharedClass();a.SetDataA(interface IPartA);a.SetDataB(interface IPartB);运行结果如下:interface IPartAinterface IPartB接口允许多重继承n接口允许多重继承interface ID:IA,IB,IC n类可以同时有一个基类和零个以上的接口,并要将基类写在前面:class ClassB:ClassA,IA,
26、IB 集合nC#为用户提供一种称为集合的新类型。集合类似于数组,是一组组合在一起的类型化的对象,可以通过遍历来访问数组中的每个元素。.NET提供实现集合的接口,包括IEnumerable,ICollection,Ilist等,只需继承实现集合接口。另外也可以直接使用.NET已经定义的一些集合类,包括 Array,ArrayList,Queue,Stack,BitArrary,Hashtable等。集合类是由命名空间System.Collections提供。自定义集合n自定义集合是指实现System.Collections提供的集合接口的集合。n下面以IEnumerable集合接口为例自定义集合
27、。IEnumerable接口定义:public interface IEnumerable Ienumerator GetEnumerator();实现IEnumerable的同时也要实现IEnumerator接口。IEnumerator接口为:public interface IEnumeratorobject Current get();bool MoveNext();void Reset();自定义集合举例例例:IEnumerable自定义集合。using System;using System.Collections;/集合类的命名空间namespace CustomCollectio
28、nclass MyClass/定义集合中的元素MyClass类public string Name;public int Age;public MyClass(string name,int age)/带参构造器 this.Name=name;this.Age=age;public class Iterator:IEnumerator,IEnumerable/实现接口Ienumerator和IEnumerable类Iteratorprivate MyClass ClassArray;/初如化MyClass 类型的集合 int Cnt;public Iterator()ClassArray=ne
29、w MyClass4;/使用带参构造器赋值 ClassArray0=new MyClass(Kith,23);ClassArray1=new MyClass(Smith,30);ClassArray2=new MyClass(Geo,19);ClassArray3=new MyClass(Greg,14);Cnt=-1;自定义集合举例 public void Reset()/实现IEnumerator的Reset()方法 Cnt=-1;/指向第一个元素之前,Cnt为-1,遍历是从0开始public bool MoveNext()/实现IEnumerator的MoveNext()方法return
30、(+Cnt 0)string str =(string)s.Pop();/弹出元素,后进先出 Console.WriteLine(Poping 0,str);Console.WriteLine(Done);索引器n使用索引器的目的是为了能够像数组一样访问类中的数组型的对象。通过对对象元素的下n标的索引,就可以访问指定的对象。索引器类似于属性,也是使用 get 关键字和 set 关键n字定义了对被索引元素的读写权限,它们之间不同的是索引器有索引参数。索引器举例例例:索引器示例。using System;class MyClassprivate string data=new string5;pu
31、blic string this int index/索引器定义,根据下标访问dataget return dataindex;set dataindex=value;class MyClientpublic static void Main()MyClass mc=new MyClass();mc0=Rajesh;/调用索引器set赋值mc1=A3-126;mc2=Snehadara;mc3=Irla;mc4=Mumbai;Console.WriteLine(0,1,2,3,4,mc0,mc1,mc2,mc3,mc4);/调用索引器get读出运行结果如下:Rajesh,A3-126,Sneh
32、adara,Irla,Mumbai异常处理 n程序经常会有这样或那样的错误,比如参数格式有误,或者是变量超出范围等。因此,编程语言一般要有异常处理。n异常异常:Win32 API程序出现错误时,没有使用异常处理机制作处理,大多数的Win32 API都是通过返回bool值,用false来表示函数调用出了问题。COM用HRESULT来描述程序的运行情况。当HRESULT的高位为1,则表示一个假设被违反,HRESULT的其它位则可以帮助判断问题的原因。异常bool RemoveFromAccount(string AccountId,decimal Amount)bool Exists=Verify
33、AccountId(AccountId);if(Exists)bool CanWithdraw=CheckAvailability(AccountId,Amount);if(CanWithdraw)return Withdraw(AccountId,Amount);return false;nC#异常返回不再是简单的true或false,而是异常传播,每一个异常都包含一个描述字符串,通过字符串就可以知道程序在哪出了问题。通过这样的异常处理,使得程序更加容易阅读,维护和升级。异常类n当代码出现诸如被除数为零、分配空间失败等错误时,就会自动创建异常对象,它们大多是C#异常类的实例。System.E
34、xception类是异常类的基类,一般不要直接使用System.Exception,它没有反映具体的异常信息,而是一般用它的派生类nSystem.Exception提供了一些了解异常信息的属性常用异常类异常处理ntry语句语句:将有可能发生异常的代码作为try语句块,处理try语句中出现的异常代码放到catch语句块。finally语句则是不管try语句中有没有异常发生最后都要执行其中的程序块。ncatch块中包含的是出现异常时要执行的代码。一个try后面可以有零个以上的catch块。如果try块中没有异常,则catch块中代码不会被执行。catch后面括号放入希望捕获的异常,如例4.20中D
35、ivideByZeroException异常。当两个catch语句的异常类有派生关系的时候,要将包括派生的异常类catch语句放到前面,包括基类的catch语句放置到后面。nfinally块包含了一定要执行的代码,通常是一些资源释放,关闭文件等代码。异常处理举例例例:多catch语句示例。using System;class Withfinallypublic static void Main()try int x=5;int y=0;int z=x/y;/异常,除数为0Console.WriteLine(z);catch(FormatException)Console.WriteLine(E
36、rror occurred,FormatException);catch(DivideByZeroException)Console.WriteLine(Error occurred,DivideByZeroException);catch(Exception)Console.WriteLine(Error occurred,Exception);finallyConsole.WriteLine(Thank you for using the program);委托nC#的委托相当于在C/C+中的函数指针。函数指针用指针获取一个函数的入口地址,实现对函数的操作。委托与C/C+中的函数指针不同在
37、于委托是面向对象的,是引用类型,因此对委托的使用要先定义后实例化,最后才调用。委托使用步骤如下:n定义委托使用关键字delegate,定义一个委托:delegate int SomeDelegate(int nID,string sName);n实例化:SomeDelegate d1=new SomeDelegate(wr.InstanceMethod);委托的实例化中的参数既可以是非静态方法,也可以是静态方法n方法定义public int InstanceMethod(int nID,string sName)n调用:d1(5,aaa);委托举例using System;class Simp
38、leClasspublic class WorkerClass/委托引用的非静态方法public int InstanceMethod(int nID,string sName)int retval=0;retval=nID*sName.Length;Console.WriteLine(调用InstanceMethod方法);return retval;/委托引用的静态方法static public int StaticMethod(int nID,string sName)int retval=0;retval=nID*sName.Length;Console.WriteLine(调用Sta
39、ticMethod方法);return retval;/定义委托,参数与上面两个方法相同public delegate int SomeDelegate(int nID,string sName);委托举例static void Main(string args)/调用实例方法(非静态方法)WorkerClass wr=new WorkerClass();SomeDelegate d1=new SomeDelegate(wr.InstanceMethod);Console.WriteLine(Invoking delegate InstanceMethod,return=0,d1(5,aaa)
40、;/调用静态方法SomeDelegate d2=new SomeDelegate(WorkerClass.StaticMethod);Console.WriteLine(Invoking delegate StaticMethod,return=0,d2(5,aaa);事件n事件作为C#中的一种类型,为类和类的实例定义发出通知的能力,从而将事件和可执行代码捆绑在了一起。事件最常见的用途是用于窗体编程,当发生像点击按钮、移动鼠标等事件时,相应的程序将收到通知,再执行代码。nC#事件是按“发布-预订”的方式工作。先在一个类中公布事件,然后就可以在任意数量的类中对事件预订。事件nC#事件机制是基于委
41、托实现的,因此要首先定义一个委托EventHandler:npublic delegate void EventHandler(object from,myEventArgs e)nSystem.EventArgs是包含事件数据的类的基类,在代码中可直接使用EventArgs类。myEventArgs类派生于EventArgs类,实现自定义事件数据的功能。这里from表示发生事件的对象。n定义事件格式为:event 事件的委托名 事件名n如事件TextOut定义:public event EventHandler TextOut;n事件的激活一般写成:if(TextOut!=null)Text
42、Out(this,new EventArgs();n订阅事件:nevsrc.TextOut+=new EventSource.EventHandler(CatchEvent);n取消订阅:nevsrc.TextOut-=new EventSource.EventHandler(CatchEvent);事件举例using System;/定义事件包含数据public class MyEventArgs:EventArgsprivate string StrText;public MyEventArgs(string StrText)this.StrText=StrText;public stri
43、ng GetStrTextget return StrText;class EventSource/发布事件的类MyEventArgs EvArgs=new MyEventArgs(触发事件);public delegate void EventHandler(object from,MyEventArgs e);/定义委托public event EventHandler TextOut;/定义事件 public void TriggerEvent()/激活事件的方法if(TextOut!=null)TextOut(this,EvArgs);事件举例class TestApp/订阅事件的类p
44、ublic static void Main()EventSource evsrc=new EventSource();evsrc.TextOut+=new EventSource.EventHandler(CatchEvent);/订阅事件evsrc.TriggerEvent();/触发事件Console.WriteLine(-);evsrc.TextOut-=new EventSource.EventHandler(CatchEvent);evsrc.TriggerEvent();/事件订阅已取消,什么也不执行TestApp theApp=new TestApp();evsrc.TextO
45、ut+=new EventSource.EventHandler(theApp.InstanceCatch);evsrc.TriggerEvent();/处理事件的静态方法public static void CatchEvent(object from,MyEventArgs e)Console.WriteLine(CathcEvent:0,e.GetStrText);/处理事件的方法public void InstanceCatch(object from,MyEventArgs e)Console.WriteLine(InstanceCatch:0,e.GetStrText);预处理命令
46、n预处理就是在编译程序之前由预处理器对源程序进行一些加工处理工作。C#的预处理类似于C+预处理,但是,与 c+不同的是C#没有独立的预处理器,并不是编译器开始编译代码之前的一个单独的处理步骤,它是作为词法分析的一部分来执行的。预处理指令都是以#开头,并且一行只能有一个预处理指令,指令结尾不需用分号表示语句的结束。预处理指令包括#define,#undef、#if、#elif、#else、#endif、#warning、#error、#region、#endregion和#line指令等。#define、#undef指令n#define和#undef指令是用于定义符号和取消符号定义的预处理指令。
47、#define DEBUG#undef DEBUGn这里定义和取消的符号是DEBUB。如果定义的符号已经存在,#define则不起作用,同样道理,如果符号不存在,#undef也就没有任何作用。#define和#undef指令必须放于源程序的代码之前。例:using System;#define DEBUGn这样的写法是错误的,而是要将“using System;”语句放到#define指令的后面。#define指令本身并没有什么用,但和其它的预处理命令结合使用,特别是#if,功能将非常强大。#if、#elif、#else、#endif指令n#if、#elif、#else、#endif指令被用作
48、条件编译,它们类似于if/else结构。#if和#elif后的 标识符表达式可以使用运算符与(&)、或(|)、非(!)。它们在程序中结构如下所示:n一条#if 语句(必须有)n零或多条#elif 语句n零或一条#else 语句n一条#endif 语句(必须有)预处理举例。#define DEBUGusing System;public class MyClass public static void Main()#if(DEBUG)Console.WriteLine(DEBUG is defined);#else Console.WriteLine(DEBUG is not defined);
49、#endif运行结果如下:DEBUG is definedn当执行到#if语句,首先检查DEBUG是否已经定义,如果符号已经定义,就编译#if块中的代码,否则编译#else块中的代码。n#elif指令相当于“else if”。#warning、#error指令 n#warning、#error指令用于产生警告或错误。当编译器遇到#warning指令时,会显示#waring后面的文本,编译还会继续进行。当遇到#error指令时,会显示后面的文本,并终止编译退出。这两条指令可以用于检查#define是否定义了什么不正确的符号。#define DEBUG#define RELEASE#if DEBU
50、G#error 你定义了DEBUG#endif#if DEBUG&RELEASE#warning 你定义了DEBUG和RELEASE#endif#region、#endregion指令n#region、#endregion指令用于标识代码块。#region OutVar methodpublic static void OutVar()int var=5;Console.WriteLine(var equals:0,var);#endregionn这两个指令不会影响编译,而是在一些编辑器,如Visual Studio.NET,可以将其中的代码折叠和展开,便于浏览和编辑。#line指令n#lin