第3章对象和类型.docx

上传人:b****7 文档编号:23958768 上传时间:2023-05-22 格式:DOCX 页数:35 大小:37.85KB
下载 相关 举报
第3章对象和类型.docx_第1页
第1页 / 共35页
第3章对象和类型.docx_第2页
第2页 / 共35页
第3章对象和类型.docx_第3页
第3页 / 共35页
第3章对象和类型.docx_第4页
第4页 / 共35页
第3章对象和类型.docx_第5页
第5页 / 共35页
点击查看更多>>
下载资源
资源描述

第3章对象和类型.docx

《第3章对象和类型.docx》由会员分享,可在线阅读,更多相关《第3章对象和类型.docx(35页珍藏版)》请在冰豆网上搜索。

第3章对象和类型.docx

第3章对象和类型

第3章对象和类型

到目前为止,我们介绍了组成C#语言的主要内容,包括变量、数据类型和程序流语句,并简要介绍了一个只包含Main()方法的完整小例子。

但还没有介绍如何把这些内容组合在一起,构成一个完整的程序,其关键就在于对类的处理。

这就是本章的主题。

本章的主要内容如下:

● 类和结构的区别

● 类成员

● 按值和引用传送参数

● 方法重载

● 构造函数和静态构造函数

● 只读字段

● 部分类

● 静态类

● Object类,其他类型都从该类派生而来

第4章将介绍继承以及与继承相关的特性。

提示:

本章将讨论与类相关的基本语法,但假定您已经熟悉了使用类的基本原则,例如,知道构造函数和属性的含义,因此我们只是大致论述如何把这些原则应用于C#代码。

本章介绍的这些概念不一定得到了大多数面向对象语言的支持。

例如对象构造函数是您熟悉的、使用广泛的一个概念,但静态构造函数就是C#的新增内容,所以我们将解释静态构造函数的工作原理。

3.1 类和结构

类和结构实际上都是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法。

类定义了每个类对象(称为实例)可以包含什么数据和功能。

例如,如果一个类表示一个顾客,就可以定义字段CustomerID、FirstName、LastName和Address,以包含该顾客的信息。

还可以定义处理存储在这些字段中的数据的功能。

接着,就可以实例化这个类的对象,以表示某个顾客,并为这个实例设置这些字段,使用其功能。

classPhoneCustomer

{

publicconststringDayOfSendingBill="Monday";

publicintCustomerID;

publicstringFirstName;

publicstringLastName;

}

结构与类的区别是它们在内存中的存储方式(类是存储在堆(heap)上的引用类型,而结构是存储在堆栈(stack)上的值类型)、访问方式和一些特征(如结构不支持继承)。

较小的数据类型使用结构可提高性能。

但在语法上,结构与类非常相似,主要的区别是使用关键字struct代替class来声明结构。

例如,如果希望所有的PhoneCustomer实例都存储在堆栈上,而不是存储在托管堆上,就可以编写下面的语句:

structPhoneCustomerStruct

{

publicconststringDayOfSendingBill="Monday";

publicintCustomerID;

publicstringFirstName;

publicstringLastName;

}

对于类和结构,都使用关键字new来声明实例:

这个关键字创建对象并对其进行初始化。

在下面的例子中,类和结构的字段值都默认为0:

PhoneCustomermyCustomer=newPhoneCustomer();    //worksforaclass

PhoneCustomerStructmyCustomer2=newPhoneCustomerStruct();//worksforastruct

在大多数情况下,类要比结构常用得多。

因此,我们先讨论类,然后指出类和结构的区别,以及选择使用结构而不使用类的特殊原因。

但除非特别说明,否则就可以假定用于类的代码也适用于结构。

3.2 类成员

类中的数据和函数称为类的成员。

Microsoft的正式术语对数据成员和函数成员进行了区分。

除了这些成员外,类还可以包含嵌套的类型(例如其他类)。

类中的所有成员都可以声明为public(此时可以在类的外部直接访问它们)或private(此时,它们只能由类中的其他代码来访问)。

与VisualBasic、C++和Java一样,C#在这个方面还有变化,例如protected(表示成员仅能由该成员所在的类及其派生类访问),第4章将详细解释各种访问级别。

3.2.1 数据成员

数据成员包含了类的数据--字段、常量和事件。

数据成员可以是静态数据(与整个类相关)或实例数据(类的每个实例都有它自己的数据副本)。

通常,对于面向对象的语言,类成员总是实例成员,除非用static进行了显式的声明。

字段是与类相关的变量。

在前面的例子中已经使用了PhoneCustomer类中的字段。

一旦实例化PhoneCustomer对象,就可以使用语法Object.FieldName来访问这些字段:

PhoneCustomerCustomer1=newPhoneCustomer();

Customer1.FirstName="Simon";

