面向对象和组件的C#编程科学出版社李军武汉大学测绘学院“我成长”网站什么是类和对象类(类类型)是一种用户定义的数据类型,类本质上是一种人类认知事物所采用的模型,近似地反映事物内实体之间的关系。其特点:1)类具有自己的数据成员和函数成员,2)它们是对现实世界某一事物的抽象:数据成员—对事物属性的抽象。函数成员—对事物行为的抽象,和对数据的操作。对象就是类的实例,由类的构造函数构造出来的具体实例。class关键字是用于定义类的。下面代码定义了汽车类:classCar{}【例4-1】classCar{发动机;车轮;座椅;//数据,描述了汽车各实体,//以及它们之间的关系移动();加速();刹车();//函数,汽车功能性的操作}•类并不是一个严格的数学表达式,仅仅是一个认知模型。•类的好坏是由人(设计者和用户)在具体系统中来评判的。•如果是一个简单的汽车演示系统,以上的类可能还行,但如果是一个汽车仿真系统,Car类型就远远不够。4.1.1类的声明•以程序员的视角来看类,我们把类定义为一种数据结构,它可包括数据成员、函数成员、内嵌类型。•函数成员包括方法、属性、事件、索引器、重载运算符、构造函数和析构函数;•内嵌类型可以是所有的用户自定义类型,如类、结构等。类声明(定义)的一般形式:修饰符class类名{//下面是类体,用于定义类的成员修饰符类型字段;//数据成员修饰符类名(){…}//构造函数~类名(){…}//析构函数修饰符类型方法(){…}//方法,其它函数成员见后修饰符内嵌类型类型名//内嵌类型可以是类,结构等。{}}访问性修饰符+其它修饰符“访问性修饰符”只能是零个或一个;“其它修饰符”可以是零个,或一个或多个;其它修饰符【例4-2】成员的可见性namespaceHello{classProgram//它是internal的类,无法为它添加private-不能是私有的{privateclassA//它是内嵌类型,可以为private、protected{}//非内嵌类型中的成员可声明为private、protected、protectedinternalprivateintx;//私有的x,只有Program类中成员可以访问它publicintMethod()//公有方法,所有类型和成员可以访问它{returnx;}//访问私有的xinternalinty;//内部y,本程序集的所有类型和成员可以访问它protectedfloatz;//受保护的z,Program类和派生类成员可以访问它protectedinternalintw;//Program类和它的本程序集派生类成员可访问}}4.1.2类的用途int类型有什么用途?当然是用它来定义整数变量。同理,我们用类来定义对象变量,然后赋值操作。下面用例4-1中的汽车类定义对象car1:Carcar1;//与intx语句相似,car1是引用变量//它的初值为null;说明还没有赋值。//下面进行赋值:car1=newCar();//new运算符在堆空间创建一个汽车对象,//=运算符把该对象赋给car1变量//然后我们可以对car1进行操作:car1.移动();car1.刹车();把Car类型理解为汽车工厂也可以把定义变量和赋值写在一个语句中:Carcar1=newCar();//与intx=1;语句相似我们把Car类型理解为汽车工厂,把具体的car1理解为由工厂生成出的产品--汽车对象。这种解释更贴切类与对象的本质:即对象是类的实例。类并非简单地包含了所有对象,类不仅有“描述对象的蓝图”,还有把“蓝图”转变成“产品”的一些工具(如构造函数),以及一些辅助工具(static静态成员)。4.1.3面向对象的思想方法强调类和对象的抽象、封装、继承、多态。1)抽象现实世界中的事物是复杂多样的,不进行抽象,就无法用数据描述事物的信息。信息并不是物质本身:信息来源于本体(物质和精神),但又不是本体自身,它可脱离源本体而寄生于其它物质(媒体—数字式数据),独立存在。•“信息就是信息,不是物质,也不是能量”。2)封装封装就是将抽象出的信息-数据和函数绑定到特定单元(类或结构等),以方便用户使用。•用户利用类的实例(对象)来处理、完成特定的任务。•对象实现了数据和操作的结合,使数据和操作封装于对象的统一体中。但用户不必知道对象的隐私--对象内部的实现细节。•从用户的角度看,封装就是保护对象隐私。封装给用户带来的好处•封装可以提高易用性封装后只暴露最少的信息给用户,对外接口清晰,使用更方便,更具用户友好性。试想,如果用户都要知道机箱内部各种芯片和跳线,那是多么恐怖的事情。•封装可以提高安全性所有对数据的访问和操作都必须通过特定的方法,否则便无法使用,从而达到数据隐藏的目的。用户不必担心误操作破坏了数据。用访问修饰符来实现信息的隐藏【例4-3】用访问修饰符来实现信息的可见性publicclassStudent//公有的学生类{privatestringName;//私有的NamepublicStudent(stringst)//公有的构造函数{Name=st;}publicstringGetName()//公有函数{returnName;}//类中成员可以访问本类的私有成员}classProgram{staticvoidMain(){Students1=newStudent(李明);//定义学生对象变量Console.WriteLine(s1.GetName());//学生对象的操作}}4.2数据成员类中可以包括数据成员、函数成员和内嵌类型。数据成员数据成员包括常量和字段,字段也就是类中定义的变量。在第二章已经讨论了它们。定义或声明字段的方法如下:修饰符类型var_name;修饰符类型var_name=初值;系统默认字段的可访问性是private,我们推荐采用系统默认的—即私有的字段;因为字段描述的是对象的状态,如果不加保护地开放给用户,很容易破坏性地修改它们,造成不可预知的错误。正确的方法是:用函数成员中的属性封装它们,给用户提供安全的访问方法。4.3函数成员函数成员包括方法、属性、索引器、重载运算符、事件、构造函数和析构函数。4.3.1方法方法就是代码的逻辑片断,它可以执行特定的操作。某些代码段常常要在一个程序中运行好多次,我们就可以把这些相同的代码段写成一个单独的功能单元,需要的时候我们就来调用它。我们把这个功能单元叫做方法(或函数)。方法声明的语法为:修饰符返回类型方法名称(类型参数1,…){代码段}1)声明方法下面声明的方法CircleArea是计算圆的面积:publicdoubleCircleArea(doubleradius){return3.14*radius*radius;}publicstaticdoubleCircleArea(doubleradius)修饰符1修饰符2返回类型方法名类型参数2)方法调用classProgram{publicstaticdoubleCircleArea(doubleradius){return3.14*radius*radius;}staticvoidMain(){doubler=CircleArea(9);//1)调用方法Console.WriteLine(r);//2)类.方法Carcar1=newCar();car1.Run();//3)对象.方法}三种调用方法:直接调用,“类名.方法”调用,“对象名.方法”调用。Console是静态类,WriteLine是它的静态方法,故用“类.方法”调用。Run是car1对象的非静态方法,故用“对象.方法”调用。3)方法的形参和实参方法定义中的参数称为形参,在调用方法时使用的参数称为实参。形参与实参可以同名,但不是同一个东西。方法的参数有四种类型,它们是:值参数(无修饰符),引用型参数(ref修饰的数值类型的参数,或非内建引用类型定义的参数),输出型参数(out修饰符),数组型参数(params修饰符)值参数是单向传递:实参的值赋给形参【例4-4】值参数的定义和传递实参。classProgram{staticvoidMain(){inta=6,b=4;change(a,b);//传递实参a,b}staticvoidchange(inta1,intb1)//定义形参{intt=a1;a1=b1;b1=t;}}引用型参数:双向传递classProgram{staticvoidMain(){inta=6,b=4;change(refa,refb);//传递实参a,b}staticvoidchange(refinta1,refintb1)//定义形参{intt=a1;a1=b1;b1=t;}}当利用引用型参数传递时,系统把实参在内存中的地址传递给方法的形参。与值参不同,引用型参数并不开辟新的内存区域,实参与形参共用同一内存单元。数组型参数可以使用个数不定的参数调用方法staticvoidMain(){int[]c={6,4};change(c);}staticvoidchange(paramsint[]d){intt=d[0];d[0]=d[1];d[1]=t;}结果是c[0]=4,c[1]=6。它是双向传递。c和d所指的是同一内存区域,所以当d的值互换时,c的值自然会发生变化。在定义形参前要用params修饰符。在调用时,实参前不要用params修饰符。【例4-9】数组型参数方法调用的多种形式staticvoidMain(){int[]f1={1,2,3,4};Output(f1);//传递数组Output(10,20,30);//直接传递多个数值Output();//无实参}staticvoidOutput(paramsint[]a){}4.3.2属性公有方法为用户提供有用的操作和服务,也可以用来封装字段;但C#为我们提供了一种特殊的方法—属性(attribute),它是专门用来封装字段的。属性声明的语法为:修饰符返回类型属性名称{set{设置代码}get{获取代码}}属性的修饰符与函数成员的修饰符完全一样。表4-1,2下面定义了一个学生类:classStudent{publicstringname;//姓名publicintage;//年龄}字段name和age都是公有的,用户很容易修改它们:Students=newStudent();s.age=-20;用户错误地修改了age,破坏了对象的状态。下面用方法来封装字段。【例4-10】公有方法封装字段classStudent{privatestringname;//姓名privateintage;//年龄publicvoidSetName(stringnamevalue){name=namevalue;}publicstringGetName(){returnname;}publicvoidSetAge(intagevalue){if(agevalue0)Console.WriteLine(“年龄不能小于零”);elseage=agevalue;}publicintGetAge(){returnage;}}字段name和age都是私有的,用户可以安全的访问它们:s.SetAge(-20);//发现-20不对,系统会给出提示但代价是用户必须调用方法而不是简单地引用字段名称。下面用属性替代方法,就可克服这个缺点。【例4-11】公有属性封装字段classStudent{privatestringname;//姓名privateintage;//年龄publicstringName//属性名是把字段名的第一个字母变成大写{set{name=value;}get{returnname;}}publicintAge//定义Age属性{set{if(value0)Console.WriteLine