《.net编程先锋C#》第五章 类(转)

编程

第五章 类
前一章讨论了数据类型和它们的用法。现在我们转移到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);
例子的要点为:通过显式地利用基类的资格检查,你就能轻而易举地调用基类实现改写方法。
当你需要实现其在基类中的功能,而不愿意在改写方法中重复它时,这就非常有帮助。 

时间: 2024-09-16 15:53:38

《.net编程先锋C#》第五章 类(转)的相关文章

> 第五章 类(rainbow 翻译)*1 (来自重粒子空间)

<<展现C#>> 第五章 类(rainbow 翻译)出处:http://www.informit.com\matter\ser0000002正文:第五章  类       前一章讨论了数据类型和它们的用法.现在我们转移到C#中至关重要的结构--类.没有了类,就连简单的C#程序都不能编译.这一章假定你知道了一个类的基本组成部分:方法.属性.构造函数和析构函数. C#在其中增加了索引和事件.       在这一章中,你学到下列有关类的话题.      . 使用构造函数和析构函数    

《.net编程先锋C#》第一章 C#简介(转)

编程 第一章 C# 简介 欢迎您加入C#的世界! 这一章将把您引进C#的天地,并回答一些相关的问题,如:您为什么要使用C#,C++和C#的主要有什么不同点,以及为什么C#使开发更容易而且还使您感到很有趣.为什么是另外一种编程语言? 必须回答的一个问题:当您已经使用C++或VB从事企业开发时,为什么还要学习另一种语言? 市场式的回答就是:"在企业计算领域,C#将会变成为用于编写"下一代窗口服务"(Next Generation Windows Services,简写为NGWS

《快学Scala》第五章 类

关于case class和普通class的区别,可以参考:https://www.iteblog.com/archives/1508.html 本文转自博客园xingoo的博客,原文链接:<快学Scala>第五章 类,如需转载请自行联系原博主.

《.net编程先锋C#》第五章 类(下)(转)

编程 5.2.3 方法屏蔽重定义方法的一个不同手段就是要屏蔽基类的方法.当从别人提供的类派生类时,这个功能特 别有价值.看清单 5.6,假设BaseClass由其他人所写,而你从它派生出 DerivedClass . 清单 5.6 Derived Class 实现一个没有包含于 Base Class中的方法 1: using System;2: 3: class BaseClass4: {5: }6: 7: class DerivedClass:BaseClass8: {9: public vo

&amp;gt; 第五章 类(rainbow 翻译)*2 (来自重粒子空间)

5.3  类属性    有两种途径揭示类的命名属性--通过域成员或者通过属性.前者是作为具有公共访问性的成员变量而被实现的:后者并不直接回应存储位置,只是通过 存取标志(accessors)被访问.    当你想读出或写入属性的值时,存取标志限定了被实现的语句.用于读出属性的值的存取标志记为关键字get,而要修改属性的值的读写符标志记为set.在你对该理论一知半解以前,请看一下清单5.9中的例子,属性SquareFeet被标上了get和set的存取标志.清单 5.9  实现属性存取标志 1: u

《.net编程先锋C#》第二章 理论基础-公用语言 运行环境(转)

编程 第二章 理论基础-公用语言 运行环境既然你已经具有了C#全面的印象,我也想让你了解NGWS runtime的全貌.C#依靠由NGWS提供的运行时:因此,有必要知道运行时如何工作,以及它背后所蕴含的概念.所以,这一章分为两部分--它们是所有的概念和使用的基础.两部分的内容虽然有些重叠,但它有助于加深理解正在学习的概念. 2.1 NGWS RuntimeNGWS和NGWS Runtime为你提供了一种运行时环境.该运行时管理执行代码,并提供了使编程更容易的服务.只要你的编译器支持这种运行时,你

《.net编程先锋C#》第九章 配置和调度(转)

编程 第九章 配置和调度在上一章,你学到如何创建一个通用语言运行时(CLR)组件,且如何在一个简单的测试应用程序中使用它.虽然CLR组件就要准备装载了,但你还是应该思考以下技术之一:.条件编译.文档注释.代码版本化 9.1 条件编译 没有代码的条件编译功能,我就不能继续工作.条件编译允许执行或包括基于某些条件的代码:例如,生成应用程序的一个查错(DEBUG)版本.演示(DEMO)版本或零售(RELEASE)版本.可能被包括或被执行的代码的例子为许可证代码. 屏幕保护或你出示的任何程序.在C#中,

《.net编程先锋C#》第四章 C#类型(转)

编程 第四章 C#类型 既然你知道了怎样创建一个简单的C#程序,我将会给你介绍C#的类型系统.在这一章中,你学到如何使用不同的值和引用类型,加框和消框机制能为你作些什么.尽管这一章的不侧重于例子,但你可以学到很多重要的信息,关于如何创建现成类型的程序.4.1 值类型 各种值类型总是含有相应该类型的一个值.C#迫使你初始化变量才能使用它们进行计算-变量没有初始化不会出问题,因为当你企图使用它们时,编译器会告诉你. 每当把一个值赋给一个值类型时,该值实际上被拷贝了.相比,对于引用类型,仅是引用被拷贝

《.net编程先锋C#》第一章 C#简介

编程 第一章 C# 简介 欢迎您加入C#的世界! 这一章将把您引进C#的天地,并回答一些相关的问题,如:您为什么要使用C#,C++和C#的主要有什么不同点,以及为什么C#使开发更容易而且还使您感到很有趣.为什么是另外一种编程语言? 必须回答的一个问题:当您已经使用C++或VB从事企业开发时,为什么还要学习另一种语言? 市场式的回答就是:"在企业计算领域,C#将会变成为用于编写"下一代窗口服务"(Next Generation Windows Services,简写为NGWS