分享
 
 
 

《展现 C#》(五)类

王朝c#·作者佚名  2006-11-24
窄屏简体版  字體: |||超大  

第五章 类

前一章讨论了数据类型和它们的用法。现在我们转移到C#中至关重要的结构——类。没有了类,就连简单的C#程序都不能编译。这一章假定你知道了一个类的基本组成部分:方法、属性、构造函数和析构函数。C#在其中增加了索引和事件。

在这一章中,你学到下列有关类的话题。

。使用构造函数和析构函数

。给类写方法

。给一个类增加属性存取标志

。实现索引

。创建事件并通过代表元为事件关联客户

。应用类、成员和存取修饰符。

5.1 构造函数和析构函数

在你可以访问一个类的方法、属性或任何其它东西之前, 第一条执行的语句是包含有相应类的构造函数。甚至你自己不写一个构造函数,也会有一个缺省的构造函数提供给你。

class TestClass

{

public TestClass(): base() {} // 由编译器提供

}

一个构造函数总是和它的类名相同,但是,它没有声明返回类型。总之,构造函数总是public的,你可以用它们来初始化变量。

public TestClass()

{

// 在这给变量

// 初始化代码等等。

}

如果类仅包含静态成员(能以类型调用,而不是以实例调用的成员),你可以创建一个private的构造函数。

private TestClass()

{

}

尽管存取修饰符在这一章的后面将要大篇幅地讨论,但是private意味着从类的外面不可能访问该构造函数。所以,它不能被调用,且没有对象可以自该类定义被实例化。

并不仅限于无参数构造函数——你可以传递初始参数来初始化成员。

public TestClass(string strName, int nAge) { ... }

作为一个C/C++程序员,你可能习惯于给初始化写一个附加的方法,因为在构造函数中没有返回值。当然,尽管在C#中也没有返回值,但你可以引发一个自制的异常,以从构造函数获得返回值。更多有关异常处理的知识在第七章 "异常处理"中有讨论。但是,当你保留引用给宝贵的资源,应该想到写一个方法来解决:一个可以被显式地调用来释放这些资源。问题是当你可以在析构函数(以类名的前面加"~"的方式命名)中做同样的事情时,为何还要写一个附加的方法.

public ~TestClass()

{

// 清除

}

你应该写一个附加方法的原因是垃圾收集器,它在变量超出范围后并不会立即被调用,而仅当间歇期间或内存条件满足时才被触发。当你锁住资源的时间长于你所计划的时间时,它就会发生。

因此,提供一个显式的释放方式是一个好主意,它同样能从析构函数中调用。

public void Release()

{

// 释放所有宝贵的资源

}

public ~TestClass()

{

Release();

}

调用析构函数中的释放方法并不是必要的——总之,垃圾收集会留意释放对象。但没有忘记清除是一种良好的习惯。

5.2 方法

既然对象能正确地初始化和结束,所剩下来的就是往类中增加功能。在大多数情况下,功能的主要部分在方法中能得到实现。你早已见过静态方法的使用,但是,这些是类型(类)的部分,不是实例(对象)。

为了让你迅速入门,我把这些方法的烦琐问题安排为三节:

。方法参数

。改写方法

。方法屏蔽

5.2.1 方法参数

因方法要处理更改数值,你多多少少要传递值给方法,并从方法获得返回值。以下三个部分涉及到由传递值和为调用者获取返回结果所引起的问题。

。输入参数

。引用参数

。输出参数

5.2.1.1 输入参数

你早已在例子中见过的一个参数就是输入参数。你用一个输入参数通过值传递一个变量给一个方法——方法的变量被调用者传递进来的值的一个拷贝初始化。清单5.1 示范输入参数的使用。

清单 5.1 通过值传递参数

1: using System;

2:

3: public class SquareSample

4: {

5: public int CalcSquare(int nSideLength)

6: {

7: return nSideLength*nSideLength;

8: }

9: }

10:

11: class SquareApp

