电脑爱好者,提供IT资讯信息及各类编程知识文章介绍,欢迎大家来本站学习电脑知识。 最近更新 | 联系我们 RSS订阅本站最新文章
电脑爱好者
站内搜索: 
当前位置:首页>> C#>>展现C#:第五章 类(1):

展现C#:第五章 类(1)

来源:网络 | 2007-5-3 | (有1663人读过)

      前一章讨论了数据类型和它们的用法。现在我们转移到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 DerivedClass:BaseClass 
10: { 
11:  public void TestMethod() 
12:  { 
13:   Console.WriteLine("DerivedClass::TestMethod"); 
14:  } 
15: } 

在优秀的编程语言中,你现在会遇到一个真正的大麻烦。但是,C#会给你提出警告: 
hiding2.cs(13,14): warning CS0114: ’DerivedClass.TestMethod()’ hides inherited member ’BaseClass.TestMethod 
()’. To make the current method override that implementation, add the override keyword. Otherwise add the  
new keyword. 
(hiding2.cs(13,14):警告  CS0114:’DerivedClass.TestMethod()’ 屏蔽了所继承的成员’BaseClass.TestMethod()’。要 
想使当前方法改写原来的实现,加上 override关键字。否则加上新的关键字。) 
具有了修饰符new,你就可以告诉编译器,不必重写派生类或改变使用到派生类的代码,你的方法就能屏蔽新加入的基类方 
法。**5.8  显示如何在例子中运用new修饰符。 

**  5.8   屏蔽基类方法 

1: class BaseClass 
2: { 
3:  public void TestMethod() 
4:  { 
5:   Console.WriteLine("BaseClass::TestMethod"); 
6:  } 
7: } 
8:  
9: class DerivedClass:BaseClass 
10: { 
11:  new public void TestMethod() 
12:  { 
13:   Console.WriteLine("DerivedClass::TestMethod"); 
14:  } 
15: } 

使用了附加的new修饰符,编译器就知道你重定义了基类的方法,它应该屏蔽基类方法。但是,如果你按以下方式编写: 
DerivedClass test = new DerivedClass(); 
((BaseClass)test).TestMethod(); 
  基类方法的实现就被调用了。这种行为不同于改写方法,后者保证大部分派生方法获得调用。 
C#热门文章排行
网站赞助商
购买此位置

 

关于我们 | 网站地图 | 文档一览 | 友情链接| 联系我们

Copyright © 2003-2024 电脑爱好者 版权所有 备案号:鲁ICP备09059398号