《第四章 类与类成员》由会员分享,可在线阅读,更多相关《第四章 类与类成员(126页珍藏版)》请在金锄头文库上搜索。
1、第4章类与类成员4.1 类与对象类与对象4.2 字段字段4.3方法方法4.4 构造函数与析构函数构造函数与析构函数4.5 属性属性4.6 索引器索引器4.7 分部类分部类4.1 类和对象类是C#应用程序的基本组成单元,是在编写应用程序时对系统中的相关概念进行抽象并进一步封装的产物。4.1.1 类的声明类的声明访问修饰符class类名/类的成员定义;说明:1.访问修饰符可以省略。2.访问修饰符:用来修饰类和类的成员,它指出了类或类的成员是否能够被其它类的代码合法引用,体现了面向对象中的封装思想。它是定义类的可选部分。C#中有五种访问修饰符,如表41所示:表4-1 访问修饰符访问访问修修饰饰符符访
2、问权访问权限限修修饰饰类类访问权访问权限限修修饰饰类类的成的成员员private不能使用私有的,最低的访问权限,只能在声明它的类中被访问protected不能使用受保护的,只能在声明它的类和子类中被访问internal内部的,只能在所在的程序集中被访问内部的,只能在所在的程序集中被访问protected internal不能使用受保护的或内部的,可以在声明它的类和子类中被访问,也可以在它所在的程序集中被访问。是protected和internal访问权限的“并集” public公有的,访问不受限制公有的,访问不受限制3.类名:是C#中的一个合法标识符。类名最好能够体现类的含义和用途。其第一个字
3、母一般采用大写。4.类的成员定义用一对大括号“”括起来,我们通常称之为类的主体。类的主体并不是一定要包括成员的定义,我们甚至可以声明一个类,不包括任何成员。如:classReader4.1.2 对象类是一个抽象的概念。通常情况下,一个类在声明之后并不能直接使用。我们需要创建这个类的对象(通常也称对象为实例,创建对象的过程称为类的实例化),并且声明对这个对象的引用。声明一个对象引用的形式如下:类名 对象名;一定要注意的是,类是一种引用类型。引用类型变量与值类型变量不同的是:值类型变量中存储的是实际数据,而引用类型变量中存储的是实际数据所在的内存地址。如语句:ReaderTony;内存图41所示:
4、C#中使用关键字new来创建一个对象,其声明格式如下:new类名();可以通过赋值语句将之前声明的对象引用Tony与创建的这个对象建立关联,通常说是让对象引用Tony指向新的对象:Tony=newReader();通过上句代码,对象引用Tony中就存放了新对象的地址,内存图42所示:一般情况下,我们同时声明对象引用和创建对象。格式如下:类名 对象名 =new类名();有了类的对象,就可以访问其内部的成员,C#语言中使用运算符“.”,形式如下:对象名.成员名假设Reader类有两个数据成员:classReaderpublicstringreaderID;/读者证号publicstringread
5、erName;/读者姓名则可以通过以下语句对成员进行访问:ReaderTony=newReader();Tony.readerID=R0001;Tony.readerName=Tony;类与对象的区别和联系:类是一个相对抽象的概念,而对象是一个相对具体的概念;类为生成一个或多个对象提供模板、蓝图。4.1.3 类的成员类的成员包括数据成员和函数成员。其中数据成员用来描述该类或对象的状态,而函数成员用来描述该类或对象所具有的行为。成员成员成员描述成员描述字段类的变量常量与类相关联的常数值方法类可执行的操作属性与读写字段相关的操作索引器能够以数组方式索引类的实例的操作构造函数用于初始化类的实例时执行
6、的操作析构函数用于删除实例之前执行的操作嵌套类型在类中声明的类型运算符类所支持的表达式运算符委托本质也是个类,可以引用一个或多个方法事件可由类生成的通知,对用户提供方法的回调表表4-2 类的成员类的成员字段是类最常见的数据成员。字段用来表示在类中定义的与类或对象相关联的变量成员。根据这些字段是跟实例对象相关还是和类相关,可以分为实例字段和静态字段。另外还有只读字段。4.1.2实例字段实例字段是与类的实例对象相关的字段,在该类的每个实例中都有它的数据副本。改变其中一个实例的某个字段不会影响到其他实例中的相同字段。4.2 字段实例字段的声明格式如下:访问修饰符数据类型 字段名=初始值;当省略了“访
7、问修饰符”,则类的成员的默认访问权限为private;而省略“=初始值”对字段的值进行初始化时,则字段会根据其数据类型的不同而具有相应的默认值。【例4-1】声明一个图书类Book,并为其添加实例字段,并进行字段访问。/41.csenumEBookStatus/枚举类型图书状态AtLibrary,/在馆Borrowed,/借出classBook/图书类publicstringbookID;/实例字段:图书编号publicstringbookName;/实例字段:图书名称publicEBookStatuscurrentStatus;/实例字段:当前状态publicDateTimeborrowDat
8、e;/实例字段:借出日期publicDateTimereturnDate;/实例字段:应还日期class_4_1/启动类staticvoidMain(stringargs)/创建一个图书对象Bookbook=newBook();/在类Book的外部,通过对象名访问其实例字段book.bookID=B0001;book.bookName=C语言;book.currentStatus=EBookStatus.AtLibrary;Console.WriteLine(图书编号:t+book.bookID+n图书名称:t+book.bookName+n当前状态:t+book.currentStatus.
9、ToString();【例4-2】声明一个读者类Reader,并为其添加实例字段。/42.cs/图书类classBook/读者类classReaderpublicstringreaderID;/实例字段:读者证号publicstringreaderName;/实例字段:读者姓名publicintreaderAge;/实例字段:读者年龄publicBookborrowedBook;/实例字段:所借图书/启动类class_4_2staticvoidMain(stringargs)/实例化一个读者对象TonyReaderTony=newReader();/在类Reader的外部,通过对象名访问其实例
10、字段Tony.readerID=S0001;Tony.readerName=Tony;Tony.readerAge=20;Console.WriteLine(读者证号:t+Tony.readerID+n读者姓名:t+Tony.readerName+n读者年龄:t+Tony.readerAge);4.2.2 静态字段与实例字段不同,静态字段表明该字段是属于类本身而不是属于具体某一个实例对象,它被所有的实例共享。类的字段默认情况下都是实例字段,除非在声明字段时使用了static关键字修饰。定义一个静态字段的格式如下:访问修饰符static数据类型 字段名=初始值;在类的外部访问静态字段时,由于静态
11、字段是属于类的数据成员,因此在类的外部直接通过类名来引用,而无需创建类的任何实例: 类名.静态字段名而在类的内部,可以使用上面的访问方法,也可以省略类名而直接使用字段名来访问。但无论在任何地方都不能用类的实例对象来访问静态成员。【例4-3】为读者类Reader声明静态字段读者人数。/读者类classReaderpublicstaticintreaderCount;/静态字段readerCountclass_4_3/启动类staticvoidMain()ReaderTony=newReader();ReaderRose=newReader();/Rose.readerCount=2;/错误的访问
12、方式Reader.readerCount=2;Console.WriteLine(图书馆已注册的读者人数为:+Reader.readerCount.ToString();/用类名Reader直接访问4.2.3 只读字段在声明一个字段时若使用关键字readonly,则表明该字段是一个只读字段。只读字段只能在声明或者在构造函数(具体参见4.5节)中进行赋值,而在其他地方无法修改其值。声明格式如下:访问修饰符readonly数据类型 字段名=初始值;4.3 常量在类中,常量是具有常数值的类的数据成员。在声明常量时就要确定它的值,而之后值不能再被修改。可以用关键字const来声明类的常量数据成员:访问
13、修饰符const数据类型 常量名=初始值;说明:1、在上面的声明语句中,“初始值”是必须在编译阶段就能确定的值,这与之前我们看到的字段的定义不同。2、常量可以是一个简单类型(sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool或 string类型、枚举)的常数,或由其它常量组成的表达式。3、如果是某个对象的引用,则初始值只能是null,而不能指向某一个对象,因为对象只有在运行时才能确定。如以下示例的常量声明:classDateConstancepublicconstinthours=24;/整型常数
14、publicconstintminutes=hours*60; /整型常量表达式publicconstintseconds=minutes*60;/整型常量表达式publicconststringmonday=星期一;/字符串常数/publicconstReaderr=newReader();/错误的常量声明,编译时无法确定其值如果要声明多个相同类型的常量,我们可以在一条语句中同时声明,中间用逗号隔开:classDateConstancepublicconstinthours=24,minutes=hours*60,seconds=minutes*60;由于常量的值在声明之后就不能再改变,因此
15、对于该类的所有实例来说,这个常量的值都是相同的。所以这个常量相当于是类的成员,而不是对象的成员,虽然它在声明时不能使用关键字static,但是访问常量的方式和访问静态字段一样:类名.常量名只读字段与常量字段的区别:1、常量只能在声明时赋值;而只读字段可以在声明时赋值,同时也可以在构造函数中赋值,但以在构造函数中的赋值为最终的值。2、常量在程序编译时值就必须确定,而只读字段的值可以在程序运行时确定。3、常量虽然不能用static关键字修饰,但它默认是类级别的成员;而只读字段可以是类的成员(用static关键字修饰),也可以是对象的成员,它允许类的每个实例对象都有不同的值。4.4 方法方法是类最普
16、通的函数成员。它包含一系列的执行语句,用于实现可以由类或对象执行的计算或操作。一般情况下,方法包括方法声明和方法体。类的其他函数成员本质上也是方法。如之前章节都会涉及到的应用程序的启动方法Main:publicstaticvoidMain(stringargs)/方法体4.4.1方法声明在C#中,没有像C和C+语言中的全局函数,每一个方法都必须和类或结构相关。方法在类或结构中声明,需要指定访问修饰符、返回值类型、方法名称和方法参数。其声明的一般格式如下:访问修饰符返回值类型 方法名称(参数列表)/方法体说明:1、访问修饰符:五种访问修饰符的一种。该项可以省略,默认访问权限为private(私有
17、的)。2、返回值类型:方法执行相应操作后返回的值的数据类型。方法的执行不一定要有返回值,但没有返回值并不意味着该项可以省略。如果方法没有返回值,则返回值类型必须为void。3、方法名称:对方法名的声明推荐具有一定的含义,例如PrintResult的大意就是打印计算结果,这样其他的开发人员也能够读懂该函数的作用,增加了代码的可读性4、参数列表:在方法定义时,参数列表中的参数称为形参(形式参数)。参数列表用来向方法传递参数。参数列表可以省略,表示方法没有参数,但是一对小括号不能省略。如果包含多个参数,则参数之间用逗号分隔。参数列表声明格式如下:数据类型 参数1,数据类型 参数2,.,数据类型 参数
18、n5、方法体可以为空,但一对大括号不能缺少。下面的语句声明了两个方法:publicintAdd(inta,intb)voidPrintResult(inta,intb)第一条语句用public关键字声明了一个公有方法Add。返回值类型为int,接受两个int类型参数a和b;第二条语句声明方法PrintResult时省略了访问修饰符,则该方法默认是私有方法。返回值类型为void,表示没有返回值。同样接受两个int类型的参数a和b。两个方法的方法体都为空,当仍要加上一对大括号。4.4.2方法体方法体是用来描述方法所要执行的语句序列,包含在一对大括号“”中。方法体中可以包含变量的定义、控制语句块以及
19、对其他方法的调用。1.局部变量局部变量在方法体中定义的变量,一般称为局部变量。它用于临时保存方法体中的计算数据。其定义格式如下:数据类型 变量名称 =初始值;局部变量和实例字段都用来保存数据,但它们之间存在较多区别:首先,实例字段在定义时,若不使用初始值对其进行初始化,则系统会将默认值赋值给该字段。而对于局部变量,在引用该局部变量之前,必须显式的对其赋值,否则系统会报错:“使用了未赋值的局部变量”。如下代码所示:classMyClasspublicvoidMethod()inti=1;intj;intk;j=1;Console.WriteLine(i);/正确,已在定义时i赋初值Console
20、.WriteLine(j);/正确,在引用j之前已赋值Console.WriteLine(k);/错误,k未赋值其次,局部变量不能用访问修饰符修饰。如下代码所示:classMyClasspublicinti;/正确,实例字段可以用访问修饰符修饰publicvoidMethod()publicintj=1;/错误,不能用访问修饰符修饰intk=1;/正确,未使用访问修饰符修饰最后,他们的生存周期不同。实例字段的生存周期从实例被创建开始,到实例被销毁时结束。而对于局部变量来说,当局部变量所在的语句块执行到其被定义的语句时开始,到所在的语句块执行完成后结束。2.return语句语句如果方法有返回值,
21、则必须在方法体中使用return语句从方法中返回一个值。return语句的使用方式如下:return表达式 ;说明:1、return语句会将值返回给方法的调用方。另外还会终止当前方法的执行并将控制权返回给调用方,而不管return语句后是否还有其他语句未执行。例如,下面的两个方法使用return语句来返回2个整数之和:classMyClasspublicintAdd(intnum1,intnum2)intSum=0;Sum=num1+num2;returnSum;/将Sum的值以及控制权返回给调用方Console.WriteLine(Sum);/此语句不会被执行当执行完return语句之后,方
22、法就会终止,返回到调用方,而之后的语句都不会被执行。2、return关键字后面是与返回值类型匹配的表达式(表达式值的类型必须与方法声明的返回值类型相同,或是能隐式转换成为返回值类型)。classMyClasspublicDoubleAdd(intnum1,intnum2)intSum=0;Sum=num1+num2;returnSum;以上方法返回值类型为Double类型,但是return语句后的Sum是int类型。由于int类型能够安全转换为Double类型,且不会丢失信息,因此程序能够正常运行。3、方法体中可以有多条return语句,但如果方法有返回值,就必须保证有一条return语句必定
23、会执行一次,例如,对于下面的方法,编译器就会报错:classMyClasspublicintDiv(intnum1,intnum2)if(num2!=0)returnnum1/num2;/该return语句不一定会执行在上面的方法中,只有当num2不等于0时,才能够保证return语句的执行。但该方法必须要有一个返回值,因此对于以上方法编译器会报错:“并非所有的代码路径都返回值”。以上方法可以改为:classMyClasspublicintDiv(intnum1,intnum2)if(num2!=0)returnnum1/num2;elsereturn0;4、在没有返回值的方法体中,方法会按照
24、语句的流程执行完成后自动终止,返回给调用方。但也可以使用return语句来提前停止方法的执行,由于没有返回值,因此省略return关键字后的表达式,直接用分号结束。格式如下:return;【例44】编写一个方法,在控制台输出读者的相关编写一个方法,在控制台输出读者的相关信息。信息。 编写方法之前首先要考虑方法应该写在哪个类中,因为读者的信息都在Reader类中声明,因此该方法应写在Reader类中,作为它的一个函数成员。其次考虑方法的声明:u访问修饰符:由于该方法要供其他类使用,因此可定义成为public。u返回值类型:该方法只是将读者信息在控制台输出即可,不需要返回值,因此该项为void。u
25、方法名称:当然,将方法命名为a或xyz都不会出错,但一般要为方法起个有意义的名称便于理解。在此使用Display。u参数列表:由于要显示的信息为类的实例字段,在类的内部直接用字段名访问。因此不需要其他参数。/4-4.cs/图书类class Book /读者类class Readerpublic string readerID;/实例字段:读者证号public string readerName;/实例字段:读者姓名public int readerAge; /实例字段:读者年龄public Book borrowedBook;/实例字段:所借图书 /显示读者信息方法public void Di
26、splay()Console.WriteLine( 读者证号:t +readerID + n读者姓名:t +readerName + n读者年龄:t +readerAge); /启动类class _4_4 static void Main() Reader Tony = new Reader(); Tony.readerID = S0001; Tony.readerName = Tony; Tony.readerAge = 20; Tony.Display();/方法的调用,在4.4.4中具体讲解 4.4.3 实例方法与静态方法声明方法时使用了static修饰符的是静态方法,没有使用stati
27、c修饰符的方法则是实例方法。之前讲到的方法都是实例方法。同字段类似,实例方法属于实例对象,而静态方法则属于类本身。静态方法除了在声明时与实例方法有区别以外,还有两个区别:一个区别是:在静态方法体中不能引用类的实例成员,只能访问类的静态成员。如下代码所示:classMyClassinti;staticintj;publicstaticvoidMethod()i=1;/错误,静态方法不能引用实例字段j=1;/正确,静态方法可以引用静态字段还另一个区别是在方法的调用方式上。见下节介绍:4.4.4 方法调用除了应用程序的入口方法Main外,其他方法声明之后,并不会自动调用(执行)。要使用这些方法,就需
28、要使用语句去调用。对于实例方法,在方法所在类的外部调用该方法时,由于它是实例对象的成员,因此需要用对象名来引用;而对于静态方法,它属于类本身,因此要用类名来引用。调用的形式分别如下:对象名.实例方法名(参数列表)类名.静态方法名(参数列表)而在类的内部,不管是实例方法还是静态方法,都可以用方法名直接调用:方法名(参数列表)说明:1、在方法调用时,参数列表中的参数称为实参(实际参数)。2、参数匹配:在方法调用时,实参必须与形参相匹配。匹配是指参数的类型(类型相同或能隐式转换)、个数以及顺序。例如有以下方法声明:classMyClasspublicvoidMethod(inti,stringj,b
29、oolk)调用语句:MyClassMC=newMyClass();inta=5;strings=hello;boolb=false;MC.Method(a,s);/错误,参数个数不匹配MC.Method(s,a,b);/类型不匹配,s无法隐式转换到int类型,a也无法隐式转换为string类型MC.Method(a,s,b);/正确,实参与形参相匹配3、如果方法的返回类型是void,则方法调用表达式就没有值。如果方法的返回类型不是void,则调用表达式的值就是方法体内return语句中表达式的值。classMyClasspublicvoidMethodA()publicintMethodB()
30、return1;调用语句:MyClassMC=newMyClass();inta,MC.MethodA();/正确,作为方法调用语句a=MC.MethodA();/错误,MC.MethodA()没有值,无法对变量a进行赋值MC.MethodB();/正确,作为方法调用语句a=MC.MethodB();/正确,调用方法后MC.MethodB()的值为1,然后赋值给变量a4.4.5 参数传递所谓参数传递是指实参把数据传给形参的方式,或者说是方法调用方与方法之间传递信息的一种方式。在C#中,参数既可以通过值传递也可以通过引用传递。这两种传递方式有着本质上的区别.1.值传递值传递在C#中,所有的参数默
31、认都是通过值来传递的,除非特别说明。但由于值类型直接存储其值,而引用类型只是存储其值的地址。这就使按值传递分为两种形式:值类型的按值传递和引用类型的按值传递。值类型的按值传递本质是:实参将值复制一份传给形参,形参接收了实参的值后与实参已不再存在任何联系。在方法中对形参的修改不会影响到对应的实参,这种传递方式又称为单向传递。【例45】值类型的按值传递/45.cs/启动类class_4_5publicstaticvoidMethod(inta)a=100;staticvoidMain(stringargs)intA=1;Console.WriteLine(调用前实参A=+A.ToString();
32、Method(A);Console.WriteLine(调用后实参A=+A.ToString();【例4-6】引用类型的按值传递/46.csclassMyClasspublicintn=5;/启动类class_4_6publicstaticvoidMethod(MyClassmc)mc.n=100; staticvoidMain(stringargs)MyClassMC=newMyClass();MC.n=1;Console.WriteLine(调前后MC.n的值为:+MC.n.ToString();Method(MC);Console.WriteLine(调用后MC.n的值为:+MC.n.T
33、oString();但这里要注意,若修改形参本身,是不会影响到实参的,比如Method方法体修改如下:publicstaticvoidMethod(MyClassmc)mc.n=100;mc=newMyClass();mc.n=200;2.引用传递引用传递除了按值传递参数外,C#还允许按引用的方式来传递参数(注意:“按引用的方式传递参数”和之前讲到的“引用类型按值传递”是不同的)。当使用“引用传递”方式传递参数时,在方法中对形参进行的任意修改都会反应在相应的实参中,这种方式又称双向传递。在C#中,我们可以用ref和out关键字来实现引用传递。ref参数参数在C#中要通过引用方式传递数据,可以使
34、用关键字ref。使用方法是:在定义方法时,在需要按引用传递的参数的类型说明符前加上关键字ref。在调用方法时,在按引用传递的际参之前也要加上关键字ref。另外,使用ref进行引用传递前,实参必须要初始化。【例4-7】通过ref参数进行参数的引用传递。/47.csclassMyClasspublicintn=5;/启动类class_4_7publicstaticvoidMethod(refinta,refMyClassmc)/形参a前用ref修饰,表明该参数按引用传递a=100;/修改值类型形参的值MyClassmc1=newMyClass();mc1.n=200;mc=mc1;/修改引用类型形
35、参本身的值staticvoidMain(stringargs)intx=1;MyClassMC=newMyClass();MC.n=1;Console.WriteLine(调用前实参x的值为:+MC.n.ToString();Console.WriteLine(调用前实参MC.n的值为:+MC.n.ToString();/调用前,参数必须要初始化,并且在调用时,实参前也要用加上关键字refMethod(refx,refMC);Console.WriteLine(调用后实参x的值为:+x.ToString();Console.WriteLine(调用后实参MC.n的值为:+MC.n.ToStri
36、ng();out参数参数out关键字同样会使参数通过引用来传递,这与ref关键字类似。若要使用out参数,方法定义和调用方法都必须显式使用out关键字。【例4-8】通过out参数进行参数的引用传递/48.csclassMyClasspublicintn=5;/启动类class_4_8publicstaticvoidMethod(outinta,outMyClassmc)a=100;/修改值类型形参的值mc=newMyClass();mc.n=100;/修改引用类型形参本身的值staticvoidMain(stringargs)intx;MyClassMC=newMyClass();/调用前,参
37、数可以不用初始化;在调用时,实参前也要用加上关键字outMethod(outx,outMC);Console.WriteLine(方法调用后x=+x.ToString()+MC.n=+MC.n.ToString();关键字ref和out都可以用于参数的引用传递,并且都适合于返回多个值的应用。它们的不同之处在于哪个方法负责初始化参数。如果一个方法的参数被标识为ref,那么调用代码在调用该方法之前必须首先初始化参数。被调用方法则可以任意选择读取该参数、或者为该参数赋值;而如果一个方法的参数被标识为out,那么调用代码在调用该方法之前可以不初始化该参数。实际上,即使在调用前初始化了该参数,在进行传递
38、时,该值也会被忽略。如将上例中Method方法体修改如下则会出现编译错误: publicstaticvoidMethod(outinta)Console.WriteLine(a.ToString();/错误:引用未赋值的参数astaticvoidMain(stringargs)intx=1;/初始化实参xMethod(outx);其次,还必须在方法返回之前为out参数赋值。如将上例中Method方法体修改如下则会出现编译错误:publicstaticvoidMethod(outinta,outMyClassmc)mc=newMyClass();mc.n=100;/修改引用类型形参本身的值由于在
39、方法体内未给参数a赋值,因此在编译代码时会报错:“控制离开当前方法之前必须对out参数a赋值”。由此可以得出结论:out参数不能将值带进方法体,而只能将值带出方法体。3.params参数参数在某些情况下,当我们为方法定义参数时,无法确定参数的个数。params关键字给我们提供了实现此类应用的能力:为方法定义一个接受可变数目参数的方法。parms参数定义如下:方法修饰符返回类型方法名(params类型变量名)/方法体说明:params参数也称为参数数组,当我们要声明参数数组时,要注意以下几个方面:1、在方法声明的参数列表中最多只能出现一个参数数组,并且该参数数组必须位于形参列表的最后。2、参数数
40、组必须是一维数组。3、与参数数组对应的实参可以是任意多个与该数组的元素属于同一类型的变量,也可以是同一类型的数组。4、不允许将params修饰符与ref和out修饰符组合起来使用。【例4-9】声明params参数实现多个整数的累加。/49.csclassMyClasspublicintAdd(paramsInt32nums)intsum=0;for(inti=0;i1&value1&value120)readerAge=value;在上面的代码中,使用了自动属性ReaderName。对于该属性,编译器将为它自动创建一个私有的匿名后备字段,该字段只能通过属性的get和set访问器进行访问。对于读
41、者年龄ReaderAge不能使用自动属性,因为在ReaderAge中的set访问器中,并不是简单的将新值通过value赋值给私有字段,而是需要进行稍微复杂的逻辑判断。4.6.3 只读与只写属性在类中的有些字段只能读取它的值,而不能修改;还有些字段可能只能修改而不能进行读取。因此我们可以在属性中只包含get访问器或只包含set访问器,从而实现只读属性和只写属性。自动属性必须同时声明get和set访问器。若要创建只读自动实现属性,只能通过在set访问器前加private关键字修饰来实现publicstringReaderNameget;privateset; /只读自动属性classReaderp
42、rivateDateTimebornDate;/私有字段:出生日期publicintAge /只读属性:年龄。根据出生日期计算得到读者年龄getreturnDateTime.Today.YearbornDate.Year;classCircleconstdoublePI=3.14;/常量privatedoubler;/半径rpublicdoubleArea/只读属性,计算得到圆的面积getreturnPI*r*r;4.7 索引器在C#中,还支持访问器接受一个或者多个参数的属性索引器索引器也可以包含一个get访问器方法和一个set访问器方法,但是声明的头部有所不同:1、名称要用this关键字。2
43、、包含参数,并且参数用一对方括号“”括起来。索引器可以用类似访问数组的语法来访问它,格式如下:对象名实参;【例4-16】声明一个读者列表类,并为其添加索引器,用来访问器中的读者对象。/416.cs/读者类classReaderpublicstringReaderIDget;set;/常规属性:读者证号publicstringReaderNameget;set;/常规属性:读者姓名publicReader(stringid,stringname) /构造函数ReaderID=id;ReaderName=name;/读者列表类classReaderListprivateInt32readerCou
44、nt;/读者的人数privateReaderreaderArray;/用来存放读者对象的数组publicReaderList(Int32readerNum)/构造函数readerCount=readerNum;/得到读者数量readerArray=newReaderreaderCount;/初始化内部数组publicReaderthisInt32i/索引器声明getreturnreaderArrayi;/返回第i个读者信息setreaderArrayi=value;/修改第i个读者信息class_4_16/启动类staticvoidMain(stringargs)/用带参构造函数初始化读者列表
45、类ReaderListrList=newReaderList(3);/声明3个读者实例Readerr0=newReader(R0001,Tony);Readerr1=newReader(R0002,Rose);Readerr2=newReader(R0003,Jack);/调用索引器的set访问器方法加入3位读者信息rList0=r0;rList1=r1;rList2=r2;Console.WriteLine(读者列表中有3位读者:);for(Int32i=0;i3;i+)/调用索引器的get访问器方法读取3位读者信息Console.WriteLine(读者证号:+rListi.ReaderI
46、D+读者姓名:+rListi.ReaderName);索引器都必须至少有一个参数,也可以有多个。这些参数(以及返回值)可以为任何类型。并且在一个类中,可以定义多个重载的索引器,只要它们的参数个数或参数类型不同即可.如publicReaderthisstringreaderID/按照读者证号来索引读者对象getfor(Int32i=0;ireaderCount;i+)if(readerArrayi.ReaderID=readerID)returnreaderArrayi;returnnull;4.8命名空间4.8.1为什么要使用命名空间在设计类的过程中,不可避免类的名称会出现相同的情况。然而在一
47、个应用程序中出现同名的类(若不使用命名空间)是不允许的,编译器会报错:“命名空间“xxx”已经包含了Tony的定义”,如下代码所示:classTonyclassTony命名空间可以把类进行分组,并给它们一个名称,称为命名空间名称。命名空间名称应该体现命名空间的内容并和其他命名空间名称相区别。可见,命名空间起到了划分类、区分类的作用。避免类同名是我们使用命名空间的最重要的原因。同时,如果我们使用命名空间对众多的类进行合理的划分,则可以极大的提高代码的可读性和维护性,即使我们的系统中没有同名类。4.8.2 创建和使用命名空间在.NET类库中对定义命名空间使用关键字namespace,语法格式如下所
48、示。namespace命名空间名称/类的声明同样,命名空间成员也通过“.”号访问:命名空间名称.类名。n示例代码如下所示。namespaceClassRoom1/命名空间:一班publicclassTonynamespaceClassRoom2/命名空间:二班publicclassTony命名空间还可以嵌套,可以创建两层或多层命名空间。这样可以使众多的类有合理的层次嵌套的命名空间是指在一个命名空间中嵌套一个子命名空间,从而进一步的划分命名空间。嵌套的命名空间有两种声明方式:1.原文嵌套:把命名空间的声明放在一个封装的命名空间声明体内部。namespace命名空间名称namespace子命名空间
49、名称/类的声明2.分离嵌套:在声明时通过使用完全限定名称给命名空间命名。namespace命名空间名称/类的声明namespace命名空间名称.子命名空间名称/类的声明以上两种做法都能实现在“命名空间名称”中嵌套“子命名空间名称”。这两种做法是等价的,只是表现形式不同,如下代码所示:namespaceGrade1/命名空间:大一namespaceClassRoom1/命名空间:一班publicclassTonynamespaceGrade1.ClassRoom1/与上面的声明等价publicclassTony4.8.3 using指令使用完全限定名引用命名空间会使代码变的冗长,因为命名空间的嵌
50、套结构可能会有很多级。为了简化代码,我们可以使用using指令。正如我们在C#代码文件顶端看到的指令:usingSystem。using指令的语法格式如下:using命名空间名称;using指令必须放在代码文件的顶端,在任何类型声明之前。比如在之前的例子中我们通过using指令加入了对命名空间Grade1.ClassRoom1的引用:usingGrade1.ClassRoom1;在我们调用该命名空间中的类Tony时,就可以省略命名空间直接调用:Tonyt=newTony();4.9分部类通常情况下,我们会把一个类的源代码放在一个单独的类文件(.cs文件)中。但若一个类的代码较多,内部的逻辑功能
51、也比较复杂的话,我们可以使用partial修饰符将类的声明分割成多个部分放在不同的类文件中。比如有以下代码:/类文件PartialA.cspartialclassPartialClasspublicintfieldA;publicvoidMethodA()/类文件PartialB.cspartialclassPartialClasspublicintfieldB;publicvoidMethodB()以上的两段代码分别写在两个cs文件中,它们等同于在单个cs文件中的以下声明:classPartialClasspublicintfieldA;publicvoidMethodA()publicintfieldB;publicvoidMethodB()在声明分部类时需要注意:1.将一个类分割成多个分部类时,声明的分部类类名一定要相同。2.分割后的分部类的声明可以分别放在多个cs文件中,也可以放在同一个cs文件中。3.分部类的声明中,彼此之间不能出现重复的成员定义,因为它们最终将会合成一个类。如以下代码:partialclassPartialClasspublicintfieldA;partialclassPartialClasspublicintfieldA;/提示错误:已经包含“fieldA”的定义