常量与类的关联方式同变量与类的关联方式一样。

使用const关键字来声明常量。

如果它们声明为public,就可以在类的外部访问。

classPhoneCustomer

{

publicconststringDayOfSendingBill="Monday";

publicintCustomerID;

publicstringFirstName;

publicstringLastName;

}

事件是类的成员,在发生某些行为(例如改变类的字段或属性,或者进行了某种形式的用户交互操作)时,它可以让对象通知调用程序。

客户可以包含所谓"事件处理程序"的代码来响应该事件。

第7章将详细介绍事件。

3.2.2 函数成员

函数成员提供了操作类中数据的某些功能,包括方法、属性、构造函数和终结器(finalizer)、运算符以及索引器。

方法是与某个类相关的函数,它们可以是实例方法,也可以是静态方法。

实例方法处理类的某个实例,静态方法提供了更一般的功能,不需要实例化一个类(例如Console.WriteLine()方法)。

下一节介绍方法。

属性是可以在客户机上访问的函数组,其访问方式与访问类的公共字段类似。

C#为读写类上的属性提供了专用语法,所以不必使用那些名称中嵌有Get或Set的偷工减料的方法。

因为属性的这种语法不同于一般函数的语法,在客户代码中,虚拟的对象被当做实际的东西。

构造函数是在实例化对象时自动调用的函数。

它们必须与所属的类同名,且不能有返回类型。

构造函数用于初始化字段的值。

终结器类似于构造函数,但是在CLR检测到不再需要某个对象时调用。

它们的名称与类相同,但前面有一个~符号。

C++程序员应注意,终结器在C#中比在C++中用得少得多,因为CLR会自动进行垃圾收集,另外,不可能预测什么时候调用终结器。

第12章将介绍终结器。

运算符执行的最简单的操作就是+和-。

在对两个整数进行相加操作时,严格地说,就是对整数使用+运算符。

C#还允许指定把已有的运算符应用于自己的类(运算符重载)。

第6章将详细论述运算符。

索引器允许对象以数组或集合的方式进行索引。

第6章介绍索引器。

1.方法

在VisualBasic、C和C++中,可以定义与类完全不相关的全局函数,但在C#中不能这样做。

在C#中,每个函数都必须与类或结构相关。

注意,正式的C#术语实际上区分了函数和方法。

在这个术语中,"函数成员"不仅包含方法,而且也包含类或结构的一些非数据成员,例如索引器、运算符、构造函数和析构函数等,甚至还有属性。

这些都不是数据成员,字段、常量和事件才是数据成员。

(1)方法的声明

在C#中,定义方法的语法与C风格的语言相同,与C++和Java中的语法也相同。

与C++的主要语法区别是,在C#中,每个方法都单独声明为public或private,不能使用public:

块把几个方法定义组合起来。

另外,所有的C#方法都在类定义中声明和定义。

在C#中,不能像在C++中那样把方法的实现代码分隔开来。

在C#中,方法的定义包括方法的修饰符(例如方法的可访问性)、返回值的类型,然后是方法名、输入参数的列表(用圆括号括起来)和方法体(用花括号括起来)。

[modifiers]return_typeMethodName([parameters])

{

//Methodbody

}

每个参数都包括参数的类型名及在方法体中的引用名称。

但如果方法有返回值,return语句就必须与返回值一起使用,以指定出口点,例如:

publicboolIsSquare(Rectanglerect)

{

return(rect.Height==rect.Width);

}

这段代码使用了一个表示矩形的.NET基类System.Drawing.Rectangle。

如果方法没有返回值,就把返回类型指定为void,因为不能省略返回类型。

如果方法不带参数,仍需要在方法名的后面写上一对空的圆括号()(就像本章前面的Main()方法)。

此时return语句就是可选的--当到达右花括号时,方法会自动返回。

注意方法可以包含任意多个return语句:

publicboolIsPositive(intvalue)

{

if(value<0)

returnfalse;

returntrue;

}

(2)调用方法

C#中调用方法的语法与C++和Java中的一样,C#和VisualBasic的唯一区别是在C#中调用方法时,必须使用圆括号,这要比VisualBasic6中有时需要括号,有时不需要括号的规则简单一些。

下面的例子MathTest说明了类的定义和实例化、方法的定义和调用的语法。

除了包含Main()方法的类之外,它还定义了类MathTest,该类包含两个方法和一个字段。

usingSystem;

namespaceWrox.ProCSharp.MathTestSample

{

classMainEntryPoint

{

staticvoidMain()

{

//Trycallingsomestaticfunctions

Console.WriteLine("Piis"+MathTest.GetPi());

intx=MathTest.GetSquareOf(5);

Console.WriteLine("Squareof5is"+x);

        //InstantiateatMathTestobject

MathTestmath=newMathTest(); //thisisC#'swayof

 //instantiatingareferencetype

//Callnon-staticmethods

math.value=30;

Console.WriteLine(

"Valuefieldofmathvariablecontains"+math.value);

Console.WriteLine("Squareof30is"+math.GetSquare());

}

}

  //DefineaclassnamedMathTestonwhichwewillcallamethod

classMathTest

{

publicintvalue;

     publicintGetSquare()

{

returnvalue*value;

}

     publicstaticintGetSquareOf(intx)

{

returnx*x;

}

     publicstaticdoubleGetPi()

{

return3.14159;

}

}

}

运行mathTest示例,会得到如下结果:

cscMathTest.cs

Microsoft(R)VisualC#Compilerversion9.00.20404

forMicrosoft(R).NETFrameworkversion3.5

Copyright(C)MicrosoftCorporation.Allrightsreserved.

MathTest.exe

Piis3.14159

Squareof5is25

Valuefieldofmathvariablecontains30

Squareof30is900

从代码中可以看出,MathTest类包含一个字段和一个方法,该字段包含一个数字,该方法计算数字的平方。

这个类还包含两个静态方法,一个返回pi的值,另一个计算作为参数传入的数字的平方。

这个类有一些功能并不是C#程序设计的好例子。

例如,GetPi()通常作为const字段来执行,而好的设计应使用目前还没有介绍的概念。

C++和Java开发人员应很熟悉这个例子的大多数语法。

如果您有VisualBasic的编程经验,只需把MathTest类看作一个执行字段和方法的VisualBasic类模块。

但无论使用什么语言,都要注意两个要点。

(3)给方法传递参数

参数可以通过引用或值传递给方法。

在变量通过引用传递给方法时,被调用的方法得到的就是这个变量,所以在方法内部对变量进行的任何改变在方法退出后仍旧发挥作用。

而如果变量是通过值传送给方法的,被调用的方法得到的是变量的一个副本,也就是说,在方法退出后,对变量进行的修改会丢失。

对于复杂的数据类型,按引用传递的效率更高,因为在按值传递时,必须复制大量的数据。

在C#中,所有的参数都是通过值来传递的,除非特别说明。

这与C++是相同的,但与VisualBasic相反。

但是,在理解引用类型的传递过程时需要注意。

因为引用类型的对象只包含对象的引用,它们只给方法传递这个引用,而不是对象本身,所以对底层对象的修改会保留下来。

相反,值类型的对象包含的是实际数据,所以传递给方法的是数据本身的副本。

例如,int通过值传递给方法,方法对该int的值所作的任何改变都没有改变原int对象的值。

但如果数组或其他引用类型(如类)传递给方法,方法就会使用该引用改变这个数组中的值,而新值会反射到原来的数组对象上。

下面的例子ParameterTest.cs说明了这一点:

usingSystem;

namespaceWrox.ProCSharp.ParameterTestSample

{

classParameterTest

{

staticvoidSomeFunction(int[]ints,inti)

{

ints[0]=100;

i=100;

}

publicstaticintMain()

{

inti=0;

int[]ints={0,1,2,4,8};

//Displaytheoriginalvalues

Console.WriteLine("i="+i);

Console.WriteLine("ints[0]="+ints[0]);

Console.WriteLine("CallingSomeFunction...");

        //Afterthismethodreturns,intswillbechanged,

//butiwillnot

SomeFunction(ints,i);

Console.WriteLine("i="+i);

Console.WriteLine("ints[0]="+ints[0]);

return0;

}

}

}

结果如下:

cscParameterTest.cs

Microsoft(R)VisualC#Compilerversion9.00.20404

forMicrosoft(R).NETFrameworkversion3.5

Copyright(C)MicrosoftCorporation.Allrightsreserved.

ParameterTest.exe

i=0

ints[0]=0

CallingSomeFunction...

i=0

ints[0]=100

注意,i的值保持不变,而在ints中改变的值在原来的数组中也改变了。

注意字符串是不同的,因为字符串是不能改变的(如果改变字符串的值,就会创建一个全新的字符串),所以字符串无法采用一般引用类型的行为方式。

在方法调用中,对字符串所作的任何改变都不会影响原来的字符串。

这一点将在第8章详细讨论。

(4)ref参数

通过值传送变量是默认的,也可以迫使值参数通过引用传送给方法。

为此,要使用ref关键字。

如果把一个参数传递给方法,且这个方法的输入参数前带有ref关键字,则该方法对变量所作的任何改变都会影响原来对象的值:

staticvoidSomeFunction(int[]ints,refinti)

{

ints[0]=100;

i=100;      //thechangetoiwillpersistafterSomeFunction()exits

}

在调用该方法时,还需要添加ref关键字:

SomeFunction(ints,refi);

在C#中添加ref关键字等同于在C++中使用&语法指定按引用传递参数。

但是,C#在调用方法时要求使用ref关键字,使操作更明确(因此有助于防止错误)。

最后,C#仍要求对传递给方法的参数进行初始化,理解这一点也是非常重要的。

在传递给方法之前,无论是按值传递,还是按引用传递,变量都必须初始化。

(5)out关键字

在C风格的语言中,函数常常能从一个例程中输出多个值,这是使用输出参数实现的,只要把输出值赋给通过引用传递给方法的变量即可。

通常,变量通过引用传送的初值是不重要的,这些值会被函数重写,函数甚至从来没有使用过它们。

如果可以在C#中使用这种约定,就会非常方便。

但C#要求变量在被引用前必须用一个初值进行初始化。

尽管在把输入变量传递给函数前,可以用没有意义的值初始化它们,因为函数将使用真实、有意义的值初始化它们,但是这样做是没有必要的,有时甚至会引起混乱。

但有一种方法能够简化C#编译器所坚持的输入参数的初始化。

编译器使用out关键字来初始化。

在方法的输入参数前面加上out关键字时,传递给该方法的变量可以不初始化。

该变量通过引用传送,所以在从被调用的方法中返回时,方法对该变量进行的任何改变都会保留下来。

在调用该方法时,还需要使用out关键字,与在定义该方法时一样:

staticvoidSomeFunction(outinti)

{

i=100;

}

publicstaticintMain()

{

inti;//notehowiisdeclaredbutnotinitialized

SomeFunction(outi);

Console.WriteLine(i);

return0;

}

out关键字是C#中的新增内容,在VisualBasic和C++中没有对应的关键字,该关键字的引入使C#更安全,更不容易出错。

如果在函数体中没有给out参数分配一个值,该方法就不能编译。

(6)方法的重载

C#支持方法的重载--方法的几个有不同签名(方法名相同、但参数的个数和类型不同)的版本,但不支持C++或VisualBasic中的默认参数。

为了重载方法,只需声明同名但参数个数或类型不同的方法即可:

classResultDisplayer

{

voidDisplayResult(stringresult)

{

//implementation

}

  voidDisplayResult(intresult)

{

//implementation

}

}

因为C#不直接支持可选参数,所以需要使用方法重载来达到此目的:

classMyClass

{

intDoSomething(intx)  //want2ndparameterwithdefaultvalue10

{

DoSomething(x,10);

}

  intDoSomething(intx,inty)

{

//implementation

}

}

在任何语言中,对于方法重载来说,如果调用了错误的重载方法,就有可能出现运行错误。

第4章将讨论如何使代码避免这些错误。

现在,知道C#在重载方法的参数方面有一些小区别即可:

● 两个方法不能仅在返回类型上有区别。

● 两个方法不能仅根据参数是声明为ref还是out来区分。

2.属性

属性(property)不太常见,因为它们表示的概念是C#从VisualBasic中提取的,而不是从C++/Java中提取的。

属性的概念是:

它是一个方法或一对方法,在客户机代码看来,它们是一个字段。

例如Windows窗体的Height属性。

假定有下面的代码:

//mainFormisoftypeSystem.Windows.Form

mainForm.Height=400;

执行这段代码,窗口的高度设置为400,因此窗口会在屏幕上重新设置大小。

在语法上,上面的代码类似于设置一个字段,但实际上是调用了属性访问器,它包含的代码重新设置了窗体的大小。

在C#中定义属性,可以使用下面的语法:

publicstringSomeProperty

{

get

{

return"Thisisthepropertyvalue";

}

set

{

//dowhateverneedstobedonetosettheproperty

}

}

get访问器不带参数,且必须返回属性声明的类型。

也不应为set访问器指定任何显式参数,但编译器假定它带一个参数,其类型也与属性相同,并表示为value。

例如,下面的代码包含一个属性ForeName,它设置了一个字段foreName,该字段有一个长度限制。

privatestringforeName;

publicstringForeName

{

get

{

returnforeName;

}

set

{

if(value.Length>20)

//codeheretotakeerrorrecoveryaction

//(eg.throwanexception)

else

foreName=value;

}

}

注意这里的命名模式。

我们采用C#的区分大小写模式,使用相同的名称,但公共属性采用Pascal大小写命名规则,而私有属性采用camel大小写命名规则。

一些开发人员喜欢使用前面有下划线的字段名_foreName,这会为识别字段提供极大的便利。

VisualBasic6程序员应注意,C#不区分VisualBasic6中的Set和Let,在C#中,写入访问器总是用关键字set标识。

(1)只读和只写属性

在属性定义中省略set访问器,就可以创建只读属性。

因此,把上面例子中的ForeName变成只读属性:

privatestringforeName;

publicstringForeName

{

get

{

returnforeName;

}

}

同样

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 经管营销 > 经济市场

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1