4.5 类和结构
C#支持面向对象的所有关键概念:封装、继承和多态性等。整个C#的类模型建立在.NET虚拟对象系统之上,对象模型是.NET Framework基础架构的一部分,而不再是编程语言的一部分。
4.5.1 定义类和结构
类和结构都可以包含构造函数、常数、字段、方法、属性、索引器、运算符、事件和嵌套类型等,但结构是值类型,而类是引用类型。在使用与声明类和结构时,需要注意下面几点。
● 不能声明结构的默认(无参数)构造函数,系统总是提供默认构造函数将结构成员初始化为它们的默认值,不需要再显式地声明。
● 在结构中,不能初始化实例字段。
● 结构不能像类那样继承。
● C#仅允许单个继承,也就是说类只能从一个基类继承实现。但是一个类可以实现一个以上的接口,即类最多继承一个类,但可以继承多个接口。
● 结构类型永远不会是抽象的(抽象的概念将在4.5.6节介绍)。
● 使用new运算符创建结构对象时,将创建该对象的实例,并且调用适当的构造函数。
下面是一个定义类和结构的例子。
程序中分别创建了一个NameClass和一个NameStruct实例,并分别调用了SetName()方法设值和GetName()方法取值。运行结果为:
在此例中,类和结构还没有太大的差别。
4.5.2 定义属性
类和结构都可以定义属性。属性的定义通常包含两个部分。
● 专用数据成员的定义。
● 使用属性声明语法对公共属性进行的定义。该语法通过get和set访问函数将专用数据成员和公共属性关联起来。
在定义属性时,不能申明为void类型,即属性必须具有一个名称和一个类型。set函数常使用关键字value,value的类型必须同它被分配到的属性的声明类型相同。
下面的程序段为类定义了一个Name属性。
属性定义中通常包含专用数据成员,但这不是必须的。get访问器不用访问专用数据成员也可以返回值。
4.5.3 定义索引器
索引器允许类或结构的实例按照与数组相同的方式进行索引。索引器类似于属性,和前述属性的区别在于索引器可以带有参数。this关键字用于定义索引器。
下面是一个使用索引器的例子。
运行结果为:
如果将程序中被注释掉的语句恢复,再次执行时将会引发异常。类IntArr中定义了一个整数数组buf来保存数据,并定义了索引器来访问buf,如果索引超出数组的范围,则抛出异常。
本例中索引器的参数为数组的下标。这只是使用索引器的一种情况,其实索引器不必根据整数值进行索引,完全可以由用户自行定义特殊的查找机制。
4.5.4 方法重载
重载的概念本书不再介绍,仅用一个例子演示方法的重载。
程序中定义了两个类:Student和GoodStudent。GoodStudent从Student继承。Student中定义了方法SetMess (),在GoodStudent中重新实现(重载)了该方法。
运行结果为:
4.5.5 使用ref和out类型参数
ref关键字使参数按引用方式传递。若要使用ref类型的参数,只能将变量作为ref参数显式地传递到被调函数。在调用函数之前,ref参数必须先初始化。当从被调函数退出时,在被调函数中对参数所做的任何更改都将反映在该变量中。
与ref类似,out关键字同样使参数按引用方式传递。不同之处在于:ref参数要求变量必须在传递之前进行初始化,而out参数则不必;out参数必须在被调函数内、函数结束之前,即传出值之前初始化,而ref参数则不必。请看下面实例。
运行结果为:
4.5.6 抽象类和接口
抽象类是用abstract修饰符修饰的类,表示该类是不完整的,它只能用作基类。抽象类与非抽象类在以下方面是不同的。
● 抽象类不能直接实例化,对抽象类使用new操作符会编译出错。虽然一些变量和值在编译时的类型可以是抽象的,但是这样的变量和值必须或者为null,或者含有对非抽象类的实例的引用。
● 允许(但不要求)抽象类包含抽象成员。
● 抽象类不能被密封(密封的类不能被继承)。
当从抽象类派生非抽象类时,非抽象类必须真正实现所继承的所有抽象成员(重写那些抽象成员),例如:
抽象类A引入抽象方法F。类B引入另一方法G,但由于它不提供F的实现,B也必须声明为抽象类。类C重写F,并提供一个具体实现。由于C中没有抽象成员,因此可以(但并非必须)声明为非抽象类。
接口定义一个协定,实现某接口的类或结构必须遵守该接口定义的协定,一个类或结构可以实现多个接口。
在.NET Framework中,接口可以包含方法、属性、事件和索引器。接口本身不提供它所定义的成员的实现。接口只指定实现该接口的类或结构必须提供的成员,实现接口的任何类都必须提供接口中所声明的抽象成员的定义。