对象和类型.docx
- 文档编号:26037340
- 上传时间:2023-06-17
- 格式:DOCX
- 页数:36
- 大小:38.15KB
对象和类型.docx
《对象和类型.docx》由会员分享,可在线阅读,更多相关《对象和类型.docx(36页珍藏版)》请在冰豆网上搜索。
对象和类型
目录
第3章对象和类型2
3.1 类和结构2
3.2 类成员2
3.2.1 数据成员2
3.2.2 函数成员2
3.2.3 只读字段14
3.3 匿名类型16
3.4 结构16
3.4.1 结构是值类型17
3.4.2 结构和继承18
3.4.3 结构的构造函数18
3.5 部分类18
3.6 静态类19
3.7 Object类20
3.7.1 System.Object方法20
3.7.2 ToString()方法20
3.8 扩展方法22
3.9 小结22
第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大小写命名规则,而私有属性采用c
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 对象 类型
![提示](https://static.bdocx.com/images/bang_tan.gif)