Web应用开发技术与案例教程
上QQ阅读APP看书,第一时间看更新

4.5 类和结构

C#支持面向对象的所有关键概念:封装、继承和多态性等。整个C#的类模型建立在.NET虚拟对象系统之上,对象模型是.NET Framework基础架构的一部分,而不再是编程语言的一部分。

4.5.1 定义类和结构

类和结构都可以包含构造函数、常数、字段、方法、属性、索引器、运算符、事件和嵌套类型等,但结构是值类型,而类是引用类型。在使用与声明类和结构时,需要注意下面几点。

● 不能声明结构的默认(无参数)构造函数,系统总是提供默认构造函数将结构成员初始化为它们的默认值,不需要再显式地声明。

● 在结构中,不能初始化实例字段。

● 结构不能像类那样继承。

● C#仅允许单个继承,也就是说类只能从一个基类继承实现。但是一个类可以实现一个以上的接口,即类最多继承一个类,但可以继承多个接口。

● 结构类型永远不会是抽象的(抽象的概念将在4.5.6节介绍)。

● 使用new运算符创建结构对象时,将创建该对象的实例,并且调用适当的构造函数。

下面是一个定义类和结构的例子。

978-7-111-63649-6-Chapter04-72.jpg

978-7-111-63649-6-Chapter04-73.jpg

程序中分别创建了一个NameClass和一个NameStruct实例,并分别调用了SetName()方法设值和GetName()方法取值。运行结果为:

978-7-111-63649-6-Chapter04-74.jpg

在此例中,类和结构还没有太大的差别。

4.5.2 定义属性

类和结构都可以定义属性。属性的定义通常包含两个部分。

● 专用数据成员的定义。

● 使用属性声明语法对公共属性进行的定义。该语法通过get和set访问函数将专用数据成员和公共属性关联起来。

在定义属性时,不能申明为void类型,即属性必须具有一个名称和一个类型。set函数常使用关键字value,value的类型必须同它被分配到的属性的声明类型相同。

下面的程序段为类定义了一个Name属性。

978-7-111-63649-6-Chapter04-75.jpg

属性定义中通常包含专用数据成员,但这不是必须的。get访问器不用访问专用数据成员也可以返回值。

4.5.3 定义索引器

索引器允许类或结构的实例按照与数组相同的方式进行索引。索引器类似于属性,和前述属性的区别在于索引器可以带有参数。this关键字用于定义索引器。

下面是一个使用索引器的例子。

978-7-111-63649-6-Chapter04-76.jpg

978-7-111-63649-6-Chapter04-77.jpg

运行结果为:

978-7-111-63649-6-Chapter04-78.jpg

如果将程序中被注释掉的语句恢复,再次执行时将会引发异常。类IntArr中定义了一个整数数组buf来保存数据,并定义了索引器来访问buf,如果索引超出数组的范围,则抛出异常。

本例中索引器的参数为数组的下标。这只是使用索引器的一种情况,其实索引器不必根据整数值进行索引,完全可以由用户自行定义特殊的查找机制。

4.5.4 方法重载

重载的概念本书不再介绍,仅用一个例子演示方法的重载。

978-7-111-63649-6-Chapter04-79.jpg

978-7-111-63649-6-Chapter04-80.jpg

程序中定义了两个类:Student和GoodStudent。GoodStudent从Student继承。Student中定义了方法SetMess (),在GoodStudent中重新实现(重载)了该方法。

运行结果为:

978-7-111-63649-6-Chapter04-81.jpg

4.5.5 使用ref和out类型参数

ref关键字使参数按引用方式传递。若要使用ref类型的参数,只能将变量作为ref参数显式地传递到被调函数。在调用函数之前,ref参数必须先初始化。当从被调函数退出时,在被调函数中对参数所做的任何更改都将反映在该变量中。

与ref类似,out关键字同样使参数按引用方式传递。不同之处在于:ref参数要求变量必须在传递之前进行初始化,而out参数则不必;out参数必须在被调函数内、函数结束之前,即传出值之前初始化,而ref参数则不必。请看下面实例。

978-7-111-63649-6-Chapter04-82.jpg

运行结果为:

978-7-111-63649-6-Chapter04-83.jpg

4.5.6 抽象类和接口

抽象类是用abstract修饰符修饰的类,表示该类是不完整的,它只能用作基类。抽象类与非抽象类在以下方面是不同的。

● 抽象类不能直接实例化,对抽象类使用new操作符会编译出错。虽然一些变量和值在编译时的类型可以是抽象的,但是这样的变量和值必须或者为null,或者含有对非抽象类的实例的引用。

● 允许(但不要求)抽象类包含抽象成员。

● 抽象类不能被密封(密封的类不能被继承)。

当从抽象类派生非抽象类时,非抽象类必须真正实现所继承的所有抽象成员(重写那些抽象成员),例如:

978-7-111-63649-6-Chapter04-84.jpg

抽象类A引入抽象方法F。类B引入另一方法G,但由于它不提供F的实现,B也必须声明为抽象类。类C重写F,并提供一个具体实现。由于C中没有抽象成员,因此可以(但并非必须)声明为非抽象类。

接口定义一个协定,实现某接口的类或结构必须遵守该接口定义的协定,一个类或结构可以实现多个接口。

在.NET Framework中,接口可以包含方法、属性、事件和索引器。接口本身不提供它所定义的成员的实现。接口只指定实现该接口的类或结构必须提供的成员,实现接口的任何类都必须提供接口中所声明的抽象成员的定义。