实验三 类与类的封装.docx
- 文档编号:30253095
- 上传时间:2023-08-13
- 格式:DOCX
- 页数:22
- 大小:34.99KB
实验三 类与类的封装.docx
《实验三 类与类的封装.docx》由会员分享,可在线阅读,更多相关《实验三 类与类的封装.docx(22页珍藏版)》请在冰豆网上搜索。
实验三类与类的封装
实验三类与类的封装
【实验目的与要求】
∙理解面向对象程序设计的思想和基本概念
∙使用构造函数和析构函数
∙给类写方法
∙给一个类增加属性
∙理解静态方法和实例方法之间的异同
∙掌握方法的重载
∙掌握类索引器的编写方式
∙掌握运算符的重载
【实验内容与步骤】
类是C#语言实现面向对象程序设计的基础,它是C#封装的基本单元,类把对象、属性和方法这些类成员封装在一起构成一个有机的整体,即数据机构。
在C#中,类是程序的最核心部分,没有了类,就连简单的C#程序都不能编译。
类是可以包含数据成员(常数和字段)、函数成员(方法、属性、事件、索引器、运算符、实例构造函数、静态构造函数和析构函数)以及嵌套类型的数据结构。
类类型支持继承,继承是派生类可用以扩展和专用化基类的机制。
一、类的声明
类的声明就是定义一个新类,其格式如下:
[属性][类修饰符]class标识符[:
基类]
{
类体
};
类声明包含一组可选的属性(C#使程序员可以创造新的说明性信息种类,称为属性),后跟一组可选的类修饰符(new;public;protected;internal;private;abstract;sealed),然后是关键字class和一个命名该类的标识符,接着是一个可选的继承基类,最后是可根据需要后接一个分号的类体。
二、类的成员
类的成员包括由它的类成员声明引入的成员和从直接基类继承的成员。
类的成员分为下列几种类别:
常数,表示与类相关联的常数值。
字段,类的变量。
方法,实现可由类执行的计算和操作。
属性,定义与读取和写入这些特性相关的命名特性。
事件,定义可由类生成的通知。
索引器,允许以与索引数组相同的方式索引类的实例。
运算符,定义可以应用于类的实例的表达式运算符。
实例构造函数,实现初始化类的实例所需要的操作。
析构函数,实现在永久性放弃类的实例之前要执行的操作。
静态构造函数,实现初始化类自身所需要的操作。
类型,表示类的局部类型。
可以包含可执行代码的成员被总称为类的函数成员。
类的函数成员是该类的方法、属性、事件、索引器、运算符、实例构造函数、析构函数和静态构造函数。
类声明创建新的声明空间,而由类声明包含的最近的类成员声明将新成员引入到此声明空间中。
下列规则适用于类成员声明:
实例构造函数、静态构造函数和析构函数必须具有与最近的封闭类相同的名称。
所有其他成员必须具有与最近封闭类不同的名称。
常数、字段、属性、事件或类型的名称必须不同于在同一个类中声明的所有其他成员的名称。
方法的名称必须不同于在同一个类中声明的所有其他非方法的名称。
此外,方法的签名必须不同于在同一个类中声明的所有其他方法的签名。
实例构造函数的签名必须不同于在同一个类中声明的所有其他实例构造函数的签名。
索引器的签名必须不同于在同一个类中声明的所有其他索引器的签名。
运算符的签名必须不同于在同一个类中声明的所有其他运算符的签名。
类的继承成员不是类的声明空间的组成部分。
因此,允许派生类用与继承成员相同的名称或签名来声明成员(这实质上隐藏了继承成员)。
三、构造函数与析构函数
在可以访问一个类的方法、属性或任何其它东西之前,第一条执行的语句是包含有相应类的构造函数。
甚至你自己不写一个构造函数,也会有一个缺省的构造函数提供给你。
classTestClass
{
publicTestClass():
base(){}//由编译器提供
}
一个构造函数总是和它的类名相同,但是,它没有声明返回类型。
总之,构造函数总是public的,你可以用它们来初始化变量。
publicTestClass()
{
//在这给变量
//初始化代码等等。
}
如果类仅包含静态成员(能以类型调用,而不是以实例调用的成员),你可以创建一个private的构造函数。
privateTestClass(){}
尽管存取修饰符在这一章的后面将要大篇幅地讨论,但是private意味着从类的外面不可能访问该构造函数。
所以,它不能被调用,且没有对象可以自该类定义被实例化。
并不仅限于无参数构造函数——你可以传递初始参数来初始化成员。
publicTestClass(stringstrName,intnAge){...}
作为一个C/C++程序员,你可能习惯于给初始化写一个附加的方法,因为在构造函数中没有返回值。
当然,尽管在C#中也没有返回值,但你可以引发一个自制的异常,以从构造函数获得返回值。
但是,当你保留引用给宝贵的资源,应该想到写一个方法来解决:
一个可以被显式地调用来释放这些资源。
问题是当你可以在析构函数(以类名的前面加"~"的方式命名)中做同样的事情时,为何还要写一个附加的方法.
~TestClass()
{
//清除
}
你应该写一个附加方法的原因是垃圾收集器,它在变量超出范围后并不会立即被调用,而仅当间歇期间或内存条件满足时才被触发。
当你锁住资源的时间长于你所计划的时间时,它就会发生。
因此,提供一个显式的释放方式是一个好主意,它同样能从析构函数中调用。
publicvoidRelease()
{
//释放所有宝贵的资源
}
~TestClass()
{
Release();
}
调用析构函数中的释放方法并不是必要的——总之,垃圾收集会留意释放对象。
但没有忘记清除是一种良好的习惯。
四、类方法
既然对象能正确地初始化和结束,所剩下来的就是往类中增加功能。
在大多数情况下,功能的主要部分在方法中能得到实现。
以往,对于每个程序来说,所有的工作都在Main()方法中实现。
这对于功能简单的程序是合适的,因为仅仅用来学习一些概念。
有个更好的方法来组织你的程序,那就是使用方法。
方法是很有用的,因为方法可以让你在不同的单元中分开设计你的逻辑模块。
方法的结构格式如下:
属性修饰符返回值类型方法名(参数)
{
方法体
……
[return表达式]
}
4.1.1值传递参数
我们在前面的例子中见过的一个参数就是值传递参数。
你用一个值传递参数通过值传递一个变量给一个方
法——方法的变量被调用者传递进来的值的一个拷贝初始化。
清单4.1示范值传递参数的使用。
清单4.1通过值传递参数
1:
usingSystem;
2:
3:
publicclassSquareSample
4:
{
5:
publicintCalcSquare(intnSideLength)
6:
{
7:
returnnSideLength*nSideLength;
8:
}
9:
}
10:
11:
classSquareApp
12:
{
13:
publicstaticvoidMain()
14:
{
15:
SquareSamplesq=newSquareSample();
16:
Console.WriteLine(sq.CalcSquare(25).ToString());
17:
}
18:
}
因为传递值而不是引用(地址)给一个变量,所以当调用方法时(见第16行),可以使用一个常量表达式(25)。
4.1.2引用参数
尽管可以利用值传递参数和返回值建立很多方法,但你一想到要传递值并原地修改它(也就是在相同的内存位置),就没有那么好操作了。
这里用引用参数就很方便。
voidmyMethod(refintnInOut)
因为传递了一个变量给该方法(不仅仅是它的值),变量必须被初始化。
否则,编译器会报警。
清单4.2显示如何用一个引用参数建立一个方法。
清单4.2通过引用传递参数
1:
//classSquareSample
2:
usingSystem;
3:
4:
publicclassSquareSample
5:
{
6:
publicvoidCalcSquare(refintnOne4All)
7:
{
8:
nOne4All*=nOne4All;
9:
}
10:
}
11:
12:
classSquareApp
13:
{
14:
publicstaticvoidMain()
15:
{
16:
SquareSamplesq=newSquareSample();
17:
18:
intnSquaredRef=20;//一定要初始化
19:
sq.CalcSquare(refnSquaredRef);
20:
Console.WriteLine(nSquaredRef.ToString());
21:
}
22:
}
正如所看到的,所有要做的就是给定义和调用都加上ref限定符。
因为变量通过引用传递,就可以用它来计算出结果并传回该结果。
但是,在现实的应用程序中,强烈建议要用两个变量,一个值传递参数和一个引用参数。
4.1.3输出参数
传递参数的第三种选择就是把它设作一个输出参数。
正如该名字所暗示,一个输出参数仅用于从方法传递回一个结果。
它和引用参数的另一个区别在于:
调用者不必先初始化变量才调用方法。
这显示在清单4.3中。
清单4.3定义一个输出参数
1:
usingSystem;
2:
3:
publicclassSquareSample
4:
{
5:
publicvoidCalcSquare(intnSideLength,outintnSquared)
6:
{
7:
nSquared=nSideLength*nSideLength;
8:
}
9:
}
10:
11:
classSquareApp
12:
{
13:
publicstaticvoidMain()
14:
{
15:
SquareSamplesq=newSquareSample();
16:
17:
intnSquared;//不必初始化
18:
sq.CalcSquare(15,outnSquared);
19:
Console.WriteLine(nSquared.ToString());
20:
}
21:
}
4.1.4数组参数的传递
当方法的参数前带有params关键字,这就是一个带数组型参数的方法.在方法的参数列表中使用params关键字,可用于表示方法的形参个数不确定,这样就可以在使用方法的过程中改变传入方法的实参个数.
带有关键字params的数组型参数必须是方法的参数列表中最后一个参数,否则会出现编译错误.
数组型参数中的数组必须是一维数组类型,如int[],string[][](锯齿型数组),但int[,]二维数组则不能用来作为数组参数.
示例3:
usingSystem;
publicclassParamTest
{
publicvoidpara(paramsint[]ary)//定义带数组型参数的方法成员
{
Console.Write("\n数组中包含{0}个元素:
",ary.Length);
for(intt=0;t { ary[t]++;//改变形参数组元素的值 Console.Write(“\t{0}”,ary[t]);//打印形参数组元素的值} } publicstaticvoidMain() { intm=1,n=2,p=3,q=4; ParamTestapp=newParamTest(); int[]testArr=newint[]{m,n,5}; Console.WriteLine("调用方法之前的数组元素值为: "); foreach(inttintestArr) { Console.Write(“\t{0}”,t);//打印形参数组元素的值 } app.para(testArr);//用数组名作为实参 Console.WriteLine("\n调用方法之后的数组元素值为: "); foreach(inttintestArr) { Console.Write("\t{0}",t);//打印形参数组元素的值 } app.para(m,n,p,q);//有4个实参与形参类型对应 Console.WriteLine(); } } 五、静态方法与实例方法 在类中,与上述实例方法相对应的是静态方法,在定义类的方法成员时带上Static修饰符,就可以将方法定义为静态方法。 另外注意,定义一个静态方法时,不能含有Virtual,abstract,和override修饰符。 静态方法与静态成员变量类似,虽然在类中定义它,但他不属于某个具体的实例。 静态方法只能访问类中的静态成员,而实例方法可以访问类中的所有成员。 示例1: publicclassstudent { staticintstudcount=0;//定义一个私有的静态变量成员,用于统计学生人数 publicstudent() { studcount++;//统计学生人数 } publicstaticintGetStudentCount()//定义一个静态方法 { return(studcount);//静态方法访问静态成员变量 } } classAppStud { publicstaticvoidMain() { studentstudent1=newstudent();//创建类的一个实例 Console.WriteLine("学生人数={0}",student.GetStudentCount()); //通过类名访问静态方法 studentstudent2=newstudent();//创建类的另一个实例 Console.WriteLine("学生人数={0}",student.GetStudentCount()); } } 六、方法重载 重载是面向对象程序设计的一个重要特征,通过重载可以使多个具有相同功能但参数不同的方法共享同一个方法名.这样将降低程序员的工作强度. 示例2: UsingSystem; Classprintclass { publicvoidprint(inti)//打印整数 { Console.WriteLine(“打印整数{0}”,i);} publicvoidprint(uinti)//打印整数 { Console.WriteLine(“打印整数{0}”,i); } Publicvoidprint(strings)//打印字符串 { Console.WriteLine(“打印字符串{0}”,s); } Publicvoidprint(paramsint[]arys)//打印数组 { foreach(intainarys) Console.Write(“0}\t”,a); } PublicstaticvoidMain() { int[]myary=newint[]{2,4,6,8,10}; Printclassapp=newprintclass(); App.print((int)6);//调用打印整数方法 App.print(uint)6);//调用打印无符号整数方法 App.print(“方法重载好处多! ”);//调用打印字符串方法 App.print(myary);//调用打印数组方法 } } 七、类属性 属性是提供对对象或类的特性的访问的成员。 属性的示例包括字符串的长度、字体的大小、窗口的标题、客户的名称,等等。 属性是字段的自然扩展——两者都是具有关联类型的命名成员,而且访问字段和属性的语法是相同的。 然而,与字段不同,属性不表示存储位置。 相反,属性有访问器,这些访问器指定要在它们的值被读取或写入时执行的语句。 因此属性提供将操作与对象属性的读取和写入相关联的机制;另外,它们还允许对此类属性进行计算。 属性声明: class类名 { [修饰符]数据类型属性名 { get{}; set{}; } 示例4: 本示例展示一个Person类,它有两个属性: Name(string)和Age(int)。 两个属性都是读/写属性。 //person.cs usingSystem; classPerson { privatestringmyName="N/A"; privateintmyAge=0; //DeclareaNamepropertyoftypestring: publicstringName { get { returnmyName; } set { myName=value; } } //DeclareanAgepropertyoftypeint: publicintAge { get { returnmyAge; } set { myAge=value; } } publicoverridestringToString() { return"Name="+Name+",Age="+Age; } publicstaticvoidMain() { Console.WriteLine("SimpleProperties"); //CreateanewPersonobject: Personperson=newPerson(); //Printoutthenameandtheageassociatedwiththeperson: Console.WriteLine("Persondetails-{0}",person); //Setsomevaluesonthepersonobject: person.Name="Joe"; person.Age=99; Console.WriteLine("Persondetails-{0}",person); //IncrementtheAgeproperty: person.Age+=1; Console.WriteLine("Persondetails-{0}",person); } } 输出 SimpleProperties Persondetails-Name=N/A,Age=0 Persondetails-Name=Joe,Age=99 Persondetails-Name=Joe,Age=100 代码讨论 请注意声明属性的方式,例如,考虑Name属性: publicstringName { get { returnmyName; } set { myName=value; } } 属性的“设置”(Set)方法和“获取”(Get)方法包含在属性声明中。 可以通过控制是否包含“获取”方法或“设置”方法来控制属性是读/写属性、只读属性还是只写属性。 声明了属性后,可像使用类的字段那样使用这些属性。 这使得获取和设置属性值时都可以使用非常自然的语法,如以下语句中所示: person.Name="Joe"; person.Age=99; 注意属性“设置”方法中可以使用一个特殊的value变量。 该变量包含用户指定的值,例如: myName=value; 请注意用于使Person对象上的Age属性递增的简洁语法: person.Age+=1; 如果使用单独的“设置”方法和“获取”方法建立属性模型,等效代码可能是: person.SetAge(person.GetAge()+1); 本示例中重写了ToString方法: publicoverridestringToString() { return"Name="+Name+",Age="+Age; } 注意程序中未显式使用ToString。 默认情况下,它由WriteLine调用来调用。 八、类索引 索引器是使对象能够用与数组相同的方式进行索引的成员。 索引器的工作方式类似于属性。 只是索引器用来访问类中的数组型对象元素,而属性用来被用来访问类中的私有变量成员,所以定义索引器时,也要使用get和set访问函数,不同的是使用索引器取得的是类实例中数组对象的各元素的值,而不是特定的数据成员。 定义属性时需要定义属性的名称,而定义索引器时不需要给出具体的名称,只需要使用关键字this,它用于引用当前的对象实例。 索引器的声明: [修饰符]数据类型this[intindex] { get访问器 索引器的get访问器体与方法体类似。 它返回索引器的类型。 get访问器使用与索引器相同的formal-in
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 实验三 类与类的封装 实验 封装