12: {

13: public static void Main()

14: {

15: SquareSample sq = new SquareSample();

16: Console.WriteLine(sq.CalcSquare(25).ToString());

17: }

18: }

因为我传递值而不是引用给一个变量,所以当调用方法时(见第16行),可以使用一个常量表达式(25)。整型结果被传回给调用者作为返回值,它没有存到中间变量就被立即显示到屏幕上 。

输入参数按C/C++程序员早已习惯的工作方式工作。如果你来自VB,请注意没有能被编译器处理的隐式ByVal或ByRef——如果没有设定,参数总是用值传递。

这点似乎与我前面所陈述的有冲突:对于一些变量类型,用值传递实际上意味着用引用传递。

迷惑吗? 一点背景知识也不需要:COM中的东西就是接口,每一个类可以拥有一个或多个接口。一个接口只不过是一组函数指针,它不包含数据。重复该数组会浪费很多内存资源;所以,仅开始地址被拷贝给方法,它作为调用者,仍然指向接口的相同指针。那就是为什么对象用值传递一个引用。

5.2.1.2 引用参数

尽管可以利用输入参数和返回值建立很多方法,但你一想到要传递值并原地修改它(也就是在相同的内存位置),就没有那么好运了。这里用引用参数就很方便。

void myMethod(ref int nInOut)

因为你传递了一个变量给该方法(不仅仅是它的值),变量必须被初始化。否则,编译器会报警。

清单 5.2 显示如何用一个引用参数建立一个方法。

清单 5.2 通过引用传递参数

1: // class SquareSample

2: using System;

3:

4: public class SquareSample

5: {

6: public void CalcSquare(ref int nOne4All)

7: {

8: nOne4All *= nOne4All;

9: }

10: }

11:

12: class SquareApp

13: {

14: public static void Main()

15: {

16: SquareSample sq = new SquareSample();

17:

18: int nSquaredRef = 20; // 一定要初始化

19: sq.CalcSquare(ref nSquaredRef);

20: Console.WriteLine(nSquaredRef.ToString());

21: }

22: }

正如所看到的,所有你要做的就是给定义和调用都加上ref限定符。因为变量通过引用传递,你可以用它来计算出结果并传回该结果。但是,在现实的应用程序中,我强烈建议要用两个变量,一个输入参数和一个引用参数。

5.2.1.3 输出参数

传递参数的第三种选择就是把它设作一个输出参数。正如该名字所暗示,一个输出参数仅用于从方法传递回一个结果。它和引用参数的另一个区别在于:调用者不必先初始化变量才调用方法。这显示在清单5.3中。

清单 5.3 定义一个输出参数

1: using System;

2:

3: public class SquareSample

4: {

5: public void CalcSquare(int nSideLength, out int nSquared)

6: {

7: nSquared = nSideLength * nSideLength;

8: }

9: }

10:

11: class SquareApp

12: {

13: public static void Main()

14: {

15: SquareSample sq = new SquareSample();

16:

17: int nSquared; // 不必初始化

18: sq.CalcSquare(15, out nSquared);

19: Console.WriteLine(nSquared.ToString());

20: }

21: }

5.2.2 改写方法

面向对象设计的重要原则就是多态性。不要理会高深的理论,多态性意味着:当基类程序员已设计好用于改写的方法时,在派生类中,你就可以重定义(改写)基类的方法。基类程序员可以用virtual 关键字设计方法:

virtual void CanBOverridden()

当从基类派生时,所有你要做的就是在新方法中加入override关键字:

override void CanBOverridden()

当改写一个基类的方法时,你必须明白,不能改变方法的访问属性——在这章的后面,你会学到更多关于访问修饰符的知识。

除了改写基类方法的事实外,还有另一个甚至更重要的改写特性。当把派生类强制转换成基类类型并接着调用虚拟方法时,被调用的是派生类的方法而不是基类的方法。

((BaseClass)DerivedClassInstance).CanBOverridden();

为了演示虚拟方法的概念,清单 5.4 显示如何创建一个三角形基类,它拥有一个可以被改写的成员方法(ComputeArea)。

清单 5.4 改写一个基类的方法

1: using System;

2:

3: class Triangle

4: {

5: public virtual double ComputeArea(int a, int b, int c)

6: {

7: // Heronian formula

8: double s = (a + b + c) / 2.0;

9: double dArea = Math.Sqrt(s*(s-a)*(s-b)*(s-c));

10: return dArea;

11: }

12: }

13:

14: class RightAngledTriangle:Triangle

15: {

16: public override double ComputeArea(int a, int b, int c)

17: {

18: double dArea = a*b/2.0;

19: return dArea;

20: }

21: }

22:

23: class TriangleTestApp

24: {

25: public static void Main()

26: {

27: Triangle tri = new Triangle();

28: Console.WriteLine(tri.ComputeArea(2, 5, 6));

29:

30: RightAngledTriangle rat = new RightAngledTriangle();

31: Console.WriteLine(rat.ComputeArea(3, 4, 5));

32: }

33: }

基类Triangle定义了方法ComputeArea。它采用三个参数,返回一个double结果,且具有公共访问性。从Triangle类派生出的是RightAngledTriangle,它改写了ComputeArea 方法,并实现了自己的面积计算公式。两个类都被实例化,且在命名为TriangleTestApp的应用类的Main() 方法中得到验证。

我漏了解释第14行:

class RightAngledTriangle : Triangle

在类语句中冒号(:)表示RightAngledTriangle从类 Triangle派生。那就是你所必须要做的,以让C#知道你想把 Triangle当作RightAngledTriangle的基类。当仔细观察直角三角形的ComputeArea方法时,你会发现第3个参数并没有用于计算。但是,利用该参数就可以验证是否是“直角”。如清单5.5所示。

清单 5.5 调用基类实现

1: class RightAngledTriangle:Triangle

2: {

3: public override double ComputeArea(int a, int b, int c)

4: {

5: const double dEpsilon = 0.0001;

6: double dArea = 0;

7: if (Math.Abs((a*a + b*b - c*c)) > dEpsilon)

8: {

9: dArea = base.ComputeArea(a,b,c);

10: }

11: else

12: {

13: dArea = a*b/2.0;

14: }

15:

16: return dArea;

17: }

18: }

该检测简单地利用了毕达哥拉斯公式,对于直角三角形,检测结果必须为0。如果结果不为0,类就调用它基类的 ComputeArea来实现。

dArea = base.ComputeArea(a,b,c);

例子的要点为:通过显式地利用基类的资格检查,你就能轻而易举地调用基类实现改写方法。当你需要实现其在基类中的功能,而不愿意在改写方法中重复它时,这就非常有帮助。

5.2.3 方法屏蔽

重定义方法的一个不同手段就是要屏蔽基类的方法。当从别人提供的类派生类时,这个功能特别有价值。看清单 5.6,假设BaseClass由其他人所写,而你从它派生出 DerivedClass 。

清单 5.6 Derived Class 实现一个没有包含于 Base Class中的方法

1: using System;

2:

3: class BaseClass

4: {

5: }

6:

7: class DerivedClass:BaseClass

8: {

9: public void TestMethod()

10: {

11: Console.WriteLine("DerivedClass::TestMethod");

12: }

13: }

14:

15: class TestApp

16: {

17: public static void Main()

18: {

19: DerivedClass test = new DerivedClass();

20: test.TestMethod();

21: }

22: }

在这个例子中, DerivedClass 通过TestMethod()实现了一个额外的功能。但是,如果基类的开发者认为把TestMethod()放在基类中是个好主意,并使用相同的名字实现它时,会出现什么问题呢?(见清单5.7)

清单 5.7 Base Class 实现和 Derived Class相同的方法

1: class BaseClass

2: {

3: public void TestMethod()

4: {

5: Console.WriteLine("BaseClass::TestMethod");

6: }

7: }

8:

9: class DerivedClas

[1] [2] [3] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有