
3.8 类和对象
3.8.1 类
在C#中,类是一种功能强大的数据类型,而且是面向对象的基础。类定义属性和行为,程序员可以声明类的实例,从而可以利用这些属性和行为。
类具有如下特点:
1)C#类只支持单继承,也就是类只能从一个基类继承实现。
2)一个类可以实现多个接口。
3)类定义可以在不同的源文件之间进行拆分。
4)静态类是仅包含静态方法的密封类。
类其实是创建对象的模板,类定义了每个对象可以包含的数据类型和方法,从而在对象中可以包含这些数据,并能够实现定义的功能。
类的声明的结构形式如下:

3.8.2 类的操作
1.定义类
类通常体现了现实中的某类事物,或者开发者要研究的对象。在C#中,几乎所有的程序代码都是在类中实现的。我们以现实生活中的“建筑物”为例,定义一个关于建筑物的类。考虑到建筑物的特点,建筑物应该包括名称、楼层数、面积、居住人数等基本信息。可以定义一个叫作Building的类以代表建筑物。包含的信息对应如下。

则可以初步定义类Building。

定义类Building就相当于创建了一个新的数据类型。可以声明对象了。

有了以上初始化操作,就可以计算人均面积等其他信息了。

同一个类定义的每个对象都包含一组类中定义的实际变量的副本。每个对象包含的内容可以和另一个对象完全不同(除了具有相同类型外)。它们之间也可以没有任何联系。

2.给Building类添加方法
类的方法通常对类中包含的数据执行某种操作,并提供对类中数据的访问。
areaPP:计算人均面积改成类中的方法。可以封装类中直接与建筑物相关的数量,从而增强该类的面向对象结构。

方法可以返回值,方法采用如下形式的return语句给调用程序返回一个值:

使用返回值改进AreaPerPerson()方法的实现。

也可以给Building类添加带参数的方法。假设每个居住者必须有一定的最小空间,那么由此可以计算出建筑物的最大居住人数。将该新方法命名为MaxOccupant()。

3.构造函数

以上必须使用一系列语句手动设置每个Building对象的实例变量。
而在专业人员编写的C#代码中,很少使用这种方式。一种原因是这种方式容易出错(有可能忘记设置某个字段的值),另一种原因是有一种更好的实现该任务的方式:构造函数。
构造函数用于在创建对象时初始化对象。它的名称与类相同。语法上类似于方法。但是,构造函数没有显式的返回值类型。以下是Building类的构造函数。

初始化类时使用如下格式:

4.析构函数
可以定义在垃圾回收程序最终销毁对象之前调用的方法,该方法称为析构函数,它可以用于一些非常特殊的情况,确保对象彻底地终止。
析构函数格式:

例如:~Building()

总结上面对类的说明和实例,在C#中,类可以包含如下几种成员:
1)字段,是被视为类的一部分的对象实例,通常用来保存类数据,一般为私有成员。
2)属性,是类中可以像类中的字段一样访问的方法。属性可以为类字段提供保护,避免字段在对象不知道的情况下被修改。
3)方法,定义类可以执行的操作。
4)事件,是向其他对象提供有关事件发生通知的一种方式,事件是使用委托来定义和触发的。
5)构造函数,是第一次创建对象时调用的方法,用来对对象进行初始化。
6)析构函数,是对象使用完毕后从内存中清理对象占用的资源,在C#中一般不需要明确定义析构函数,CLR会帮助解决内存的释放问题。
3.8.3 类的访问控制
对于类中的成员的访问,分为公有访问和私有访问两种:公有访问使用关键字:public,私有访问使用关键字:private。说明如下。
1)public:使用public说明符修饰类的成员时,该成员可以由程序中的任意其他代码访问,包括其他类中定义的方法。
2)private:使用private说明符修饰类的成员时,该成员只可以由它所在类中的其他成员访问。如果没有用任何访问说明符,类成员默认为private。
栈是面向对象编程的典型实例,因为它将信息的存储和访问这些信息的方法组合在一起。
栈存储的成员是私有的,访问方法是公有的,因此它是说明类的访问控制的最佳选择。
栈这种数据结构主要有以下这些基本操作。可以写出它们的实现代码如下。
(1)定义栈

(2)初始化栈(构造函数)

(3)进栈

(4)出栈

(5)判满栈

(6)判空栈

(7)返回栈大小

(8)返回元素数量

将上述栈的基本操作所对应的代码,放到定义栈的Class中,完成栈的完整定义。则可以在Main中调用“栈”这个类,实例化栈为一个对象,并进行各种栈的操作。
Main()调用的代码如下:

3.8.4 继承
1.继承概念和定义方法
继承是面向对象编程的一大特性,通过继承,类可以从其他类继承相关特性。被继承的类称为基类,而继承基类的类称为派生类。
派生类将获取基类的所有非私有数据和行为,以及派生类为自己定义的其他数据和行为。
继承的实现方式是:在声明类时,在类名称后放置一个冒号,然后在冒号后指定要从中继承的类。例如,类B从类A中继承,类A被称为基类,类B被称为派生类:

下面示例中的TwoDShape类存储二维对象(如正方形、三角形、矩形等)的高度和宽度:


定义派生类:

主函数调用:

2.成员访问和继承
类成员经常声明为私有的(private),以避免对它们执行未授权的访问和破坏。继承一个类不会破坏私有访问的限制。
派生类虽然包括基类的所有成员,但它不能访问基类中的私有成员。例如将TwoDShape类的Width和Height成员声明为私有,则Triangle类将不能访问它们。
私有类成员总保持对其所在类的私有性,它不能被类外部的任何代码访问,包括派生类。
访问私有成员的解决办法如下。
(1)使用protected成员
protected保护成员在类层次结构中是共有的,但在该层次之外是私有的。
(2)使用公有属性
改写基类TwoDShape添加属性:

3.构造函数和继承
基类和派生类都可以有自己的构造函数。问题是,到底哪个构造函数构件派生类的对象?答案是,基类的构造函数构造对象的基类部分,而派生类的构造函数构造对象的派生类部分。
4.虚方法和重写
虚方法是指在基类中声明为virtual并在一个或多个派生类中使用override重新定义的方法。重写体现了类的多态性。
3.8.5 Object类(System.Object)
C#定义了一个特殊的类Object,它是其他所有的类和类型的隐式基类。也就是说,所有其他类型都是从Object类派生的。这意味着Object类型的引用变量可以指向任何其他类型的对象。典型的应用为装箱和拆箱。下面为示例代码:

再举一个例子:创建一个Object数组,并给其中的元素赋予不同类型的值。

3.8.6 对象
世界是由许多不同种类的对象构成的,每一个对象都有自己的运动规律和状态。面向对象程序设计是用机器语言模拟客观世界的方法,与面向过程相比,所有的对象被赋予属性和方法,使编程更富有人性化。对于如何区分类、对象、属性和方法,下面用生活中的实物来举例说明。
高速上正在行驶的一辆红色的丰田轿车,乘车人数五人,可以用于基本交通使用,由于超速行驶,交警当场开了罚单,并登记了车牌号码、违规类型、开单日期和车主信息。如表3-26所示。
表3-26 对象的概念

1.类(Class)
类是对具有相同特征的一类事物所做的归纳。类的概念来源于人们认识自然、认识社会的过程,现实世界中的类是错综复杂、种类繁多的,聪明的人类学会了将复杂的事物进行分类。例如,由大小各异的汽车抽象出车的概念,由各式各样的鸟儿抽象出鸟类的概念等。
2.对象(Object)
一切皆为对象。客观世界由各种各样的实体组成,这些实体就称为对象,可以指具体事物也可以指抽象的事物,它是由数据加动作组成。对象是类的实例化,是实实在在存在的。例如,一只黄鹂鸟、一座房子,都是对象。对象是对具有某些特性的具体事物的抽象,对象之间的相互作用是通过发送消息进行的。
3.属性(Property)
属性就是对象状态的描述,每个对象具有各自的属性。例如,小猫的体重是1.5kg,椅子的颜色是红色,沙发的价格是2000元,这些都是它们的属性,体现了个体对象的性质。
4.方法(Method)
方法反映了对象本身可以完成的动作。例如一个学生,他会听课、做笔记、打篮球等,总结为一句话:方法就是行为。
5.事件(Event)
事件是指对象本身对外部变化所做出的响应。在表3-26中,可以看出,交警令司机停车,这就是一个外部变化,而对象做出的反应是停车,也就是说,停车的动作就是一个事件。
对象是类的实例化,只有对象才能包含数据、执行行为和触发事件,而类只不过就像int一样是数据类型,只有实例化才能真正发挥作用。
对象具有以下特点:
1)C#中使用的全都是对象。
2)对象是实例化的,对象是从类和结构所定义的模板中创建的。
3)对象使用属性获取和更改它们所包含的信息。
4)对象通常具有允许它们执行操作的方法和事件。
5)所有C#对象都继承自Object。
6)对象具有多态性,对象可以实现派生类和基类的数据和行为。
对象的声明就是类的实例化,传递回该对象的引用。此引用引用了新对象,但不包含对象数据本身。
类实例化的方式很简单,通过使用new来实现。例如:
